241 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
package aws
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
 | 
						|
	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/controller/jenkins/plugins"
 | 
						|
	"github.com/VirtusLab/jenkins-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 virtuslabv1alpha1.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 virtuslabv1alpha1.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 virtuslabv1alpha1.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 virtuslabv1alpha1.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)),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 |