diff --git a/pkg/api/types.go b/pkg/api/types.go index f5afca5..412dc13 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -176,6 +176,11 @@ type Registry struct { // that are tagged "app: k3d". Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + // Environment vars to use for registry container (optional). + // + // Can be used to change some parameters likes REGISTRY_HTTP_ADDR, REGISTRY_PROXY_REMOTEURL + Env []string `json:"env,omitempty" yaml:"env,omitempty"` + // Image to use for registry container (optional). // // Can be used to provide an alternate image or use a different registry @@ -223,6 +228,9 @@ type RegistryStatus struct { // Labels attached to the running container. Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + // Env attached to the running container. + Env []string `json:"env,omitempty" yaml:"env,omitempty"` + // Image for the running container. Image string `json:"image,omitempty" yaml:"image,omitempty"` } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index c7bdd04..9ab9e1e 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -6,6 +6,8 @@ import ( "sort" "strings" "time" + "regexp" + "reflect" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" @@ -132,6 +134,11 @@ 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) + if err != nil { + return nil, err + } + env := inspect.Config.Env netSummary := container.NetworkSettings ipAddress := "" networks := []string{} @@ -163,6 +170,7 @@ func (c *Controller) List(ctx context.Context, options ListOptions) (*api.Regist State: container.State, Labels: container.Labels, Image: container.Image, + Env: env, }, } @@ -218,6 +226,38 @@ func (c *Controller) Apply(ctx context.Context, desired *api.Registry) (*api.Reg needsDelete = true } } + + r := regexp.MustCompile("^(?P[^=]+)=(?P.*)") + desiredEnvs := make(map[string]string) + for _, value := range desired.Env { + m := r.FindStringSubmatch(value) + if m != nil { + k := m[r.SubexpIndex("key")] + v := m[r.SubexpIndex("value")] + if k != "PATH" { + desiredEnvs[k] = v + } + } + } + existingEnvs := make(map[string]string) + for _, value := range existing.Status.Env { + m := r.FindStringSubmatch(value) + if m != nil { + k := m[r.SubexpIndex("key")] + v := m[r.SubexpIndex("value")] + if k != "PATH" { + existingEnvs[k] = v + } + } + } + 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 { + needsDelete = true + } + if needsDelete && existing.Name != "" { err = c.Delete(ctx, existing.Name) if err != nil { @@ -253,7 +293,7 @@ func (c *Controller) Apply(ctx context.Context, desired *api.Registry) (*api.Reg Image: desired.Image, ExposedPorts: exposedPorts, Labels: c.labelConfigs(existing, desired), - Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=true"}, + Env: desired.Env, }, &container.HostConfig{ RestartPolicy: container.RestartPolicy{Name: "always"}, diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index b5dbc80..5c24ecb 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -138,6 +138,7 @@ func TestListRegistries(t *testing.T) { State: "running", Labels: map[string]string{"dev.tilt.ctlptl.role": "registry"}, Image: "registry:2", + Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=true","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, }, }, list.Items[0]) assert.Equal(t, api.Registry{ @@ -155,6 +156,7 @@ func TestListRegistries(t *testing.T) { State: "running", Labels: map[string]string{"dev.tilt.ctlptl.role": "registry"}, Image: "fake.tilt.dev/my-registry-image:latest", + Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=true","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, }, }, list.Items[1]) assert.Equal(t, api.Registry{ @@ -171,6 +173,7 @@ func TestListRegistries(t *testing.T) { ContainerID: "d62f2587ff7b03858f144d3cf83c789578a6d6403f8b82a459ab4e317917cd42", State: "running", Image: "registry:2", + Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=true","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, }, }, list.Items[2]) } @@ -198,6 +201,7 @@ func TestGetRegistry(t *testing.T) { State: "running", Labels: map[string]string{"dev.tilt.ctlptl.role": "registry"}, Image: "registry:2", + Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=true","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, }, }, registry) } @@ -326,6 +330,47 @@ func TestCustomImage(t *testing.T) { } } +func TestCustomEnv(t *testing.T) { + f := newFixture(t) + defer f.TearDown() + + // Make sure the previous registry is wiped out + // because it doesn't have the image we want. + f.docker.containers = []types.Container{kindRegistry()} + + f.docker.onCreate = func() { + f.docker.containers = []types.Container{kindRegistry()} + } + + // ensure stable w/o image change + _, err := f.c.Apply(context.Background(), &api.Registry{ + TypeMeta: typeMeta, + Name: "kind-registry", + Image: "registry:2", + }) + if assert.NoError(t, err) { + assert.Nil(t, f.docker.lastCreateConfig, "Registry should not have been re-created") + } + + // change env, should be (re)created + registry, err := f.c.Apply(context.Background(), &api.Registry{ + TypeMeta: typeMeta, + Name: "kind-registry", + Image: "registry:2", + Env: []string{"REGISTRY_STORAGE_DELETE_ENABLED=false"}, + }) + if assert.NoError(t, err) { + assert.Equal(t, "running", registry.Status.State) + } + config := f.docker.lastCreateConfig + if assert.NotNil(t, config) { + assert.Equal(t, map[string]string{"dev.tilt.ctlptl.role": "registry"}, config.Labels) + assert.Equal(t, "kind-registry", config.Hostname) + assert.Equal(t, "registry:2", config.Image) + assert.Equal(t, []string{"REGISTRY_STORAGE_DELETE_ENABLED=false"}, config.Env) + } +} + type fakeDocker struct { containers []types.Container lastRemovedContainer string @@ -358,6 +403,33 @@ func (d *fakeDocker) ContainerInspect(ctx context.Context, containerID string) ( Running: c.State == "running", }, }, + Config: &container.Config{ + Hostname:"test", + Domainname:"", + User:"", + AttachStdin:false, + AttachStdout:false, + AttachStderr:false, + // ExposedPorts:nat.PortSet{"5000/tcp":struct {}{}}, + Tty:false, + OpenStdin:false, + StdinOnce:false, + Env:[]string{"REGISTRY_STORAGE_DELETE_ENABLED=true", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + Cmd:[]string{"/etc/docker/registry/config.yml"}, + Healthcheck:(*container.HealthConfig)(nil), + ArgsEscaped:false, + Image:"docker.io/library/registry:2", + Volumes:map[string]struct {}{"/var/lib/registry":struct {}{}}, + WorkingDir:"", + Entrypoint:[]string{"/entrypoint.sh"}, + NetworkDisabled:false, + MacAddress:"", + OnBuild:[]string(nil), + Labels:map[string]string{"dev.tilt.ctlptl.role":"registry"}, + StopSignal:"", + StopTimeout:(*int)(nil), + Shell:[]string(nil), + }, }, nil } }