diff --git a/shard.yml b/shard.yml index 3df3584..a83520b 100644 --- a/shard.yml +++ b/shard.yml @@ -8,6 +8,8 @@ description: | Services management tool. dependencies: + weird-crystal-base: + git: https://git.karchnu.fr/WeirdOS/weird-crystal-base specparser: git: https://git.karchnu.fr/WeirdOS/recipes-parser crinja: diff --git a/src/service.cr b/src/service.cr index 3134072..dd48161 100644 --- a/src/service.cr +++ b/src/service.cr @@ -2,10 +2,9 @@ require "option_parser" require "yaml" require "colorize" -require "./config.cr" +require "weird-crystal-base" -# TODO: -# - Be more declarative about the definition of commands. +require "./config.cr" require "./service/*" @@ -16,7 +15,13 @@ args = [] of String force = 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) class CommandsList def initialize @@ -43,7 +48,7 @@ class CommandsList end 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 domain = nil 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 raise Service::Exception.new "'#{service.name}' requires a domain= parameter to be provided!" 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 -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 STDERR.puts "usage: service del [id [...]]" exit 1 @@ -150,7 +171,7 @@ commands.push "del", "Removes a service from an environment." do |args| service = Service.get_by_id(id) if service.nil? - STDERR.puts "#{id}: no such service" + context.error "#{id}: no such service" rvalue = 1 next end @@ -158,7 +179,7 @@ commands.push "del", "Removes a service from an environment." do |args| revdeps = service.reverse_dependencies 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 next end @@ -166,20 +187,18 @@ commands.push "del", "Removes a service from an environment." do |args| revdeps.reverse.each do |service| next if ! service.running? PID_DIRECTORY - puts "stopping #{service.to_s}" - service.stop PID_DIRECTORY + service.stop context end revdeps.reverse.each do |service| - puts "removing #{service.to_s}" - service.remove RC_DIRECTORY + service.remove context end end exit rvalue end -commands.push "start", "Starts a service." do +commands.push "start", "Starts a service." do |context, args| services = args.map do |arg| service = Service.get_by_id(arg) @@ -192,20 +211,12 @@ commands.push "start", "Starts a service." do services.each do |service| service.dependency_tree.flatten.reverse.each do |service| - next if service.running? PID_DIRECTORY - - if service.non_runnable - STDOUT << "#{service.to_s}: non runnable\n" - next - end - - puts "starting #{service.to_s}" - service.start PID_DIRECTORY, LOG_DIRECTORY + service.start context 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| 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| next if ! service.running? PID_DIRECTORY - puts "stopping #{service.to_s}" - service.stop PID_DIRECTORY + service.stop context 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 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 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 STDERR << "usage: service show [id [...]]\n" next @@ -267,25 +277,25 @@ commands.push "show", "Shows a service's configuration and state." do |args| end end -commands.push "add-environment", "Creates a new (empty) environment." do |arg| - if arg.size != 1 +commands.push "add-environment", "Creates a new (empty) environment." do |context, args| + if args.size != 1 STDERR.puts "usage: service add-environment " exit 1 end - Environment.new(arg[0]).write ENVIRONMENTS_DIRECTORY + Environment.new(args[0]).write ENVIRONMENTS_DIRECTORY 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| puts env.to_s 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 - arg.each do |arg| + args.each do |arg| environment = Environment.all.find &.name.==(arg) if environment.nil? @@ -346,11 +356,13 @@ ServiceDefinition.load SERVICES_DIRECTORY Environment.load ENVIRONMENTS_DIRECTORY Service.load RC_DIRECTORY +context = Service::Context.new + begin args.shift - command.[2].call(args) + command.[2].call(context, args) rescue e : Service::Exception - STDERR << e.message << "\n" + context.error e.message exit 2 end diff --git a/src/service/service.cr b/src/service/service.cr index 69877dc..59e5f1a 100644 --- a/src/service/service.cr +++ b/src/service/service.cr @@ -227,6 +227,9 @@ class Service end # 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) string.gsub /\${[a-zA-Z_]+}/ do |match| match = match[2..match.size-2] @@ -235,6 +238,8 @@ class Service @environment.name elsif match.downcase == "service_root" root + elsif match.downcase == "service_name" + name else "" end @@ -245,7 +250,15 @@ class Service @environment.files + @reference.files 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 files.each do |file| @@ -262,7 +275,7 @@ class Service next unless run_hook - puts " - Creating #{file.name}" + context.info "Creating #{file.name}" child = Process.fork do Dir.cd root @@ -279,12 +292,14 @@ class Service end end + return if non_runnable + # FIXME: Should evaluate be used in split_command? What namespace should split_command use? command, args = split_command command args.map! do |arg| evaluate arg end 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" stderr_file = File.open "#{base_log_name}.err", "w" @@ -303,20 +318,19 @@ class Service env: build_environment end - self.save_pid pid_dir, process.pid + self.save_pid context.pid_directory, process.pid end # TODO: # - Custom shutdown commands. # - Should we wait for the process to die? # - Shouldn’t we remove the pid file? - def stop(pid_dir : String) - if non_runnable - STDOUT << ("%-20s " % "#{full_id}:").colorize(:white).to_s - STDOUT << "non runnable" - return - end - _pid = pid pid_dir + def stop(context : Context) + return if non_runnable + + context.title "Stopping #{to_s}" + + _pid = pid context.pid_directory if _pid command = stop_command @@ -329,7 +343,7 @@ class Service end Process.waitpid _pid - File.delete(get_pid_file pid_dir) + File.delete(get_pid_file context.pid_directory) else # Already stopped or dead, nothing to be done here. end @@ -457,15 +471,19 @@ class Service File.write "#{path}/#{name}.#{@environment.name}.spec", to_spec end - def remove(path) + def remove(context) + context.title "Removing #{to_s}" + 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 child = Process.fork do Dir.cd root - exit 0 unless File.exists? file.file_path + exit 0 unless File.exists? file_path if command Process.exec "sh", ["-c", command], @@ -473,7 +491,7 @@ class Service error: Process::Redirect::Inherit, env: build_environment else - FileUtils.rm_rf file.file_path + FileUtils.rm_rf file_path end end.wait @@ -483,7 +501,7 @@ class Service end end - File.delete "#{path}/#{name}.#{@environment.name}.spec" + File.delete "#{context.services_directory}/#{name}.#{@environment.name}.spec" end def self.get_by_id(id)