Programmatically exec into docker container - bash

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")

Related

Run a docker container inside a Go server

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)

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`)

Go "exec.Command" of tcpdump not doing anything

I am attempting to run the following code in Go. I have tried both of the following ways:
out, err := exec.Command("sh", "-c", "tcpdump -i ens0 host 192.168.1.100 -F ./testfile").Output()
fmt.Println(string(out)) // Prints nothing
fmt.Println(err) // exit status 1
I have also tried replacing sh with /bin/bash.
I have also tried the following, with and without sh as the first argument:
out, err := exec.Command("tcpdump", "-i", "ens0", "host", "192.168.1.100", "-F", "./testfile").Output()
fmt.Println(string(out)) // Prints nothing
fmt.Println(err) // exit status 1
None of this is working. Can someone see what I am doing wrong? I have also tried this go package "github.com/kami-zh/go-capturer" to read stderr and again it prints nothing.
Normally I have to use sudo to execute tcpdump from shell, so I build the go binary and execute it as root user.
Something like this should work, i am not sure if there is any specific command like -F available in tcp dump,
If you want to capture plain output of the tcp dump , you can direct the output to file using > file . The -w option is for wireshark/tcpdump format , to read and display
cmd := exec.Command("sh", "-c", "sudo tcpdump -i <eth> host <ip> -w ./testfile")
err := cmd.Run()
if err != nil {
fmt.Println(err)
}
Thanks, #torek, the -c option can be used with tcpdump to exit after capturing n packets
cmd := exec.Command("sh", "-c", "sudo tcpdump -i ens33 -c 100 host localhost -w ./testfile")
err := cmd.Run()
if err != nil {
fmt.Println(err)
}
The other way is to use cmd.Start
cmd := exec.Command("sh", "-c", "sudo tcpdump -i ens33 -c 100 host localhost -w ./testfile")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
The tcpdump command continue to run infinitely if you use cmd.run without -c option with tcpdump cmd.So you can't see if you put print statement after cmd.Run() call, the reason being the exec.Command failed is, it just work the same way on how it works from the cli, so if you need sodo in front for it , you should put it in command as well or run it from the root user.

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