Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ type WorkspaceConfig struct {
// ProjectCloneConfig defines configuration related to the project clone init container
// that is used to clone git projects into the DevWorkspace.
ProjectCloneConfig *ProjectCloneConfig `json:"projectClone,omitempty"`
// RestoreConfig defines configuration related to the workspace restore init container
// that is used to restore workspace data from a backup image.
RestoreConfig *RestoreConfig `json:"restore,omitempty"`
// ImagePullPolicy defines the imagePullPolicy used for containers in a DevWorkspace
// For additional information, see Kubernetes documentation for imagePullPolicy. If
// not specified, the default value of "Always" is used.
Expand Down Expand Up @@ -376,6 +379,18 @@ type ProjectCloneConfig struct {
Env []corev1.EnvVar `json:"env,omitempty"`
}

type RestoreConfig struct {
// ImagePullPolicy configures the imagePullPolicy for the restore container.
// If undefined, the general setting .config.workspace.imagePullPolicy is used instead.
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
// quantity as zero ('0')
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
// Env allows defining additional environment variables for the restore container.
Env []corev1.EnvVar `json:"env,omitempty"`
}

type ConfigmapReference struct {
// Name is the name of the configmap
Name string `json:"name"`
Expand Down
32 changes: 32 additions & 0 deletions apis/controller/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 2 additions & 75 deletions controllers/backupcronjob/backupcronjob_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
"github.com/devfile/devworkspace-operator/pkg/library/storage"
"github.com/devfile/devworkspace-operator/pkg/secrets"
"github.com/go-logr/logr"
"github.com/robfig/cron/v3"
batchv1 "k8s.io/api/batch/v1"
Expand Down Expand Up @@ -344,7 +345,7 @@ func (r *BackupCronJobReconciler) createBackupJob(
dwID := workspace.Status.DevWorkspaceId
backUpConfig := dwOperatorConfig.Config.Workspace.BackupCronJob

registryAuthSecret, err := r.handleRegistryAuthSecret(ctx, workspace, dwOperatorConfig, log)
registryAuthSecret, err := secrets.HandleRegistryAuthSecret(ctx, r.Client, workspace, dwOperatorConfig.Config, dwOperatorConfig.Namespace, r.Scheme, log)
if err != nil {
log.Error(err, "Failed to handle registry auth secret for DevWorkspace", "devworkspace", workspace.Name)
return err
Expand Down Expand Up @@ -480,77 +481,3 @@ func (r *BackupCronJobReconciler) createBackupJob(
log.Info("Created backup Job for DevWorkspace", "jobName", job.Name, "devworkspace", workspace.Name)
return nil
}

func (r *BackupCronJobReconciler) handleRegistryAuthSecret(ctx context.Context, workspace *dw.DevWorkspace,
dwOperatorConfig *controllerv1alpha1.DevWorkspaceOperatorConfig, log logr.Logger,
) (*corev1.Secret, error) {
secretName := dwOperatorConfig.Config.Workspace.BackupCronJob.Registry.AuthSecret
if secretName == "" {
// No auth secret configured - anonymous access to registry
return nil, nil
}

// First check the workspace namespace for the secret
registryAuthSecret := &corev1.Secret{}
err := r.Get(ctx, client.ObjectKey{
Name: secretName,
Namespace: workspace.Namespace}, registryAuthSecret)
if err == nil {
log.Info("Successfully retrieved registry auth secret for backup from workspace namespace", "secretName", secretName)
return registryAuthSecret, nil
}
if client.IgnoreNotFound(err) != nil {
return nil, err
}

log.Info("Registry auth secret not found in workspace namespace, checking operator namespace", "secretName", secretName)

// If the secret is not found in the workspace namespace, check the operator namespace as fallback
err = r.Get(ctx, client.ObjectKey{
Name: secretName,
Namespace: dwOperatorConfig.Namespace}, registryAuthSecret)
if err != nil {
log.Error(err, "Failed to get registry auth secret for backup job", "secretName", secretName)
return nil, err
}
log.Info("Successfully retrieved registry auth secret for backup job", "secretName", secretName)
return r.copySecret(ctx, workspace, registryAuthSecret, log)
}

// copySecret copies the given secret from the operator namespace to the workspace namespace.
func (r *BackupCronJobReconciler) copySecret(ctx context.Context, workspace *dw.DevWorkspace, sourceSecret *corev1.Secret, log logr.Logger) (namespaceSecret *corev1.Secret, err error) {
existingNamespaceSecret := &corev1.Secret{}
err = r.Get(ctx, client.ObjectKey{
Name: constants.DevWorkspaceBackupAuthSecretName,
Namespace: workspace.Namespace}, existingNamespaceSecret)
if client.IgnoreNotFound(err) != nil {
log.Error(err, "Failed to check for existing registry auth secret in workspace namespace", "namespace", workspace.Namespace)
return nil, err
}
if err == nil {
err = r.Delete(ctx, existingNamespaceSecret)
if err != nil {
return nil, err
}
}
namespaceSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DevWorkspaceBackupAuthSecretName,
Namespace: workspace.Namespace,
Labels: map[string]string{
constants.DevWorkspaceIDLabel: workspace.Status.DevWorkspaceId,
constants.DevWorkspaceWatchSecretLabel: "true",
},
},
Data: sourceSecret.Data,
Type: sourceSecret.Type,
}
if err := controllerutil.SetControllerReference(workspace, namespaceSecret, r.Scheme); err != nil {
return nil, err
}
err = r.Create(ctx, namespaceSecret)
if err == nil {
log.Info("Successfully created secret", "name", namespaceSecret.Name, "namespace", workspace.Namespace)
}
return namespaceSecret, err
}
3 changes: 0 additions & 3 deletions controllers/backupcronjob/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,6 @@ func (r *BackupCronJobReconciler) ensureImageStreamForBackup(ctx context.Context
},
},
}
if err := controllerutil.SetControllerReference(workspace, imageStream, r.Scheme); err != nil {
return err
}

