helpers/docker/official_docker_client.go (285 lines of code) (raw):
package docker
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"path/filepath"
"runtime"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
system "github.com/docker/docker/api/types/system"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
)
// ErrRedirectNotAllowed is returned when we get a 3xx request from the Docker
// client to prevent any redirections to malicious docker clients.
var ErrRedirectNotAllowed = errors.New("redirects disallowed")
// IsErrNotFound checks whether a returned error is due to an image or container
// not being found. Proxies the docker implementation.
func IsErrNotFound(err error) bool {
unwrapped := errors.Unwrap(err)
if unwrapped != nil {
err = unwrapped
}
return client.IsErrNotFound(err)
}
// type officialDockerClient wraps a "github.com/docker/docker/client".Client,
// giving it the methods it needs to satisfy the docker.Client interface
type officialDockerClient struct {
client *client.Client
transport *http.Transport
}
func newOfficialDockerClient(c Credentials, opts ...client.Opt) (*officialDockerClient, error) {
options := []client.Opt{
client.WithAPIVersionNegotiation(),
client.WithVersionFromEnv(),
}
// create the http.Transport instance here so we can cache it. In docker SKD >= v25 the http.Client's Transport
// instance is overwritten with an otelhttp.Transport, which does not expose its TSLCientConfig. Some tests need to
// access the TSLCientConfig to assert TSL was configured correctly.
transport := http.Transport{}
// options acting upon the client and transport need to be done in a
// specific order.
options = append(
options,
client.WithHost(c.Host),
WithCustomHTTPClient(&transport),
WithCustomTLSClientConfig(c),
)
options = append(options, opts...)
dockerClient, err := client.NewClientWithOpts(options...)
if err != nil {
logrus.Errorln("Error creating Docker client:", err)
return nil, err
}
return &officialDockerClient{
client: dockerClient,
transport: &transport,
}, nil
}
func wrapError(method string, err error, started time.Time) error {
if err == nil {
return nil
}
seconds := int(time.Since(started).Seconds())
if _, file, line, ok := runtime.Caller(2); ok {
return fmt.Errorf("%w (%s:%d:%ds)", err, filepath.Base(file), line, seconds)
}
return fmt.Errorf("%w (%s:%ds)", err, method, seconds)
}
func (c *officialDockerClient) ClientVersion() string {
return c.client.ClientVersion()
}
func (c *officialDockerClient) ServerVersion(ctx context.Context) (types.Version, error) {
return c.client.ServerVersion(ctx)
}
func (c *officialDockerClient) ImageInspectWithRaw(
ctx context.Context,
imageID string,
) (types.ImageInspect, []byte, error) {
started := time.Now()
image, data, err := c.client.ImageInspectWithRaw(ctx, imageID)
return image, data, wrapError("ImageInspectWithRaw", err, started)
}
func (c *officialDockerClient) ContainerList(
ctx context.Context,
options container.ListOptions,
) ([]types.Container, error) {
started := time.Now()
containers, err := c.client.ContainerList(ctx, options)
return containers, wrapError("ContainerList", err, started)
}
func (c *officialDockerClient) ContainerCreate(
ctx context.Context,
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *v1.Platform,
containerName string,
) (container.CreateResponse, error) {
started := time.Now()
container, err := c.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, containerName)
return container, wrapError("ContainerCreate", err, started)
}
func (c *officialDockerClient) ContainerStart(
ctx context.Context,
containerID string,
options container.StartOptions,
) error {
started := time.Now()
err := c.client.ContainerStart(ctx, containerID, options)
return wrapError("ContainerCreate", err, started)
}
func (c *officialDockerClient) ContainerKill(ctx context.Context, containerID string, signal string) error {
started := time.Now()
err := c.client.ContainerKill(ctx, containerID, signal)
return wrapError("ContainerKill", err, started)
}
func (c *officialDockerClient) ContainerStop(
ctx context.Context,
containerID string,
options container.StopOptions,
) error {
started := time.Now()
err := c.client.ContainerStop(ctx, containerID, options)
return wrapError("ContainerStop", err, started)
}
func (c *officialDockerClient) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
started := time.Now()
data, err := c.client.ContainerInspect(ctx, containerID)
return data, wrapError("ContainerInspect", err, started)
}
func (c *officialDockerClient) ContainerAttach(
ctx context.Context,
container string,
options container.AttachOptions,
) (types.HijackedResponse, error) {
started := time.Now()
response, err := c.client.ContainerAttach(ctx, container, options)
return response, wrapError("ContainerAttach", err, started)
}
func (c *officialDockerClient) ContainerRemove(
ctx context.Context,
containerID string,
options container.RemoveOptions,
) error {
started := time.Now()
err := c.client.ContainerRemove(ctx, containerID, options)
return wrapError("ContainerRemove", err, started)
}
func (c *officialDockerClient) ContainerWait(
ctx context.Context,
containerID string,
condition container.WaitCondition,
) (<-chan container.WaitResponse, <-chan error) {
return c.client.ContainerWait(ctx, containerID, condition)
}
func (c *officialDockerClient) ContainerLogs(
ctx context.Context,
container string,
options container.LogsOptions,
) (io.ReadCloser, error) {
started := time.Now()
rc, err := c.client.ContainerLogs(ctx, container, options)
return rc, wrapError("ContainerLogs", err, started)
}
func (c *officialDockerClient) ContainerExecCreate(
ctx context.Context,
container string,
config container.ExecOptions,
) (types.IDResponse, error) {
started := time.Now()
resp, err := c.client.ContainerExecCreate(ctx, container, config)
return resp, wrapError("ContainerExecCreate", err, started)
}
func (c *officialDockerClient) ContainerExecAttach(
ctx context.Context,
execID string,
config container.ExecStartOptions,
) (types.HijackedResponse, error) {
started := time.Now()
resp, err := c.client.ContainerExecAttach(ctx, execID, config)
return resp, wrapError("ContainerExecAttach", err, started)
}
func (c *officialDockerClient) NetworkCreate(
ctx context.Context,
networkName string,
options network.CreateOptions,
) (network.CreateResponse, error) {
started := time.Now()
response, err := c.client.NetworkCreate(ctx, networkName, options)
return response, wrapError("NetworkCreate", err, started)
}
func (c *officialDockerClient) NetworkRemove(ctx context.Context, networkID string) error {
started := time.Now()
err := c.client.NetworkRemove(ctx, networkID)
return wrapError("NetworkRemove", err, started)
}
func (c *officialDockerClient) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error {
started := time.Now()
err := c.client.NetworkDisconnect(ctx, networkID, containerID, force)
return wrapError("NetworkDisconnect", err, started)
}
func (c *officialDockerClient) NetworkList(
ctx context.Context,
options network.ListOptions,
) ([]network.Summary, error) {
started := time.Now()
networks, err := c.client.NetworkList(ctx, options)
return networks, wrapError("NetworkList", err, started)
}
func (c *officialDockerClient) NetworkInspect(ctx context.Context, networkID string) (network.Inspect, error) {
started := time.Now()
resource, err := c.client.NetworkInspect(ctx, networkID, network.InspectOptions{})
return resource, wrapError("NetworkInspect", err, started)
}
func (c *officialDockerClient) VolumeCreate(
ctx context.Context,
options volume.CreateOptions,
) (volume.Volume, error) {
started := time.Now()
v, err := c.client.VolumeCreate(ctx, options)
return v, wrapError("VolumeCreate", err, started)
}
func (c *officialDockerClient) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
started := time.Now()
err := c.client.VolumeRemove(ctx, volumeID, force)
return wrapError("VolumeRemove", err, started)
}
func (c *officialDockerClient) VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error) {
started := time.Now()
v, err := c.client.VolumeInspect(ctx, volumeID)
return v, wrapError("VolumeInspect", err, started)
}
func (c *officialDockerClient) Info(ctx context.Context) (system.Info, error) {
started := time.Now()
info, err := c.client.Info(ctx)
return info, wrapError("Info", err, started)
}
func (c *officialDockerClient) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (image.LoadResponse, error) {
started := time.Now()
resp, err := c.client.ImageLoad(ctx, input, quiet)
return resp, wrapError("ImageLoad", err, started)
}
func (c *officialDockerClient) ImageTag(ctx context.Context, source string, target string) error {
started := time.Now()
return wrapError("ImageTag", c.client.ImageTag(ctx, source, target), started)
}
func (c *officialDockerClient) ImageImportBlocking(
ctx context.Context,
source image.ImportSource,
ref string,
options image.ImportOptions,
) error {
started := time.Now()
rc, err := c.client.ImageImport(ctx, source, ref, options)
if err != nil {
return wrapError("ImageImport", err, started)
}
return wrapError("ImageImport", c.handleEventStream(rc), started)
}
func (c *officialDockerClient) ImagePullBlocking(
ctx context.Context,
ref string,
options image.PullOptions,
) error {
started := time.Now()
rc, err := c.client.ImagePull(ctx, ref, options)
if err != nil {
return wrapError("ImagePull", err, started)
}
return wrapError("ImagePull", c.handleEventStream(rc), started)
}
func (c *officialDockerClient) handleEventStream(rc io.ReadCloser) error {
defer func() { _ = rc.Close() }()
return jsonmessage.DisplayJSONMessagesStream(rc, io.Discard, 0, false, nil)
}
func (c *officialDockerClient) Close() error {
return c.client.Close()
}
// New attempts to create a new Docker client of the specified version. If the
// specified version is empty, it will use the default version.
//
// If no host is given in the Credentials, it will attempt to look up
// details from the environment. If that fails, it will use the default
// connection details for your platform.
func New(c Credentials, options ...client.Opt) (Client, error) {
if c.Host == "" {
c = credentialsFromEnv()
}
// Use the default if nothing is specified by caller *or* environment
if c.Host == "" {
c.Host = client.DefaultDockerHost
}
return newOfficialDockerClient(c, options...)
}