From aeee8f62409717448942bba03873e8556eb9b9a7 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 12 Aug 2026 11:25:30 -0400 Subject: [PATCH 1/6] fix: default init container imagePullPolicy to IfNotPresent Assisted-by: Claude Opus 4.6 Co-Authored-By: Claude Opus 4.6 Signed-off-by: David Kwon --- .../workspace/devworkspace_controller.go | 3 + .../workspace/devworkspace_controller_test.go | 177 ++++++++++++++++++ pkg/config/defaults.go | 1 + pkg/config/sync_test.go | 1 + 4 files changed, 182 insertions(+) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 2685e1213..dd244f396 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -436,6 +436,9 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request continue } } + if container.ImagePullPolicy == "" { + container.ImagePullPolicy = corev1.PullIfNotPresent + } patches = append(patches, container) } diff --git a/controllers/workspace/devworkspace_controller_test.go b/controllers/workspace/devworkspace_controller_test.go index e197d9066..9854a9689 100644 --- a/controllers/workspace/devworkspace_controller_test.go +++ b/controllers/workspace/devworkspace_controller_test.go @@ -1555,6 +1555,183 @@ var _ = Describe("DevWorkspace Controller", func() { }) + Context("Init container imagePullPolicy", func() { + const testURL = "test-url" + + BeforeEach(func() { + workspacecontroller.SetupHttpClientsForTesting(&http.Client{ + Transport: &testutil.TestRoundTripper{ + Data: map[string]testutil.TestResponse{ + fmt.Sprintf("%s/healthz", testURL): { + StatusCode: http.StatusOK, + }, + }, + }, + }) + }) + + AfterEach(func() { + deleteDevWorkspace(devWorkspaceName) + workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient()) + }) + + It("Defaults project-clone imagePullPolicy to IfNotPresent", func() { + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var projectClone *corev1.Container + for i := range deploy.Spec.Template.Spec.InitContainers { + if deploy.Spec.Template.Spec.InitContainers[i].Name == projects.ProjectClonerContainerName { + projectClone = &deploy.Spec.Template.Spec.InitContainers[i] + } + } + Expect(projectClone).NotTo(BeNil(), "project-clone init container should be present") + Expect(projectClone.ImagePullPolicy).To(Equal(corev1.PullIfNotPresent), "project-clone should default to IfNotPresent") + }) + + It("Uses DWOC override for project-clone imagePullPolicy", func() { + config.SetGlobalConfigForTesting(&controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProjectCloneConfig: &controllerv1alpha1.ProjectCloneConfig{ + ImagePullPolicy: corev1.PullAlways, + }, + }, + }) + defer config.SetGlobalConfigForTesting(nil) + + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var projectClone *corev1.Container + for i := range deploy.Spec.Template.Spec.InitContainers { + if deploy.Spec.Template.Spec.InitContainers[i].Name == projects.ProjectClonerContainerName { + projectClone = &deploy.Spec.Template.Spec.InitContainers[i] + } + } + Expect(projectClone).NotTo(BeNil(), "project-clone init container should be present") + Expect(projectClone.ImagePullPolicy).To(Equal(corev1.PullAlways), "project-clone should use DWOC override") + }) + + It("Defaults DWOC init container imagePullPolicy to IfNotPresent", func() { + config.SetGlobalConfigForTesting(&controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "test-init", + Image: "busybox:latest", + Command: []string{"sh", "-c", "echo hello"}, + }, + }, + }, + }) + defer config.SetGlobalConfigForTesting(nil) + + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var testInit *corev1.Container + for i := range deploy.Spec.Template.Spec.InitContainers { + if deploy.Spec.Template.Spec.InitContainers[i].Name == "test-init" { + testInit = &deploy.Spec.Template.Spec.InitContainers[i] + } + } + Expect(testInit).NotTo(BeNil(), "test-init container should be present") + Expect(testInit.ImagePullPolicy).To(Equal(corev1.PullIfNotPresent), "DWOC init container should default to IfNotPresent") + }) + + It("Preserves explicit imagePullPolicy on DWOC init containers", func() { + config.SetGlobalConfigForTesting(&controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + InitContainers: []corev1.Container{ + { + Name: "test-init", + Image: "busybox:latest", + Command: []string{"sh", "-c", "echo hello"}, + ImagePullPolicy: corev1.PullAlways, + }, + }, + }, + }) + defer config.SetGlobalConfigForTesting(nil) + + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var testInit *corev1.Container + for i := range deploy.Spec.Template.Spec.InitContainers { + if deploy.Spec.Template.Spec.InitContainers[i].Name == "test-init" { + testInit = &deploy.Spec.Template.Spec.InitContainers[i] + } + } + Expect(testInit).NotTo(BeNil(), "test-init container should be present") + Expect(testInit.ImagePullPolicy).To(Equal(corev1.PullAlways), "Explicit imagePullPolicy should be preserved") + }) + + It("Keeps workspace container imagePullPolicy as Always", func() { + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var devContainer *corev1.Container + for i := range deploy.Spec.Template.Spec.Containers { + if deploy.Spec.Template.Spec.Containers[i].Name == "web-terminal" { + devContainer = &deploy.Spec.Template.Spec.Containers[i] + } + } + Expect(devContainer).NotTo(BeNil(), "web-terminal container should be present") + Expect(devContainer.ImagePullPolicy).To(Equal(corev1.PullAlways), "Workspace containers should still default to Always") + }) + }) + Context("Edge cases", func() { It("Allows Kubernetes and Container components to share same target port on endpoint", func() { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 1b772e0ed..c202c4b73 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -61,6 +61,7 @@ var defaultConfig = &v1alpha1.OperatorConfiguration{ NetworkPolicy: nil, // Set per-platform in setDefaultNetworkPolicy() DefaultTemplate: nil, ProjectCloneConfig: &v1alpha1.ProjectCloneConfig{ + ImagePullPolicy: corev1.PullIfNotPresent, Resources: &corev1.ResourceRequirements{ Limits: corev1.ResourceList{ corev1.ResourceMemory: resource.MustParse("1Gi"), diff --git a/pkg/config/sync_test.go b/pkg/config/sync_test.go index 0882e52bf..05199936a 100644 --- a/pkg/config/sync_test.go +++ b/pkg/config/sync_test.go @@ -100,6 +100,7 @@ func TestMergesAllFieldsFromClusterConfig(t *testing.T) { func(_ *dw.DevWorkspaceTemplateSpecContent, c fuzz.Continue) {}, // Ensure no empty strings are generated as they cause default values to be used func(s *string, c fuzz.Continue) { *s = "a" + c.RandString() }, + func(p *corev1.PullPolicy, c fuzz.Continue) { *p = corev1.PullPolicy("a" + c.RandString()) }, // The only valid deployment strategies are Recreate and RollingUpdate func(deploymentStrategy *appsv1.DeploymentStrategyType, c fuzz.Continue) { if c.Int()%2 == 0 { From af21498f6a9bb10f28a06c8f42d05b218c42199c Mon Sep 17 00:00:00 2001 From: David Kwon Date: Tue, 8 Sep 2026 13:41:31 -0400 Subject: [PATCH 2/6] Set the default imagePullPolicy after the initcontainer merge Signed-off-by: David Kwon --- .../workspace/devworkspace_controller.go | 8 ++-- .../workspace/devworkspace_controller_test.go | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index dd244f396..717eff1a2 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -436,9 +436,6 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request continue } } - if container.ImagePullPolicy == "" { - container.ImagePullPolicy = corev1.PullIfNotPresent - } patches = append(patches, container) } @@ -448,13 +445,16 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request return r.failWorkspace(workspace, fmt.Sprintf("Failed to merge init containers: %s", err), metrics.ReasonBadRequest, reqLogger, &reconcileStatus), nil } - // Ensure init-persistent-home container has correct fields after merge + // Post-merge fixups on init containers for i := range merged { if merged[i].Name == constants.HomeInitComponentName { if err := home.EnsureHomeInitContainerFields(&merged[i]); err != nil { return r.failWorkspace(workspace, fmt.Sprintf("Failed to configure %s container: %s", constants.HomeInitComponentName, err), metrics.ReasonBadRequest, reqLogger, &reconcileStatus), nil } } + if merged[i].ImagePullPolicy == "" { + merged[i].ImagePullPolicy = corev1.PullIfNotPresent + } } devfilePodAdditions.InitContainers = merged diff --git a/controllers/workspace/devworkspace_controller_test.go b/controllers/workspace/devworkspace_controller_test.go index 9854a9689..a187820f1 100644 --- a/controllers/workspace/devworkspace_controller_test.go +++ b/controllers/workspace/devworkspace_controller_test.go @@ -1707,6 +1707,45 @@ var _ = Describe("DevWorkspace Controller", func() { Expect(testInit.ImagePullPolicy).To(Equal(corev1.PullAlways), "Explicit imagePullPolicy should be preserved") }) + It("Does not override base imagePullPolicy when DWOC init container has no explicit policy", func() { + config.SetGlobalConfigForTesting(&controllerv1alpha1.OperatorConfiguration{ + Workspace: &controllerv1alpha1.WorkspaceConfig{ + ProjectCloneConfig: &controllerv1alpha1.ProjectCloneConfig{ + ImagePullPolicy: corev1.PullAlways, + }, + InitContainers: []corev1.Container{ + { + Name: projects.ProjectClonerContainerName, + Env: []corev1.EnvVar{{Name: "EXTRA_VAR", Value: "injected"}}, + }, + }, + }, + }) + defer config.SetGlobalConfigForTesting(nil) + + createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") + devworkspace := getExistingDevWorkspace(devWorkspaceName) + workspaceID := devworkspace.Status.DevWorkspaceId + + By("Manually making Routing ready to continue") + markRoutingReady(testURL, common.DevWorkspaceRoutingName(workspaceID)) + + deploy := &appsv1.Deployment{} + deployNN := namespacedName(common.DeploymentName(workspaceID), testNamespace) + Eventually(func() error { + return k8sClient.Get(ctx, deployNN, deploy) + }, timeout, interval).Should(Succeed(), "Getting workspace deployment from cluster") + + var projectClone *corev1.Container + for i := range deploy.Spec.Template.Spec.InitContainers { + if deploy.Spec.Template.Spec.InitContainers[i].Name == projects.ProjectClonerContainerName { + projectClone = &deploy.Spec.Template.Spec.InitContainers[i] + } + } + Expect(projectClone).NotTo(BeNil(), "project-clone init container should be present") + Expect(projectClone.ImagePullPolicy).To(Equal(corev1.PullAlways), "DWOC patch should not override base imagePullPolicy") + }) + It("Keeps workspace container imagePullPolicy as Always", func() { createDevWorkspace(devWorkspaceName, "test-devworkspace.yaml") devworkspace := getExistingDevWorkspace(devWorkspaceName) From 8f17034c3d8fb6337fc11fc5a741e238ff9e738d Mon Sep 17 00:00:00 2001 From: David Kwon Date: Mon, 14 Sep 2026 17:57:28 -0400 Subject: [PATCH 3/6] Update the imagepullpolicy logic for restore containers, update pull policy fields' documentation to mention the IfNotPresent defaults Signed-off-by: David Kwon --- .../controller/v1alpha1/devworkspaceoperatorconfig_types.go | 2 ++ controllers/workspace/devworkspace_controller.go | 6 +++--- .../controller.devfile.io_devworkspaceoperatorconfigs.yaml | 2 ++ deploy/deployment/kubernetes/combined.yaml | 2 ++ ...figs.controller.devfile.io.CustomResourceDefinition.yaml | 2 ++ deploy/deployment/openshift/combined.yaml | 2 ++ ...figs.controller.devfile.io.CustomResourceDefinition.yaml | 2 ++ .../controller.devfile.io_devworkspaceoperatorconfigs.yaml | 2 ++ 8 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go b/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go index 61741c34d..35a449753 100644 --- a/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go +++ b/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go @@ -438,6 +438,7 @@ type ProjectCloneConfig struct { Image string `json:"image,omitempty"` // ImagePullPolicy configures the imagePullPolicy for the project clone container. // If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + // If neither is set, defaults to IfNotPresent. ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` // Resources defines the resource (cpu, memory) limits and requests for the project // clone container. To explicitly not specify a limit or request, define the resource @@ -450,6 +451,7 @@ type ProjectCloneConfig struct { type RestoreConfig struct { // ImagePullPolicy configures the imagePullPolicy for the restore container. // If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + // If neither is set, defaults to IfNotPresent. ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` // Resources defines the resource (cpu, memory) limits and requests for the restore // container. To explicitly not specify a limit or request, define the resource diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 717eff1a2..73c7660b4 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -373,10 +373,10 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request Env: env.GetEnvironmentVariablesForProjectRestore(workspace), Resources: workspace.Config.Workspace.RestoreConfig.Resources, } - if config.Workspace.ImagePullPolicy != "" { + if workspace.Config.Workspace.RestoreConfig.ImagePullPolicy != "" { + restoreOptions.PullPolicy = workspace.Config.Workspace.RestoreConfig.ImagePullPolicy + } else if config.Workspace.ImagePullPolicy != "" { restoreOptions.PullPolicy = corev1.PullPolicy(config.Workspace.ImagePullPolicy) - } else { - restoreOptions.PullPolicy = corev1.PullIfNotPresent } if workspaceRestore, registryAuthSecret, err := restore.GetWorkspaceRestoreInitContainer(ctx, workspace, r.Client, restoreOptions, r.Scheme, reqLogger); err != nil { return r.failWorkspace(workspace, fmt.Sprintf("Failed to set up workspace-restore init container: %s", err), metrics.ReasonInfrastructureFailure, reqLogger, &reconcileStatus), nil diff --git a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml index f051c1b23..b259c547e 100644 --- a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml +++ b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml @@ -4915,6 +4915,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5149,6 +5150,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/kubernetes/combined.yaml b/deploy/deployment/kubernetes/combined.yaml index 0ddaa2a60..760c8911c 100644 --- a/deploy/deployment/kubernetes/combined.yaml +++ b/deploy/deployment/kubernetes/combined.yaml @@ -5150,6 +5150,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5396,6 +5397,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml b/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml index cdab553ef..8900116f0 100644 --- a/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml +++ b/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml @@ -5150,6 +5150,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5396,6 +5397,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/openshift/combined.yaml b/deploy/deployment/openshift/combined.yaml index c0ce7c17e..b06decd36 100644 --- a/deploy/deployment/openshift/combined.yaml +++ b/deploy/deployment/openshift/combined.yaml @@ -5150,6 +5150,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5396,6 +5397,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml b/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml index cdab553ef..8900116f0 100644 --- a/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml +++ b/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml @@ -5150,6 +5150,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5396,6 +5397,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml b/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml index a60d57b16..bee6bbff6 100644 --- a/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml +++ b/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml @@ -5148,6 +5148,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5394,6 +5395,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- From f2a5349cca1cb9cce6fade3fd382d207904df39f Mon Sep 17 00:00:00 2001 From: David Kwon Date: Tue, 22 Sep 2026 20:49:31 -0400 Subject: [PATCH 4/6] Update defaults for restore containers Signed-off-by: David Kwon --- apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go | 3 +-- controllers/workspace/devworkspace_controller.go | 4 ++-- .../controller.devfile.io_devworkspaceoperatorconfigs.yaml | 2 -- deploy/deployment/kubernetes/combined.yaml | 3 +-- ...onfigs.controller.devfile.io.CustomResourceDefinition.yaml | 3 +-- deploy/deployment/openshift/combined.yaml | 3 +-- ...onfigs.controller.devfile.io.CustomResourceDefinition.yaml | 3 +-- .../controller.devfile.io_devworkspaceoperatorconfigs.yaml | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go b/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go index 35a449753..0aeba956c 100644 --- a/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go +++ b/apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go @@ -450,8 +450,7 @@ type ProjectCloneConfig struct { type RestoreConfig struct { // ImagePullPolicy configures the imagePullPolicy for the restore container. - // If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - // If neither is set, defaults to IfNotPresent. + // If undefined, defaults to IfNotPresent. ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` // Resources defines the resource (cpu, memory) limits and requests for the restore // container. To explicitly not specify a limit or request, define the resource diff --git a/controllers/workspace/devworkspace_controller.go b/controllers/workspace/devworkspace_controller.go index 73c7660b4..d5a0683f1 100644 --- a/controllers/workspace/devworkspace_controller.go +++ b/controllers/workspace/devworkspace_controller.go @@ -375,8 +375,8 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request } if workspace.Config.Workspace.RestoreConfig.ImagePullPolicy != "" { restoreOptions.PullPolicy = workspace.Config.Workspace.RestoreConfig.ImagePullPolicy - } else if config.Workspace.ImagePullPolicy != "" { - restoreOptions.PullPolicy = corev1.PullPolicy(config.Workspace.ImagePullPolicy) + } else { + restoreOptions.PullPolicy = corev1.PullIfNotPresent } if workspaceRestore, registryAuthSecret, err := restore.GetWorkspaceRestoreInitContainer(ctx, workspace, r.Client, restoreOptions, r.Scheme, reqLogger); err != nil { return r.failWorkspace(workspace, fmt.Sprintf("Failed to set up workspace-restore init container: %s", err), metrics.ReasonInfrastructureFailure, reqLogger, &reconcileStatus), nil diff --git a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml index b259c547e..f051c1b23 100644 --- a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml +++ b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml @@ -4915,7 +4915,6 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5150,7 +5149,6 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/kubernetes/combined.yaml b/deploy/deployment/kubernetes/combined.yaml index 760c8911c..94fb7a812 100644 --- a/deploy/deployment/kubernetes/combined.yaml +++ b/deploy/deployment/kubernetes/combined.yaml @@ -5396,8 +5396,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. + If undefined, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml b/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml index 8900116f0..09e896d0a 100644 --- a/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml +++ b/deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml @@ -5396,8 +5396,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. + If undefined, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/openshift/combined.yaml b/deploy/deployment/openshift/combined.yaml index b06decd36..0969b4094 100644 --- a/deploy/deployment/openshift/combined.yaml +++ b/deploy/deployment/openshift/combined.yaml @@ -5396,8 +5396,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. + If undefined, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml b/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml index 8900116f0..09e896d0a 100644 --- a/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml +++ b/deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml @@ -5396,8 +5396,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. + If undefined, defaults to IfNotPresent. type: string resources: description: |- diff --git a/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml b/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml index bee6bbff6..73b72ffe6 100644 --- a/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml +++ b/deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml @@ -5394,8 +5394,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. - If neither is set, defaults to IfNotPresent. + If undefined, defaults to IfNotPresent. type: string resources: description: |- From 7d1107076dd0fa7dc0251deea5d8782b79ccfd02 Mon Sep 17 00:00:00 2001 From: David Kwon Date: Fri, 25 Sep 2026 15:24:59 -0400 Subject: [PATCH 5/6] Update formatting Signed-off-by: David Kwon --- deploy/templates/components/csv/clusterserviceversion.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/templates/components/csv/clusterserviceversion.yaml b/deploy/templates/components/csv/clusterserviceversion.yaml index db69d53f9..870684bdd 100644 --- a/deploy/templates/components/csv/clusterserviceversion.yaml +++ b/deploy/templates/components/csv/clusterserviceversion.yaml @@ -1,3 +1,4 @@ +--- apiVersion: operators.coreos.com/v1alpha1 kind: ClusterServiceVersion metadata: From 9f234913c341bf750691b37a7f257ad2410ae15b Mon Sep 17 00:00:00 2001 From: David Kwon Date: Wed, 7 Oct 2026 17:15:47 -0400 Subject: [PATCH 6/6] Fix CI failures, update test import and regenerate bundle manifest Assisted-by: Claude Opus 4.6 Signed-off-by: David Kwon --- controllers/workspace/devworkspace_controller_test.go | 4 ++-- .../controller.devfile.io_devworkspaceoperatorconfigs.yaml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/controllers/workspace/devworkspace_controller_test.go b/controllers/workspace/devworkspace_controller_test.go index a187820f1..84738cd48 100644 --- a/controllers/workspace/devworkspace_controller_test.go +++ b/controllers/workspace/devworkspace_controller_test.go @@ -1559,7 +1559,7 @@ var _ = Describe("DevWorkspace Controller", func() { const testURL = "test-url" BeforeEach(func() { - workspacecontroller.SetupHttpClientsForTesting(&http.Client{ + httpfactory.SetupHttpClientsForTesting(&http.Client{ Transport: &testutil.TestRoundTripper{ Data: map[string]testutil.TestResponse{ fmt.Sprintf("%s/healthz", testURL): { @@ -1572,7 +1572,7 @@ var _ = Describe("DevWorkspace Controller", func() { AfterEach(func() { deleteDevWorkspace(devWorkspaceName) - workspacecontroller.SetupHttpClientsForTesting(getBasicTestHttpClient()) + httpfactory.SetupHttpClientsForTesting(getBasicTestHttpClient()) }) It("Defaults project-clone imagePullPolicy to IfNotPresent", func() { diff --git a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml index f051c1b23..ecafdbffa 100644 --- a/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml +++ b/deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml @@ -4915,6 +4915,7 @@ spec: description: |- ImagePullPolicy configures the imagePullPolicy for the project clone container. If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If neither is set, defaults to IfNotPresent. type: string resources: description: |- @@ -5148,7 +5149,7 @@ spec: imagePullPolicy: description: |- ImagePullPolicy configures the imagePullPolicy for the restore container. - If undefined, the general setting .config.workspace.imagePullPolicy is used instead. + If undefined, defaults to IfNotPresent. type: string resources: description: |-