Remove backup mechanism
This commit is contained in:
		
							parent
							
								
									ab87922bd1
								
							
						
					
					
						commit
						dd04c0cf5b
					
				|  | @ -33,49 +33,6 @@ | |||
|   pruneopts = "NT" | ||||
|   revision = "de5bf2ad457846296e2031421a34e2568e304e35" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:d6d8c5e82a2ac7f2d44a9cd37412f72c4c0342b61ed5fa57e0c6c53bf3f1a4cc" | ||||
|   name = "github.com/aws/aws-sdk-go" | ||||
|   packages = [ | ||||
|     "aws", | ||||
|     "aws/awserr", | ||||
|     "aws/awsutil", | ||||
|     "aws/client", | ||||
|     "aws/client/metadata", | ||||
|     "aws/corehandlers", | ||||
|     "aws/credentials", | ||||
|     "aws/credentials/ec2rolecreds", | ||||
|     "aws/credentials/endpointcreds", | ||||
|     "aws/credentials/processcreds", | ||||
|     "aws/credentials/stscreds", | ||||
|     "aws/csm", | ||||
|     "aws/defaults", | ||||
|     "aws/ec2metadata", | ||||
|     "aws/endpoints", | ||||
|     "aws/request", | ||||
|     "aws/session", | ||||
|     "aws/signer/v4", | ||||
|     "internal/ini", | ||||
|     "internal/s3err", | ||||
|     "internal/sdkio", | ||||
|     "internal/sdkrand", | ||||
|     "internal/sdkuri", | ||||
|     "internal/shareddefaults", | ||||
|     "private/protocol", | ||||
|     "private/protocol/eventstream", | ||||
|     "private/protocol/eventstream/eventstreamapi", | ||||
|     "private/protocol/query", | ||||
|     "private/protocol/query/queryutil", | ||||
|     "private/protocol/rest", | ||||
|     "private/protocol/restxml", | ||||
|     "private/protocol/xml/xmlutil", | ||||
|     "service/s3", | ||||
|     "service/sts", | ||||
|   ] | ||||
|   pruneopts = "NT" | ||||
|   revision = "a2484497433aa110263c045df2f1d49336b38cfd" | ||||
|   version = "v1.16.22" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:8d13c70d5898b091728540686c696baee0d64013b8e43089da80621a49410391" | ||||
|   name = "github.com/bndr/gojenkins" | ||||
|  | @ -292,13 +249,6 @@ | |||
|   revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" | ||||
|   version = "v0.3.6" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:1234e31f3de67447e344dabcdf72c4588d31b8eed2d28f1889377ec006a086a9" | ||||
|   name = "github.com/jmespath/go-jmespath" | ||||
|   packages = ["."] | ||||
|   pruneopts = "NT" | ||||
|   revision = "c2b33e84" | ||||
| 
 | ||||
| [[projects]] | ||||
|   digest = "1:f5b9328966ccea0970b1d15075698eff0ddb3e75889560aad2e9f76b289b536a" | ||||
|   name = "github.com/joho/godotenv" | ||||
|  | @ -468,10 +418,7 @@ | |||
| [[projects]] | ||||
|   digest = "1:4af061277c04a7660e082acc2020f4c66d2c21dfc62e0242ffa1d2120cdfb4ec" | ||||
|   name = "github.com/stretchr/testify" | ||||
|   packages = [ | ||||
|     "assert", | ||||
|     "require", | ||||
|   ] | ||||
|   packages = ["assert"] | ||||
|   pruneopts = "NT" | ||||
|   revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" | ||||
|   version = "v1.2.2" | ||||
|  | @ -517,7 +464,7 @@ | |||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|   digest = "1:a7fcf4f3e5247a06ad4c28108f0bc1d4ab980a1a0567e7790260cf2d3d77f37d" | ||||
|   digest = "1:922c0bb9dc59af35400f6725ed08582f99f710ffc1a1075e8914c73515bb269e" | ||||
|   name = "golang.org/x/net" | ||||
|   packages = [ | ||||
|     "context", | ||||
|  | @ -530,7 +477,7 @@ | |||
|     "idna", | ||||
|   ] | ||||
|   pruneopts = "NT" | ||||
|   revision = "c10e9556a7bc0e7c942242b606f0acf024ad5d6a" | ||||
|   revision = "3a22650c66bd7f4fb6d1e8072ffd7b75c8a27898" | ||||
| 
 | ||||
