diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index d8bbbe06ba8..ccfeb3a270a 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -29,13 +29,8 @@ binary { triage { suppress { vulnerabilities = [ - "GO-2022-0635", // github.com/aws/aws-sdk-go@v1.55.6 TODO(jrasell): remove when dep updated. - "GO-2025-3543", // github.com/opencontainers/runc TODO(jrasell): remove once withdrawn from DBs. - "GO-2025-3829", // https://github.com/moby/moby/releases/tag/v28.3.3 TODO(tgross): remove once verified, updated or withdrawn https://pkg.go.dev/vuln/GO-2025-3829 - "GO-2026-4887", // github.com/docker/docker with no current fix. - "GO-2026-4883", // github.com/docker/docker with no current fix. - "GHSA-x744-4wpc-v9h2", // github.com/docker/docker with no current fix. - "GHSA-pxq6-2prw-chj9", // github.com/docker/docker with no current fix. + "GO-2022-0635", // github.com/aws/aws-sdk-go@v1.55.6 TODO(jrasell): remove when dep updated. + "GO-2025-3543", // github.com/opencontainers/runc TODO(jrasell): remove once withdrawn from DBs. ] } } diff --git a/client/testutil/docker.go b/client/testutil/docker.go index 243660bf085..cb128201f14 100644 --- a/client/testutil/docker.go +++ b/client/testutil/docker.go @@ -7,8 +7,8 @@ import ( "runtime" "testing" - docker "github.com/docker/docker/client" "github.com/hashicorp/nomad/testutil" + docker "github.com/moby/moby/client" ) // DockerIsConnected checks to see if a docker daemon is available (local or remote) diff --git a/command/agent/agent_endpoint.go b/command/agent/agent_endpoint.go index ad6503da5b3..3ef587061a2 100644 --- a/command/agent/agent_endpoint.go +++ b/command/agent/agent_endpoint.go @@ -16,7 +16,6 @@ import ( "strings" "time" - "github.com/docker/docker/pkg/ioutils" log "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-msgpack/v2/codec" "github.com/hashicorp/nomad/acl" @@ -28,6 +27,7 @@ import ( "github.com/hashicorp/nomad/nomad" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/serf/serf" + "github.com/moby/moby/v2/pkg/ioutils" ) type Member struct { diff --git a/command/agent/event_endpoint.go b/command/agent/event_endpoint.go index ee91e2b3065..d002e522edc 100644 --- a/command/agent/event_endpoint.go +++ b/command/agent/event_endpoint.go @@ -15,9 +15,9 @@ import ( "strings" "time" - "github.com/docker/docker/pkg/ioutils" "github.com/hashicorp/go-msgpack/v2/codec" "github.com/hashicorp/nomad/nomad/structs" + "github.com/moby/moby/v2/pkg/ioutils" "golang.org/x/sync/errgroup" ) diff --git a/command/agent/fs_endpoint.go b/command/agent/fs_endpoint.go index 078a7224ff0..49d54a8ff04 100644 --- a/command/agent/fs_endpoint.go +++ b/command/agent/fs_endpoint.go @@ -13,10 +13,10 @@ import ( "strconv" "strings" - "github.com/docker/docker/pkg/ioutils" "github.com/hashicorp/go-msgpack/v2/codec" cstructs "github.com/hashicorp/nomad/client/structs" "github.com/hashicorp/nomad/nomad/structs" + "github.com/moby/moby/v2/pkg/ioutils" ) var ( diff --git a/drivers/docker/config.go b/drivers/docker/config.go index dd1c5adf2b1..9a5b48cb133 100644 --- a/drivers/docker/config.go +++ b/drivers/docker/config.go @@ -12,8 +12,6 @@ import ( "strings" "time" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" "github.com/hashicorp/go-hclog" "github.com/hashicorp/nomad/drivers/shared/capabilities" "github.com/hashicorp/nomad/helper/pluginutils/hclutils" @@ -22,6 +20,8 @@ import ( "github.com/hashicorp/nomad/plugins/drivers" "github.com/hashicorp/nomad/plugins/drivers/fsisolation" "github.com/hashicorp/nomad/plugins/shared/hclspec" + containerapi "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" ) const ( diff --git a/drivers/docker/coordinator.go b/drivers/docker/coordinator.go index 99cc8639122..d66599d2766 100644 --- a/drivers/docker/coordinator.go +++ b/drivers/docker/coordinator.go @@ -13,11 +13,10 @@ import ( "time" "github.com/containerd/errdefs" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/registry" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/nomad/nomad/structs" + "github.com/moby/moby/api/types/registry" + mclient "github.com/moby/moby/client" ) var ( @@ -72,9 +71,9 @@ func (p *pullFuture) set(imageID, imageUser string, err error) { // DockerImageClient provides the methods required to do CRUD operations on the // Docker images type DockerImageClient interface { - ImagePull(ctx context.Context, refStr string, opts image.PullOptions) (io.ReadCloser, error) - ImageInspectWithRaw(ctx context.Context, id string) (types.ImageInspect, []byte, error) - ImageRemove(ctx context.Context, id string, opts image.RemoveOptions) ([]image.DeleteResponse, error) + ImagePull(ctx context.Context, refStr string, opts mclient.ImagePullOptions) (mclient.ImagePullResponse, error) + ImageInspect(ctx context.Context, id string, inspectOpts ...mclient.ImageInspectOption) (mclient.ImageInspectResult, error) + ImageRemove(ctx context.Context, id string, opts mclient.ImageRemoveOptions) (mclient.ImageRemoveResult, error) } // LogEventFn is a callback which allows Drivers to emit task events. @@ -205,7 +204,7 @@ func (d *dockerCoordinator) pullImageImpl(imageID string, authOptions *registry. auth = *authOptions } - pullOptions := image.PullOptions{RegistryAuth: auth.Auth} + pullOptions := mclient.ImagePullOptions{RegistryAuth: auth.Auth} reader, err := d.client.ImagePull(pullCtx, dockerImageRef(repo, tag), pullOptions) if errors.Is(err, context.DeadlineExceeded) { @@ -230,7 +229,7 @@ func (d *dockerCoordinator) pullImageImpl(imageID string, authOptions *registry. d.logger.Debug("docker pull succeeded", "image_ref", dockerImageRef(repo, tag)) - dockerImage, _, err := d.client.ImageInspectWithRaw(d.ctx, imageID) + dockerImage, err := d.client.ImageInspect(d.ctx, imageID) if err != nil { d.logger.Error("failed getting image id", "image_name", imageID, "error", err) return "", "", recoverableErrTimeouts(err) @@ -344,7 +343,7 @@ func (d *dockerCoordinator) removeImageImpl(id string, ctx context.Context) { d.imageLock.Unlock() for i := 0; i < 3; i++ { - _, err := d.client.ImageRemove(d.ctx, id, image.RemoveOptions{ + _, err := d.client.ImageRemove(d.ctx, id, mclient.ImageRemoveOptions{ Force: true, // necessary to GC images referenced by multiple tags }) if err == nil { diff --git a/drivers/docker/coordinator_test.go b/drivers/docker/coordinator_test.go index 4a016eecc2c..1eb565568c8 100644 --- a/drivers/docker/coordinator_test.go +++ b/drivers/docker/coordinator_test.go @@ -7,17 +7,18 @@ import ( "context" "errors" "fmt" - "io" + "iter" "sync" "testing" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/image" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/testutil" + "github.com/moby/moby/api/types/image" + "github.com/moby/moby/api/types/jsonstream" + mclient "github.com/moby/moby/client" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" ) @@ -27,7 +28,7 @@ type mockImageClient struct { idToName map[string]string removed map[string]int pullDelay time.Duration - pullReader io.ReadCloser + pullReader mclient.ImagePullResponse lock sync.Mutex } @@ -40,7 +41,7 @@ func newMockImageClient(idToName map[string]string, pullDelay time.Duration) *mo } } -func (m *mockImageClient) ImagePull(ctx context.Context, refStr string, opts image.PullOptions) (io.ReadCloser, error) { +func (m *mockImageClient) ImagePull(ctx context.Context, refStr string, opts mclient.ImagePullOptions) (mclient.ImagePullResponse, error) { select { case <-ctx.Done(): return nil, fmt.Errorf("mockImageClient.ImagePull aborted: %w", ctx.Err()) @@ -52,19 +53,21 @@ func (m *mockImageClient) ImagePull(ctx context.Context, refStr string, opts ima return m.pullReader, nil } -func (m *mockImageClient) ImageInspectWithRaw(ctx context.Context, id string) (types.ImageInspect, []byte, error) { +func (m *mockImageClient) ImageInspect(ctx context.Context, id string, inspectOpts ...mclient.ImageInspectOption) (mclient.ImageInspectResult, error) { m.lock.Lock() defer m.lock.Unlock() - return types.ImageInspect{ - ID: m.idToName[id], - }, []byte{}, nil + return mclient.ImageInspectResult{ + InspectResponse: image.InspectResponse{ + ID: m.idToName[id], + }, + }, nil } -func (m *mockImageClient) ImageRemove(ctx context.Context, id string, opts image.RemoveOptions) ([]image.DeleteResponse, error) { +func (m *mockImageClient) ImageRemove(ctx context.Context, id string, opts mclient.ImageRemoveOptions) (mclient.ImageRemoveResult, error) { m.lock.Lock() defer m.lock.Unlock() m.removed[id]++ - return []image.DeleteResponse{}, nil + return mclient.ImageRemoveResult{}, nil } type readErrorer struct { @@ -72,7 +75,7 @@ type readErrorer struct { closeError error } -var _ io.ReadCloser = &readErrorer{} +var _ mclient.ImagePullResponse = &readErrorer{} func (r *readErrorer) Read(p []byte) (n int, err error) { return len(p), r.readErr @@ -82,6 +85,14 @@ func (r *readErrorer) Close() error { return r.closeError } +func (r *readErrorer) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error] { + return func(yield func(jsonstream.Message, error) bool) {} +} + +func (r *readErrorer) Wait(ctx context.Context) error { + return nil +} + func TestDockerCoordinator_ConcurrentPulls(t *testing.T) { ci.Parallel(t) image := "foo" diff --git a/drivers/docker/docklog/docker_logger.go b/drivers/docker/docklog/docker_logger.go index 04c021d1bd9..a16d7ffa522 100644 --- a/drivers/docker/docklog/docker_logger.go +++ b/drivers/docker/docklog/docker_logger.go @@ -13,11 +13,11 @@ import ( "time" "github.com/containerd/errdefs" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" + "github.com/moby/moby/api/pkg/stdcopy" + "github.com/moby/moby/client" + mclient "github.com/moby/moby/client" "github.com/hashicorp/nomad/client/lib/fifo" ) @@ -96,7 +96,7 @@ func (d *dockerLogger) Start(opts *StartOpts) error { backoff := 0.0 for { - logOpts := containerapi.LogsOptions{ + logOpts := mclient.ContainerLogsOptions{ Since: sinceTime.Format(time.RFC3339), Follow: true, ShowStdout: true, @@ -134,12 +134,12 @@ func (d *dockerLogger) Start(opts *StartOpts) error { sinceTime = time.Now() - container, err := client.ContainerInspect(ctx, opts.ContainerID) + container, err := client.ContainerInspect(ctx, opts.ContainerID, mclient.ContainerInspectOptions{}) if err != nil { if !errdefs.IsNotFound(err) { return } - } else if !container.State.Running { + } else if !container.Container.State.Running { return } } @@ -223,27 +223,23 @@ func (d *dockerLogger) getDockerClient(opts *StartOpts) (*client.Client, error) if opts.Endpoint != "" { if opts.TLSCert+opts.TLSKey+opts.TLSCA != "" { d.logger.Debug("using TLS client connection to docker", "endpoint", opts.Endpoint) - newClient, err = client.NewClientWithOpts( + newClient, err = client.New( client.WithHost(opts.Endpoint), client.WithTLSClientConfig(opts.TLSCA, opts.TLSCert, opts.TLSKey), - client.WithAPIVersionNegotiation(), ) if err != nil { merr.Errors = append(merr.Errors, err) } } else { d.logger.Debug("using plaintext client connection to docker", "endpoint", opts.Endpoint) - newClient, err = client.NewClientWithOpts( - client.WithHost(opts.Endpoint), - client.WithAPIVersionNegotiation(), - ) + newClient, err = client.New(client.WithHost(opts.Endpoint)) if err != nil { merr.Errors = append(merr.Errors, err) } } } else { d.logger.Debug("using client connection initialized from environment") - newClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + newClient, err = client.New(client.FromEnv) if err != nil { merr.Errors = append(merr.Errors, err) } diff --git a/drivers/docker/docklog/docker_logger_test.go b/drivers/docker/docklog/docker_logger_test.go index 8ce80e4a30c..b62161a2bba 100644 --- a/drivers/docker/docklog/docker_logger_test.go +++ b/drivers/docker/docklog/docker_logger_test.go @@ -14,9 +14,8 @@ import ( "testing" "time" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/client" + containerapi "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" "github.com/shoenig/test/must" "github.com/stretchr/testify/require" @@ -45,39 +44,42 @@ func TestDockerLogger_Success(t *testing.T) { containerImage := testRemoteContainerImage() - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + client, err := mclient.New(mclient.FromEnv) if err != nil { t.Skip("docker unavailable:", err) } - if img, _, err := client.ImageInspectWithRaw(ctx, containerImage); err != nil || img.ID == "" { + if img, err := client.ImageInspect(ctx, containerImage); err != nil || img.ID == "" { t.Log("image not found locally, downloading...") - out, err := client.ImagePull(ctx, containerImage, image.PullOptions{}) + out, err := client.ImagePull(ctx, containerImage, mclient.ImagePullOptions{}) must.NoError(t, err, must.Sprint("failed to pull image")) defer out.Close() io.Copy(os.Stdout, out) } - container, err := client.ContainerCreate(ctx, &containerapi.Config{ - Cmd: []string{ - "sh", "-c", "touch ~/docklog; tail -f ~/docklog", + container, err := client.ContainerCreate( + ctx, + mclient.ContainerCreateOptions{ + Config: &containerapi.Config{ + Cmd: []string{"sh", "-c", "touch ~/docklog; tail -f ~/docklog"}, + Image: containerImage, + }, }, - Image: containerImage, - }, nil, nil, nil, "") + ) must.NoError(t, err) - cleanup := func() { client.ContainerRemove(ctx, container.ID, containerapi.RemoveOptions{Force: true}) } + cleanup := func() { client.ContainerRemove(ctx, container.ID, mclient.ContainerRemoveOptions{Force: true}) } t.Cleanup(cleanup) - err = client.ContainerStart(ctx, container.ID, containerapi.StartOptions{}) + _, err = client.ContainerStart(ctx, container.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) testutil.WaitForResult(func() (bool, error) { - container, err := client.ContainerInspect(ctx, container.ID) + container, err := client.ContainerInspect(ctx, container.ID, mclient.ContainerInspectOptions{}) if err != nil { return false, err } - if !container.State.Running { + if !container.Container.State.Running { return false, fmt.Errorf("container not running") } return true, nil @@ -117,37 +119,42 @@ func TestDockerLogger_Success_TTY(t *testing.T) { containerImage := testRemoteContainerImage() - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + client, err := mclient.New(mclient.FromEnv) if err != nil { t.Skip("docker unavailable:", err) } - if img, _, err := client.ImageInspectWithRaw(ctx, containerImage); err != nil || img.ID == "" { + if img, err := client.ImageInspect(ctx, containerImage); err != nil || img.ID == "" { t.Log("image not found locally, downloading...") - _, err = client.ImagePull(ctx, containerImage, image.PullOptions{}) + _, err = client.ImagePull(ctx, containerImage, mclient.ImagePullOptions{}) must.NoError(t, err, must.Sprint("failed to pull image")) } - container, err := client.ContainerCreate(ctx, &containerapi.Config{ - Cmd: []string{ - "sh", "-c", "touch ~/docklog; tail -f ~/docklog", + container, err := client.ContainerCreate( + ctx, + mclient.ContainerCreateOptions{ + Config: &containerapi.Config{ + Cmd: []string{ + "sh", "-c", "touch ~/docklog; tail -f ~/docklog", + }, + Image: containerImage, + Tty: true, + }, }, - Image: containerImage, - Tty: true, - }, nil, nil, nil, "") + ) must.NoError(t, err) - defer client.ContainerRemove(ctx, container.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, container.ID, mclient.ContainerRemoveOptions{Force: true}) - err = client.ContainerStart(ctx, container.ID, containerapi.StartOptions{}) + _, err = client.ContainerStart(ctx, container.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) testutil.WaitForResult(func() (bool, error) { - container, err := client.ContainerInspect(ctx, container.ID) + container, err := client.ContainerInspect(ctx, container.ID, mclient.ContainerInspectOptions{}) if err != nil { return false, err } - if !container.State.Running { + if !container.Container.State.Running { return false, fmt.Errorf("container not running") } return true, nil @@ -181,18 +188,19 @@ func TestDockerLogger_Success_TTY(t *testing.T) { }) } -func echoToContainer(t *testing.T, client *client.Client, id string, line string) { +func echoToContainer(t *testing.T, client *mclient.Client, id string, line string) { ctx := context.Background() - op := containerapi.ExecOptions{ + op := mclient.ExecCreateOptions{ Cmd: []string{ "/bin/sh", "-c", fmt.Sprintf("echo %s >>~/docklog", line), }, } - exec, err := client.ContainerExecCreate(ctx, id, op) + exec, err := client.ExecCreate(ctx, id, op) + must.NoError(t, err) + _, err = client.ExecStart(ctx, exec.ID, mclient.ExecStartOptions{Detach: true}) must.NoError(t, err) - must.NoError(t, client.ContainerExecStart(ctx, exec.ID, containerapi.ExecStartOptions{Detach: true})) } func TestDockerLogger_LoggingNotSupported(t *testing.T) { @@ -202,43 +210,47 @@ func TestDockerLogger_LoggingNotSupported(t *testing.T) { containerImage := testRemoteContainerImage() - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + client, err := mclient.NewClientWithOpts(mclient.FromEnv, mclient.WithAPIVersionNegotiation()) if err != nil { t.Skip("docker unavailable:", err) } - if img, _, err := client.ImageInspectWithRaw(ctx, containerImage); err != nil || img.ID == "" { + if img, err := client.ImageInspect(ctx, containerImage); err != nil || img.ID == "" { t.Log("image not found locally, downloading...") - _, err = client.ImagePull(ctx, containerImage, image.PullOptions{}) + _, err = client.ImagePull(ctx, containerImage, mclient.ImagePullOptions{}) require.NoError(t, err, "failed to pull image") } - container, err := client.ContainerCreate(ctx, - &containerapi.Config{ - Cmd: []string{ - "sh", "-c", "touch ~/docklog; tail -f ~/docklog", + container, err := client.ContainerCreate( + ctx, + mclient.ContainerCreateOptions{ + Config: &containerapi.Config{ + Cmd: []string{ + "sh", "-c", "touch ~/docklog; tail -f ~/docklog", + }, + Image: containerImage, }, - Image: containerImage, - }, - &containerapi.HostConfig{ - LogConfig: containerapi.LogConfig{ - Type: "none", - Config: map[string]string{}, + HostConfig: &containerapi.HostConfig{ + LogConfig: containerapi.LogConfig{ + Type: "none", + Config: map[string]string{}, + }, }, - }, nil, nil, "") + }, + ) must.NoError(t, err) - defer client.ContainerRemove(ctx, container.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, container.ID, mclient.ContainerRemoveOptions{Force: true}) - err = client.ContainerStart(ctx, container.ID, containerapi.StartOptions{}) + _, err = client.ContainerStart(ctx, container.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) testutil.WaitForResult(func() (bool, error) { - container, err := client.ContainerInspect(ctx, container.ID) + container, err := client.ContainerInspect(ctx, container.ID, mclient.ContainerInspectOptions{}) if err != nil { return false, err } - if !container.State.Running { + if !container.Container.State.Running { return false, fmt.Errorf("container not running") } return true, nil diff --git a/drivers/docker/driver.go b/drivers/docker/driver.go index 0cda35ead97..c294ca93ce1 100644 --- a/drivers/docker/driver.go +++ b/drivers/docker/driver.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net" + "net/netip" "os" "path/filepath" "runtime" @@ -21,14 +22,6 @@ import ( "time" "github.com/containerd/errdefs" - "github.com/docker/docker/api/types" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/mount" - networkapi "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" "github.com/hashicorp/consul-template/signals" hclog "github.com/hashicorp/go-hclog" multierror "github.com/hashicorp/go-multierror" @@ -48,9 +41,15 @@ import ( "github.com/hashicorp/nomad/plugins/base" "github.com/hashicorp/nomad/plugins/drivers" pstructs "github.com/hashicorp/nomad/plugins/shared/structs" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/moby/moby/api/pkg/stdcopy" + containerapi "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + networkapi "github.com/moby/moby/api/types/network" + "github.com/moby/moby/api/types/registry" + "github.com/moby/moby/api/types/system" + "github.com/moby/moby/client" + mclient "github.com/moby/moby/client" "github.com/ryanuber/go-glob" - "golang.org/x/mod/semver" ) var ( @@ -212,7 +211,7 @@ func (d *Driver) reattachToDockerLogger(reattachConfig *pstructs.ReattachConfig) return dlogger, dloggerPluginClient, nil } -func (d *Driver) setupNewDockerLogger(container types.ContainerJSON, cfg *drivers.TaskConfig, startTime time.Time) (docklog.DockerLogger, *plugin.Client, error) { +func (d *Driver) setupNewDockerLogger(container mclient.ContainerInspectResult, cfg *drivers.TaskConfig, startTime time.Time) (docklog.DockerLogger, *plugin.Client, error) { dlogger, pluginClient, err := docklog.LaunchDockerLogger(d.logger) if err != nil { if pluginClient != nil { @@ -223,8 +222,8 @@ func (d *Driver) setupNewDockerLogger(container types.ContainerJSON, cfg *driver if err := dlogger.Start(&docklog.StartOpts{ Endpoint: d.config.Endpoint, - ContainerID: container.ID, - TTY: container.Config.Tty, + ContainerID: container.Container.ID, + TTY: container.Container.Config.Tty, Stdout: cfg.StdoutPath, Stderr: cfg.StderrPath, TLSCert: d.config.TLS.Cert, @@ -233,7 +232,7 @@ func (d *Driver) setupNewDockerLogger(container types.ContainerJSON, cfg *driver StartTime: startTime.Unix(), }); err != nil { pluginClient.Kill() - return nil, nil, fmt.Errorf("failed to launch docker logger process %s: %v", container.ID, err) + return nil, nil, fmt.Errorf("failed to launch docker logger process %s: %v", container.Container.ID, err) } return dlogger, pluginClient, nil @@ -254,7 +253,7 @@ func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { return fmt.Errorf("failed to get docker client: %w", err) } - dockerInfo, err := dockerClient.Info(d.ctx) + dockerInfo, err := dockerClient.Info(d.ctx, mclient.InfoOptions{}) if err != nil { return fmt.Errorf("failed to fetch docker daemon info: %v", err) } @@ -264,20 +263,20 @@ func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { return fmt.Errorf("failed to get docker long operations client: %w", err) } - container, err := dockerClient.ContainerInspect(d.ctx, handleState.ContainerID) + container, err := dockerClient.ContainerInspect(d.ctx, handleState.ContainerID, mclient.ContainerInspectOptions{}) if err != nil { return fmt.Errorf("failed to inspect container for id %q: %v", handleState.ContainerID, err) } h := &taskHandle{ dockerClient: dockerClient, - dockerCGroupDriver: dockerInfo.CgroupDriver, + dockerCGroupDriver: dockerInfo.Info.CgroupDriver, infinityClient: infinityClient, - logger: d.logger.With("container_id", container.ID), + logger: d.logger.With("container_id", container.Container.ID), task: handle.Config, - containerID: container.ID, - containerCgroup: string(container.HostConfig.Cgroup), - containerImage: container.Image, + containerID: container.Container.ID, + containerCgroup: string(container.Container.HostConfig.Cgroup), + containerImage: container.Container.Image, doneCh: make(chan bool), waitCh: make(chan struct{}), removeContainerOnExit: d.config.GC.Container, @@ -292,14 +291,14 @@ func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error { h.dlogger, h.dloggerPluginClient, err = d.setupNewDockerLogger(container, handle.Config, time.Now()) if err != nil { - if err := dockerClient.ContainerStop(d.ctx, handleState.ContainerID, stopWithZeroTimeout()); err != nil { + if _, err := dockerClient.ContainerStop(d.ctx, handleState.ContainerID, stopWithZeroTimeout()); err != nil { d.logger.Warn("failed to stop container during cleanup", "container_id", handleState.ContainerID, "error", err) } return fmt.Errorf("failed to setup replacement docker logger: %v", err) } if err := handle.SetDriverState(h.buildState()); err != nil { - if err := dockerClient.ContainerStop(d.ctx, handleState.ContainerID, stopWithZeroTimeout()); err != nil { + if _, err := dockerClient.ContainerStop(d.ctx, handleState.ContainerID, stopWithZeroTimeout()); err != nil { d.logger.Warn("failed to stop container during cleanup", "container_id", handleState.ContainerID, "error", err) } return fmt.Errorf("failed to store driver state: %v", err) @@ -354,7 +353,7 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive return nil, nil, fmt.Errorf("Failed to create docker client: %v", err) } - dockerInfo, err := dockerClient.Info(d.ctx) + dockerInfo, err := dockerClient.Info(d.ctx, mclient.InfoOptions{}) if err != nil { return nil, nil, fmt.Errorf("failed to fetch docker daemon info: %v", err) } @@ -395,45 +394,45 @@ CREATE: if err != nil { d.logger.Error("failed to create container", "error", err) if container != nil { - removeErr := dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + _, removeErr := dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) if removeErr != nil { - return nil, nil, fmt.Errorf("failed to remove container %s: %v", container.ID, removeErr) + return nil, nil, fmt.Errorf("failed to remove container %s: %v", container.Container.ID, removeErr) } } return nil, nil, nstructs.WrapRecoverable(fmt.Sprintf("failed to create container: %v", err), err) } - d.logger.Info("created container", "container_id", container.ID) + d.logger.Info("created container", "container_id", container.Container.ID) - if !container.State.Running { + if !container.Container.State.Running { // Start the container if err := d.startContainer(*container); err != nil { - d.logger.Error("failed to start container", "container_id", container.ID, "error", err) - dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + d.logger.Error("failed to start container", "container_id", container.Container.ID, "error", err) + _, _ = dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) // Some sort of docker race bug, recreating the container usually works if errdefs.IsConflict(err) && startAttempts < 5 { startAttempts++ d.logger.Debug("reattempting container create/start sequence", "attempt", startAttempts, "container_id", id) goto CREATE } - return nil, nil, nstructs.WrapRecoverable(fmt.Sprintf("Failed to start container %s: %s", container.ID, err), err) + return nil, nil, nstructs.WrapRecoverable(fmt.Sprintf("Failed to start container %s: %s", container.Container.ID, err), err) } // Inspect container to get all of the container metadata as much of the // metadata (eg networking) isn't populated until the container is started - runningContainer, err := dockerClient.ContainerInspect(d.ctx, container.ID) + runningContainer, err := dockerClient.ContainerInspect(d.ctx, container.Container.ID, mclient.ContainerInspectOptions{}) if err != nil { - dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + _, _ = dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) msg := "failed to inspect started container" d.logger.Error(msg, "error", err) - dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) - return nil, nil, nstructs.NewRecoverableError(fmt.Errorf("%s %s: %s", msg, container.ID, err), true) + _, _ = dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) + return nil, nil, nstructs.NewRecoverableError(fmt.Errorf("%s %s: %s", msg, container.Container.ID, err), true) } container = &runningContainer - d.logger.Info("started container", "container_id", container.ID) + d.logger.Info("started container", "container_id", container.Container.ID) } else { d.logger.Debug("re-attaching to container", "container_id", - container.ID, "container_state", container.State.Status) + container.Container.ID, "container_state", container.Container.State.Status) } collectingLogs := loggingIsEnabled(d.config, cfg) @@ -444,8 +443,8 @@ CREATE: if collectingLogs { dlogger, pluginClient, err = d.setupNewDockerLogger(*container, cfg, time.Unix(0, 0)) if err != nil { - d.logger.Error("an error occurred after container startup, terminating container", "container_id", container.ID) - dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + d.logger.Error("an error occurred after container startup, terminating container", "container_id", container.Container.ID) + _, _ = dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) return nil, nil, err } } @@ -462,14 +461,14 @@ CREATE: // Return a driver handle h := &taskHandle{ dockerClient: dockerClient, - dockerCGroupDriver: dockerInfo.CgroupDriver, + dockerCGroupDriver: dockerInfo.Info.CgroupDriver, infinityClient: infinityClient, dlogger: dlogger, dloggerPluginClient: pluginClient, - logger: d.logger.With("container_id", container.ID), + logger: d.logger.With("container_id", container.Container.ID), task: cfg, - containerID: container.ID, - containerImage: container.Image, + containerID: container.Container.ID, + containerImage: container.Container.Image, doneCh: make(chan bool), waitCh: make(chan struct{}), removeContainerOnExit: d.config.GC.Container, @@ -478,12 +477,12 @@ CREATE: } if err := handle.SetDriverState(h.buildState()); err != nil { - d.logger.Error("error encoding container occurred after startup, terminating container", "container_id", container.ID, "error", err) + d.logger.Error("error encoding container occurred after startup, terminating container", "container_id", container.Container.ID, "error", err) if collectingLogs { dlogger.Stop() pluginClient.Kill() } - dockerClient.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + _, _ = dockerClient.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) return nil, nil, err } @@ -496,21 +495,26 @@ CREATE: // createContainerClient is the subset of Docker Client methods used by the // createContainer method to ease testing subtle error conditions. type createContainerClient interface { - ContainerCreate(context.Context, *containerapi.Config, *containerapi.HostConfig, *networkapi.NetworkingConfig, *ocispec.Platform, string) (containerapi.CreateResponse, error) - ContainerInspect(context.Context, string) (types.ContainerJSON, error) - ContainerList(context.Context, containerapi.ListOptions) ([]types.Container, error) - ContainerRemove(context.Context, string, containerapi.RemoveOptions) error + ContainerCreate(context.Context, mclient.ContainerCreateOptions) (mclient.ContainerCreateResult, error) + ContainerInspect(context.Context, string, mclient.ContainerInspectOptions) (mclient.ContainerInspectResult, error) + ContainerList(context.Context, mclient.ContainerListOptions) (mclient.ContainerListResult, error) + ContainerRemove(context.Context, string, mclient.ContainerRemoveOptions) (mclient.ContainerRemoveResult, error) } // createContainer creates the container given the passed configuration. It // attempts to handle any transient Docker errors. -func (d *Driver) createContainer(client createContainerClient, config createContainerOptions, image string) (*types.ContainerJSON, error) { +func (d *Driver) createContainer(client createContainerClient, config createContainerOptions, image string) (*mclient.ContainerInspectResult, error) { // Create a container var attempted uint64 var backoff time.Duration CREATE: - _, createErr := client.ContainerCreate(d.ctx, config.Config, config.Host, config.Networking, nil, config.Name) + _, createErr := client.ContainerCreate(d.ctx, mclient.ContainerCreateOptions{ + Name: config.Name, + Config: config.Config, + HostConfig: config.Host, + NetworkingConfig: config.Networking, + }) if createErr == nil { containerJSON, err := d.containerByName(config.Name) if err != nil { @@ -539,7 +543,7 @@ CREATE: return nil, err } - if container != nil && container.State.Running { + if container != nil && container.Container.State.Running { return container, nil } @@ -548,12 +552,12 @@ CREATE: // deleted in our check here, so retry again. if container != nil { // Delete matching containers - err = client.ContainerRemove(d.ctx, container.ID, containerapi.RemoveOptions{Force: true}) + _, err = client.ContainerRemove(d.ctx, container.Container.ID, mclient.ContainerRemoveOptions{Force: true}) if err != nil { - d.logger.Error("failed to purge container", "container_id", container.ID) - return nil, recoverableErrTimeouts(fmt.Errorf("Failed to purge container %s: %s", container.ID, err)) + d.logger.Error("failed to purge container", "container_id", container.Container.ID) + return nil, recoverableErrTimeouts(fmt.Errorf("Failed to purge container %s: %s", container.Container.ID, err)) } else { - d.logger.Info("purged container", "container_id", container.ID) + d.logger.Info("purged container", "container_id", container.Container.ID) } } @@ -580,7 +584,7 @@ CREATE: // startContainer starts the passed container. It attempts to handle any // transient Docker errors. -func (d *Driver) startContainer(c types.ContainerJSON) error { +func (d *Driver) startContainer(c mclient.ContainerInspectResult) error { dockerClient, err := d.getDockerClient() if err != nil { return err @@ -590,12 +594,12 @@ func (d *Driver) startContainer(c types.ContainerJSON) error { var backoff time.Duration START: - startErr := dockerClient.ContainerStart(d.ctx, c.ID, containerapi.StartOptions{}) - if startErr == nil || errdefs.IsConflict(err) { + _, startErr := dockerClient.ContainerStart(d.ctx, c.Container.ID, mclient.ContainerStartOptions{}) + if startErr == nil || errdefs.IsConflict(startErr) { return nil } - d.logger.Debug("failed to start container", "container_id", c.ID, "attempt", attempted+1, "error", startErr) + d.logger.Debug("failed to start container", "container_id", c.Container.ID, "attempt", attempted+1, "error", startErr) if isDockerTransientError(startErr) { if attempted < 5 { @@ -625,7 +629,7 @@ func (d *Driver) createImage(task *drivers.TaskConfig, driverConfig *TaskConfig, if driverConfig.ForcePull { d.logger.Debug("force pulling image instead of inspecting local", "image_ref", dockerImageRef(repo, tag)) } else if tag != "latest" { - if dockerImage, _, _ := client.ImageInspectWithRaw(d.ctx, image); dockerImage.ID != "" { + if dockerImage, _ := client.ImageInspect(d.ctx, image); dockerImage.ID != "" { // Image exists so just increment its reference count d.coordinator.IncrementImageReference(dockerImage.ID, image, task.ID) var user string @@ -721,7 +725,7 @@ func (d *Driver) loadImage(task *drivers.TaskConfig, driverConfig *TaskConfig, d } f.Close() - dockerImage, _, err := dockerClient.ImageInspectWithRaw(d.ctx, driverConfig.Image) + dockerImage, err := dockerClient.ImageInspect(d.ctx, driverConfig.Image) if err != nil { return "", "", recoverableErrTimeouts(err) } @@ -740,7 +744,7 @@ func (d *Driver) convertAllocPathsForWindowsLCOW(task *drivers.TaskConfig, image return err } - imageConfig, _, err := dockerClient.ImageInspectWithRaw(d.ctx, image) + imageConfig, err := dockerClient.ImageInspect(d.ctx, image) if err != nil { return fmt.Errorf("the image does not exist: %v", err) } @@ -825,16 +829,16 @@ func (d *Driver) findPauseContainer(allocID string) (string, error) { return "", err } - containers, listErr := dockerClient.ContainerList(context.Background(), containerapi.ListOptions{ + containers, listErr := dockerClient.ContainerList(context.Background(), mclient.ContainerListOptions{ All: false, // running only - Filters: filters.NewArgs(filters.KeyValuePair{Key: "label", Value: dockerLabelAllocID}), + Filters: mclient.Filters{}.Add("label", dockerLabelAllocID), }) if listErr != nil { d.logger.Error("failed to list pause containers for recovery", "error", listErr) return "", listErr } - for _, c := range containers { + for _, c := range containers.Items { if !slices.ContainsFunc(c.Names, func(s string) bool { return strings.HasPrefix(s, "/nomad_init_") }) { @@ -859,9 +863,9 @@ func (d *Driver) recoverPauseContainers(ctx context.Context) { return } - containers, listErr := dockerClient.ContainerList(ctx, containerapi.ListOptions{ + containers, listErr := dockerClient.ContainerList(ctx, mclient.ContainerListOptions{ All: false, // running only - Filters: filters.NewArgs(filters.KeyValuePair{Key: "label", Value: dockerLabelAllocID}), + Filters: mclient.Filters{}.Add("label", dockerLabelAllocID), }) if listErr != nil && !errors.Is(listErr, ctx.Err()) { d.logger.Error("failed to list pause containers for recovery", "error", listErr) @@ -869,7 +873,7 @@ func (d *Driver) recoverPauseContainers(ctx context.Context) { } CONTAINER: - for _, c := range containers { + for _, c := range containers.Items { for _, name := range c.Names { if strings.HasPrefix(name, "/nomad_init_") { d.pauseContainers.add(c.ID) @@ -1172,14 +1176,25 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T if err != nil { return c, err } - ver, err := client.ServerVersion(d.ctx) + ver, err := client.ServerVersion(d.ctx, mclient.ServerVersionOptions{}) if err != nil { return c, err } // set add/drop capabilities if hostConfig.CapAdd, hostConfig.CapDrop, err = capabilities.Delta( - capabilities.DockerDefaults(ver), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop, + capabilities.DockerDefaults(system.VersionResponse{ + Platform: system.PlatformInfo{ + Name: ver.Platform.Name, + }, + Version: ver.Version, + APIVersion: ver.APIVersion, + MinAPIVersion: ver.MinAPIVersion, + Os: ver.Os, + Arch: ver.Arch, + Components: ver.Components, + Experimental: ver.Experimental, + }), d.config.AllowCaps, driverConfig.CapAdd, driverConfig.CapDrop, ); err != nil { return c, err } @@ -1276,8 +1291,9 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T } // set DNS servers for _, ip := range driverConfig.DNSServers { - if net.ParseIP(ip) != nil { - hostConfig.DNS = append(hostConfig.DNS, ip) + addr, err := netip.ParseAddr(ip) + if err == nil { + hostConfig.DNS = append(hostConfig.DNS, addr) } else { logger.Error("invalid ip address for container dns server", "ip", ip) } @@ -1378,8 +1394,26 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T return c, fmt.Errorf("Trying to map ports but no network interface is available") } } - hostConfig.PortBindings = ports.publishedPorts - config.ExposedPorts = ports.exposedPorts + hostConfig.PortBindings = make(networkapi.PortMap, len(ports.publishedPorts)) + for port, bindings := range ports.publishedPorts { + parsedPort := networkapi.MustParsePort(string(port)) + convertedBindings := make([]networkapi.PortBinding, len(bindings)) + for i, binding := range bindings { + convertedBinding := networkapi.PortBinding{ + HostPort: binding.HostPort, + } + if binding.HostIP != "" { + convertedBinding.HostIP = netip.MustParseAddr(binding.HostIP) + } + convertedBindings[i] = convertedBinding + } + hostConfig.PortBindings[parsedPort] = convertedBindings + } + + config.ExposedPorts = make(networkapi.PortSet, len(ports.exposedPorts)) + for port := range ports.exposedPorts { + config.ExposedPorts[networkapi.MustParsePort(string(port))] = struct{}{} + } // If the user specified a custom command to run, we'll inject it here. if driverConfig.Command != "" { @@ -1461,31 +1495,42 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T } if driverConfig.IPv4Address != "" || driverConfig.IPv6Address != "" { - networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)].IPAMConfig = &networkapi.EndpointIPAMConfig{ - IPv4Address: driverConfig.IPv4Address, - IPv6Address: driverConfig.IPv6Address, + ipamConfig := &networkapi.EndpointIPAMConfig{} + if driverConfig.IPv4Address != "" { + ipv4, err := netip.ParseAddr(driverConfig.IPv4Address) + if err != nil { + return c, fmt.Errorf("failed to parse ipv4_address %q: %v", driverConfig.IPv4Address, err) + } + ipamConfig.IPv4Address = ipv4 } + if driverConfig.IPv6Address != "" { + ipv6, err := netip.ParseAddr(driverConfig.IPv6Address) + if err != nil { + return c, fmt.Errorf("failed to parse ipv6_address %q: %v", driverConfig.IPv6Address, err) + } + ipamConfig.IPv6Address = ipv6 + } + networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)].IPAMConfig = ipamConfig logger.Debug("setting container network configuration", "network_mode", hostConfig.NetworkMode, "ipv4_address", driverConfig.IPv4Address, "ipv6_address", driverConfig.IPv6Address) } if driverConfig.MacAddress != "" { - config.MacAddress = driverConfig.MacAddress - - // newer docker versions obsolete the config.MacAddress field - isTooNew := semver.Compare(fmt.Sprintf("v%s", ver.APIVersion), "v1.44") - if isTooNew >= 0 { - if networkingConfig == nil { - networkingConfig = &networkapi.NetworkingConfig{ - EndpointsConfig: map[string]*networkapi.EndpointSettings{ - string(hostConfig.NetworkMode): {}, - }, - } + mac, err := net.ParseMAC(driverConfig.MacAddress) + if err != nil { + return c, fmt.Errorf("failed to parse mac_address %q: %v", driverConfig.MacAddress, err) + } + + if networkingConfig == nil { + networkingConfig = &networkapi.NetworkingConfig{ + EndpointsConfig: map[string]*networkapi.EndpointSettings{ + string(hostConfig.NetworkMode): {}, + }, } - networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)].MacAddress = driverConfig.MacAddress } + networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)].MacAddress = networkapi.HardwareAddr(mac) - logger.Debug("setting container mac address", "mac_address", config.MacAddress) + logger.Debug("setting container mac address", "mac_address", driverConfig.MacAddress) } if driverConfig.Healthchecks.Disabled() { @@ -1535,26 +1580,26 @@ func (d *Driver) toDockerMount(m *DockerMount, task *drivers.TaskConfig) (*mount // detectIP of Docker container. Returns the first IP found as well as true if // the IP should be advertised (bridge network IPs return false). Returns an // empty string and false if no IP could be found. -func (d *Driver) detectIP(c types.ContainerJSON, driverConfig *TaskConfig) (string, bool) { - if c.NetworkSettings == nil { +func (d *Driver) detectIP(c mclient.ContainerInspectResult, driverConfig *TaskConfig) (string, bool) { + if c.Container.NetworkSettings == nil { // This should only happen if there's been a coding error (such // as not calling InspectContainer after CreateContainer). Code // defensively in case the Docker API changes subtly. - d.logger.Error("no network settings for container", "container_id", c.ID) + d.logger.Error("no network settings for container", "container_id", c.Container.ID) return "", false } ip, ipName := "", "" auto := false - for name, net := range c.NetworkSettings.Networks { - if net.IPAddress == "" { + for name, net := range c.Container.NetworkSettings.Networks { + if !net.IPAddress.IsValid() { // Ignore networks without an IP address continue } - ip = net.IPAddress - if driverConfig.AdvertiseIPv6Addr { - ip = net.GlobalIPv6Address + ip = net.IPAddress.String() + if driverConfig.AdvertiseIPv6Addr && net.GlobalIPv6Address.IsValid() { + ip = net.GlobalIPv6Address.String() auto = true } ipName = name @@ -1568,10 +1613,10 @@ func (d *Driver) detectIP(c types.ContainerJSON, driverConfig *TaskConfig) (stri break } - if n := len(c.NetworkSettings.Networks); n > 1 { + if n := len(c.Container.NetworkSettings.Networks); n > 1 { d.logger.Warn("multiple Docker networks for container found but Nomad only supports 1", "total_networks", n, - "container_id", c.ID, + "container_id", c.Container.ID, "container_network", ipName) } @@ -1580,13 +1625,13 @@ func (d *Driver) detectIP(c types.ContainerJSON, driverConfig *TaskConfig) (stri // containerByName finds a running container by name, and returns an error // if the container is dead or can't be found. -func (d *Driver) containerByName(name string) (*types.ContainerJSON, error) { +func (d *Driver) containerByName(name string) (*mclient.ContainerInspectResult, error) { dockerClient, err := d.getDockerClient() if err != nil { return nil, err } - containers, err := dockerClient.ContainerList(d.ctx, containerapi.ListOptions{All: true}) + containers, err := dockerClient.ContainerList(d.ctx, mclient.ContainerListOptions{All: true}) if err != nil { d.logger.Error("failed to query list of containers matching name", "container_name", name) @@ -1597,11 +1642,11 @@ func (d *Driver) containerByName(name string) (*types.ContainerJSON, error) { // container names with a / pre-pended to the Nomad generated container names containerName := "/" + name var ( - shimContainer types.Container + shimContainer containerapi.Summary found bool ) OUTER: - for _, shimContainer = range containers { + for _, shimContainer = range containers.Items { d.logger.Trace("listed container", "names", hclog.Fmt("%+v", shimContainer.Names)) for _, name := range shimContainer.Names { if name == containerName { @@ -1616,7 +1661,7 @@ OUTER: return nil, nil } - container, err := dockerClient.ContainerInspect(d.ctx, shimContainer.ID) + container, err := dockerClient.ContainerInspect(d.ctx, shimContainer.ID, mclient.ContainerInspectOptions{}) if err != nil { err = fmt.Errorf("Failed to inspect container %s: %s", shimContainer.ID, err) @@ -1687,7 +1732,7 @@ func (d *Driver) DestroyTask(taskID string, force bool) error { return err } - c, err := dockerClient.ContainerInspect(d.ctx, h.containerID) + c, err := dockerClient.ContainerInspect(d.ctx, h.containerID, mclient.ContainerInspectOptions{}) if err != nil { if errdefs.IsNotFound(err) { h.logger.Info("container was removed out of band, will proceed with DestroyTask", @@ -1696,17 +1741,17 @@ func (d *Driver) DestroyTask(taskID string, force bool) error { return fmt.Errorf("failed to inspect container state: %v", err) } } else { - if c.State.Running { + if c.Container.State.Running { if !force { return fmt.Errorf("must call StopTask for the given task before Destroy or set force to true") } - if err := dockerClient.ContainerStop(d.ctx, h.containerID, containerapi.StopOptions{Timeout: pointer.Of(0)}); err != nil { + if _, err := dockerClient.ContainerStop(d.ctx, h.containerID, mclient.ContainerStopOptions{Timeout: pointer.Of(0)}); err != nil { h.logger.Warn("failed to stop container during destroy", "error", err) } } if h.removeContainerOnExit { - if err := dockerClient.ContainerRemove(d.ctx, h.containerID, containerapi.RemoveOptions{RemoveVolumes: true, Force: true}); err != nil { + if _, err := dockerClient.ContainerRemove(d.ctx, h.containerID, mclient.ContainerRemoveOptions{Force: true}); err != nil { h.logger.Error("error removing container", "error", err) } } else { @@ -1747,13 +1792,13 @@ func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { return nil, err } - container, err := dockerClient.ContainerInspect(d.ctx, h.containerID) + container, err := dockerClient.ContainerInspect(d.ctx, h.containerID, mclient.ContainerInspectOptions{}) if err != nil { return nil, fmt.Errorf("failed to inspect container %q: %v", h.containerID, err) } - started, _ := time.Parse(time.RFC3339, container.State.StartedAt) - completed, _ := time.Parse(time.RFC3339, container.State.FinishedAt) + started, _ := time.Parse(time.RFC3339, container.Container.State.StartedAt) + completed, _ := time.Parse(time.RFC3339, container.Container.State.FinishedAt) status := &drivers.TaskStatus{ ID: h.task.ID, @@ -1761,17 +1806,17 @@ func (d *Driver) InspectTask(taskID string) (*drivers.TaskStatus, error) { StartedAt: started, CompletedAt: completed, DriverAttributes: map[string]string{ - "container_id": container.ID, + "container_id": container.Container.ID, }, NetworkOverride: h.net, ExitResult: h.ExitResult(), } status.State = drivers.TaskStateUnknown - if container.State.Running { + if container.Container.State.Running { status.State = drivers.TaskStateRunning } - if container.State.Dead { + if container.Container.State.Dead { status.State = drivers.TaskStateExited } @@ -1805,7 +1850,8 @@ func (d *Driver) SignalTask(taskID string, signal string) error { // TODO: review whether we can timeout in this and other Docker API // calls without breaking the expected client behavior. // see https://github.com/hashicorp/nomad/issues/9503 - return h.dockerClient.ContainerKill(d.ctx, h.containerID, signal) + _, err = h.dockerClient.ContainerKill(d.ctx, h.containerID, mclient.ContainerKillOptions{Signal: signal}) + return err } func (d *Driver) ExecTask(taskID string, cmd []string, timeout time.Duration) (*drivers.ExecTaskResult, error) { @@ -1842,20 +1888,20 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri return nil, fmt.Errorf("command is required but was empty") } - createExecOpts := containerapi.ExecOptions{ + createExecOpts := mclient.ExecCreateOptions{ AttachStdin: true, AttachStdout: true, AttachStderr: true, - Tty: opts.Tty, + TTY: opts.Tty, Cmd: opts.Command, } - dockerClient, err := d.getDockerClient() + client, err := d.getDockerClient() if err != nil { return nil, err } - exec, err := dockerClient.ContainerExecCreate(d.ctx, h.containerID, createExecOpts) + exec, err := client.ExecCreate(d.ctx, h.containerID, createExecOpts) if err != nil { return nil, fmt.Errorf("failed to create exec object: %v", err) } @@ -1871,7 +1917,7 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri if !ok { return } - dockerClient.ContainerExecResize(d.ctx, exec.ID, containerapi.ResizeOptions{ + _, _ = client.ExecResize(d.ctx, exec.ID, mclient.ExecResizeOptions{ Height: uint(s.Height), Width: uint(s.Width), }) @@ -1879,7 +1925,7 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri } }() - resp, err := dockerClient.ContainerExecAttach(ctx, exec.ID, containerapi.ExecAttachOptions{Tty: opts.Tty}) + resp, err := client.ExecAttach(ctx, exec.ID, mclient.ExecAttachOptions{TTY: opts.Tty}) if err != nil { return nil, fmt.Errorf("failed to attach to exec: %v", err) } @@ -1904,7 +1950,7 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri exitCode := 999 for { - inspect, err := dockerClient.ContainerExecInspect(ctx, exec.ID) + inspect, err := client.ExecInspect(ctx, exec.ID, mclient.ExecInspectOptions{}) if err != nil { return nil, fmt.Errorf("failed to inspect exec: %v", err) } @@ -1924,9 +1970,9 @@ func (d *Driver) ExecTaskStreaming(ctx context.Context, taskID string, opts *dri }, nil } -func (d *Driver) getOrCreateClient(timeout time.Duration) (*client.Client, error) { +func (d *Driver) getOrCreateClient(timeout time.Duration) (*mclient.Client, error) { var ( - client *client.Client + client *mclient.Client err error ) @@ -1948,20 +1994,25 @@ func (d *Driver) getOrCreateClient(timeout time.Duration) (*client.Client, error } // getInfinityClient creates a docker API client with no timeout. -func (d *Driver) getInfinityClient() (*client.Client, error) { +func (d *Driver) getInfinityClient() (*mclient.Client, error) { return d.getOrCreateClient(0) } // getDockerClient creates a docker API client with a hard-coded timeout. -func (d *Driver) getDockerClient() (*client.Client, error) { +func (d *Driver) getDockerClient() (*mclient.Client, error) { return d.getOrCreateClient(dockerTimeout) } -// newDockerClient creates a new *client.Client with a configurable timeout -func (d *Driver) newDockerClient(timeout time.Duration) (*client.Client, error) { +// newDockerClient creates a new *mclient.Client with a configurable timeout +func (d *Driver) newDockerClient(timeout time.Duration) (*mclient.Client, error) { var err error var merr multierror.Error - var newClient *client.Client + var newClient *mclient.Client + + opts := []mclient.Opt{mclient.WithAPIVersionNegotiation()} + if timeout != 0 { + opts = append(opts, mclient.WithTimeout(timeout)) + } // Default to using whatever is configured in docker.endpoint. If this is // not specified we'll fall back on NewClientFromEnv which reads config from @@ -1976,19 +2027,21 @@ func (d *Driver) newDockerClient(timeout time.Duration) (*client.Client, error) if cert+key+ca != "" { d.logger.Debug("using TLS client connection", "endpoint", dockerEndpoint) - newClient, err = client.NewClientWithOpts( - client.WithHost(dockerEndpoint), - client.WithTLSClientConfig(ca, cert, key), - client.WithAPIVersionNegotiation(), + newClient, err = mclient.NewClientWithOpts( + append(opts, + mclient.WithHost(dockerEndpoint), + mclient.WithTLSClientConfig(ca, cert, key), + )..., ) if err != nil { merr.Errors = append(merr.Errors, err) } } else { d.logger.Debug("using standard client connection", "endpoint", dockerEndpoint) - newClient, err = client.NewClientWithOpts( - client.WithHost(dockerEndpoint), - client.WithAPIVersionNegotiation(), + newClient, err = mclient.NewClientWithOpts( + append(opts, + mclient.WithHost(dockerEndpoint), + )..., ) if err != nil { merr.Errors = append(merr.Errors, err) @@ -1996,15 +2049,14 @@ func (d *Driver) newDockerClient(timeout time.Duration) (*client.Client, error) } } else { d.logger.Debug("using client connection initialized from environment") - newClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + newClient, err = mclient.NewClientWithOpts( + append(opts, mclient.FromEnv)..., + ) if err != nil { merr.Errors = append(merr.Errors, err) } } - if timeout != 0 && newClient != nil { - newClient.HTTPClient().Timeout = timeout - } return newClient, merr.ErrorOrNil() } @@ -2058,6 +2110,6 @@ func isDockerTransientError(err error) bool { return false } -func stopWithZeroTimeout() containerapi.StopOptions { - return containerapi.StopOptions{Timeout: pointer.Of(0)} +func stopWithZeroTimeout() mclient.ContainerStopOptions { + return mclient.ContainerStopOptions{Timeout: pointer.Of(0)} } diff --git a/drivers/docker/driver_test.go b/drivers/docker/driver_test.go index 802a30c4422..df4dfaea956 100644 --- a/drivers/docker/driver_test.go +++ b/drivers/docker/driver_test.go @@ -18,16 +18,6 @@ import ( "time" "github.com/containerd/errdefs" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/mount" - networkapi "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/client" "github.com/docker/go-connections/nat" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-set/v3" @@ -45,7 +35,12 @@ import ( "github.com/hashicorp/nomad/plugins/drivers" dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" tu "github.com/hashicorp/nomad/testutil" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/moby/moby/api/types/container" + containerapi "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + "github.com/moby/moby/api/types/registry" + "github.com/moby/moby/client" + mclient "github.com/moby/moby/client" "github.com/shoenig/test/must" "github.com/shoenig/test/wait" ) @@ -72,7 +67,7 @@ var ( ) func dockerIsRemote() bool { - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + client, err := mclient.NewClientWithOpts(mclient.FromEnv, mclient.WithAPIVersionNegotiation()) if err != nil { return false } @@ -155,7 +150,7 @@ func dockerTask(t *testing.T) (*drivers.TaskConfig, *TaskConfig, []int) { // // If there is a problem during setup this function will abort or skip the test // and indicate the reason. -func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]interface{}) (*client.Client, *dtestutil.DriverHarness, *taskHandle, func()) { +func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]interface{}) (*mclient.Client, *dtestutil.DriverHarness, *taskHandle, func()) { client := newTestDockerClient(t) driver := dockerDriverHarness(t, driverCfg) cleanup := driver.MkAllocDir(task, loggingIsEnabled(&DriverConfig{}, task)) @@ -178,22 +173,19 @@ func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]in // cleanSlate removes the specified docker image, including potentially stopping/removing any // containers based on that image. This is used to decouple tests that would be coupled // by using the same container image. -func cleanSlate(client *client.Client, imageID string) { +func cleanSlate(client *mclient.Client, imageID string) { ctx := context.Background() - if img, _, _ := client.ImageInspectWithRaw(ctx, imageID); img.ID == "" { + if img, err := client.ImageInspect(ctx, imageID); err != nil || img.ID == "" { return } - containers, _ := client.ContainerList(ctx, containerapi.ListOptions{ - All: true, - Filters: filters.NewArgs(filters.KeyValuePair{ - Key: "ancestor", - Value: imageID, - }), + containers, _ := client.ContainerList(ctx, mclient.ContainerListOptions{ + All: true, + Filters: mclient.Filters{}.Add("ancestor", imageID), }) - for _, c := range containers { - client.ContainerRemove(ctx, c.ID, containerapi.RemoveOptions{Force: true}) + for _, c := range containers.Items { + _, _ = client.ContainerRemove(ctx, c.ID, mclient.ContainerRemoveOptions{Force: true}) } - client.ImageRemove(ctx, imageID, image.RemoveOptions{ + _, _ = client.ImageRemove(ctx, imageID, mclient.ImageRemoveOptions{ Force: true, }) } @@ -239,11 +231,11 @@ func dockerDriverHarness(t *testing.T, cfg map[string]interface{}) *dtestutil.Dr return driver } -func newTestDockerClient(t *testing.T) *client.Client { +func newTestDockerClient(t *testing.T) *mclient.Client { t.Helper() testutil.DockerCompatible(t) - client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + client, err := mclient.NewClientWithOpts(mclient.FromEnv, mclient.WithAPIVersionNegotiation()) if err != nil { t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack()) } @@ -369,7 +361,7 @@ func TestDockerDriver_Start_StoppedContainer(t *testing.T) { if runtime.GOOS != "windows" { imageID, _, err = d.Impl().(*Driver).loadImage(task, &taskCfg, client) } else { - image, _, lErr := client.ImageInspectWithRaw(context.Background(), taskCfg.Image) + image, lErr := client.ImageInspect(context.Background(), taskCfg.Image) err = lErr if image.ID != "" { imageID = image.ID @@ -388,10 +380,16 @@ func TestDockerDriver_Start_StoppedContainer(t *testing.T) { Image: taskCfg.Image, } - _, err = client.ContainerCreate(context.Background(), opts, nil, nil, nil, containerName) + _, err = client.ContainerCreate(context.Background(), mclient.ContainerCreateOptions{ + Name: containerName, + Config: opts, + }) must.NoError(t, err) - if _, err := client.ContainerCreate(context.Background(), opts, nil, nil, nil, containerName); err != nil { + if _, err := client.ContainerCreate(context.Background(), mclient.ContainerCreateOptions{ + Name: containerName, + Config: opts, + }); err != nil { if !errdefs.IsConflict(err) { t.Fatalf("error creating initial container: %v", err) } @@ -404,7 +402,8 @@ func TestDockerDriver_Start_StoppedContainer(t *testing.T) { must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) must.NoError(t, d.DestroyTask(task.ID, true)) - must.NoError(t, client.ContainerRemove(context.Background(), containerName, containerapi.RemoveOptions{Force: true})) + _, err = client.ContainerRemove(context.Background(), containerName, mclient.ContainerRemoveOptions{Force: true}) + must.NoError(t, err) } // TestDockerDriver_ContainerAlreadyExists asserts that when Nomad tries to @@ -438,7 +437,7 @@ func TestDockerDriver_ContainerAlreadyExists(t *testing.T) { // create a container c, err := d.createContainer(client, containerCfg, cfg.Image) must.NoError(t, err) - defer client.ContainerRemove(ctx, c.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, c.Container.ID, mclient.ContainerRemoveOptions{Force: true}) // now that the container has been created, start the task that uses it, and // assert that it doesn't end up in "container already exists" fail loop @@ -450,7 +449,7 @@ func TestDockerDriver_ContainerAlreadyExists(t *testing.T) { // container c, err = d.createContainer(client, containerCfg, cfg.Image) must.NoError(t, err) - defer client.ContainerRemove(ctx, c.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, c.Container.ID, mclient.ContainerRemoveOptions{Force: true}) must.NoError(t, d.startContainer(*c)) _, _, err = d.StartTask(task) @@ -855,15 +854,15 @@ func TestDockerDriver_Labels(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) if err != nil { t.Fatalf("err: %v", err) } // expect to see 1 additional standard labels (allocID) - must.Eq(t, len(cfg.Labels)+1, len(container.Config.Labels)) + must.Eq(t, len(cfg.Labels)+1, len(container.Container.Config.Labels)) for k, v := range cfg.Labels { - must.Eq(t, v, container.Config.Labels[k]) + must.Eq(t, v, container.Container.Config.Labels[k]) } } @@ -882,7 +881,7 @@ func TestDockerDriver_ExtraLabels(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) if err != nil { t.Fatalf("err: %v", err) } @@ -895,9 +894,9 @@ func TestDockerDriver_ExtraLabels(t *testing.T) { } // expect to see 4 labels (allocID by default, task_name and task_group_name due to task*, and job_name) - must.Eq(t, 4, len(container.Config.Labels)) + must.Eq(t, 4, len(container.Container.Config.Labels)) for k, v := range expectedLabels { - must.Eq(t, v, container.Config.Labels[k]) + must.Eq(t, v, container.Container.Config.Labels[k]) } } @@ -920,11 +919,11 @@ func TestDockerDriver_LoggingConfiguration(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, "gelf", container.HostConfig.LogConfig.Type) - must.Eq(t, loggerConfig, container.HostConfig.LogConfig.Config) + must.Eq(t, "gelf", container.Container.HostConfig.LogConfig.Type) + must.Eq(t, loggerConfig, container.Container.HostConfig.LogConfig.Config) } // TestDockerDriver_LogCollectionDisabled ensures that logmon isn't configured @@ -950,12 +949,12 @@ func TestDockerDriver_LogCollectionDisabled(t *testing.T) { client, d, handle, cleanup := dockerSetup(t, task, dockerClientConfig) t.Cleanup(cleanup) must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) must.Nil(t, handle.dlogger) - must.Eq(t, "gelf", container.HostConfig.LogConfig.Type) - must.Eq(t, loggerConfig, container.HostConfig.LogConfig.Config) + must.Eq(t, "gelf", container.Container.HostConfig.LogConfig.Type) + must.Eq(t, loggerConfig, container.Container.HostConfig.LogConfig.Config) } func TestDockerDriver_HealthchecksDisable(t *testing.T) { @@ -971,11 +970,11 @@ func TestDockerDriver_HealthchecksDisable(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.NotNil(t, container.Config.Healthcheck) - must.Eq(t, []string{"NONE"}, container.Config.Healthcheck.Test) + must.NotNil(t, container.Container.Config.Healthcheck) + must.Eq(t, []string{"NONE"}, container.Container.Config.Healthcheck.Test) } func TestDockerDriver_ForcePull(t *testing.T) { @@ -992,7 +991,7 @@ func TestDockerDriver_ForcePull(t *testing.T) { must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - _, err := client.ContainerInspect(context.Background(), handle.containerID) + _, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.Nil(t, err) } @@ -1022,16 +1021,16 @@ func TestDockerDriver_ForcePull_RepoDigest(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) switch runtime.GOARCH { // TODO(jrasell): Renable this test for amd64 once we have investigated why // it has suddently changed to a different digest. case "amd64": - // must.Eq(t, "sha256:8ac48589692a53a9b8c2d1ceaa6b402665aa7fe667ba51ccc03002300856d8c7", container.Image) + // must.Eq(t, "sha256:8ac48589692a53a9b8c2d1ceaa6b402665aa7fe667ba51ccc03002300856d8c7", container.Container.Image) case "arm64": - must.Eq(t, "sha256:ba3a78826904c625e65a2eed1f247bbab59898f043490e7113e88907bf7c6b3b", container.Image) + must.Eq(t, "sha256:ba3a78826904c625e65a2eed1f247bbab59898f043490e7113e88907bf7c6b3b", container.Container.Image) default: t.Fatalf("unsupported test architecture: %s", runtime.GOARCH) } @@ -1053,12 +1052,12 @@ func TestDockerDriver_SecurityOptUnconfined(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) if err != nil { t.Fatalf("err: %v", err) } - must.Eq(t, cfg.SecurityOpt, container.HostConfig.SecurityOpt) + must.SliceContains(t, container.Container.HostConfig.SecurityOpt, "seccomp=unconfined") } func TestDockerDriver_SecurityOptFromFile(t *testing.T) { @@ -1077,10 +1076,10 @@ func TestDockerDriver_SecurityOptFromFile(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.StrContains(t, container.HostConfig.SecurityOpt[0], "reboot") + must.StrContains(t, container.Container.HostConfig.SecurityOpt[0], "reboot") } func TestDockerDriver_Runtime(t *testing.T) { @@ -1096,10 +1095,10 @@ func TestDockerDriver_Runtime(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.StrContains(t, cfg.Runtime, container.HostConfig.Runtime) + must.Eq(t, "runc", container.Container.HostConfig.Runtime) } func TestDockerDriver_CreateContainerConfig(t *testing.T) { @@ -1644,11 +1643,11 @@ func TestDockerDriver_Capabilities(t *testing.T) { must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, len(tc.CapAdd), len(container.HostConfig.CapAdd)) - must.Eq(t, len(tc.CapDrop), len(container.HostConfig.CapDrop)) + must.Eq(t, len(tc.CapAdd), len(container.Container.HostConfig.CapAdd)) + must.Eq(t, len(tc.CapDrop), len(container.Container.HostConfig.CapDrop)) }) } } @@ -1714,10 +1713,10 @@ func TestDockerDriver_Init(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.Init, *container.HostConfig.Init) + must.Eq(t, cfg.Init, *container.Container.HostConfig.Init) } func TestDockerDriver_CPUSetCPUs(t *testing.T) { @@ -1756,10 +1755,10 @@ func TestDockerDriver_CPUSetCPUs(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.CPUSetCPUs, container.HostConfig.Resources.CpusetCpus) + must.Eq(t, cfg.CPUSetCPUs, container.Container.HostConfig.Resources.CpusetCpus) }) } } @@ -1780,11 +1779,11 @@ func TestDockerDriver_MemoryHardLimit(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, task.Resources.LinuxResources.MemoryLimitBytes, container.HostConfig.MemoryReservation) - must.Eq(t, cfg.MemoryHardLimit*1024*1024, container.HostConfig.Memory) + must.Eq(t, task.Resources.LinuxResources.MemoryLimitBytes, container.Container.HostConfig.MemoryReservation) + must.Eq(t, cfg.MemoryHardLimit*1024*1024, container.Container.HostConfig.Memory) } func TestDockerDriver_MACAddress(t *testing.T) { @@ -1803,10 +1802,17 @@ func TestDockerDriver_MACAddress(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.MacAddress, container.NetworkSettings.MacAddress) + found := false + for _, endpoint := range container.Container.NetworkSettings.Networks { + if endpoint.MacAddress.String() == cfg.MacAddress { + found = true + break + } + } + must.True(t, found) } func TestDockerWorkDir(t *testing.T) { @@ -1822,9 +1828,9 @@ func TestDockerWorkDir(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.WorkDir, filepath.ToSlash(container.Config.WorkingDir)) + must.Eq(t, cfg.WorkDir, filepath.ToSlash(container.Container.Config.WorkingDir)) } func TestDockerDriver_PortsNoMap(t *testing.T) { @@ -1839,18 +1845,22 @@ func TestDockerDriver_PortsNoMap(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) // Verify that the correct ports are EXPOSED - expectedExposedPorts := map[nat.Port]struct{}{ - nat.Port(fmt.Sprintf("%d/tcp", res)): {}, - nat.Port(fmt.Sprintf("%d/udp", res)): {}, - nat.Port(fmt.Sprintf("%d/tcp", dyn)): {}, - nat.Port(fmt.Sprintf("%d/udp", dyn)): {}, + expectedExposedPorts := map[string]struct{}{ + fmt.Sprintf("%d/tcp", res): {}, + fmt.Sprintf("%d/udp", res): {}, + fmt.Sprintf("%d/tcp", dyn): {}, + fmt.Sprintf("%d/udp", dyn): {}, } - must.Eq(t, expectedExposedPorts, container.Config.ExposedPorts) + actualExposedPorts := make(map[string]struct{}, len(container.Container.Config.ExposedPorts)) + for port := range container.Container.Config.ExposedPorts { + actualExposedPorts[port.String()] = struct{}{} + } + must.Eq(t, expectedExposedPorts, actualExposedPorts) hostIP := "127.0.0.1" if runtime.GOOS == "windows" { @@ -1858,14 +1868,30 @@ func TestDockerDriver_PortsNoMap(t *testing.T) { } // Verify that the correct ports are FORWARDED - expectedPortBindings := map[nat.Port][]nat.PortBinding{ - nat.Port(fmt.Sprintf("%d/tcp", res)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port(fmt.Sprintf("%d/udp", res)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port(fmt.Sprintf("%d/tcp", dyn)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, - nat.Port(fmt.Sprintf("%d/udp", dyn)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + expectedPortBindings := map[string][]nat.PortBinding{ + fmt.Sprintf("%d/tcp", res): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + fmt.Sprintf("%d/udp", res): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + fmt.Sprintf("%d/tcp", dyn): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + fmt.Sprintf("%d/udp", dyn): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + } + + actualPortBindings := make(map[string][]nat.PortBinding, len(container.Container.HostConfig.PortBindings)) + for port, bindings := range container.Container.HostConfig.PortBindings { + converted := make([]nat.PortBinding, len(bindings)) + for i, binding := range bindings { + hostIP := "" + if binding.HostIP.IsValid() { + hostIP = binding.HostIP.String() + } + converted[i] = nat.PortBinding{ + HostIP: hostIP, + HostPort: binding.HostPort, + } + } + actualPortBindings[port.String()] = converted } - must.Eq(t, expectedPortBindings, container.HostConfig.PortBindings) + must.Eq(t, expectedPortBindings, actualPortBindings) } func TestDockerDriver_PortsMapping(t *testing.T) { @@ -1885,22 +1911,26 @@ func TestDockerDriver_PortsMapping(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) // Verify that the port environment variables are set - must.SliceContains(t, container.Config.Env, "NOMAD_PORT_main=8080") - must.SliceContains(t, container.Config.Env, "NOMAD_PORT_REDIS=6379") + must.SliceContains(t, container.Container.Config.Env, "NOMAD_PORT_main=8080") + must.SliceContains(t, container.Container.Config.Env, "NOMAD_PORT_REDIS=6379") // Verify that the correct ports are EXPOSED - expectedExposedPorts := map[nat.Port]struct{}{ - nat.Port("8080/tcp"): {}, - nat.Port("8080/udp"): {}, - nat.Port("6379/tcp"): {}, - nat.Port("6379/udp"): {}, + expectedExposedPorts := map[string]struct{}{ + "8080/tcp": {}, + "8080/udp": {}, + "6379/tcp": {}, + "6379/udp": {}, } - must.Eq(t, expectedExposedPorts, container.Config.ExposedPorts) + actualExposedPorts := make(map[string]struct{}, len(container.Container.Config.ExposedPorts)) + for port := range container.Container.Config.ExposedPorts { + actualExposedPorts[port.String()] = struct{}{} + } + must.Eq(t, expectedExposedPorts, actualExposedPorts) hostIP := "127.0.0.1" if runtime.GOOS == "windows" { @@ -1908,13 +1938,29 @@ func TestDockerDriver_PortsMapping(t *testing.T) { } // Verify that the correct ports are FORWARDED - expectedPortBindings := map[nat.Port][]nat.PortBinding{ - nat.Port("8080/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port("8080/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port("6379/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, - nat.Port("6379/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + expectedPortBindings := map[string][]nat.PortBinding{ + "8080/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + "8080/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + "6379/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + "6379/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + } + + actualPortBindings := make(map[string][]nat.PortBinding, len(container.Container.HostConfig.PortBindings)) + for port, bindings := range container.Container.HostConfig.PortBindings { + converted := make([]nat.PortBinding, len(bindings)) + for i, binding := range bindings { + hostIP := "" + if binding.HostIP.IsValid() { + hostIP = binding.HostIP.String() + } + converted[i] = nat.PortBinding{ + HostIP: hostIP, + HostPort: binding.HostPort, + } + } + actualPortBindings[port.String()] = converted } - must.Eq(t, expectedPortBindings, container.HostConfig.PortBindings) + must.Eq(t, expectedPortBindings, actualPortBindings) } func TestDockerDriver_CreateContainerConfig_Ports(t *testing.T) { @@ -1950,13 +1996,29 @@ func TestDockerDriver_CreateContainerConfig_Ports(t *testing.T) { must.Eq(t, "org/repo:0.1", c.Config.Image) // Verify that the correct ports are FORWARDED - expectedPortBindings := map[nat.Port][]nat.PortBinding{ - nat.Port("8080/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[0])}}, - nat.Port("8080/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[0])}}, - nat.Port("6379/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[1])}}, - nat.Port("6379/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[1])}}, + expectedPortBindings := map[string][]nat.PortBinding{ + "8080/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[0])}}, + "8080/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[0])}}, + "6379/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[1])}}, + "6379/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", ports[1])}}, + } + + actualPortBindings := make(map[string][]nat.PortBinding, len(c.Host.PortBindings)) + for port, bindings := range c.Host.PortBindings { + converted := make([]nat.PortBinding, len(bindings)) + for i, binding := range bindings { + hostIP := "" + if binding.HostIP.IsValid() { + hostIP = binding.HostIP.String() + } + converted[i] = nat.PortBinding{ + HostIP: hostIP, + HostPort: binding.HostPort, + } + } + actualPortBindings[port.String()] = converted } - must.Eq(t, expectedPortBindings, c.Host.PortBindings) + must.Eq(t, expectedPortBindings, actualPortBindings) } func TestDockerDriver_CreateContainerConfig_PortsMapping(t *testing.T) { @@ -1984,13 +2046,29 @@ func TestDockerDriver_CreateContainerConfig_PortsMapping(t *testing.T) { if runtime.GOOS == "windows" { hostIP = "" } - expectedPortBindings := map[nat.Port][]nat.PortBinding{ - nat.Port("8080/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port("8080/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, - nat.Port("6379/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, - nat.Port("6379/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + expectedPortBindings := map[string][]nat.PortBinding{ + "8080/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + "8080/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}}, + "6379/tcp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, + "6379/udp": {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}}, } - must.Eq(t, expectedPortBindings, c.Host.PortBindings) + + actualPortBindings := make(map[string][]nat.PortBinding, len(c.Host.PortBindings)) + for port, bindings := range c.Host.PortBindings { + converted := make([]nat.PortBinding, len(bindings)) + for i, binding := range bindings { + hostIP := "" + if binding.HostIP.IsValid() { + hostIP = binding.HostIP.String() + } + converted[i] = nat.PortBinding{ + HostIP: hostIP, + HostPort: binding.HostPort, + } + } + actualPortBindings[port.String()] = converted + } + must.Eq(t, expectedPortBindings, actualPortBindings) } @@ -2021,7 +2099,7 @@ func TestDockerDriver_CleanupContainer(t *testing.T) { time.Sleep(3 * time.Second) // Ensure that the container isn't present - _, err := client.ContainerInspect(context.Background(), handle.containerID) + _, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) if err == nil { t.Fatalf("expected to not get container") } @@ -2076,19 +2154,19 @@ func TestDockerDriver_EnableImageGC(t *testing.T) { } // we haven't called DestroyTask, image should be present - _, _, err = client.ImageInspectWithRaw(ctx, cfg.Image) + _, err = client.ImageInspect(ctx, cfg.Image) must.NoError(t, err) err = dockerDriver.DestroyTask(task.ID, false) must.NoError(t, err) // image_delay is 3s, so image should still be around for a bit - _, _, err = client.ImageInspectWithRaw(ctx, cfg.Image) + _, err = client.ImageInspect(ctx, cfg.Image) must.NoError(t, err) // Ensure image was removed tu.WaitForResult(func() (bool, error) { - if _, _, err := client.ImageInspectWithRaw(ctx, cfg.Image); err == nil { + if _, err := client.ImageInspect(ctx, cfg.Image); err == nil { return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image) } @@ -2144,7 +2222,7 @@ func TestDockerDriver_DisableImageGC(t *testing.T) { } // we haven't called DestroyTask, image should be present - _, _, err = client.ImageInspectWithRaw(ctx, handle.containerImage) + _, err = client.ImageInspect(ctx, handle.containerImage) must.NoError(t, err) err = dockerDriver.DestroyTask(task.ID, false) @@ -2154,7 +2232,7 @@ func TestDockerDriver_DisableImageGC(t *testing.T) { time.Sleep(3 * time.Second) // image should not have been removed or scheduled to be removed - _, _, err = client.ImageInspectWithRaw(ctx, cfg.Image) + _, err = client.ImageInspect(ctx, cfg.Image) must.NoError(t, err) dockerDriver.coordinator.imageLock.Lock() _, ok = dockerDriver.coordinator.deleteFuture[handle.containerImage] @@ -2209,13 +2287,14 @@ func TestDockerDriver_MissingContainer_Cleanup(t *testing.T) { } // remove the container out-of-band - must.NoError(t, client.ContainerRemove(ctx, h.containerID, containerapi.RemoveOptions{})) + _, err = client.ContainerRemove(ctx, h.containerID, mclient.ContainerRemoveOptions{}) + must.NoError(t, err) must.NoError(t, dockerDriver.DestroyTask(task.ID, false)) // Ensure image was removed tu.WaitForResult(func() (bool, error) { - if _, _, err := client.ImageInspectWithRaw(ctx, cfg.Image); err == nil { + if _, err := client.ImageInspect(ctx, cfg.Image); err == nil { return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image) } @@ -2713,11 +2792,11 @@ func TestDockerDriver_Device_Success(t *testing.T) { defer cleanup() must.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.SliceNotEmpty(t, container.HostConfig.Devices, must.Sprint("Expected one device")) - must.Eq(t, tc.Expected, container.HostConfig.Devices[0], must.Sprint("Incorrect device")) + must.SliceNotEmpty(t, container.Container.HostConfig.Devices, must.Sprint("Expected one device")) + must.Eq(t, tc.Expected, container.Container.HostConfig.Devices[0], must.Sprint("Incorrect device")) }) } } @@ -2740,11 +2819,11 @@ func TestDockerDriver_Entrypoint(t *testing.T) { must.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Len(t, 2, container.Config.Entrypoint, must.Sprint("Expected one entrypoint")) - must.Eq(t, entrypoint, container.Config.Entrypoint, must.Sprint("Incorrect entrypoint")) + must.Len(t, 2, container.Container.Config.Entrypoint, must.Sprint("Expected one entrypoint")) + must.Eq(t, entrypoint, container.Container.Config.Entrypoint, must.Sprint("Incorrect entrypoint")) } func TestDockerDriver_ReadonlyRootfs(t *testing.T) { @@ -2764,26 +2843,26 @@ func TestDockerDriver_ReadonlyRootfs(t *testing.T) { defer cleanup() must.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.True(t, container.HostConfig.ReadonlyRootfs, must.Sprint("ReadonlyRootfs option not set")) + must.True(t, container.Container.HostConfig.ReadonlyRootfs, must.Sprint("ReadonlyRootfs option not set")) } // fakeDockerClient can be used in places that accept an interface for the // docker client such as createContainer. type fakeDockerClient struct{} -func (fakeDockerClient) ContainerCreate(context.Context, *containerapi.Config, *containerapi.HostConfig, *networkapi.NetworkingConfig, *ocispec.Platform, string) (containerapi.CreateResponse, error) { - return containerapi.CreateResponse{}, fmt.Errorf("duplicate mount point") +func (fakeDockerClient) ContainerCreate(context.Context, mclient.ContainerCreateOptions) (mclient.ContainerCreateResult, error) { + return mclient.ContainerCreateResult{}, fmt.Errorf("duplicate mount point") } -func (fakeDockerClient) ContainerInspect(context.Context, string) (types.ContainerJSON, error) { +func (fakeDockerClient) ContainerInspect(context.Context, string, mclient.ContainerInspectOptions) (mclient.ContainerInspectResult, error) { panic("not implemented") } -func (fakeDockerClient) ContainerList(context.Context, containerapi.ListOptions) ([]types.Container, error) { +func (fakeDockerClient) ContainerList(context.Context, mclient.ContainerListOptions) (mclient.ContainerListResult, error) { panic("not implemented") } -func (fakeDockerClient) ContainerRemove(context.Context, string, containerapi.RemoveOptions) error { +func (fakeDockerClient) ContainerRemove(context.Context, string, mclient.ContainerRemoveOptions) (mclient.ContainerRemoveResult, error) { panic("not implemented") } @@ -2819,11 +2898,11 @@ func TestDockerDriver_AdvertiseIPv6Address(t *testing.T) { client := newTestDockerClient(t) // Make sure IPv6 is enabled - net, err := client.NetworkInspect(ctx, "bridge", networkapi.InspectOptions{}) + net, err := client.NetworkInspect(ctx, "bridge", mclient.NetworkInspectOptions{}) if err != nil { t.Skip("error retrieving bridge network information, skipping") } - if !net.EnableIPv6 { + if !net.Network.EnableIPv6 { t.Skip("IPv6 not enabled on bridge network, skipping") } @@ -2848,12 +2927,17 @@ func TestDockerDriver_AdvertiseIPv6Address(t *testing.T) { must.NoError(t, driver.WaitUntilStarted(task.ID, time.Second)) - container, err := client.ContainerInspect(ctx, handle.containerID) + container, err := client.ContainerInspect(ctx, handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - if !strings.HasPrefix(container.NetworkSettings.GlobalIPv6Address, expectedPrefix) { - t.Fatalf("Got GlobalIPv6address %s want GlobalIPv6address with prefix %s", expectedPrefix, container.NetworkSettings.GlobalIPv6Address) + found := false + for _, endpoint := range container.Container.NetworkSettings.Networks { + if strings.HasPrefix(endpoint.GlobalIPv6Address.String(), expectedPrefix) { + found = true + break + } } + must.True(t, found) } func TestDockerImageRef(t *testing.T) { @@ -2877,14 +2961,14 @@ func TestDockerImageRef(t *testing.T) { func waitForExist(t *testing.T, client *client.Client, containerID string) { tu.WaitForResult(func() (bool, error) { - container, err := client.ContainerInspect(context.Background(), containerID) + container, err := client.ContainerInspect(context.Background(), containerID, mclient.ContainerInspectOptions{}) if err != nil { if !errdefs.IsNotFound(err) { return false, err } } - return container.ID != "", nil + return container.Container.ID != "", nil }, func(err error) { must.NoError(t, err) }) @@ -2921,17 +3005,17 @@ func TestDockerDriver_CreationIdempotent(t *testing.T) { c, err := d.createContainer(client, containerCfg, cfg.Image) must.NoError(t, err) - defer client.ContainerRemove(ctx, c.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, c.Container.ID, mclient.ContainerRemoveOptions{Force: true}) // calling createContainer again creates a new one and remove old one c2, err := d.createContainer(client, containerCfg, cfg.Image) must.NoError(t, err) - defer client.ContainerRemove(ctx, c2.ID, containerapi.RemoveOptions{Force: true}) + defer client.ContainerRemove(ctx, c2.Container.ID, mclient.ContainerRemoveOptions{Force: true}) - must.NotEq(t, c.ID, c2.ID) + must.NotEq(t, c.Container.ID, c2.Container.ID) // old container was destroyed { - _, err := client.ContainerInspect(ctx, c.ID) + _, err := client.ContainerInspect(ctx, c.Container.ID, mclient.ContainerInspectOptions{}) must.Error(t, err) must.StrContains(t, err.Error(), NoSuchContainerError) } @@ -2941,13 +3025,13 @@ func TestDockerDriver_CreationIdempotent(t *testing.T) { must.NoError(t, d.startContainer(*c2)) tu.WaitForResult(func() (bool, error) { - c, err := client.ContainerInspect(ctx, c2.ID) + c, err := client.ContainerInspect(ctx, c2.Container.ID, mclient.ContainerInspectOptions{}) if err != nil { return false, fmt.Errorf("failed to get container status: %v", err) } - if !c.State.Running { - return false, fmt.Errorf("container is not running but %v", c.State) + if !c.Container.State.Running { + return false, fmt.Errorf("container is not running but %v", c.Container.State) } return true, nil @@ -3164,7 +3248,7 @@ func TestDockerDriver_StopSignal(t *testing.T) { client := newTestDockerClient(t) ctx, cancel := context.WithCancel(context.Background()) - listener, _ := client.Events(ctx, events.ListOptions{}) + eventsResult := client.Events(ctx, mclient.EventsListOptions{}) defer cancel() _, _, err := d.StartTask(task) @@ -3182,7 +3266,7 @@ func TestDockerDriver_StopSignal(t *testing.T) { WAIT: for { select { - case msg := <-listener: + case msg := <-eventsResult.Messages: // Only add kill signals if msg.Action == "kill" { sig := msg.Actor.Attributes["signal"] @@ -3192,6 +3276,10 @@ func TestDockerDriver_StopSignal(t *testing.T) { break WAIT } } + case err := <-eventsResult.Err: + if err != nil && err != context.Canceled { + must.NoError(t, err) + } case err := <-stopErr: must.NoError(t, err, must.Sprint("stop task failed")) case <-timeout: @@ -3217,10 +3305,10 @@ func TestDockerDriver_GroupAdd(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.GroupAdd, container.HostConfig.GroupAdd) + must.Eq(t, cfg.GroupAdd, container.Container.HostConfig.GroupAdd) } // TestDockerDriver_CollectStats verifies that the TaskStats API collects stats diff --git a/drivers/docker/driver_unix_test.go b/drivers/docker/driver_unix_test.go index 4defb0d4962..6ada4b576dc 100644 --- a/drivers/docker/driver_unix_test.go +++ b/drivers/docker/driver_unix_test.go @@ -18,9 +18,6 @@ import ( "testing" "time" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/api/types/network" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/client/allocdir" "github.com/hashicorp/nomad/client/testutil" @@ -28,6 +25,9 @@ import ( "github.com/hashicorp/nomad/plugins/drivers" dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" ntestutil "github.com/hashicorp/nomad/testutil" + "github.com/moby/moby/api/types/container" + "github.com/moby/moby/api/types/mount" + mclient "github.com/moby/moby/client" "github.com/shoenig/test/must" ) @@ -68,16 +68,16 @@ func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) { // Create network, network-scoped alias is supported only for containers in user defined networks client := newTestDockerClient(t) - networkResponse, err := client.NetworkCreate(ctx, "foobar", network.CreateOptions{Driver: "bridge"}) + networkResponse, err := client.NetworkCreate(ctx, "foobar", mclient.NetworkCreateOptions{Driver: "bridge"}) must.NoError(t, err) - defer client.NetworkRemove(ctx, networkResponse.ID) + defer client.NetworkRemove(ctx, networkResponse.ID, mclient.NetworkRemoveOptions{}) - network, err := client.NetworkInspect(ctx, networkResponse.ID, network.InspectOptions{}) + network, err := client.NetworkInspect(ctx, networkResponse.ID, mclient.NetworkInspectOptions{}) must.NoError(t, err) expected := []string{"foobar"} taskCfg := newTaskConfig(busyboxLongRunningCmd) - taskCfg.NetworkMode = network.Name + taskCfg.NetworkMode = network.Network.Name taskCfg.NetworkAliases = expected task := &drivers.TaskConfig{ ID: uuid.Generate(), @@ -103,7 +103,7 @@ func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) { handle, ok := dockerDriver.tasks.Get(task.ID) must.True(t, ok) - _, err = client.ContainerInspect(ctx, handle.containerID) + _, err = client.ContainerInspect(ctx, handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) } @@ -142,10 +142,10 @@ func TestDockerDriver_NetworkMode_Host(t *testing.T) { client := newTestDockerClient(t) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - actual := string(container.HostConfig.NetworkMode) + actual := string(container.Container.HostConfig.NetworkMode) must.Eq(t, expected, actual) } @@ -164,10 +164,10 @@ func TestDockerDriver_CPUCFSPeriod(t *testing.T) { waitForExist(t, client, handle.containerID) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - must.Eq(t, cfg.CPUCFSPeriod, container.HostConfig.CPUPeriod) + must.Eq(t, cfg.CPUCFSPeriod, container.Container.HostConfig.CPUPeriod) } func TestDockerDriver_Sysctl_Ulimit(t *testing.T) { @@ -190,20 +190,20 @@ func TestDockerDriver_Sysctl_Ulimit(t *testing.T) { defer cleanup() must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - container, err := client.ContainerInspect(context.Background(), handle.containerID) + container, err := client.ContainerInspect(context.Background(), handle.containerID, mclient.ContainerInspectOptions{}) must.NoError(t, err) want := "16384" - got := container.HostConfig.Sysctls["net.core.somaxconn"] + got := container.Container.HostConfig.Sysctls["net.core.somaxconn"] must.Eq(t, want, got, must.Sprintf( "Wrong net.core.somaxconn config for docker job. Expect: %s, got: %s", want, got)) expectedUlimitLen := 2 - actualUlimitLen := len(container.HostConfig.Ulimits) + actualUlimitLen := len(container.Container.HostConfig.Ulimits) must.Eq(t, want, got, must.Sprintf( "Wrong number of ulimit configs for docker job. Expect: %d, got: %d", expectedUlimitLen, actualUlimitLen)) - for _, got := range container.HostConfig.Ulimits { + for _, got := range container.Container.HostConfig.Ulimits { if expectedStr, ok := expectedUlimits[got.Name]; !ok { t.Errorf("%s config unexpected for docker job.", got.Name) } else { @@ -696,7 +696,7 @@ func TestDockerDriver_Cleanup(t *testing.T) { // Ensure image was removed ntestutil.WaitForResult(func() (bool, error) { - if _, _, err := client.ImageInspectWithRaw(context.Background(), cfg.Image); err == nil { + if _, err := client.ImageInspect(context.Background(), cfg.Image); err == nil { return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image) } diff --git a/drivers/docker/fingerprint.go b/drivers/docker/fingerprint.go index 450a6fa8316..7473acae0f9 100644 --- a/drivers/docker/fingerprint.go +++ b/drivers/docker/fingerprint.go @@ -10,11 +10,11 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/network" "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/plugins/drivers" "github.com/hashicorp/nomad/plugins/drivers/utils" pstructs "github.com/hashicorp/nomad/plugins/shared/structs" + mclient "github.com/moby/moby/client" ) func (d *Driver) Fingerprint(ctx context.Context) (<-chan *drivers.Fingerprint, error) { @@ -109,7 +109,7 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint { } } - env, err := dockerClient.ServerVersion(d.ctx) + env, err := dockerClient.ServerVersion(d.ctx, mclient.ServerVersionOptions{}) if err != nil { if d.fingerprintSuccessful() { d.logger.Debug("could not connect to docker daemon", "endpoint", dockerClient.DaemonHost(), "error", err) @@ -142,10 +142,10 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint { fp.Attributes["driver.docker.volumes.enabled"] = pstructs.NewBoolAttribute(true) } - if nets, err := dockerClient.NetworkList(d.ctx, network.ListOptions{}); err != nil { + if nets, err := dockerClient.NetworkList(d.ctx, mclient.NetworkListOptions{}); err != nil { d.logger.Warn("error discovering bridge IP", "error", err) } else { - for _, n := range nets { + for _, n := range nets.Items { if n.Name != "bridge" { continue } @@ -155,8 +155,8 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint { break } - if n.IPAM.Config[0].Gateway != "" { - fp.Attributes["driver.docker.bridge_ip"] = pstructs.NewStringAttribute(n.IPAM.Config[0].Gateway) + if !n.IPAM.Config[0].Gateway.IsUnspecified() { + fp.Attributes["driver.docker.bridge_ip"] = pstructs.NewStringAttribute(n.IPAM.Config[0].Gateway.String()) } else if d.fingerprintSuccess == nil { // Docker 17.09.0-ce dropped the Gateway IP from the bridge network // See https://github.com/moby/moby/issues/32648 @@ -166,11 +166,11 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint { } } - if dockerInfo, err := dockerClient.Info(d.ctx); err != nil { + if dockerInfo, err := dockerClient.Info(d.ctx, mclient.InfoOptions{}); err != nil { d.logger.Warn("failed to get Docker system info", "error", err) } else { - runtimeNames := make([]string, 0, len(dockerInfo.Runtimes)) - for name := range dockerInfo.Runtimes { + runtimeNames := make([]string, 0, len(dockerInfo.Info.Runtimes)) + for name := range dockerInfo.Info.Runtimes { if d.config.GPURuntimeName == name { // Nvidia runtime is detected by Docker. // It makes possible to run GPU workloads using Docker driver on this host. @@ -182,10 +182,10 @@ func (d *Driver) buildFingerprint() *drivers.Fingerprint { fp.Attributes["driver.docker.runtimes"] = pstructs.NewStringAttribute( strings.Join(runtimeNames, ",")) - fp.Attributes["driver.docker.os_type"] = pstructs.NewStringAttribute(dockerInfo.OSType) + fp.Attributes["driver.docker.os_type"] = pstructs.NewStringAttribute(dockerInfo.Info.OSType) // If this situations arises, we are running in Windows 10 with Linux Containers enabled via VM - if runtime.GOOS == "windows" && dockerInfo.OSType == "linux" { + if runtime.GOOS == "windows" && dockerInfo.Info.OSType == "linux" { if d.fingerprintSuccessful() { d.logger.Warn("Docker is configured with Linux containers; switch to Windows Containers") } diff --git a/drivers/docker/handle.go b/drivers/docker/handle.go index 82d3c50e7f1..b5062b082ba 100644 --- a/drivers/docker/handle.go +++ b/drivers/docker/handle.go @@ -13,9 +13,6 @@ import ( "github.com/armon/circbuf" "github.com/containerd/errdefs" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" - "github.com/docker/docker/pkg/stdcopy" "github.com/hashicorp/consul-template/signals" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" @@ -24,12 +21,15 @@ import ( "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/plugins/drivers" pstructs "github.com/hashicorp/nomad/plugins/shared/structs" + "github.com/moby/moby/api/pkg/stdcopy" + containerapi "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" ) type taskHandle struct { // dockerClient is useful for normal docker API calls. It should be used // for all calls that aren't Wait() or Stop() (and their variations). - dockerClient *client.Client + dockerClient *mclient.Client dockerCGroupDriver string @@ -38,7 +38,7 @@ type taskHandle struct { // - the Stop docker API call(s) (context with task kill_timeout required) // Do not use this client for any other docker API calls, instead use the // normal dockerClient which includes a default timeout. - infinityClient *client.Client + infinityClient *mclient.Client logger hclog.Logger dlogger docklog.DockerLogger @@ -86,14 +86,14 @@ func (h *taskHandle) Exec(ctx context.Context, cmd string, args []string) (*driv fullCmd := make([]string, len(args)+1) fullCmd[0] = cmd copy(fullCmd[1:], args) - createExecOpts := containerapi.ExecOptions{ + createExecOpts := mclient.ExecCreateOptions{ AttachStdin: false, AttachStdout: true, AttachStderr: true, - Tty: false, + TTY: false, Cmd: fullCmd, } - exec, err := h.dockerClient.ContainerExecCreate(ctx, h.containerID, createExecOpts) + exec, err := h.dockerClient.ExecCreate(ctx, h.containerID, createExecOpts) if err != nil { return nil, fmt.Errorf("failed to create exec object: %v", err) } @@ -101,13 +101,10 @@ func (h *taskHandle) Exec(ctx context.Context, cmd string, args []string) (*driv execResult := &drivers.ExecTaskResult{ExitResult: &drivers.ExitResult{}} stdout, _ := circbuf.NewBuffer(int64(drivers.CheckBufSize)) stderr, _ := circbuf.NewBuffer(int64(drivers.CheckBufSize)) - startOpts := containerapi.ExecStartOptions{ - Detach: false, - Tty: false, - } + startOpts := mclient.ExecAttachOptions{TTY: false} // hijack exec output streams - hijacked, err := h.dockerClient.ContainerExecAttach(ctx, exec.ID, startOpts) + hijacked, err := h.dockerClient.ExecAttach(ctx, exec.ID, startOpts) if err != nil { return nil, fmt.Errorf("failed to attach to exec object: %w", err) } @@ -120,7 +117,7 @@ func (h *taskHandle) Exec(ctx context.Context, cmd string, args []string) (*driv execResult.Stdout = stdout.Bytes() execResult.Stderr = stderr.Bytes() - res, err := h.dockerClient.ContainerExecInspect(ctx, exec.ID) + res, err := h.dockerClient.ExecInspect(ctx, exec.ID, mclient.ExecInspectOptions{}) if err != nil { return execResult, fmt.Errorf("failed to inspect exit code of exec object: %w", err) } @@ -135,7 +132,8 @@ func (h *taskHandle) Signal(ctx context.Context, s string) error { return fmt.Errorf("failed to parse signal: %v", err) } - return h.dockerClient.ContainerKill(ctx, h.containerID, s) + _, err = h.dockerClient.ContainerKill(ctx, h.containerID, mclient.ContainerKillOptions{Signal: s}) + return err } // parseSignal interprets the signal name into an os.Signal. If no name is @@ -174,7 +172,7 @@ func (h *taskHandle) Kill(killTimeout time.Duration, signal string) error { ctx, cancel := context.WithTimeout(context.Background(), graciousTimeout) defer cancel() apiTimeout := int(killTimeout.Seconds()) - err = h.infinityClient.ContainerStop(ctx, h.containerID, containerapi.StopOptions{Timeout: pointer.Of(apiTimeout)}) + _, err = h.infinityClient.ContainerStop(ctx, h.containerID, mclient.ContainerStopOptions{Timeout: pointer.Of(apiTimeout)}) } else { _, parseErr := parseSignal(runtime.GOOS, signal) if parseErr != nil { @@ -207,7 +205,7 @@ func (h *taskHandle) Kill(killTimeout time.Duration, signal string) error { } // Stop the container forcefully. - err = h.dockerClient.ContainerStop(context.Background(), h.containerID, containerapi.StopOptions{Timeout: pointer.Of(0)}) + _, err = h.dockerClient.ContainerStop(context.Background(), h.containerID, mclient.ContainerStopOptions{Timeout: pointer.Of(0)}) } if err != nil { @@ -290,26 +288,29 @@ func (h *taskHandle) run() { var exitCode containerapi.WaitResponse // this needs to use the background context because the container can // outlive Nomad itself - exitCodeC, errC := h.infinityClient.ContainerWait( - context.Background(), h.containerID, containerapi.WaitConditionNotRunning) + waitResult := h.infinityClient.ContainerWait( + context.Background(), + h.containerID, + mclient.ContainerWaitOptions{Condition: containerapi.WaitConditionNotRunning}, + ) select { - case exitCode = <-exitCodeC: + case exitCode = <-waitResult.Result: if exitCode.StatusCode != 0 { werr = fmt.Errorf("Docker container exited with non-zero exit code: %d", exitCode.StatusCode) } - case werr = <-errC: + case werr = <-waitResult.Error: h.logger.Error("failed to wait for container; already terminated") } ctx, inspectCancel := context.WithTimeout(context.Background(), 10*time.Second) defer inspectCancel() - container, ierr := h.dockerClient.ContainerInspect(ctx, h.containerID) + container, ierr := h.dockerClient.ContainerInspect(ctx, h.containerID, mclient.ContainerInspectOptions{}) oom := false if ierr != nil { h.logger.Error("failed to inspect container", "error", ierr) - } else if container.State.OOMKilled { + } else if container.Container.State.OOMKilled { h.logger.Error("OOM Killed", "container_id", h.containerID, "container_image", h.containerImage, @@ -332,7 +333,7 @@ func (h *taskHandle) run() { // ignored. ctx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second) defer stopCancel() - if err := h.dockerClient.ContainerStop(ctx, h.containerID, containerapi.StopOptions{ + if _, err := h.dockerClient.ContainerStop(ctx, h.containerID, mclient.ContainerStopOptions{ Timeout: pointer.Of(0), }); err != nil { if !errdefs.IsNotModified(err) && !errdefs.IsNotFound(err) { diff --git a/drivers/docker/network.go b/drivers/docker/network.go index 6145252402f..4b76c727641 100644 --- a/drivers/docker/network.go +++ b/drivers/docker/network.go @@ -6,10 +6,11 @@ package docker import ( "fmt" - "github.com/docker/docker/api/types" - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/plugins/drivers" + "github.com/moby/moby/api/types/container" + containerapi "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" ) const ( @@ -42,12 +43,12 @@ func (d *Driver) CreateNetwork(allocID string, createSpec *drivers.NetworkCreate return nil, false, err } - specFromContainer := func(c *types.ContainerJSON, hostname string) *drivers.NetworkIsolationSpec { + specFromContainer := func(id string, net *container.NetworkSettings, hostname string) *drivers.NetworkIsolationSpec { spec := &drivers.NetworkIsolationSpec{ Mode: drivers.NetIsolationModeGroup, - Path: c.NetworkSettings.SandboxKey, + Path: net.SandboxKey, Labels: map[string]string{ - dockerNetSpecLabelKey: c.ID, + dockerNetSpecLabelKey: id, }, } @@ -66,8 +67,8 @@ func (d *Driver) CreateNetwork(allocID string, createSpec *drivers.NetworkCreate if err != nil { return nil, false, err } - if container != nil && container.State.Running { - return specFromContainer(container, createSpec.Hostname), false, nil + if container != nil && container.Container.State.Running { + return specFromContainer(container.Container.ID, container.Container.NetworkSettings, createSpec.Hostname), false, nil } container, err = d.createContainer(dockerClient, *config, d.config.InfraImage) @@ -81,15 +82,15 @@ func (d *Driver) CreateNetwork(allocID string, createSpec *drivers.NetworkCreate // until the container is started, InspectContainerWithOptions // returns a mostly-empty struct - *container, err = dockerClient.ContainerInspect(d.ctx, container.ID) + *container, err = dockerClient.ContainerInspect(d.ctx, container.Container.ID, mclient.ContainerInspectOptions{}) if err != nil { return nil, false, err } // keep track of this pause container for reconciliation - d.pauseContainers.add(container.ID) + d.pauseContainers.add(container.Container.ID) - return specFromContainer(container, createSpec.Hostname), true, nil + return specFromContainer(container.Container.ID, container.Container.NetworkSettings, createSpec.Hostname), true, nil } func (d *Driver) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSpec) error { @@ -128,11 +129,11 @@ func (d *Driver) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSp } // this is the pause container, just kill it fast - if err := dockerClient.ContainerStop(d.ctx, id, containerapi.StopOptions{Timeout: pointer.Of(1)}); err != nil { + if _, err := dockerClient.ContainerStop(d.ctx, id, mclient.ContainerStopOptions{Timeout: pointer.Of(1)}); err != nil { d.logger.Warn("failed to stop pause container", "id", id, "error", err) } - if err := dockerClient.ContainerRemove(d.ctx, id, containerapi.RemoveOptions{ + if _, err := dockerClient.ContainerRemove(d.ctx, id, mclient.ContainerRemoveOptions{ Force: true, }); err != nil { return fmt.Errorf("failed to remove pause container: %w", err) @@ -143,7 +144,7 @@ func (d *Driver) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSp // The Docker image ID is needed in order to correctly update the image // reference count. Any error finding this, however, should not result // in an error shutting down the allocrunner. - dockerImage, _, err := dockerClient.ImageInspectWithRaw(d.ctx, d.config.InfraImage) + dockerImage, err := dockerClient.ImageInspect(d.ctx, d.config.InfraImage) if err != nil { d.logger.Warn("InspectImage failed for infra_image container destroy", "image", d.config.InfraImage, "error", err) @@ -201,7 +202,7 @@ func (d *Driver) pullInfraImage(allocID string) error { d.coordinator.imageLock.Lock() if tag != "latest" { - dockerImage, _, err := dockerClient.ImageInspectWithRaw(d.ctx, d.config.InfraImage) + dockerImage, err := dockerClient.ImageInspect(d.ctx, d.config.InfraImage) if err != nil { d.logger.Debug("InspectImage failed for infra_image container pull", "image", d.config.InfraImage, "error", err) diff --git a/drivers/docker/network_test.go b/drivers/docker/network_test.go index 5895889948a..714e0575ebf 100644 --- a/drivers/docker/network_test.go +++ b/drivers/docker/network_test.go @@ -6,9 +6,9 @@ package docker import ( "testing" - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/plugins/drivers" + containerapi "github.com/moby/moby/api/types/container" "github.com/shoenig/test/must" ) diff --git a/drivers/docker/progress.go b/drivers/docker/progress.go index 4f2145d162b..fb176db217f 100644 --- a/drivers/docker/progress.go +++ b/drivers/docker/progress.go @@ -13,8 +13,8 @@ import ( "sync" "time" - "github.com/docker/docker/pkg/jsonmessage" units "github.com/docker/go-units" + "github.com/moby/moby/api/types/jsonstream" ) const ( @@ -77,7 +77,7 @@ func lpsFromString(status string) layerProgressStatus { // docker image repo type imageProgress struct { sync.RWMutex - lastMessage *jsonmessage.JSONMessage + lastMessage *jsonstream.Message timestamp time.Time layers map[string]*layerProgress pullStart time.Time @@ -127,7 +127,7 @@ func (p *imageProgress) get() (string, time.Time) { // set takes a status message received from the docker engine api during an image // pull and updates the status of the corresponding layer -func (p *imageProgress) set(msg *jsonmessage.JSONMessage) { +func (p *imageProgress) set(msg *jsonstream.Message) { p.Lock() defer p.Unlock() @@ -259,7 +259,7 @@ func (pm *imageProgressManager) stop() { func (pm *imageProgressManager) Write(p []byte) (n int, err error) { n, err = pm.buf.Write(p) - var msg jsonmessage.JSONMessage + var msg jsonstream.Message for { line, err := pm.buf.ReadBytes('\n') diff --git a/drivers/docker/reconcile_dangling.go b/drivers/docker/reconcile_dangling.go index 8726337a597..3b6b4db8988 100644 --- a/drivers/docker/reconcile_dangling.go +++ b/drivers/docker/reconcile_dangling.go @@ -10,11 +10,10 @@ import ( "sync" "time" - "github.com/docker/docker/api/types" - containerapi "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-set/v3" + "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" ) // containerReconciler detects and kills unexpectedly running containers. @@ -27,11 +26,11 @@ type containerReconciler struct { ctx context.Context config *ContainerGCConfig logger hclog.Logger - getClient func() (*client.Client, error) + getClient func() (*mclient.Client, error) isDriverHealthy func() bool trackedContainers func() set.Collection[string] - isNomadContainer func(c types.Container) bool + isNomadContainer func(c container.Summary) bool once sync.Once } @@ -118,7 +117,7 @@ func (r *containerReconciler) removeDanglingContainersIteration() error { for id := range untracked.Items() { ctx, cancel := r.dockerAPIQueryContext() - err := dockerClient.ContainerRemove(ctx, id, containerapi.RemoveOptions{Force: true}) + _, err := dockerClient.ContainerRemove(ctx, id, mclient.ContainerRemoveOptions{Force: true}) cancel() if err != nil { r.logger.Warn("failed to remove untracked container", "container_id", id, "error", err) @@ -143,7 +142,7 @@ func (r *containerReconciler) untrackedContainers(tracked set.Collection[string] return nil, err } - cc, err := dockerClient.ContainerList(ctx, containerapi.ListOptions{ + cc, err := dockerClient.ContainerList(ctx, mclient.ContainerListOptions{ All: false, // only reconcile running containers }) if err != nil { @@ -152,7 +151,7 @@ func (r *containerReconciler) untrackedContainers(tracked set.Collection[string] cutoff := cutoffTime.Unix() - for _, c := range cc { + for _, c := range cc.Items { if tracked.Contains(c.ID) { continue } @@ -185,7 +184,7 @@ func (r *containerReconciler) dockerAPIQueryContext() (context.Context, context. return context.WithTimeout(context.Background(), timeout) } -func isNomadContainer(c types.Container) bool { +func isNomadContainer(c container.Summary) bool { if _, ok := c.Labels[dockerLabelAllocID]; ok { return true } @@ -203,7 +202,7 @@ func isNomadContainer(c types.Container) bool { return true } -func hasMount(c types.Container, p string) bool { +func hasMount(c container.Summary, p string) bool { for _, m := range c.Mounts { if m.Destination == p { return true @@ -215,7 +214,7 @@ func hasMount(c types.Container, p string) bool { var nomadContainerNamePattern = regexp.MustCompile(`\/.*-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`) -func hasNomadName(c types.Container) bool { +func hasNomadName(c container.Summary) bool { for _, n := range c.Names { if nomadContainerNamePattern.MatchString(n) { return true diff --git a/drivers/docker/reconcile_dangling_test.go b/drivers/docker/reconcile_dangling_test.go index 1cef80ffdc9..c609130c039 100644 --- a/drivers/docker/reconcile_dangling_test.go +++ b/drivers/docker/reconcile_dangling_test.go @@ -12,9 +12,9 @@ import ( "testing" "time" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/hashicorp/go-set/v3" + "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" "github.com/shoenig/test/must" "github.com/shoenig/test/wait" @@ -25,13 +25,13 @@ import ( "github.com/hashicorp/nomad/plugins/drivers" ) -func fakeContainerList(t *testing.T) (nomadContainer, nonNomadContainer types.Container) { +func fakeContainerList(t *testing.T) (nomadContainer, nonNomadContainer container.Summary) { path := "./test-resources/docker/reconciler_containers_list.json" f, err := os.Open(path) must.NoError(t, err, must.Sprintf("failed to open %s", path)) - var sampleContainerList []types.Container + var sampleContainerList []container.Summary err = json.NewDecoder(f).Decode(&sampleContainerList) must.NoError(t, err, must.Sprint("failed to decode container list")) @@ -81,35 +81,41 @@ func TestDanglingContainerRemoval_normal(t *testing.T) { // wait for task to start must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) - nonNomadContainer, err := dockerClient.ContainerCreate(ctx, &container.Config{ - Image: cfg.Image, - Cmd: append([]string{cfg.Command}, cfg.Args...), - }, nil, nil, nil, "mytest-image-"+uuid.Generate()) + nonNomadContainer, err := dockerClient.ContainerCreate(ctx, mclient.ContainerCreateOptions{ + Config: &container.Config{ + Image: cfg.Image, + Cmd: append([]string{cfg.Command}, cfg.Args...), + }, + Name: "mytest-image-" + uuid.Generate(), + }) must.NoError(t, err) t.Cleanup(func() { - _ = dockerClient.ContainerRemove(ctx, nonNomadContainer.ID, container.RemoveOptions{ + _, _ = dockerClient.ContainerRemove(ctx, nonNomadContainer.ID, mclient.ContainerRemoveOptions{ Force: true, }) }) - err = dockerClient.ContainerStart(ctx, nonNomadContainer.ID, container.StartOptions{}) + _, err = dockerClient.ContainerStart(ctx, nonNomadContainer.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) - untrackedNomadContainer, err := dockerClient.ContainerCreate(ctx, &container.Config{ - Image: cfg.Image, - Cmd: append([]string{cfg.Command}, cfg.Args...), - Labels: map[string]string{ - dockerLabelAllocID: uuid.Generate(), + untrackedNomadContainer, err := dockerClient.ContainerCreate(ctx, mclient.ContainerCreateOptions{ + Config: &container.Config{ + Image: cfg.Image, + Cmd: append([]string{cfg.Command}, cfg.Args...), + Labels: map[string]string{ + dockerLabelAllocID: uuid.Generate(), + }, }, - }, nil, nil, nil, "mytest-image-"+uuid.Generate()) + Name: "mytest-image-" + uuid.Generate(), + }) must.NoError(t, err) t.Cleanup(func() { - _ = dockerClient.ContainerRemove(ctx, untrackedNomadContainer.ID, container.RemoveOptions{ + _, _ = dockerClient.ContainerRemove(ctx, untrackedNomadContainer.ID, mclient.ContainerRemoveOptions{ Force: true, }) }) - err = dockerClient.ContainerStart(ctx, untrackedNomadContainer.ID, container.StartOptions{}) + _, err = dockerClient.ContainerStart(ctx, untrackedNomadContainer.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) dd := d.Impl().(*Driver) @@ -155,13 +161,13 @@ func TestDanglingContainerRemoval_normal(t *testing.T) { err = nReconciler.removeDanglingContainersIteration() must.NoError(t, err) - _, err = dockerClient.ContainerInspect(ctx, nonNomadContainer.ID) + _, err = dockerClient.ContainerInspect(ctx, nonNomadContainer.ID, mclient.ContainerInspectOptions{}) must.NoError(t, err) - _, err = dockerClient.ContainerInspect(ctx, handle.containerID) + _, err = dockerClient.ContainerInspect(ctx, handle.containerID, mclient.ContainerInspectOptions{}) must.ErrorContains(t, err, NoSuchContainerError) - _, err = dockerClient.ContainerInspect(ctx, untrackedNomadContainer.ID) + _, err = dockerClient.ContainerInspect(ctx, untrackedNomadContainer.ID, mclient.ContainerInspectOptions{}) must.ErrorContains(t, err, NoSuchContainerError) } @@ -192,9 +198,9 @@ func TestDanglingContainerRemoval_network(t *testing.T) { must.NoError(t, err) dockerClient := newTestDockerClient(t) - c, iErr := dockerClient.ContainerInspect(context.Background(), id) + c, iErr := dockerClient.ContainerInspect(context.Background(), id, mclient.ContainerInspectOptions{}) must.NoError(t, iErr) - must.Eq(t, "running", c.State.Status) + must.Eq(t, "running", c.Container.State.Status) // cleanup pause container err = dd.DestroyNetwork(allocID, spec) @@ -212,24 +218,27 @@ func TestDanglingContainerRemoval_Stopped(t *testing.T) { _, cfg, _ := dockerTask(t) dockerClient := newTestDockerClient(t) - cont, err := dockerClient.ContainerCreate(ctx, &container.Config{ - Image: cfg.Image, - Cmd: append([]string{cfg.Command}, cfg.Args...), - Labels: map[string]string{ - dockerLabelAllocID: uuid.Generate(), + cont, err := dockerClient.ContainerCreate(ctx, mclient.ContainerCreateOptions{ + Config: &container.Config{ + Image: cfg.Image, + Cmd: append([]string{cfg.Command}, cfg.Args...), + Labels: map[string]string{ + dockerLabelAllocID: uuid.Generate(), + }, }, - }, nil, nil, nil, "mytest-image-"+uuid.Generate()) + Name: "mytest-image-" + uuid.Generate(), + }) must.NoError(t, err) t.Cleanup(func() { - _ = dockerClient.ContainerRemove(ctx, cont.ID, container.RemoveOptions{ + _, _ = dockerClient.ContainerRemove(ctx, cont.ID, mclient.ContainerRemoveOptions{ Force: true, }) }) - err = dockerClient.ContainerStart(ctx, cont.ID, container.StartOptions{}) + _, err = dockerClient.ContainerStart(ctx, cont.ID, mclient.ContainerStartOptions{}) must.NoError(t, err) - err = dockerClient.ContainerStop(ctx, cont.ID, container.StopOptions{Timeout: pointer.Of(60)}) + _, err = dockerClient.ContainerStop(ctx, cont.ID, mclient.ContainerStopOptions{Timeout: pointer.Of(60)}) must.NoError(t, err) dd := dockerDriverHarness(t, nil).Impl().(*Driver) @@ -256,7 +265,8 @@ func TestDanglingContainerRemoval_Stopped(t *testing.T) { )) // if we start container again, it'll be marked as untracked - must.NoError(t, dockerClient.ContainerStart(ctx, cont.ID, container.StartOptions{})) + _, err = dockerClient.ContainerStart(ctx, cont.ID, mclient.ContainerStartOptions{}) + must.NoError(t, err) untracked, err := reconciler.untrackedContainers(set.New[string](0), time.Now()) must.NoError(t, err) diff --git a/drivers/docker/stats.go b/drivers/docker/stats.go index 336f26183a0..c1fa8312bf5 100644 --- a/drivers/docker/stats.go +++ b/drivers/docker/stats.go @@ -12,12 +12,13 @@ import ( "sync" "time" - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/client/lib/cpustats" cstructs "github.com/hashicorp/nomad/client/structs" "github.com/hashicorp/nomad/drivers/docker/util" "github.com/hashicorp/nomad/helper" nstructs "github.com/hashicorp/nomad/nomad/structs" + containerapi "github.com/moby/moby/api/types/container" + mclient "github.com/moby/moby/client" ) const ( @@ -132,7 +133,7 @@ func (h *taskHandle) collectDockerStats(ctx context.Context) (*containerapi.Stat var stats *containerapi.StatsResponse - statsReader, err := h.dockerClient.ContainerStats(ctx, h.containerID, false) + statsReader, err := h.dockerClient.ContainerStats(ctx, h.containerID, mclient.ContainerStatsOptions{}) if err != nil && err != io.EOF { return nil, fmt.Errorf("failed to collect stats: %w", err) } diff --git a/drivers/docker/stats_test.go b/drivers/docker/stats_test.go index 7bd0aa6e9aa..0f96e34d257 100644 --- a/drivers/docker/stats_test.go +++ b/drivers/docker/stats_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/client/lib/cpustats" cstructs "github.com/hashicorp/nomad/client/structs" "github.com/hashicorp/nomad/client/testutil" "github.com/hashicorp/nomad/drivers/docker/util" + containerapi "github.com/moby/moby/api/types/container" "github.com/shoenig/test/must" ) @@ -145,14 +145,17 @@ func Test_taskHandle_collectDockerStats(t *testing.T) { must.NoError(t, err) must.NotNil(t, dockerStats) - // Ensure all the stats we use for calculating CPU percentages within - // DockerStatsToTaskResourceUsage are present and non-zero. + // Ensure the current sample has the CPU stats we use for calculating CPU + // percentages within DockerStatsToTaskResourceUsage. + // + // We intentionally do not assert on PreCPUStats here. This test performs a + // single stats collection, and Docker may return a first sample with the + // "previous" CPU snapshot zeroed because there is no earlier sample yet. + // That behavior is valid and does not indicate a regression in stats + // collection. must.NonZero(t, dockerStats.CPUStats.CPUUsage.TotalUsage) must.NonZero(t, dockerStats.CPUStats.CPUUsage.TotalUsage) - must.NonZero(t, dockerStats.PreCPUStats.CPUUsage.TotalUsage) - must.NonZero(t, dockerStats.PreCPUStats.CPUUsage.TotalUsage) - // System usage is only populated on Linux machines. GitHub Actions Windows // runners do not have UsageInKernelmode or UsageInUsermode populated and // these datapoints are not used by the Windows stats usage function. Also @@ -162,10 +165,6 @@ func Test_taskHandle_collectDockerStats(t *testing.T) { must.NonZero(t, dockerStats.CPUStats.CPUUsage.UsageInKernelmode) must.NonZero(t, dockerStats.CPUStats.CPUUsage.UsageInUsermode) - must.NonZero(t, dockerStats.PreCPUStats.SystemUsage) - must.NonZero(t, dockerStats.PreCPUStats.CPUUsage.UsageInKernelmode) - must.NonZero(t, dockerStats.PreCPUStats.CPUUsage.UsageInUsermode) - must.NonZero(t, dockerStats.MemoryStats.Usage) must.MapContainsKey(t, dockerStats.MemoryStats.Stats, "file_mapped") diff --git a/drivers/docker/util/stats_posix.go b/drivers/docker/util/stats_posix.go index 4d7f96aa690..c93e0130ab4 100644 --- a/drivers/docker/util/stats_posix.go +++ b/drivers/docker/util/stats_posix.go @@ -6,9 +6,9 @@ package util import ( - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/client/lib/cpustats" cstructs "github.com/hashicorp/nomad/client/structs" + containerapi "github.com/moby/moby/api/types/container" ) var ( diff --git a/drivers/docker/util/stats_windows.go b/drivers/docker/util/stats_windows.go index 145af918c81..4edbde92e69 100644 --- a/drivers/docker/util/stats_windows.go +++ b/drivers/docker/util/stats_windows.go @@ -6,9 +6,9 @@ package util import ( - containerapi "github.com/docker/docker/api/types/container" "github.com/hashicorp/nomad/client/lib/cpustats" cstructs "github.com/hashicorp/nomad/client/structs" + containerapi "github.com/moby/moby/api/types/container" ) var ( @@ -17,7 +17,7 @@ var ( DockerMeasuredMemStats = []string{"RSS", "Usage", "Max Usage"} ) -func DockerStatsToTaskResourceUsage(s *containerapi.Stats, compute cpustats.Compute) *cstructs.TaskResourceUsage { +func DockerStatsToTaskResourceUsage(s *containerapi.StatsResponse, compute cpustats.Compute) *cstructs.TaskResourceUsage { var ( totalCompute = compute.TotalCompute totalCores = compute.NumCores diff --git a/drivers/docker/utils.go b/drivers/docker/utils.go index fb5023307d8..ddeb6193466 100644 --- a/drivers/docker/utils.go +++ b/drivers/docker/utils.go @@ -18,8 +18,12 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" - registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/registry" + registrytypes "github.com/moby/moby/api/types/registry" +) + +const ( + dockerRegistryIndexName = "docker.io" + dockerRegistryIndexServer = "https://index.docker.io/v1/" ) var ( @@ -73,17 +77,30 @@ func loadDockerConfig(file string) (*configfile.ConfigFile, error) { return cfile, nil } -// parseRepositoryInfo takes a repo and returns the Docker RepositoryInfo. This -// is useful for interacting with a Docker config object. -func parseRepositoryInfo(repo string) (*registry.RepositoryInfo, error) { +// repositoryInfo contains the subset of repository metadata needed for auth +// lookup against Docker config files and credential helpers. +type repositoryInfo struct { + Index *registrytypes.IndexInfo +} + +// parseRepositoryInfo takes a repo and returns the repository metadata needed +// for interacting with a Docker config object. +func parseRepositoryInfo(repo string) (*repositoryInfo, error) { name, err := reference.ParseNormalizedNamed(repo) if err != nil { return nil, fmt.Errorf("Failed to parse named repo %q: %v", repo, err) } - repoInfo, err := registry.ParseRepositoryInfo(name) - if err != nil { - return nil, fmt.Errorf("Failed to parse repository: %v", err) + domain := reference.Domain(name) + indexName := normalizeRegistryIndexName(domain) + + repoInfo := &repositoryInfo{ + Index: ®istrytypes.IndexInfo{ + Name: indexName, + Mirrors: []string{}, + Secure: true, + Official: indexName == dockerRegistryIndexName, + }, } return repoInfo, nil @@ -160,7 +177,7 @@ func authFromDockerConfig(file string) authBackend { } return auth, nil }, - authFromHelper(cfile.CredentialHelpers[registry.GetAuthConfigKey(repoInfo.Index)]), + authFromHelper(cfile.CredentialHelpers[registryGetAuthConfigKey(repoInfo.Index)]), authFromHelper(cfile.CredentialsStore), }) } @@ -326,11 +343,10 @@ func parseVolumeSpecLinux(volBind string) (hostPath string, containerPath string return parts[0], parts[1], m, nil } -// ResolveAuthConfig matches an auth configuration to a server address or a URL // copied from https://github.com/moby/moby/blob/ca20bc4214e6a13a5f134fb0d2f67c38065283a8/registry/auth.go#L217-L235 // but with the CLI types.AuthConfig type rather than api/types func registryResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig { - configKey := registry.GetAuthConfigKey(index) + configKey := registryGetAuthConfigKey(index) // First try the happy case if c, found := authConfigs[configKey]; found || index.Official { return c @@ -339,7 +355,7 @@ func registryResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *r // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing for r, ac := range authConfigs { - if configKey == registry.ConvertToHostname(r) { + if configKey == registryConvertToHostname(r) { return ac } } @@ -348,6 +364,34 @@ func registryResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *r return types.AuthConfig{} } +func registryGetAuthConfigKey(index *registrytypes.IndexInfo) string { + if index != nil && index.Official { + return dockerRegistryIndexServer + } + if index == nil { + return "" + } + return index.Name +} + +func registryConvertToHostname(rawURL string) string { + stripped := rawURL + if strings.HasPrefix(stripped, "http://") { + stripped = strings.TrimPrefix(stripped, "http://") + } else if strings.HasPrefix(stripped, "https://") { + stripped = strings.TrimPrefix(stripped, "https://") + } + stripped, _, _ = strings.Cut(stripped, "/") + return stripped +} + +func normalizeRegistryIndexName(domain string) string { + if domain == "index.docker.io" { + return dockerRegistryIndexName + } + return domain +} + // getValue returns the val if provided, or returns the defaultVal as a fallback func getValue(val, defaultVal string) string { if val == "" { diff --git a/drivers/shared/capabilities/defaults_default.go b/drivers/shared/capabilities/defaults_default.go index 5f1492d5751..425aea3c2c0 100644 --- a/drivers/shared/capabilities/defaults_default.go +++ b/drivers/shared/capabilities/defaults_default.go @@ -5,14 +5,16 @@ package capabilities -import "github.com/docker/docker/api/types" +import ( + "github.com/moby/moby/api/types/system" +) // DockerDefaults is a list of Linux capabilities enabled by Docker by default // and is used to compute the set of capabilities to add/drop given docker driver // configuration. // // https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities -func DockerDefaults(ver types.Version) *Set { +func DockerDefaults(ver system.VersionResponse) *Set { defaults := NomadDefaults() defaults.Add("NET_RAW") return defaults diff --git a/drivers/shared/capabilities/defaults_test.go b/drivers/shared/capabilities/defaults_test.go index 1835fef3cfb..f23e1400c11 100644 --- a/drivers/shared/capabilities/defaults_test.go +++ b/drivers/shared/capabilities/defaults_test.go @@ -8,28 +8,28 @@ import ( "strings" "testing" - "github.com/docker/docker/api/types" "github.com/hashicorp/nomad/ci" - "github.com/stretchr/testify/require" + "github.com/moby/moby/api/types/system" + "github.com/shoenig/test/must" ) func TestSet_NomadDefaults(t *testing.T) { ci.Parallel(t) result := NomadDefaults() - require.Len(t, result.Slice(false), 13) + must.Len(t, 13, result.Slice(false)) defaults := strings.ToLower(HCLSpecLiteral) for _, c := range result.Slice(false) { - require.Contains(t, defaults, c) + must.StrContains(t, defaults, c) } } func TestSet_DockerDefaults(t *testing.T) { ci.Parallel(t) - result := DockerDefaults(types.Version{}) - require.Len(t, result.Slice(false), 14) - require.Contains(t, result.String(), "net_raw") + result := DockerDefaults(system.VersionResponse{}) + must.Len(t, 14, result.Slice(false)) + must.StrContains(t, result.String(), "net_raw") } func TestCaps_Calculate(t *testing.T) { @@ -149,11 +149,11 @@ func TestCaps_Calculate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { caps, err := Calculate(NomadDefaults(), tc.allowCaps, tc.capAdd, tc.capDrop) if !tc.skip { - require.Equal(t, tc.err, err) - require.Equal(t, tc.exp, caps) + must.Eq(t, tc.err, err) + must.Eq(t, tc.exp, caps) } else { - require.Error(t, err) - require.Equal(t, tc.exp, caps) + must.Error(t, err) + must.Eq(t, tc.exp, caps) } }) } @@ -281,14 +281,14 @@ func TestCaps_Delta(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - add, drop, err := Delta(DockerDefaults(types.Version{}), tc.allowCaps, tc.capAdd, tc.capDrop) + add, drop, err := Delta(DockerDefaults(system.VersionResponse{}), tc.allowCaps, tc.capAdd, tc.capDrop) if !tc.skip { - require.Equal(t, tc.err, err) - require.Equal(t, tc.expAdd, add) - require.Equal(t, tc.expDrop, drop) + must.Eq(t, tc.err, err) + must.Eq(t, tc.expAdd, add) + must.Eq(t, tc.expDrop, drop) } else { - require.Error(t, err) - require.Equal(t, tc.expDrop, drop) + must.Error(t, err) + must.Eq(t, tc.expDrop, drop) } }) } diff --git a/drivers/shared/capabilities/defaults_windows.go b/drivers/shared/capabilities/defaults_windows.go index b047ec5c9f4..57f85c82b2f 100644 --- a/drivers/shared/capabilities/defaults_windows.go +++ b/drivers/shared/capabilities/defaults_windows.go @@ -8,7 +8,7 @@ package capabilities import ( "strings" - "github.com/docker/docker/api/types" + "github.com/moby/moby/api/types/system" ) // DockerDefaults is a list of Windows capabilities enabled by Docker by default @@ -17,7 +17,7 @@ import ( // // Doing this on windows is somewhat tricky, because capabilities differ by // runtime, so we have to perform some extra checks. -func DockerDefaults(ver types.Version) *Set { +func DockerDefaults(ver system.VersionResponse) *Set { defaults := NomadDefaults() // Docker CE doesn't include NET_RAW on Windows, Mirantis (aka Docker EE) does diff --git a/drivers/shared/resolvconf/mount.go b/drivers/shared/resolvconf/mount.go index c0712ab0924..b347a55585c 100644 --- a/drivers/shared/resolvconf/mount.go +++ b/drivers/shared/resolvconf/mount.go @@ -8,8 +8,7 @@ import ( "os" "path/filepath" - "github.com/docker/docker/libnetwork/resolvconf" - "github.com/docker/docker/libnetwork/types" + "github.com/hashicorp/nomad/lib/resolvconf" "github.com/hashicorp/nomad/plugins/drivers" ) @@ -43,7 +42,7 @@ func GenerateDNSMount(taskDir string, conf *drivers.DNSConfig) (*drivers.MountCo } var ( - dnsList = resolvconf.GetNameservers(currRC.Content, types.IP) + dnsList = resolvconf.GetNameservers(currRC.Content, resolvconf.IP) dnsSearchList = resolvconf.GetSearchDomains(currRC.Content) dnsOptionsList = resolvconf.GetOptions(currRC.Content) ) diff --git a/drivers/shared/resolvconf/mount_unix_test.go b/drivers/shared/resolvconf/mount_unix_test.go index ac927fe1355..bacdf42fe88 100644 --- a/drivers/shared/resolvconf/mount_unix_test.go +++ b/drivers/shared/resolvconf/mount_unix_test.go @@ -10,7 +10,7 @@ import ( "path/filepath" "testing" - "github.com/docker/docker/libnetwork/resolvconf" + "github.com/hashicorp/nomad/lib/resolvconf" "github.com/shoenig/test/must" ) diff --git a/go.mod b/go.mod index 54f39554673..4fc923f6a56 100644 --- a/go.mod +++ b/go.mod @@ -15,19 +15,19 @@ require ( github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 github.com/Masterminds/sprig/v3 v3.3.0 github.com/Microsoft/go-winio v0.6.2 - github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e + github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/aws/aws-sdk-go-v2/config v1.32.16 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 github.com/aws/smithy-go v1.25.0 github.com/container-storage-interface/spec v1.12.0 github.com/containerd/errdefs v1.0.0 github.com/containerd/go-cni v1.1.13 + github.com/containerd/log v0.1.0 github.com/containernetworking/cni v1.3.0 github.com/coreos/go-iptables v0.8.0 github.com/creack/pty v1.1.24 github.com/distribution/reference v0.6.0 github.com/docker/cli v29.4.1+incompatible - github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.7.0 github.com/docker/go-units v0.5.0 github.com/dustin/go-humanize v1.0.1 @@ -104,15 +104,20 @@ require ( github.com/mitchellh/hashstructure v1.1.0 github.com/mitchellh/pointerstructure v1.2.1 github.com/mitchellh/reflectwalk v1.0.2 + github.com/moby/moby/api v1.54.2 + github.com/moby/moby/client v0.4.1 + github.com/moby/moby/v2 v2.0.0-beta.11 + github.com/moby/sys/atomicwriter v0.1.0 github.com/moby/sys/capability v0.4.0 github.com/moby/sys/mount v0.3.4 github.com/moby/sys/mountinfo v0.7.2 github.com/moby/term v0.5.2 github.com/muesli/reflow v0.3.0 github.com/opencontainers/cgroups v0.0.6 - github.com/opencontainers/image-spec v1.1.1 + github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/runc v1.4.2 github.com/opencontainers/runtime-spec v1.3.0 + github.com/pkg/errors v0.9.1 github.com/posener/complete v1.2.3 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/common v0.67.5 @@ -129,7 +134,6 @@ require ( go.etcd.io/bbolt v1.4.3 go.uber.org/goleak v1.3.0 golang.org/x/crypto v0.50.0 - golang.org/x/mod v0.35.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.43.0 golang.org/x/text v0.36.0 @@ -147,17 +151,17 @@ require ( cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/kms v1.25.0 // indirect + cloud.google.com/go/iam v1.6.0 // indirect + cloud.google.com/go/kms v1.26.0 // indirect cloud.google.com/go/longrunning v0.8.0 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/storage v1.61.3 // indirect cyphar.com/go-pathrs v0.2.4 // indirect dario.cat/mergo v1.0.2 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect @@ -171,7 +175,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect @@ -219,7 +223,6 @@ require ( github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/containerd/console v1.0.5 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/coreos/etcd v3.3.27+incompatible // indirect github.com/coreos/go-oidc/v3 v3.11.0 // indirect github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect @@ -229,21 +232,17 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba // indirect github.com/digitalocean/godo v1.10.0 // indirect - github.com/dimchansky/utfbom v1.1.0 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/gojuno/minimock/v3 v3.0.6 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/btree v1.1.3 // indirect @@ -251,10 +250,9 @@ require ( github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/googleapis/gax-go/v2 v2.19.0 // indirect github.com/gookit/color v1.3.1 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-discover/provider/gce v0.0.0-20241120163552-5eb1507d16b4 // indirect @@ -273,7 +271,6 @@ require ( github.com/hashicorp/vault/api/auth/kubernetes v0.12.0 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 // indirect github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/joyent/triton-go v0.0.0-20190112182421-51ffac552869 // indirect @@ -288,28 +285,26 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/mrunalp/fileutils v0.5.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/selinux v1.13.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/selinux v1.13.1 // indirect github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect @@ -317,7 +312,7 @@ require ( github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/seccomp/libseccomp-golang v0.11.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect @@ -336,28 +331,27 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/term v0.42.0 // indirect golang.org/x/tools v0.44.0 // indirect - google.golang.org/api v0.271.0 // indirect - google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/api v0.272.0 // indirect + google.golang.org/genproto v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.1 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect ) diff --git a/go.sum b/go.sum index 4fdeca271d3..60bbfa1bb7a 100644 --- a/go.sum +++ b/go.sum @@ -10,12 +10,12 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/kms v1.25.0 h1:gVqvGGUmz0nYCmtoxWmdc1wli2L1apgP8U4fghPGSbQ= -cloud.google.com/go/kms v1.25.0/go.mod h1:XIdHkzfj0bUO3E+LvwPg+oc7s58/Ns8Nd8Sdtljihbk= -cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY= -cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw= +cloud.google.com/go/iam v1.6.0 h1:JiSIcEi38dWBKhB3BtfKCW+dMvCZJEhBA2BsaGJgoxs= +cloud.google.com/go/iam v1.6.0/go.mod h1:ZS6zEy7QHmcNO18mjO2viYv/n+wOUkhJqGNkPPGueGU= +cloud.google.com/go/kms v1.26.0 h1:cK9mN2cf+9V63D3H1f6koxTatWy39aTI/hCjz1I+adU= +cloud.google.com/go/kms v1.26.0/go.mod h1:pHKOdFJm63hxBsiPkYtowZPltu9dW0MWvBa6IA4HM58= +cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= +cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= @@ -30,14 +30,14 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= @@ -74,8 +74,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -113,8 +113,8 @@ github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4t github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= @@ -182,8 +182,6 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/ github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -245,26 +243,19 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/digitalocean/godo v1.10.0 h1:uW1/FcvZE/hoixnJcnlmIUvTVNdZCLjRLzmDtRi1xXY= github.com/digitalocean/godo v1.10.0/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= -github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.4.1+incompatible h1:02RT8QqqwtGRn+6SYypv8IUEbD/ltY6sfKCJIoUcGzk= github.com/docker/cli v29.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= @@ -291,8 +282,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ= github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= @@ -322,8 +313,8 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gojuno/minimock/v3 v3.0.4/go.mod h1:HqeqnwV8mAABn3pO5hqF+RE7gjA0jsN8cbbSogoGrzI= @@ -381,24 +372,20 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= -github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= -github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE= +github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA= github.com/gookit/color v1.3.1 h1:PPD/C7sf8u2L8XQPdPgsWRoAiLQGZEZOzU3cf5IYYUk= github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72 h1:vTCWu1wbdYo7PEZFem/rlr01+Un+wwVmI7wiegFdRLk= github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.72/go.mod h1:Vn+BBgKQHVQYdVQ4NZDICE1Brb+JfaONyDHr3q07oQc= github.com/hashicorp/cap v0.12.0 h1:z1PU5j8iqLpyortK8JatvPVqEGSl3eN2vxZUWtOgdZU= @@ -547,8 +534,6 @@ github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745 h1:8as8OQ+RF1QrsHvW github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 h1:rw3IAne6CDuVFlZbPOkA7bhxlqawFh7RJJ+CejfMaxE= -github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= @@ -569,7 +554,6 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -577,8 +561,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= @@ -654,6 +638,12 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/moby/moby/v2 v2.0.0-beta.11 h1:0s92vMOUn8H/MDXGeYyEqI/ulYlVpQ1Dh1zSgprgQ/Y= +github.com/moby/moby/v2 v2.0.0-beta.11/go.mod h1:CJq/k6N+wkom/MTVsGVZfsB4FxrHt4sVS0yJZphif6w= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= @@ -677,8 +667,9 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= github.com/mrunalp/fileutils v0.5.1/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -706,8 +697,8 @@ github.com/opencontainers/runc v1.4.2 h1:/AEjjXuVH9lTRl9ZyUFQj7oWBM7Xv00qFV6Vx9q github.com/opencontainers/runc v1.4.2/go.mod h1:ufk5PTTsy5pnGBAvTh50e+eqGk01pYH2YcVxh557Qlk= github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= -github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= @@ -735,7 +726,6 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -749,7 +739,6 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -758,14 +747,13 @@ github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxza github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -801,8 +789,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -861,16 +849,12 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0 h1:ZrPRak/kS4xI3AVXy8F7pipuDXmDsrO8Lg+yQjBLjw0= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.40.0/go.mod h1:3y6kQCWztq6hyW8Z9YxQDDm0Je9AJoFar2G0yDcmhRk= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= @@ -881,8 +865,6 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -978,7 +960,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1003,9 +984,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1070,19 +1049,19 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY= -google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q= +google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= +google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= -google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto v0.0.0-20260401024825-9d38bb4040a9 h1:w8JYjr7zHemS95YA5FFwk+fUv5tdQU4I8twN9bFdxVU= +google.golang.org/genproto v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:YCEC8W7HTtK7iBv+pI7g7hGAi7qdGB6bQXw3BIYAusM= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1124,8 +1103,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 h1:Z06sMOzc0GNCwp6efaVrIrz4ywGJ1v+DP0pjVkOfDuA= diff --git a/lib/resolvconf/lib.go b/lib/resolvconf/lib.go new file mode 100644 index 00000000000..7364e773e53 --- /dev/null +++ b/lib/resolvconf/lib.go @@ -0,0 +1,512 @@ +// Copyright 2013-2026 Moby authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is vendored from: +// https://github.com/moby/moby/blob/v28.5.2/libnetwork/internal/resolvconf/resolvconf.go + +// Package resolvconf is used to generate a container's /etc/resolv.conf file. +// +// Constructor Load and Parse read a resolv.conf file from the filesystem or +// a reader respectively, and return a ResolvConf object. +// +// The ResolvConf object can then be updated with overrides for nameserver, +// search domains, and DNS options. +// +// ResolvConf can then be transformed to make it suitable for legacy networking, +// a network with an internal nameserver, or used as-is for host networking. +// +// This package includes methods to write the file for the container, along with +// a hash that can be used to detect modifications made by the user to avoid +// overwriting those updates. +package resolvconf + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "io/fs" + "net/netip" + "os" + "strconv" + "strings" + "text/template" + + "github.com/containerd/log" + "github.com/moby/sys/atomicwriter" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" +) + +// Fallback nameservers, to use if none can be obtained from the host or command +// line options. +var ( + defaultIPv4NSs = []netip.Addr{ + netip.MustParseAddr("8.8.8.8"), + netip.MustParseAddr("8.8.4.4"), + } + defaultIPv6NSs = []netip.Addr{ + netip.MustParseAddr("2001:4860:4860::8888"), + netip.MustParseAddr("2001:4860:4860::8844"), + } +) + +// ResolvConf represents a resolv.conf file. It can be constructed by +// reading a resolv.conf file, using method Parse(). +type ResolvConf struct { + nameServers []netip.Addr + search []string + options []string + other []string // Unrecognised directives from the host's file, if any. + + md metadata +} + +// ExtDNSEntry represents a nameserver address that was removed from the +// container's resolv.conf when it was transformed by TransformForIntNS(). These +// are addresses read from the host's file, or applied via an override ('--dns'). +type ExtDNSEntry struct { + Addr netip.Addr + HostLoopback bool // The address is loopback, in the host's namespace. +} + +func (ed ExtDNSEntry) String() string { + if ed.HostLoopback { + return fmt.Sprintf("host(%s)", ed.Addr) + } + return ed.Addr.String() +} + +// metadata is used to track where components of the generated file have come +// from, in order to generate comments in the file for debug/info. Struct members +// are exported for use by 'text/template'. +type metadata struct { + SourcePath string + Header string + NSOverride bool + SearchOverride bool + OptionsOverride bool + NDotsFrom string + Transform string + InvalidNSs []string + ExtNameServers []ExtDNSEntry + Warnings []string +} + +// Load opens a file at path and parses it as a resolv.conf file. +// On error, the returned ResolvConf will be zero-valued. +func Load(path string) (ResolvConf, error) { + f, err := os.Open(path) + if err != nil { + return ResolvConf{}, err + } + defer f.Close() + return Parse(f, path) +} + +// Parse parses a resolv.conf file from reader. +// path is optional if reader is an *os.File. +// On error, the returned ResolvConf will be zero-valued. +func Parse(reader io.Reader, path string) (ResolvConf, error) { + var rc ResolvConf + rc.md.SourcePath = path + if path == "" { + if namer, ok := reader.(interface{ Name() string }); ok { + rc.md.SourcePath = namer.Name() + } + } + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + rc.processLine(scanner.Text()) + } + if err := scanner.Err(); err != nil { + return ResolvConf{}, errSystem{err} + } + if _, ok := rc.Option("ndots"); ok { + rc.md.NDotsFrom = "host" + } + return rc, nil +} + +// SetHeader sets the content to be included verbatim at the top of the +// generated resolv.conf file. No formatting or checking is done on the +// string. It must be valid resolv.conf syntax. (Comments must have '#' +// or ';' in the first column of each line). +// +// For example: +// +// SetHeader("# My resolv.conf\n# This file was generated.") +func (rc *ResolvConf) SetHeader(c string) { + rc.md.Header = c +} + +// NameServers returns addresses used in nameserver directives. +func (rc *ResolvConf) NameServers() []netip.Addr { + return append([]netip.Addr(nil), rc.nameServers...) +} + +// OverrideNameServers replaces the current set of nameservers. +func (rc *ResolvConf) OverrideNameServers(nameServers []netip.Addr) { + rc.nameServers = nameServers + rc.md.NSOverride = true +} + +// Search returns the current DNS search domains. +func (rc *ResolvConf) Search() []string { + return append([]string(nil), rc.search...) +} + +// OverrideSearch replaces the current DNS search domains. +func (rc *ResolvConf) OverrideSearch(search []string) { + var filtered []string + for _, s := range search { + if s != "." { + filtered = append(filtered, s) + } + } + rc.search = filtered + rc.md.SearchOverride = true +} + +// Options returns the current options. +func (rc *ResolvConf) Options() []string { + return append([]string(nil), rc.options...) +} + +// Option finds the last option named search, and returns (value, true) if +// found, else ("", false). Options are treated as "name:value", where the +// ":value" may be omitted. +// +// For example, for "ndots:1 edns0": +// +// Option("ndots") -> ("1", true) +// Option("edns0") -> ("", true) +func (rc *ResolvConf) Option(search string) (string, bool) { + for i := len(rc.options) - 1; i >= 0; i -= 1 { + k, v, _ := strings.Cut(rc.options[i], ":") + if k == search { + return v, true + } + } + return "", false +} + +// OverrideOptions replaces the current DNS options. +func (rc *ResolvConf) OverrideOptions(options []string) { + rc.options = append([]string(nil), options...) + rc.md.NDotsFrom = "" + if _, exists := rc.Option("ndots"); exists { + rc.md.NDotsFrom = "override" + } + rc.md.OptionsOverride = true +} + +// AddOption adds a single DNS option. +func (rc *ResolvConf) AddOption(option string) { + if len(option) > 6 && option[:6] == "ndots:" { + rc.md.NDotsFrom = "internal" + } + rc.options = append(rc.options, option) +} + +// TransformForLegacyNw makes sure the resolv.conf file will be suitable for +// use in a legacy network (one that has no internal resolver). +// - Remove loopback addresses inherited from the host's resolv.conf, because +// they'll only work in the host's namespace. +// - Remove IPv6 addresses if !ipv6. +// - Add default nameservers if there are no addresses left. +func (rc *ResolvConf) TransformForLegacyNw(ipv6 bool) { + rc.md.Transform = "legacy" + if rc.md.NSOverride { + return + } + var filtered []netip.Addr + for _, addr := range rc.nameServers { + if !addr.IsLoopback() && (!addr.Is6() || ipv6) { + filtered = append(filtered, addr) + } + } + rc.nameServers = filtered + if len(rc.nameServers) == 0 { + log.G(context.TODO()).Info("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers") + rc.nameServers = defaultNSAddrs(ipv6) + rc.md.Warnings = append(rc.md.Warnings, "Used default nameservers.") + } +} + +// TransformForIntNS makes sure the resolv.conf file will be suitable for +// use in a network sandbox that has an internal DNS resolver. +// - Add internalNS as a nameserver. +// - Remove other nameservers, stashing them as ExtNameServers for the +// internal resolver to use. +// - Mark ExtNameServers that must be accessed from the host namespace. +// - If no ExtNameServer addresses are found, use the defaults. +// - Ensure there's an 'options' value for each entry in reqdOptions. If the +// option includes a ':', and an option with a matching prefix exists, it +// is not modified. +func (rc *ResolvConf) TransformForIntNS( + internalNS netip.Addr, + reqdOptions []string, +) ([]ExtDNSEntry, error) { + // Add each of the nameservers read from the host's /etc/hosts or supplied as an + // override to ExtNameServers, for the internal resolver to talk to. Addresses + // read from host config should be accessed from the host's network namespace + // (HostLoopback=true). Addresses supplied as overrides are accessed from the + // container's namespace. + rc.md.ExtNameServers = nil + for _, addr := range rc.nameServers { + rc.md.ExtNameServers = append(rc.md.ExtNameServers, ExtDNSEntry{ + Addr: addr, + HostLoopback: !rc.md.NSOverride, + }) + } + + // The transformed config only lists the internal nameserver. + rc.nameServers = []netip.Addr{internalNS} + + // For each option required by the nameserver, add it if not already present. If + // the option is already present, don't override it. Apart from ndots - if the + // ndots value is invalid and an ndots option is required, replace the existing + // value. + for _, opt := range reqdOptions { + optName, _, _ := strings.Cut(opt, ":") + if optName == "ndots" { + rc.options = removeInvalidNDots(rc.options) + // No need to update rc.md.NDotsFrom, if there is no ndots option remaining, + // it'll be set to "internal" when the required value is added. + } + if _, exists := rc.Option(optName); !exists { + rc.AddOption(opt) + } + } + + rc.md.Transform = "internal resolver" + if len(rc.md.ExtNameServers) == 0 { + rc.md.Warnings = append(rc.md.Warnings, "NO EXTERNAL NAMESERVERS DEFINED") + } + return append([]ExtDNSEntry(nil), rc.md.ExtNameServers...), nil +} + +// Generate returns content suitable for writing to a resolv.conf file. If comments +// is true, the file will include header information if supplied, and a trailing +// comment that describes how the file was constructed and lists external resolvers. +func (rc *ResolvConf) Generate(comments bool) ([]byte, error) { + s := struct { + Md *metadata + NameServers []netip.Addr + Search []string + Options []string + Other []string + Overrides []string + Comments bool + }{ + Md: &rc.md, + NameServers: rc.nameServers, + Search: rc.search, + Options: rc.options, + Other: rc.other, + Comments: comments, + } + if rc.md.NSOverride { + s.Overrides = append(s.Overrides, "nameservers") + } + if rc.md.SearchOverride { + s.Overrides = append(s.Overrides, "search") + } + if rc.md.OptionsOverride { + s.Overrides = append(s.Overrides, "options") + } + + const templateText = `{{if .Comments}}{{with .Md.Header}}{{.}} + +{{end}}{{end}}{{range .NameServers -}} +nameserver {{.}} +{{end}}{{with .Search -}} +search {{join . " "}} +{{end}}{{with .Options -}} +options {{join . " "}} +{{end}}{{with .Other -}} +{{join . "\n"}} +{{end}}{{if .Comments}} +# Based on host file: '{{.Md.SourcePath}}'{{with .Md.Transform}} ({{.}}){{end}} +{{range .Md.Warnings -}} +# {{.}} +{{end -}} +{{with .Md.ExtNameServers -}} +# ExtServers: {{.}} +{{end -}} +{{with .Md.InvalidNSs -}} +# Invalid nameservers: {{.}} +{{end -}} +# Overrides: {{.Overrides}} +{{with .Md.NDotsFrom -}} +# Option ndots from: {{.}} +{{end -}} +{{end -}} +` + + funcs := template.FuncMap{"join": strings.Join} + var buf bytes.Buffer + templ, err := template.New("summary").Funcs(funcs).Parse(templateText) + if err != nil { + return nil, errSystem{err} + } + if err := templ.Execute(&buf, s); err != nil { + return nil, errSystem{err} + } + return buf.Bytes(), nil +} + +// WriteFile generates content and writes it to path. If hashPath is non-zero, it +// also writes a file containing a hash of the content, to enable UserModified() +// to determine whether the file has been modified. +func (rc *ResolvConf) WriteFile(path, hashPath string, perm os.FileMode) error { + content, err := rc.Generate(true) + if err != nil { + return err + } + + // Write the resolv.conf file - it's bind-mounted into the container, so can't + // move a temp file into place, just have to truncate and write it. + if err := os.WriteFile(path, content, perm); err != nil { + return errSystem{err} + } + + // Write the hash file. + if hashPath != "" { + hashFile, err := atomicwriter.New(hashPath, perm) + if err != nil { + return errSystem{err} + } + defer hashFile.Close() + + if _, err = hashFile.Write([]byte(digest.FromBytes(content))); err != nil { + return err + } + } + + return nil +} + +// UserModified can be used to determine whether the resolv.conf file has been +// modified since it was generated. It returns false with no error if the file +// matches the hash, true with no error if the file no longer matches the hash, +// and false with an error if the result cannot be determined. +func UserModified(rcPath, rcHashPath string) (bool, error) { + currRCHash, err := os.ReadFile(rcHashPath) + if err != nil { + // If the hash file doesn't exist, can only assume it hasn't been written + // yet (so, the user hasn't modified the file it hashes). + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + return false, errors.Wrapf(err, "failed to read hash file %s", rcHashPath) + } + expected, err := digest.Parse(string(currRCHash)) + if err != nil { + return false, errors.Wrapf(err, "failed to parse hash file %s", rcHashPath) + } + v := expected.Verifier() + currRC, err := os.Open(rcPath) + if err != nil { + return false, errors.Wrapf(err, "failed to open %s to check for modifications", rcPath) + } + defer currRC.Close() + if _, err := io.Copy(v, currRC); err != nil { + return false, errors.Wrapf(err, "failed to hash %s to check for modifications", rcPath) + } + return !v.Verified(), nil +} + +func (rc *ResolvConf) processLine(line string) { + fields := strings.Fields(line) + + // Strip blank lines and comments. + if len(fields) == 0 || fields[0][0] == '#' || fields[0][0] == ';' { + return + } + + switch fields[0] { + case "nameserver": + if len(fields) < 2 { + return + } + if addr, err := netip.ParseAddr(fields[1]); err != nil { + rc.md.InvalidNSs = append(rc.md.InvalidNSs, fields[1]) + } else { + rc.nameServers = append(rc.nameServers, addr) + } + case "domain": + // 'domain' is an obsolete name for 'search'. + fallthrough + case "search": + if len(fields) < 2 { + return + } + // Only the last 'search' directive is used. + rc.search = fields[1:] + case "options": + if len(fields) < 2 { + return + } + // Accumulate options. + rc.options = append(rc.options, fields[1:]...) + default: + // Copy anything that's not a recognised directive. + rc.other = append(rc.other, line) + } +} + +func defaultNSAddrs(ipv6 bool) []netip.Addr { + var addrs []netip.Addr + addrs = append(addrs, defaultIPv4NSs...) + if ipv6 { + addrs = append(addrs, defaultIPv6NSs...) + } + return addrs +} + +// removeInvalidNDots filters ill-formed "ndots" settings from options. +// The backing array of the options slice is reused. +func removeInvalidNDots(options []string) []string { + n := 0 + for _, opt := range options { + k, v, _ := strings.Cut(opt, ":") + if k == "ndots" { + ndots, err := strconv.Atoi(v) + if err != nil || ndots < 0 { + continue + } + } + options[n] = opt + n++ + } + clear(options[n:]) // Zero out the obsolete elements, for GC. + return options[:n] +} + +// errSystem implements [github.com/docker/docker/errdefs.ErrSystem]. +// +// We don't use the errdefs helpers here, because the resolvconf package +// is imported in BuildKit, and this is the only location that used the +// errdefs package outside of the client. +type errSystem struct{ error } + +func (errSystem) System() {} + +func (e errSystem) Unwrap() error { + return e.error +} diff --git a/lib/resolvconf/path.go b/lib/resolvconf/path.go new file mode 100644 index 00000000000..d0f81ad9f78 --- /dev/null +++ b/lib/resolvconf/path.go @@ -0,0 +1,73 @@ +// Copyright 2013-2026 Moby authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is vendored from: +// https://github.com/moby/moby/blob/v28.5.2/libnetwork/internal/resolvconf/resolvconf_path.go + +package resolvconf + +import ( + "context" + "net/netip" + "sync" + + "github.com/containerd/log" +) + +const ( + // defaultPath is the default path to the resolv.conf that contains information to resolve DNS. See Path(). + defaultPath = "/etc/resolv.conf" + // alternatePath is a path different from defaultPath, that may be used to resolve DNS. See Path(). + alternatePath = "/run/systemd/resolve/resolv.conf" +) + +// For Path to detect systemd (only needed for legacy networking). +var ( + detectSystemdResolvConfOnce sync.Once + pathAfterSystemdDetection = defaultPath +) + +// Path returns the path to the resolv.conf file that libnetwork should use. +// +// When /etc/resolv.conf contains 127.0.0.53 as the only nameserver, then +// it is assumed systemd-resolved manages DNS. Because inside the container 127.0.0.53 +// is not a valid DNS server, Path() returns /run/systemd/resolve/resolv.conf +// which is the resolv.conf that systemd-resolved generates and manages. +// Otherwise Path() returns /etc/resolv.conf. +// +// Errors are silenced as they will inevitably resurface at future open/read calls. +// +// More information at https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html#/etc/resolv.conf +// +// TODO(robmry) - alternatePath is only needed for legacy networking ... +// +// Host networking can use the host's resolv.conf as-is, and with an internal +// resolver it's also possible to use nameservers on the host's loopback +// interface. Once legacy networking is removed, this can always return +// defaultPath. +func Path() string { + detectSystemdResolvConfOnce.Do(func() { + rc, err := Load(defaultPath) + if err != nil { + // silencing error as it will resurface at next calls trying to read defaultPath + return + } + ns := rc.nameServers + if len(ns) == 1 && ns[0] == netip.MustParseAddr("127.0.0.53") { + pathAfterSystemdDetection = alternatePath + log.G(context.TODO()).Infof("detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: %s", alternatePath) + } + }) + return pathAfterSystemdDetection +} diff --git a/lib/resolvconf/resolvconf.go b/lib/resolvconf/resolvconf.go new file mode 100644 index 00000000000..2a5bd7db765 --- /dev/null +++ b/lib/resolvconf/resolvconf.go @@ -0,0 +1,171 @@ +// Copyright 2013-2026 Moby authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is vendored from: +// https://github.com/moby/moby/blob/v28.5.2/libnetwork/resolvconf/resolvconf.go + +// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf +package resolvconf + +import ( + "bytes" + "fmt" + "net/netip" + "os" + + "github.com/opencontainers/go-digest" +) + +// constants for the IP address type +const ( + IP = iota // IPv4 and IPv6 + IPv4 + IPv6 +) + +// File contains the resolv.conf content and its hash +type File struct { + Content []byte + Hash []byte +} + +// Get returns the contents of /etc/resolv.conf and its hash +func Get() (*File, error) { + return GetSpecific(Path()) +} + +// GetSpecific returns the contents of the user specified resolv.conf file and its hash +func GetSpecific(path string) (*File, error) { + resolv, err := os.ReadFile(path) + if err != nil { + return nil, err + } + hash := digest.FromBytes(resolv) + return &File{Content: resolv, Hash: []byte(hash)}, nil +} + +// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: +// 1. It looks for localhost (127.*|::1) entries in the provided +// resolv.conf, removing local nameserver entries, and, if the resulting +// cleaned config has no defined nameservers left, adds default DNS entries +// 2. Given the caller provides the enable/disable state of IPv6, the filter +// code will remove all IPv6 nameservers if it is not enabled for containers +func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) { + rc, err := Parse(bytes.NewBuffer(resolvConf), "") + if err != nil { + return nil, err + } + rc.TransformForLegacyNw(ipv6Enabled) + content, err := rc.Generate(false) + if err != nil { + return nil, err + } + hash := digest.FromBytes(content) + return &File{Content: content, Hash: []byte(hash)}, nil +} + +// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf +func GetNameservers(resolvConf []byte, kind int) []string { + rc, err := Parse(bytes.NewBuffer(resolvConf), "") + if err != nil { + return nil + } + nsAddrs := rc.NameServers() + var nameservers []string + for _, addr := range nsAddrs { + if kind == IP { + nameservers = append(nameservers, addr.String()) + } else if kind == IPv4 && addr.Is4() { + nameservers = append(nameservers, addr.String()) + } else if kind == IPv6 && addr.Is6() { + nameservers = append(nameservers, addr.String()) + } + } + return nameservers +} + +// GetNameserversAsPrefix returns nameservers (if any) listed in +// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") +func GetNameserversAsPrefix(resolvConf []byte) []netip.Prefix { + rc, err := Parse(bytes.NewBuffer(resolvConf), "") + if err != nil { + return nil + } + nsAddrs := rc.NameServers() + nameservers := make([]netip.Prefix, 0, len(nsAddrs)) + for _, addr := range nsAddrs { + nameservers = append(nameservers, netip.PrefixFrom(addr, addr.BitLen())) + } + return nameservers +} + +// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf +// If more than one search line is encountered, only the contents of the last +// one is returned. +func GetSearchDomains(resolvConf []byte) []string { + rc, err := Parse(bytes.NewBuffer(resolvConf), "") + if err != nil { + return nil + } + return rc.Search() +} + +// GetOptions returns options (if any) listed in /etc/resolv.conf +// If more than one options line is encountered, only the contents of the last +// one is returned. +func GetOptions(resolvConf []byte) []string { + rc, err := Parse(bytes.NewBuffer(resolvConf), "") + if err != nil { + return nil + } + return rc.Options() +} + +// Build generates and writes a configuration file to path containing a nameserver +// entry for every element in nameservers, a "search" entry for every element in +// dnsSearch, and an "options" entry for every element in dnsOptions. It returns +// a File containing the generated content and its (sha256) hash. +// +// Note that the resolv.conf file is written, but the hash file is not. +func Build(path string, nameservers, dnsSearch, dnsOptions []string) (*File, error) { + var ns []netip.Addr + for _, addr := range nameservers { + ipAddr, err := netip.ParseAddr(addr) + if err != nil { + return nil, fmt.Errorf("bad nameserver address: %w", err) + } + ns = append(ns, ipAddr) + } + rc := ResolvConf{} + rc.OverrideNameServers(ns) + rc.OverrideSearch(dnsSearch) + rc.OverrideOptions(dnsOptions) + + content, err := rc.Generate(false) + if err != nil { + return nil, err + } + + // Write the resolv.conf file - it's bind-mounted into the container, so can't + // move a temp file into place, just have to truncate and write it. + // + // TODO(thaJeztah): the Build function is currently only used by BuildKit, which only uses "File.Content", and doesn't require the file to be written. + if err := os.WriteFile(path, content, 0o644); err != nil { + return nil, err + } + + // TODO(thaJeztah): the Build function is currently only used by BuildKit, which does not use the Hash + hash := digest.FromBytes(content) + return &File{Content: content, Hash: []byte(hash)}, nil +} diff --git a/plugins/drivers/testutils/dns_testing.go b/plugins/drivers/testutils/dns_testing.go index efd3ecbbf35..df5ca0a6f5e 100644 --- a/plugins/drivers/testutils/dns_testing.go +++ b/plugins/drivers/testutils/dns_testing.go @@ -7,8 +7,9 @@ import ( "strings" "testing" - "github.com/docker/docker/libnetwork/resolvconf" + "github.com/hashicorp/nomad/lib/resolvconf" "github.com/hashicorp/nomad/plugins/drivers" + "github.com/hashicorp/nomad/plugins/drivers/fsisolation" "github.com/shoenig/test/must" ) @@ -20,8 +21,8 @@ func TestTaskDNSConfig(t *testing.T, driver *DriverHarness, taskID string, dns * // FS isolation is used here as a proxy for network isolation. // This is true for the current built-in drivers but it is not necessarily so. - isolated := caps.FSIsolation != drivers.FSIsolationNone - usesHostNetwork := caps.FSIsolation != drivers.FSIsolationImage + isolated := caps.FSIsolation != fsisolation.None + usesHostNetwork := caps.FSIsolation != fsisolation.Image if !isolated { t.Skip("dns config not supported on non isolated drivers")