Compare commits
No commits in common. "6ca518ae321520471a8789ef900d24722dc44820" and "a4f722a1e08d2f483727472388afc23c9840172e" have entirely different histories.
6ca518ae32
...
a4f722a1e0
2
Makefile
2
Makefile
@ -3,7 +3,7 @@ build: shard.yml $(wildcard src/**/*cr)
|
|||||||
proxy: build
|
proxy: build
|
||||||
docker build . -t faaso-proxy --no-cache
|
docker build . -t faaso-proxy --no-cache
|
||||||
start-proxy:
|
start-proxy:
|
||||||
docker run --name faaso_proxy --rm --network=faaso-net -v /var/run/docker.sock:/var/run/docker.sock -v secrets:/home/app/secrets -p 8888:8888 faaso-proxy
|
docker run --network=faaso-net -v /var/run/docker.sock:/var/run/docker.sock -v secrets:/home/app/secrets -p 8888:8888 faaso-proxy
|
||||||
|
|
||||||
|
|
||||||
.PHONY: build proxy-image start-proxy
|
.PHONY: build proxy-image start-proxy
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css" />
|
<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css" />
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<body>
|
<body>
|
||||||
<header class="container">
|
<script>
|
||||||
<h1>Your Funko Collection</h1>
|
async function getFunkos() {
|
||||||
</header>
|
const response = await fetch("funkos/");
|
||||||
<main class=container>
|
funkos = await response.json();
|
||||||
|
console.log(funkos);
|
||||||
|
}
|
||||||
|
getFunkos();
|
||||||
|
</script>
|
||||||
<button hx-get="funkos/?format=html" hx-target="#funko-list">Update</button>
|
<button hx-get="funkos/?format=html" hx-target="#funko-list">Update</button>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -19,7 +20,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tbody id="funko-list">
|
<tbody id="funko-list">
|
||||||
</tbody>
|
</tbody>
|
||||||
</thead>
|
</thead>
|
||||||
</main>
|
|
||||||
</body>
|
</body>
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
require "docr"
|
|
||||||
require "kemal"
|
|
||||||
require "../funko.cr"
|
|
||||||
|
|
||||||
module Funko
|
|
||||||
extend self
|
|
||||||
|
|
||||||
get "/funkos/" do |env|
|
|
||||||
funkos = Funko.from_docker
|
|
||||||
funkos.sort! { |a, b| a.name <=> b.name }
|
|
||||||
result = [] of Hash(String, String)
|
|
||||||
|
|
||||||
funkos.each do |funko|
|
|
||||||
state = ""
|
|
||||||
case funko
|
|
||||||
when .running?
|
|
||||||
state = "running"
|
|
||||||
when .paused?
|
|
||||||
state = "paused"
|
|
||||||
else
|
|
||||||
state = "stopped"
|
|
||||||
end
|
|
||||||
|
|
||||||
result << {
|
|
||||||
"name" => funko.name,
|
|
||||||
"state" => state,
|
|
||||||
"status" => funko.status,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if env.params.query.fetch("format", "json") == "html"
|
|
||||||
render "src/views/funkos.ecr"
|
|
||||||
else
|
|
||||||
result.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
17
src/daemon/funkos.cr
Normal file
17
src/daemon/funkos.cr
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
require "docr"
|
||||||
|
require "kemal"
|
||||||
|
require "../funko.cr"
|
||||||
|
|
||||||
|
module Funkos
|
||||||
|
get "/funkos/" do |env|
|
||||||
|
funkos : Array(Funko) = Funko.from_docker
|
||||||
|
|
||||||
|
funkos.sort! { |a, b| a.name <=> b.name }
|
||||||
|
|
||||||
|
if env.params.query.fetch("format", "json") == "html"
|
||||||
|
render "src/views/funkos.ecr"
|
||||||
|
else
|
||||||
|
funkos.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,4 +1,4 @@
|
|||||||
require "./funko.cr"
|
require "./funkos.cr"
|
||||||
require "./proxyconf.cr"
|
require "./proxyconf.cr"
|
||||||
require "./secrets.cr"
|
require "./secrets.cr"
|
||||||
require "compress/gzip"
|
require "compress/gzip"
|
||||||
|
@ -1,10 +1,44 @@
|
|||||||
require "kemal"
|
require "kemal"
|
||||||
require "../secrets.cr"
|
|
||||||
|
|
||||||
module Secrets
|
module Secrets
|
||||||
extend self
|
SECRETS = Hash(String, String).new
|
||||||
|
SECRET_PATH = "./secrets/"
|
||||||
|
|
||||||
# TODO: sanitize all inputs
|
# TODO: sanitize all inputs
|
||||||
|
|
||||||
|
# Store secrets in a tree of files
|
||||||
|
def self.update_secrets
|
||||||
|
# Save new secrets
|
||||||
|
SECRETS.map do |_name, value|
|
||||||
|
funko, name = _name.split("-", 2)
|
||||||
|
funko_dir = Path.new(SECRET_PATH, funko)
|
||||||
|
Dir.mkdir_p(funko_dir)
|
||||||
|
File.write(Path.new(funko_dir, name), value)
|
||||||
|
end
|
||||||
|
# Delete secrets not in the hash
|
||||||
|
Dir.glob(Path.new(SECRET_PATH, "*")).each do |funko_dir|
|
||||||
|
funko = File.basename(funko_dir)
|
||||||
|
Dir.glob(Path.new(funko_dir, "*")).each do |secret_file|
|
||||||
|
name = File.basename(secret_file)
|
||||||
|
unless SECRETS.has_key?("#{funko}-#{name}")
|
||||||
|
File.delete(secret_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Load secrets from the disk
|
||||||
|
def self.load_secrets
|
||||||
|
Dir.glob(Path.new(SECRET_PATH, "*")).each do |funko_dir|
|
||||||
|
funko = File.basename(funko_dir)
|
||||||
|
Dir.glob(Path.new(funko_dir, "*")).each do |secret_file|
|
||||||
|
name = File.basename(secret_file)
|
||||||
|
value = File.read(secret_file)
|
||||||
|
SECRETS["#{funko}-#{name}"] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Gets a secret in form {"name": "funko_name-secret_name", "value": "secret_value"}
|
# Gets a secret in form {"name": "funko_name-secret_name", "value": "secret_value"}
|
||||||
post "/secrets/" do |env|
|
post "/secrets/" do |env|
|
||||||
name = env.params.json["name"].as(String)
|
name = env.params.json["name"].as(String)
|
||||||
|
@ -38,7 +38,7 @@ module Faaso
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
funkos = Funko::Funko.from_paths(@arguments)
|
funkos = Funko.from_paths(@arguments)
|
||||||
local = @options.@bool["local"]
|
local = @options.@bool["local"]
|
||||||
|
|
||||||
if local
|
if local
|
||||||
@ -120,7 +120,7 @@ module Faaso
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
funkos = Funko::Funko.from_names(@arguments)
|
funkos = Funko.from_names(@arguments)
|
||||||
funkos.each do |funko|
|
funkos.each do |funko|
|
||||||
local = @options.@bool["local"]
|
local = @options.@bool["local"]
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ module Faaso
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
funkos = Funko::Funko.from_paths(@arguments)
|
funkos = Funko.from_paths(@arguments)
|
||||||
funkos.each do |funko|
|
funkos.each do |funko|
|
||||||
# Create temporary build location
|
# Create temporary build location
|
||||||
dst_path = Path.new("export", funko.name)
|
dst_path = Path.new("export", funko.name)
|
||||||
|
408
src/funko.cr
408
src/funko.cr
@ -3,226 +3,210 @@ require "file_utils"
|
|||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
# A funko, built from its source metadata
|
# A funko, built from its source metadata
|
||||||
module Funko
|
class Funko
|
||||||
extend self
|
include YAML::Serializable
|
||||||
|
|
||||||
class Funko
|
# Required, the name of the funko. Must be unique across FaaSO
|
||||||
include YAML::Serializable
|
property name : String
|
||||||
|
|
||||||
# Required, the name of the funko. Must be unique across FaaSO
|
# if Nil, it has no template whatsoever
|
||||||
property name : String
|
property runtime : (String | Nil)? = nil
|
||||||
|
|
||||||
# if Nil, it has no template whatsoever
|
# Extra operating system packages shipped with the runtime's Docker image
|
||||||
property runtime : (String | Nil)? = nil
|
property ship_packages : Array(String) = [] of String
|
||||||
|
|
||||||
# Extra operating system packages shipped with the runtime's Docker image
|
# Extra operating system packages used only when *building* the funko
|
||||||
property ship_packages : Array(String) = [] of String
|
property devel_packages : Array(String) = [] of String
|
||||||
|
|
||||||
# Extra operating system packages used only when *building* the funko
|
# Where this is located in the filesystem
|
||||||
property devel_packages : Array(String) = [] of String
|
@[YAML::Field(ignore: true)]
|
||||||
|
property path : String = ""
|
||||||
|
|
||||||
# Where this is located in the filesystem
|
# Healthcheck properties
|
||||||
@[YAML::Field(ignore: true)]
|
property healthcheck_options : String = "--interval=1m --timeout=2s --start-period=2s --retries=3"
|
||||||
property path : String = ""
|
property healthcheck_command : String = "curl --fail http://localhost:3000/ping || exit 1"
|
||||||
|
|
||||||
# Healthcheck properties
|
def _to_context
|
||||||
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"
|
"name" => name,
|
||||||
|
"ship_packages" => ship_packages,
|
||||||
|
"devel_packages" => devel_packages,
|
||||||
|
"healthcheck_options" => healthcheck_options,
|
||||||
|
"healthcheck_command" => healthcheck_command,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def _to_context
|
def to_json(json : JSON::Builder)
|
||||||
{
|
json.object do
|
||||||
"name" => name,
|
json.field("name", name)
|
||||||
"ship_packages" => ship_packages,
|
json.field("ship_packages", ship_packages)
|
||||||
"devel_packages" => devel_packages,
|
json.field("devel_packages", devel_packages)
|
||||||
"healthcheck_options" => healthcheck_options,
|
json.field("healthcheck_options", healthcheck_options)
|
||||||
"healthcheck_command" => healthcheck_command,
|
json.field("healthcheck_command", healthcheck_command)
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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-")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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
|
||||||
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
|
||||||
|
|
||||||
|
# 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-")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pp! names
|
||||||
|
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|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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
|
end
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
module Secrets
|
|
||||||
extend self
|
|
||||||
SECRETS = Hash(String, String).new
|
|
||||||
SECRET_PATH = "./secrets/"
|
|
||||||
|
|
||||||
# TODO: sanitize all inputs
|
|
||||||
|
|
||||||
# Store secrets in a tree of files
|
|
||||||
def update_secrets
|
|
||||||
# Save new secrets
|
|
||||||
SECRETS.map do |_name, value|
|
|
||||||
funko, name = _name.split("-", 2)
|
|
||||||
funko_dir = Path.new(SECRET_PATH, funko)
|
|
||||||
Dir.mkdir_p(funko_dir)
|
|
||||||
File.write(Path.new(funko_dir, name), value)
|
|
||||||
end
|
|
||||||
# Delete secrets not in the hash
|
|
||||||
Dir.glob(Path.new(SECRET_PATH, "*")).each do |funko_dir|
|
|
||||||
funko = File.basename(funko_dir)
|
|
||||||
Dir.glob(Path.new(funko_dir, "*")).each do |secret_file|
|
|
||||||
name = File.basename(secret_file)
|
|
||||||
unless SECRETS.has_key?("#{funko}-#{name}")
|
|
||||||
File.delete(secret_file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load secrets from the disk
|
|
||||||
def load_secrets
|
|
||||||
Dir.glob(Path.new(SECRET_PATH, "*")).each do |funko_dir|
|
|
||||||
funko = File.basename(funko_dir)
|
|
||||||
Dir.glob(Path.new(funko_dir, "*")).each do |secret_file|
|
|
||||||
name = File.basename(secret_file)
|
|
||||||
value = File.read(secret_file)
|
|
||||||
SECRETS["#{funko}-#{name}"] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Secrets.load_secrets
|
|
@ -1,26 +1,7 @@
|
|||||||
<%- result.each do |f| -%>
|
<%- funkos.each do |funko| -%>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= f["name"] %></td>
|
<td><%= funko.name %></td>
|
||||||
<td><%= f["status"] %></td>
|
<td>sarasa</td>
|
||||||
<td>
|
<td><button>DOSTUFF</button></td>
|
||||||
<%- if f["state"] == "running" -%>
|
|
||||||
<button state="disabled">Start</button>
|
|
||||||
<button>Pause</button>
|
|
||||||
<button>Stop</button>
|
|
||||||
<button>Restart</button>
|
|
||||||
<%- end -%>
|
|
||||||
<%- if f["state"] == "paused" -%>
|
|
||||||
<button>Start</button>
|
|
||||||
<button disabled>Pause</button>
|
|
||||||
<button>Stop</button>
|
|
||||||
<button>Restart</button>
|
|
||||||
<%- end -%>
|
|
||||||
<%- if f["state"] == "stopped" -%>
|
|
||||||
<button>Start</button>
|
|
||||||
<button disabled>Pause</button>
|
|
||||||
<button disabled>Stop</button>
|
|
||||||
<button disabled>Restart</button>
|
|
||||||
<%- end -%>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
Loading…
Reference in New Issue
Block a user