Compare commits
No commits in common. "64778bba193789da025e59f1e0722c3e92681af6" and "11d7cf1f9f88503275e06abe862f6ccf3ff961c9" have entirely different histories.
64778bba19
...
11d7cf1f9f
@ -1,18 +1,18 @@
|
||||
# This configuration file was generated by `ameba --gen-config`
|
||||
# on 2024-07-03 18:18:32 UTC using Ameba version 1.6.1.
|
||||
# on 2024-07-02 22:36:34 UTC using Ameba version 1.6.1.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the reported problems are removed from the code base.
|
||||
|
||||
# Problems found: 6
|
||||
# Problems found: 12
|
||||
# Run `ameba --only Documentation/DocumentationAdmonition` for details
|
||||
Documentation/DocumentationAdmonition:
|
||||
Description: Reports documentation admonitions
|
||||
Timezone: UTC
|
||||
Excluded:
|
||||
- src/faaso.cr
|
||||
- src/secrets.cr
|
||||
- src/daemon/main.cr
|
||||
- src/daemon/secrets.cr
|
||||
- src/daemon/funko.cr
|
||||
- src/funko.cr
|
||||
- spec/faaso_spec.cr
|
||||
Admonitions:
|
||||
@ -29,8 +29,8 @@ Naming/BlockParameterName:
|
||||
MinNameLength: 3
|
||||
AllowNamesEndingInNumbers: true
|
||||
Excluded:
|
||||
- src/faaso.cr
|
||||
- src/daemon/funko.cr
|
||||
- src/commands/build.cr
|
||||
AllowedNames:
|
||||
- _
|
||||
- e
|
||||
|
@ -1,68 +0,0 @@
|
||||
module Faaso
|
||||
module Commands
|
||||
# Build images for one or more funkos from source
|
||||
struct Build
|
||||
def run(options, folders : Array(String))
|
||||
funkos = Funko::Funko.from_paths(folders)
|
||||
|
||||
if options["--local"]
|
||||
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
|
||||
funko.prepare_build tmp_dir
|
||||
|
||||
Log.info { "Building function... #{funko.name} in #{tmp_dir}" }
|
||||
funko.build tmp_dir
|
||||
end
|
||||
else # Running against a server
|
||||
funkos.each do |funko|
|
||||
# Create a tarball for the funko
|
||||
buf = IO::Memory.new
|
||||
Compress::Gzip::Writer.open(buf) do |gzip|
|
||||
Crystar::Writer.open(gzip) do |tw|
|
||||
Dir.glob("#{funko.path}/**/*").each do |path|
|
||||
next unless File.file? path
|
||||
rel_path = Path[path].relative_to funko.path
|
||||
file_info = File.info(path)
|
||||
hdr = Crystar::Header.new(
|
||||
name: rel_path.to_s,
|
||||
mode: file_info.permissions.to_u32,
|
||||
size: file_info.size,
|
||||
)
|
||||
tw.write_header(hdr)
|
||||
tw.write(File.read(path).to_slice)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tmp = File.tempname
|
||||
File.open(tmp, "w") do |outf|
|
||||
outf << buf
|
||||
end
|
||||
|
||||
url = "#{FAASO_SERVER}funkos/build/"
|
||||
|
||||
begin
|
||||
Log.info { "Uploading funko to #{FAASO_SERVER}" }
|
||||
response = Crest.post(
|
||||
url,
|
||||
{"funko.tgz" => File.open(tmp), "name" => "funko.tgz"},
|
||||
user: "admin", password: "admin"
|
||||
)
|
||||
Log.info { "Build finished successfully." }
|
||||
body = JSON.parse(response.body)
|
||||
Log.info { body["stdout"] }
|
||||
rescue ex : Crest::InternalServerError
|
||||
Log.error { "Error building funko #{funko.name} from #{funko.path}" }
|
||||
body = JSON.parse(ex.response.body)
|
||||
Log.info { body["stdout"] }
|
||||
Log.error { body["stderr"] }
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,18 +0,0 @@
|
||||
module Faaso
|
||||
module Commands
|
||||
struct Export
|
||||
def run(options, source : String, destination : String)
|
||||
funko = Funko::Funko.from_paths([source])[0]
|
||||
# Create temporary build location
|
||||
dst_path = destination
|
||||
if File.exists? dst_path
|
||||
Log.error { "#{dst_path} already exists, not exporting #{funko.path}" }
|
||||
return 1
|
||||
end
|
||||
Log.info { "Exporting #{funko.path} to #{dst_path}" }
|
||||
Dir.mkdir_p(dst_path)
|
||||
funko.prepare_build Path[dst_path]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,85 +0,0 @@
|
||||
module Faaso
|
||||
module Commands
|
||||
# Bring up one or more funkos by name.
|
||||
#
|
||||
# This doesn't guarantee that they will be running the latest
|
||||
# version, and it will try to recicle paused and exited containers.
|
||||
#
|
||||
# If there is no other way, it will create a brand new container with
|
||||
# the latest known image and start it.
|
||||
#
|
||||
# If there are no images for the funko, it will fail to bring it up.
|
||||
struct Scale
|
||||
def local(options, name, scale)
|
||||
funko = Funko::Funko.from_names([name])[0]
|
||||
# Asked about scale
|
||||
if !scale
|
||||
Log.info { "Funko #{name} has a scale of #{funko.scale}" }
|
||||
return 0
|
||||
end
|
||||
# Asked to set scale
|
||||
if funko.image_history.empty?
|
||||
Log.error { "Error: no images available for #{funko.name}:latest" }
|
||||
exit 1
|
||||
end
|
||||
funko.scale(scale.as(String).to_i)
|
||||
end
|
||||
|
||||
def remote(options, name, scale)
|
||||
if !scale
|
||||
response = Crest.get(
|
||||
"#{FAASO_SERVER}funkos/#{name}/scale/", \
|
||||
user: "admin", password: "admin")
|
||||
else
|
||||
response = Crest.post(
|
||||
"#{FAASO_SERVER}funkos/#{name}/scale/",
|
||||
{"scale" => scale}, user: "admin", password: "admin")
|
||||
end
|
||||
body = JSON.parse(response.body)
|
||||
Log.info { body["output"] }
|
||||
rescue ex : Crest::InternalServerError
|
||||
Log.error { "Error scaling funko #{name}" }
|
||||
body = JSON.parse(ex.response.body)
|
||||
Log.info { body["output"] }
|
||||
exit 1
|
||||
end
|
||||
|
||||
def run(options, name, scale)
|
||||
if options["--local"]
|
||||
return local(options, name, scale)
|
||||
end
|
||||
remote(options, name, scale)
|
||||
|
||||
# case self
|
||||
# when .running?
|
||||
# # If it's already up, do nothing
|
||||
# # FIXME: bring back out-of-date warning
|
||||
# Log.info { "#{funko.name} is already up" }
|
||||
# when .paused?
|
||||
# # If it is paused, unpause it
|
||||
# Log.info { "Resuming existing paused container" }
|
||||
# funko.unpause
|
||||
# when .exited?
|
||||
# Log.info { "Starting function #{funko.name}" }
|
||||
# Log.info { "Restarting existing exited container" }
|
||||
# funko.start
|
||||
# else
|
||||
# # Only have an image, deploy from scratch
|
||||
# Faaso.setup_network # We need it
|
||||
# Log.info { "Creating and starting new container" }
|
||||
# funko.create_container(autostart: true)
|
||||
|
||||
# (1..5).each { |_|
|
||||
# break if funko.running?
|
||||
# sleep 0.1.seconds
|
||||
# }
|
||||
# if !funko.running?
|
||||
# Log.warn { "Container for #{funko.name} is not running yet" }
|
||||
# next
|
||||
# end
|
||||
# Log.info { "Container for #{funko.name} is running" }
|
||||
# end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,43 +0,0 @@
|
||||
module Faaso
|
||||
module Commands
|
||||
struct Status
|
||||
def local(options, name)
|
||||
funko = Funko::Funko.from_names([name])[0]
|
||||
status = funko.docker_status
|
||||
|
||||
Log.info { "Name: #{status.@name}" }
|
||||
Log.info { "Scale: #{status.scale}" }
|
||||
|
||||
Log.info { "Containers: #{status.containers.size}" }
|
||||
status.containers.each do |container|
|
||||
Log.info { " #{container.@names[0]} #{container.status}" }
|
||||
end
|
||||
|
||||
Log.info { "Images: #{status.images.size}" }
|
||||
status.images.each do |image|
|
||||
Log.info { " #{image.repo_tags} #{Time.unix(image.created)}" }
|
||||
end
|
||||
end
|
||||
|
||||
def remote(options, name)
|
||||
response = Crest.get(
|
||||
"#{FAASO_SERVER}funkos/#{name}/status/", \
|
||||
user: "admin", password: "admin")
|
||||
body = JSON.parse(response.body)
|
||||
Log.info { body["output"] }
|
||||
rescue ex : Crest::InternalServerError
|
||||
Log.error { "Error scaling funko #{name}" }
|
||||
body = JSON.parse(ex.response.body)
|
||||
Log.info { body["output"] }
|
||||
exit 1
|
||||
end
|
||||
|
||||
def run(options, name)
|
||||
if options["--local"]
|
||||
return local(options, name)
|
||||
end
|
||||
remote(options, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -5,71 +5,31 @@ require "../funko.cr"
|
||||
module Funko
|
||||
extend self
|
||||
|
||||
# Get the funko's status
|
||||
get "/funkos/:name/status/" do |env|
|
||||
name = env.params.url["name"]
|
||||
response = run_faaso(["status", name])
|
||||
|
||||
if response["exit_code"] != 0
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
response.to_json
|
||||
end
|
||||
get "/funkos/:name/pause/" do |env|
|
||||
funko = Funko.from_names([env.params.url["name"]])[0]
|
||||
funko.pause
|
||||
funko.wait_for("paused", 5)
|
||||
end
|
||||
|
||||
# Get the funko's scale
|
||||
get "/funkos/:name/scale/" do |env|
|
||||
name = env.params.url["name"]
|
||||
response = run_faaso(["scale", name])
|
||||
|
||||
if response["exit_code"] != 0
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
response.to_json
|
||||
end
|
||||
get "/funkos/:name/unpause/" do |env|
|
||||
funko = Funko.from_names([env.params.url["name"]])[0]
|
||||
funko.unpause
|
||||
funko.wait_for("running", 5)
|
||||
end
|
||||
|
||||
# Set the funko's scale
|
||||
post "/funkos/:name/scale/" do |env|
|
||||
name = env.params.url["name"]
|
||||
scale = env.params.body["scale"].as(String)
|
||||
response = run_faaso(["scale", name, scale])
|
||||
if response["exit_code"] != 0
|
||||
Log.error { response }
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
Log.info { response }
|
||||
response.to_json
|
||||
end
|
||||
get "/funkos/:name/start/" do |env|
|
||||
funko = Funko.from_names([env.params.url["name"]])[0]
|
||||
funko.start
|
||||
funko.wait_for("running", 5)
|
||||
end
|
||||
|
||||
# Build image for funko received as "funko.tgz"
|
||||
# TODO: This may take a while, consider using something like
|
||||
# mosquito-cr/mosquito to make it a job queue
|
||||
post "/funkos/build/" do |env|
|
||||
# Create place to build funko
|
||||
tmp_dir = Path.new("tmp", UUID.random.to_s)
|
||||
Dir.mkdir_p(tmp_dir) unless File.exists? tmp_dir
|
||||
|
||||
# Expand tarball in there
|
||||
file = env.params.files["funko.tgz"].tempfile
|
||||
Compress::Gzip::Reader.open(file) do |gzip|
|
||||
Crystar::Reader.open(gzip) do |tar|
|
||||
tar.each_entry do |entry|
|
||||
File.open(Path.new(tmp_dir, entry.name), "w") do |dst|
|
||||
IO.copy entry.io, dst
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Build the thing
|
||||
response = run_faaso(["build", tmp_dir.to_s])
|
||||
|
||||
if response["exit_code"] != 0
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
response.to_json
|
||||
get "/funkos/:name/stop/" do |env|
|
||||
funko = Funko.from_names([env.params.url["name"]])[0]
|
||||
begin
|
||||
funko.stop
|
||||
funko.wait_for("exited", 5)
|
||||
rescue ex : Docr::Errors::DockerAPIError
|
||||
halt env, status_code: 500, response: "Failed to stop container"
|
||||
end
|
||||
end
|
||||
|
||||
@ -79,12 +39,20 @@ module Funko
|
||||
result = [] of Hash(String, String)
|
||||
|
||||
funkos.each do |funko|
|
||||
state = "FIXME"
|
||||
state = ""
|
||||
case funko
|
||||
when .running?
|
||||
state = "running"
|
||||
when .paused?
|
||||
state = "paused"
|
||||
else
|
||||
state = "stopped"
|
||||
end
|
||||
|
||||
result << {
|
||||
"name" => funko.name,
|
||||
"state" => state,
|
||||
"status" => "FIXME",
|
||||
"status" => funko.status,
|
||||
}
|
||||
end
|
||||
|
||||
@ -94,20 +62,4 @@ module Funko
|
||||
result.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def run_faaso(args : Array(String))
|
||||
Log.info { "Running faaso [#{args.join(", ")}, -l]" }
|
||||
output = IO::Memory.new
|
||||
status = Process.run(
|
||||
command: "faaso",
|
||||
args: args + ["-l"], # Always local in the server
|
||||
output: output,
|
||||
error: output,
|
||||
)
|
||||
result = {
|
||||
"exit_code" => status.exit_code,
|
||||
"output" => output.to_s,
|
||||
}
|
||||
result
|
||||
end
|
||||
end
|
||||
|
@ -11,4 +11,62 @@ require "uuid"
|
||||
# FIXME: make configurable
|
||||
basic_auth "admin", "admin"
|
||||
|
||||
# Bring up the funko
|
||||
get "/funko/:name/up/" do |env|
|
||||
name = env.params.url["name"]
|
||||
response = run_faaso(["up", name])
|
||||
|
||||
if response["exit_code"] != 0
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
response.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# Build image for funko received as "funko.tgz"
|
||||
# TODO: This may take a while, consider using something like
|
||||
# mosquito-cr/mosquito to make it a job queue
|
||||
post "/funko/build/" do |env|
|
||||
# Create place to build funko
|
||||
tmp_dir = Path.new("tmp", UUID.random.to_s)
|
||||
Dir.mkdir_p(tmp_dir) unless File.exists? tmp_dir
|
||||
|
||||
# Expand tarball in there
|
||||
file = env.params.files["funko.tgz"].tempfile
|
||||
Compress::Gzip::Reader.open(file) do |gzip|
|
||||
Crystar::Reader.open(gzip) do |tar|
|
||||
tar.each_entry do |entry|
|
||||
File.open(Path.new(tmp_dir, entry.name), "w") do |dst|
|
||||
IO.copy entry.io, dst
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Build the thing
|
||||
response = run_faaso(["build", tmp_dir.to_s])
|
||||
|
||||
if response["exit_code"] != 0
|
||||
halt env, status_code: 500, response: response.to_json
|
||||
else
|
||||
response.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def run_faaso(args : Array(String))
|
||||
stderr = IO::Memory.new
|
||||
stdout = IO::Memory.new
|
||||
status = Process.run(
|
||||
command: "faaso",
|
||||
args: args + ["-l"], # Always local in the server
|
||||
output: stdout,
|
||||
error: stderr,
|
||||
)
|
||||
{
|
||||
"exit_code" => status.exit_code,
|
||||
"stdout" => stdout.to_s,
|
||||
"stderr" => stderr.to_s,
|
||||
}
|
||||
end
|
||||
|
||||
Kemal.run
|
||||
|
160
src/faaso.cr
160
src/faaso.cr
@ -1,7 +1,3 @@
|
||||
require "./commands/build.cr"
|
||||
require "./commands/export.cr"
|
||||
require "./commands/scale.cr"
|
||||
require "./commands/status.cr"
|
||||
require "./funko.cr"
|
||||
require "crest"
|
||||
require "docr"
|
||||
@ -30,5 +26,161 @@ module Faaso
|
||||
end
|
||||
|
||||
module Commands
|
||||
# Build images for one or more funkos
|
||||
class Build
|
||||
def run(options, folders : Array(String))
|
||||
funkos = Funko::Funko.from_paths(folders)
|
||||
|
||||
if options["--local"]
|
||||
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
|
||||
funko.prepare_build tmp_dir
|
||||
|
||||
Log.info { "Building function... #{funko.name} in #{tmp_dir}" }
|
||||
funko.build tmp_dir
|
||||
end
|
||||
else # Running against a server
|
||||
funkos.each do |funko|
|
||||
# Create a tarball for the funko
|
||||
buf = IO::Memory.new
|
||||
Compress::Gzip::Writer.open(buf) do |gzip|
|
||||
Crystar::Writer.open(gzip) do |tw|
|
||||
Dir.glob("#{funko.path}/**/*").each do |path|
|
||||
next unless File.file? path
|
||||
rel_path = Path[path].relative_to funko.path
|
||||
file_info = File.info(path)
|
||||
hdr = Crystar::Header.new(
|
||||
name: rel_path.to_s,
|
||||
mode: file_info.permissions.to_u32,
|
||||
size: file_info.size,
|
||||
)
|
||||
tw.write_header(hdr)
|
||||
tw.write(File.read(path).to_slice)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tmp = File.tempname
|
||||
File.open(tmp, "w") do |outf|
|
||||
outf << buf
|
||||
end
|
||||
|
||||
url = "#{FAASO_SERVER}funko/build/"
|
||||
|
||||
begin
|
||||
Log.info { "Uploading funko to #{FAASO_SERVER}" }
|
||||
response = Crest.post(
|
||||
url,
|
||||
{"funko.tgz" => File.open(tmp), "name" => "funko.tgz"},
|
||||
user: "admin", password: "admin"
|
||||
)
|
||||
Log.info { "Build finished successfully." }
|
||||
body = JSON.parse(response.body)
|
||||
Log.info { body["stdout"] }
|
||||
rescue ex : Crest::InternalServerError
|
||||
Log.error { "Error building funko #{funko.name} from #{funko.path}" }
|
||||
body = JSON.parse(ex.response.body)
|
||||
Log.info { body["stdout"] }
|
||||
Log.error { body["stderr"] }
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Bring up one or more funkos by name.
|
||||
#
|
||||
# This doesn't guarantee that they will be running the latest
|
||||
# version, and it will try to recicle paused and exited containers.
|
||||
#
|
||||
# If there is no other way, it will create a brand new container with
|
||||
# the latest known image and start it.
|
||||
#
|
||||
# If there are no images for the funko, it will fail to bring it up.
|
||||
class Up
|
||||
@arguments : Array(String) = [] of String
|
||||
@options : Hash(String, Bool)
|
||||
|
||||
def initialize(options, arguments)
|
||||
@options = options
|
||||
@arguments = arguments
|
||||
end
|
||||
|
||||
def run
|
||||
funkos = Funko::Funko.from_names(@arguments)
|
||||
funkos.each do |funko|
|
||||
local = @options.@bool["local"]
|
||||
|
||||
if !local
|
||||
begin
|
||||
response = Crest.get("#{FAASO_SERVER}funko/#{funko.name}/up/",
|
||||
user: "admin", password: "admin")
|
||||
body = JSON.parse(response.body)
|
||||
Log.info { body["stdout"] }
|
||||
next
|
||||
rescue ex : Crest::InternalServerError
|
||||
Log.error { "Error bringing up #{funko.name}" }
|
||||
body = JSON.parse(ex.response.body)
|
||||
Log.info { body["stdout"] }
|
||||
Log.error { body["stderr"] }
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
if funko.image_history.empty?
|
||||
Log.error { "Error: no images available for #{funko.name}:latest" }
|
||||
exit 1
|
||||
end
|
||||
|
||||
case funko
|
||||
when .running?
|
||||
# If it's already up, do nothing
|
||||
# FIXME: bring back out-of-date warning
|
||||
Log.info { "#{funko.name} is already up" }
|
||||
when .paused?
|
||||
# If it is paused, unpause it
|
||||
Log.info { "Resuming existing paused container" }
|
||||
funko.unpause
|
||||
when .exited?
|
||||
Log.info { "Starting function #{funko.name}" }
|
||||
Log.info { "Restarting existing exited container" }
|
||||
funko.start
|
||||
else
|
||||
# Only have an image, deploy from scratch
|
||||
Faaso.setup_network # We need it
|
||||
Log.info { "Creating and starting new container" }
|
||||
funko.create_container(autostart: true)
|
||||
|
||||
(1..5).each { |_|
|
||||
break if funko.running?
|
||||
sleep 0.1.seconds
|
||||
}
|
||||
if !funko.running?
|
||||
Log.warn { "Container for #{funko.name} is not running yet" }
|
||||
next
|
||||
end
|
||||
Log.info { "Container for #{funko.name} is running" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Export
|
||||
def run(options, source : String, destination : String)
|
||||
funko = Funko::Funko.from_paths([source])[0]
|
||||
# Create temporary build location
|
||||
dst_path = destination
|
||||
if File.exists? dst_path
|
||||
Log.error { "#{dst_path} already exists, not exporting #{funko.path}" }
|
||||
return 1
|
||||
end
|
||||
Log.info { "Exporting #{funko.path} to #{dst_path}" }
|
||||
Dir.mkdir_p(dst_path)
|
||||
funko.prepare_build Path[dst_path]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
126
src/funko.cr
126
src/funko.cr
@ -6,20 +6,6 @@ require "yaml"
|
||||
module Funko
|
||||
extend self
|
||||
|
||||
struct Status
|
||||
property name : String = ""
|
||||
property scale : Int32 = 0
|
||||
property containers : Array(Docr::Types::ContainerSummary) = [] of Docr::Types::ContainerSummary
|
||||
property images : Array(Docr::Types::ImageSummary) = [] of Docr::Types::ImageSummary
|
||||
|
||||
def initialize(name, scale, containers, images)
|
||||
@name = name
|
||||
@scale = scale
|
||||
@containers = containers
|
||||
@images = images
|
||||
end
|
||||
end
|
||||
|
||||
class Funko
|
||||
include YAML::Serializable
|
||||
|
||||
@ -81,10 +67,8 @@ module Funko
|
||||
# Get the number of running instances of this funko
|
||||
def scale
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.containers.list.select { |container|
|
||||
container.@state == "running"
|
||||
}.count { |container|
|
||||
container.@names.any?(&.starts_with?("/faaso-#{name}-"))
|
||||
docker_api.containers.list.count { |container|
|
||||
container.@name.starts_with? "faaso-#{name}-" && container.@state == "running"
|
||||
}
|
||||
end
|
||||
|
||||
@ -94,29 +78,17 @@ module Funko
|
||||
current_scale = self.scale
|
||||
return if current_scale == new_scale
|
||||
|
||||
Log.info { "Scaling #{name} from #{current_scale} to #{new_scale}" }
|
||||
if new_scale > current_scale
|
||||
Log.info { "Adding instance" }
|
||||
(current_scale...new_scale).each {
|
||||
id = create_container
|
||||
start(id)
|
||||
}
|
||||
(current_scale...new_scale).each { start(create_container) }
|
||||
else
|
||||
containers.select { |container| container.@state == "running" }.sort! { |i, j|
|
||||
containers.select { |contatiner| container.@state == "running" }.sort! { |i, j|
|
||||
i.@created <=> j.@created
|
||||
}.each { |container|
|
||||
Log.info { "Removing instance" }
|
||||
docker_api.containers.stop(container.@id)
|
||||
current_scale -= 1
|
||||
break if current_scale == new_scale
|
||||
}
|
||||
end
|
||||
|
||||
# And now, let's kill all the containers that are NOT running
|
||||
containers.select { |container| container.@state != "running" }.each { |container|
|
||||
Log.info { "Pruning dead instance" }
|
||||
docker_api.containers.delete(container.@id)
|
||||
}
|
||||
end
|
||||
|
||||
# Setup the target directory `path` with all the files needed
|
||||
@ -155,16 +127,7 @@ module Funko
|
||||
tags: ["faaso-#{name}:latest"]) { |x| Log.info { x } }
|
||||
end
|
||||
|
||||
def images
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.images.list.select { |image|
|
||||
false if image.@repo_tags.nil?
|
||||
true if image.@repo_tags.as(Array(String)).any?(&.starts_with?("faaso-#{name}:"))
|
||||
}
|
||||
end
|
||||
|
||||
# Return a list of image IDs for this funko, most recent first
|
||||
# FIXME: use self.images and add filters
|
||||
def image_history
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
begin
|
||||
@ -180,29 +143,63 @@ module Funko
|
||||
# Get all containers related to this funko
|
||||
def containers
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
docker_api.containers.list(all: true).select { |container|
|
||||
container.@names.any?(&.starts_with?("/faaso-#{name}-"))
|
||||
}
|
||||
end
|
||||
|
||||
# A comprehensive status for the funko:
|
||||
def docker_status
|
||||
Status.new(
|
||||
name: name,
|
||||
containers: containers,
|
||||
images: images,
|
||||
scale: scale,
|
||||
docker_api.containers.list(
|
||||
all: true,
|
||||
filters: {"name" => ["faaso-#{name}"]}
|
||||
)
|
||||
end
|
||||
|
||||
# Start container with given id
|
||||
def start(id : String)
|
||||
# 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
|
||||
|
||||
# Pause running container
|
||||
def pause
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
begin
|
||||
docker_api.containers.start(id)
|
||||
rescue ex : Docr::Errors::DockerAPIError
|
||||
Log.error { "#{ex}" } unless ex.status_code == 304 # This just happens
|
||||
end
|
||||
images = self.image_history
|
||||
running = self.containers.select { |container|
|
||||
container.@state == "running"
|
||||
}.sort! { |i, j|
|
||||
(images.index(j.@image_id) || 9999) <=> (images.index(i.@image_id) || 9999)
|
||||
}
|
||||
docker_api.containers.pause(running[0].@id) unless running.empty?
|
||||
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
|
||||
|
||||
# Start exited container with the newer image
|
||||
@ -261,7 +258,7 @@ module Funko
|
||||
Dir.mkdir_p(secrets_mount)
|
||||
conf = Docr::Types::CreateContainerConfig.new(
|
||||
image: "faaso-#{name}:latest",
|
||||
hostname: "#{name}",
|
||||
hostname: name,
|
||||
# Port in the container side
|
||||
host_config: Docr::Types::HostConfig.new(
|
||||
network_mode: "faaso-net",
|
||||
@ -276,7 +273,7 @@ module Funko
|
||||
)
|
||||
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
response = docker_api.containers.create(name: "faaso-#{name}-#{randstr}", config: conf)
|
||||
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
|
||||
@ -316,8 +313,3 @@ module Funko
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def randstr(length = 6) : String
|
||||
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
String.new(Bytes.new(chars.to_slice.sample(length).to_unsafe, length))
|
||||
end
|
||||
|
37
src/main.cr
37
src/main.cr
@ -2,6 +2,21 @@ require "./faaso.cr"
|
||||
require "colorize"
|
||||
require "docopt"
|
||||
|
||||
doc = <<-DOC
|
||||
FaaSO CLI tool.
|
||||
|
||||
Usage:
|
||||
faaso build FOLDER ... [-l] [-v=<level>]
|
||||
faaso scale FUNKO_NAME SCALE [-l] [-v=<level>]
|
||||
faaso export SOURCE DESTINATION [-v=<level>]
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--version Show version.
|
||||
-l --local Run commands locally instead of against a FaaSO server.
|
||||
-v=level Control the logging verbosity, 0 to 5 [default: 3]
|
||||
DOC
|
||||
|
||||
# Log formatter for
|
||||
struct LogFormat < Log::StaticFormatter
|
||||
@@colors = {
|
||||
@ -14,7 +29,7 @@ struct LogFormat < Log::StaticFormatter
|
||||
}
|
||||
|
||||
def run
|
||||
string "#{@entry.message}".colorize(@@colors[@entry.severity.label])
|
||||
string "[#{Time.local}] #{@entry.severity.label}: #{@entry.message}".colorize(@@colors[@entry.severity.label])
|
||||
end
|
||||
|
||||
def self.setup(verbosity)
|
||||
@ -33,22 +48,6 @@ struct LogFormat < Log::StaticFormatter
|
||||
end
|
||||
end
|
||||
|
||||
doc = <<-DOC
|
||||
FaaSO CLI tool.
|
||||
|
||||
Usage:
|
||||
faaso build FOLDER ... [-l] [-v=<level>]
|
||||
faaso scale FUNKO_NAME [SCALE] [-l] [-v=<level>]
|
||||
faaso status FUNKO_NAME [-l] [-v=<level>]
|
||||
faaso export SOURCE DESTINATION [-v=<level>]
|
||||
|
||||
Options:
|
||||
-l --local Run commands locally instead of against a FaaSO server.
|
||||
-h --help Show this screen.
|
||||
--version Show version.
|
||||
-v=level Control the logging verbosity, 0 to 5 [default: 3]
|
||||
DOC
|
||||
|
||||
ans = Docopt.docopt(doc, ARGV)
|
||||
LogFormat.setup(ans["-v"].to_s.to_i)
|
||||
|
||||
@ -57,8 +56,4 @@ when .fetch("build", false)
|
||||
Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
|
||||
when .fetch("export", false)
|
||||
Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
|
||||
when .fetch("scale", false)
|
||||
Faaso::Commands::Scale.new.run(ans, ans["FUNKO_NAME"].as(String), ans["SCALE"])
|
||||
when .fetch("status", false)
|
||||
Faaso::Commands::Status.new.run(ans, ans["FUNKO_NAME"].as(String))
|
||||
end
|
||||
|
@ -8,24 +8,4 @@
|
||||
ReverseOnly Yes
|
||||
ReverseMagic Yes
|
||||
ReversePath "/admin/" "http://127.0.0.1:3000/"
|
||||
ReversePath "/faaso/hello-101807275100116/" "http://hello-101807275100116:3000/"
|
||||
ReversePath "/faaso/hello-109717610410253/" "http://hello-109717610410253:3000/"
|
||||
ReversePath "/faaso/hello-115791188481102/" "http://hello-115791188481102:3000/"
|
||||
ReversePath "/faaso/hello-489850679780/" "http://hello-489850679780:3000/"
|
||||
ReversePath "/faaso/hello-52122101707950/" "http://hello-52122101707950:3000/"
|
||||
ReversePath "/faaso/hello-53828586108120/" "http://hello-53828586108120:3000/"
|
||||
ReversePath "/faaso/hello-5412177121100122/" "http://hello-5412177121100122:3000/"
|
||||
ReversePath "/faaso/hello-679912011112183/" "http://hello-679912011112183:3000/"
|
||||
ReversePath "/faaso/hello-687849738368/" "http://hello-687849738368:3000/"
|
||||
ReversePath "/faaso/hello-689880877456/" "http://hello-689880877456:3000/"
|
||||
ReversePath "/faaso/hello-6987113768753/" "http://hello-6987113768753:3000/"
|
||||
ReversePath "/faaso/hello-7273704811390/" "http://hello-7273704811390:3000/"
|
||||
ReversePath "/faaso/hello-761221081008155/" "http://hello-761221081008155:3000/"
|
||||
ReversePath "/faaso/hello-9798100678476/" "http://hello-9798100678476:3000/"
|
||||
ReversePath "/faaso/hello-98103104100103100/" "http://hello-98103104100103100:3000/"
|
||||
ReversePath "/faaso/hello-e24ojr/" "http://hello-e24ojr:3000/"
|
||||
ReversePath "/faaso/hello-foo/" "http://hello-foo:3000/"
|
||||
ReversePath "/faaso/hello-gfvij3/" "http://hello-gfvij3:3000/"
|
||||
ReversePath "/faaso/hello-ngisvh/" "http://hello-ngisvh:3000/"
|
||||
ReversePath "/faaso/hello-rqp8o3/" "http://hello-rqp8o3:3000/"
|
||||
ReversePath "/faaso/hello-xtpu69/" "http://hello-xtpu69:3000/"
|
||||
ReversePath "/faaso/hello/" "http://hello:3000/"
|
Loading…
Reference in New Issue
Block a user