From fc87b383ee66b5c46e13f44d210bdf0900f428be Mon Sep 17 00:00:00 2001 From: Nick Santos Date: Fri, 18 Aug 2023 09:33:51 -0400 Subject: [PATCH] docker: pass credentials to image pull (#306) this was mainly a yak shave, since the credentials come from the CLI rather than the API client. fixes https://github.com/tilt-dev/ctlptl/issues/303 Signed-off-by: Nick Santos --- internal/dctr/run.go | 74 ++++++++++++++++++++++++++++++++--- internal/socat/socat.go | 8 ++-- pkg/cluster/admin.go | 3 +- pkg/cluster/admin_kind.go | 7 ++-- pkg/cluster/admin_minikube.go | 5 ++- pkg/cluster/cluster.go | 46 +++++++++++----------- pkg/cluster/cluster_test.go | 16 +++++++- pkg/cluster/docker.go | 13 +----- pkg/cluster/machine.go | 5 ++- pkg/cmd/socat.go | 4 +- pkg/registry/registry.go | 44 ++++++++++----------- pkg/registry/registry_test.go | 29 +++++++++++++- 12 files changed, 175 insertions(+), 79 deletions(-) diff --git a/internal/dctr/run.go b/internal/dctr/run.go index 2f7ee0f..a2e6590 100644 --- a/internal/dctr/run.go +++ b/internal/dctr/run.go @@ -7,11 +7,15 @@ import ( "github.com/docker/cli/cli/command" cliflags "github.com/docker/cli/cli/flags" + "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/client" + "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/spf13/pflag" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -26,9 +30,38 @@ type Client interface { ContainerRemove(ctx context.Context, id string, options types.ContainerRemoveOptions) error ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.CreateResponse, error) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error + + ServerVersion(ctx context.Context) (types.Version, error) + Info(ctx context.Context) (types.Info, error) + NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error + NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error +} + +type CLI interface { + Client() Client + AuthInfo(ctx context.Context, repoInfo *registry.RepositoryInfo, cmdName string) (string, types.RequestPrivilegeFunc, error) +} + +type realCLI struct { + cli *command.DockerCli +} + +func (c *realCLI) Client() Client { + return c.cli.Client() } -func NewAPIClient(streams genericclioptions.IOStreams) (client.APIClient, error) { +func (c *realCLI) AuthInfo(ctx context.Context, repoInfo *registry.RepositoryInfo, cmdName string) (string, types.RequestPrivilegeFunc, error) { + authConfig := command.ResolveAuthConfig(ctx, c.cli, repoInfo.Index) + requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(c.cli, repoInfo.Index, cmdName) + + auth, err := registrytypes.EncodeAuthConfig(authConfig) + if err != nil { + return "", nil, errors.Wrap(err, "authInfo#EncodeAuthToBase64") + } + return auth, requestPrivilege, nil +} + +func NewCLI(streams genericclioptions.IOStreams) (CLI, error) { dockerCli, err := command.NewDockerCli( command.WithOutputStream(streams.Out), command.WithErrorStream(streams.ErrOut)) @@ -51,7 +84,15 @@ func NewAPIClient(streams genericclioptions.IOStreams) (client.APIClient, error) if endpoint.Host == "" { return nil, fmt.Errorf("initializing docker client: no valid endpoint") } - return dockerCli.Client(), nil + return &realCLI{cli: dockerCli}, nil +} + +func NewAPIClient(streams genericclioptions.IOStreams) (Client, error) { + cli, err := NewCLI(streams) + if err != nil { + return nil, err + } + return cli.Client(), nil } // A simplified remove-container-if-necessary helper. @@ -73,7 +114,8 @@ func RemoveIfNecessary(ctx context.Context, c Client, name string) error { } // A simplified run-container-and-detach helper for background support containers (like socat and the registry). -func Run(ctx context.Context, c Client, name string, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) error { +func Run(ctx context.Context, cli CLI, name string, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig) error { + c := cli.Client() ctr, err := c.ContainerInspect(ctx, name) if err == nil && (ctr.ContainerJSONBase != nil && ctr.State.Running) { @@ -95,7 +137,7 @@ func Run(ctx context.Context, c Client, name string, config *container.Config, h return fmt.Errorf("creating %s: %v", name, err) } - err := pull(ctx, c, config.Image) + err := pull(ctx, cli, config.Image) if err != nil { return fmt.Errorf("pulling image %s: %v", config.Image, err) } @@ -114,8 +156,28 @@ func Run(ctx context.Context, c Client, name string, config *container.Config, h return nil } -func pull(ctx context.Context, c Client, image string) error { - resp, err := c.ImagePull(ctx, image, types.ImagePullOptions{}) +func pull(ctx context.Context, cli CLI, image string) error { + c := cli.Client() + + ref, err := reference.ParseNormalizedNamed(image) + if err != nil { + return fmt.Errorf("could not parse image %q: %v", image, err) + } + + repoInfo, err := registry.ParseRepositoryInfo(ref) + if err != nil { + return fmt.Errorf("could not parse registry for %q: %v", image, err) + } + + encodedAuth, requestPrivilege, err := cli.AuthInfo(ctx, repoInfo, "pull") + if err != nil { + return fmt.Errorf("could not authenticate: %v", err) + } + + resp, err := c.ImagePull(ctx, image, types.ImagePullOptions{ + RegistryAuth: encodedAuth, + PrivilegeFunc: requestPrivilege, + }) if err != nil { return fmt.Errorf("pulling image %s: %v", image, err) } diff --git a/internal/socat/socat.go b/internal/socat/socat.go index 90a4b82..cd8adfd 100644 --- a/internal/socat/socat.go +++ b/internal/socat/socat.go @@ -19,11 +19,11 @@ import ( const serviceName = "ctlptl-portforward-service" type Controller struct { - client dctr.Client + cli dctr.CLI } -func NewController(client dctr.Client) *Controller { - return &Controller{client: client} +func NewController(cli dctr.CLI) *Controller { + return &Controller{cli: cli} } // Connect a port on the local machine to a port on a remote docker machine. @@ -41,7 +41,7 @@ func (c *Controller) ConnectRemoteDockerPort(ctx context.Context, port int) erro func (c *Controller) StartRemotePortforwarder(ctx context.Context) error { return dctr.Run( ctx, - c.client, + c.cli, serviceName, &container.Config{ Hostname: serviceName, diff --git a/pkg/cluster/admin.go b/pkg/cluster/admin.go index e5e33db..9de34ce 100644 --- a/pkg/cluster/admin.go +++ b/pkg/cluster/admin.go @@ -5,6 +5,7 @@ import ( "github.com/tilt-dev/localregistry-go" + "github.com/tilt-dev/ctlptl/internal/dctr" "github.com/tilt-dev/ctlptl/pkg/api" ) @@ -28,5 +29,5 @@ type Admin interface { // An extension of cluster admin that indicates the cluster configuration can be // modified for use from inside containers. type AdminInContainer interface { - ModifyConfigInContainer(ctx context.Context, cluster *api.Cluster, containerID string, dockerClient dockerClient, configWriter configWriter) error + ModifyConfigInContainer(ctx context.Context, cluster *api.Cluster, containerID string, dockerClient dctr.Client, configWriter configWriter) error } diff --git a/pkg/cluster/admin_kind.go b/pkg/cluster/admin_kind.go index 6dc0ede..68b99db 100644 --- a/pkg/cluster/admin_kind.go +++ b/pkg/cluster/admin_kind.go @@ -18,6 +18,7 @@ import ( "k8s.io/klog/v2" "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" + "github.com/tilt-dev/ctlptl/internal/dctr" "github.com/tilt-dev/ctlptl/pkg/api" ) @@ -33,10 +34,10 @@ func kindNetworkName() string { // once the underlying machine has been setup. type kindAdmin struct { iostreams genericclioptions.IOStreams - dockerClient dockerClient + dockerClient dctr.Client } -func newKindAdmin(iostreams genericclioptions.IOStreams, dockerClient dockerClient) *kindAdmin { +func newKindAdmin(iostreams genericclioptions.IOStreams, dockerClient dctr.Client) *kindAdmin { return &kindAdmin{ iostreams: iostreams, dockerClient: dockerClient, @@ -206,7 +207,7 @@ func (a *kindAdmin) Delete(ctx context.Context, config *api.Cluster) error { return nil } -func (a *kindAdmin) ModifyConfigInContainer(ctx context.Context, cluster *api.Cluster, containerID string, dockerClient dockerClient, configWriter configWriter) error { +func (a *kindAdmin) ModifyConfigInContainer(ctx context.Context, cluster *api.Cluster, containerID string, dockerClient dctr.Client, configWriter configWriter) error { err := dockerClient.NetworkConnect(ctx, kindNetworkName(), containerID, nil) if err != nil { if !errdefs.IsForbidden(err) || !strings.Contains(err.Error(), "already exists") { diff --git a/pkg/cluster/admin_minikube.go b/pkg/cluster/admin_minikube.go index 01f3069..72b6863 100644 --- a/pkg/cluster/admin_minikube.go +++ b/pkg/cluster/admin_minikube.go @@ -15,6 +15,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/klog/v2" + "github.com/tilt-dev/ctlptl/internal/dctr" cexec "github.com/tilt-dev/ctlptl/internal/exec" "github.com/tilt-dev/ctlptl/pkg/api" ) @@ -28,10 +29,10 @@ var v1_27 = semver.MustParse("1.27.0") type minikubeAdmin struct { iostreams genericclioptions.IOStreams runner cexec.CmdRunner - dockerClient dockerClient + dockerClient dctr.Client } -func newMinikubeAdmin(iostreams genericclioptions.IOStreams, dockerClient dockerClient, runner cexec.CmdRunner) *minikubeAdmin { +func newMinikubeAdmin(iostreams genericclioptions.IOStreams, dockerClient dctr.Client, runner cexec.CmdRunner) *minikubeAdmin { return &minikubeAdmin{ iostreams: iostreams, dockerClient: dockerClient, diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 7d69be8..a574c19 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -89,7 +89,7 @@ type Controller struct { config clientcmdapi.Config clients map[string]kubernetes.Interface admins map[clusterid.Product]Admin - dockerClient dockerClient + dockerCLI dctr.CLI dmachine *dockerMachine configLoader configLoader configWriter configWriter @@ -146,7 +146,7 @@ func DefaultController(iostreams genericclioptions.IOStreams) (*Controller, erro } func (c *Controller) getSocatController(ctx context.Context) (socatController, error) { - dcli, err := c.getDockerClient(ctx) + dcli, err := c.getDockerCLI(ctx) if err != nil { return nil, err } @@ -161,25 +161,25 @@ func (c *Controller) getSocatController(ctx context.Context) (socatController, e return c.socat, nil } -func (c *Controller) getDockerClient(ctx context.Context) (dockerClient, error) { +func (c *Controller) getDockerCLI(ctx context.Context) (dctr.CLI, error) { c.mu.Lock() defer c.mu.Unlock() - if c.dockerClient != nil { - return c.dockerClient, nil + if c.dockerCLI != nil { + return c.dockerCLI, nil } - client, err := dctr.NewAPIClient(c.iostreams) + cli, err := dctr.NewCLI(c.iostreams) if err != nil { return nil, err } - c.dockerClient = client - return client, nil + c.dockerCLI = cli + return cli, nil } func (c *Controller) machine(ctx context.Context, name string, product clusterid.Product) (Machine, error) { - dockerClient, err := c.getDockerClient(ctx) + dockerCLI, err := c.getDockerCLI(ctx) if err != nil { return nil, err } @@ -190,7 +190,7 @@ func (c *Controller) machine(ctx context.Context, name string, product clusterid switch product { case clusterid.ProductDockerDesktop, clusterid.ProductKIND, clusterid.ProductK3D: if c.dmachine == nil { - machine, err := NewDockerMachine(ctx, dockerClient, c.iostreams) + machine, err := NewDockerMachine(ctx, dockerCLI.Client(), c.iostreams) if err != nil { return nil, err } @@ -200,7 +200,7 @@ func (c *Controller) machine(ctx context.Context, name string, product clusterid case clusterid.ProductMinikube: if c.dmachine == nil { - machine, err := NewDockerMachine(ctx, dockerClient, c.iostreams) + machine, err := NewDockerMachine(ctx, dockerCLI.Client(), c.iostreams) if err != nil { return nil, err } @@ -213,7 +213,7 @@ func (c *Controller) machine(ctx context.Context, name string, product clusterid } func (c *Controller) registryController(ctx context.Context) (registryController, error) { - dockerClient, err := c.getDockerClient(ctx) + dockerCLI, err := c.getDockerCLI(ctx) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func (c *Controller) registryController(ctx context.Context) (registryController result := c.registryCtl if result == nil { - result = registry.NewController(c.iostreams, dockerClient) + result = registry.NewController(c.iostreams, dockerCLI) c.registryCtl = result } return result, nil @@ -232,7 +232,7 @@ func (c *Controller) registryController(ctx context.Context) (registryController // A cluster admin provides the basic start/stop functionality of a cluster, // independent of the configuration of the machine it's running on. func (c *Controller) admin(ctx context.Context, product clusterid.Product) (Admin, error) { - dockerClient, err := c.getDockerClient(ctx) + dockerCLI, err := c.getDockerCLI(ctx) if err != nil { return nil, err } @@ -247,18 +247,18 @@ func (c *Controller) admin(ctx context.Context, product clusterid.Product) (Admi switch product { case clusterid.ProductDockerDesktop: - if !docker.IsLocalDockerDesktop(dockerClient.DaemonHost(), c.os) { + if !docker.IsLocalDockerDesktop(dockerCLI.Client().DaemonHost(), c.os) { return nil, fmt.Errorf("Detected remote DOCKER_HOST. Remote Docker engines do not support Docker Desktop clusters: %s", - dockerClient.DaemonHost()) + dockerCLI.Client().DaemonHost()) } - admin = newDockerDesktopAdmin(dockerClient.DaemonHost(), c.os, c.dmachine.d4m) + admin = newDockerDesktopAdmin(dockerCLI.Client().DaemonHost(), c.os, c.dmachine.d4m) case clusterid.ProductKIND: - admin = newKindAdmin(c.iostreams, dockerClient) + admin = newKindAdmin(c.iostreams, dockerCLI.Client()) case clusterid.ProductK3D: admin = newK3DAdmin(c.iostreams, c.runner) case clusterid.ProductMinikube: - admin = newMinikubeAdmin(c.iostreams, dockerClient, c.runner) + admin = newMinikubeAdmin(c.iostreams, dockerCLI.Client(), c.runner) } if product == "" { @@ -1018,12 +1018,12 @@ func (c *Controller) List(ctx context.Context, options ListOptions) (*api.Cluste // If the current cluster is on a remote docker instance, // we need a port-forwarder to connect it. func (c *Controller) maybeCreateForwarderForCurrentCluster(ctx context.Context, errOut io.Writer) error { - dockerClient, err := c.getDockerClient(ctx) + dockerCLI, err := c.getDockerCLI(ctx) if err != nil { return err } - if docker.IsLocalHost(dockerClient.DaemonHost()) { + if docker.IsLocalHost(dockerCLI.Client().DaemonHost()) { return nil } @@ -1133,7 +1133,7 @@ func (c *Controller) waitForHealthCheckAfterCreate(ctx context.Context, cluster // currently running inside a container and the cluster admin object supports // the modifications. func (c *Controller) maybeFixKubeConfigInsideContainer(ctx context.Context, cluster *api.Cluster) error { - containerID := insideContainer(ctx, c.dockerClient) + containerID := insideContainer(ctx, c.dockerCLI.Client()) if containerID == "" { return nil } @@ -1148,7 +1148,7 @@ func (c *Controller) maybeFixKubeConfigInsideContainer(ctx context.Context, clus return nil } - err = adminInC.ModifyConfigInContainer(ctx, cluster, containerID, c.dockerClient, c.configWriter) + err = adminInC.ModifyConfigInContainer(ctx, cluster, containerID, c.dockerCLI.Client(), c.configWriter) if err != nil { return fmt.Errorf("error updating kube config: %w", err) } diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index d24d47a..04d4b2b 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + dockerregistry "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,6 +31,7 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" + "github.com/tilt-dev/ctlptl/internal/dctr" "github.com/tilt-dev/ctlptl/internal/exec" "github.com/tilt-dev/ctlptl/pkg/api" "github.com/tilt-dev/ctlptl/pkg/registry" @@ -532,7 +534,7 @@ func newFixture(t *testing.T) *fixture { waitForKubeConfigTimeout: time.Millisecond, waitForClusterCreateTimeout: time.Millisecond, os: osName, - dockerClient: dockerClient, + dockerCLI: &fakeCLI{client: dockerClient}, } return &fixture{ t: t, @@ -572,6 +574,18 @@ func newFakeController(t *testing.T) *Controller { return newFixture(t).controller } +type fakeCLI struct { + client *fakeDockerClient +} + +func (c *fakeCLI) Client() dctr.Client { + return c.client +} + +func (c *fakeCLI) AuthInfo(ctx context.Context, repoInfo *dockerregistry.RepositoryInfo, cmdName string) (string, types.RequestPrivilegeFunc, error) { + return "", nil, nil +} + type fakeDockerClient struct { started bool ncpu int diff --git a/pkg/cluster/docker.go b/pkg/cluster/docker.go index 3b9bf4f..2450eef 100644 --- a/pkg/cluster/docker.go +++ b/pkg/cluster/docker.go @@ -4,21 +4,10 @@ import ( "context" "os" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/network" "github.com/docker/docker/pkg/stringid" - "github.com/tilt-dev/ctlptl/internal/dctr" ) -type dockerClient interface { - dctr.Client - ServerVersion(ctx context.Context) (types.Version, error) - Info(ctx context.Context) (types.Info, error) - NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error - NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error -} - type detectInContainer interface { insideContainer(ctx context.Context) string } @@ -32,7 +21,7 @@ type detectInContainer interface { // container // // Returns a non-empty string representing the container ID if inside a container. -func insideContainer(ctx context.Context, client dockerClient) string { +func insideContainer(ctx context.Context, client dctr.Client) string { // allows fake client to mock the result if detect, ok := client.(detectInContainer); ok { return detect.insideContainer(ctx) diff --git a/pkg/cluster/machine.go b/pkg/cluster/machine.go index 9e04568..d474e05 100644 --- a/pkg/cluster/machine.go +++ b/pkg/cluster/machine.go @@ -18,6 +18,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" klog "k8s.io/klog/v2" + "github.com/tilt-dev/ctlptl/internal/dctr" cexec "github.com/tilt-dev/ctlptl/internal/exec" "github.com/tilt-dev/ctlptl/pkg/api" "github.com/tilt-dev/ctlptl/pkg/docker" @@ -58,13 +59,13 @@ type d4mClient interface { type dockerMachine struct { iostreams genericclioptions.IOStreams - dockerClient dockerClient + dockerClient dctr.Client sleep sleeper d4m d4mClient os string } -func NewDockerMachine(ctx context.Context, client dockerClient, iostreams genericclioptions.IOStreams) (*dockerMachine, error) { +func NewDockerMachine(ctx context.Context, client dctr.Client, iostreams genericclioptions.IOStreams) (*dockerMachine, error) { d4m, err := NewDockerDesktopClient() if err != nil { return nil, err diff --git a/pkg/cmd/socat.go b/pkg/cmd/socat.go index 15eaf20..e6d3314 100644 --- a/pkg/cmd/socat.go +++ b/pkg/cmd/socat.go @@ -39,13 +39,13 @@ func connectRemoteDocker(cmd *cobra.Command, args []string) { ctx := context.Background() streams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} - dockerAPI, err := dctr.NewAPIClient(streams) + dockerCLI, err := dctr.NewCLI(streams) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err) os.Exit(1) } - c := socat.NewController(dockerAPI) + c := socat.NewController(dockerCLI) err = c.ConnectRemoteDockerPort(ctx, port) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "connect-remote-docker: %v\n", err) diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 9ab9e1e..a33e528 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -3,11 +3,11 @@ package registry import ( "context" "fmt" + "reflect" + "regexp" "sort" "strings" "time" - "regexp" - "reflect" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" @@ -75,29 +75,29 @@ type socatController interface { } type Controller struct { - iostreams genericclioptions.IOStreams - dockerClient dctr.Client - socat socatController + iostreams genericclioptions.IOStreams + dockerCLI dctr.CLI + socat socatController } -func NewController(iostreams genericclioptions.IOStreams, dockerClient dctr.Client) *Controller { +func NewController(iostreams genericclioptions.IOStreams, dockerCLI dctr.CLI) *Controller { return &Controller{ - iostreams: iostreams, - dockerClient: dockerClient, - socat: socat.NewController(dockerClient), + iostreams: iostreams, + dockerCLI: dockerCLI, + socat: socat.NewController(dockerCLI), } } func DefaultController(iostreams genericclioptions.IOStreams) (*Controller, error) { - dockerClient, err := dctr.NewAPIClient(iostreams) + dockerCLI, err := dctr.NewCLI(iostreams) if err != nil { return nil, err } return &Controller{ - iostreams: iostreams, - dockerClient: dockerClient, - socat: socat.NewController(dockerClient), + iostreams: iostreams, + dockerCLI: dockerCLI, + socat: socat.NewController(dockerCLI), }, nil } @@ -134,7 +134,7 @@ func (c *Controller) List(ctx context.Context, options ListOptions) (*api.Regist name := strings.TrimPrefix(container.Names[0], "/") created := time.Unix(container.Created, 0) - inspect, err := c.dockerClient.ContainerInspect(ctx, container.ID) + inspect, err := c.dockerCLI.Client().ContainerInspect(ctx, container.ID) if err != nil { return nil, err } @@ -250,11 +250,11 @@ func (c *Controller) Apply(ctx context.Context, desired *api.Registry) (*api.Reg } } } - if _, ok := desiredEnvs["REGISTRY_STORAGE_DELETE_ENABLED"]; ! ok { + if _, ok := desiredEnvs["REGISTRY_STORAGE_DELETE_ENABLED"]; !ok { desiredEnvs["REGISTRY_STORAGE_DELETE_ENABLED"] = "true" desired.Env = append(desired.Env, "REGISTRY_STORAGE_DELETE_ENABLED=true") } - if eq := reflect.DeepEqual(desiredEnvs, existingEnvs); ! eq { + if eq := reflect.DeepEqual(desiredEnvs, existingEnvs); !eq { needsDelete = true } @@ -274,7 +274,7 @@ func (c *Controller) Apply(ctx context.Context, desired *api.Registry) (*api.Reg _, _ = fmt.Fprintf(c.iostreams.ErrOut, "Creating registry %q...\n", desired.Name) - err = dctr.RemoveIfNecessary(ctx, c.dockerClient, desired.Name) + err = dctr.RemoveIfNecessary(ctx, c.dockerCLI.Client(), desired.Name) if err != nil { return nil, err } @@ -286,7 +286,7 @@ func (c *Controller) Apply(ctx context.Context, desired *api.Registry) (*api.Reg err = dctr.Run( ctx, - c.dockerClient, + c.dockerCLI, desired.Name, &container.Config{ Hostname: desired.Name, @@ -378,7 +378,7 @@ func (c *Controller) labelConfigs(existing *api.Registry, desired *api.Registry) } func (c *Controller) maybeCreateForwarder(ctx context.Context, port int) error { - if docker.IsLocalHost(c.dockerClient.DaemonHost()) { + if docker.IsLocalHost(c.dockerCLI.Client().DaemonHost()) { return nil } @@ -390,7 +390,7 @@ func (c *Controller) maybeCreateForwarder(ctx context.Context, port int) error { func (c *Controller) registryContainers(ctx context.Context) ([]types.Container, error) { containers := make(map[string]types.Container) - roleContainers, err := c.dockerClient.ContainerList(ctx, types.ContainerListOptions{ + roleContainers, err := c.dockerCLI.Client().ContainerList(ctx, types.ContainerListOptions{ Filters: filters.NewArgs( filters.Arg("label", fmt.Sprintf("%s=registry", docker.ContainerLabelRole))), All: true, @@ -402,7 +402,7 @@ func (c *Controller) registryContainers(ctx context.Context) ([]types.Container, containers[roleContainers[i].ID] = roleContainers[i] } - ancestorContainers, err := c.dockerClient.ContainerList(ctx, types.ContainerListOptions{ + ancestorContainers, err := c.dockerCLI.Client().ContainerList(ctx, types.ContainerListOptions{ Filters: filters.NewArgs( filters.Arg("ancestor", DefaultRegistryImageRef)), All: true, @@ -436,7 +436,7 @@ func (c *Controller) Delete(ctx context.Context, name string) error { return fmt.Errorf("container not running registry: %s", name) } - return c.dockerClient.ContainerRemove(ctx, registry.Status.ContainerID, types.ContainerRemoveOptions{ + return c.dockerCLI.Client().ContainerRemove(ctx, registry.Status.ContainerID, types.ContainerRemoveOptions{ Force: true, }) } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index d247d84..17047da 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -12,12 +12,14 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/registry" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" + "github.com/tilt-dev/ctlptl/internal/dctr" "github.com/tilt-dev/ctlptl/pkg/api" ) @@ -371,6 +373,18 @@ func TestCustomEnv(t *testing.T) { } } +type fakeCLI struct { + client *fakeDocker +} + +func (c *fakeCLI) Client() dctr.Client { + return c.client +} + +func (c *fakeCLI) AuthInfo(ctx context.Context, repoInfo *registry.RepositoryInfo, cmdName string) (string, types.RequestPrivilegeFunc, error) { + return "", nil, nil +} + type fakeDocker struct { containers []types.Container lastRemovedContainer string @@ -478,6 +492,18 @@ func (d *fakeDocker) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { return nil } +func (d *fakeDocker) ServerVersion(ctx context.Context) (types.Version, error) { + return types.Version{}, nil +} +func (d *fakeDocker) Info(ctx context.Context) (types.Info, error) { + return types.Info{}, nil +} +func (d *fakeDocker) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error { + return nil +} +func (d *fakeDocker) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error { + return nil +} type fixture struct { t *testing.T @@ -490,7 +516,8 @@ func newFixture(t *testing.T) *fixture { d := &fakeDocker{} controller := NewController( - genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}, d) + genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}, + &fakeCLI{client: d}) return &fixture{ t: t, docker: d,