faaso/src/faaso.cr

172 lines
5.1 KiB
Crystal
Raw Normal View History

require "commander"
2024-06-28 20:42:10 +00:00
require "docr"
require "docr/utils.cr"
2024-06-28 19:24:52 +00:00
require "file_utils"
require "uuid"
require "./funko.cr"
2024-06-29 15:29:53 +00:00
# FIXME make it configurable
REPO = "localhost:5000"
# Functions as a Service, Ops!
2024-06-28 15:41:21 +00:00
module Faaso
VERSION = "0.1.0"
module Commands
class Build
@arguments : Array(String) = [] of String
@options : Commander::Options
def initialize(options, arguments)
@options = options
@arguments = arguments
end
def run
funkos = Funko.from_paths(@arguments)
funkos.each do |funko|
# Create temporary build location
tmp_dir = Path.new("tmp", UUID.random.to_s)
Dir.mkdir_p(tmp_dir) unless File.exists? tmp_dir
# Copy runtime if requested
if !funko.runtime.nil?
runtime_dir = Path.new("runtimes", funko.runtime.to_s)
if !File.exists? runtime_dir
puts "Error: runtime #{funko.runtime} not found"
next
end
Dir.glob("#{runtime_dir}/*").each { |src|
FileUtils.cp_r(src, tmp_dir)
}
2024-06-28 19:24:52 +00:00
end
# Copy funko
if funko.path.empty?
puts "Internal error: empty funko path for #{funko.name}"
next
end
Dir.glob("#{funko.path}/*").each { |src|
FileUtils.cp_r(src, tmp_dir)
}
puts "Building function... #{funko.name} in #{tmp_dir}"
slug = funko.name
# FIXME: this should be configurable
2024-06-29 15:29:53 +00:00
repo = REPO
tag = "#{repo}/#{funko.name}:latest"
2024-06-28 20:42:10 +00:00
docker_api = Docr::API.new(Docr::Client.new)
docker_api.images.build(
context: tmp_dir.to_s,
tags: [tag, "#{funko.name}:latest"]) { }
2024-06-28 22:15:42 +00:00
puts "Pushing to repo as #{tag}"
docker_api.images.tag(repo: repo, name: slug, tag: "latest")
2024-06-28 23:07:27 +00:00
# FIXME: pushing is broken because my test registry has no auth
# docker_api.images.push(name: slug, tag: "latest", auth: "")
end
end
end
class Up
@arguments : Array(String) = [] of String
@options : Commander::Options
def initialize(options, arguments)
@options = options
@arguments = arguments
end
def run
2024-06-29 15:29:53 +00:00
funkos = Funko.from_paths(@arguments)
funkos.each do |funko|
repo = REPO
tag = "#{repo}/#{funko.name}:latest"
2024-06-28 23:07:27 +00:00
docker_api = Docr::API.new(Docr::Client.new)
# Pull image from registry
docker_api.images.create(image: tag)
2024-06-29 15:29:53 +00:00
2024-06-28 23:07:27 +00:00
containers = docker_api.containers.list(all: true)
# If it's running, do nothing
2024-06-29 15:29:53 +00:00
if containers.any? { |container|
container.@image == tag && container.@state == "running"
2024-06-28 23:57:58 +00:00
}
2024-06-29 15:29:53 +00:00
puts "#{funko.name} is already running"
next
2024-06-28 23:07:27 +00:00
end
2024-06-28 23:57:04 +00:00
# If it is paused, unpause it
2024-06-29 15:29:53 +00:00
paused = containers.select { |container|
container.@image == tag && container.@state == "paused"
2024-06-28 23:57:04 +00:00
}
2024-06-28 23:57:58 +00:00
if paused.size > 0
2024-06-28 23:57:04 +00:00
puts "Resuming existing paused container"
docker_api.containers.unpause(paused[0].@id)
2024-06-29 15:29:53 +00:00
next
2024-06-28 23:57:04 +00:00
end
2024-06-28 23:07:27 +00:00
# If it is exited, start it
2024-06-29 15:29:53 +00:00
existing = containers.select { |container|
container.@image == tag && container.@state == "exited"
2024-06-28 23:07:27 +00:00
}
2024-06-29 15:29:53 +00:00
puts "Starting function #{funko.name}"
2024-06-28 23:57:58 +00:00
if existing.size > 0
2024-06-28 23:07:27 +00:00
puts "Restarting existing exited container"
docker_api.containers.start(existing[0].@id)
2024-06-29 15:29:53 +00:00
next
2024-06-28 23:07:27 +00:00
end
2024-06-28 23:57:04 +00:00
# Creating from scratch
puts "Creating new container"
conf = Docr::Types::CreateContainerConfig.new(
image: tag,
2024-06-29 15:29:53 +00:00
hostname: funko.name,
# Port in the container side
2024-06-29 15:29:53 +00:00
exposed_ports: {"#{funko.port}/tcp" => {} of String => String},
host_config: Docr::Types::HostConfig.new(
2024-06-29 15:29:53 +00:00
port_bindings: {"#{funko.port}/tcp" => [Docr::Types::PortBinding.new(
host_port: "", # Host port, empty means random
host_ip: "127.0.0.1", # Host IP
)]}
)
2024-06-28 23:57:04 +00:00
)
2024-06-29 15:29:53 +00:00
2024-06-28 23:57:04 +00:00
# FIXME: name should be unique
response = docker_api.containers.create(name: "fungus", config: conf)
2024-06-29 15:35:03 +00:00
response.@warnings.each { |msg| puts "Warning: #{msg}" }
container_id = response.@id
2024-06-28 23:57:04 +00:00
docker_api.containers.start(response.@id)
2024-06-29 15:35:03 +00:00
# TODO: wait until container is running before next
end
2024-06-28 23:57:04 +00:00
# TODO: Run test for healthcheck
# TODO: Map route in reverse proxy to function
# TODO: Return function URL for testing
end
end
class Down
@arguments : Array(String) = [] of String
@options : Commander::Options
def initialize(options, arguments)
@options = options
@arguments = arguments
end
def run
@arguments.each do |arg|
puts "Stopping function... #{arg}"
# TODO: check if function is running
# TODO: stop function container
# TODO: delete function container
# TODO: remove route from reverse proxy
end
end
end
end
2024-06-28 15:41:21 +00:00
end