Switched to docopt because all CLI things suck

This commit is contained in:
Roberto Alsina 2024-07-03 11:19:34 -03:00
parent bef6ded369
commit 11d7cf1f9f
5 changed files with 117 additions and 206 deletions

View File

@ -4,10 +4,6 @@ shards:
git: https://github.com/sija/backtracer.cr.git git: https://github.com/sija/backtracer.cr.git
version: 1.2.2 version: 1.2.2
commander:
git: https://github.com/mrrooijen/commander.git
version: 0.4.0
crest: crest:
git: https://github.com/mamantoha/crest.git git: https://github.com/mamantoha/crest.git
version: 1.3.13 version: 1.3.13
@ -20,6 +16,10 @@ shards:
git: https://github.com/naqvis/crystar.git git: https://github.com/naqvis/crystar.git
version: 0.4.0 version: 0.4.0
docopt:
git: https://github.com/chenkovsky/docopt.cr.git
version: 0.2.0+git.commit.620fce4f334ff15d7321e5ecb6665ad258fe9297
docr: docr:
git: https://github.com/ralsina/docr.git git: https://github.com/ralsina/docr.git
version: 0.1.1+git.commit.18f15cc7111b1d0c63347c7cca07aee9ec87a7a8 version: 0.1.1+git.commit.18f15cc7111b1d0c63347c7cca07aee9ec87a7a8

View File

@ -15,18 +15,18 @@ crystal: ">= 1.12.2"
license: MIT license: MIT
dependencies: dependencies:
docr: crest:
github: ralsina/docr github: mamantoha/crest
branch: add_exposed_ports
commander:
github: mrrooijen/commander
kemal:
github: kemalcr/kemal
kemal-basic-auth:
github: kemalcr/kemal-basic-auth
crinja: crinja:
github: straight-shoota/crinja github: straight-shoota/crinja
crystar: crystar:
github: naqvis/crystar github: naqvis/crystar
crest: docopt:
github: mamantoha/crest github: chenkovsky/docopt.cr
docr:
github: ralsina/docr
branch: add_exposed_ports
kemal:
github: kemalcr/kemal
kemal-basic-auth:
github: kemalcr/kemal-basic-auth

View File

@ -1,5 +1,4 @@
require "./funko.cr" require "./funko.cr"
require "commander"
require "crest" require "crest"
require "docr" require "docr"
require "docr/utils.cr" require "docr/utils.cr"
@ -29,19 +28,10 @@ module Faaso
module Commands module Commands
# Build images for one or more funkos # Build images for one or more funkos
class Build class Build
@arguments : Array(String) = [] of String def run(options, folders : Array(String))
@options : Commander::Options funkos = Funko::Funko.from_paths(folders)
def initialize(options, arguments) if options["--local"]
@options = options
@arguments = arguments
end
def run
funkos = Funko::Funko.from_paths(@arguments)
local = @options.@bool["local"]
if local
funkos.each do |funko| funkos.each do |funko|
# Create temporary build location # Create temporary build location
tmp_dir = Path.new("tmp", UUID.random.to_s) tmp_dir = Path.new("tmp", UUID.random.to_s)
@ -112,7 +102,7 @@ module Faaso
# If there are no images for the funko, it will fail to bring it up. # If there are no images for the funko, it will fail to bring it up.
class Up class Up
@arguments : Array(String) = [] of String @arguments : Array(String) = [] of String
@options : Commander::Options @options : Hash(String, Bool)
def initialize(options, arguments) def initialize(options, arguments)
@options = options @options = options
@ -179,64 +169,17 @@ module Faaso
end end
class Export class Export
@arguments : Array(String) = [] of String def run(options, source : String, destination : String)
@options : Commander::Options funko = Funko::Funko.from_paths([source])[0]
# Create temporary build location
def initialize(options, arguments) dst_path = destination
@options = options if File.exists? dst_path
@arguments = arguments Log.error { "#{dst_path} already exists, not exporting #{funko.path}" }
end return 1
def run
funkos = Funko::Funko.from_paths(@arguments)
funkos.each do |funko|
# Create temporary build location
dst_path = Path.new("export", funko.name)
if File.exists? dst_path
Log.error { "#{dst_path} already exists, not exporting #{funko.path}" }
next
end
Log.info { "Exporting #{funko.path} to #{dst_path}" }
Dir.mkdir_p(dst_path)
funko.prepare_build dst_path
end
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|
Log.info { "Stopping funko... #{arg}" }
# TODO: check if funko is running
# TODO: stop funko container
# TODO: delete funko container
# TODO: remove route from reverse proxy
end
end
end
class Deploy
@arguments : Array(String) = [] of String
@options : Commander::Options
def initialize(options, arguments)
@options = options
@arguments = arguments
end
def run
@arguments.each do |arg|
Log.info { "Deploying funko... #{arg}" }
# TODO: Everything
end end
Log.info { "Exporting #{funko.path} to #{dst_path}" }
Dir.mkdir_p(dst_path)
funko.prepare_build Path[dst_path]
end end
end end
end end

