From ca8a59e3cab4b65ee84e7cf9c03566a2894f0d96 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 4 Apr 2017 07:48:38 -0400 Subject: [PATCH 1/5] Added kill signal definition feature for v2 and all exporters --- cli/cli.go | 2 +- export/export_test.go | 17 ++++------- export/systemd.go | 1 + export/upstart.go | 1 + procfile/procfile.go | 57 +++++++++++++++++++++++++++++++++++ procfile/procfile_test.go | 3 +- procfile/procfile_v1.go | 62 ++++----------------------------------- procfile/procfile_v2.go | 8 +++++ testdata/procfile_v2 | 3 +- 9 files changed, 83 insertions(+), 71 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index e5192f1..119e8a9 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -30,7 +30,7 @@ import ( // App props const ( APP = "init-exporter" - VER = "0.9.0" + VER = "0.10.0" DESC = "Utility for exporting services described by Procfile to init system" ) diff --git a/export/export_test.go b/export/export_test.go index 8f946cd..0bd8455 100644 --- a/export/export_test.go +++ b/export/export_test.go @@ -112,12 +112,6 @@ func (s *ExportSuite) TestUpstartExport(c *C) { service1Helper := strings.Split(string(service1HelperData), "\n") service2Helper := strings.Split(string(service2HelperData), "\n") - c.Assert(appUnit, HasLen, 16) - c.Assert(service1Unit, HasLen, 21) - c.Assert(service2Unit, HasLen, 21) - c.Assert(service1Helper, HasLen, 8) - c.Assert(service2Helper, HasLen, 8) - c.Assert(appUnit[2:], DeepEquals, []string{ "start on runlevel [3]", @@ -144,6 +138,7 @@ func (s *ExportSuite) TestUpstartExport(c *C) { "respawn limit 15 25", "", "kill timeout 10", + "kill signal SIGQUIT", "", "limit nofile 1024 1024", "", @@ -167,6 +162,7 @@ func (s *ExportSuite) TestUpstartExport(c *C) { "", "kill timeout 0", "", + "", "limit nofile 4096 4096", "limit nproc 4096 4096", "", @@ -275,12 +271,6 @@ func (s *ExportSuite) TestSystemdExport(c *C) { service1Helper := strings.Split(string(service1HelperData), "\n") service2Helper := strings.Split(string(service2HelperData), "\n") - c.Assert(appUnit, HasLen, 22) - c.Assert(service1Unit, HasLen, 29) - c.Assert(service2Unit, HasLen, 29) - c.Assert(service1Helper, HasLen, 8) - c.Assert(service2Helper, HasLen, 8) - c.Assert(appUnit[2:], DeepEquals, []string{ "[Unit]", @@ -314,6 +304,7 @@ func (s *ExportSuite) TestSystemdExport(c *C) { "[Service]", "Type=simple", "", + "KillSignal=SIGQUIT", "TimeoutStopSec=10", "Restart=on-failure", "StartLimitInterval=25", @@ -345,6 +336,7 @@ func (s *ExportSuite) TestSystemdExport(c *C) { "[Service]", "Type=simple", "", + "", "TimeoutStopSec=0", "Restart=on-failure", "", @@ -416,6 +408,7 @@ func createTestApp(helperDir, targetDir string) *procfile.Application { WorkingDir: "/srv/service/service1-dir", LogPath: "log/service1.log", KillTimeout: 10, + KillSignal: "SIGQUIT", Count: 2, RespawnInterval: 25, RespawnCount: 15, diff --git a/export/systemd.go b/export/systemd.go index 977b263..8372dcf 100644 --- a/export/systemd.go +++ b/export/systemd.go @@ -68,6 +68,7 @@ PartOf={{.Application.Name}}.service [Service] Type=simple +{{ if .Service.Options.IsKillSignalSet }}KillSignal={{.Service.Options.KillSignal}}{{ end }} TimeoutStopSec={{.Service.Options.KillTimeout}} {{ if .Service.Options.IsRespawnEnabled }}Restart=on-failure{{ end }} {{ if .Service.Options.IsRespawnLimitSet }}StartLimitInterval={{.Service.Options.RespawnInterval}}{{ end }} diff --git a/export/upstart.go b/export/upstart.go index 956d170..6752a08 100644 --- a/export/upstart.go +++ b/export/upstart.go @@ -60,6 +60,7 @@ stop on {{.StopLevel}} {{ if .Service.Options.IsRespawnLimitSet }}respawn limit {{.Service.Options.RespawnCount}} {{.Service.Options.RespawnInterval}}{{ end }} kill timeout {{.Service.Options.KillTimeout}} +{{ if .Service.Options.IsKillSignalSet }}kill signal {{.Service.Options.KillSignal}}{{ end }} {{ if .Service.Options.IsFileLimitSet }}limit nofile {{.Service.Options.LimitFile}} {{.Service.Options.LimitFile}}{{ end }} {{ if .Service.Options.IsProcLimitSet }}limit nproc {{.Service.Options.LimitProc}} {{.Service.Options.LimitProc}}{{ end }} diff --git a/procfile/procfile.go b/procfile/procfile.go index 601f972..f66efac 100644 --- a/procfile/procfile.go +++ b/procfile/procfile.go @@ -58,6 +58,7 @@ type ServiceOptions struct { WorkingDir string // Working directory LogPath string // Path to log file KillTimeout int // Kill timeout in seconds + KillSignal string // Kill signal name Count int // Exec count RespawnInterval int // Respawn interval in seconds RespawnCount int // Respawn count @@ -237,6 +238,11 @@ func (so *ServiceOptions) IsProcLimitSet() bool { return so.LimitProc != 0 } +// IsKillSignalSet return true if custom kill signal set +func (so *ServiceOptions) IsKillSignalSet() bool { + return so.KillSignal != "" +} + // EnvString return environment variables as string func (so *ServiceOptions) EnvString() string { if len(so.Env) == 0 { @@ -265,6 +271,57 @@ func (so *ServiceOptions) FullLogPath() string { // ////////////////////////////////////////////////////////////////////////////////// // +// parseCommand parse shell command and extract command body, output redirection +// and environment variables +func parseCommand(command string) (string, string, map[string]string) { + var ( + env map[string]string + cmd []string + log string + + isEnv bool + isLog bool + ) + + cmdSlice := strings.Fields(command) + + for _, cmdPart := range cmdSlice { + if strings.TrimSpace(cmdPart) == "" { + continue + } + + if strings.HasPrefix(cmdPart, "env") { + env = make(map[string]string) + isEnv = true + continue + } + + if isEnv { + if strings.Contains(cmdPart, "=") { + envSlice := strings.Split(cmdPart, "=") + env[envSlice[0]] = envSlice[1] + continue + } else { + isEnv = false + } + } + + if strings.Contains(cmdPart, ">>") { + isLog = true + continue + } + + if isLog { + log = cmdPart + break + } + + cmd = append(cmd, cmdPart) + } + + return strings.Join(cmd, " "), log, env +} + // determineProcVersion process procfile data and return procfile version func determineProcVersion(data []byte) int { if regexp.MustCompile(REGEXP_V2_VERSION).Match(data) { diff --git a/procfile/procfile_test.go b/procfile/procfile_test.go index 612349d..98d9389 100644 --- a/procfile/procfile_test.go +++ b/procfile/procfile_test.go @@ -96,6 +96,7 @@ func (s *ProcfileSuite) TestProcV2Parsing(c *C) { c.Assert(service.Options.WorkingDir, Equals, "/srv/projects/my_website/current") c.Assert(service.Options.IsCustomLogEnabled(), Equals, false) c.Assert(service.Options.KillTimeout, Equals, 60) + c.Assert(service.Options.KillSignal, Equals, "QUIT") c.Assert(service.Options.RespawnCount, Equals, 7) c.Assert(service.Options.RespawnInterval, Equals, 22) c.Assert(service.Options.IsRespawnEnabled, Equals, false) @@ -109,7 +110,7 @@ func (s *ProcfileSuite) TestProcV2Parsing(c *C) { c.Assert(service.Application.Name, Equals, "test-app") case "my_one_another_tail_cmd": - c.Assert(service.Cmd, Equals, "/usr/bin/tail -F /var/log/messages") + c.Assert(service.Cmd, Equals, "/usr/bin/tail -F /var/log/messages >> log/my_one_another_tail_cmd.log 2>&1") c.Assert(service.Options, NotNil) c.Assert(service.Options.WorkingDir, Equals, "/srv/projects/my_website/current") c.Assert(service.Options.LogPath, Equals, "log/my_one_another_tail_cmd.log") diff --git a/procfile/procfile_v1.go b/procfile/procfile_v1.go index db3f749..53408be 100644 --- a/procfile/procfile_v1.go +++ b/procfile/procfile_v1.go @@ -113,14 +113,14 @@ func parseV1Command(name, command string) *Service { switch len(cmdSlice) { case 3: - pre, _, _ = extractV1Data(cmdSlice[0]) - cmd, log, env = extractV1Data(cmdSlice[1]) - post, _, _ = extractV1Data(cmdSlice[2]) + pre, _, _ = parseCommand(cmdSlice[0]) + cmd, log, env = parseCommand(cmdSlice[1]) + post, _, _ = parseCommand(cmdSlice[2]) case 2: - pre, _, _ = extractV1Data(cmdSlice[0]) - cmd, log, env = extractV1Data(cmdSlice[1]) + pre, _, _ = parseCommand(cmdSlice[0]) + cmd, log, env = parseCommand(cmdSlice[1]) default: - cmd, log, env = extractV1Data(cmdSlice[0]) + cmd, log, env = parseCommand(cmdSlice[0]) } service.Cmd = cmd @@ -132,56 +132,6 @@ func parseV1Command(name, command string) *Service { return service } -// extractV1Data extract data from command -func extractV1Data(command string) (string, string, map[string]string) { - var ( - env map[string]string - cmd []string - log string - - isEnv bool - isLog bool - ) - - cmdSlice := strings.Fields(command) - - for _, cmdPart := range cmdSlice { - if strings.TrimSpace(cmdPart) == "" { - continue - } - - if strings.HasPrefix(cmdPart, "env") { - env = make(map[string]string) - isEnv = true - continue - } - - if isEnv { - if strings.Contains(cmdPart, "=") { - envSlice := strings.Split(cmdPart, "=") - env[envSlice[0]] = envSlice[1] - continue - } else { - isEnv = false - } - } - - if strings.Contains(cmdPart, ">>") { - isLog = true - continue - } - - if isLog { - log = cmdPart - break - } - - cmd = append(cmd, cmdPart) - } - - return strings.Join(cmd, " "), log, env -} - // splitV1Cmd split command and format command func splitV1Command(cmd string) []string { var result []string diff --git a/procfile/procfile_v2.go b/procfile/procfile_v2.go index 9a1c821..a48d788 100644 --- a/procfile/procfile_v2.go +++ b/procfile/procfile_v2.go @@ -158,6 +158,14 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { } } + if yaml.IsExist("kill_signal") { + options.KillSignal, err = yaml.Get("kill_signal").String() + + if err != nil { + return nil, fmt.Errorf("Can't parse kill_signal value: %v", err) + } + } + if yaml.IsExist("count") { options.Count, err = yaml.Get("count").Int() diff --git a/testdata/procfile_v2 b/testdata/procfile_v2 index 4d7afaf..ffdcffa 100644 --- a/testdata/procfile_v2 +++ b/testdata/procfile_v2 @@ -35,10 +35,11 @@ commands: nofile: 8192 nproc: 8192 kill_timeout: 60 + kill_signal: QUIT respawn: false # by default respawn option is enabled my_one_another_tail_cmd: - command: /usr/bin/tail -F /var/log/messages + command: /usr/bin/tail -F /var/log/messages >> log/my_one_another_tail_cmd.log 2>&1 log: log/my_one_another_tail_cmd.log my_multi_tail_cmd: From 4e95da43fa9d79b66458a0cdb2008fbc23f9f1be Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 4 Apr 2017 07:52:10 -0400 Subject: [PATCH 2/5] Improved readme --- readme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/readme.md b/readme.md index b52ae75..6dc5193 100644 --- a/readme.md +++ b/readme.md @@ -137,12 +137,16 @@ look like this: ```yaml version: 2 + start_on_runlevel: 3 stop_on_runlevel: 3 + env: RAILS_ENV: production TEST: true + working_directory: /srv/projects/my_website/current + commands: my_tail_cmd: command: /usr/bin/tail -F /var/log/messages @@ -152,13 +156,17 @@ commands: env: RAILS_ENV: staging # if needs to be redefined or extended working_directory: '/var/...' # if needs to be redefined + my_another_tail_cmd: command: /usr/bin/tail -F /var/log/messages kill_timeout: 60 + kill_signal: SIGQUIT respawn: false # by default respawn option is enabled + my_one_another_tail_cmd: command: /usr/bin/tail -F /var/log/messages log: /var/log/messages_copy + my_multi_tail_cmd: command: /usr/bin/tail -F /var/log/messages count: 2 @@ -185,6 +193,8 @@ env RAILS_ENV=staging TEST=true your_command `kill_timeout` option lets you override the default process kill timeout of 30 seconds. +`kill_signal` specifies which signal to use when killing a service. + `respawn` option controls how often the job can fail. If the job restarts more often than `count` times in `interval`, it won't be restarted anymore. From eaa4ec3a2e081b69d89beee24d7976d2c2b1a065 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 4 Apr 2017 08:28:37 -0400 Subject: [PATCH 3/5] Improved parsing commands in v2 syntax --- export/export_test.go | 4 +- procfile/procfile.go | 4 +- procfile/procfile_test.go | 5 +- procfile/procfile_v2.go | 107 ++++++++++++++++++++++++-------------- testdata/procfile_v2 | 4 +- 5 files changed, 78 insertions(+), 46 deletions(-) diff --git a/export/export_test.go b/export/export_test.go index 0bd8455..ac662d4 100644 --- a/export/export_test.go +++ b/export/export_test.go @@ -178,7 +178,7 @@ func (s *ExportSuite) TestUpstartExport(c *C) { c.Assert(service1Helper[4:], DeepEquals, []string{ "[[ -r /etc/profile.d/rbenv.sh ]] && source /etc/profile.d/rbenv.sh", "", - "cd /srv/service/service1-dir && exec env STAGING=true /bin/echo 'service1:pre' >>/srv/service/service1-dir/log/service1.log && exec env STAGING=true /bin/echo 'service1' >>/srv/service/service1-dir/log/service1.log && exec env STAGING=true /bin/echo 'service1:post' >>/srv/service/service1-dir/log/service1.log", + "cd /srv/service/service1-dir && exec env STAGING=true /bin/echo 'service1:pre' &>>/srv/service/service1-dir/log/service1.log && exec env STAGING=true /bin/echo 'service1' &>>/srv/service/service1-dir/log/service1.log && exec env STAGING=true /bin/echo 'service1:post' &>>/srv/service/service1-dir/log/service1.log", ""}, ) @@ -361,7 +361,7 @@ func (s *ExportSuite) TestSystemdExport(c *C) { c.Assert(service1Helper[4:], DeepEquals, []string{ "[[ -r /etc/profile.d/rbenv.sh ]] && source /etc/profile.d/rbenv.sh", "", - "exec /bin/echo 'service1:pre' >>/srv/service/service1-dir/log/service1.log && exec /bin/echo 'service1' >>/srv/service/service1-dir/log/service1.log && exec /bin/echo 'service1:post' >>/srv/service/service1-dir/log/service1.log", + "exec /bin/echo 'service1:pre' &>>/srv/service/service1-dir/log/service1.log && exec /bin/echo 'service1' &>>/srv/service/service1-dir/log/service1.log && exec /bin/echo 'service1:post' &>>/srv/service/service1-dir/log/service1.log", ""}, ) diff --git a/procfile/procfile.go b/procfile/procfile.go index f66efac..826d953 100644 --- a/procfile/procfile.go +++ b/procfile/procfile.go @@ -187,7 +187,7 @@ func (s *Service) GetCommandExecWithEnv(command string) string { } if s.Options.IsCustomLogEnabled() { - result += " >>" + s.Options.FullLogPath() + result += " &>>" + s.Options.FullLogPath() } return result @@ -207,7 +207,7 @@ func (s *Service) GetCommandExec(command string) string { } if s.Options.IsCustomLogEnabled() { - result += " >>" + s.Options.FullLogPath() + result += " &>>" + s.Options.FullLogPath() } return result diff --git a/procfile/procfile_test.go b/procfile/procfile_test.go index 98d9389..30d6ff4 100644 --- a/procfile/procfile_test.go +++ b/procfile/procfile_test.go @@ -75,7 +75,8 @@ func (s *ProcfileSuite) TestProcV2Parsing(c *C) { c.Assert(service.Cmd, Equals, "/usr/bin/tail -F /var/log/messages") c.Assert(service.Options, NotNil) c.Assert(service.Options.WorkingDir, Equals, "/var/...") - c.Assert(service.Options.IsCustomLogEnabled(), Equals, false) + c.Assert(service.Options.LogPath, Equals, "log/my_tail_cmd.log") + c.Assert(service.Options.IsCustomLogEnabled(), Equals, true) c.Assert(service.Options.RespawnCount, Equals, 5) c.Assert(service.Options.RespawnInterval, Equals, 10) c.Assert(service.Options.IsRespawnEnabled, Equals, true) @@ -110,7 +111,7 @@ func (s *ProcfileSuite) TestProcV2Parsing(c *C) { c.Assert(service.Application.Name, Equals, "test-app") case "my_one_another_tail_cmd": - c.Assert(service.Cmd, Equals, "/usr/bin/tail -F /var/log/messages >> log/my_one_another_tail_cmd.log 2>&1") + c.Assert(service.Cmd, Equals, "/usr/bin/tail -F /var/log/messages") c.Assert(service.Options, NotNil) c.Assert(service.Options.WorkingDir, Equals, "/srv/projects/my_website/current") c.Assert(service.Options.LogPath, Equals, "log/my_one_another_tail_cmd.log") diff --git a/procfile/procfile_v2.go b/procfile/procfile_v2.go index a48d788..0b5c085 100644 --- a/procfile/procfile_v2.go +++ b/procfile/procfile_v2.go @@ -34,7 +34,7 @@ func parseV2Procfile(data []byte, config *Config) (*Application, error) { return nil, fmt.Errorf("Commands missing in Procfile") } - services, err := parseV2Commands(yaml, commands, config) + services, err := parseV2Services(yaml, commands, config) if err != nil { return nil, err @@ -80,65 +80,96 @@ func parseV2Procfile(data []byte, config *Config) (*Application, error) { return app, nil } -// parseCommands parse command section in yaml based procfile -func parseV2Commands(yaml *simpleyaml.Yaml, commands map[interface{}]interface{}, config *Config) ([]*Service, error) { +// parseV2Services parse services sections in yaml based procfile +func parseV2Services(yaml *simpleyaml.Yaml, commands map[interface{}]interface{}, config *Config) ([]*Service, error) { var services []*Service - commonOptions, err := parseV2Options(yaml) + commonOptions := &ServiceOptions{} + err := parseV2Options(commonOptions, yaml) if err != nil { return nil, err } for key := range commands { - serviceName := fmt.Sprint(key) - commandYaml := yaml.GetPath("commands", serviceName) - serviceCmd, err := commandYaml.Get("command").String() - - if err != nil { - return nil, err + service := &Service{ + Name: fmt.Sprint(key), + Options: &ServiceOptions{}, } - servicePreCmd := commandYaml.Get("pre").MustString() - servicePostCmd := commandYaml.Get("post").MustString() + serviceYaml := yaml.GetPath("commands", service.Name) - serviceOptions, err := parseV2Options(commandYaml) + err := parseV2Commands(service, serviceYaml) if err != nil { return nil, err } - mergeServiceOptions(serviceOptions, commonOptions) - configureDefaults(serviceOptions, config) + err = parseV2Options(service.Options, serviceYaml) - service := &Service{ - Name: serviceName, - Cmd: serviceCmd, - PreCmd: servicePreCmd, - PostCmd: servicePostCmd, - Options: serviceOptions, + if err != nil { + return nil, err } + mergeServiceOptions(service.Options, commonOptions) + configureDefaults(service.Options, config) + services = append(services, service) } return services, nil } -// parseV2Options parse service options in yaml based procfile -func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { +// parseV2Commands parse service commands +func parseV2Commands(service *Service, yaml *simpleyaml.Yaml) error { var err error + var cmd, log string + + cmd, err = yaml.Get("command").String() + + if err != nil { + return fmt.Errorf("Can't parse \"command\" value: %v", err) + } + + cmd, log, _ = parseCommand(cmd) + + if log != "" { + service.Options.LogPath = log + } - options := &ServiceOptions{ - Env: make(map[string]string), - IsRespawnEnabled: true, + service.Cmd = cmd + + if yaml.IsExist("pre") { + service.PreCmd, err = yaml.Get("pre").String() + + if err != nil { + return fmt.Errorf("Can't parse \"pre\" value: %v", err) + } + } + + if yaml.IsExist("post") { + service.PostCmd, err = yaml.Get("post").String() + + if err != nil { + return fmt.Errorf("Can't parse \"post\" value: %v", err) + } } + return nil +} + +// parseV2Options parse service options in yaml based procfile +func parseV2Options(options *ServiceOptions, yaml *simpleyaml.Yaml) error { + var err error + + options.Env = make(map[string]string) + options.IsRespawnEnabled = true + if yaml.IsExist("working_directory") { options.WorkingDir, err = yaml.Get("working_directory").String() if err != nil { - return nil, fmt.Errorf("Can't parse working_directory value: %v", err) + return fmt.Errorf("Can't parse \"working_directory\" value: %v", err) } } @@ -146,7 +177,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.LogPath, err = yaml.Get("log").String() if err != nil { - return nil, fmt.Errorf("Can't parse log value: %v", err) + return fmt.Errorf("Can't parse \"log\" value: %v", err) } } @@ -154,7 +185,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.KillTimeout, err = yaml.Get("kill_timeout").Int() if err != nil { - return nil, fmt.Errorf("Can't parse kill_timeout value: %v", err) + return fmt.Errorf("Can't parse \"kill_timeout\" value: %v", err) } } @@ -162,7 +193,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.KillSignal, err = yaml.Get("kill_signal").String() if err != nil { - return nil, fmt.Errorf("Can't parse kill_signal value: %v", err) + return fmt.Errorf("Can't parse \"kill_signal\" value: %v", err) } } @@ -170,7 +201,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.Count, err = yaml.Get("count").Int() if err != nil { - return nil, fmt.Errorf("Can't parse count value: %v", err) + return fmt.Errorf("Can't parse \"count\" value: %v", err) } } @@ -178,7 +209,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { env, err := yaml.Get("env").Map() if err != nil { - return nil, fmt.Errorf("Can't parse env value: %v", err) + return fmt.Errorf("Can't parse \"env\" value: %v", err) } options.Env = convertMapType(env) @@ -189,7 +220,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.RespawnCount, err = yaml.Get("respawn").Get("count").Int() if err != nil { - return nil, fmt.Errorf("Can't parse respawn.count value: %v", err) + return fmt.Errorf("Can't parse \"respawn.count\" value: %v", err) } } @@ -197,7 +228,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.RespawnInterval, err = yaml.Get("respawn").Get("interval").Int() if err != nil { - return nil, fmt.Errorf("Can't parse respawn.interval value: %v", err) + return fmt.Errorf("Can't parse \"respawn.interval\" value: %v", err) } } @@ -205,7 +236,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.IsRespawnEnabled, err = yaml.Get("respawn").Bool() if err != nil { - return nil, fmt.Errorf("Can't parse respawn value: %v", err) + return fmt.Errorf("Can't parse \"respawn\" value: %v", err) } } @@ -214,7 +245,7 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.LimitFile, err = yaml.Get("limits").Get("nofile").Int() if err != nil { - return nil, fmt.Errorf("Can't parse limits.nofile value: %v", err) + return fmt.Errorf("Can't parse \"limits.nofile\" value: %v", err) } } @@ -222,10 +253,10 @@ func parseV2Options(yaml *simpleyaml.Yaml) (*ServiceOptions, error) { options.LimitProc, err = yaml.Get("limits").Get("nproc").Int() if err != nil { - return nil, fmt.Errorf("Can't parse limits.nproc value: %v", err) + return fmt.Errorf("Can't parse \"limits.nproc\" value: %v", err) } } } - return options, nil + return nil } diff --git a/testdata/procfile_v2 b/testdata/procfile_v2 index ffdcffa..8ed7b68 100644 --- a/testdata/procfile_v2 +++ b/testdata/procfile_v2 @@ -19,7 +19,7 @@ working_directory: /srv/projects/my_website/current commands: my_tail_cmd: - command: /usr/bin/tail -F /var/log/messages + command: /usr/bin/tail -F /var/log/messages >> log/my_tail_cmd.log 2>&1 respawn: count: 5 interval: 10 @@ -39,7 +39,7 @@ commands: respawn: false # by default respawn option is enabled my_one_another_tail_cmd: - command: /usr/bin/tail -F /var/log/messages >> log/my_one_another_tail_cmd.log 2>&1 + command: /usr/bin/tail -F /var/log/messages log: log/my_one_another_tail_cmd.log my_multi_tail_cmd: From 8fa44950b843906f1724c9d5d16533b09e06637d Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 4 Apr 2017 09:23:14 -0400 Subject: [PATCH 4/5] Added reload signal definition feature for v2 and all exporters --- export/export_test.go | 5 +++++ export/systemd.go | 1 + export/upstart.go | 1 + procfile/procfile.go | 6 ++++++ procfile/procfile_test.go | 3 ++- procfile/procfile_v2.go | 8 ++++++++ readme.md | 3 +++ testdata/procfile_v2 | 3 ++- 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/export/export_test.go b/export/export_test.go index ac662d4..9418c14 100644 --- a/export/export_test.go +++ b/export/export_test.go @@ -140,6 +140,7 @@ func (s *ExportSuite) TestUpstartExport(c *C) { "kill timeout 10", "kill signal SIGQUIT", "", + "", "limit nofile 1024 1024", "", "", @@ -162,6 +163,7 @@ func (s *ExportSuite) TestUpstartExport(c *C) { "", "kill timeout 0", "", + "reload signal SIGUSR2", "", "limit nofile 4096 4096", "limit nproc 4096 4096", @@ -323,6 +325,7 @@ func (s *ExportSuite) TestSystemdExport(c *C) { "WorkingDirectory=/srv/service/service1-dir", "Environment=STAGING=true", fmt.Sprintf("ExecStart=/bin/bash %s/test_application-service1.sh &>>/var/log/test_application/service1.log", helperDir), + "", ""}, ) @@ -355,6 +358,7 @@ func (s *ExportSuite) TestSystemdExport(c *C) { "WorkingDirectory=/srv/service/working-dir", "", fmt.Sprintf("ExecStart=/bin/bash %s/test_application-service2.sh &>>/var/log/test_application/service2.log", helperDir), + "ExecReload=/bin/kill -SIGUSR2 $MAINPID", ""}, ) @@ -423,6 +427,7 @@ func createTestApp(helperDir, targetDir string) *procfile.Application { Application: app, Options: &procfile.ServiceOptions{ WorkingDir: "/srv/service/working-dir", + ReloadSignal: "SIGUSR2", IsRespawnEnabled: true, LimitFile: 4096, LimitProc: 4096, diff --git a/export/systemd.go b/export/systemd.go index 8372dcf..e5ff9b2 100644 --- a/export/systemd.go +++ b/export/systemd.go @@ -87,6 +87,7 @@ Group={{.Application.Group}} WorkingDirectory={{.Service.Options.WorkingDir}} {{ if .Service.Options.IsEnvSet }}Environment={{.Service.Options.EnvString}}{{ end }} ExecStart=/bin/bash {{.Service.HelperPath}} &>>/var/log/{{.Application.Name}}/{{.Service.Name}}.log +{{ if .Service.Options.IsReloadSignalSet }}ExecReload=/bin/kill -{{.Service.Options.ReloadSignal}} $MAINPID{{ end }} ` // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/export/upstart.go b/export/upstart.go index 6752a08..d3b3152 100644 --- a/export/upstart.go +++ b/export/upstart.go @@ -61,6 +61,7 @@ stop on {{.StopLevel}} kill timeout {{.Service.Options.KillTimeout}} {{ if .Service.Options.IsKillSignalSet }}kill signal {{.Service.Options.KillSignal}}{{ end }} +{{ if .Service.Options.IsReloadSignalSet }}reload signal {{.Service.Options.ReloadSignal}}{{ end }} {{ if .Service.Options.IsFileLimitSet }}limit nofile {{.Service.Options.LimitFile}} {{.Service.Options.LimitFile}}{{ end }} {{ if .Service.Options.IsProcLimitSet }}limit nproc {{.Service.Options.LimitProc}} {{.Service.Options.LimitProc}}{{ end }} diff --git a/procfile/procfile.go b/procfile/procfile.go index 826d953..6838ade 100644 --- a/procfile/procfile.go +++ b/procfile/procfile.go @@ -59,6 +59,7 @@ type ServiceOptions struct { LogPath string // Path to log file KillTimeout int // Kill timeout in seconds KillSignal string // Kill signal name + ReloadSignal string // Reload signal name Count int // Exec count RespawnInterval int // Respawn interval in seconds RespawnCount int // Respawn count @@ -243,6 +244,11 @@ func (so *ServiceOptions) IsKillSignalSet() bool { return so.KillSignal != "" } +// IsReloadSignalSet return true if custom reload signal set +func (so *ServiceOptions) IsReloadSignalSet() bool { + return so.ReloadSignal != "" +} + // EnvString return environment variables as string func (so *ServiceOptions) EnvString() string { if len(so.Env) == 0 { diff --git a/procfile/procfile_test.go b/procfile/procfile_test.go index 30d6ff4..5a71028 100644 --- a/procfile/procfile_test.go +++ b/procfile/procfile_test.go @@ -97,7 +97,8 @@ func (s *ProcfileSuite) TestProcV2Parsing(c *C) { c.Assert(service.Options.WorkingDir, Equals, "/srv/projects/my_website/current") c.Assert(service.Options.IsCustomLogEnabled(), Equals, false) c.Assert(service.Options.KillTimeout, Equals, 60) - c.Assert(service.Options.KillSignal, Equals, "QUIT") + c.Assert(service.Options.KillSignal, Equals, "SIGQUIT") + c.Assert(service.Options.ReloadSignal, Equals, "SIGUSR2") c.Assert(service.Options.RespawnCount, Equals, 7) c.Assert(service.Options.RespawnInterval, Equals, 22) c.Assert(service.Options.IsRespawnEnabled, Equals, false) diff --git a/procfile/procfile_v2.go b/procfile/procfile_v2.go index 0b5c085..8e7d3c2 100644 --- a/procfile/procfile_v2.go +++ b/procfile/procfile_v2.go @@ -197,6 +197,14 @@ func parseV2Options(options *ServiceOptions, yaml *simpleyaml.Yaml) error { } } + if yaml.IsExist("reload_signal") { + options.ReloadSignal, err = yaml.Get("reload_signal").String() + + if err != nil { + return fmt.Errorf("Can't parse \"reload_signal\" value: %v", err) + } + } + if yaml.IsExist("count") { options.Count, err = yaml.Get("count").Int() diff --git a/readme.md b/readme.md index 6dc5193..30481d0 100644 --- a/readme.md +++ b/readme.md @@ -161,6 +161,7 @@ commands: command: /usr/bin/tail -F /var/log/messages kill_timeout: 60 kill_signal: SIGQUIT + reload_signal: SIGUSR2 respawn: false # by default respawn option is enabled my_one_another_tail_cmd: @@ -195,6 +196,8 @@ env RAILS_ENV=staging TEST=true your_command `kill_signal` specifies which signal to use when killing a service. +`reload_signal` specifies which signal to use when reloading a service. + `respawn` option controls how often the job can fail. If the job restarts more often than `count` times in `interval`, it won't be restarted anymore. diff --git a/testdata/procfile_v2 b/testdata/procfile_v2 index 8ed7b68..e3eda15 100644 --- a/testdata/procfile_v2 +++ b/testdata/procfile_v2 @@ -35,7 +35,8 @@ commands: nofile: 8192 nproc: 8192 kill_timeout: 60 - kill_signal: QUIT + kill_signal: SIGQUIT + reload_signal: SIGUSR2 respawn: false # by default respawn option is enabled my_one_another_tail_cmd: From 7357c137afc569a14512c518631f4d7d1d0d1c92 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 4 Apr 2017 09:25:13 -0400 Subject: [PATCH 5/5] Updated spec --- common/init-exporter.spec | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/init-exporter.spec b/common/init-exporter.spec index 639cc97..22bcf27 100644 --- a/common/init-exporter.spec +++ b/common/init-exporter.spec @@ -42,8 +42,8 @@ Summary: Utility for exporting services described by Procfile to init system Name: init-exporter -Version: 0.9.0 -Release: 2%{?dist} +Version: 0.10.0 +Release: 0%{?dist} Group: Development/Tools License: MIT URL: https://github.com/funbox/init-exporter @@ -132,6 +132,11 @@ rm -rf %{buildroot} ############################################################################### %changelog +* Tue Apr 04 2017 Anton Novojilov - 0.10.0-0 +- Added kill signal definition feature for v2 and all exporters +- Added reload signal definition feature for v2 and all exporters +- Improved parsing commands in v2 Procfile format + * Mon Apr 03 2017 Anton Novojilov - 0.9.0-2 - [converter] Fixed bug with wrong path to working dir