diff --git a/scripts/test.sh b/scripts/test.sh index 1eb0ed3..b5063d7 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -75,6 +75,12 @@ bin/procodile status --simple |grep '^Issues || app1 has 0 instances (should hav header '(5) Checking procodile stop ...' bin/procodile stop && sleep 3 bin/procodile status --simple |grep '^Issues || app1 has 0 instances (should have 1), app2 has 0 instances (should have 1), app3 has 0 instances (should have 1)$' +header '(5.1) Checking procodile start when stopped ...' +bin/procodile start && sleep 3 +bin/procodile status --simple |grep '^OK || app1\[1\], app2\[1\], app3\[1\]$' +header '(5.2) Checking procodile stop when started ...' +bin/procodile stop && sleep 3 +bin/procodile status --simple |grep '^Issues || app1 has 0 instances (should have 1), app2 has 0 instances (should have 1), app3 has 0 instances (should have 1)$' header '(6) Checking procodile restart when stopped ...' bin/procodile restart && sleep 3 bin/procodile status --simple |grep '^OK || app1\[1\], app2\[1\], app3\[1\]$' @@ -119,15 +125,15 @@ HEREDOC header '(12) Checking procodile restart will failed when run app3.sh ...' bin/procodile restart && sleep 3 header '(12.1) Checking procodile restart app3.sh Unknown status ...' -bin/procodile status |grep -F 'app3.4' |grep -F 'Unknown' +bin/procodile status |grep -F 'app3.5' |grep -F 'Unknown' -while ! bin/procodile status |grep -F 'app3.4' |grep -F 'Failed' |grep -F 'respawns:5'; do +while ! bin/procodile status |grep -F 'app3.5' |grep -F 'Failed' |grep -F 'respawns:5'; do sleep 1 echo 'Waiting respawns to become 5' done header '(12.1) Checking procodile restart app3.sh Failed status ...' -bin/procodile status |grep -F 'app3.4' |grep -F 'Failed' +bin/procodile status |grep -F 'app3.5' |grep -F 'Failed' header '(13) Change Procfile to set correct env for app3.sh' diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index dae60bc..7ca3801 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,6 +1,6 @@ require "spec" require "yaml" -require "../src/procodile/procfile_option" +require "../src/procodile" APPS_ROOT = File.expand_path("apps", __DIR__) # require "../src/procodile" diff --git a/spec/specs/app_determination_spec.cr b/spec/specs/app_determination_spec.cr index ebb9fb3..a7da5ac 100644 --- a/spec/specs/app_determination_spec.cr +++ b/spec/specs/app_determination_spec.cr @@ -52,7 +52,7 @@ describe Procodile::AppDetermination do pwd: "/myapps", given_root: nil, given_procfile: nil, - global_options: Procodile::ProcfileOption.from_yaml(global_options) + global_options: Procodile::Config::Option.from_yaml(global_options) ) ap.root.should eq "/app" ap.procfile.should eq "/app/Procfile" diff --git a/spec/specs/cli_spec.cr b/spec/specs/cli_spec.cr index 7d1c547..2501cc2 100644 --- a/spec/specs/cli_spec.cr +++ b/spec/specs/cli_spec.cr @@ -9,7 +9,7 @@ describe Procodile::CLI do it "should run help command" do command = cli.class.commands["help"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "help" command.description.should eq "Shows this help output" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -19,7 +19,7 @@ describe Procodile::CLI do it "should run kill command" do command = cli.class.commands["kill"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "kill" command.description.should eq "Forcefully kill all known processes" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -29,7 +29,7 @@ describe Procodile::CLI do it "should run start command" do command = cli.class.commands["start"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "start" command.description.should eq "Starts processes and/or the supervisor" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -39,7 +39,7 @@ describe Procodile::CLI do it "should run stop command" do command = cli.class.commands["stop"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "stop" command.description.should eq "Stops processes and/or the supervisor" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -49,7 +49,7 @@ describe Procodile::CLI do it "should run status command" do command = cli.class.commands["status"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "status" command.description.should eq "Show the current status of processes" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -59,7 +59,7 @@ describe Procodile::CLI do it "should run exec command" do command = cli.class.commands["exec"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "exec" command.description.should eq "Execute a command within the environment" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -69,7 +69,7 @@ describe Procodile::CLI do it "should run reload command" do command = cli.class.commands["reload"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "reload" command.description.should eq "Reload Procodile configuration" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -79,7 +79,7 @@ describe Procodile::CLI do it "should run check_concurrency command" do command = cli.class.commands["check_concurrency"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "check_concurrency" command.description.should eq "Check process concurrency" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -89,7 +89,7 @@ describe Procodile::CLI do it "should run log command" do command = cli.class.commands["log"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "log" command.description.should eq "Open/stream a Procodile log file" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) @@ -99,7 +99,7 @@ describe Procodile::CLI do it "should run restart command" do command = cli.class.commands["restart"] - command.should be_a Procodile::CliCommand + command.should be_a Procodile::CLI::Command command.name.should eq "restart" command.description.should eq "Restart processes" command.options.should be_a Proc(OptionParser, Procodile::CLI, Nil) diff --git a/spec/specs/config_spec.cr b/spec/specs/config_spec.cr index 59c6606..35eabb7 100644 --- a/spec/specs/config_spec.cr +++ b/spec/specs/config_spec.cr @@ -12,13 +12,13 @@ describe Procodile::Config do end it "should not have any options" do - config.options.should be_a Procodile::ProcfileOption - config.options.should eq Procodile::ProcfileOption.new + config.options.should be_a Procodile::Config::Option + config.options.should eq Procodile::Config::Option.new end it "should not have any local options" do - config.local_options.should be_a Procodile::ProcfileOption - config.local_options.should eq Procodile::ProcfileOption.new + config.local_options.should be_a Procodile::Config::Option + config.local_options.should eq Procodile::Config::Option.new end it "should have a determined pid root and socket path" do diff --git a/spec/specs/procfile_option_spec.cr b/spec/specs/procfile_option_spec.cr index 639311a..2980a8c 100644 --- a/spec/specs/procfile_option_spec.cr +++ b/spec/specs/procfile_option_spec.cr @@ -1,10 +1,9 @@ require "../spec_helper" -require "../../src/procodile/procfile_option" -describe Procodile::ProcfileOption do +describe Procodile::Config::Option do it "should allow root and procfile to be provided" do procfile_option_file = File.join(APPS_ROOT, "full", "Procfile.options") - procfile_option = Procodile::ProcfileOption.from_yaml(File.read(procfile_option_file)) + procfile_option = Procodile::Config::Option.from_yaml(File.read(procfile_option_file)) procfile_option.app_name.should eq "specapp" procfile_option.pid_root.should eq "tmp/pids" procfile_option.log_path.should eq "log/procodile.log" diff --git a/src/procodile.cr b/src/procodile.cr index 7fd15f3..75f4497 100644 --- a/src/procodile.cr +++ b/src/procodile.cr @@ -1,5 +1,10 @@ require "option_parser" +require "yaml" +require "json" +require "socket" require "file_utils" +require "wait_group" + require "./procodile/app_determination" require "./procodile/cli" @@ -50,7 +55,7 @@ end global_config_path = ENV["PROCODILE_CONFIG"]? || "/etc/procodile" if File.file?(global_config_path) - global_config = Procodile::ProcfileOption.from_yaml(File.read(global_config_path)) + global_config = Procodile::Config::Option.from_yaml(File.read(global_config_path)) end # Create a determination to work out where we want to load our app from @@ -58,7 +63,7 @@ ap = Procodile::AppDetermination.new( FileUtils.pwd, options[:root]?, options[:procfile]?, - global_config || Procodile::ProcfileOption.new + global_config || Procodile::Config::Option.new ) begin diff --git a/src/procodile/app_determination.cr b/src/procodile/app_determination.cr index 3e4090c..abcc4f4 100644 --- a/src/procodile/app_determination.cr +++ b/src/procodile/app_determination.cr @@ -1,5 +1,3 @@ -require "./procfile_option" - module Procodile # # This class is responsible for determining which application should be used @@ -18,7 +16,7 @@ module Procodile @pwd : String, given_root : String?, @given_procfile : String?, - @global_options : ProcfileOption = ProcfileOption.new + @global_options : Config::Option = Config::Option.new ) @given_root = given_root ? expand_path(given_root, pwd) : nil @@ -81,12 +79,12 @@ module Procodile end end - private def find_root_and_procfile_from_options(options : ProcfileOption) : String? + private def find_root_and_procfile_from_options(options : Config::Option) : String? case options - when ProcfileOption + when Config::Option # Use the current hash find_root_and_procfile(@pwd, options.root, options.procfile) - when Array(ProcfileOption) + when Array(Config::Option) # Global options is provides a list of apps. We need to know which one of # these we should be looking at. diff --git a/src/procodile/cli.cr b/src/procodile/cli.cr index 2a34250..7043f2a 100644 --- a/src/procodile/cli.cr +++ b/src/procodile/cli.cr @@ -22,8 +22,8 @@ module Procodile ] property options, config - def self.commands : Hash(String, CliCommand) - @@commands ||= {} of String => CliCommand + def self.commands : Hash(String, Command) + @@commands ||= {} of String => Command end @@options = {} of Symbol => Proc(OptionParser, Procodile::CLI, Nil) @@ -39,14 +39,14 @@ module Procodile {% end %} def initialize - @options = Procodile::CliOptions.new + @options = Options.new @config = uninitialized Procodile::Config {% for e in COMMANDS %} {% name = e[0] %} {% description = e[1] %} - self.class.commands[{{ name.id.stringify }}] = CliCommand.new( + self.class.commands[{{ name.id.stringify }}] = Command.new( name: {{ name.id.stringify }}, description: {{ description.id.stringify }}, options: @@options[{{ name }}], @@ -66,16 +66,17 @@ module Procodile def self.start_supervisor( config : Procodile::Config, - options = Procodile::CliOptions.new, + options : Options = Options.new, &after_start : Proc(Procodile::Supervisor, Nil) ) - run_options = RunOptions.new - run_options.respawn = options.respawn - run_options.stop_when_none = options.stop_when_none - # run_options.proxy = options.proxy - run_options.force_single_log = options.foreground - run_options.port_allocations = options.port_allocations - run_options.foreground = options.foreground + run_options = Supervisor::RunOptions.new( + respawn: options.respawn, + stop_when_none: options.stop_when_none, + proxy: options.proxy, + force_single_log: options.foreground, + port_allocations: options.port_allocations, + foreground: !!options.foreground + ) tidy_pids(config) @@ -161,5 +162,38 @@ module Procodile processes end end + + struct Command + getter name : String, description : String?, options : Proc(OptionParser, Procodile::CLI, Nil)?, callable : Proc(Nil) + + def initialize(@name, @description, @options, @callable) + end + end + + struct Options + property foreground : Bool? + property respawn : Bool? + property stop_when_none : Bool? + property proxy : Bool? + property tag : String? + property port_allocations : Hash(String, Int32)? + property start_supervisor : Bool? + property start_processes : Bool? + property stop_supervisor : Bool? + property wait_until_supervisor_stopped : Bool? + property reload : Bool? + property json : Bool? + property json_pretty : Bool? + property simple : Bool? + property processes : String? # A String split by comma. + property clean : Bool? + property development : Bool? + property wait : Bool? + property lines : Int32? + property process : String? + + def initialize + end + end end end diff --git a/src/procodile/commands/start_command.cr b/src/procodile/commands/start_command.cr index 9198a88..5d96d70 100644 --- a/src/procodile/commands/start_command.cr +++ b/src/procodile/commands/start_command.cr @@ -56,7 +56,7 @@ module Procodile cli.options.respawn = false cli.options.foreground = true cli.options.stop_when_none = true - # cli.options.proxy = true + cli.options.proxy = true end end end @@ -79,7 +79,7 @@ module Procodile raise Error.new "Cannot enable the proxy when the supervisor is running" end - instances = ControlClient.run( + instance_configs = ControlClient.run( @config.sock_path, "start_processes", processes: process_names_from_cli_option, @@ -87,11 +87,11 @@ module Procodile port_allocations: @options.port_allocations, ).as Array(Instance::Config) - if instances.empty? + if instance_configs.empty? puts "No processes to start." else - instances.each do |instance| - puts "Started".color(32) + " #{instance.description} (PID: #{instance.pid})" + instance_configs.each do |instance_config| + puts "Started".color(32) + " #{instance_config.description} (PID: #{instance_config.pid})" end end else diff --git a/src/procodile/commands/status_command.cr b/src/procodile/commands/status_command.cr index 85124b5..3d65a00 100644 --- a/src/procodile/commands/status_command.cr +++ b/src/procodile/commands/status_command.cr @@ -22,14 +22,17 @@ module Procodile def status : Nil if supervisor_running? - status = ControlClient.run(@config.sock_path, "status").as ControlClient::ReplyOfStatusCommand + status = ControlClient.run( + @config.sock_path, "status" + ).as ControlClient::ReplyOfStatusCommand - if @options.json + case @options + when .json puts status.to_json - elsif @options.json_pretty + when .json_pretty puts status nil - elsif @options.simple + when .simple if status.messages.empty? message = status.instances.map { |p, i| "#{p}[#{i.size}]" } diff --git a/src/procodile/config.cr b/src/procodile/config.cr index ed89346..1a4fd1f 100644 --- a/src/procodile/config.cr +++ b/src/procodile/config.cr @@ -8,8 +8,8 @@ module Procodile @process_list : Hash(String, String)? @processes : Hash(String, Procodile::Process)? @procfile_path : String? - @options : ProcfileOption? - @local_options : ProcfileOption? + @options : Option? + @local_options : Option? @process_options : Hash(String, Process::Option)? @local_process_options : Hash(String, Process::Option)? @loaded_at : Time? @@ -113,11 +113,11 @@ module Procodile @process_list ||= load_process_list_from_file end - def options : ProcfileOption + def options : Option @options ||= load_options_from_file end - def local_options : ProcfileOption + def local_options : Option @local_options ||= load_local_options_from_file end @@ -195,19 +195,39 @@ module Procodile Hash(String, String).from_yaml(File.read(procfile_path)) end - private def load_options_from_file : ProcfileOption + private def load_options_from_file : Option if File.exists?(options_path) - ProcfileOption.from_yaml(File.read(options_path)) + Option.from_yaml(File.read(options_path)) else - ProcfileOption.new + Option.new end end - private def load_local_options_from_file : ProcfileOption + private def load_local_options_from_file : Option if File.exists?(local_options_path) - ProcfileOption.from_yaml(File.read(local_options_path)) + Option.from_yaml(File.read(local_options_path)) else - ProcfileOption.new + Option.new + end + end + + struct Option + include YAML::Serializable + + property app_name : String? + property root : String? + property procfile : String? + property pid_root : String? + property log_path : String? + property log_root : String? + property user : String? + property console_command : String? + property exec_prefix : String? + property env : Hash(String, String)? + property processes : Hash(String, Process::Option)? + property app_id : Process::Option? + + def initialize end end end diff --git a/src/procodile/control_client.cr b/src/procodile/control_client.cr index b3f886c..b3e6f42 100644 --- a/src/procodile/control_client.cr +++ b/src/procodile/control_client.cr @@ -1,6 +1,3 @@ -require "json" -require "socket" - module Procodile class ControlClient alias SocketResponse = Array(Instance::Config) | diff --git a/src/procodile/control_server.cr b/src/procodile/control_server.cr index c6c4cfa..d5de636 100644 --- a/src/procodile/control_server.cr +++ b/src/procodile/control_server.cr @@ -1,4 +1,3 @@ -require "socket" require "./control_session" module Procodile diff --git a/src/procodile/control_session.cr b/src/procodile/control_session.cr index 4208b29..10ebf4c 100644 --- a/src/procodile/control_session.cr +++ b/src/procodile/control_session.cr @@ -1,4 +1,3 @@ -require "json" require "./version" module Procodile diff --git a/src/procodile/instance.cr b/src/procodile/instance.cr index 7f0b128..9eb3012 100644 --- a/src/procodile/instance.cr +++ b/src/procodile/instance.cr @@ -120,9 +120,7 @@ module Procodile if running? Procodile.log(@process.log_color, description, "Sending #{@process.term_signal} to #{@pid}") - if (pid = @pid) - ::Process.signal(@process.term_signal, pid) - end + ::Process.signal(@process.term_signal, @pid.not_nil!) else Procodile.log(@process.log_color, description, "Process already stopped") end @@ -142,9 +140,7 @@ module Procodile case restart_mode when Signal::USR1, Signal::USR2 if running? - if (pid = @pid) - ::Process.signal(restart_mode.as(Signal), pid) - end + ::Process.signal(restart_mode.as(Signal), @pid.not_nil!) @tag = @supervisor.tag if @supervisor.tag Procodile.log(@process.log_color, description, "Sent #{restart_mode.to_s.upcase} signal to process #{@pid}") @@ -235,7 +231,6 @@ module Procodile pid: self.pid, respawns: self.respawns, status: self.status, - running: self.running?, started_at: started_at ? started_at.to_unix : nil, tag: self.tag, port: @port, @@ -456,7 +451,6 @@ module Procodile @pid : Int64?, @respawns : Int32, @status : Instance::Status, - @running : Bool, @started_at : Int64?, @tag : String?, @port : Int32?, diff --git a/src/procodile/procfile_option.cr b/src/procodile/procfile_option.cr deleted file mode 100644 index d600162..0000000 --- a/src/procodile/procfile_option.cr +++ /dev/null @@ -1,67 +0,0 @@ -require "yaml" -require "json" -require "../../src/procodile/cli" - -module Procodile - struct ProcfileOption - include YAML::Serializable - - property app_name : String? - property root : String? - property procfile : String? - property pid_root : String? - property log_path : String? - property log_root : String? - property user : String? - property console_command : String? - property exec_prefix : String? - property env : Hash(String, String)? - property processes : Hash(String, Process::Option)? - property app_id : Process::Option? - - def initialize - end - end - - struct CliCommand - getter name : String, description : String?, options : Proc(OptionParser, Procodile::CLI, Nil)?, callable : Proc(Nil) - - def initialize(@name, @description, @options, @callable) - end - end - - struct CliOptions - property foreground : Bool = false - property respawn : Bool? - property stop_when_none : Bool? - property proxy : Bool? - property tag : String? - property port_allocations : Hash(String, Int32)? - property start_supervisor : Bool? - property start_processes : Bool? - property stop_supervisor : Bool? - property wait_until_supervisor_stopped : Bool? - property reload : Bool? - property json : Bool? - property json_pretty : Bool? - property simple : Bool? - property processes : String? # A String split by comma. - property clean : Bool? - property development : Bool? - property wait : Bool? - property lines : Int32? - property process : String? - - def initialize - end - end - - struct RunOptions - property respawn : Bool? - property stop_when_none : Bool? - property? proxy = false - property force_single_log : Bool? - property? foreground : Bool = false - property port_allocations : Hash(String, Int32)? - end -end diff --git a/src/procodile/supervisor.cr b/src/procodile/supervisor.cr index eaad3a4..d46bacd 100644 --- a/src/procodile/supervisor.cr +++ b/src/procodile/supervisor.cr @@ -1,5 +1,3 @@ -require "file_utils" -require "wait_group" require "./logger" require "./error" require "./control_server" @@ -41,11 +39,11 @@ module Procodile ControlServer.start(self) - # if @run_options.proxy? - # Procodile.log nil, "system", "Proxy is enabled" + if @run_options.proxy? + Procodile.log nil, "system", "Proxy is enabled" - # @tcp_proxy = TCPProxy.start(self) - # end + @tcp_proxy = TCPProxy.start(self) + end after_start.call(self) # invoke supervisor.start_processes @@ -113,7 +111,7 @@ module Procodile instances_stopped end - def run_use_foreground? + def run_use_foreground? : Bool @run_options.foreground? end @@ -246,7 +244,10 @@ module Procodile def remove_instance(instance : Instance) : Nil if @processes[instance.process] @processes[instance.process].delete(instance) - @readers.delete(instance) + + # Only useful when run in foreground + key = @readers.key_for?(instance) + @readers.delete(key) if key end end @@ -261,7 +262,7 @@ module Procodile Fiber.yield if (str = reader.gets).nil? - sleep 0.1 + sleep 0.1.seconds next end @@ -455,6 +456,21 @@ module Procodile end end + struct RunOptions + property respawn, stop_when_none, force_single_log, port_allocations + property? proxy, foreground + + def initialize( + @respawn : Bool?, + @stop_when_none : Bool?, + @force_single_log : Bool?, + @port_allocations : Hash(String, Int32)?, + @proxy : Bool?, + @foreground : Bool = false, + ) + end + end + enum CheckInstanceQuantitiesType Both Started