Improved UI a bit.
parent
32dd294c1b
commit
5852a1b818
|
@ -8,6 +8,8 @@ description: |
|
||||||
Services management tool.
|
Services management tool.
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
weird-crystal-base:
|
||||||
|
git: https://git.karchnu.fr/WeirdOS/weird-crystal-base
|
||||||
specparser:
|
specparser:
|
||||||
git: https://git.karchnu.fr/WeirdOS/recipes-parser
|
git: https://git.karchnu.fr/WeirdOS/recipes-parser
|
||||||
crinja:
|
crinja:
|
||||||
|
|
|
@ -2,10 +2,9 @@ require "option_parser"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
require "colorize"
|
require "colorize"
|
||||||
|
|
||||||
require "./config.cr"
|
require "weird-crystal-base"
|
||||||
|
|
||||||
# TODO:
|
require "./config.cr"
|
||||||
# - Be more declarative about the definition of commands.
|
|
||||||
|
|
||||||
require "./service/*"
|
require "./service/*"
|
||||||
|
|
||||||
|
@ -16,7 +15,13 @@ args = [] of String
|
||||||
force = false
|
force = false
|
||||||
verbose = false
|
verbose = false
|
||||||
|
|
||||||
alias Command = Proc(Array(String), Nil)
|
class Service::Context < Weird::Base
|
||||||
|
property pid_directory = PID_DIRECTORY
|
||||||
|
property log_directory = LOG_DIRECTORY
|
||||||
|
property services_directory = RC_DIRECTORY
|
||||||
|
end
|
||||||
|
|
||||||
|
alias Command = Proc(Service::Context, Array(String), Nil)
|
||||||
alias CommandTuple = Tuple(String, String, Command)
|
alias CommandTuple = Tuple(String, String, Command)
|
||||||
class CommandsList
|
class CommandsList
|
||||||
def initialize
|
def initialize
|
||||||
|
@ -43,7 +48,7 @@ class CommandsList
|
||||||
end
|
end
|
||||||
commands = CommandsList.new
|
commands = CommandsList.new
|
||||||
|
|
||||||
commands.push "add", "Adds a service to an environment." do |args|
|
commands.push "add", "Adds a service to an environment." do |context, args|
|
||||||
providers = Hash(String, String).new
|
providers = Hash(String, String).new
|
||||||
domain = nil
|
domain = nil
|
||||||
ports = Hash(String, Int32).new
|
ports = Hash(String, Int32).new
|
||||||
|
@ -136,10 +141,26 @@ commands.push "add", "Adds a service to an environment." do |args|
|
||||||
if service.requires_domain && !service.domain
|
if service.requires_domain && !service.domain
|
||||||
raise Service::Exception.new "'#{service.name}' requires a domain= parameter to be provided!"
|
raise Service::Exception.new "'#{service.name}' requires a domain= parameter to be provided!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context.title "Adding #{service.to_s}"
|
||||||
|
|
||||||
|
service.providers.each do |token, provider|
|
||||||
|
context.info "'#{token}' provider will be #{provider.to_s}"
|
||||||
|
end
|
||||||
|
|
||||||
|
service.port_definitions.each do |definition|
|
||||||
|
name = definition.name
|
||||||
|
|
||||||
|
context.info "Port for '#{name}' is #{service.ports[name]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if service.domain
|
||||||
|
context.info "Domain is '#{service.domain}'"
|
||||||
|
end
|
||||||
end.write RC_DIRECTORY
|
end.write RC_DIRECTORY
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "del", "Removes a service from an environment." do |args|
|
commands.push "del", "Removes a service from an environment." do |context, args|
|
||||||
if args.size < 1
|
if args.size < 1
|
||||||
STDERR.puts "usage: service del <id> [id [...]]"
|
STDERR.puts "usage: service del <id> [id [...]]"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -150,7 +171,7 @@ commands.push "del", "Removes a service from an environment." do |args|
|
||||||
service = Service.get_by_id(id)
|
service = Service.get_by_id(id)
|
||||||
|
|
||||||
if service.nil?
|
if service.nil?
|
||||||
STDERR.puts "#{id}: no such service"
|
context.error "#{id}: no such service"
|
||||||
rvalue = 1
|
rvalue = 1
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
@ -158,7 +179,7 @@ commands.push "del", "Removes a service from an environment." do |args|
|
||||||
revdeps = service.reverse_dependencies
|
revdeps = service.reverse_dependencies
|
||||||
|
|
||||||
if revdeps.size > 1 && ! force
|
if revdeps.size > 1 && ! force
|
||||||
STDERR.puts "#{id}: has reverse dependencies, use -f to force"
|
context.error "#{id}: has reverse dependencies, use -f to force"
|
||||||
rvalue = 2
|
rvalue = 2
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
@ -166,20 +187,18 @@ commands.push "del", "Removes a service from an environment." do |args|
|
||||||
revdeps.reverse.each do |service|
|
revdeps.reverse.each do |service|
|
||||||
next if ! service.running? PID_DIRECTORY
|
next if ! service.running? PID_DIRECTORY
|
||||||
|
|
||||||
puts "stopping #{service.to_s}"
|
service.stop context
|
||||||
service.stop PID_DIRECTORY
|
|
||||||
end
|
end
|
||||||
|
|
||||||
revdeps.reverse.each do |service|
|
revdeps.reverse.each do |service|
|
||||||
puts "removing #{service.to_s}"
|
service.remove context
|
||||||
service.remove RC_DIRECTORY
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
exit rvalue
|
exit rvalue
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "start", "Starts a service." do
|
commands.push "start", "Starts a service." do |context, args|
|
||||||
services = args.map do |arg|
|
services = args.map do |arg|
|
||||||
service = Service.get_by_id(arg)
|
service = Service.get_by_id(arg)
|
||||||
|
|
||||||
|
@ -192,20 +211,12 @@ commands.push "start", "Starts a service." do
|
||||||
|
|
||||||
services.each do |service|
|
services.each do |service|
|
||||||
service.dependency_tree.flatten.reverse.each do |service|
|
service.dependency_tree.flatten.reverse.each do |service|
|
||||||
next if service.running? PID_DIRECTORY
|
service.start context
|
||||||
|
|
||||||
if service.non_runnable
|
|
||||||
STDOUT << "#{service.to_s}: non runnable\n"
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "starting #{service.to_s}"
|
|
||||||
service.start PID_DIRECTORY, LOG_DIRECTORY
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "stop", "Stops a running service." do |args|
|
commands.push "stop", "Stops a running service." do |context, args|
|
||||||
services = args.map do |arg|
|
services = args.map do |arg|
|
||||||
service = Service.get_by_id(arg)
|
service = Service.get_by_id(arg)
|
||||||
|
|
||||||
|
@ -222,13 +233,12 @@ commands.push "stop", "Stops a running service." do |args|
|
||||||
service.reverse_dependencies.reverse.each do |service|
|
service.reverse_dependencies.reverse.each do |service|
|
||||||
next if ! service.running? PID_DIRECTORY
|
next if ! service.running? PID_DIRECTORY
|
||||||
|
|
||||||
puts "stopping #{service.to_s}"
|
service.stop context
|
||||||
service.stop PID_DIRECTORY
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "status", "Prints the status of services." do |args|
|
commands.push "status", "Prints the status of services." do |context, args|
|
||||||
ENV["SERVICE_VERBOSE"] = verbose.to_s
|
ENV["SERVICE_VERBOSE"] = verbose.to_s
|
||||||
|
|
||||||
child = Process.run "#{OWN_LIBEXEC_DIR}/status", args,
|
child = Process.run "#{OWN_LIBEXEC_DIR}/status", args,
|
||||||
|
@ -244,7 +254,7 @@ commands.push "status", "Prints the status of services." do |args|
|
||||||
exit return_value
|
exit return_value
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "show", "Shows a service's configuration and state." do |args|
|
commands.push "show", "Shows a service's configuration and state." do |context, args|
|
||||||
if args.size < 1
|
if args.size < 1
|
||||||
STDERR << "usage: service show <id> [id [...]]\n"
|
STDERR << "usage: service show <id> [id [...]]\n"
|
||||||
next
|
next
|
||||||
|
@ -267,25 +277,25 @@ commands.push "show", "Shows a service's configuration and state." do |args|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "add-environment", "Creates a new (empty) environment." do |arg|
|
commands.push "add-environment", "Creates a new (empty) environment." do |context, args|
|
||||||
if arg.size != 1
|
if args.size != 1
|
||||||
STDERR.puts "usage: service add-environment <name>"
|
STDERR.puts "usage: service add-environment <name>"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
Environment.new(arg[0]).write ENVIRONMENTS_DIRECTORY
|
Environment.new(args[0]).write ENVIRONMENTS_DIRECTORY
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "list-environments", "Lists all currently defined environments.s", do |arg|
|
commands.push "list-environments", "Lists all currently defined environments.s", do |context, args|
|
||||||
Environment.all.map do |env|
|
Environment.all.map do |env|
|
||||||
puts env.to_s
|
puts env.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commands.push "del-environment", "Removes an empty environment." do |arg|
|
commands.push "del-environment", "Removes an empty environment." do |context, args|
|
||||||
rvalue = 0
|
rvalue = 0
|
||||||
|
|
||||||
arg.each do |arg|
|
args.each do |arg|
|
||||||
environment = Environment.all.find &.name.==(arg)
|
environment = Environment.all.find &.name.==(arg)
|
||||||
|
|
||||||
if environment.nil?
|
if environment.nil?
|
||||||
|
@ -346,11 +356,13 @@ ServiceDefinition.load SERVICES_DIRECTORY
|
||||||
Environment.load ENVIRONMENTS_DIRECTORY
|
Environment.load ENVIRONMENTS_DIRECTORY
|
||||||
Service.load RC_DIRECTORY
|
Service.load RC_DIRECTORY
|
||||||
|
|
||||||
|
context = Service::Context.new
|
||||||
|
|
||||||
begin
|
begin
|
||||||
args.shift
|
args.shift
|
||||||
command.[2].call(args)
|
command.[2].call(context, args)
|
||||||
rescue e : Service::Exception
|
rescue e : Service::Exception
|
||||||
STDERR << e.message << "\n"
|
context.error e.message
|
||||||
exit 2
|
exit 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -227,6 +227,9 @@ class Service
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME: Is working on ${} really a good idea?
|
# FIXME: Is working on ${} really a good idea?
|
||||||
|
# FIXME: There should be a unified way of obtaining a service’s
|
||||||
|
# environment, and we should take our variables from that
|
||||||
|
# instead of hardcoding everything.
|
||||||
private def evaluate(string)
|
private def evaluate(string)
|
||||||
string.gsub /\${[a-zA-Z_]+}/ do |match|
|
string.gsub /\${[a-zA-Z_]+}/ do |match|
|
||||||
match = match[2..match.size-2]
|
match = match[2..match.size-2]
|
||||||
|
@ -235,6 +238,8 @@ class Service
|
||||||
@environment.name
|
@environment.name
|
||||||
elsif match.downcase == "service_root"
|
elsif match.downcase == "service_root"
|
||||||
root
|
root
|
||||||
|
elsif match.downcase == "service_name"
|
||||||
|
name
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
@ -245,7 +250,15 @@ class Service
|
||||||
@environment.files + @reference.files
|
@environment.files + @reference.files
|
||||||
end
|
end
|
||||||
|
|
||||||
def start(pid_dir : String, log_dir : String)
|
def start(context : Context)
|
||||||
|
return if running? context.pid_directory
|
||||||
|
|
||||||
|
if non_runnable
|
||||||
|
context.title "Setting up #{to_s}"
|
||||||
|
else
|
||||||
|
context.title "Starting #{to_s}"
|
||||||
|
end
|
||||||
|
|
||||||
FileUtils.mkdir_p root
|
FileUtils.mkdir_p root
|
||||||
|
|
||||||
files.each do |file|
|
files.each do |file|
|
||||||
|
@ -262,7 +275,7 @@ class Service
|
||||||
|
|
||||||
next unless run_hook
|
next unless run_hook
|
||||||
|
|
||||||
puts " - Creating #{file.name}"
|
context.info "Creating #{file.name}"
|
||||||
|
|
||||||
child = Process.fork do
|
child = Process.fork do
|
||||||
Dir.cd root
|
Dir.cd root
|
||||||
|
@ -279,12 +292,14 @@ class Service
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return if non_runnable
|
||||||
|
|
||||||
# FIXME: Should evaluate be used in split_command? What namespace should split_command use?
|
# FIXME: Should evaluate be used in split_command? What namespace should split_command use?
|
||||||
command, args = split_command command
|
command, args = split_command command
|
||||||
args.map! do |arg| evaluate arg end
|
args.map! do |arg| evaluate arg end
|
||||||
|
|
||||||
process = Process.fork do
|
process = Process.fork do
|
||||||
base_log_name = "#{log_dir}/#{name}.#{@environment.name}"
|
base_log_name = "#{context.log_directory}/#{name}.#{@environment.name}"
|
||||||
stdout_file = File.open "#{base_log_name}.out", "w"
|
stdout_file = File.open "#{base_log_name}.out", "w"
|
||||||
stderr_file = File.open "#{base_log_name}.err", "w"
|
stderr_file = File.open "#{base_log_name}.err", "w"
|
||||||
|
|
||||||
|
@ -303,20 +318,19 @@ class Service
|
||||||
env: build_environment
|
env: build_environment
|
||||||
end
|
end
|
||||||
|
|
||||||
self.save_pid pid_dir, process.pid
|
self.save_pid context.pid_directory, process.pid
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - Custom shutdown commands.
|
# - Custom shutdown commands.
|
||||||
# - Should we wait for the process to die?
|
# - Should we wait for the process to die?
|
||||||
# - Shouldn’t we remove the pid file?
|
# - Shouldn’t we remove the pid file?
|
||||||
def stop(pid_dir : String)
|
def stop(context : Context)
|
||||||
if non_runnable
|
return if non_runnable
|
||||||
STDOUT << ("%-20s " % "#{full_id}:").colorize(:white).to_s
|
|
||||||
STDOUT << "non runnable"
|
context.title "Stopping #{to_s}"
|
||||||
return
|
|
||||||
end
|
_pid = pid context.pid_directory
|
||||||
_pid = pid pid_dir
|
|
||||||
|
|
||||||
if _pid
|
if _pid
|
||||||
command = stop_command
|
command = stop_command
|
||||||
|
@ -329,7 +343,7 @@ class Service
|
||||||
end
|
end
|
||||||
|
|
||||||
Process.waitpid _pid
|
Process.waitpid _pid
|
||||||
File.delete(get_pid_file pid_dir)
|
File.delete(get_pid_file context.pid_directory)
|
||||||
else
|
else
|
||||||
# Already stopped or dead, nothing to be done here.
|
# Already stopped or dead, nothing to be done here.
|
||||||
end
|
end
|
||||||
|
@ -457,15 +471,19 @@ class Service
|
||||||
File.write "#{path}/#{name}.#{@environment.name}.spec", to_spec
|
File.write "#{path}/#{name}.#{@environment.name}.spec", to_spec
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(path)
|
def remove(context)
|
||||||
|
context.title "Removing #{to_s}"
|
||||||
|
|
||||||
files.reverse.each do |file|
|
files.reverse.each do |file|
|
||||||
puts " - removing '#{file.file_path}'"
|
file_path = evaluate file.file_path
|
||||||
|
|
||||||
|
context.info "Removing '#{file_path}'"
|
||||||
command = file.deletion_command
|
command = file.deletion_command
|
||||||
|
|
||||||
child = Process.fork do
|
child = Process.fork do
|
||||||
Dir.cd root
|
Dir.cd root
|
||||||
|
|
||||||
exit 0 unless File.exists? file.file_path
|
exit 0 unless File.exists? file_path
|
||||||
|
|
||||||
if command
|
if command
|
||||||
Process.exec "sh", ["-c", command],
|
Process.exec "sh", ["-c", command],
|
||||||
|
@ -473,7 +491,7 @@ class Service
|
||||||
error: Process::Redirect::Inherit,
|
error: Process::Redirect::Inherit,
|
||||||
env: build_environment
|
env: build_environment
|
||||||
else
|
else
|
||||||
FileUtils.rm_rf file.file_path
|
FileUtils.rm_rf file_path
|
||||||
end
|
end
|
||||||
end.wait
|
end.wait
|
||||||
|
|
||||||
|
@ -483,7 +501,7 @@ class Service
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
File.delete "#{path}/#{name}.#{@environment.name}.spec"
|
File.delete "#{context.services_directory}/#{name}.#{@environment.name}.spec"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get_by_id(id)
|
def self.get_by_id(id)
|
||||||
|
|
Loading…
Reference in New Issue