Skip to content

Commit d9533a7

Browse files
committed
feat: Allow using registry auth for private registies
The backup job can now push to registries which requires auth token. The token is provided as a secret in operator namespace and added to the operator config. Signed-off-by: Ales Raszka <[email protected]>
1 parent 1765b4c commit d9533a7

16 files changed

+236
-19
lines changed

apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ type BackupCronJobConfig struct {
8686
// in {registry}/backup-${DEVWORKSPACE_NAMESPACE}-${DEVWORKSPACE_NAME}
8787
// +kubebuilder:validation:Optional
8888
Registry string `json:"registry,omitempty"`
89+
90+
// RegistryAuthSecret is the name of a Kubernetes secret of
91+
// type kubernetes.io/dockerconfigjson
92+
// +kubebuilder:validation:Optional
93+
RegistryAuthSecret string `json:"registryAuthSecret,omitempty"`
8994
}
9095

9196
type RoutingConfig struct {

controllers/backupcronjob/backupcronjob_controller.go

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ const (
4848
// BackupCronJobReconciler reconciles `BackupCronJob` configuration for the purpose of backing up workspace PVCs.
4949
type BackupCronJobReconciler struct {
5050
client.Client
51-
Log logr.Logger
52-
Scheme *runtime.Scheme
51+
NonCachingClient client.Client
52+
Log logr.Logger
53+
Scheme *runtime.Scheme
5354

5455
cron *cron.Cron
5556
}
@@ -94,6 +95,12 @@ func shouldReconcileOnUpdate(e event.UpdateEvent, log logr.Logger) bool {
9495
if oldBackup.Schedule != newBackup.Schedule {
9596
return true
9697
}
98+
if oldBackup.Registry != newBackup.Registry {
99+
return true
100+
}
101+
if oldBackup.RegistryAuthSecret != newBackup.RegistryAuthSecret {
102+
return true
103+
}
97104

98105
return false
99106
}
@@ -139,6 +146,7 @@ func (r *BackupCronJobReconciler) SetupWithManager(mgr ctrl.Manager) error {
139146
Complete(r)
140147
}
141148

149+
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;create;update;patch;delete
142150
// +kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list
143151
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete
144152
// +kubebuilder:rbac:groups=controller.devfile.io,resources=devworkspaceoperatorconfigs,verbs=get;list;update;patch;watch
@@ -235,8 +243,14 @@ func (r *BackupCronJobReconciler) stopCron(logger logr.Logger) {
235243
func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, dwOperatorConfig *controllerv1alpha1.DevWorkspaceOperatorConfig, logger logr.Logger) error {
236244
log := logger.WithName("executeBackupSync")
237245
log.Info("Executing backup sync for all DevWorkspaces")
246+
247+
registyAuthSecret, err := r.getRegistryAuthSecret(ctx, dwOperatorConfig, logger)
248+
if err != nil {
249+
log.Error(err, "Failed to get registry auth secret for backup job")
250+
return err
251+
}
238252
devWorkspaces := &dw.DevWorkspaceList{}
239-
err := r.List(ctx, devWorkspaces)
253+
err = r.List(ctx, devWorkspaces)
240254
if err != nil {
241255
log.Error(err, "Failed to list DevWorkspaces")
242256
return err
@@ -253,7 +267,7 @@ func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, dwOpera
253267
dwID := dw.Status.DevWorkspaceId
254268
log.Info("Found DevWorkspace", "namespace", dw.Namespace, "devworkspace", dw.Name, "id", dwID)
255269

256-
if err := r.createBackupJob(&dw, ctx, dwOperatorConfig, logger); err != nil {
270+
if err := r.createBackupJob(&dw, ctx, dwOperatorConfig, registyAuthSecret, logger); err != nil {
257271
log.Error(err, "Failed to create backup Job for DevWorkspace", "id", dwID)
258272
continue
259273
}
@@ -274,6 +288,24 @@ func (r *BackupCronJobReconciler) executeBackupSync(ctx context.Context, dwOpera
274288
return nil
275289
}
276290

291+
func (r *BackupCronJobReconciler) getRegistryAuthSecret(ctx context.Context, dwOperatorConfig *controllerv1alpha1.DevWorkspaceOperatorConfig, logger logr.Logger) (*corev1.Secret, error) {
292+
log := logger.WithName("getRegistryAuthSecret")
293+
registryAuthSecret := &corev1.Secret{}
294+
if dwOperatorConfig.Config.Workspace.BackupCronJob.RegistryAuthSecret != "" {
295+
err := r.NonCachingClient.Get(ctx, client.ObjectKey{
296+
Name: dwOperatorConfig.Config.Workspace.BackupCronJob.RegistryAuthSecret,
297+
Namespace: dwOperatorConfig.Namespace,
298+
}, registryAuthSecret)
299+
if err != nil {
300+
log.Error(err, "Failed to get registry auth secret for backup job", "secretName", dwOperatorConfig.Config.Workspace.BackupCronJob.RegistryAuthSecret)
301+
return nil, err
302+
}
303+
log.Info("Successfully retrieved registry auth secret for backup job", "secretName", dwOperatorConfig.Config.Workspace.BackupCronJob.RegistryAuthSecret)
304+
return registryAuthSecret, nil
305+
}
306+
return nil, nil
307+
}
308+
277309
// wasStoppedSinceLastBackup checks if the DevWorkspace was stopped since the last backup time.
278310
func (r *BackupCronJobReconciler) wasStoppedSinceLastBackup(workspace *dw.DevWorkspace, lastBackupTime *metav1.Time, logger logr.Logger) bool {
279311
log := logger.WithName("wasStoppedSinceLastBackup")
@@ -308,7 +340,13 @@ func ptrInt32(i int32) *int32 { return &i }
308340
func ptrBool(b bool) *bool { return &b }
309341

310342
// createBackupJob creates a Kubernetes Job to back up the workspace's PVC data.
311-
func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ctx context.Context, dwOperatorConfig *controllerv1alpha1.DevWorkspaceOperatorConfig, logger logr.Logger) error {
343+
func (r *BackupCronJobReconciler) createBackupJob(
344+
workspace *dw.DevWorkspace,
345+
ctx context.Context,
346+
dwOperatorConfig *controllerv1alpha1.DevWorkspaceOperatorConfig,
347+
registyAuthSecret *corev1.Secret,
348+
logger logr.Logger,
349+
) error {
312350
log := logger.WithName("createBackupJob")
313351
dwID := workspace.Status.DevWorkspaceId
314352
backUpConfig := dwOperatorConfig.Config.Workspace.BackupCronJob
@@ -358,7 +396,6 @@ func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ct
358396
{Name: "BUILDAH_PUSH_OPTIONS", Value: "--tls-verify=false"},
359397
},
360398
Image: images.GetProjectBackupImage(),
361-
// Image: "localhost:5001/workspace-backup-image:v3",
362399
Args: []string{
363400
"/workspace-recovery.sh",
364401
"--backup",
@@ -402,6 +439,30 @@ func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ct
402439
},
403440
},
404441
}
442+
if registyAuthSecret != nil {
443+
secret, err := r.copySecret(workspace, ctx, registyAuthSecret, logger)
444+
if err != nil {
445+
return err
446+
}
447+
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{
448+
Name: "registry-auth-secret",
449+
VolumeSource: corev1.VolumeSource{
450+
Secret: &corev1.SecretVolumeSource{
451+
SecretName: secret.Name,
452+
},
453+
},
454+
})
455+
job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
456+
Name: "registry-auth-secret",
457+
MountPath: "/home/user/.docker",
458+
ReadOnly: true,
459+
})
460+
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
461+
Name: "REGISTRY_AUTH_FILE",
462+
Value: "/home/user/.docker/.dockerconfigjson",
463+
})
464+
465+
}
405466
if err := controllerutil.SetControllerReference(workspace, job, r.Scheme); err != nil {
406467
return err
407468
}
@@ -413,3 +474,40 @@ func (r *BackupCronJobReconciler) createBackupJob(workspace *dw.DevWorkspace, ct
413474
log.Info("Created backup Job for DevWorkspace", "jobName", job.Name, "devworkspace", workspace.Name)
414475
return nil
415476
}
477+
478+
func (r *BackupCronJobReconciler) copySecret(workspace *dw.DevWorkspace, ctx context.Context, sourceSecret *corev1.Secret, logger logr.Logger) (namespaceSecret *corev1.Secret, err error) {
479+
log := logger.WithName("copySecret")
480+
existingNamespaceSecret := &corev1.Secret{}
481+
err = r.NonCachingClient.Get(ctx, client.ObjectKey{
482+
Name: constants.DevWorkspaceBackuptAuthSecretName,
483+
Namespace: workspace.Namespace}, existingNamespaceSecret)
484+
if client.IgnoreNotFound(err) != nil {
485+
log.Error(err, "Failed to check for existing registry auth secret in workspace namespace", "namespace", workspace.Namespace)
486+
return nil, err
487+
}
488+
if err == nil {
489+
log.Info("Deleting existing registry auth secret in workspace namespace", "namespace", workspace.Namespace)
490+
err = r.Delete(ctx, existingNamespaceSecret)
491+
if err != nil {
492+
return nil, err
493+
}
494+
log.Info("Successfully deleted existing registry auth secret in workspace namespace", "namespace", workspace.Namespace)
495+
}
496+
namespaceSecret = &corev1.Secret{
497+
ObjectMeta: metav1.ObjectMeta{
498+
Name: constants.DevWorkspaceBackuptAuthSecretName,
499+
Namespace: workspace.Namespace,
500+
Labels: map[string]string{
501+
constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId,
502+
constants.DevWorkspaceWatchSecretLabel: "true",
503+
},
504+
},
505+
Data: sourceSecret.Data,
506+
Type: sourceSecret.Type,
507+
}
508+
if err := controllerutil.SetControllerReference(workspace, namespaceSecret, r.Scheme); err != nil {
509+
return nil, err
510+
}
511+
err = r.Create(ctx, namespaceSecret)
512+
return namespaceSecret, err
513+
}

