Switched to docopt because all CLI things suck
This commit is contained in:
parent
bef6ded369
commit
11d7cf1f9f
@ -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
|
||||||
|
22
shard.yml
22
shard.yml
@ -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
|
||||||
|
85
src/faaso.cr
85
src/faaso.cr
@ -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
|
||||||
|
81
src/funko.cr
81
src/funko.cr
@ -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
|
||||||
|
127
src/main.cr
127
src/main.cr
@ -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)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user