Skip to content

Commit

Permalink
[WIP] AWS S3 backup
Browse files Browse the repository at this point in the history
- add backup validation in base configuration phase
- add backup validation in user configuration phase
- add tests
  • Loading branch information
tomaszsek committed Jan 15, 2019
1 parent e507d23 commit 79112b5
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
79 changes: 74 additions & 5 deletions pkg/controller/jenkins/configuration/base/validate.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
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 (
dockerImageRegexp = regexp.MustCompile(`^` + docker.TagRegexp.String() + `$`)
)

// 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 {
Expand Down Expand Up @@ -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
}
166 changes: 166 additions & 0 deletions pkg/controller/jenkins/configuration/base/validate_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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)
})
}
}
39 changes: 38 additions & 1 deletion pkg/controller/jenkins/configuration/user/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 79112b5

Please sign in to comment.