Skip to content

Commit

Permalink
[#51]: feature: allow specifying raw command
Browse files Browse the repository at this point in the history
  • Loading branch information
rustatian authored Aug 7, 2023
2 parents 63bdb06 + 8a4be8c commit 107d62e
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 60 deletions.
20 changes: 2 additions & 18 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
name: Linux

on:
push:
branches:
- master
- beta
- stable
tags-ignore:
- "**"
paths-ignore:
- "**.md"
- "**.yaml"
- "**.yml"
pull_request:
paths-ignore:
- "**.md"
- "**.yaml"
- "**.yml"
on: [push, pull_request]

jobs:
golang:
Expand Down Expand Up @@ -48,4 +32,4 @@ jobs:
run: go mod download

- name: Run golang tests with coverage
run: make test
run: make testpkg
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ linters-settings:
nolintlint:
allow-leading-space: false
require-specific: true
gosec:
excludes:
- G204

linters: # All available linters list: <https://golangci-lint.run/usage/linters/>
disable-all: true
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"cSpell.words": ["Configurer"]
"cSpell.words": ["Configurer", "mapstructure"]
}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
test:
testpkg:
go test -v -race -cover -tags=debug ./...
19 changes: 5 additions & 14 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,30 @@ import (
type Config struct {
// OnInit configuration
OnInit *InitConfig `mapstructure:"on_init"`

// AfterInit command to run after pool initialization
AfterInit *InitConfig `mapstructure:"after_init"`

// Command to run as application.
Command string `mapstructure:"command"`

Command []string `mapstructure:"command"`
// User to run application under.
User string `mapstructure:"user"`

// Group to run application under.
Group string `mapstructure:"group"`

// Env represents application environment.
Env map[string]string `mapstructure:"env"`

// Relay defines connection method and factory to be used to connect to workers:
// "pipes", "tcp://:6001", "unix://rr.sock"
// This config section must not change on re-configuration.
Relay string `mapstructure:"relay"`

// RelayTimeout defines for how long socket factory will be waiting for worker connection. This config section
// must not change on re-configuration. Defaults to 60s.
RelayTimeout time.Duration `mapstructure:"relay_timeout"`
}

type InitConfig struct {
// Command which is started before worker starts
Command string `mapstructure:"command"`

Command []string `mapstructure:"command"`
// ExecTimeout is execute timeout for the command
ExecTimeout time.Duration `mapstructure:"exec_timeout"`

// Env represents application environment.
Env map[string]string `mapstructure:"env"`
}
Expand All @@ -56,7 +47,7 @@ type RPCConfig struct {

// InitDefaults for the server config
func (cfg *Config) InitDefaults() error {
if cfg.Command == "" {
if len(cfg.Command) == 0 {
return errors.Str("command should not be empty")
}

Expand All @@ -69,7 +60,7 @@ func (cfg *Config) InitDefaults() error {
}

if cfg.AfterInit != nil {
if cfg.AfterInit.Command == "" {
if len(cfg.AfterInit.Command) == 0 {
return errors.Str("after_init command should not be empty")
}

Expand All @@ -79,7 +70,7 @@ func (cfg *Config) InitDefaults() error {
}

if cfg.OnInit != nil {
if cfg.OnInit.Command == "" {
if len(cfg.OnInit.Command) == 0 {
return errors.Str("on_init command should not be empty")
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.20

require (
github.com/roadrunner-server/errors v1.2.0
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.4
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.5
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
go.uber.org/zap v1.25.0
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,8 @@ github.com/roadrunner-server/errors v1.2.0 h1:qBmNXt8Iex9QnYTjCkbJKsBZu2EtYkQCM0
github.com/roadrunner-server/errors v1.2.0/go.mod h1:z0ECxZp/dDa5RahtMcy4mBIavVxiZ9vwE5kByl7kFtY=
github.com/roadrunner-server/goridge/v3 v3.6.3 h1:8hCuPVK9BxIE4IGyNphK6KPAy9Kg6t5tHaItBIQKh2o=
github.com/roadrunner-server/goridge/v3 v3.6.3/go.mod h1:hB5+lHhl8msuHrngjKQ+Wx8B705AU0/2DlYGFXbjtgU=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.3 h1:XBMmlRf7JhXMjkuhuY5VWhoqhHcFtEkPP4BbmEtGy6A=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.3/go.mod h1:QcZBTccDGT8zhbHkbzqM7SORktVtvh6Jigkz3hy6kBk=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.4 h1:DtHQBo9xXdN5ru2GAf8tCGnwxLG6lgknzYaDr+c3Fv4=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.4/go.mod h1:QcZBTccDGT8zhbHkbzqM7SORktVtvh6Jigkz3hy6kBk=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.5 h1:+CmSTbz+y51auBr48ldwx6ZgAnWKl7f3537xyW8GDsU=
github.com/roadrunner-server/sdk/v4 v4.4.0-beta.5/go.mod h1:UkiAk5IdmUzkXncfy671OoH6i/zWpWc+JY3IU/AnQuc=
github.com/roadrunner-server/tcplisten v1.3.0 h1:VDd6IbP8oIjm5vKvMVozeZgeHgOcoP0XYLOyOqcZHCY=
github.com/roadrunner-server/tcplisten v1.3.0/go.mod h1:VR6Ob5am0oEuLMOeLiVvQxG9ShykAEgrlvZddX8EfoU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down
26 changes: 19 additions & 7 deletions init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
type command struct {
log *zap.Logger
env map[string]string
command string
command []string
execTimeout time.Duration
}

func newCommand(log *zap.Logger, env map[string]string, cmd string, execTimeout time.Duration) *command {
func newCommand(log *zap.Logger, env map[string]string, cmd []string, execTimeout time.Duration) *command {
return &command{
log: log,
env: env,
Expand Down Expand Up @@ -68,15 +68,27 @@ func (b *command) Write(data []byte) (int, error) {
}

// create command for the process
func (b *command) createProcess(env map[string]string, cmd string) *exec.Cmd {
func (b *command) createProcess(env map[string]string, cmd []string) *exec.Cmd {
// cmdArgs contain command arguments if the command in form of: php <command> or ls <command> -i -b
var cmdArgs []string
var command *exec.Cmd
cmdArgs = append(cmdArgs, strings.Split(cmd, " ")...)
if len(cmdArgs) < 2 {
command = exec.Command(cmd)

// here we may have 2 cases: command declared as a space separated string or as a slice
switch len(cmd) {
// command defined as a space separated string
case 1:
// we know that the len is 1, so we can safely use the first element
cmdArgs = append(cmdArgs, strings.Split(cmd[0], " ")...)
default:
// we have a slice with a 2 or more elements
// first element is the command, the rest are arguments
cmdArgs = cmd
}

if len(cmdArgs) == 1 {
command = exec.Command(cmd[0])
} else {
command = exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:gosec
command = exec.Command(cmdArgs[0], cmdArgs[1:]...)
}

// set env variables from the config
Expand Down
44 changes: 33 additions & 11 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,18 @@ func (p *Plugin) Init(cfg Configurer, log NamedLogger) error {

p.log = new(zap.Logger)
p.log = log.NamedLogger(PluginName)
p.preparedCmd = append(p.preparedCmd, strings.Split(p.cfg.Command, " ")...)

// here we may have 2 cases: command declared as a space separated string or as a slice
switch len(p.cfg.Command) {
// command defined as a space separated string
case 1:
// we know that the len is 1, so we can safely use the first element
p.preparedCmd = append(p.preparedCmd, strings.Split(p.cfg.Command[0], " ")...)
default:
// we have a slice with a 2 or more elements
// first element is the command, the rest are arguments
p.preparedCmd = p.cfg.Command
}

p.preparedEnvs = append(os.Environ(), fmt.Sprintf(RrRelay+"=%s", p.cfg.Relay))
if p.rpcCfg != nil && p.rpcCfg.Listen != "" {
Expand Down Expand Up @@ -180,9 +191,9 @@ func (p *Plugin) CmdFactory(env map[string]string) func() *exec.Cmd {
var cmd *exec.Cmd

if len(p.preparedCmd) == 1 {
cmd = exec.Command(p.preparedCmd[0]) //nolint:gosec
cmd = exec.Command(p.preparedCmd[0])
} else {
cmd = exec.Command(p.preparedCmd[0], p.preparedCmd[1:]...) //nolint:gosec
cmd = exec.Command(p.preparedCmd[0], p.preparedCmd[1:]...)
}

// copy prepared envs
Expand Down Expand Up @@ -253,22 +264,32 @@ func (p *Plugin) GID() int {
}

// customCmd used as and enhancement for the CmdFactory to use with a custom command string (used by default)
func (p *Plugin) customCmd(env map[string]string) func(command string) *exec.Cmd {
return func(command string) *exec.Cmd {
func (p *Plugin) customCmd(env map[string]string) func(command []string) *exec.Cmd {
return func(command []string) *exec.Cmd {
// if no command provided, use the server's one
if command == "" {
if len(command) == 0 {
command = p.cfg.Command
}

var cmd *exec.Cmd

preparedCmd := make([]string, 0, 10)
preparedCmd = append(preparedCmd, strings.Split(command, " ")...)
preparedCmd := make([]string, 0, 5)
// here we may have 2 cases: command declared as a space separated string or as a slice
switch len(command) {
// command defined as a space separated string
case 1:
// we know that the len is 1, so we can safely use the first element
preparedCmd = append(preparedCmd, strings.Split(command[0], " ")...)
default:
// we have a slice with a 2 or more elements
// first element is the command, the rest are arguments
preparedCmd = command
}

if len(preparedCmd) == 1 {
cmd = exec.Command(preparedCmd[0]) //nolint:gosec
cmd = exec.Command(preparedCmd[0])
} else {
cmd = exec.Command(preparedCmd[0], preparedCmd[1:]...) //nolint:gosec
cmd = exec.Command(preparedCmd[0], preparedCmd[1:]...)
}

// copy prepared envs
Expand Down Expand Up @@ -323,7 +344,8 @@ func (p *Plugin) NewPool(ctx context.Context, cfg *pool.Config, env map[string]s

// we have after init command
if p.cfg.AfterInit != nil {
if pl.GetConfig().AfterInitCommand != "" {
// if AfterInitCommand is not empty, use it
if len(pl.GetConfig().AfterInitCommand) != 0 {
p.cfg.AfterInit.Command = pl.GetConfig().AfterInitCommand
}

Expand Down
32 changes: 29 additions & 3 deletions plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestCommandUnknownUser(t *testing.T) {
log: log,
}

_ = p.customCmd(nil)("php foo/bar")
_ = p.customCmd(nil)([]string{"php foo/bar"})
})
}

Expand All @@ -45,7 +45,7 @@ func TestCommand1(t *testing.T) {
log: log,
}

cmd := p.customCmd(nil)("php foo/bar")
cmd := p.customCmd(nil)([]string{"php foo/bar"})
require.Equal(t, "php", cmd.Args[0])
require.Equal(t, "foo/bar", cmd.Args[1])
}
Expand All @@ -58,12 +58,38 @@ func TestCommand2(t *testing.T) {
log: log,
}

cmd := p.customCmd(nil)("php foo bar")
cmd := p.customCmd(nil)([]string{"php foo bar"})
require.Equal(t, "php", cmd.Args[0])
require.Equal(t, "foo", cmd.Args[1])
require.Equal(t, "bar", cmd.Args[2])
}

func TestCommand3(t *testing.T) {
log, _ := zap.NewDevelopment()
p := &Plugin{
preparedEnvs: make([]string, 0),
cfg: &Config{},
log: log,
}

cmd := p.customCmd(nil)([]string{"php", "foo/bar"})
require.Equal(t, "php", cmd.Args[0])
require.Equal(t, "foo/bar", cmd.Args[1])
}

func TestCommand4_spaces(t *testing.T) {
log, _ := zap.NewDevelopment()
p := &Plugin{
preparedEnvs: make([]string, 0),
cfg: &Config{},
log: log,
}

cmd := p.customCmd(nil)([]string{"/Application Support/folder/php", "foo/bar"})
require.Equal(t, "/Application Support/folder/php", cmd.Args[0])
require.Equal(t, "foo/bar", cmd.Args[1])
}

func TestEnv(t *testing.T) {
log, _ := zap.NewDevelopment()
p := &Plugin{
Expand Down

0 comments on commit 107d62e

Please sign in to comment.