firehol/netdata

View on GitHub
src/go/plugin/go.d/pkg/dockerhost/dockerhost.go

Summary

Maintainability
A
1 hr
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package dockerhost

import (
    "bytes"
    "context"
    "fmt"
    "os"
    "strings"

    typesContainer "github.com/docker/docker/api/types/container"
    docker "github.com/docker/docker/client"
    "github.com/docker/docker/pkg/stdcopy"
)

func FromEnv() string {
    addr := os.Getenv("DOCKER_HOST")
    if addr == "" {
        return ""
    }
    if strings.HasPrefix(addr, "tcp://") || strings.HasPrefix(addr, "unix://") {
        return addr
    }
    if strings.HasPrefix(addr, "/") {
        return fmt.Sprintf("unix://%s", addr)
    }
    return fmt.Sprintf("tcp://%s", addr)
}

func Exec(ctx context.Context, container string, cmd string, args ...string) ([]byte, error) {
    // based on https://github.com/moby/moby/blob/8e610b2b55bfd1bfa9436ab110d311f5e8a74dcb/integration/internal/container/exec.go#L38

    addr := docker.DefaultDockerHost
    if v := FromEnv(); v != "" {
        addr = v
    }

    cli, err := docker.NewClientWithOpts(docker.WithHost(addr))
    if err != nil {
        return nil, fmt.Errorf("failed to create docker client: %v", err)
    }

    defer func() { _ = cli.Close() }()

    cli.NegotiateAPIVersion(ctx)

    execCreateConfig := typesContainer.ExecOptions{
        AttachStderr: true,
        AttachStdout: true,
        Cmd:          append([]string{cmd}, args...),
    }

    createResp, err := cli.ContainerExecCreate(ctx, container, execCreateConfig)
    if err != nil {
        return nil, fmt.Errorf("failed to container exec create ('%s'): %v", container, err)
    }

    attachResp, err := cli.ContainerExecAttach(ctx, createResp.ID, typesContainer.ExecAttachOptions{})
    if err != nil {
        return nil, fmt.Errorf("failed to container exec attach ('%s'): %v", container, err)
    }
    defer attachResp.Close()

    var outBuf, errBuf bytes.Buffer
    done := make(chan error)

    defer close(done)

    go func() {
        _, err := stdcopy.StdCopy(&outBuf, &errBuf, attachResp.Reader)
        select {
        case done <- err:
        case <-ctx.Done():
        }
    }()

    select {
    case err := <-done:
        if err != nil {
            return nil, fmt.Errorf("failed to read response from container ('%s'): %v", container, err)
        }
    case <-ctx.Done():
        return nil, fmt.Errorf("timed out reading response")
    }

    inspResp, err := cli.ContainerExecInspect(ctx, createResp.ID)
    if err != nil {
        return nil, fmt.Errorf("failed to container exec inspect ('%s'): %v", container, err)
    }

    if inspResp.ExitCode != 0 {
        msg := strings.ReplaceAll(errBuf.String(), "\n", " ")
        return nil, fmt.Errorf("command returned non-zero exit code (%d), error: '%s'", inspResp.ExitCode, msg)
    }

    return outBuf.Bytes(), nil
}