Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] pull-through caching/proxying for images #370

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions examples/kind_registry_auth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Creates a kind cluster with Kind's custom cluster config
#
apiVersion: ctlptl.dev/v1alpha1
kind: Cluster
product: kind
registry: ctlptl-registry
registryAuths:
- host: docker.io
endpoint: https://registry-1.docker.io
username: <docker hub username>
password: <docker hub token>
kindV1Alpha4Cluster:
name: my-cluster
nodes:
- role: control-plane
16 changes: 16 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ type TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
}

// RegistryAuth contains configuration for pull-through registries
type RegistryAuth struct {
// The FQDN of the registry (i.e. docker.io)
Host string `json:"host,omitempty" yaml:"host,omitempty"`

// The Endpoint of the registry (i.e. https://registry-1.docker.io)
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
}

// Cluster contains cluster configuration.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Cluster struct {
Expand All @@ -42,6 +53,11 @@ type Cluster struct {
// Not supported on all cluster products.
Registry string `json:"registry,omitempty" yaml:"registry,omitempty"`

// A list of pull-through registries to configure on the cluster.
//
// Not supported on all cluster products.
RegistryAuths []RegistryAuth `json:"registryAuths,omitempty" yaml:"registryAuths,omitempty"`

// The desired version of Kubernetes to run.
//
// Examples:
Expand Down
3 changes: 3 additions & 0 deletions pkg/cluster/admin_docker_desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func (a *dockerDesktopAdmin) Create(ctx context.Context, desired *api.Cluster, r
if registry != nil {
return fmt.Errorf("ctlptl currently does not support connecting a registry to docker-desktop")
}
if len(desired.RegistryAuths) > 0 {
return fmt.Errorf("ctlptl currently does not support connecting pull-through registries to docker-desktop")
}

isLocalDockerDesktop := docker.IsLocalDockerDesktop(a.host, a.os)
if !isLocalDockerDesktop {
Expand Down
3 changes: 3 additions & 0 deletions pkg/cluster/admin_k3d.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func (a *k3dAdmin) Create(ctx context.Context, desired *api.Cluster, registry *a
if registry != nil {
klog.V(3).Infof("Initializing cluster with registry config:\n%+v\n---\n", registry)
}
if len(desired.RegistryAuths) > 0 {
return fmt.Errorf("ctlptl currently does not support connecting pull-through registries to k3d")
}

k3dV, err := a.version(ctx)
if err != nil {
Expand Down
31 changes: 30 additions & 1 deletion pkg/cluster/admin_kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"context"
"fmt"
"net/url"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -67,7 +68,7 @@ func (a *kindAdmin) kindClusterConfig(desired *api.Cluster, registry *api.Regist
kindConfig.APIVersion = "kind.x-k8s.io/v1alpha4"

if registry != nil {
if registryAPI == containerdRegistryV2 {
if registryAPI == containerdRegistryV2 && len(desired.RegistryAuths) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll also need to update the Create method so that it doesn't try to patch the containerd config

// Point to the registry config path.
// We'll add these files post-creation.
patch := `[plugins."io.containerd.grpc.v1.cri".registry]
Expand All @@ -84,6 +85,34 @@ func (a *kindAdmin) kindClusterConfig(desired *api.Cluster, registry *api.Regist
kindConfig.ContainerdConfigPatches = append(kindConfig.ContainerdConfigPatches, patch)
}
}

for _, reg := range desired.RegistryAuths {
// Parse the endpoint
parsedEndpoint, err := url.Parse(reg.Endpoint)
if err != nil {
klog.Warningf("Failed to parse registry URL %s: %v", reg.Endpoint, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you return the error here? if the url is malformed, we shouldn't create the cluster at all; we should make the user fix it.

continue
}

// Add the registry to the list of mirrors.
patch := fmt.Sprintf(`[plugins."io.containerd.grpc.v1.cri".registry.mirrors."%s"]
DerekTBrown marked this conversation as resolved.
Show resolved Hide resolved
endpoint = ["%s"]
`, reg.Host, reg.Endpoint)
kindConfig.ContainerdConfigPatches = append(kindConfig.ContainerdConfigPatches, patch)

// Specify the auth for the registry, if provided.
if reg.Username != "" || reg.Password != "" {
usernameValue := os.ExpandEnv(reg.Username)
passwordValue := os.ExpandEnv(reg.Password)

patch := fmt.Sprintf(`[plugins."io.containerd.grpc.v1.cri".registry.configs."%s".auth]
username = "%s"
password = "%s"
`, parsedEndpoint.Host, usernameValue, passwordValue)
kindConfig.ContainerdConfigPatches = append(kindConfig.ContainerdConfigPatches, patch)
}
}

return kindConfig
}

Expand Down
36 changes: 36 additions & 0 deletions pkg/cluster/admin_kind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,39 @@ kind-control-plane2
"kind-control-plane2",
}, nodeExec)
}

func TestKindClusterConfigWithPullThroughRegistries(t *testing.T) {
iostreams := genericclioptions.IOStreams{
In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr,
}
runner := exec.NewFakeCmdRunner(func(argv []string) string {
return ""
})
a := newKindAdmin(iostreams, runner, &fakeDockerClient{})

desired := &api.Cluster{
RegistryAuths: []api.RegistryAuth{
{
Host: "example.com",
Endpoint: "http://example.com:5000",
Username: "user",
Password: "pass",
},
},
}

kindConfig := a.kindClusterConfig(desired, nil, containerdRegistryV2)

expectedMirror := `[plugins."io.containerd.grpc.v1.cri".registry.mirrors."example.com"]
endpoint = ["http://example.com:5000"]
`
expectedAuth := `[plugins."io.containerd.grpc.v1.cri".registry.configs."example.com:5000".auth]
username = "user"
password = "pass"
`

assert.Contains(t, kindConfig.ContainerdConfigPatches, expectedMirror)
assert.Contains(t, kindConfig.ContainerdConfigPatches, expectedAuth)
}
3 changes: 3 additions & 0 deletions pkg/cluster/admin_minikube.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ func (a *minikubeAdmin) Create(ctx context.Context, desired *api.Cluster, regist
if registry != nil {
klog.V(3).Infof("Initializing cluster with registry config:\n%+v\n---\n", registry)
}
if len(desired.RegistryAuths) > 0 {
return fmt.Errorf("ctlptl currently does not support connecting pull-through registries to minikube")
}

v, err := a.version(ctx)
if err != nil {
Expand Down