diff --git a/configuration_flags.go b/configuration_flags.go index ef0e27e..722f01c 100644 --- a/configuration_flags.go +++ b/configuration_flags.go @@ -2,9 +2,12 @@ package ctrl import ( "flag" + "fmt" "log" "os" + "os/exec" "strconv" + "strings" "github.com/joho/godotenv" ) @@ -16,6 +19,8 @@ import ( // an if check should be added to the checkConfigValues function // to set default values when reading from config file. type Configuration struct { + // Shell on the operating system to use when executing cliCommands + ShellOnNode string // ConfigFolder, the location for the configuration folder on disk ConfigFolder string `comment:"ConfigFolder, the location for the configuration folder on disk"` // The folder where the socket file should live @@ -152,6 +157,7 @@ func NewConfiguration() *Configuration { } //flag.StringVar(&c.ConfigFolder, "configFolder", fc.ConfigFolder, "Defaults to ./usr/local/ctrl/etc/. *NB* This flag is not used, if your config file are located somwhere else than default set the location in an env variable named CONFIGFOLDER") + flag.StringVar(&c.ShellOnNode, "shellOnNode", CheckEnv("SHELL_ON_NODE", c.ShellOnNode).(string), "set a value to override the default shell used as interpreter for running cliCommand's on node.") flag.StringVar(&c.SocketFolder, "socketFolder", CheckEnv("SOCKET_FOLDER", c.SocketFolder).(string), "folder who contains the socket file. Defaults to ./tmp/. If other folder is used this flag must be specified at startup.") flag.StringVar(&c.ReadFolder, "readFolder", CheckEnv("READ_FOLDER", c.ReadFolder).(string), "folder who contains the readfolder. Defaults to ./readfolder/. If other folder is used this flag must be specified at startup.") flag.StringVar(&c.TCPListener, "tcpListener", CheckEnv("TCP_LISTENER", c.TCPListener).(string), "start up a TCP listener in addition to the Unix Socket, to give messages to the system. e.g. localhost:8888. No value means not to start the listener, which is default. NB: You probably don't want to start this on any other interface than localhost") @@ -220,14 +226,30 @@ func NewConfiguration() *Configuration { log.Fatalf("error: the centralNodeName config option or flag cannot be empty, check -help\n") } + c.ShellOnNode = getShell() + fmt.Printf("\n******** DETECTED SHELL: %v\n\n", c.ShellOnNode) + flag.Parse() return &c } +func getShell() string { + out, err := exec.Command("echo", os.ExpandEnv("$SHELL")).Output() + if err != nil { + log.Fatalf("error: unable to detect shell: %v\n", err) + } + + shell := string(out) + shell = strings.TrimSuffix(shell, "\n") + + return string(shell) +} + // Get a Configuration struct with the default values set. func newConfigurationDefaults() Configuration { c := Configuration{ + ShellOnNode: "", ConfigFolder: "./etc/", SocketFolder: "./tmp", ReadFolder: "./readfolder", diff --git a/requests_cli.go b/requests_cli.go index f8cb03c..0e418a4 100644 --- a/requests_cli.go +++ b/requests_cli.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "os/exec" + "runtime" "strings" "time" ) @@ -27,8 +28,6 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error go func() { defer proc.processes.wg.Done() - var a []string - switch { case len(message.MethodArgs) < 1: er := fmt.Errorf("error: methodCliCommand: got <1 number methodArgs") @@ -36,12 +35,8 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error newReplyMessage(proc, msgForErrors, []byte(er.Error())) return - case len(message.MethodArgs) >= 0: - a = message.MethodArgs[1:] } - c := message.MethodArgs[0] - // Get a context with the timeout specified in message.MethodTimeout. ctx, cancel := getContextForMethodTimeout(proc.ctx, message) @@ -70,7 +65,24 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error } } - cmd := exec.CommandContext(ctx, c, a...) + var cmd *exec.Cmd + + // For the Linux and Darwin operating system we allow to automatically detect + // shell interpreter, so the user don't have to type "bash", "-c" as the first + // two arguments of the methodArgs. + // We use the shell defined in the ShellOnNode variable as interpreter. Since + // it expects a "-c" directly after in the command we prefix it to the args. + if proc.configuration.ShellOnNode != "" { + switch runtime.GOOS { + case "linux", "darwin": + args := []string{"-c"} + args = append(args, message.MethodArgs...) + + cmd = exec.CommandContext(ctx, proc.configuration.ShellOnNode, args...) + default: + cmd = exec.CommandContext(ctx, message.MethodArgs[0], message.MethodArgs...) + } + } // Check for the use of env variable for CTRL_DATA, and set env if found. if foundEnvData { @@ -149,8 +161,6 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e // fmt.Printf(" * DONE *\n") }() - var a []string - switch { case len(message.MethodArgs) < 1: er := fmt.Errorf("error: methodCliCommand: got <1 number methodArgs") @@ -158,12 +168,8 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e newReplyMessage(proc, msgForErrors, []byte(er.Error())) return - case len(message.MethodArgs) >= 0: - a = message.MethodArgs[1:] } - c := message.MethodArgs[0] - // Get a context with the timeout specified in message.MethodTimeout. ctx, cancel := getContextForMethodTimeout(proc.ctx, message) // deadline, _ := ctx.Deadline() @@ -176,7 +182,24 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e go func() { defer proc.processes.wg.Done() - cmd := exec.CommandContext(ctx, c, a...) + var cmd *exec.Cmd + + // For the Linux and Darwin operating system we allow to automatically detect + // shell interpreter, so the user don't have to type "bash", "-c" as the first + // two arguments of the methodArgs. + // We use the shell defined in the ShellOnNode variable as interpreter. Since + // it expects a "-c" directly after in the command we prefix it to the args. + if proc.configuration.ShellOnNode != "" { + switch runtime.GOOS { + case "linux", "darwin": + args := []string{"-c"} + args = append(args, message.MethodArgs...) + + cmd = exec.CommandContext(ctx, proc.configuration.ShellOnNode, args...) + default: + cmd = exec.CommandContext(ctx, message.MethodArgs[0], message.MethodArgs...) + } + } // Using cmd.StdoutPipe here so we are continuosly // able to read the out put of the command. diff --git a/requests_operator.go b/requests_operator.go index bd4889d..763e1b0 100644 --- a/requests_operator.go +++ b/requests_operator.go @@ -61,7 +61,7 @@ func methodOpProcessStart(proc process, message Message, node string) ([]byte, e method := Method(m) tmpH := mt.getHandler(Method(method)) if tmpH == nil { - er := fmt.Errorf("error: OpProcessStart: no such request type defined: %v" + m) + er := fmt.Errorf("error: OpProcessStart: no such request type defined: %v", m) proc.errorKernel.errSend(proc, message, er, logWarning) return } @@ -119,7 +119,7 @@ func methodOpProcessStop(proc process, message Message, node string) ([]byte, er method := Method(methodString) tmpH := mt.getHandler(Method(method)) if tmpH == nil { - er := fmt.Errorf("error: OpProcessStop: no such request type defined: %v, check that the methodArgs are correct: " + methodString) + er := fmt.Errorf("error: OpProcessStop: no such request type defined: %v, check that the methodArgs are correct: ", methodString) proc.errorKernel.errSend(proc, message, er, logWarning) return }