| [[projects]] | ||||
|   branch = "master" | ||||
|  | @ -914,10 +861,6 @@ | |||
|   analyzer-name = "dep" | ||||
|   analyzer-version = 1 | ||||
|   input-imports = [ | ||||
|     "github.com/aws/aws-sdk-go/aws", | ||||
|     "github.com/aws/aws-sdk-go/aws/credentials", | ||||
|     "github.com/aws/aws-sdk-go/aws/session", | ||||
|     "github.com/aws/aws-sdk-go/service/s3", | ||||
|     "github.com/bndr/gojenkins", | ||||
|     "github.com/docker/distribution/reference", | ||||
|     "github.com/go-logr/logr", | ||||
|  | @ -930,7 +873,6 @@ | |||
|     "github.com/operator-framework/operator-sdk/version", | ||||
|     "github.com/pkg/errors", | ||||
|     "github.com/stretchr/testify/assert", | ||||
|     "github.com/stretchr/testify/require", | ||||
|     "k8s.io/api/core/v1", | ||||
|     "k8s.io/api/rbac/v1", | ||||
|     "k8s.io/apimachinery/pkg/api/errors", | ||||
|  |  | |||
|  | @ -12,30 +12,8 @@ import ( | |||
| type JenkinsSpec struct { | ||||
| 	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
 | ||||
| 	// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
 | ||||
| 	Backup         JenkinsBackup         `json:"backup,omitempty"` | ||||
| 	BackupAmazonS3 JenkinsBackupAmazonS3 `json:"backupAmazonS3,omitempty"` | ||||
| 	Master         JenkinsMaster         `json:"master,omitempty"` | ||||
| 	SeedJobs       []SeedJob             `json:"seedJobs,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // JenkinsBackup defines type of Jenkins backup
 | ||||
| type JenkinsBackup string | ||||
| 
 | ||||
| const ( | ||||
| 	// JenkinsBackupTypeNoBackup tells that Jenkins won't backup jobs
 | ||||
| 	JenkinsBackupTypeNoBackup = "NoBackup" | ||||
| 	// JenkinsBackupTypeAmazonS3 tells that Jenkins will backup jobs into AWS S3 bucket
 | ||||
| 	JenkinsBackupTypeAmazonS3 = "AmazonS3" | ||||
| ) | ||||
| 
 | ||||
| // AllowedJenkinsBackups consists allowed Jenkins backup types
 | ||||
| var AllowedJenkinsBackups = []JenkinsBackup{JenkinsBackupTypeNoBackup, JenkinsBackupTypeAmazonS3} | ||||
| 
 | ||||
| // JenkinsBackupAmazonS3 defines backup configuration to AWS S3 bucket
 | ||||
| type JenkinsBackupAmazonS3 struct { | ||||
| 	BucketName string `json:"bucketName,omitempty"` | ||||
| 	BucketPath string `json:"bucketPath,omitempty"` | ||||
| 	Region     string `json:"region,omitempty"` | ||||
| 	Master   JenkinsMaster `json:"master,omitempty"` | ||||
| 	SeedJobs []SeedJob     `json:"seedJobs,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // JenkinsMaster defines the Jenkins master pod attributes and plugins,
 | ||||
|  | @ -51,7 +29,6 @@ type JenkinsMaster struct { | |||
| type JenkinsStatus struct { | ||||
| 	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
 | ||||
| 	// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
 | ||||
| 	BackupRestored                 bool         `json:"backupRestored,omitempty"` | ||||
| 	BaseConfigurationCompletedTime *metav1.Time `json:"baseConfigurationCompletedTime,omitempty"` | ||||
| 	UserConfigurationCompletedTime *metav1.Time `json:"userConfigurationCompletedTime,omitempty"` | ||||
| 	Builds                         []Build      `json:"builds,omitempty"` | ||||
|  |  | |||
|  | @ -77,22 +77,6 @@ func (in *Jenkins) DeepCopyObject() runtime.Object { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||
| func (in *JenkinsBackupAmazonS3) DeepCopyInto(out *JenkinsBackupAmazonS3) { | ||||
| 	*out = *in | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsBackupAmazonS3.
 | ||||
| func (in *JenkinsBackupAmazonS3) DeepCopy() *JenkinsBackupAmazonS3 { | ||||
| 	if in == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	out := new(JenkinsBackupAmazonS3) | ||||
| 	in.DeepCopyInto(out) | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||
| func (in *JenkinsList) DeepCopyInto(out *JenkinsList) { | ||||
| 	*out = *in | ||||
|  | @ -168,7 +152,6 @@ func (in *JenkinsMaster) DeepCopy() *JenkinsMaster { | |||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||
| func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { | ||||
| 	*out = *in | ||||
| 	out.BackupAmazonS3 = in.BackupAmazonS3 | ||||
| 	in.Master.DeepCopyInto(&out.Master) | ||||
| 	if in.SeedJobs != nil { | ||||
| 		in, out := &in.SeedJobs, &out.SeedJobs | ||||
|  |  | |||
|  | @ -1,240 +0,0 @@ | |||
| package aws | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||
| 
 | ||||
| 	"github.com/go-logr/logr" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| ) | ||||
| 
 | ||||
| // AmazonS3Backup is a backup strategy where backup is stored in AWS S3 bucket
 | ||||
| // credentials required to make calls to AWS API are provided by user in backup credentials Kubernetes secret
 | ||||
| type AmazonS3Backup struct{} | ||||
| 
 | ||||
| // GetRestoreJobXML returns Jenkins restore backup job config XML
 | ||||
| func (b *AmazonS3Backup) GetRestoreJobXML(jenkins v1alpha1.Jenkins) (string, error) { | ||||
| 	return `<?xml version='1.1' encoding='UTF-8'?> | ||||
| <flow-definition plugin="workflow-job@2.31"> | ||||
|   <actions/> | ||||
|   <description></description> | ||||
|   <keepDependencies>false</keepDependencies> | ||||
|   <properties> | ||||
|     <org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty/> | ||||
|     <org.jenkinsci.plugins.workflow.job.properties.DisableResumeJobProperty/> | ||||
|   </properties> | ||||
|   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61.1"> | ||||
|     <script>import com.amazonaws.auth.PropertiesFileCredentialsProvider | ||||
| import com.amazonaws.services.s3.AmazonS3ClientBuilder | ||||
| import com.amazonaws.services.s3.model.AmazonS3Exception | ||||
| import com.amazonaws.services.s3.model.S3Object | ||||
| 
 | ||||
| node('master') { | ||||
|     def accessKeyFilePath = "` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretAccessKey + `" | ||||
|     def secretKeyFilePath = "` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretSecretKey + `" | ||||
|     def credentialsFileName = "backup-credentials" | ||||
|     def bucketName = "` + jenkins.Spec.BackupAmazonS3.BucketName + `" | ||||
|     def bucketKey = "` + jenkins.Spec.BackupAmazonS3.BucketPath + `" | ||||
|     def region = "` + jenkins.Spec.BackupAmazonS3.Region + `" | ||||
|     def latestBackupFile = "` + constants.BackupLatestFileName + `" | ||||
| 
 | ||||
|     def jenkinsHome = env.JENKINS_HOME | ||||
|     def latestBackupKey = "${bucketKey}/${latestBackupFile}" | ||||
|     def tmpBackupPath = "/tmp/restore.tar.gz" | ||||
|     boolean backupExists = true | ||||
| 
 | ||||
|     def accessKey = new java.io.File(accessKeyFilePath).text | ||||
|     def secretKey = new java.io.File(secretKeyFilePath).text | ||||
| 	sh "touch ${env.WORKSPACE}/${credentialsFileName}" | ||||
|     new java.io.File("${env.WORKSPACE}/${credentialsFileName}").write("accessKey=${accessKey}\nsecretKey=${secretKey}\n") | ||||
| 
 | ||||
|     stage('Check if backup exists') { | ||||
|         def s3 = AmazonS3ClientBuilder | ||||
|                 .standard() | ||||
|                 .withCredentials(new PropertiesFileCredentialsProvider("${env.WORKSPACE}/${credentialsFileName}")) | ||||
|                 .withRegion(region) | ||||
|                 .build() | ||||
|         try { | ||||
|             println s3.getObjectMetadata(bucketName, latestBackupKey) | ||||
|         } catch (AmazonS3Exception e) { | ||||
|             if (e.getStatusCode() == 404) { | ||||
|                 println "There is no backup ${bucketName}/${latestBackupKey}" | ||||
|                 backupExists = false | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (backupExists) { | ||||
|         stage('Download backup') { | ||||
|             def s3 = AmazonS3ClientBuilder | ||||
|                     .standard() | ||||
|                     .withCredentials(new PropertiesFileCredentialsProvider("${env.WORKSPACE}/${credentialsFileName}")) | ||||
|                     .withRegion(region) | ||||
|                     .build() | ||||
|             S3Object backup = s3.getObject(bucketName, latestBackupKey) | ||||
|             java.nio.file.Files.copy( | ||||
|                     backup.getObjectContent(), | ||||
|                     new java.io.File(tmpBackupPath).toPath(), | ||||
|                     java.nio.file.StandardCopyOption.REPLACE_EXISTING); | ||||
|         } | ||||
| 
 | ||||
|         stage('Unpack backup') { | ||||
|             sh "tar -C ${jenkinsHome} -zxf ${tmpBackupPath}" | ||||
|         } | ||||
| 
 | ||||
|         stage('Reload Jenkins') { | ||||
|             jenkins.model.Jenkins.getInstance().reload() | ||||
|         } | ||||
| 
 | ||||
|         sh "rm ${tmpBackupPath}" | ||||
| 		sh "rm ${env.WORKSPACE}/${credentialsFileName}" | ||||
|     } | ||||
| }</script> | ||||
|     <sandbox>false</sandbox> | ||||
|   </definition> | ||||
|   <triggers/> | ||||
|   <disabled>false</disabled> | ||||
| </flow-definition>`, nil | ||||
| } | ||||
| 
 | ||||
| // GetBackupJobXML returns Jenkins backup job config XML
 | ||||
| func (b *AmazonS3Backup) GetBackupJobXML(jenkins v1alpha1.Jenkins) (string, error) { | ||||
| 	return `<?xml version='1.1' encoding='UTF-8'?> | ||||
| <flow-definition plugin="workflow-job@2.31"> | ||||
|   <actions/> | ||||
|   <description></description> | ||||
|   <keepDependencies>false</keepDependencies> | ||||
|   <properties> | ||||
|     <org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty/> | ||||
|     <org.jenkinsci.plugins.workflow.job.properties.DisableResumeJobProperty/> | ||||
|     <org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty> | ||||
|       <triggers> | ||||
|         <hudson.triggers.TimerTrigger> | ||||
|           <spec>H/60 * * * *</spec> | ||||
|         </hudson.triggers.TimerTrigger> | ||||
|       </triggers> | ||||
|     </org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty> | ||||
|   </properties> | ||||
|   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61"> | ||||
|     <script>import com.amazonaws.auth.PropertiesFileCredentialsProvider | ||||
| import com.amazonaws.services.s3.AmazonS3ClientBuilder | ||||
| 
 | ||||
| import java.io.File | ||||
| 
 | ||||
| node('master') { | ||||
|     def accessKeyFilePath = "` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretAccessKey + `" | ||||
|     def secretKeyFilePath = "` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretSecretKey + `" | ||||
|     def credentialsFileName = "backup-credentials" | ||||
|     def bucketName = "` + jenkins.Spec.BackupAmazonS3.BucketName + `" | ||||
|     def bucketKey = "` + jenkins.Spec.BackupAmazonS3.BucketPath + `" | ||||
|     def region = "` + jenkins.Spec.BackupAmazonS3.Region + `" | ||||
|     def latestBackupFile = "` + constants.BackupLatestFileName + `" | ||||
| 
 | ||||
|     def jenkinsHome = env.JENKINS_HOME | ||||
|     def backupTime = sh(script: "date '+%Y-%m-%d-%H-%M'", returnStdout: true).trim() | ||||
|     def tmpBackupPath = "/tmp/backup.tar.gz" | ||||
| 
 | ||||
|     def backupKey = "${bucketKey}/build-history-${backupTime}.tar.gz" | ||||
|     def latestBackupKey = "${bucketKey}/${latestBackupFile}" | ||||
| 
 | ||||
|     def accessKey = new java.io.File(accessKeyFilePath).text | ||||
|     def secretKey = new java.io.File(secretKeyFilePath).text | ||||
| 	sh "touch ${env.WORKSPACE}/${credentialsFileName}" | ||||
|     new java.io.File("${env.WORKSPACE}/${credentialsFileName}").write("accessKey=${accessKey}\nsecretKey=${secretKey}\n") | ||||
| 
 | ||||
|     stage('Create backup archive') { | ||||
|         println "Creating backup archive to ${tmpBackupPath}" | ||||
|         sh "tar -C ${jenkinsHome} -z --exclude jobs/*/config.xml --exclude jobs/*/workspace* --exclude jobs/*/simulation.log -c config-history jobs  -f ${tmpBackupPath}" | ||||
|     } | ||||
| 
 | ||||
|     stage('Upload backup') { | ||||
|         def s3 = AmazonS3ClientBuilder | ||||
|                 .standard() | ||||
|                 .withCredentials(new PropertiesFileCredentialsProvider("${env.WORKSPACE}/${credentialsFileName}")) | ||||
|                 .withRegion(region) | ||||
|                 .build() | ||||
|         println "Uploading backup to ${bucketName}/${backupKey}" | ||||
|         s3.putObject(bucketName, backupKey, new File(tmpBackupPath)) | ||||
|         println s3.getObjectMetadata(bucketName, backupKey) | ||||
|     } | ||||
| 
 | ||||
|     stage('Copy backup') { | ||||
|         def s3 = AmazonS3ClientBuilder | ||||
|                 .standard() | ||||
|                 .withCredentials(new PropertiesFileCredentialsProvider("${env.WORKSPACE}/${credentialsFileName}")) | ||||
|                 .withRegion(region) | ||||
|                 .build() | ||||
|         println "Coping backup ${bucketName}${backupKey} to ${bucketName}/${latestBackupKey}" | ||||
|         s3.copyObject(bucketName, backupKey, bucketName, latestBackupKey) | ||||
|         println s3.getObjectMetadata(bucketName, latestBackupKey) | ||||
|     } | ||||
| 
 | ||||
|     sh "rm ${tmpBackupPath}" | ||||
| 	sh "rm ${env.WORKSPACE}/${credentialsFileName}" | ||||
| }</script> | ||||
|     <sandbox>false</sandbox> | ||||
|   </definition> | ||||
|   <triggers/> | ||||
|   <disabled>false</disabled> | ||||
| </flow-definition>`, nil | ||||
| } | ||||
| 
 | ||||
| // IsConfigurationValidForBasePhase validates if user provided valid configuration of backup for base phase
 | ||||
| func (b *AmazonS3Backup) IsConfigurationValidForBasePhase(jenkins v1alpha1.Jenkins, logger logr.Logger) bool { | ||||
| 	if len(jenkins.Spec.BackupAmazonS3.BucketName) == 0 { | ||||
| 		logger.V(log.VWarn).Info("Bucket name not set in 'spec.backupAmazonS3.bucketName'") | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if len(jenkins.Spec.BackupAmazonS3.BucketPath) == 0 { | ||||
| 		logger.V(log.VWarn).Info("Bucket path not set in 'spec.backupAmazonS3.bucketPath'") | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if len(jenkins.Spec.BackupAmazonS3.Region) == 0 { | ||||
| 		logger.V(log.VWarn).Info("Region not set in 'spec.backupAmazonS3.region'") | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // IsConfigurationValidForUserPhase validates if user provided valid configuration of backup for user phase
 | ||||
| func (b *AmazonS3Backup) IsConfigurationValidForUserPhase(k8sClient k8s.Client, jenkins v1alpha1.Jenkins, logger logr.Logger) (bool, error) { | ||||
| 	backupSecretName := resources.GetBackupCredentialsSecretName(&jenkins) | ||||
| 	backupSecret := &corev1.Secret{} | ||||
| 	err := k8sClient.Get(context.TODO(), types.NamespacedName{Namespace: jenkins.Namespace, Name: backupSecretName}, backupSecret) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if len(backupSecret.Data[constants.BackupAmazonS3SecretSecretKey]) == 0 { | ||||
| 		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 { | ||||
| 		logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' doesn't contains key: %s", backupSecretName, constants.BackupAmazonS3SecretAccessKey)) | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| // GetRequiredPlugins returns all required Jenkins plugins by this backup strategy
 | ||||
| func (b *AmazonS3Backup) GetRequiredPlugins() map[string][]plugins.Plugin { | ||||
| 	return map[string][]plugins.Plugin{ | ||||
| 		"aws-java-sdk:1.11.457": { | ||||
| 			plugins.Must(plugins.New(plugins.ApacheComponentsClientPlugin)), | ||||
| 			plugins.Must(plugins.New(plugins.Jackson2ADIPlugin)), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | @ -1,165 +0,0 @@ | |||
| package aws | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
| 
 | ||||
| 	"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" | ||||
| ) | ||||
| 
 | ||||
| func TestAmazonS3Backup_IsConfigurationValidForBasePhase(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		jenkins v1alpha1.Jenkins | ||||
| 		want    bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "happy", | ||||
| 			jenkins: v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					BackupAmazonS3: v1alpha1.JenkinsBackupAmazonS3{ | ||||
| 						BucketName: "some-value", | ||||
| 						BucketPath: "some-value", | ||||
| 						Region:     "some-value", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "fail, no bucket name", | ||||
| 			jenkins: v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					BackupAmazonS3: v1alpha1.JenkinsBackupAmazonS3{ | ||||
| 						BucketName: "", | ||||
| 						BucketPath: "some-value", | ||||
| 						Region:     "some-value", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "fail, no bucket path", | ||||
| 			jenkins: v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					BackupAmazonS3: v1alpha1.JenkinsBackupAmazonS3{ | ||||
| 						BucketName: "some-value", | ||||
| 						BucketPath: "", | ||||
| 						Region:     "some-value", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "fail, no region", | ||||
| 			jenkins: v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					BackupAmazonS3: v1alpha1.JenkinsBackupAmazonS3{ | ||||
| 						BucketName: "some-value", | ||||
| 						BucketPath: "some-value", | ||||
| 						Region:     "", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: false, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			r := &AmazonS3Backup{} | ||||
| 			got := r.IsConfigurationValidForBasePhase(tt.jenkins, logf.ZapLogger(false)) | ||||
| 			assert.Equal(t, tt.want, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestAmazonS3Backup_IsConfigurationValidForUserPhase(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		jenkins *v1alpha1.Jenkins | ||||
| 		secret  *corev1.Secret | ||||
| 		want    bool | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "happy", | ||||
| 			jenkins: &v1alpha1.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: &v1alpha1.Jenkins{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, | ||||
| 			}, | ||||
| 			want:    false, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "fail, no secret key in secret", | ||||
| 			jenkins: &v1alpha1.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: &v1alpha1.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) { | ||||
| 			k8sClient := fake.NewFakeClient() | ||||
| 			logger := logf.ZapLogger(false) | ||||
| 			b := &AmazonS3Backup{} | ||||
| 			if tt.secret != nil { | ||||
| 				e := k8sClient.Create(context.TODO(), tt.secret) | ||||
| 				assert.NoError(t, e) | ||||
| 			} | ||||
| 			got, err := b.IsConfigurationValidForUserPhase(k8sClient, *tt.jenkins, logger) | ||||
| 			if tt.wantErr { | ||||
| 				assert.Error(t, err) | ||||
| 			} else { | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			assert.Equal(t, tt.want, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -1,157 +0,0 @@ | |||
| package backup | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup/aws" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup/nobackup" | ||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 
 | ||||
| 	"github.com/go-logr/logr" | ||||
| 	"github.com/pkg/errors" | ||||
| 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	restoreJobName = constants.OperatorName + "-restore-backup" | ||||
| ) | ||||
| 
 | ||||
| // Provider defines API of backup providers
 | ||||
| type Provider interface { | ||||
| 	GetRestoreJobXML(jenkins v1alpha1.Jenkins) (string, error) | ||||
| 	GetBackupJobXML(jenkins v1alpha1.Jenkins) (string, error) | ||||
| 	IsConfigurationValidForBasePhase(jenkins v1alpha1.Jenkins, logger logr.Logger) bool | ||||
| 	IsConfigurationValidForUserPhase(k8sClient k8s.Client, jenkins v1alpha1.Jenkins, logger logr.Logger) (bool, error) | ||||
| 	GetRequiredPlugins() map[string][]plugins.Plugin | ||||
| } | ||||
| 
 | ||||
| // Backup defines backup manager which is responsible of backup of jobs history
 | ||||
| type Backup struct { | ||||
| 	jenkins       *v1alpha1.Jenkins | ||||
| 	k8sClient     k8s.Client | ||||
| 	logger        logr.Logger | ||||
| 	jenkinsClient jenkinsclient.Jenkins | ||||
| } | ||||
| 
 | ||||
| // New returns instance of backup manager
 | ||||
| func New(jenkins *v1alpha1.Jenkins, k8sClient k8s.Client, logger logr.Logger, jenkinsClient jenkinsclient.Jenkins) *Backup { | ||||
| 	return &Backup{jenkins: jenkins, k8sClient: k8sClient, logger: logger, jenkinsClient: jenkinsClient} | ||||
| } | ||||
| 
 | ||||
| // EnsureRestoreJob creates and updates Jenkins job used to restore backup
 | ||||
| func (b *Backup) EnsureRestoreJob() error { | ||||
| 	if b.jenkins.Status.UserConfigurationCompletedTime == nil { | ||||
| 		provider, err := GetBackupProvider(b.jenkins.Spec.Backup) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		restoreJobXML, err := provider.GetRestoreJobXML(*b.jenkins) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		_, created, err := b.jenkinsClient.CreateOrUpdateJob(restoreJobXML, restoreJobName) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if created { | ||||
| 			b.logger.Info(fmt.Sprintf("'%s' job has been created", restoreJobName)) | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RestoreBackup restores backup
 | ||||
| func (b *Backup) RestoreBackup() (reconcile.Result, error) { | ||||
| 	if !b.jenkins.Status.BackupRestored && b.jenkins.Status.UserConfigurationCompletedTime == nil { | ||||
| 		jobsClient := jobs.New(b.jenkinsClient, b.k8sClient, b.logger) | ||||
| 
 | ||||
| 		hash := "hash-restore" // it can be hardcoded because restore job can be run only once
 | ||||
| 		done, err := jobsClient.EnsureBuildJob(restoreJobName, hash, map[string]string{}, b.jenkins, true) | ||||
| 		if err != nil { | ||||
| 			// build failed and can be recovered - retry build and requeue reconciliation loop with timeout
 | ||||
| 			if err == jobs.ErrorBuildFailed { | ||||
| 				return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil | ||||
| 			} | ||||
| 			// build failed and cannot be recovered
 | ||||
| 			if err == jobs.ErrorUnrecoverableBuildFailed { | ||||
| 				b.logger.Info(fmt.Sprintf("Restore backup can not be performed. Please check backup configuration in CR and credentials in secret '%s'.", resources.GetBackupCredentialsSecretName(b.jenkins))) | ||||
| 				b.logger.Info(fmt.Sprintf("You can also check '%s' job logs in Jenkins", constants.BackupJobName)) | ||||
| 				return reconcile.Result{}, nil | ||||
| 			} | ||||
| 			// unexpected error - requeue reconciliation loop
 | ||||
| 			return reconcile.Result{}, err | ||||
| 		} | ||||
| 		// build not finished yet - requeue reconciliation loop with timeout
 | ||||
| 		if !done { | ||||
| 			return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil | ||||
| 		} | ||||
| 
 | ||||
| 		b.jenkins.Status.BackupRestored = true | ||||
| 		err = b.k8sClient.Update(context.TODO(), b.jenkins) | ||||
| 		return reconcile.Result{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return reconcile.Result{}, nil | ||||
| } | ||||
| 
 | ||||
| // EnsureBackupJob creates and updates Jenkins job used to backup
 | ||||
| func (b *Backup) EnsureBackupJob() error { | ||||
| 	provider, err := GetBackupProvider(b.jenkins.Spec.Backup) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	backupJobXML, err := provider.GetBackupJobXML(*b.jenkins) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	_, created, err := b.jenkinsClient.CreateOrUpdateJob(backupJobXML, constants.BackupJobName) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if created { | ||||
| 		b.logger.Info(fmt.Sprintf("'%s' job has been created", constants.BackupJobName)) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetBackupProvider returns backup provider by type
 | ||||
| func GetBackupProvider(backupType v1alpha1.JenkinsBackup) (Provider, error) { | ||||
| 	switch backupType { | ||||
| 	case v1alpha1.JenkinsBackupTypeNoBackup: | ||||
| 		return &nobackup.NoBackup{}, nil | ||||
| 	case v1alpha1.JenkinsBackupTypeAmazonS3: | ||||
| 		return &aws.AmazonS3Backup{}, nil | ||||
| 	default: | ||||
| 		return nil, errors.Errorf("Invalid BackupManager type '%s'", backupType) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetPluginsRequiredByAllBackupProviders returns plugins required by all backup providers
 | ||||
| func GetPluginsRequiredByAllBackupProviders() map[string][]plugins.Plugin { | ||||
| 	allPlugins := map[string][]plugins.Plugin{} | ||||
| 	for _, provider := range getAllProviders() { | ||||
| 		for key, value := range provider.GetRequiredPlugins() { | ||||
| 			allPlugins[key] = value | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return allPlugins | ||||
| } | ||||
| 
 | ||||
| func getAllProviders() []Provider { | ||||
| 	return []Provider{ | ||||
| 		&nobackup.NoBackup{}, &aws.AmazonS3Backup{}, | ||||
| 	} | ||||
| } | ||||
|  | @ -1,52 +0,0 @@ | |||
| package nobackup | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 
 | ||||
| 	"github.com/go-logr/logr" | ||||
| 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| ) | ||||
| 
 | ||||
| // NoBackup is a backup strategy where there is no backup
 | ||||
| type NoBackup struct{} | ||||
| 
 | ||||
| var emptyJob = `<?xml version='1.1' encoding='UTF-8'?> | ||||
| <flow-definition plugin="workflow-job@2.31"> | ||||
|   <actions/> | ||||
|   <description></description> | ||||
|   <keepDependencies>false</keepDependencies> | ||||
|   <properties></properties> | ||||
|   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61"> | ||||
|     <script></script> | ||||
|     <sandbox>false</sandbox> | ||||
|   </definition> | ||||
|   <triggers/> | ||||
|   <disabled>false</disabled> | ||||
| </flow-definition> | ||||
| ` | ||||
| 
 | ||||
| // GetRestoreJobXML returns Jenkins restore backup job config XML
 | ||||
| func (b *NoBackup) GetRestoreJobXML(jenkins v1alpha1.Jenkins) (string, error) { | ||||
| 	return emptyJob, nil | ||||
| } | ||||
| 
 | ||||
| // GetBackupJobXML returns Jenkins backup job config XML
 | ||||
| func (b *NoBackup) GetBackupJobXML(jenkins v1alpha1.Jenkins) (string, error) { | ||||
| 	return emptyJob, nil | ||||
| } | ||||
| 
 | ||||
| // IsConfigurationValidForBasePhase validates if user provided valid configuration of backup for base phase
 | ||||
| func (b *NoBackup) IsConfigurationValidForBasePhase(jenkins v1alpha1.Jenkins, logger logr.Logger) bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // IsConfigurationValidForUserPhase validates if user provided valid configuration of backup for user phase
 | ||||
| func (b *NoBackup) IsConfigurationValidForUserPhase(k8sClient k8s.Client, jenkins v1alpha1.Jenkins, logger logr.Logger) (bool, error) { | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| // GetRequiredPlugins returns all required Jenkins plugins by this backup strategy
 | ||||
| func (b *NoBackup) GetRequiredPlugins() map[string][]plugins.Plugin { | ||||
| 	return map[string][]plugins.Plugin{} | ||||
| } | ||||
|  | @ -7,7 +7,6 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup" | ||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
|  | @ -62,16 +61,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki | |||
| 		return reconcile.Result{}, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	pluginsRequiredByAllBackupProviders := backup.GetPluginsRequiredByAllBackupProviders() | ||||
| 	result, err := r.ensurePluginsRequiredByAllBackupProviders(pluginsRequiredByAllBackupProviders) | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, nil, err | ||||
| 	} | ||||
| 	if result.Requeue { | ||||
| 		return result, nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	result, err = r.ensureJenkinsMasterPod(metaObject) | ||||
| 	result, err := r.ensureJenkinsMasterPod(metaObject) | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, nil, err | ||||
| 	} | ||||
|  | @ -95,7 +85,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki | |||
| 	} | ||||
| 	r.logger.V(log.VDebug).Info("Jenkins API client set") | ||||
| 
 | ||||
| 	ok, err := r.verifyPlugins(jenkinsClient, plugins.BasePluginsMap, pluginsRequiredByAllBackupProviders) | ||||
| 	ok, err := r.verifyPlugins(jenkinsClient, plugins.BasePluginsMap) | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, nil, err | ||||
| 	} | ||||
|  | @ -144,11 +134,6 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod | |||
| 	} | ||||
| 	r.logger.V(log.VDebug).Info("Service is present") | ||||
| 
 | ||||
| 	if err := r.createBackupCredentialsSecret(metaObject); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	r.logger.V(log.VDebug).Info("Backup credentials secret is present") | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -473,23 +458,6 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClien | |||
| 	return reconcile.Result{}, nil | ||||
| } | ||||
| 
 | ||||
| func (r *ReconcileJenkinsBaseConfiguration) createBackupCredentialsSecret(meta metav1.ObjectMeta) error { | ||||
| 	currentSecret := &corev1.Secret{} | ||||
| 	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetBackupCredentialsSecretName(r.jenkins), Namespace: r.jenkins.Namespace}, currentSecret) | ||||
| 	if err != nil && errors.IsNotFound(err) { | ||||
| 		return r.k8sClient.Create(context.TODO(), resources.NewBackupCredentialsSecret(r.jenkins)) | ||||
| 	} else if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	valid := r.verifyLabelsForWatchedResource(currentSecret) | ||||
| 	if !valid { | ||||
| 		currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins) | ||||
| 		return r.k8sClient.Update(context.TODO(), currentSecret) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (r *ReconcileJenkinsBaseConfiguration) verifyLabelsForWatchedResource(object metav1.Object) bool { | ||||
| 	requiredLabels := resources.BuildLabelsForWatchedResources(r.jenkins) | ||||
| 	for key, value := range requiredLabels { | ||||
|  | @ -500,28 +468,3 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyLabelsForWatchedResource(objec | |||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (r *ReconcileJenkinsBaseConfiguration) ensurePluginsRequiredByAllBackupProviders(requiredPlugins map[string][]plugins.Plugin) (reconcile.Result, error) { | ||||
| 	copiedPlugins := map[string][]string{} | ||||
| 	for key, value := range r.jenkins.Spec.Master.Plugins { | ||||
| 		copiedPlugins[key] = value | ||||
| 	} | ||||
| 	for key, value := range requiredPlugins { | ||||
| 		copiedPlugins[key] = func() []string { | ||||
| 			var pluginsWithVersion []string | ||||
| 			for _, plugin := range value { | ||||
| 				pluginsWithVersion = append(pluginsWithVersion, plugin.String()) | ||||
| 			} | ||||
| 			return pluginsWithVersion | ||||
| 		}() | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(r.jenkins.Spec.Master.Plugins, copiedPlugins) { | ||||
| 		r.logger.Info("Adding plugins required by backup providers to '.spec.master.plugins'") | ||||
| 		r.jenkins.Spec.Master.Plugins = copiedPlugins | ||||
| 		err := r.k8sClient.Update(context.TODO(), r.jenkins) | ||||
| 		return reconcile.Result{Requeue: true}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return reconcile.Result{}, nil | ||||
| } | ||||
|  |  | |||
|  | @ -1,116 +0,0 @@ | |||
| package base | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"k8s.io/client-go/kubernetes/scheme" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" | ||||
| ) | ||||
| 
 | ||||
| func TestReconcileJenkinsBaseConfiguration_ensurePluginsRequiredByAllBackupProviders(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name            string | ||||
| 		jenkins         *v1alpha1.Jenkins | ||||
| 		requiredPlugins map[string][]plugins.Plugin | ||||
| 		want            reconcile.Result | ||||
| 		wantErr         bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "happy, no required plugins", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Master: v1alpha1.JenkinsMaster{ | ||||
| 						Plugins: map[string][]string{ | ||||
| 							"first-plugin:0.0.1": {"second-plugin:0.0.1"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want:    reconcile.Result{Requeue: false}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "happy, required plugins are set", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Master: v1alpha1.JenkinsMaster{ | ||||
| 						Plugins: map[string][]string{ | ||||
| 							"first-plugin:0.0.1": {"second-plugin:0.0.1"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			requiredPlugins: map[string][]plugins.Plugin{ | ||||
| 				"first-plugin:0.0.1": {plugins.Must(plugins.New("second-plugin:0.0.1"))}, | ||||
| 			}, | ||||
| 			want:    reconcile.Result{Requeue: false}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "happy, jenkins CR must be updated", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Master: v1alpha1.JenkinsMaster{ | ||||
| 						Plugins: map[string][]string{ | ||||
| 							"first-plugin:0.0.1": {"second-plugin:0.0.1"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			requiredPlugins: map[string][]plugins.Plugin{ | ||||
| 				"first-plugin:0.0.1": {plugins.Must(plugins.New("second-plugin:0.0.1"))}, | ||||
| 				"third-plugin:0.0.1": {}, | ||||
| 			}, | ||||
| 			want:    reconcile.Result{Requeue: true}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "happy, jenkins CR must be updated", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Master: v1alpha1.JenkinsMaster{ | ||||
| 						Plugins: map[string][]string{ | ||||
| 							"first-plugin:0.0.1": {"second-plugin:0.0.1"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			requiredPlugins: map[string][]plugins.Plugin{ | ||||
| 				"first-plugin:0.0.1": {plugins.Must(plugins.New("second-plugin:0.0.1"))}, | ||||
| 				"third-plugin:0.0.1": {plugins.Must(plugins.New("fourth-plugin:0.0.1"))}, | ||||
| 			}, | ||||
| 			want:    reconcile.Result{Requeue: true}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			err := v1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme) | ||||
| 			assert.NoError(t, err) | ||||
| 			r := &ReconcileJenkinsBaseConfiguration{ | ||||
| 				k8sClient: fake.NewFakeClient(), | ||||
| 				scheme:    nil, | ||||
| 				logger:    logf.ZapLogger(false), | ||||
| 				jenkins:   tt.jenkins, | ||||
| 				local:     false, | ||||
| 				minikube:  false, | ||||
| 			} | ||||
| 			err = r.k8sClient.Create(context.TODO(), tt.jenkins) | ||||
| 			assert.NoError(t, err) | ||||
| 			got, err := r.ensurePluginsRequiredByAllBackupProviders(tt.requiredPlugins) | ||||
| 			if tt.wantErr { | ||||
| 				assert.Error(t, err) | ||||
| 			} else { | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
| 			assert.Equal(t, tt.want, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -1,30 +0,0 @@ | |||
| package resources | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-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 *v1alpha1.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(jenkins *v1alpha1.Jenkins) *corev1.Secret { | ||||
| 	meta := metav1.ObjectMeta{ | ||||
| 		Name:      GetBackupCredentialsSecretName(jenkins), | ||||
| 		Namespace: jenkins.ObjectMeta.Namespace, | ||||
| 		Labels:    BuildLabelsForWatchedResources(jenkins), | ||||
| 	} | ||||
| 
 | ||||
| 	return &corev1.Secret{ | ||||
| 		TypeMeta:   buildSecretTypeMeta(), | ||||
| 		ObjectMeta: meta, | ||||
| 	} | ||||
| } | ||||
|  | @ -16,7 +16,7 @@ import jenkins.model.JenkinsLocationConfiguration | |||
| import hudson.model.Node.Mode | ||||
| 
 | ||||
| def jenkins = Jenkins.instance | ||||
| //Number of jobs that run simultaneously on master, currently only backup and SeedJob.
 | ||||
| //Number of jobs that run simultaneously on master, currently only SeedJob.
 | ||||
| jenkins.setNumExecutors(%d) | ||||
| //Jobs must specify that they want to run on master
 | ||||
| jenkins.setMode(Mode.EXCLUSIVE) | ||||
|  |  | |||
|  | @ -16,7 +16,6 @@ const ( | |||
| 	jenkinsScriptsVolumeName = "scripts" | ||||
| 	jenkinsScriptsVolumePath = "/var/jenkins/scripts" | ||||
| 	initScriptName           = "init.sh" | ||||
| 	backupScriptName         = "backup.sh" | ||||
| 
 | ||||
| 	jenkinsOperatorCredentialsVolumeName = "operator-credentials" | ||||
| 	jenkinsOperatorCredentialsVolumePath = "/var/jenkins/operator-credentials" | ||||
|  | @ -34,11 +33,6 @@ const ( | |||
| 	// this scripts are provided by user
 | ||||
| 	JenkinsUserConfigurationVolumePath = "/var/jenkins/user-configuration" | ||||
| 
 | ||||
| 	jenkinsBackupCredentialsVolumeName = "backup-credentials" | ||||
| 	// JenkinsBackupCredentialsVolumePath is a path where are credentials used for backup/restore
 | ||||
| 	// credentials are provided by user
 | ||||
| 	JenkinsBackupCredentialsVolumePath = "/var/jenkins/backup-credentials" | ||||
| 
 | ||||
| 	httpPortName  = "http" | ||||
| 	slavePortName = "slavelistener" | ||||
| 	// HTTPPortInt defines Jenkins master HTTP port
 | ||||
|  | @ -84,16 +78,6 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | |||
| 						"bash", | ||||
| 						fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName), | ||||
| 					}, | ||||
| 					Lifecycle: &corev1.Lifecycle{ | ||||
| 						PreStop: &corev1.Handler{ | ||||
| 							Exec: &corev1.ExecAction{ | ||||
| 								Command: []string{ | ||||
| 									"bash", | ||||
| 									fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, backupScriptName), | ||||
| 								}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					LivenessProbe: &corev1.Probe{ | ||||
| 						Handler: corev1.Handler{ | ||||
| 							HTTPGet: &corev1.HTTPGetAction{ | ||||
|  | @ -168,11 +152,6 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | |||
| 							MountPath: jenkinsOperatorCredentialsVolumePath, | ||||
| 							ReadOnly:  true, | ||||
| 						}, | ||||
| 						{ | ||||
| 							Name:      jenkinsBackupCredentialsVolumeName, | ||||
| 							MountPath: JenkinsBackupCredentialsVolumePath, | ||||
| 							ReadOnly:  true, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
|  | @ -231,14 +210,6 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins | |||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					Name: jenkinsBackupCredentialsVolumeName, | ||||
| 					VolumeSource: corev1.VolumeSource{ | ||||
| 						Secret: &corev1.SecretVolumeSource{ | ||||
| 							SecretName: GetBackupCredentialsSecretName(jenkins), | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  |  | |||
|  | @ -264,70 +264,6 @@ echo "Installing plugins - end" | |||
| /sbin/tini -s -- /usr/local/bin/jenkins.sh | ||||
| `)) | ||||
| 
 | ||||
| const backupBashFmt = `#!/usr/bin/env bash | ||||
| 
 | ||||
| # don't add set -e | ||||
| 
 | ||||
| JENKINS_SERVER="http://%s:$(cat %s/%s)@localhost:%d" | ||||
| JOB="%s" | ||||
| JOB_QUERY=/job/${JOB} | ||||
| 
 | ||||
| echo 'Starting the build' | ||||
| curl -f -v -X POST "${JENKINS_SERVER}${JOB_QUERY}/build?delay=0sec" || exit -1 | ||||
| sleep 3 # give some time for Jenkins to update builds numbers | ||||
| 
 | ||||
| BUILD_STATUS_QUERY=/lastBuild/api/json | ||||
| 
 | ||||
| CURRENT_BUILD_NUMBER_QUERY=/lastBuild/buildNumber | ||||
| CURRENT_BUILD_JSON=$(curl -s -f "${JENKINS_SERVER}${JOB_QUERY}${CURRENT_BUILD_NUMBER_QUERY}") | ||||
| LAST_STABLE_BUILD_NUMBER_QUERY=/lastStableBuild/buildNumber | ||||
| 
 | ||||
| check_build() | ||||
| { | ||||
|     GOOD_BUILD="Last build successful. " | ||||
|     BAD_BUILD="Last build failed. " | ||||
|     JOB_STATUS_JSON=$(curl -s -f "${JENKINS_SERVER}${JOB_QUERY}${BUILD_STATUS_QUERY}") | ||||
|     RESULT=$(echo "${JOB_STATUS_JSON}" | sed -n 's/.*"result":\([\"A-Za-z]*\),.*/\1/p') | ||||
|     CURRENT_BUILD_NUMBER=${CURRENT_BUILD_JSON} | ||||
|     LAST_STABLE_BUILD_JSON=$(curl --silent "${JENKINS_SERVER}${JOB_QUERY}${LAST_STABLE_BUILD_NUMBER_QUERY}") | ||||
|     LAST_STABLE_BUILD_NUMBER=${LAST_STABLE_BUILD_JSON} | ||||
|     LAST_BUILD_STATUS=${GOOD_BUILD} | ||||
|     echo "${LAST_STABLE_BUILD_NUMBER}" | grep "is not available" > /dev/null | ||||
|     GREP_RETURN_CODE=$? | ||||
|     if [[ ${GREP_RETURN_CODE} -ne 0 ]] | ||||
|     then | ||||
|         if [[ $(expr ${CURRENT_BUILD_NUMBER} - 1) -gt ${LAST_STABLE_BUILD_NUMBER} ]] | ||||
|         then | ||||
|             LAST_BUILD_STATUS=${BAD_BUILD} | ||||
|         fi | ||||
|     fi | ||||
| 
 | ||||
|     if [[ "${RESULT}" = "null" ]] | ||||
|     then | ||||
|         echo "${LAST_BUILD_STATUS}Building ${JOB} ${CURRENT_BUILD_NUMBER}... last stable was ${LAST_STABLE_BUILD_NUMBER}" | ||||
|     elif [[ "${RESULT}" = "\"SUCCESS\"" ]] | ||||
|     then | ||||
|         echo "${LAST_BUILD_STATUS}${JOB} ${CURRENT_BUILD_NUMBER} completed successfully." | ||||
|         exit 0 | ||||
|     elif [[ "${RESULT}" = "\"FAILURE\"" ]] | ||||
|     then | ||||
|         LAST_BUILD_STATUS=${BAD_BUILD} | ||||
|         echo "${LAST_BUILD_STATUS}${JOB} ${CURRENT_BUILD_NUMBER} failed" | ||||
|         exit -1 | ||||
|     else | ||||
|         LAST_BUILD_STATUS=${BAD_BUILD} | ||||
|         echo "${LAST_BUILD_STATUS}${JOB} ${CURRENT_BUILD_NUMBER} status unknown - '${RESULT}'" | ||||
|         exit -1 | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| while [[ true ]] | ||||
| do | ||||
|     check_build | ||||
|     sleep 1 | ||||
| done | ||||
| ` | ||||
| 
 | ||||
| func buildConfigMapTypeMeta() metav1.TypeMeta { | ||||
| 	return metav1.TypeMeta{ | ||||
| 		Kind:       "ConfigMap", | ||||
|  | @ -377,8 +313,6 @@ func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins) (*co | |||
| 		Data: map[string]string{ | ||||
| 			initScriptName:        *initBashScript, | ||||
| 			installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, jenkinsHomePath), | ||||
| 			backupScriptName: fmt.Sprintf(backupBashFmt, | ||||
| 				OperatorUserName, jenkinsOperatorCredentialsVolumePath, OperatorCredentialsSecretTokenKey, HTTPPortInt, constants.BackupJobName), | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  |  | |||
|  | @ -1,20 +1,14 @@ | |||
| package base | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 	"github.com/jenkinsci/kubernetes-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 ( | ||||
|  | @ -38,20 +32,6 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) | |||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	valid, err := r.verifyBackup() | ||||
| 	if !valid || err != nil { | ||||
| 		return valid, err | ||||
| 	} | ||||
| 
 | ||||
| 	backupProvider, err := backup.GetBackupProvider(r.jenkins.Spec.Backup) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if !backupProvider.IsConfigurationValidForBasePhase(*r.jenkins, r.logger) { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -84,39 +64,3 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(pluginsWithVersions | |||
| 
 | ||||
| 	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 _, backupType := range v1alpha1.AllowedJenkinsBackups { | ||||
| 		if r.jenkins.Spec.Backup == backupType { | ||||
| 			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'", v1alpha1.AllowedJenkinsBackups)) | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if r.jenkins.Spec.Backup == v1alpha1.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 | ||||
| } | ||||
|  |  | |||
|  | @ -1,16 +1,10 @@ | |||
| package base | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/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" | ||||
| ) | ||||
| 
 | ||||
|  | @ -62,87 +56,3 @@ func TestValidatePlugins(t *testing.T) { | |||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestReconcileJenkinsBaseConfiguration_verifyBackup(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		jenkins *v1alpha1.Jenkins | ||||
| 		secret  *corev1.Secret | ||||
| 		want    bool | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "happy, no backup", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Backup: v1alpha1.JenkinsBackupTypeNoBackup, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want:    true, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "happy", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Backup: v1alpha1.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: &v1alpha1.Jenkins{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, | ||||
| 				Spec: v1alpha1.JenkinsSpec{ | ||||
| 					Backup: v1alpha1.JenkinsBackupTypeAmazonS3, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want:    false, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "fail, empty backup type", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace-name", Name: "jenkins-cr-name"}, | ||||
| 				Spec: v1alpha1.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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup" | ||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||
|  | @ -41,21 +40,8 @@ func New(k8sClient k8s.Client, jenkinsClient jenkinsclient.Jenkins, logger logr. | |||
| 
 | ||||
| // Reconcile it's a main reconciliation loop for user supplied configuration
 | ||||
| func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | ||||
| 	backupManager := backup.New(r.jenkins, r.k8sClient, r.logger, r.jenkinsClient) | ||||
| 	if err := backupManager.EnsureRestoreJob(); err != nil { | ||||
| 		return reconcile.Result{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	result, err := backupManager.RestoreBackup() | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, err | ||||
| 	} | ||||
| 	if result.Requeue { | ||||
| 		return result, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// reconcile seed jobs
 | ||||
| 	result, err = r.ensureSeedJobs() | ||||
| 	result, err := r.ensureSeedJobs() | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, err | ||||
| 	} | ||||
|  | @ -71,11 +57,6 @@ func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | |||
| 		return result, nil | ||||
| 	} | ||||
| 
 | ||||
| 	err = backupManager.EnsureBackupJob() | ||||
| 	if err != nil { | ||||
| 		return reconcile.Result{}, err | ||||
| 	} | ||||
| 
 | ||||
| 	return reconcile.Result{}, nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/backup" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||
| 
 | ||||
| 	"k8s.io/api/core/v1" | ||||
|  | @ -24,12 +23,7 @@ func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, | |||
| 		return valid, err | ||||
| 	} | ||||
| 
 | ||||
| 	backupProvider, err := backup.GetBackupProvider(r.jenkins.Spec.Backup) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	return backupProvider.IsConfigurationValidForUserPhase(r.k8sClient, *r.jenkins, r.logger) | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| func (r *ReconcileUserConfiguration) validateSeedJobs(jenkins *v1alpha1.Jenkins) (bool, error) { | ||||
|  |  | |||
|  | @ -9,14 +9,6 @@ const ( | |||
| 	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" | ||||
| 	// BackupJobName is the Jenkins job name used to backup jobs history
 | ||||
| 	BackupJobName = OperatorName + "-backup" | ||||
| 	// UserConfigurationJobName is the Jenkins job name used to configure Jenkins by groovy scripts provided by user
 | ||||
| 	UserConfigurationJobName = OperatorName + "-user-configuration" | ||||
| 	// BackupLatestFileName is the latest backup file name
 | ||||
| 	BackupLatestFileName = "build-history-latest.tar.gz" | ||||
| ) | ||||
|  |  | |||
|  | @ -212,12 +212,6 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo | |||
| 		changed = true | ||||
| 		jenkins.Spec.Master.Image = constants.DefaultJenkinsMasterImage | ||||
| 	} | ||||
| 	if len(jenkins.Spec.Backup) == 0 { | ||||
| 		logger.Info("Setting default backup strategy: " + v1alpha1.JenkinsBackupTypeNoBackup) | ||||
| 		logger.V(log.VWarn).Info("Backup is disable !!! Please configure backup in '.spec.backup'") | ||||
| 		changed = true | ||||
| 		jenkins.Spec.Backup = v1alpha1.JenkinsBackupTypeNoBackup | ||||
| 	} | ||||
| 	if len(jenkins.Spec.Master.Plugins) == 0 { | ||||
| 		logger.Info("Setting default base plugins") | ||||
| 		changed = true | ||||
|  |  | |||
|  | @ -1,157 +0,0 @@ | |||
| package e2e | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
| 
 | ||||
| 	"github.com/aws/aws-sdk-go/aws" | ||||
| 	"github.com/aws/aws-sdk-go/aws/credentials" | ||||
| 	"github.com/aws/aws-sdk-go/aws/session" | ||||
| 	"github.com/aws/aws-sdk-go/service/s3" | ||||
| 	"github.com/bndr/gojenkins" | ||||
| 	framework "github.com/operator-framework/operator-sdk/pkg/test" | ||||
| 	assert "github.com/stretchr/testify/require" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
| 
 | ||||
| type amazonS3BackupConfiguration struct { | ||||
| 	BucketName string `json:"bucketName,omitempty"` | ||||
| 	BucketPath string `json:"bucketPath,omitempty"` | ||||
| 	Region     string `json:"region,omitempty"` | ||||
| 	AccessKey  string `json:"accessKey,omitempty"` | ||||
| 	SecretKey  string `json:"secretKey,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func TestAmazonS3Backup(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	if amazonS3BackupConfigurationFile == nil || len(*amazonS3BackupConfigurationFile) == 0 { | ||||
| 		t.Skipf("Skipping testing because flag '%s' is not set", amazonS3BackupConfigurationParameterName) | ||||
| 	} | ||||
| 	backupConfig := loadAmazonS3BackupConfig(t) | ||||
| 
 | ||||
| 	s3Client := createS3Client(t, backupConfig) | ||||
| 	deleteAllBackupsInS3(t, backupConfig, s3Client) | ||||
| 	namespace, ctx := setupTest(t) | ||||
| 	defer ctx.Cleanup() // Deletes test namespace
 | ||||
| 
 | ||||
| 	jenkins := createJenkinsCRWithAmazonS3Backup(t, namespace, backupConfig) | ||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||
| 
 | ||||
| 	restartJenkinsMasterPod(t, jenkins) | ||||
| 	waitForRecreateJenkinsMasterPod(t, jenkins) | ||||
| 
 | ||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||
| 	jenkinsClient := verifyJenkinsAPIConnection(t, jenkins) | ||||
| 	verifyIfBackupAndRestoreWasSuccessfull(t, jenkinsClient, backupConfig, s3Client) | ||||
| } | ||||
| 
 | ||||
| func createS3Client(t *testing.T, backupConfig amazonS3BackupConfiguration) *s3.S3 { | ||||
| 	sess, err := session.NewSession(&aws.Config{ | ||||
| 		Region:      aws.String(backupConfig.Region), | ||||
| 		Credentials: credentials.NewStaticCredentials(backupConfig.AccessKey, backupConfig.SecretKey, ""), | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	return s3.New(sess) | ||||
| } | ||||
| 
 | ||||
| func deleteAllBackupsInS3(t *testing.T, backupConfig amazonS3BackupConfiguration, s3Client *s3.S3) { | ||||
| 	input := &s3.DeleteObjectInput{ | ||||
| 		Bucket: aws.String(backupConfig.BucketName), | ||||
| 		Key:    aws.String(backupConfig.BucketPath), | ||||
| 	} | ||||
| 
 | ||||
| 	_, err := s3Client.DeleteObject(input) | ||||
| 	assert.NoError(t, err) | ||||
| } | ||||
| 
 | ||||
| func verifyIfBackupAndRestoreWasSuccessfull(t *testing.T, jenkinsClient *gojenkins.Jenkins, backupConfig amazonS3BackupConfiguration, s3Client *s3.S3) { | ||||
| 	job, err := jenkinsClient.GetJob(constants.UserConfigurationJobName) | ||||
| 	assert.NoError(t, err) | ||||
| 	// jenkins runs twice(2) + 1 as next build number
 | ||||
| 	assert.Equal(t, int64(3), job.Raw.NextBuildNumber) | ||||
| 
 | ||||
| 	listObjects, err := s3Client.ListObjects(&s3.ListObjectsInput{ | ||||
| 		Bucket: aws.String(backupConfig.BucketName), | ||||
| 		Marker: aws.String(backupConfig.BucketPath), | ||||
| 	}) | ||||
| 	assert.NoError(t, err) | ||||
| 	t.Logf("Backups in S3:%+v", listObjects.Contents) | ||||
| 	assert.Equal(t, len(listObjects.Contents), 2) | ||||
| 	latestBackupFound := false | ||||
| 	for _, backup := range listObjects.Contents { | ||||
| 		if *backup.Key == fmt.Sprintf("%s/%s", backupConfig.BucketPath, constants.BackupLatestFileName) { | ||||
| 			latestBackupFound = true | ||||
| 		} | ||||
| 	} | ||||
| 	assert.True(t, latestBackupFound) | ||||
| } | ||||
| 
 | ||||
| func createJenkinsCRWithAmazonS3Backup(t *testing.T, namespace string, backupConfig amazonS3BackupConfiguration) *v1alpha1.Jenkins { | ||||
| 	jenkins := &v1alpha1.Jenkins{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      "e2e", | ||||
| 			Namespace: namespace, | ||||
| 		}, | ||||
| 		Spec: v1alpha1.JenkinsSpec{ | ||||
| 			Backup: v1alpha1.JenkinsBackupTypeAmazonS3, | ||||
| 			BackupAmazonS3: v1alpha1.JenkinsBackupAmazonS3{ | ||||
| 				Region:     backupConfig.Region, | ||||
| 				BucketPath: backupConfig.BucketPath, | ||||
| 				BucketName: backupConfig.BucketName, | ||||
| 			}, | ||||
| 			Master: v1alpha1.JenkinsMaster{ | ||||
| 				Image: "jenkins/jenkins", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	t.Logf("Jenkins CR %+v", *jenkins) | ||||
| 	err := framework.Global.Client.Create(context.TODO(), jenkins, nil) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	backupCredentialsSecret := &corev1.Secret{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      resources.GetBackupCredentialsSecretName(jenkins), | ||||
| 			Namespace: namespace, | ||||
| 		}, | ||||
| 		Data: map[string][]byte{ | ||||
| 			constants.BackupAmazonS3SecretAccessKey: []byte(backupConfig.AccessKey), | ||||
| 			constants.BackupAmazonS3SecretSecretKey: []byte(backupConfig.SecretKey), | ||||
| 		}, | ||||
| 	} | ||||
| 	err = framework.Global.Client.Create(context.TODO(), backupCredentialsSecret, nil) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	return jenkins | ||||
| } | ||||
| 
 | ||||
| func loadAmazonS3BackupConfig(t *testing.T) amazonS3BackupConfiguration { | ||||
| 	jsonFile, err := os.Open(*amazonS3BackupConfigurationFile) | ||||
| 	assert.NoError(t, err) | ||||
| 	defer func() { _ = jsonFile.Close() }() | ||||
| 
 | ||||
| 	byteValue, err := ioutil.ReadAll(jsonFile) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	var result amazonS3BackupConfiguration | ||||
| 	err = json.Unmarshal([]byte(byteValue), &result) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.NotEmpty(t, result.AccessKey) | ||||
| 	assert.NotEmpty(t, result.BucketName) | ||||
| 	assert.NotEmpty(t, result.Region) | ||||
| 	assert.NotEmpty(t, result.SecretKey) | ||||
| 	result.BucketPath = t.Name() | ||||
| 	return result | ||||
| } | ||||
|  | @ -1,7 +1,6 @@ | |||
| package e2e | ||||
| 
 | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis" | ||||
|  | @ -15,16 +14,10 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	jenkinsOperatorDeploymentName            = constants.OperatorName | ||||
| 	amazonS3BackupConfigurationParameterName = "s3BackupConfig" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	amazonS3BackupConfigurationFile *string | ||||
| 	jenkinsOperatorDeploymentName = constants.OperatorName | ||||
| ) | ||||
| 
 | ||||
| func TestMain(m *testing.M) { | ||||
| 	amazonS3BackupConfigurationFile = flag.String(amazonS3BackupConfigurationParameterName, "", "path to AWS S3 backup config") | ||||
| 	f.MainEntry(m) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue