diff --git a/pkg/controller/jenkins/configuration/base/resources/backup_secret.go b/pkg/controller/jenkins/configuration/base/resources/backup_secret.go new file mode 100644 index 00000000..740aa8bd --- /dev/null +++ b/pkg/controller/jenkins/configuration/base/resources/backup_secret.go @@ -0,0 +1,25 @@ +package resources + +import ( + "fmt" + + virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetBackupCredentialsSecretName returns name of Kubernetes secret used to store backup credentials +func GetBackupCredentialsSecretName(jenkins *virtuslabv1alpha1.Jenkins) string { + return fmt.Sprintf("%s-backup-credentials-%s", constants.OperatorName, jenkins.Name) +} + +// NewBackupCredentialsSecret builds the Kubernetes secret used to store backup credentials +func NewBackupCredentialsSecret(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) *corev1.Secret { + meta.Name = GetBackupCredentialsSecretName(jenkins) + return &corev1.Secret{ + TypeMeta: buildSecretTypeMeta(), + ObjectMeta: meta, + } +} diff --git a/pkg/controller/jenkins/configuration/base/resources/meta.go b/pkg/controller/jenkins/configuration/base/resources/meta.go index 9e0d5216..8cdcc164 100644 --- a/pkg/controller/jenkins/configuration/base/resources/meta.go +++ b/pkg/controller/jenkins/configuration/base/resources/meta.go @@ -2,9 +2,9 @@ package resources import ( "fmt" - "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/controller/jenkins/configuration/base/resources/secret.go b/pkg/controller/jenkins/configuration/base/resources/operator_credentials_secret.go similarity index 100% rename from pkg/controller/jenkins/configuration/base/resources/secret.go rename to pkg/controller/jenkins/configuration/base/resources/operator_credentials_secret.go diff --git a/pkg/controller/jenkins/configuration/base/validate.go b/pkg/controller/jenkins/configuration/base/validate.go index 1e3c9e9c..301f4c13 100644 --- a/pkg/controller/jenkins/configuration/base/validate.go +++ b/pkg/controller/jenkins/configuration/base/validate.go @@ -1,14 +1,19 @@ package base import ( + "context" "fmt" "regexp" virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base/resources" "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin" "github.com/VirtusLab/jenkins-operator/pkg/log" docker "github.com/docker/distribution/reference" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" ) var ( @@ -16,23 +21,32 @@ var ( ) // Validate validates Jenkins CR Spec.master section -func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *virtuslabv1alpha1.Jenkins) bool { +func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *virtuslabv1alpha1.Jenkins) (bool, error) { if jenkins.Spec.Master.Image == "" { r.logger.V(log.VWarn).Info("Image not set") - return false + return false, nil } if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) { r.logger.V(log.VWarn).Info("Invalid image") - return false + return false, nil } if !r.validatePlugins(jenkins.Spec.Master.Plugins) { - return false + return false, nil } - return true + valid, err := r.verifyBackup() + if !valid || err != nil { + return valid, err + } + + if r.jenkins.Spec.Backup == virtuslabv1alpha1.JenkinsBackupTypeAmazonS3 && !r.verifyBackupAmazonS3() { + return false, nil + } + + return true, nil } func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(plugins map[string][]string) bool { @@ -64,3 +78,58 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(plugins map[string][ return valid } + +func (r *ReconcileJenkinsBaseConfiguration) verifyBackup() (bool, error) { + if r.jenkins.Spec.Backup == "" { + r.logger.V(log.VWarn).Info("Backup strategy not set in 'spec.backup'") + return false, nil + } + + valid := false + for _, backup := range virtuslabv1alpha1.AllowedJenkinsBackups { + if r.jenkins.Spec.Backup == backup { + valid = true + } + } + + if !valid { + r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid backup strategy '%s'", r.jenkins.Spec.Backup)) + r.logger.V(log.VWarn).Info(fmt.Sprintf("Allowed backups '%+v'", virtuslabv1alpha1.AllowedJenkinsBackups)) + return false, nil + } + + if r.jenkins.Spec.Backup == virtuslabv1alpha1.JenkinsBackupTypeNoBackup { + return true, nil + } + + backupSecretName := resources.GetBackupCredentialsSecretName(r.jenkins) + backupSecret := &corev1.Secret{} + err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: r.jenkins.Namespace, Name: backupSecretName}, backupSecret) + if err != nil && errors.IsNotFound(err) { + r.logger.V(log.VWarn).Info(fmt.Sprintf("Please create secret '%s' in namespace '%s'", backupSecretName, r.jenkins.Namespace)) + return false, nil + } else if err != nil && !errors.IsNotFound(err) { + return false, err + } + + return true, nil +} + +func (r *ReconcileJenkinsBaseConfiguration) verifyBackupAmazonS3() bool { + if len(r.jenkins.Spec.BackupAmazonS3.BucketName) == 0 { + r.logger.V(log.VWarn).Info("Bucket name not set in 'spec.backupAmazonS3.bucketName'") + return false + } + + if len(r.jenkins.Spec.BackupAmazonS3.BucketPath) == 0 { + r.logger.V(log.VWarn).Info("Bucket path not set in 'spec.backupAmazonS3.bucketPath'") + return false + } + + if len(r.jenkins.Spec.BackupAmazonS3.Region) == 0 { + r.logger.V(log.VWarn).Info("Region not set in 'spec.backupAmazonS3.region'") + return false + } + + return true +} diff --git a/pkg/controller/jenkins/configuration/base/validate_test.go b/pkg/controller/jenkins/configuration/base/validate_test.go index a44bf5c7..10d72579 100644 --- a/pkg/controller/jenkins/configuration/base/validate_test.go +++ b/pkg/controller/jenkins/configuration/base/validate_test.go @@ -1,10 +1,16 @@ package base import ( + "context" "fmt" "testing" + virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" ) @@ -56,3 +62,163 @@ func TestValidatePlugins(t *testing.T) { }) } } + +func TestReconcileJenkinsBaseConfiguration_verifyBackup(t *testing.T) { + tests := []struct { + name string + jenkins *virtuslabv1alpha1.Jenkins + secret *corev1.Secret + want bool + wantErr bool + }{ + { + name: "happy, no backup", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + Spec: virtuslabv1alpha1.JenkinsSpec{ + Backup: virtuslabv1alpha1.JenkinsBackupTypeNoBackup, + }, + }, + want: true, + wantErr: false, + }, + { + name: "happy", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + Spec: virtuslabv1alpha1.JenkinsSpec{ + Backup: virtuslabv1alpha1.JenkinsBackupTypeAmazonS3, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-operator-backup-credentials-jenkins-cr-name"}, + }, + want: true, + wantErr: false, + }, + { + name: "fail, no secret", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + Spec: virtuslabv1alpha1.JenkinsSpec{ + Backup: virtuslabv1alpha1.JenkinsBackupTypeAmazonS3, + }, + }, + want: false, + wantErr: false, + }, + { + name: "fail, empty backup type", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + Spec: virtuslabv1alpha1.JenkinsSpec{ + Backup: "", + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-operator-backup-credentials-jenkins-cr-name"}, + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileJenkinsBaseConfiguration{ + k8sClient: fake.NewFakeClient(), + scheme: nil, + logger: logf.ZapLogger(false), + jenkins: tt.jenkins, + local: false, + minikube: false, + } + if tt.secret != nil { + e := r.k8sClient.Create(context.TODO(), tt.secret) + assert.NoError(t, e) + } + got, err := r.verifyBackup() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestReconcileJenkinsBaseConfiguration_verifyBackupAmazonS3(t *testing.T) { + + tests := []struct { + name string + jenkins *virtuslabv1alpha1.Jenkins + want bool + }{ + { + name: "happy", + jenkins: &virtuslabv1alpha1.Jenkins{ + Spec: virtuslabv1alpha1.JenkinsSpec{ + BackupAmazonS3: virtuslabv1alpha1.JenkinsBackupAmazonS3{ + BucketName: "some-value", + BucketPath: "some-value", + Region: "some-value", + }, + }, + }, + want: true, + }, + { + name: "fail, no bucket name", + jenkins: &virtuslabv1alpha1.Jenkins{ + Spec: virtuslabv1alpha1.JenkinsSpec{ + BackupAmazonS3: virtuslabv1alpha1.JenkinsBackupAmazonS3{ + BucketName: "", + BucketPath: "some-value", + Region: "some-value", + }, + }, + }, + want: false, + }, + { + name: "fail, no bucket path", + jenkins: &virtuslabv1alpha1.Jenkins{ + Spec: virtuslabv1alpha1.JenkinsSpec{ + BackupAmazonS3: virtuslabv1alpha1.JenkinsBackupAmazonS3{ + BucketName: "some-value", + BucketPath: "", + Region: "some-value", + }, + }, + }, + want: false, + }, + { + name: "fail, no region", + jenkins: &virtuslabv1alpha1.Jenkins{ + Spec: virtuslabv1alpha1.JenkinsSpec{ + BackupAmazonS3: virtuslabv1alpha1.JenkinsBackupAmazonS3{ + BucketName: "some-value", + BucketPath: "some-value", + Region: "", + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileJenkinsBaseConfiguration{ + k8sClient: fake.NewFakeClient(), + scheme: nil, + logger: logf.ZapLogger(false), + jenkins: tt.jenkins, + local: false, + minikube: false, + } + got := r.verifyBackupAmazonS3() + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/controller/jenkins/configuration/user/validate.go b/pkg/controller/jenkins/configuration/user/validate.go index 6b9ea0f6..7119f7d2 100644 --- a/pkg/controller/jenkins/configuration/user/validate.go +++ b/pkg/controller/jenkins/configuration/user/validate.go @@ -9,16 +9,24 @@ import ( "strings" virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base/resources" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" "github.com/VirtusLab/jenkins-operator/pkg/log" "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" ) // Validate validates Jenkins CR Spec section func (r *ReconcileUserConfiguration) Validate(jenkins *virtuslabv1alpha1.Jenkins) (bool, error) { - return r.validateSeedJobs(jenkins) + valid, err := r.validateSeedJobs(jenkins) + if !valid || err != nil { + return valid, err + } + + return r.verifyBackup() } func (r *ReconcileUserConfiguration) validateSeedJobs(jenkins *virtuslabv1alpha1.Jenkins) (bool, error) { @@ -87,3 +95,32 @@ func validatePrivateKey(privateKey string) error { return nil } + +func (r *ReconcileUserConfiguration) verifyBackup() (bool, error) { + if r.jenkins.Spec.Backup == virtuslabv1alpha1.JenkinsBackupTypeAmazonS3 { + return r.verifyBackupAmazonS3() + } + + return true, nil +} + +func (r *ReconcileUserConfiguration) verifyBackupAmazonS3() (bool, error) { + backupSecretName := resources.GetBackupCredentialsSecretName(r.jenkins) + backupSecret := &corev1.Secret{} + err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: r.jenkins.Namespace, Name: backupSecretName}, backupSecret) + if err != nil { + return false, err + } + + if len(backupSecret.Data[constants.BackupAmazonS3SecretSecretKey]) == 0 { + r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' doesn't contains key: %s", backupSecretName, constants.BackupAmazonS3SecretSecretKey)) + return false, nil + } + + if len(backupSecret.Data[constants.BackupAmazonS3SecretAccessKey]) == 0 { + r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' doesn't contains key: %s", backupSecretName, constants.BackupAmazonS3SecretAccessKey)) + return false, nil + } + + return true, nil +} diff --git a/pkg/controller/jenkins/configuration/user/validate_test.go b/pkg/controller/jenkins/configuration/user/validate_test.go index 3f0f01a4..c38843af 100644 --- a/pkg/controller/jenkins/configuration/user/validate_test.go +++ b/pkg/controller/jenkins/configuration/user/validate_test.go @@ -6,6 +6,7 @@ import ( "testing" virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -14,7 +15,42 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" ) -func TestValidateUserConfiguration(t *testing.T) { +var fakePrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm +285UwqLnDDlBhSi9nDgJ+m1XU87VCpz/DXW23R/CQcMX2xunib4wWLQqoR3CWbk3 +SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 +UveRNSOx7IXPKtHFiypBhWM4l6ZjgJKm+DRIEhyvoC+pHzcum2ZEPOv+ZJDy5jXK +ZHcNQXVnAZtCcojcjVUBw2rZms+fQ6Volv2JT71Gpykzx/rChhwNwxdAEwjLjKjL +nBWEh/WxsS3NbM7zb4B2XGMCeWVeb/niUwpy+wIDAQABAoIBAQCjGkJNidARmYQI +/u/DxWNWwb2H+o3BFW/1YixYBIjS9BK96cT/bR5mUZRG2XXnnpmqCsxx/AE2KfDU +e4H1ZrB4oFzN3MaVsMNIuZnUzyhM0l0WfnmZp9KEKCm01ilmLCpdcARacPaylIej +6f7QcznmYUShqtbaK8OUhyoWfvz3s0VLkpBlqm63uPtjAx6sAl399THxHVwbYgYy +TxPY8wdjOvNzQJ7ColUh05Zq6TsCGGFUFg7v4to/AXtDhcTMVONlapP+XxekRx8P +98BepIgzgvQhWak8gm+cKQYANk14Q8BDzUCDplYuIZVvKl+/ZHltjHGjrqxDrcDA +0U7REgtxAoGBAN+LAEf2o14ffs/ebVSxiv7LnuAxFh2L6i7RqtehpSf7BnYC65vB +6TMsc/0/KFkD5Az7nrJmA7HmM8J/NI2ks0Mbft+0XCRFx/zfU6pOvPinRKp/8Vtm +aUmNzhz8UMaQ1JXOvBOqvXKWYrN1WPha1+/BnUQrpTdhGxAoAh1FW4eHAoGBAMXA +mXTN5X8+mp9KW2bIpFsjrZ+EyhxO6a6oBMZY54rceeOzf5RcXY7EOiTrnmr+lQvp +fAKBeX5V8G96nSEIDmPhKGZ1C1vEP6hRWahJo1XkN5E1j6hRHCu3DQLtL2lxlyfG +Fx11fysgmLoPVVytLAEQwt4WxMp7OsM1NWqB+u3tAoGBAILUg3Gas7pejIV0FGDB +GCxPV8i2cc8RGBoWs/pHrLVdgUaIJwSd1LISjj/lOuP+FvZSPWsDsZ3osNpgQI21 +mwTnjrW2hUblYEprGjhOpOKSYum2v7dSlMRrrfng4hWUphaXTBPmlcH+qf2F7HBO +GptDoZtIQAXNW111TOd8tDj5AoGAC1PO9nvcy38giENQHQEdOQNALMUEdr6mcBS7 +wUjSaofai4p6olrwGP9wfTDp8CMJEpebPOGBvhTaIuiZG41ElcAN+mB1+Bmzs8aF +JjihnIfoDu9MfU24GWDw49wGPTn+eI7GQC+8yxGg7fd24kohHSaCowoW16pbYVco +6iLr5rkCgYBt0bcYJ3AOTH0UXS8kvJvnyce/RBIAMoUABwvdkZt9r5B4UzsoLq5e +WrrU6fSRsE6lSsBd83pOAQ46tv+vntQ+0EihD9/0INhkQM99lBw1TFdFTgGSAs1e +ns4JGP6f5uIuwqu/nbqPqMyDovjkGbX2znuGBcvki90Pi97XL7MMWw== +-----END RSA PRIVATE KEY----- +` + +var fakeInvalidPrivateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm +285UwqLnDDlBhSi9nDgJ+m1XU87VCpz/DXW23R/CQcMX2xunib4wWLQqoR3CWbk3 +SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 +` + +func TestValidateSeedJobs(t *testing.T) { data := []struct { description string jenkins *virtuslabv1alpha1.Jenkins @@ -195,44 +231,94 @@ func TestValidateUserConfiguration(t *testing.T) { assert.NoError(t, err) } userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) - result, err := userReconcileLoop.Validate(testingData.jenkins) + result, err := userReconcileLoop.validateSeedJobs(testingData.jenkins) assert.NoError(t, err) assert.Equal(t, testingData.expectedResult, result) }) } } -var fakePrivateKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm -285UwqLnDDlBhSi9nDgJ+m1XU87VCpz/DXW23R/CQcMX2xunib4wWLQqoR3CWbk3 -SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 -UveRNSOx7IXPKtHFiypBhWM4l6ZjgJKm+DRIEhyvoC+pHzcum2ZEPOv+ZJDy5jXK -ZHcNQXVnAZtCcojcjVUBw2rZms+fQ6Volv2JT71Gpykzx/rChhwNwxdAEwjLjKjL -nBWEh/WxsS3NbM7zb4B2XGMCeWVeb/niUwpy+wIDAQABAoIBAQCjGkJNidARmYQI -/u/DxWNWwb2H+o3BFW/1YixYBIjS9BK96cT/bR5mUZRG2XXnnpmqCsxx/AE2KfDU -e4H1ZrB4oFzN3MaVsMNIuZnUzyhM0l0WfnmZp9KEKCm01ilmLCpdcARacPaylIej -6f7QcznmYUShqtbaK8OUhyoWfvz3s0VLkpBlqm63uPtjAx6sAl399THxHVwbYgYy -TxPY8wdjOvNzQJ7ColUh05Zq6TsCGGFUFg7v4to/AXtDhcTMVONlapP+XxekRx8P -98BepIgzgvQhWak8gm+cKQYANk14Q8BDzUCDplYuIZVvKl+/ZHltjHGjrqxDrcDA -0U7REgtxAoGBAN+LAEf2o14ffs/ebVSxiv7LnuAxFh2L6i7RqtehpSf7BnYC65vB -6TMsc/0/KFkD5Az7nrJmA7HmM8J/NI2ks0Mbft+0XCRFx/zfU6pOvPinRKp/8Vtm -aUmNzhz8UMaQ1JXOvBOqvXKWYrN1WPha1+/BnUQrpTdhGxAoAh1FW4eHAoGBAMXA -mXTN5X8+mp9KW2bIpFsjrZ+EyhxO6a6oBMZY54rceeOzf5RcXY7EOiTrnmr+lQvp -fAKBeX5V8G96nSEIDmPhKGZ1C1vEP6hRWahJo1XkN5E1j6hRHCu3DQLtL2lxlyfG -Fx11fysgmLoPVVytLAEQwt4WxMp7OsM1NWqB+u3tAoGBAILUg3Gas7pejIV0FGDB -GCxPV8i2cc8RGBoWs/pHrLVdgUaIJwSd1LISjj/lOuP+FvZSPWsDsZ3osNpgQI21 -mwTnjrW2hUblYEprGjhOpOKSYum2v7dSlMRrrfng4hWUphaXTBPmlcH+qf2F7HBO -GptDoZtIQAXNW111TOd8tDj5AoGAC1PO9nvcy38giENQHQEdOQNALMUEdr6mcBS7 -wUjSaofai4p6olrwGP9wfTDp8CMJEpebPOGBvhTaIuiZG41ElcAN+mB1+Bmzs8aF -JjihnIfoDu9MfU24GWDw49wGPTn+eI7GQC+8yxGg7fd24kohHSaCowoW16pbYVco -6iLr5rkCgYBt0bcYJ3AOTH0UXS8kvJvnyce/RBIAMoUABwvdkZt9r5B4UzsoLq5e -WrrU6fSRsE6lSsBd83pOAQ46tv+vntQ+0EihD9/0INhkQM99lBw1TFdFTgGSAs1e -ns4JGP6f5uIuwqu/nbqPqMyDovjkGbX2znuGBcvki90Pi97XL7MMWw== ------END RSA PRIVATE KEY----- -` - -var fakeInvalidPrivateKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm -285UwqLnDDlBhSi9nDgJ+m1XU87VCpz/DXW23R/CQcMX2xunib4wWLQqoR3CWbk3 -SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 -` +func TestReconcileUserConfiguration_verifyBackupAmazonS3(t *testing.T) { + tests := []struct { + name string + jenkins *virtuslabv1alpha1.Jenkins + secret *corev1.Secret + want bool + wantErr bool + }{ + { + name: "happy", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-operator-backup-credentials-jenkins-cr-name"}, + Data: map[string][]byte{ + constants.BackupAmazonS3SecretSecretKey: []byte("some-value"), + constants.BackupAmazonS3SecretAccessKey: []byte("some-value"), + }, + }, + want: true, + wantErr: false, + }, + { + name: "fail, no secret", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + }, + want: false, + wantErr: true, + }, + { + name: "fail, no secret key in secret", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-operator-backup-credentials-jenkins-cr-name"}, + Data: map[string][]byte{ + constants.BackupAmazonS3SecretSecretKey: []byte(""), + constants.BackupAmazonS3SecretAccessKey: []byte("some-value"), + }, + }, + want: false, + wantErr: false, + }, + { + name: "fail, no access key in secret", + jenkins: &virtuslabv1alpha1.Jenkins{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-operator-backup-credentials-jenkins-cr-name"}, + Data: map[string][]byte{ + constants.BackupAmazonS3SecretSecretKey: []byte("some-value"), + constants.BackupAmazonS3SecretAccessKey: []byte(""), + }, + }, + want: false, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileUserConfiguration{ + k8sClient: fake.NewFakeClient(), + jenkinsClient: nil, + logger: logf.ZapLogger(false), + jenkins: tt.jenkins, + } + if tt.secret != nil { + e := r.k8sClient.Create(context.TODO(), tt.secret) + assert.NoError(t, e) + } + got, err := r.verifyBackupAmazonS3() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/controller/jenkins/constants/constants.go b/pkg/controller/jenkins/constants/constants.go index 7a33ce15..b8d36631 100644 --- a/pkg/controller/jenkins/constants/constants.go +++ b/pkg/controller/jenkins/constants/constants.go @@ -7,4 +7,10 @@ const ( DefaultAmountOfExecutors = 3 // SeedJobSuffix is a suffix added for all seed jobs SeedJobSuffix = "job-dsl-seed" + // DefaultJenkinsMasterImage is the default Jenkins master docker image + DefaultJenkinsMasterImage = "jenkins/jenkins:lts" + // BackupAmazonS3SecretAccessKey is the Amazon user access key used to Amazon S3 backup + BackupAmazonS3SecretAccessKey = "access-key" + // BackupAmazonS3SecretSecretKey is the Amazon user secret key used to Amazon S3 backup + BackupAmazonS3SecretSecretKey = "secret-key" ) diff --git a/pkg/controller/jenkins/jenkins_controller.go b/pkg/controller/jenkins/jenkins_controller.go index 120f52ed..1cd49833 100644 --- a/pkg/controller/jenkins/jenkins_controller.go +++ b/pkg/controller/jenkins/jenkins_controller.go @@ -7,6 +7,7 @@ import ( virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1" "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base" "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/user" + "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/constants" "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin" "github.com/VirtusLab/jenkins-operator/pkg/log" @@ -99,7 +100,7 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed: %+v", err)) return reconcile.Result{Requeue: true}, nil } - return result, err + return result, nil } func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logger) (reconcile.Result, error) { @@ -124,10 +125,16 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg // Reconcile base configuration baseConfiguration := base.New(r.client, r.scheme, logger, jenkins, r.local, r.minikube) - if !baseConfiguration.Validate(jenkins) { - logger.V(log.VWarn).Info("Validation of base configuration failed, please correct Jenkins CR") + + valid, err := baseConfiguration.Validate(jenkins) + if err != nil { + return reconcile.Result{}, err + } + if !valid { + logger.V(log.VWarn).Info("Validation of user configuration failed, please correct Jenkins CR") return reconcile.Result{}, nil // don't requeue } + result, jenkinsClient, err := baseConfiguration.Reconcile() if err != nil { return reconcile.Result{}, err @@ -148,7 +155,8 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg // Reconcile user configuration userConfiguration := user.New(r.client, jenkinsClient, logger, jenkins) - valid, err := userConfiguration.Validate(jenkins) + + valid, err = userConfiguration.Validate(jenkins) if err != nil { return reconcile.Result{}, err } @@ -156,6 +164,7 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg logger.V(log.VWarn).Info("Validation of user configuration failed, please correct Jenkins CR") return reconcile.Result{}, nil // don't requeue } + result, err = userConfiguration.Reconcile() if err != nil { return reconcile.Result{}, err @@ -183,8 +192,18 @@ func (r *ReconcileJenkins) buildLogger(jenkinsName string) logr.Logger { func (r *ReconcileJenkins) setDefaults(jenkins *virtuslabv1alpha1.Jenkins, logger logr.Logger) error { changed := false + if len(jenkins.Spec.Master.Image) == 0 { + logger.Info("Setting default Jenkins master image: " + constants.DefaultJenkinsMasterImage) + changed = true + jenkins.Spec.Master.Image = constants.DefaultJenkinsMasterImage + } + if len(jenkins.Spec.Backup) == 0 { + logger.Info("Setting default backup strategy: " + virtuslabv1alpha1.JenkinsBackupTypeNoBackup) + changed = true + jenkins.Spec.Backup = virtuslabv1alpha1.JenkinsBackupTypeNoBackup + } if len(jenkins.Spec.Master.Plugins) == 0 { - logger.Info("Setting default base plugins in CR") + logger.Info("Setting default base plugins") changed = true jenkins.Spec.Master.Plugins = plugin.BasePlugins() } @@ -193,7 +212,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *virtuslabv1alpha1.Jenkins, logge _, limitCPUSet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceCPU] _, limitMemporySet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceMemory] if !limitCPUSet || !limitMemporySet || !requestCPUSet || !requestMemporySet { - logger.Info("Setting default Jenkins master pod resource requirements in CR") + logger.Info("Setting default Jenkins master pod resource requirements") changed = true jenkins.Spec.Master.Resources = corev1.ResourceRequirements{ Requests: corev1.ResourceList{