Last active
July 5, 2023 21:09
-
-
Save ramizpolic/235267cd10eb79c3cc121d9327f3d059 to your computer and use it in GitHub Desktop.
Running arbitrary scripts in Docker via Golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"context" | |
"fmt" | |
"github.com/docker/docker/api/types" | |
"github.com/docker/docker/api/types/container" | |
"github.com/docker/docker/client" | |
"io" | |
"os" | |
"path" | |
) | |
func main() { | |
dc, ctx := getClient(), context.Background() | |
// Exec data | |
image := "alpine:3.18.2" | |
script := ` | |
#!/bin/sh | |
# Print to console | |
echo "First line in console" | |
echo "Second line in console" | |
echo -e "Console split\n\tLine" | |
# Pipe to a file | |
echo "First line in file" > /tmp/docker.file | |
echo "Second line in file" >> /tmp/docker.file | |
echo -e "File split\n\tLine" >> /tmp/docker.file | |
# e.g. Invoke some CLI here | |
# Fail on purpose, should be visible in status | |
exit 1 | |
` | |
// Pull | |
pl, err := dc.ImagePull(ctx, image, types.ImagePullOptions{}) | |
if err != nil { | |
panic(err) | |
} | |
_, _ = io.Copy(io.Discard, pl) | |
_ = pl.Close() | |
// Use unique dir to ensure idempotency between runs | |
localFilePath := path.Join(os.TempDir(), "unique-dir", "docker.file") | |
// Create | |
resp, err := dc.ContainerCreate( | |
ctx, | |
&container.Config{ | |
Image: image, | |
Cmd: []string{"sh", "-c", script}, | |
}, | |
&container.HostConfig{ | |
Binds: []string{ | |
// It is possible to attach a file directly, | |
// but it has to exist on the host already. | |
// Otherwise, a dir will be created. | |
// We will attach a dir directly instead and keep it simple. | |
fmt.Sprintf("%s:/tmp", path.Dir(localFilePath)), | |
}, | |
}, | |
nil, | |
nil, | |
"", | |
) | |
if err != nil { | |
panic(err) | |
} | |
// Start | |
err = dc.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) | |
if err != nil { | |
panic(err) | |
} | |
// Wait | |
statusCh, errCh := dc.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) | |
select { | |
case status := <-statusCh: | |
fmt.Println("wait status: ", status) | |
case err := <-errCh: | |
panic(err) | |
} | |
// Get logs | |
out, err := dc.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ | |
ShowStdout: true, | |
ShowStderr: true, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
scanner := bufio.NewScanner(out) | |
for scanner.Scan() { | |
data := scanner.Text() | |
if len(data) <= 8 { // internal flags | |
continue | |
} | |
fmt.Println("log: ", string(data[8:])) | |
} | |
// Remove | |
err = dc.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{ | |
RemoveVolumes: true, | |
RemoveLinks: false, | |
Force: true, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
// Print contents from a local file | |
err = printFile(localFilePath) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func getClient() *client.Client { | |
dockerClient, err := client.NewClientWithOpts(client.FromEnv) | |
if err != nil { | |
panic(err) | |
} | |
dockerClient.NegotiateAPIVersion(context.Background()) | |
return dockerClient | |
} | |
func printFile(path string) error { | |
file, err := os.Open(path) | |
if err != nil { | |
return err | |
} | |
defer file.Close() | |
scanner := bufio.NewScanner(file) | |
for scanner.Scan() { | |
fmt.Println("file: ", scanner.Text()) | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment