require "option_parser" require "yaml" require "colorize" require "./config.cr" # TODO: # - Be more declarative about the definition of commands. require "./service/*" args = [] of String parser = OptionParser.parse do |parser| parser.banner = "usage: service [options]\n" + "options:\n" parser.on "-h", "--help", "Prints this help message." do puts parser exit 0 end parser.unknown_args do |x| args = x end end alias Command = Proc(Array(String), Nil) alias CommandTuple = Tuple(String, String, Command) class CommandsList def initialize @commands = Array(CommandTuple).new end def push(name : String, description : String, &proc : Command) @commands << Tuple.new(name, description, proc) end def find(&block : Proc(CommandTuple, Bool)) @commands.find do |tuple| if block.call tuple next true end end end def to_s "commands:\n\n"+ @commands.map do |tuple| " %-32s %s" % [tuple[0], tuple[1]] end.join "\n" end end commands = CommandsList.new commands.push "add", "Adds a service to an environment." do |args| providers = Hash(String, String).new environment, service = Service.parse_id args[0] args.each_with_index do |arg, i| next if i == 0 match = arg.match /(.*)=(.*)/ if match.nil? raise ::Service::Exception.new "usage: service add " next end providers[match[1]] = match[2] end Service.new(service, environment).tap do |service| service.consumes.each do |token| provider = providers[token.token]? if provider.nil? provider = service.get_default_provider token.token end 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}=` syntax to specify it." exit 1 end service.providers[token.token] = provider end pp! service.providers end.write RC_DIRECTORY end commands.push "del", "Removes a service from an environment." do |args| if args.size < 1 STDERR.puts "usage: service del [id [...]]" exit 1 end args.each do |id| Service.get_by_id(id).try &.remove RC_DIRECTORY end end 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." end service end services.each do |service| service.dependency_tree.flatten.reverse.each do |service| next if service.running? PID_DIRECTORY puts "starting #{service.to_s}" service.start PID_DIRECTORY, LOG_DIRECTORY end end end commands.push "stop", "Stops a running service." do |args| services = args.map do |arg| service = Service.get_by_id(arg) unless service raise Service::Exception.new "Service '#{arg}' does not exist." end service end services.each do |service| # FIXME: Build revdep tree and stop services started as dependencies? next if ! service.running? PID_DIRECTORY # FIXME: Should we remove duplicate services from the # tree once flattened? service.reverse_dependency_tree.flatten.reverse.each do |service| next if ! service.running? PID_DIRECTORY puts "stopping #{service.to_s}" service.stop PID_DIRECTORY end end end commands.push "status", "Prints the status of services." do |args| 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| if args.size < 1 STDERR << "usage: service show [id [...]]\n" next end args.each do |arg| environment_name, service_name = Service.parse_id arg service = Service.all.find do |service| service.name == service_name && service.environment.name == environment_name end if service puts service.summary else STDERR << "No such service is registered.\n" exit 2 end end end commands.push "add-environment", "Creates a new (empty) environment." do |arg| if arg.size != 1 STDERR.puts "usage: service add-environment " exit 1 end Environment.new(arg[0]).write ENVIRONMENTS_DIRECTORY end commands.push "list-environments", "Lists all currently defined environments.s", do |arg| Environment.all.map do |env| puts env.to_s end end 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 commands.push "help", "Prints this help message." do puts parser puts puts commands.to_s end 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) rescue e : Service::Exception STDERR << e.message << "\n" exit 2 end