I'm trying to run a command in Golang, but it looks like it loses the exit code because the err is nil:
func runCommand() []byte, error {
cmd := exec.Command("/bin/bash", "-c", "KUBECONFIG=/tmp/.kube/config helm version")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
stdOut, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(stdOut)
if err != nil {
return nil, err
}
if err := cmd.Wait(); err != nil {
return nil, err
}
fmt.Println(string(bytes))
return bytes, nil
}
This return nil, even though the command returns with exit code != 0.
If I type:
$> /bin/bash -c KUBECONFIG=/tmp/.kube/config helm version
$<
$> echo $?
$< 0
If I type:
$> /bin/bash -c 'KUBECONFIG=/tmp/.kube/config helm version'
$< ...connection refused
$> echo $?
$< 1
So I tried to wrap the command in single quote:
cmd := exec.Command("/bin/bash", "-c", "'KUBECONFIG=/tmp/.kube/config helm version'")
but then I get:
/bin/bash: KUBECONFIG=/tmp/.kube/config helm version: No such file or directory
(needless to say that /tmp/.kube/config is there, but I don't think the no such file or directory refers to that anyway).
What am I doing wrong?
Thanks
UPDATE: turns out I got it wrong. In fact I had two commands attempted and for some reason I was sure the one failing was the one I mentioned above, when instead the second command was exiting with a status code different from 0. The code works as expected and the err is not nil in case of exit code != 0. Sorry about that.
Seems like you should be able to get it with exec.ExitError
, see exec package. Note that you may need Go 1.12. Here's a runnable example (but it won't give you realistic output at the go playground):
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
)
func main() {
cmd := exec.Command(`/bin/bash`, `-c`, `FOO=bar ls /foo`)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
stdOut, err := cmd.StdoutPipe()
if err != nil {
fmt.Println("Error 1")
}
if err := cmd.Start(); err != nil {
fmt.Println("Error 2")
}
bytes, err := ioutil.ReadAll(stdOut)
if err != nil {
fmt.Println("Error 3")
}
if err := cmd.Wait(); err != nil {
fmt.Println("Error 4")
if exitError, ok := err.(*exec.ExitError); ok {
fmt.Printf("Exit code is %d
", exitError.ExitCode())
}
}
fmt.Println(string(bytes))
}
On my system that prints:
$ go run main.go
ls: cannot access '/foo': No such file or directory
Error 4
Exit code is 2
If that doesn't work for you, maybe it's worth following @JimB's suggestion and invoking helm directly? The Go standard library should support pipes as well:
It wraps os.StartProcess to make it easier to remap stdin and stdout, connect I/O with pipes, and do other adjustments.