View File

@ -25,6 +25,10 @@ module Funko
@[YAML::Field(ignore: true)] @[YAML::Field(ignore: true)]
property path : String = "" property path : String = ""
# Scale: how many instances of this funko should be running
@[YAML::Field(ignore: true)]
property scale = 0
# Healthcheck properties # Healthcheck properties
property healthcheck_options : String = "--interval=1m --timeout=2s --start-period=2s --retries=3" 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" property healthcheck_command : String = "curl --fail http://localhost:3000/ping || exit 1"
@ -60,37 +64,31 @@ module Funko
} }
end end
# Create an array of funkos just from names. These are limited in function # Get the number of running instances of this funko
# and can't call `prepare_build` or some other functionality def scale
def self.from_names(names : Array(String)) : Array(Funko) docker_api = Docr::API.new(Docr::Client.new)
names.map { |name| docker_api.containers.list.count { |container|
Funko.from_yaml("name: #{name}") container.@name.starts_with? "faaso-#{name}-" && container.@state == "running"
} }
end end
# Get all the funkos docker knows about. # Set the number of running instances of this funko
def self.from_docker : Array(Funko) def scale(new_scale : Int)
docker_api = Docr::API.new(Docr::Client.new) docker_api = Docr::API.new(Docr::Client.new)
names = [] of String current_scale = self.scale
return if current_scale == new_scale
# Get all containers that look like funkos if new_scale > current_scale
docker_api.containers.list( (current_scale...new_scale).each { start(create_container) }
all: true, else
).each { |container| containers.select { |contatiner| container.@state == "running" }.sort! { |i, j|
container.@names.each { |name| i.@created <=> j.@created
names << name.split("-", 2)[1].lstrip("/") if name.starts_with?("/faaso-") }.each { |container|
docker_api.containers.stop(container.@id)
current_scale -= 1
break if current_scale == new_scale
} }
} end
# Now get all images that look like funkos, since
# we can start them just fine.
docker_api.images.list.each { |image|
next if image.@repo_tags.nil?
image.@repo_tags.as(Array(String)).each { |tag|
names << tag.split("-", 2)[1].split(":", 2)[0] if tag.starts_with?("faaso-")
}
}
from_names(names.to_set.to_a.sort!)
end end
# Setup the target directory `path` with all the files needed # Setup the target directory `path` with all the files needed
@ -280,5 +278,38 @@ module Funko
docker_api.containers.start(response.@id) if autostart docker_api.containers.start(response.@id) if autostart
response.@id response.@id
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 = [] of String
# Get all containers that look like funkos
docker_api.containers.list(
all: true,
).each { |container|
container.@names.each { |name|
names << name.split("-", 2)[1].lstrip("/") if name.starts_with?("/faaso-")
}
}
# Now get all images that look like funkos, since
# we can start them just fine.
docker_api.images.list.each { |image|
next if image.@repo_tags.nil?
image.@repo_tags.as(Array(String)).each { |tag|
names << tag.split("-", 2)[1].split(":", 2)[0] if tag.starts_with?("faaso-")
}
}
from_names(names.to_set.to_a.sort!)
end
end end
end end

View File

