Skip to content

Commit 5413a98

Browse files
snecklifterclaude
andauthored
fix(backup): apply configured podSecurityContext to backup CronJob (#1655)
The backup CronJob controller does not set podSecurityContext on the backup Job pod spec, even though DevWorkspaceOperatorConfig documents that podSecurityContext applies to all workspace-related pods. This can cause permission or SELinux failures when the backup container reads workspace PVC data on clusters with a custom podSecurityContext. Apply the configured podSecurityContext from DevWorkspaceOperatorConfig to the backup Job pod template, consistent with workspace deployments and PVC cleanup jobs. Fixes: #1636 Assisted-by: Claude Code Signed-off-by: Chris Brown <chribrow@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1cd06ee commit 5413a98

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

controllers/backupcronjob/backupcronjob_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ func (r *BackupCronJobReconciler) createBackupJob(
400400
Spec: corev1.PodSpec{
401401
ServiceAccountName: JobRunnerSAName + "-" + workspace.Status.DevWorkspaceId,
402402
RestartPolicy: corev1.RestartPolicyNever,
403+
SecurityContext: dwOperatorConfig.Config.Workspace.PodSecurityContext,
403404
Containers: []corev1.Container{
404405
{
405406
Name: "backup-workspace",

controllers/backupcronjob/backupcronjob_controller_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,80 @@ var _ = Describe("BackupCronJobReconciler", func() {
426426
Expect(*jobList.Items[0].Spec.BackoffLimit).To(Equal(int32(2)))
427427
})
428428

429+
It("creates a Job with configured podSecurityContext", func() {
430+
enabled := true
431+
schedule := "* * * * *"
432+
fsGroupChangeOnRootMismatch := corev1.FSGroupChangeOnRootMismatch
433+
customPodSecurityContext := &corev1.PodSecurityContext{
434+
FSGroupChangePolicy: &fsGroupChangeOnRootMismatch,
435+
SELinuxOptions: &corev1.SELinuxOptions{Type: "spc_t"},
436+
}
437+
dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{
438+
ObjectMeta: metav1.ObjectMeta{Name: nameNamespace.Name, Namespace: nameNamespace.Namespace},
439+
Config: &controllerv1alpha1.OperatorConfiguration{
440+
Workspace: &controllerv1alpha1.WorkspaceConfig{
441+
PodSecurityContext: customPodSecurityContext,
442+
BackupCronJob: &controllerv1alpha1.BackupCronJobConfig{
443+
Enable: &enabled,
444+
Schedule: schedule,
445+
Registry: &controllerv1alpha1.RegistryConfig{
446+
Path: "fake-registry",
447+
},
448+
},
449+
},
450+
},
451+
}
452+
Expect(fakeClient.Create(ctx, dwoc)).To(Succeed())
453+
dw := createDevWorkspace("dw-secctx", "ns-a", false, metav1.NewTime(time.Now().Add(-10*time.Minute)))
454+
dw.Status.Phase = dwv2.DevWorkspaceStatusStopped
455+
dw.Status.DevWorkspaceId = "id-secctx"
456+
Expect(fakeClient.Create(ctx, dw)).To(Succeed())
457+
458+
pvc := &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-devworkspace", Namespace: dw.Namespace}}
459+
Expect(fakeClient.Create(ctx, pvc)).To(Succeed())
460+
461+
Expect(reconciler.executeBackupSync(ctx, dwoc, log)).To(Succeed())
462+
463+
jobList := &batchv1.JobList{}
464+
Expect(fakeClient.List(ctx, jobList, &client.ListOptions{Namespace: dw.Namespace})).To(Succeed())
465+
Expect(jobList.Items).To(HaveLen(1))
466+
Expect(jobList.Items[0].Spec.Template.Spec.SecurityContext).To(Equal(customPodSecurityContext))
467+
})
468+
469+
It("does not set podSecurityContext when not configured", func() {
470+
enabled := true
471+
schedule := "* * * * *"
472+
dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{
473+
ObjectMeta: metav1.ObjectMeta{Name: nameNamespace.Name, Namespace: nameNamespace.Namespace},
474+
Config: &controllerv1alpha1.OperatorConfiguration{
475+
Workspace: &controllerv1alpha1.WorkspaceConfig{
476+
BackupCronJob: &controllerv1alpha1.BackupCronJobConfig{
477+
Enable: &enabled,
478+
Schedule: schedule,
479+
Registry: &controllerv1alpha1.RegistryConfig{
480+
Path: "fake-registry",
481+
},
482+
},
483+
},
484+
},
485+
}
486+
Expect(fakeClient.Create(ctx, dwoc)).To(Succeed())
487+
dw := createDevWorkspace("dw-no-secctx", "ns-a", false, metav1.NewTime(time.Now().Add(-10*time.Minute)))
488+
dw.Status.Phase = dwv2.DevWorkspaceStatusStopped
489+
dw.Status.DevWorkspaceId = "id-no-secctx"
490+
Expect(fakeClient.Create(ctx, dw)).To(Succeed())
491+
492+
pvc := &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-devworkspace", Namespace: dw.Namespace}}
493+
Expect(fakeClient.Create(ctx, pvc)).To(Succeed())
494+
495+
Expect(reconciler.executeBackupSync(ctx, dwoc, log)).To(Succeed())
496+
497+
jobList := &batchv1.JobList{}
498+
Expect(fakeClient.List(ctx, jobList, &client.ListOptions{Namespace: dw.Namespace})).To(Succeed())
499+
Expect(jobList.Items).To(HaveLen(1))
500+
Expect(jobList.Items[0].Spec.Template.Spec.SecurityContext).To(BeNil())
501+
})
502+
429503
It("creates a Job with configured imagePullPolicy", func() {
430504
enabled := true
431505
schedule := "* * * * *"

0 commit comments

Comments
 (0)