Run a docker container inside a Go server - bash

I am trying to run a docker container in a Go server with using exec.Command.
Here is my code to run:
entrypoint := "/bin/bash"
run := fmt.Sprintf("docker run -a stdout -a stderr --rm %s %s %s", env, image, args)
cmd := exec.Command(entrypoint, "-c", run)
if err := cmd.Start(); err != nil {
return err
}
where env is something like:
-e KEY=VALUE
However, I get an error such as:
/bin/bash: docker run ... : No such file or directory
Then, I added double-quotes to the beginning and the end of docker command:
cmd := exec.Command(entrypoint, "-c", "\""+run+"\"")
This time, I can run command /bin/bash -c "docker run ..." in my Zsh terminal. However Go still throws the same error.
What should I do?

You are passing the docker run command and all its arguments as a single argument. Instead, try this:
cmd := exec.Command(entrypoint, "-c", "docker", "run", "-a", "stdout", "-a", "stderr", "--rm", <break up env>, image, args)

Related

Complex command in exec.Command()

I want to exec this command:
docker exec -it demoContainer bash -c "pip freeze" > "requirements.txt"
But don't find any example of this kind of complex commands with pipes, and output writes.
I tryed this:
cmd := exec.Command("docker", "exec -it demoContainer bash -c \"pip freeze\" > \"requirements.txt\"")
_, err = cmd.CombinedOutput()
And:
cmd := exec.Command("docker", "exec", "-it", "demoContainer", "bash", "-c", "pip freeze", ">", "scheduleDeploy/requirements.txt")
_, err = cmd.CombinedOutput()
But always crash with the following output:
Error: Exit status 1
Edit
Updates with answers ideas:
err = exec.Command("sh", "-c","'docker exec -it demoContainer bash -c 'pip freeze' > 'requirements.txt''").Run()
Output:
Exit error status 2
You don't have to use the shell for redirection, you can just let Go do it:
package main
import (
"os"
"os/exec"
)
func main() {
f, e := os.Create("requirements.txt")
if e != nil {
panic(e)
}
defer f.Close()
c := exec.Command(
"docker", "exec", "-it", "demoContainer", "bash", "-c", "pip freeze",
)
c.Stdout = f
c.Run()
}
The > redirection is a "shell" thing, not an "exec" thing. You will need to do one of "run bash, with the command you want executed" or "open a file, set the resulting os.File as the Stdout of the exec.Command, before starting it".
Either would probably solve the issue you are facing.
To use the "bash -c" version, something like:
exec.Command("bash", "-c", `docker exec -it demoContainer bash -c "pip freeze" > requirements.txt`)

Programmatically exec into docker container

I would like to write a program that allows the user to choose a running docker container which they can be given an interactive shell to. The flow of the program I would like to have is roughly the following:
user runs program from bash - ./my_program
user is given an
interactive cli they can use to choose which docker container to
exec into
when user chooses container, something like docker exec -it <CONTAINER_ID> bash is run from my_program, my_program exits, and the user is transferred into a shell session of the docker container as if they had manually run docker exec -it <CONTAINER_ID> bash
I'm trying this from golang with the following code:
rv, err := exec.Command("docker", "exec", "-it", containerId, "bash").Output()
log.Infof("RV: %v", rv)
if err != nil {
log.Errorf("Error exec-ing into container: %s", err)
}
and see the following output:
RV: []
Error exec-ing into container: exit status 1
I'm trying with err := exec.Command("docker", "exec", "-it", containerId, "bash").Run() as well and see the same error.
How might I go about creating a program like this or debugging what's currently happening?
Since you are launching your docker exec in interactive mode (-i) and with a tty (-t) the exec.Command needs to be provided a means for the user to send input/receive output from the tty.
To tie the cmd's stdin/stdout directly to your go process:
cmd := exec.Command("docker", "exec", "-it", containerId, "bash")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
panic(err)
}
// reached once user types `exit` from the bash shell
fmt.Println("Docker: bash console ended successfully")

exec command with parameters in Go?

In my shell, I can exec command acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please and get output.
now I want to do that in go, and my code like:
cmd := exec.Command("bash", "-c", "acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please");
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("issue failed with error: %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
but I got error exit status 1.
and as the comment said, I separate argument:
exec.Command("bash", "-c", "acme.sh", "--issue", "--dns", "-d exmaple.com", "--yes-I-know-dns-manual-mode-enough-go-ahead-please");
but the result is that it exec acme.sh without parameters.
Using this script as acme.sh
#!/bin/bash
echo "$*"
with the program as given in the same directory the error you report occurs
However, if I add the current directory to the shell PATH
export PATH=.:$PATH
Then the program executes as hoped for, in the case of my version
$ go run c.go
combined out:
--issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please
Ok so that's the case when the bash -c takes a single string as the command (more about that in a moment)
If the command is issued like this
cmd := exec.Command("acme.sh", "--issue", "--dns", "-d exmaple.com", "--
yes-I-know-dns-manual-mode-enough-go-ahead-please")
Then as the later edits to your question state, the command acme.sh is run without arguments.
The problem is with the way that bash -c behaves.
From the man page
bash interprets the following options when it
is invoked:
-c If the -c option is present, then commands are read from the
first non-option argument command_string. If there are argu‐
ments after the command_string, they are assigned to the
positional parameters, starting with $0.
In your case what this means is that the first arg to bash -c is accepted as a command. The other args are lost as they are positional args to the new bash shell and not to the acme.sh command
See this for more details on bash -c https://unix.stackexchange.com/questions/144514/add-arguments-to-bash-c
Lastly, what I would do in this situation: Skip the "bash" "-c", ensure that the script has a correct bang line and rely on the kernel binfmt handler
exclude exit status 1 from err will get right result.
cmd := exec.Command("bash", "-c", "acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please");
out, err := cmd.CombinedOutput()
if err != nil && err.Error() != "exit status 1" {
log.Fatalf("issue failed with error: %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))

The result of docker exec command

I need to know in my shell script the output of some docker exec commands, for example I have an nginx container and in my script I run:
docker exec -it containerName /etc/init.d/nginx configtest
I want to continue script execution only if nginx config test is success, not when fail.
I've tried out to use $?, but it is 0 even then configtest output is fail (because docker exec is successfully executed, as I understand).
I found this to be working quite well:
docker exec -t -i my-container sh -c 'my-command; exit $?'
Translating my comment into an answer. This should work:
docker exec -it mynginx /etc/init.d/nginx configtest && echo "pass" || echo "fail"
It works for me.
The api/client/exec.go#L97-L100 does get the exit code:
var status int
if _, status, err = getExecExitCode(cli, execID); err != nil {
return err
}
That comes from api/client/utils.go#L84-L97
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
resp, err := cli.client.ContainerExecInspect(execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != lib.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return resp.Running, resp.ExitCode, nil
}
So if your command fail, you will get the exit code.
Although, as mentioned here, you could use nginx -t instead of configtest.
If you don't want to immediately respond to exit code returned from a process triggered by docker exec command, you could do something like this.
docker exec my-app-container /app/scripts/test.sh
UNIT_TEST_EXIT_CODE=$? # Get exit code from last command (/app/scripts/test.sh)
... # do some stuff after unit testing
exit $UNIT_TEST_EXIT_CODE
I used this approach in the context of CI/CD pipelines.
A new version ready to test to run a bash command:
docker exec -t -i oracle bash -c "echo hola && exit"
hola

Executing docker command using golang exec fails

I am using cmd.go (see below) to execute a docker command but it fails. I do the following steps to execute and get the following error.
go build
sudo ./cmd
Output:
docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m
2014/10/16 14:32:12 exit status 1
On the other hand running directly as
sudo docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m
results in the correct output of a.out.
Hello World
This is the code of cmd.go. How can I get it to work? Thanks!
package main
import (
"fmt"
"log"
"os/exec"
"strings"
)
func ExampleCmd_Output() {
//out, err := exec.Command("date", "--version").Output() // This works
//out, err := exec.Command("docker", "--version").Output() // This works
//out, err := exec.Command(cmd, "images").Output() // Even docker images command works!
cmd := "docker"
cmdArgs := []string{"run", "-v", "~/exp/a.out:/a.out", "ubuntu:14.04", "/a.out", "-m", "10m"}
fmt.Println(cmd + " " + strings.Join(cmdArgs, " "))
out, err := exec.Command(cmd, cmdArgs...).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", out)
}
func main() {
ExampleCmd_Output()
}
EDIT: After a comment, I tried executing the command "docker images". It works if I run the executable with sudo. That is, I am using the following line in the code now.
out, err := exec.Command(cmd, "images").Output()
After doing go build and running "sudo ./cmd", I get the output of docker images command. However, without sudo, I still get exit status 1. But with docker run command above even with sudo, I don't get an output.
Thanks to Os Exec Sudo Command in Go, I am now able to do what I want.
func main() {
cmdStr := "sudo docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m"
out, _ := exec.Command("/bin/sh", "-c", cmdStr).Output()
fmt.Printf("%s", out)
}
Output:
Hello World

Resources