@ -1,6 +1,21 @@
require "./faaso.cr" require "./faaso.cr"
require "colorize" require "colorize"
require "commander" 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 # Log formatter for
struct LogFormat < Log::StaticFormatter struct LogFormat < Log::StaticFormatter
@ -17,19 +32,15 @@ struct LogFormat < Log::StaticFormatter
string "[#{Time.local}] #{@entry.severity.label}: #{@entry.message}".colorize(@@colors[@entry.severity.label]) string "[#{Time.local}] #{@entry.severity.label}: #{@entry.message}".colorize(@@colors[@entry.severity.label])
end end
def self.setup(quiet : Bool, verbosity) def self.setup(verbosity)
if quiet _verbosity = [
_verbosity = Log::Severity::Fatal Log::Severity::Fatal,
else Log::Severity::Error,
_verbosity = [ Log::Severity::Warn,
Log::Severity::Fatal, Log::Severity::Info,
Log::Severity::Error, Log::Severity::Debug,
Log::Severity::Warn, Log::Severity::Trace,
Log::Severity::Info, ][[verbosity, 5].min]
Log::Severity::Debug,
Log::Severity::Trace,
][[verbosity, 5].min]
end
Log.setup( Log.setup(
_verbosity, _verbosity,
Log::IOBackend.new(io: STDERR, formatter: LogFormat) Log::IOBackend.new(io: STDERR, formatter: LogFormat)
@ -37,86 +48,12 @@ struct LogFormat < Log::StaticFormatter
end end
end end
cli = Commander::Command.new do |cmd| ans = Docopt.docopt(doc, ARGV)
cmd.use = "faaso" LogFormat.setup(ans["-v"].to_s.to_i)
cmd.long = "Functions as a Service, Open"
cmd.flags.add do |flag| case ans
flag.name = "local" when .fetch("build", false)
flag.short = "-l" Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
flag.long = "--local" when .fetch("export", false)
flag.description = "Run commands locally instead of against a FaaSO server." Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
flag.default = false
flag.persistent = true
end
cmd.flags.add do |flag|
flag.name = "quiet"
flag.short = "-q"
flag.long = "--quiet"
flag.description = "Don't log anything"
flag.default = false
flag.persistent = true
end
cmd.flags.add do |flag|
flag.name = "verbosity"
flag.short = "-v"
flag.long = "--verbosity"
flag.description = "Control the logging verbosity, 0 to 5 "
flag.default = 3
flag.persistent = true
end
cmd.commands.add do |command|
command.use = "build"
command.short = "Build a funko"
command.long = "Build a funko's Docker image and upload it to registry"
command.run do |options, arguments|
LogFormat.setup(options.@bool["quiet"], options.@int["verbosity"])
Faaso::Commands::Build.new(options, arguments).run
end
end
cmd.commands.add do |command|
command.use = "up"
command.short = "Ensure funkos are running"
command.long = "Start/unpause/create containers for requested funkos and ensure they are up."
command.run do |options, arguments|
LogFormat.setup(options.@bool["quiet"], options.@int["verbosity"])
Faaso::Commands::Up.new(options, arguments).run
end
end
cmd.commands.add do |command|
command.use = "deploy"
command.short = "Deploy latest images"
command.long = "Update containers for all funkos to latest image."
command.run do |options, arguments|
LogFormat.setup(options.@bool["quiet"], options.@int["verbosity"])
Faaso::Commands::Deploy.new(options, arguments).run
end
end
cmd.commands.add do |command|
command.use = "down"
command.short = "Stop a funko"
command.long = "Stop a funko in a container"
command.run do |options, arguments|
LogFormat.setup(options.@bool["quiet"], options.@int["verbosity"])
Faaso::Commands::Down.new(options, arguments).run
end
end
cmd.commands.add do |command|
command.use = "export"
command.short = "Export a funko to a directory"
command.long = "Exports a funko as a self-contained directory."
command.run do |options, arguments|
LogFormat.setup(options.@bool["quiet"], options.@int["verbosity"])
Faaso::Commands::Export.new(options, arguments).run
end
end
end end
Commander.run(cli, ARGV)