@cryptotaxi247 / netdata-1 / commits / c1454e4d4

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 +}