2

So I've tried to execute a chain i.e. multiple commands on a Pod's container using client-go, and it seems to only work for some commands like ls.

Here is what I've tried:

    req := client.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.ObjectMeta.Namespace).SubResource("exec") // .Param("container", containerName)
    scheme := runtime.NewScheme()
    if err := _v1.AddToScheme(scheme); err != nil {
        panic(err.Error())
    }
    parameterCodec := runtime.NewParameterCodec(scheme)
    req.VersionedParams(&_v1.PodExecOptions{
        Stdin:     false,
        Stdout:    true,
        Stderr:    true,
        TTY:       false,
        Container: containerName,
        Command:   strings.Fields("/bin/sh -c " + command),
    }, parameterCodec)
    exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL())
    if err != nil {
        panic(err.Error())
    }
    var stdout, stderr bytes.Buffer
    err = exec.Stream(remotecommand.StreamOptions{
        Stdin:  nil,
        Stdout: &stdout,
        Stderr: &stderr,
        Tty:    false,
    })
    if err != nil {
        panic(err.Error())
    }
log.Printf("Output from pod: %v", stdout.String())
log.Printf("Error from pod: %v", stderr.String())

When the command variable is just a simple ls -l, I get the desired output. But when I try to do something like 'ls -l && echo hello' it produces an error command terminated with exit code 2. It doesn't output anything if I only put echo hello. However, it does produce the desired output hello if I remove the Bourne Shell prefix /bin/sh -c and the Command attribute equals to string.Fields("echo hello"), but this approach doesn't let me chain commands.

All in all, what I am trying to do is to execute a chain of commands on a Pod's container.

Gigaxel
  • 1,058
  • 1
  • 9
  • 20
  • 2
    I think `Command` should be `[ "sh", "-c", "ls -l && echo hello" ]` rather than `[ "sh", "-c", "ls", "-l", "&&", "echo", "hello" ]` which is how string.Fields splits it. – Matt Mar 18 '20 at 11:38
  • Do you know if there's a way to get the exit code of the remotely executed command? – Paymahn Moghadasian Jun 09 '21 at 16:05

1 Answers1

4

The corev1.PodExecOptions.Command accept value of []string type.

req.VersionedParams(&_v1.PodExecOptions{
  Stdin:     false,
  Stdout:    true,
  Stderr:    true,
  TTY:       false,
  Container: containerName,
  Command:   cmds,
}, parameterCodec)

where, cmds can be:

cmds := []string{
    "sh",
    "-c",
    "echo $HOME; ls -l && echo hello",
    }

Output:

/root
total 68
drwxr-xr-x   2 root root 4096 Feb 24 00:00 bin
drwxr-xr-x   2 root root 4096 Feb  1 17:09 boot
drwxr-xr-x   2 root root 4096 Feb 24 00:00 mnt
drwxr-xr-x   2 root root 4096 Feb 24 00:00 opt
dr-xr-xr-x 396 root root    0 Mar 19 11:47 proc
drwx------   2 root root 4096 Feb 24 00:00 root
.
.
hello

Explanation: Tim's answer

command: ["/bin/sh","-c"]
args: ["command one; command two && command three"]

The command ["/bin/sh", "-c"] says "run a shell, and execute the following instructions". The args are then passed as commands to the shell. In shell scripting a semicolon separates commands, and && conditionally runs the following command if the first succeed. In the above example, it always runs command one followed by command two, and only runs command three if command two succeeded.

N.B.: For bash, it will be similar to something like below:

cmds := []string{
     "bash",
     "-c",
     `export MYSQL_PWD=${MYSQL_ROOT_PASSWORD}
mysql -h localhost -nsLNE -e "select 1;" 2>/dev/null | grep -v "*"`,
                    },
Kamol Hasan
  • 12,218
  • 1
  • 37
  • 46