2019-06-09 18:08:59 +02:00
|
|
|
require "option_parser"
|
2019-06-09 16:59:05 +02:00
|
|
|
require "yaml"
|
|
|
|
require "colorize"
|
|
|
|
|
2019-06-09 18:08:59 +02:00
|
|
|
require "./config.cr"
|
2019-06-09 17:14:18 +02:00
|
|
|
|
2019-06-09 18:08:59 +02:00
|
|
|
# TODO:
|
|
|
|
# - Be more declarative about the definition of commands.
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-06-09 18:08:59 +02:00
|
|
|
require "./service/*"
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-25 18:40:27 +02:00
|
|
|
parser = uninitialized OptionParser
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-25 18:40:27 +02:00
|
|
|
args = [] of String
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-25 18:40:27 +02:00
|
|
|
force = false
|
2019-10-29 13:08:58 +01:00
|
|
|
verbose = false
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
alias Command = Proc(Array(String), Nil)
|
|
|
|
alias CommandTuple = Tuple(String, String, Command)
|
|
|
|
class CommandsList
|
|
|
|
def initialize
|
|
|
|
@commands = Array(CommandTuple).new
|
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
def push(name : String, description : String, &proc : Command)
|
|
|
|
@commands << Tuple.new(name, description, proc)
|
|
|
|
end
|
2019-06-09 18:08:59 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
def find(&block : Proc(CommandTuple, Bool))
|
|
|
|
@commands.find do |tuple|
|
|
|
|
if block.call tuple
|
|
|
|
next true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
def to_s
|
|
|
|
"commands:\n\n"+ @commands.map do |tuple|
|
|
|
|
" %-32s %s" % [tuple[0], tuple[1]]
|
|
|
|
end.join "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
commands = CommandsList.new
|
2019-10-24 17:53:36 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
commands.push "add", "Adds a service to an environment." do |args|
|
|
|
|
providers = Hash(String, String).new
|
2019-10-28 13:22:15 +01:00
|
|
|
domain = nil
|
2019-10-29 12:32:49 +01:00
|
|
|
ports = Hash(String, Int32).new
|
|
|
|
|
|
|
|
if Service.get_by_id args[0]
|
|
|
|
raise ::Service::Exception.new "'#{args[0]}' already exists. You have to remove it before re-adding it."
|
|
|
|
next
|
|
|
|
end
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
environment, service = Service.parse_id args[0]
|
2019-11-07 18:08:57 +01:00
|
|
|
name = service
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
args.each_with_index do |arg, i|
|
|
|
|
next if i == 0
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
match = arg.match /(.*)=(.*)/
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-28 13:22:15 +01:00
|
|
|
if match.nil? || match.size < 2
|
2019-10-24 20:31:38 +02:00
|
|
|
raise ::Service::Exception.new "usage: service add <service> <token=provider>"
|
|
|
|
next
|
|
|
|
end
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-28 13:22:15 +01:00
|
|
|
_, key, value = match
|
|
|
|
|
2019-11-07 18:08:57 +01:00
|
|
|
if key == "type"
|
|
|
|
service = value
|
|
|
|
elsif key == "domain"
|
2019-10-28 13:22:15 +01:00
|
|
|
domain = value
|
2019-10-29 12:32:49 +01:00
|
|
|
elsif match = key.match /^port:(.*)/
|
|
|
|
ports[match[1]] = value.to_i
|
2019-10-28 13:22:15 +01:00
|
|
|
else
|
|
|
|
providers[key] = value
|
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
2019-08-10 22:29:11 +02:00
|
|
|
|
2019-10-29 12:32:49 +01:00
|
|
|
# FIXME: Some of that code is ugly and should not even be here.
|
2019-10-24 20:31:38 +02:00
|
|
|
Service.new(service, environment).tap do |service|
|
2019-10-28 13:22:15 +01:00
|
|
|
if domain
|
|
|
|
service.domain = domain
|
|
|
|
end
|
|
|
|
|
2019-11-07 18:08:57 +01:00
|
|
|
if name != service
|
|
|
|
service.name = name
|
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
service.consumes.each do |token|
|
|
|
|
provider = providers[token.token]?
|
2019-08-10 22:02:11 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
if provider.nil?
|
|
|
|
provider = service.get_default_provider token.token
|
2019-08-10 22:02:11 +02:00
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
|
2019-10-28 14:06:05 +01:00
|
|
|
next if provider.nil? && token.optional
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
if provider.nil?
|
|
|
|
STDERR.puts "This service consumes a “#{token.token}” token, but you have not specified what other service is supposed to provide it."
|
|
|
|
STDERR.puts "Use the `service add #{args[1]} #{token.token}=<provider>` syntax to specify it."
|
|
|
|
exit 1
|
2019-06-09 18:08:59 +02:00
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
service.providers[token.token] = provider
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
2019-10-28 13:22:15 +01:00
|
|
|
|
2019-10-29 12:32:49 +01:00
|
|
|
service.port_definitions.each do |definition|
|
|
|
|
name = definition.name
|
|
|
|
number = ports[name]?
|
|
|
|
|
|
|
|
if number.nil?
|
|
|
|
default_port = definition.default_value
|
|
|
|
|
|
|
|
if default_port && ! Service.is_port_used default_port, ports.map { |k, v| v }
|
|
|
|
number = default_port
|
|
|
|
ports[name] = number
|
|
|
|
end
|
|
|
|
elsif Service.is_port_used number
|
|
|
|
raise Service::Exception.new "Port #{number} is already reserved by another service."
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if number.nil?
|
|
|
|
number = Service.get_free_port ports.map { |k, v| v }
|
|
|
|
ports[name] = number
|
|
|
|
end
|
|
|
|
|
|
|
|
# FIXME: Check the port is not duplicated here.
|
|
|
|
|
|
|
|
service.ports[definition.name] = number
|
|
|
|
end
|
|
|
|
|
2019-10-28 13:22:15 +01:00
|
|
|
if service.requires_domain && !service.domain
|
|
|
|
raise Service::Exception.new "'#{service.name}' requires a domain= parameter to be provided!"
|
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
end.write RC_DIRECTORY
|
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
commands.push "del", "Removes a service from an environment." do |args|
|
2019-10-25 13:15:44 +02:00
|
|
|
if args.size < 1
|
|
|
|
STDERR.puts "usage: service del <id> [id [...]]"
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
|
2019-10-25 18:40:27 +02:00
|
|
|
rvalue = 0
|
2019-10-25 13:15:44 +02:00
|
|
|
args.each do |id|
|
2019-10-25 18:40:27 +02:00
|
|
|
service = Service.get_by_id(id)
|
|
|
|
|
|
|
|
if service.nil?
|
|
|
|
STDERR.puts "#{id}: no such service"
|
|
|
|
rvalue = 1
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
revdeps = service.reverse_dependencies
|
|
|
|
|
|
|
|
if revdeps.size > 1 && ! force
|
|
|
|
STDERR.puts "#{id}: has reverse dependencies, use -f to force"
|
|
|
|
rvalue = 2
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
revdeps.reverse.each do |service|
|
|
|
|
next if ! service.running? PID_DIRECTORY
|
|
|
|
|
|
|
|
puts "stopping #{service.to_s}"
|
|
|
|
service.stop PID_DIRECTORY
|
|
|
|
end
|
|
|
|
|
|
|
|
revdeps.reverse.each do |service|
|
|
|
|
service.remove RC_DIRECTORY
|
|
|
|
end
|
2019-10-25 13:15:44 +02:00
|
|
|
end
|
2019-10-25 18:40:27 +02:00
|
|
|
|
|
|
|
exit rvalue
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
commands.push "start", "Starts a service." do
|
|
|
|
services = args.map do |arg|
|
|
|
|
service = Service.get_by_id(arg)
|
|
|
|
|
|
|
|
unless service
|
|
|
|
raise Service::Exception.new "Service '#{arg}' does not exist."
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
service
|
|
|
|
end
|
|
|
|
|
|
|
|
services.each do |service|
|
|
|
|
service.dependency_tree.flatten.reverse.each do |service|
|
|
|
|
next if service.running? PID_DIRECTORY
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-11-08 01:08:57 +01:00
|
|
|
if service.non_runnable
|
|
|
|
STDOUT << "#{service.to_s}: non runnable\n"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
puts "starting #{service.to_s}"
|
|
|
|
service.start PID_DIRECTORY, LOG_DIRECTORY
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
commands.push "stop", "Stops a running service." do |args|
|
|
|
|
services = args.map do |arg|
|
|
|
|
service = Service.get_by_id(arg)
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
unless service
|
|
|
|
raise Service::Exception.new "Service '#{arg}' does not exist."
|
2019-06-10 14:32:30 +02:00
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
service
|
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
services.each do |service|
|
|
|
|
next if ! service.running? PID_DIRECTORY
|
2019-10-25 18:40:27 +02:00
|
|
|
|
|
|
|
service.reverse_dependencies.reverse.each do |service|
|
2019-10-24 20:31:38 +02:00
|
|
|
next if ! service.running? PID_DIRECTORY
|
2019-06-09 16:59:05 +02:00
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
puts "stopping #{service.to_s}"
|
|
|
|
service.stop PID_DIRECTORY
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
commands.push "status", "Prints the status of services." do |args|
|
2019-10-29 13:08:58 +01:00
|
|
|
ENV["SERVICE_VERBOSE"] = verbose.to_s
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
child = Process.run "#{OWN_LIBEXEC_DIR}/status", args,
|
|
|
|
output: Process::Redirect::Inherit,
|
|
|
|
error: Process::Redirect::Inherit
|
|
|
|
return_value = (child.exit_status / 256).to_i
|
|
|
|
|
|
|
|
# Errors not registered here should probably be verbose in `status`.
|
|
|
|
if return_value == 1
|
|
|
|
STDERR << "No such service.\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
exit return_value
|
|
|
|
end
|
|
|
|
|
|
|
|
commands.push "show", "Shows a service's configuration and state." do |args|
|
2019-10-25 12:28:39 +02:00
|
|
|
if args.size < 1
|
|
|
|
STDERR << "usage: service show <id> [id [...]]\n"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
args.each do |arg|
|
|
|
|
environment_name, service_name = Service.parse_id arg
|
2019-10-24 20:31:38 +02:00
|
|
|
|
2019-10-25 12:28:39 +02:00
|
|
|
service = Service.all.find do |service|
|
|
|
|
service.name == service_name &&
|
|
|
|
service.environment.name == environment_name
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
|
2019-10-25 12:28:39 +02:00
|
|
|
if service
|
|
|
|
puts service.summary
|
|
|
|
else
|
|
|
|
STDERR << "No such service is registered.\n"
|
|
|
|
exit 2
|
|
|
|
end
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
commands.push "add-environment", "Creates a new (empty) environment." do |arg|
|
2019-10-25 17:59:53 +02:00
|
|
|
if arg.size != 1
|
|
|
|
STDERR.puts "usage: service add-environment <name>"
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
|
|
|
|
Environment.new(arg[0]).write ENVIRONMENTS_DIRECTORY
|
2019-10-24 20:31:38 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
commands.push "list-environments", "Lists all currently defined environments.s", do |arg|
|
|
|
|
Environment.all.map do |env|
|
|
|
|
puts env.to_s
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-10-25 17:59:53 +02:00
|
|
|
commands.push "del-environment", "Removes an empty environment." do |arg|
|
|
|
|
rvalue = 0
|
|
|
|
|
|
|
|
arg.each do |arg|
|
|
|
|
environment = Environment.all.find &.name.==(arg)
|
|
|
|
|
|
|
|
if environment.nil?
|
|
|
|
STDERR.puts "#{arg}: no such environment"
|
|
|
|
rvalue = 2
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
if Service.all.select(&.environment.name.==(environment.name)).size > 0
|
|
|
|
STDERR.puts "#{arg}: is not empty"
|
|
|
|
rvalue = 1
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
environment.remove ENVIRONMENTS_DIRECTORY
|
|
|
|
end
|
|
|
|
|
|
|
|
exit rvalue
|
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
commands.push "help", "Prints this help message." do
|
|
|
|
puts parser
|
|
|
|
puts
|
|
|
|
puts commands.to_s
|
|
|
|
end
|
|
|
|
|
2019-10-25 18:40:27 +02:00
|
|
|
parser = OptionParser.parse do |cli|
|
|
|
|
cli.banner = "usage: service <command> [options]\n" +
|
|
|
|
"options:\n"
|
|
|
|
|
|
|
|
cli.on "-h", "--help", "Prints this help message." do
|
|
|
|
puts cli
|
|
|
|
puts
|
|
|
|
puts commands.to_s
|
|
|
|
exit 0
|
|
|
|
end
|
|
|
|
cli.on "-f", "--force", "Ignores warnings and executes dangerous operations." do
|
|
|
|
force = true
|
|
|
|
end
|
2019-10-29 13:08:58 +01:00
|
|
|
cli.on "-v", "--verbose", "Prints more data when doing things." do
|
|
|
|
verbose = true
|
|
|
|
end
|
2019-10-25 18:40:27 +02:00
|
|
|
|
|
|
|
cli.unknown_args do |x|
|
|
|
|
args = x
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-10-24 20:31:38 +02:00
|
|
|
command = commands.find &.[0].==(args[0]?)
|
|
|
|
if command.nil?
|
|
|
|
STDERR << parser << "\n"
|
|
|
|
STDERR << "\n"
|
|
|
|
STDERR << commands.to_s << "\n"
|
|
|
|
exit 1
|
|
|
|
end
|
|
|
|
|
|
|
|
ServiceDefinition.load SERVICES_DIRECTORY
|
|
|
|
Environment.load ENVIRONMENTS_DIRECTORY
|
|
|
|
Service.load RC_DIRECTORY
|
|
|
|
|
|
|
|
begin
|
|
|
|
args.shift
|
|
|
|
command.[2].call(args)
|
2019-06-09 18:08:59 +02:00
|
|
|
rescue e : Service::Exception
|
|
|
|
STDERR << e.message << "\n"
|
|
|
|
exit 2
|
2019-06-09 16:59:05 +02:00
|
|
|
end
|
|
|
|
|