go.d add function to execute a command inside a Docker container (#18509)
Ilya Mashchenko committed
Sep 10, 2024 at 16:46 UTC
c1454e4d4dd788ba5f7a4d3348f7bc58f152342a
1 file changed
+75
src/go/plugin/go.d/pkg/dockerhost/dockerhost.go
+75
@@ -3,9 +3,15 @@
3
package dockerhost
4
5
import (
6
+ "bytes"
7
+ "context"
8
"fmt"
9
"os"
10
"strings"
11
+
12
+ typesContainer "github.com/docker/docker/api/types/container"
13
+ docker "github.com/docker/docker/client"
14
+ "github.com/docker/docker/pkg/stdcopy"
15
)
16
17
func FromEnv() string {
@@ -21,3 +27,72 @@ func FromEnv() string {
27
}
28
return fmt.Sprintf("tcp://%s", addr)
29
}
30
+
31
+func Exec(ctx context.Context, containerId string, cmd string, args ...string) ([]byte, error) {
32
+ // based on https://github.com/moby/moby/blob/8e610b2b55bfd1bfa9436ab110d311f5e8a74dcb/integration/internal/container/exec.go#L38
33
+
34
+ addr := docker.DefaultDockerHost
35
+ if v := FromEnv(); v != "" {
36
+ addr = v
37
+ }
38
+
39
+ cli, err := docker.NewClientWithOpts(docker.WithHost(addr))
40
+ if err != nil {
41
+ return nil, fmt.Errorf("failed to create docker client: %v", err)
42
+ }
43
+
44
+ defer cli.Close()
45
+
46
+ cli.NegotiateAPIVersion(ctx)
47
+
48
+ execCreateConfig := typesContainer.ExecOptions{
49
+ AttachStderr: true,
50
+ AttachStdout: true,
51
+ Cmd: append([]string{cmd}, args...),
52
+ }
53
+
54
+ createResp, err := cli.ContainerExecCreate(ctx, containerId, execCreateConfig)
55
+ if err != nil {
56
+ return nil, fmt.Errorf("failed to container exec create ('%s'): %v", containerId, err)
57
+ }
58
+
59
+ attachResp, err := cli.ContainerExecAttach(ctx, createResp.ID, typesContainer.ExecAttachOptions{})
60
+ if err != nil {
61
+ return nil, fmt.Errorf("failed to container exec attach ('%s'): %v", containerId, err)
62
+ }
63
+ defer attachResp.Close()
64
+
65
+ var outBuf, errBuf bytes.Buffer
66
+ done := make(chan error)
67
+
68
+ defer close(done)
69
+
70
+ go func() {
71
+ _, err := stdcopy.StdCopy(&outBuf, &errBuf, attachResp.Reader)
72
+ select {
73
+ case done <- err:
74
+ case <-ctx.Done():
75
+ }
76
+ }()
77
+
78
+ select {
79
+ case err := <-done:
80
+ if err != nil {
81
+ return nil, fmt.Errorf("failed to read response from container ('%s'): %v", containerId, err)
82
+ }
83
+ case <-ctx.Done():
84
+ return nil, fmt.Errorf("timed out reading response")
85
+ }
86
+
87
+ inspResp, err := cli.ContainerExecInspect(ctx, createResp.ID)
88
+ if err != nil {
89
+ return nil, fmt.Errorf("failed to container exec inspect ('%s'): %v", containerId, err)
90
+ }
91
+
92
+ if inspResp.ExitCode != 0 {
93
+ msg := strings.ReplaceAll(errBuf.String(), "\n", " ")
94
+ return nil, fmt.Errorf("command returned non-zero exit code (%d), error: '%s'", inspResp.ExitCode, msg)
95
+ }
96
+
97
+ return outBuf.Bytes(), nil
98
+}