Skip to content

Commit

Permalink
docker: pass credentials to image pull (#306)
Browse files Browse the repository at this point in the history
this was mainly a yak shave, since the credentials
come from the CLI rather than the API client.

fixes #303

Signed-off-by: Nick Santos <[email protected]>
  • Loading branch information
nicks authored Aug 18, 2023
1 parent 099d599 commit fc87b38
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 79 deletions.
74 changes: 68 additions & 6 deletions internal/dctr/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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))
Expand All @@ -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.
Expand All @@ -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) {
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
8 changes: 4 additions & 4 deletions internal/socat/socat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion pkg/cluster/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
}
7 changes: 4 additions & 3 deletions pkg/cluster/admin_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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,
Expand Down Expand Up @@ -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") {
Expand Down
5 changes: 3 additions & 2 deletions pkg/cluster/admin_minikube.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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,
Expand Down
46 changes: 23 additions & 23 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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 == "" {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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)
}
Expand Down
Loading

0 comments on commit fc87b38

Please sign in to comment.