imageStream.SetGroupVersionKind(schema.GroupVersionKind{
Group: "image.openshift.io",
Expand Down
59 changes: 45 additions & 14 deletions controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/devfile/devworkspace-operator/pkg/library/home"
kubesync "github.com/devfile/devworkspace-operator/pkg/library/kubernetes"
"github.com/devfile/devworkspace-operator/pkg/library/projects"
"github.com/devfile/devworkspace-operator/pkg/library/restore"
"github.com/devfile/devworkspace-operator/pkg/library/status"
"github.com/devfile/devworkspace-operator/pkg/provision/automount"
"github.com/devfile/devworkspace-operator/pkg/provision/metadata"
Expand Down Expand Up @@ -353,21 +354,51 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
if err := projects.ValidateAllProjects(&workspace.Spec.Template); err != nil {
return r.failWorkspace(workspace, fmt.Sprintf("Invalid devfile: %s", err), metrics.ReasonBadRequest, reqLogger, &reconcileStatus), nil
}
// Add init container to clone projects
projectCloneOptions := projects.Options{
Image: workspace.Config.Workspace.ProjectCloneConfig.Image,
Env: env.GetEnvironmentVariablesForProjectClone(workspace),
Resources: workspace.Config.Workspace.ProjectCloneConfig.Resources,
}
if workspace.Config.Workspace.ProjectCloneConfig.ImagePullPolicy != "" {
projectCloneOptions.PullPolicy = config.Workspace.ProjectCloneConfig.ImagePullPolicy
if restore.IsWorkspaceRestoreRequested(&workspace.Spec.Template) {
// Add init container to restore workspace from backup if requested
restoreOptions := restore.Options{
Env: env.GetEnvironmentVariablesForProjectRestore(workspace),
Resources: workspace.Config.Workspace.RestoreConfig.Resources,
}
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
} else if workspaceRestore != nil {
devfilePodAdditions.InitContainers = append([]corev1.Container{*workspaceRestore}, devfilePodAdditions.InitContainers...)
if registryAuthSecret != nil {
// Add the registry auth secret volume
devfilePodAdditions.Volumes = append(devfilePodAdditions.Volumes, corev1.Volume{
Name: "registry-auth-secret",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: registryAuthSecret.Name, // You may want to make this configurable
},
},
})
}

}
} else {
projectCloneOptions.PullPolicy = corev1.PullPolicy(config.Workspace.ImagePullPolicy)
}
if projectClone, err := projects.GetProjectCloneInitContainer(&workspace.Spec.Template, projectCloneOptions, workspace.Config.Routing.ProxyConfig); err != nil {
return r.failWorkspace(workspace, fmt.Sprintf("Failed to set up project-clone init container: %s", err), metrics.ReasonInfrastructureFailure, reqLogger, &reconcileStatus), nil
} else if projectClone != nil {
devfilePodAdditions.InitContainers = append([]corev1.Container{*projectClone}, devfilePodAdditions.InitContainers...)
// Add init container to clone projects only if restore container wasn't created
projectCloneOptions := projects.Options{
Image: workspace.Config.Workspace.ProjectCloneConfig.Image,
Env: env.GetEnvironmentVariablesForProjectClone(workspace),
Resources: workspace.Config.Workspace.ProjectCloneConfig.Resources,
}
if workspace.Config.Workspace.ProjectCloneConfig.ImagePullPolicy != "" {
projectCloneOptions.PullPolicy = config.Workspace.ProjectCloneConfig.ImagePullPolicy
} else {
projectCloneOptions.PullPolicy = corev1.PullPolicy(config.Workspace.ImagePullPolicy)
}
if projectClone, err := projects.GetProjectCloneInitContainer(&workspace.Spec.Template, projectCloneOptions, workspace.Config.Routing.ProxyConfig); err != nil {
return r.failWorkspace(workspace, fmt.Sprintf("Failed to set up project-clone init container: %s", err), metrics.ReasonInfrastructureFailure, reqLogger, &reconcileStatus), nil
} else if projectClone != nil {
devfilePodAdditions.InitContainers = append([]corev1.Container{*projectClone}, devfilePodAdditions.InitContainers...)
}
}

// Inject operator-configured init containers
Expand Down
Loading
Loading