diff --git a/apis/v1alpha1/configspec.go b/apis/v1alpha1/configspec.go index 7bf59b6..d01a20c 100644 --- a/apis/v1alpha1/configspec.go +++ b/apis/v1alpha1/configspec.go @@ -115,4 +115,11 @@ type ConfigImagePull struct { // /etc/docker and docker will be expecting the config at /etc/docker/daemon.json. // +optional DockerDaemonConfig string `json:"dockerDaemonConfig,omitempty"` + // DockerConfig allows for setting the docker user (for root) config for all launchers in this + // topology. The secret *must be present in the namespace of this topology*. The secret *must* + // contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + // and as such wil be utilized when doing docker-y things -- this means you can put auth things + // in here in the event your cluster doesn't support the preferred image pull through option. + // +optional + DockerConfig string `json:"dockerConfig,omitempty"` } diff --git a/apis/v1alpha1/topologyspec.go b/apis/v1alpha1/topologyspec.go index 77bd072..85c5c5e 100644 --- a/apis/v1alpha1/topologyspec.go +++ b/apis/v1alpha1/topologyspec.go @@ -223,4 +223,11 @@ type ImagePull struct { // be expecting the config at /etc/docker/daemon.json. // +optional DockerDaemonConfig string `json:"dockerDaemonConfig,omitempty"` + // DockerConfig allows for setting the docker user (for root) config for all launchers in this + // topology. The secret *must be present in the namespace of this topology*. The secret *must* + // contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + // and as such wil be utilized when doing docker-y things -- this means you can put auth things + // in here in the event your cluster doesn't support the preferred image pull through option. + // +optional + DockerConfig string `json:"dockerConfig,omitempty"` } diff --git a/assets/crd/clabernetes.containerlab.dev_configs.yaml b/assets/crd/clabernetes.containerlab.dev_configs.yaml index 46a7470..bef2622 100644 --- a/assets/crd/clabernetes.containerlab.dev_configs.yaml +++ b/assets/crd/clabernetes.containerlab.dev_configs.yaml @@ -262,6 +262,14 @@ spec: now, in the future maybe crio support will be added. pattern: (.*containerd\.sock) type: string + dockerConfig: + description: |- + DockerConfig allows for setting the docker user (for root) config for all launchers in this + topology. The secret *must be present in the namespace of this topology*. The secret *must* + contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + and as such wil be utilized when doing docker-y things -- this means you can put auth things + in here in the event your cluster doesn't support the preferred image pull through option. + type: string dockerDaemonConfig: description: |- DockerDaemonConfig allows for setting a default docker daemon config for launcher pods diff --git a/assets/crd/clabernetes.containerlab.dev_topologies.yaml b/assets/crd/clabernetes.containerlab.dev_topologies.yaml index 37f191e..784b72f 100644 --- a/assets/crd/clabernetes.containerlab.dev_topologies.yaml +++ b/assets/crd/clabernetes.containerlab.dev_topologies.yaml @@ -398,6 +398,14 @@ spec: ImagePull holds configurations relevant to how clabernetes launcher pods handle pulling images. properties: + dockerConfig: + description: |- + DockerConfig allows for setting the docker user (for root) config for all launchers in this + topology. The secret *must be present in the namespace of this topology*. The secret *must* + contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + and as such wil be utilized when doing docker-y things -- this means you can put auth things + in here in the event your cluster doesn't support the preferred image pull through option. + type: string dockerDaemonConfig: description: |- DockerDaemonConfig allows for setting the docker daemon config for all launchers in this diff --git a/charts/clabernetes/crds/clabernetes.containerlab.dev_configs.yaml b/charts/clabernetes/crds/clabernetes.containerlab.dev_configs.yaml index 46a7470..bef2622 100644 --- a/charts/clabernetes/crds/clabernetes.containerlab.dev_configs.yaml +++ b/charts/clabernetes/crds/clabernetes.containerlab.dev_configs.yaml @@ -262,6 +262,14 @@ spec: now, in the future maybe crio support will be added. pattern: (.*containerd\.sock) type: string + dockerConfig: + description: |- + DockerConfig allows for setting the docker user (for root) config for all launchers in this + topology. The secret *must be present in the namespace of this topology*. The secret *must* + contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + and as such wil be utilized when doing docker-y things -- this means you can put auth things + in here in the event your cluster doesn't support the preferred image pull through option. + type: string dockerDaemonConfig: description: |- DockerDaemonConfig allows for setting a default docker daemon config for launcher pods diff --git a/charts/clabernetes/crds/clabernetes.containerlab.dev_topologies.yaml b/charts/clabernetes/crds/clabernetes.containerlab.dev_topologies.yaml index 37f191e..784b72f 100644 --- a/charts/clabernetes/crds/clabernetes.containerlab.dev_topologies.yaml +++ b/charts/clabernetes/crds/clabernetes.containerlab.dev_topologies.yaml @@ -398,6 +398,14 @@ spec: ImagePull holds configurations relevant to how clabernetes launcher pods handle pulling images. properties: + dockerConfig: + description: |- + DockerConfig allows for setting the docker user (for root) config for all launchers in this + topology. The secret *must be present in the namespace of this topology*. The secret *must* + contain a key "config.json" -- as this secret will be mounted to /root/.docker/config.json + and as such wil be utilized when doing docker-y things -- this means you can put auth things + in here in the event your cluster doesn't support the preferred image pull through option. + type: string dockerDaemonConfig: description: |- DockerDaemonConfig allows for setting the docker daemon config for all launchers in this diff --git a/config/fake.go b/config/fake.go index a23e2b1..66fe34c 100644 --- a/config/fake.go +++ b/config/fake.go @@ -74,6 +74,10 @@ func (f fakeManager) GetDockerDaemonConfig() string { return "" } +func (f fakeManager) GetDockerConfig() string { + return "" +} + func (f fakeManager) GetLauncherImagePullPolicy() string { return clabernetesconstants.KubernetesImagePullIfNotPresent } diff --git a/config/get.go b/config/get.go index 53e69a3..dfbfd56 100644 --- a/config/get.go +++ b/config/get.go @@ -121,6 +121,13 @@ func (m *manager) GetDockerDaemonConfig() string { return m.config.ImagePull.DockerDaemonConfig } +func (m *manager) GetDockerConfig() string { + m.lock.RLock() + defer m.lock.RUnlock() + + return m.config.ImagePull.DockerConfig +} + func (m *manager) GetLauncherImage() string { m.lock.RLock() defer m.lock.RUnlock() diff --git a/config/manager.go b/config/manager.go index a524f81..7260b19 100644 --- a/config/manager.go +++ b/config/manager.go @@ -133,8 +133,12 @@ type Manager interface { GetImagePullCriSockOverride() string // GetImagePullCriKindOverride returns the cri kind override. GetImagePullCriKindOverride() string - // GetDockerDaemonConfig returns the secret name to mount in /etc/docker. + // GetDockerDaemonConfig returns the secret name to mount in /etc/docker -- the secret *must* + // have a key "daemon.json" so the final mounted file is /etc/docker/daemon.json. GetDockerDaemonConfig() string + // GetDockerConfig returns the secret name to mount in /root/.docker/ -- the secret *must* have + // a key "config.json" so the final mounted file is /root/.docker/config.json. + GetDockerConfig() string // GetLauncherImage returns the global default launcher image. GetLauncherImage() string // GetLauncherImagePullPolicy returns the global default launcher image pull policy. diff --git a/controllers/topology/deployment.go b/controllers/topology/deployment.go index ef52ff8..3041013 100644 --- a/controllers/topology/deployment.go +++ b/controllers/topology/deployment.go @@ -258,6 +258,37 @@ func (r *DeploymentReconciler) renderDeploymentVolumes( //nolint:funlen ) } + dockerConfigSecret := owningTopology.Spec.ImagePull.DockerConfig + if dockerConfigSecret == "" { + dockerConfigSecret = r.configManagerGetter().GetDockerConfig() + } + + if dockerConfigSecret != "" { + volumes = append( + volumes, + k8scorev1.Volume{ + Name: "docker-config", + VolumeSource: k8scorev1.VolumeSource{ + Secret: &k8scorev1.SecretVolumeSource{ + SecretName: dockerConfigSecret, + DefaultMode: clabernetesutil.ToPointer( + int32(clabernetesconstants.PermissionsEveryoneReadWriteOwnerExecute), + ), + }, + }, + }, + ) + + volumeMountsFromCommonSpec = append( + volumeMountsFromCommonSpec, + k8scorev1.VolumeMount{ + Name: "docker-config", + ReadOnly: true, + MountPath: "/root/.docker", + }, + ) + } + volumesFromConfigMaps := make([]clabernetesapisv1alpha1.FileFromConfigMap, 0) volumesFromConfigMaps = append( diff --git a/controllers/topology/deployment_test.go b/controllers/topology/deployment_test.go index 231e266..07b88dd 100644 --- a/controllers/topology/deployment_test.go +++ b/controllers/topology/deployment_test.go @@ -499,6 +499,52 @@ func TestRenderDeployment(t *testing.T) { }, nodeName: "srl1", }, + { + name: "docker-config", + owningTopology: &clabernetesapisv1alpha1.Topology{ + ObjectMeta: metav1.ObjectMeta{ + Name: "render-deployment-test", + Namespace: "clabernetes", + }, + Spec: clabernetesapisv1alpha1.TopologySpec{ + Connectivity: clabernetesconstants.ConnectivityVXLAN, + ImagePull: clabernetesapisv1alpha1.ImagePull{ + DockerConfig: "sneakydockerconfig", + }, + Definition: clabernetesapisv1alpha1.Definition{ + Containerlab: `--- + name: test + topology: + nodes: + srl1: + kind: srl + image: ghcr.io/nokia/srlinux + `, + }, + }, + }, + clabernetesConfigs: map[string]*clabernetesutilcontainerlab.Config{ + "srl1": { + Name: "srl1", + Prefix: clabernetesutil.ToPointer(""), + Topology: &clabernetesutilcontainerlab.Topology{ + Defaults: &clabernetesutilcontainerlab.NodeDefinition{ + Ports: []string{}, + }, + Kinds: nil, + Nodes: map[string]*clabernetesutilcontainerlab.NodeDefinition{ + "srl1": { + Kind: "srl", + Image: "ghcr.io/nokia/srlinux", + }, + }, + Links: nil, + }, + Debug: false, + }, + }, + nodeName: "srl1", + }, { name: "scheduling", owningTopology: &clabernetesapisv1alpha1.Topology{ diff --git a/controllers/topology/test-fixtures/golden/deployment/render-deployment/docker-config.json b/controllers/topology/test-fixtures/golden/deployment/render-deployment/docker-config.json new file mode 100755 index 0000000..b5a8a52 --- /dev/null +++ b/controllers/topology/test-fixtures/golden/deployment/render-deployment/docker-config.json @@ -0,0 +1,194 @@ +{ + "metadata": { + "name": "render-deployment-test-srl1", + "namespace": "clabernetes", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/name": "render-deployment-test-srl1", + "clabernetes/app": "clabernetes", + "clabernetes/name": "render-deployment-test-srl1", + "clabernetes/topologyNode": "srl1", + "clabernetes/topologyOwner": "render-deployment-test" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "render-deployment-test-srl1", + "clabernetes/app": "clabernetes", + "clabernetes/name": "render-deployment-test-srl1", + "clabernetes/topologyNode": "srl1", + "clabernetes/topologyOwner": "render-deployment-test" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/name": "render-deployment-test-srl1", + "clabernetes/app": "clabernetes", + "clabernetes/name": "render-deployment-test-srl1", + "clabernetes/topologyNode": "srl1", + "clabernetes/topologyOwner": "render-deployment-test" + } + }, + "spec": { + "volumes": [ + { + "name": "render-deployment-test-config", + "configMap": { + "name": "render-deployment-test", + "defaultMode": 493 + } + }, + { + "name": "docker-config", + "secret": { + "secretName": "sneakydockerconfig", + "defaultMode": 493 + } + } + ], + "containers": [ + { + "name": "srl1", + "image": "ghcr.io/srl-labs/clabernetes/clabernetes-launcher:latest", + "command": [ + "/clabernetes/manager", + "launch" + ], + "workingDir": "/clabernetes", + "ports": [ + { + "name": "vxlan", + "containerPort": 14789, + "protocol": "UDP" + }, + { + "name": "slurpeeth", + "containerPort": 4799, + "protocol": "TCP" + } + ], + "env": [ + { + "name": "NODE_NAME", + "valueFrom": { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "spec.nodeName" + } + } + }, + { + "name": "POD_NAME", + "valueFrom": { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.name" + } + } + }, + { + "name": "POD_NAMESPACE", + "valueFrom": { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } + }, + { + "name": "APP_NAME", + "value": "clabernetes" + }, + { + "name": "MANAGER_NAMESPACE", + "value": "clabernetes" + }, + { + "name": "LAUNCHER_CRI_KIND" + }, + { + "name": "LAUNCHER_IMAGE_PULL_THROUGH_MODE", + "value": "auto" + }, + { + "name": "LAUNCHER_LOGGER_LEVEL", + "value": "info" + }, + { + "name": "LAUNCHER_TOPOLOGY_NAME", + "value": "render-deployment-test" + }, + { + "name": "LAUNCHER_NODE_NAME", + "value": "srl1" + }, + { + "name": "LAUNCHER_NODE_IMAGE", + "value": "ghcr.io/nokia/srlinux" + }, + { + "name": "LAUNCHER_CONNECTIVITY_KIND", + "value": "vxlan" + }, + { + "name": "LAUNCHER_CONTAINERLAB_VERSION" + }, + { + "name": "LAUNCHER_CONTAINERLAB_TIMEOUT" + }, + { + "name": "LAUNCHER_PRIVILEGED", + "value": "true" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "render-deployment-test-config", + "readOnly": true, + "mountPath": "/clabernetes/topo.clab.yaml", + "subPath": "srl1" + }, + { + "name": "render-deployment-test-config", + "readOnly": true, + "mountPath": "/clabernetes/files-from-url.yaml", + "subPath": "srl1-files-from-url" + }, + { + "name": "render-deployment-test-config", + "readOnly": true, + "mountPath": "/clabernetes/configured-pull-secrets.yaml", + "subPath": "configured-pull-secrets" + }, + { + "name": "docker-config", + "readOnly": true, + "mountPath": "/root/.docker" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent", + "securityContext": { + "privileged": true, + "runAsUser": 0 + } + } + ], + "restartPolicy": "Always", + "serviceAccountName": "clabernetes-launcher-service-account", + "hostname": "srl1" + } + }, + "strategy": { + "type": "Recreate" + }, + "revisionHistoryLimit": 0 + }, + "status": {} +} \ No newline at end of file diff --git a/generated/openapi/openapi_generated.go b/generated/openapi/openapi_generated.go index b8a0dff..2e65c65 100644 --- a/generated/openapi/openapi_generated.go +++ b/generated/openapi/openapi_generated.go @@ -316,6 +316,13 @@ func schema_srl_labs_clabernetes_apis_v1alpha1_ConfigImagePull( Format: "", }, }, + "dockerConfig": { + SchemaProps: spec.SchemaProps{ + Description: "DockerConfig allows for setting the docker user (for root) config for all launchers in this topology. The secret *must be present in the namespace of this topology*. The secret *must* contain a key \"config.json\" -- as this secret will be mounted to /root/.docker/config.json and as such wil be utilized when doing docker-y things -- this means you can put auth things in here in the event your cluster doesn't support the preferred image pull through option.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -1064,6 +1071,13 @@ func schema_srl_labs_clabernetes_apis_v1alpha1_ImagePull( Format: "", }, }, + "dockerConfig": { + SchemaProps: spec.SchemaProps{ + Description: "DockerConfig allows for setting the docker user (for root) config for all launchers in this topology. The secret *must be present in the namespace of this topology*. The secret *must* contain a key \"config.json\" -- as this secret will be mounted to /root/.docker/config.json and as such wil be utilized when doing docker-y things -- this means you can put auth things in here in the event your cluster doesn't support the preferred image pull through option.", + Type: []string{"string"}, + Format: "", + }, + }, }, }, },