controllers/backupcronjob/backupcronjob_controller_test.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ var _ = Describe("BackupCronJobReconciler", func() {
6060
log = zap.New(zap.UseDevMode(true)).WithName("BackupCronJobReconcilerTest")
6161

6262
reconciler = BackupCronJobReconciler{
63-
Client: fakeClient,
64-
Log: log,
65-
Scheme: scheme,
66-
cron: cron.New(),
63+
Client: fakeClient,
64+
NonCachingClient: fakeClient,
65+
Log: log,
66+
Scheme: scheme,
67+
cron: cron.New(),
6768
}
6869

6970
nameNamespace = types.NamespacedName{
@@ -298,6 +299,40 @@ var _ = Describe("BackupCronJobReconciler", func() {
298299
Expect(fakeClient.List(ctx, jobList, &client.ListOptions{Namespace: dw.Namespace})).To(Succeed())
299300
Expect(jobList.Items).To(HaveLen(0))
300301
})
302+
303+
It("creates a Job for a DevWorkspace stopped with no previsou backup and auth registry", func() {
304+
enabled := true
305+
schedule := "* * * * *"
306+
dwoc := &controllerv1alpha1.DevWorkspaceOperatorConfig{
307+
ObjectMeta: metav1.ObjectMeta{Name: nameNamespace.Name, Namespace: nameNamespace.Namespace},
308+
Config: &controllerv1alpha1.OperatorConfiguration{
309+
Workspace: &controllerv1alpha1.WorkspaceConfig{
310+
BackupCronJob: &controllerv1alpha1.BackupCronJobConfig{
311+
Enable: &enabled,
312+
Schedule: schedule,
313+
Registry: "my-registry:5000",
314+
RegistryAuthSecret: "my-secret",
315+
},
316+
},
317+
},
318+
}
319+
dw := createDevWorkspace("dw-recent", "ns-a", false, metav1.NewTime(time.Now().Add(-10*time.Minute)))
320+
dw.Status.Phase = dwv2.DevWorkspaceStatusStopped
321+
dw.Status.DevWorkspaceId = "id-recent"
322+
Expect(fakeClient.Create(ctx, dw)).To(Succeed())
323+
324+
pvc := &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-devworkspace", Namespace: dw.Namespace}}
325+
Expect(fakeClient.Create(ctx, pvc)).To(Succeed())
326+
327+
authSecret := createAuthSecret("my-secret", nameNamespace.Namespace, map[string][]byte{})
328+
Expect(fakeClient.Create(ctx, authSecret)).To(Succeed())
329+
330+
Expect(reconciler.executeBackupSync(ctx, dwoc, log)).To(Succeed())
331+
332+
jobList := &batchv1.JobList{}
333+
Expect(fakeClient.List(ctx, jobList, &client.ListOptions{Namespace: dw.Namespace})).To(Succeed())
334+
Expect(jobList.Items).To(HaveLen(1))
335+
})
301336
})
302337

303338
})
@@ -334,6 +369,18 @@ func createDevWorkspace(name, namespace string, started bool, lastTransitionTime
334369
return dw
335370
}
336371

372+
func createAuthSecret(name, namespace string, data map[string][]byte) *corev1.Secret {
373+
{
374+
return &corev1.Secret{
375+
ObjectMeta: metav1.ObjectMeta{
376+
Name: name,
377+
Namespace: namespace,
378+
},
379+
Data: data,
380+
}
381+
}
382+
}
383+
337384
var _ = Describe("DevWorkspaceOperatorConfig UpdateFunc Tests", func() {
338385
var configPredicate predicate.Funcs
339386

deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/combined.yaml

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/deployment/openshift/combined.yaml

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)