Refactored funko module
This commit is contained in:
parent
2a6f64a53e
commit
6ca518ae32
@ -2,7 +2,9 @@ require "docr"
|
||||
require "kemal"
|
||||
require "../funko.cr"
|
||||
|
||||
module Funkos
|
||||
module Funko
|
||||
extend self
|
||||
|
||||
get "/funkos/" do |env|
|
||||
funkos = Funko.from_docker
|
||||
funkos.sort! { |a, b| a.name <=> b.name }
|
||||
@ -20,8 +22,9 @@ module Funkos
|
||||
end
|
||||
|
||||
result << {
|
||||
"name" => funko.name,
|
||||
"state" => state,
|
||||
"name" => funko.name,
|
||||
"state" => state,
|
||||
"status" => funko.status,
|
||||
}
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
require "./funkos.cr"
|
||||
require "./funko.cr"
|
||||
require "./proxyconf.cr"
|
||||
require "./secrets.cr"
|
||||
require "compress/gzip"
|
||||
|
@ -38,7 +38,7 @@ module Faaso
|
||||
end
|
||||
|
||||
def run
|
||||
funkos = Funko.from_paths(@arguments)
|
||||
funkos = Funko::Funko.from_paths(@arguments)
|
||||
local = @options.@bool["local"]
|
||||
|
||||
if local
|
||||
@ -120,7 +120,7 @@ module Faaso
|
||||
end
|
||||
|
||||
def run
|
||||
funkos = Funko.from_names(@arguments)
|
||||
funkos = Funko::Funko.from_names(@arguments)
|
||||
funkos.each do |funko|
|
||||
local = @options.@bool["local"]
|
||||
|
||||
@ -188,7 +188,7 @@ module Faaso
|
||||
end
|
||||
|
||||
def run
|
||||
funkos = Funko.from_paths(@arguments)
|
||||
funkos = Funko::Funko.from_paths(@arguments)
|
||||
funkos.each do |funko|
|
||||
# Create temporary build location
|
||||
dst_path = Path.new("export", funko.name)
|
||||
|
381
src/funko.cr
381
src/funko.cr
@ -3,209 +3,226 @@ require "file_utils"
|
||||
require "yaml"
|
||||
|
||||
# A funko, built from its source metadata
|
||||
class Funko
|
||||
include YAML::Serializable
|
||||
module Funko
|
||||
extend self
|
||||
|
||||
# Required, the name of the funko. Must be unique across FaaSO
|
||||
property name : String
|
||||
class Funko
|
||||
include YAML::Serializable
|
||||
|
||||
# if Nil, it has no template whatsoever
|
||||
property runtime : (String | Nil)? = nil
|
||||
# Required, the name of the funko. Must be unique across FaaSO
|
||||
property name : String
|
||||
|
||||
# Extra operating system packages shipped with the runtime's Docker image
|
||||
property ship_packages : Array(String) = [] of String
|
||||
# if Nil, it has no template whatsoever
|
||||
property runtime : (String | Nil)? = nil
|
||||
|
||||
# Extra operating system packages used only when *building* the funko
|
||||
property devel_packages : Array(String) = [] of String
|
||||
# Extra operating system packages shipped with the runtime's Docker image
|
||||
property ship_packages : Array(String) = [] of String
|
||||
|
||||
# Where this is located in the filesystem
|
||||
@[YAML::Field(ignore: true)]
|
||||
property path : String = ""
|
||||
# Extra operating system packages used only when *building* the funko
|
||||
property devel_packages : Array(String) = [] of String
|
||||
|
||||
# Healthcheck properties
|
||||
property healthcheck_options : String = "--interval=1m --timeout=2s --start-period=2s --retries=3"
|
||||
property healthcheck_command : String = "curl --fail http://localhost:3000/ping || exit 1"
|
||||
# Where this is located in the filesystem
|
||||
@[YAML::Field(ignore: true)]
|
||||
property path : String = ""
|
||||
|
||||
def _to_context
|
||||
{
|
||||
"name" => name,
|
||||
"ship_packages" => ship_packages,
|
||||
"devel_packages" => devel_packages,
|
||||
"healthcheck_options" => healthcheck_options,
|
||||
"healthcheck_command" => healthcheck_command,
|
||||
}
|
||||
end
|
||||
# Healthcheck properties
|
||||
property healthcheck_options : String = "--interval=1m --timeout=2s --start-period=2s --retries=3"
|
||||
property healthcheck_command : String = "curl --fail http://localhost:3000/ping || exit 1"
|
||||
|
||||
def to_json(json : JSON::Builder)
|
||||
json.object do
|
||||
json.field("name", name)
|
||||
json.field("ship_packages", ship_packages)
|
||||
json.field("devel_packages", devel_packages)
|
||||
json.field("healthcheck_options", healthcheck_options)
|
||||
json.field("healthcheck_command", healthcheck_command)
|
||||
def _to_context
|
||||
{
|
||||
"name" => name,
|
||||
"ship_packages" => ship_packages,
|
||||
"devel_packages" => devel_packages,
|
||||
"healthcheck_options" => healthcheck_options,
|
||||
"healthcheck_command" => healthcheck_command,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Create an Array of funkos from an Array of folders containing definitions
|
||||
def self.from_paths(paths : Array(String | Path)) : Array(Funko)
|
||||
paths.map { |path| Path.new(path, "funko.yml") }
|
||||
.select { |path| File.exists?(path) }
|
||||
.map { |path|
|
||||
f = Funko.from_yaml(File.read(path.to_s))
|
||||
f.path = path.parent.to_s
|
||||
f
|
||||
def to_json(json : JSON::Builder)
|
||||
json.object do
|
||||
json.field("name", name)
|
||||
json.field("ship_packages", ship_packages)
|
||||
json.field("devel_packages", devel_packages)
|
||||
json.field("healthcheck_options", healthcheck_options)
|
||||
json.field("healthcheck_command", healthcheck_command)
|
||||
end
|
||||
end
|
||||
|
||||
# Create an Array of funkos from an Array of folders containing definitions
|
||||
def self.from_paths(paths : Array(String | Path)) : Array(Funko)
|
||||
paths.map { |path| Path.new(path, "funko.yml") }
|
||||
.select { |path| File.exists?(path) }
|
||||
.map { |path|
|
||||
f = Funko.from_yaml(File.read(path.to_s))
|
||||
f.path = path.parent.to_s
|
||||
f
|
||||
}
|
||||
end
|
||||
|
||||
# Create an array of funkos just from names. These are limited in function
|
||||
# and can't call `prepare_build` or some other functionality
|
||||
def self.from_names(names : Array(String)) : Array(Funko)
|
||||
names.map { |name|
|
||||
Funko.from_yaml("name: #{name}")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Create an array of funkos just from names. These are limited in function
|
||||
# and can't call `prepare_build` or some other functionality
|
||||
def self.from_names(names : Array(String)) : Array(Funko)
|
||||
names.map { |name|
|
||||
Funko.from_yaml("name: #{name}")
|
||||
}
|
||||
end
|
||||
|
||||
# Get all the funkos docker knows about.
|
||||
def self.from_docker : Array(Funko)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
names = Set(String).new
|
||||
docker_api.images.list(all: true).select { |i|
|
||||
next if i.@repo_tags.nil?
|
||||
i.@repo_tags.as(Array(String)).each { |tag|
|
||||
names << tag.split(":", 2)[0].split("-", 2)[1] if tag.starts_with?("faaso-")
|
||||
# Get all the funkos docker knows about.
|
||||
def self.from_docker : Array(Funko)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
names = [] of String
|
||||
funko_containers = docker_api.containers.list(
|
||||
all: true,
|
||||
).each { |container|
|
||||
p! container.@names
|
||||
container.@names.each { |name|
|
||||
names << name.split("-", 2)[1].lstrip("/") if name.starts_with?("/faaso-")
|
||||
}
|
||||
}
|
||||
}
|
||||
from_names(names.to_a)
|
||||
end
|
||||
|
||||
# Setup the target directory `path` with all the files needed
|
||||
# to build a docker image
|
||||
def prepare_build(path : Path)
|
||||
# Copy runtime if requested
|
||||
if !runtime.nil?
|
||||
runtime_dir = Path.new("runtimes", runtime.as(String))
|
||||
raise Exception.new("Error: runtime #{runtime} not found for funko #{name} in #{path}") unless File.exists?(runtime_dir)
|
||||
Dir.glob("#{runtime_dir}/*").each { |src|
|
||||
pp! names
|
||||
|
||||
from_names(names.to_a.sort!)
|
||||
end
|
||||
|
||||
# Setup the target directory `path` with all the files needed
|
||||
# to build a docker image
|
||||
def prepare_build(path : Path)
|
||||
# Copy runtime if requested
|
||||
if !runtime.nil?
|
||||
runtime_dir = Path.new("runtimes", runtime.as(String))
|
||||
raise Exception.new("Error: runtime #{runtime} not found for funko #{name} in #{path}") unless File.exists?(runtime_dir)
|
||||
Dir.glob("#{runtime_dir}/*").each { |src|
|
||||
FileUtils.cp_r(src, path)
|
||||
}
|
||||
# Replace templates with processed files
|
||||
context = _to_context
|
||||
Dir.glob("#{path}/**/*.j2").each { |template|
|
||||
dst = template[..-4]
|
||||
File.open(dst, "w") do |file|
|
||||
file << Crinja.render(File.read(template), context)
|
||||
end
|
||||
File.delete template
|
||||
}
|
||||
end
|
||||
|
||||
# Copy funko
|
||||
raise Exception.new("Internal error: empty funko path for #{name}") if self.path.empty?
|
||||
Dir.glob("#{self.path}/*").each { |src|
|
||||
FileUtils.cp_r(src, path)
|
||||
}
|
||||
# Replace templates with processed files
|
||||
context = _to_context
|
||||
Dir.glob("#{path}/**/*.j2").each { |template|
|
||||
dst = template[..-4]
|
||||
File.open(dst, "w") do |file|
|
||||
file << Crinja.render(File.read(template), context)
|
||||
end
|
||||
File.delete template
|
||||
end
|
||||
|
||||
# Build image using docker in path previously prepared using `prepare_build`
|
||||
def build(path : Path)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.images.build(
|
||||
context: path.to_s,
|
||||
tags: ["faaso-#{name}:latest"]) { |x| Log.info { x } }
|
||||
end
|
||||
|
||||
# Return a list of image IDs for this funko, most recent first
|
||||
def image_history
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
begin
|
||||
docker_api.images.history(
|
||||
name: "faaso-#{name}"
|
||||
).sort { |i, j| j.@created <=> i.@created }.map(&.@id)
|
||||
rescue ex : Docr::Errors::DockerAPIError
|
||||
Log.error { "#{ex}" }
|
||||
[] of String
|
||||
end
|
||||
end
|
||||
|
||||
# Get all containers related to this funko
|
||||
def containers
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.containers.list(
|
||||
all: true,
|
||||
filters: {"name" => ["faaso-#{name}"]}
|
||||
)
|
||||
end
|
||||
|
||||
# Descriptive status for the funko
|
||||
def status
|
||||
status = self.containers.map { |container|
|
||||
container.@status
|
||||
}.join(", ")
|
||||
status.empty? ? "Stopped" : status
|
||||
end
|
||||
|
||||
# Is any instance of this funko running?
|
||||
def running?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "running"
|
||||
}
|
||||
end
|
||||
|
||||
# Copy funko
|
||||
raise Exception.new("Internal error: empty funko path for #{name}") if self.path.empty?
|
||||
Dir.glob("#{self.path}/*").each { |src|
|
||||
FileUtils.cp_r(src, path)
|
||||
}
|
||||
end
|
||||
# Is any instance of this funko paused?
|
||||
def paused?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "paused"
|
||||
}
|
||||
end
|
||||
|
||||
# Build image using docker in path previously prepared using `prepare_build`
|
||||
def build(path : Path)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.images.build(
|
||||
context: path.to_s,
|
||||
tags: ["faaso-#{name}:latest"]) { |x| Log.info { x } }
|
||||
end
|
||||
# Unpause paused container with the newer image
|
||||
def unpause
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
images = self.image_history
|
||||
paused = self.containers.select { |container|
|
||||
container.@state == "paused"
|
||||
}.sort! { |i, j|
|
||||
(images.index(j.@image_id) || 9999) <=> (images.index(i.@image_id) || 9999)
|
||||
}
|
||||
docker_api.containers.unpause(paused[0].@id) unless paused.empty?
|
||||
end
|
||||
|
||||
# Return a list of image IDs for this funko, most recent first
|
||||
def image_history
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
begin
|
||||
docker_api.images.history(
|
||||
name: "faaso-#{name}"
|
||||
).sort { |i, j| j.@created <=> i.@created }.map(&.@id)
|
||||
rescue ex : Docr::Errors::DockerAPIError
|
||||
Log.error { "#{ex}" }
|
||||
[] of String
|
||||
# Is any instance of this funko exited?
|
||||
def exited?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "exited"
|
||||
}
|
||||
end
|
||||
|
||||
# Restart exited container with the newer image
|
||||
def start
|
||||
# FIXME refactor DRY with unpause
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
images = self.image_history
|
||||
exited = self.containers.select { |container|
|
||||
container.@state == "exited"
|
||||
}.sort! { |i, j|
|
||||
(images.index(j.@image_id) || 9999) <=> (images.index(i.@image_id) || 9999)
|
||||
}
|
||||
docker_api.containers.restart(exited[0].@id) unless exited.empty?
|
||||
end
|
||||
|
||||
# Create a container for this funko
|
||||
def create_container(autostart : Bool = true) : String
|
||||
secrets_mount = "#{Dir.current}/secrets/#{name}"
|
||||
Dir.mkdir_p(secrets_mount)
|
||||
conf = Docr::Types::CreateContainerConfig.new(
|
||||
image: "faaso-#{name}:latest",
|
||||
hostname: name,
|
||||
# Port in the container side
|
||||
host_config: Docr::Types::HostConfig.new(
|
||||
network_mode: "faaso-net",
|
||||
mounts: [
|
||||
Docr::Types::Mount.new(
|
||||
source: secrets_mount,
|
||||
target: "/secrets",
|
||||
type: "bind"
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
response = docker_api.containers.create(name: "faaso-#{name}", config: conf)
|
||||
response.@warnings.each { |msg| Log.warn { msg } }
|
||||
docker_api.containers.start(response.@id) if autostart
|
||||
response.@id
|
||||
end
|
||||
end
|
||||
|
||||
# Get all containers related to this funko
|
||||
def containers
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.containers.list(
|
||||
all: true,
|
||||
filters: {"name" => ["faaso-#{name}"]}
|
||||
)
|
||||
end
|
||||
|
||||
# Is any instance of this funko running?
|
||||
def running?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "running"
|
||||
}
|
||||
end
|
||||
|
||||
# Is any instance of this funko paused?
|
||||
def paused?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "paused"
|
||||
}
|
||||
end
|
||||
|
||||
# Unpause paused container with the newer image
|
||||
def unpause
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
images = self.image_history
|
||||
paused = self.containers.select { |container|
|
||||
container.@state == "paused"
|
||||
}.sort! { |i, j|
|
||||
(images.index(j.@image_id) || 9999) <=> (images.index(i.@image_id) || 9999)
|
||||
}
|
||||
docker_api.containers.unpause(paused[0].@id) unless paused.empty?
|
||||
end
|
||||
|
||||
# Is any instance of this funko exited?
|
||||
def exited?
|
||||
self.containers.any? { |container|
|
||||
container.@state == "exited"
|
||||
}
|
||||
end
|
||||
|
||||
# Restart exited container with the newer image
|
||||
def start
|
||||
# FIXME refactor DRY with unpause
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
images = self.image_history
|
||||
exited = self.containers.select { |container|
|
||||
container.@state == "exited"
|
||||
}.sort! { |i, j|
|
||||
(images.index(j.@image_id) || 9999) <=> (images.index(i.@image_id) || 9999)
|
||||
}
|
||||
docker_api.containers.restart(exited[0].@id) unless exited.empty?
|
||||
end
|
||||
|
||||
# Create a container for this funko
|
||||
def create_container(autostart : Bool = true) : String
|
||||
secrets_mount = "#{Dir.current}/secrets/#{name}"
|
||||
Dir.mkdir_p(secrets_mount)
|
||||
conf = Docr::Types::CreateContainerConfig.new(
|
||||
image: "faaso-#{name}:latest",
|
||||
hostname: name,
|
||||
# Port in the container side
|
||||
host_config: Docr::Types::HostConfig.new(
|
||||
network_mode: "faaso-net",
|
||||
mounts: [
|
||||
Docr::Types::Mount.new(
|
||||
source: secrets_mount,
|
||||
target: "/secrets",
|
||||
type: "bind"
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
response = docker_api.containers.create(name: "faaso-#{name}", config: conf)
|
||||
response.@warnings.each { |msg| Log.warn { msg } }
|
||||
docker_api.containers.start(response.@id) if autostart
|
||||
response.@id
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
<%- result.each do |f| -%>
|
||||
<tr>
|
||||
<td><%= f["name"] %></td>
|
||||
<td><%= f["state"] %></td>
|
||||
<td><%= f["status"] %></td>
|
||||
<td>
|
||||
<%- if f["state"] == "running" -%>
|
||||
<button state="disabled">Start</button>
|
||||
|
Loading…
Reference in New Issue
Block a user