From e3e7f5b82ee2cf0c947cf1d3d75a040868c4508b Mon Sep 17 00:00:00 2001 From: Shawn Hurley Date: Tue, 9 Nov 2021 15:30:16 -0500 Subject: [PATCH] Adding Openshift Service Account if shared config enabled --- controllers/bsl.go | 3 + controllers/dpa_controller.go | 4 +- controllers/velero.go | 63 ++++++++- controllers/velero_test.go | 247 +++++++++++++++++++++++++++++++++- 4 files changed, 312 insertions(+), 5 deletions(-) diff --git a/controllers/bsl.go b/controllers/bsl.go index dadf367185..5a828fe5af 100644 --- a/controllers/bsl.go +++ b/controllers/bsl.go @@ -99,6 +99,9 @@ func (r *DPAReconciler) ReconcileBackupStorageLocations(log logr.Logger) (bool, } bsl.Spec.BackupSyncPeriod = bslSpec.Bucket.BackupSyncPeriod bsl.Spec.Config = bslSpec.Bucket.Config + if bucket.Spec.EnableSharedConfig { + bsl.Spec.Config["enableSharedConfig"] = "true" + } bsl.Spec.Credential = bslSpec.Bucket.Credential bsl.Spec.Default = bslSpec.Bucket.Default bsl.Spec.Provider = AWSProvider diff --git a/controllers/dpa_controller.go b/controllers/dpa_controller.go index 336c72c209..f027faee66 100644 --- a/controllers/dpa_controller.go +++ b/controllers/dpa_controller.go @@ -85,8 +85,8 @@ func (r *DPAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R r.ValidateDataProtectionCR, r.ReconcileVeleroSecurityContextConstraint, r.ReconcileResticRestoreHelperConfig, - r.ValidateBackupStorage, - r.ReconcileBackupStorage, + r.ValidateBackupStorageLocations, + r.ReconcileBackupStorageLocations, r.ReconcileRegistries, r.ReconcileRegistrySVCs, r.ReconcileRegistryRoutes, diff --git a/controllers/velero.go b/controllers/velero.go index 3b487bbd77..7658803890 100644 --- a/controllers/velero.go +++ b/controllers/velero.go @@ -27,6 +27,7 @@ import ( "k8s.io/utils/pointer" //"sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -404,6 +405,8 @@ func (r *DPAReconciler) customizeVeleroDeployment(dpa *oadpv1alpha1.DataProtecti veleroDeployment.Labels = r.getAppLabels(dpa) veleroDeployment.Spec.Selector = veleroLabelSelector + isSTSNeeded := r.isSTSTokenNeeded(dpa.Spec.BackupLocations, dpa.Namespace) + //TODO: add velero nodeselector, needs to be added to the VELERO CR first // Selector: veleroDeployment.Spec.Selector, veleroDeployment.Spec.Replicas = pointer.Int32(1) @@ -417,6 +420,30 @@ func (r *DPAReconciler) customizeVeleroDeployment(dpa *oadpv1alpha1.DataProtecti EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }) + + if isSTSNeeded { + defaultMode := int32(420) + expirationSeconds := int64(3600) + veleroDeployment.Spec.Template.Spec.Volumes = append(veleroDeployment.Spec.Template.Spec.Volumes, + corev1.Volume{ + Name: "bound-sa-token", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + DefaultMode: &defaultMode, + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Audience: "openshift", + ExpirationSeconds: &expirationSeconds, + Path: "token", + }, + }, + }, + }, + }, + }, + ) + } //add any default init containers here if needed eg: setup-certificate-secret // When you do this // - please set the ImagePullPolicy to Always, and @@ -438,13 +465,13 @@ func (r *DPAReconciler) customizeVeleroDeployment(dpa *oadpv1alpha1.DataProtecti break } } - if err := r.customizeVeleroContainer(dpa, veleroDeployment, veleroContainer); err != nil { + if err := r.customizeVeleroContainer(dpa, veleroDeployment, veleroContainer, isSTSNeeded); err != nil { return err } return credentials.AppendPluginSpecificSpecs(dpa, veleroDeployment, veleroContainer) } -func (r *DPAReconciler) customizeVeleroContainer(dpa *oadpv1alpha1.DataProtectionApplication, veleroDeployment *appsv1.Deployment, veleroContainer *corev1.Container) error { +func (r *DPAReconciler) customizeVeleroContainer(dpa *oadpv1alpha1.DataProtectionApplication, veleroDeployment *appsv1.Deployment, veleroContainer *corev1.Container, isSTSNeeded bool) error { if veleroContainer == nil { return fmt.Errorf("could not find velero container in Deployment") } @@ -455,8 +482,18 @@ func (r *DPAReconciler) customizeVeleroContainer(dpa *oadpv1alpha1.DataProtectio MountPath: "/etc/ssl/certs", }, ) + + if isSTSNeeded { + veleroContainer.VolumeMounts = append(veleroContainer.VolumeMounts, + corev1.VolumeMount{ + Name: "bound-sa-token", + MountPath: "/var/run/secrets/openshift/serviceaccount", + ReadOnly: true, + }) + } // Append proxy settings to the container from environment variables veleroContainer.Env = append(veleroContainer.Env, proxy.ReadProxyVarsFromEnv()...) + // Enable user to specify --restic-timeout (defaults to 1h) resticTimeout := "1h" if dpa.Spec.Configuration.Restic != nil && len(dpa.Spec.Configuration.Restic.Timeout) > 0 { @@ -468,6 +505,28 @@ func (r *DPAReconciler) customizeVeleroContainer(dpa *oadpv1alpha1.DataProtectio return nil } +func (r *DPAReconciler) isSTSTokenNeeded(bsls []oadpv1alpha1.BackupLocation, ns string) bool { + + for _, bsl := range bsls { + if bsl.Bucket != nil { + bucket := &oadpv1alpha1.Bucket{} + err := r.Get(r.Context, client.ObjectKey{ + Name: bsl.Bucket.BucketRef.Name, + Namespace: ns, + }, bucket) + if err != nil { + //log + return false + } + if bucket.Spec.EnableSharedConfig { + return true + } + } + } + + return false +} + func getVeleroImage(dpa *oadpv1alpha1.DataProtectionApplication) string { if dpa.Spec.UnsupportedOverrides[oadpv1alpha1.VeleroImageKey] != "" { return dpa.Spec.UnsupportedOverrides[oadpv1alpha1.VeleroImageKey] diff --git a/controllers/velero_test.go b/controllers/velero_test.go index af8c42bc0f..55869ebbe5 100644 --- a/controllers/velero_test.go +++ b/controllers/velero_test.go @@ -36,6 +36,7 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { dpa *oadpv1alpha1.DataProtectionApplication wantErr bool wantVeleroDeployment *appsv1.Deployment + clientObjects []client.Object }{ { name: "DPA CR is nil", @@ -930,10 +931,254 @@ func TestDPAReconciler_buildVeleroDeployment(t *testing.T) { }, }, }, + { + name: "given valid Velero CR with with aws plugin from bucket", + veleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + }, + Spec: appsv1.DeploymentSpec{ + Selector: veleroLabelSelector, + }, + }, + dpa: &oadpv1alpha1.DataProtectionApplication{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-Velero-CR", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.DataProtectionApplicationSpec{ + Configuration: &oadpv1alpha1.ApplicationConfig{ + Velero: &oadpv1alpha1.VeleroConfig{ + DefaultPlugins: []oadpv1alpha1.DefaultPlugin{ + oadpv1alpha1.DefaultPluginAWS, + }, + }, + }, + BackupLocations: []oadpv1alpha1.BackupLocation{ + { + Bucket: &oadpv1alpha1.BucketBackupLocation{ + BucketRef: corev1.LocalObjectReference{ + Name: "bucket-123", + }, + Config: map[string]string{}, + Credential: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "cloud-credentials", + }, + Key: "creds", + }, + Default: false, + BackupSyncPeriod: &metav1.Duration{}, + }, + }, + }, + }, + }, + wantErr: false, + wantVeleroDeployment: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-velero-deployment", + Namespace: "test-ns", + Labels: map[string]string{ + "app.kubernetes.io/name": common.Velero, + "app.kubernetes.io/instance": "test-Velero-CR", + "app.kubernetes.io/managed-by": common.OADPOperator, + "app.kubernetes.io/component": Server, + oadpv1alpha1.OadpOperatorLabel: "True", + }, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: appsv1.SchemeGroupVersion.String(), + }, + Spec: appsv1.DeploymentSpec{ + Selector: veleroLabelSelector, + Replicas: pointer.Int32(1), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: veleroLabelSelector.MatchLabels, + Annotations: map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": "8085", + "prometheus.io/path": "/metrics", + }, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyAlways, + ServiceAccountName: common.Velero, + Containers: []corev1.Container{ + { + Name: common.Velero, + Image: common.VeleroImage, + ImagePullPolicy: corev1.PullAlways, + Ports: []corev1.ContainerPort{ + { + Name: "metrics", + ContainerPort: 8085, + }, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + Command: []string{"/velero"}, + Args: []string{ + "server", + "--restic-timeout=1h", + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins", + MountPath: "/plugins", + }, + { + Name: "scratch", + MountPath: "/scratch", + }, + { + Name: "certs", + MountPath: "/etc/ssl/certs", + }, + { + Name: "bound-sa-token", + MountPath: "/var/run/secrets/openshift/serviceaccount", + ReadOnly: true, + }, + { + Name: "cloud-credentials", + MountPath: "/credentials", + }, + }, + Env: []corev1.EnvVar{ + { + Name: common.VeleroScratchDirEnvKey, + Value: "/scratch", + }, + { + Name: common.VeleroNamespaceEnvKey, + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: common.LDLibraryPathEnvKey, + Value: "/plugins", + }, + { + Name: common.HTTPProxyEnvVar, + Value: os.Getenv("HTTP_PROXY"), + }, + { + Name: common.HTTPSProxyEnvVar, + Value: os.Getenv("HTTPS_PROXY"), + }, + { + Name: common.NoProxyEnvVar, + Value: os.Getenv("NO_PROXY"), + }, + { + Name: common.AWSSharedCredentialsFileEnvKey, + Value: "/credentials/cloud", + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "plugins", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "scratch", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "certs", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + { + Name: "bound-sa-token", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + DefaultMode: pointer.Int32(420), + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Audience: "openshift", + ExpirationSeconds: pointer.Int64(3600), + Path: "token", + }, + }, + }, + }, + }, + }, + { + Name: "cloud-credentials", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "cloud-credentials", + }, + }, + }, + }, + InitContainers: []corev1.Container{ + { + Image: common.AWSPluginImage, + Name: common.VeleroPluginForAWS, + ImagePullPolicy: corev1.PullAlways, + Resources: corev1.ResourceRequirements{}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/target", + Name: "plugins", + }, + }, + }, + }, + }, + }, + }, + }, + clientObjects: []client.Object{ + &oadpv1alpha1.Bucket{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bucket-123", + Namespace: "test-ns", + }, + Spec: oadpv1alpha1.BucketSpec{ + EnableSharedConfig: true, + }, + }, + }, + }, } - r := &DPAReconciler{} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + fakeClient, err := getFakeClientFromObjects(tt.clientObjects...) + if err != nil { + t.Errorf("error in creating fake client, likely programmer error") + } + r := DPAReconciler{ + Client: fakeClient, + } if err := r.buildVeleroDeployment(tt.veleroDeployment, tt.dpa); (err != nil) != tt.wantErr { t.Errorf("buildVeleroDeployment() error = %v, wantErr %v", err, tt.wantErr) }