Remove backup mechanism

This commit is contained in:
Tomasz Sęk 2019-02-14 19:36:55 +01:00
parent ab87922bd1
commit dd04c0cf5b
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
21 changed files with 11 additions and 1370 deletions

64
Gopkg.lock generated
View File

@ -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",

View File

@ -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"`

View File

@ -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

View File

@ -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(&apos;master&apos;) {
def accessKeyFilePath = &quot;` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretAccessKey + `&quot;
def secretKeyFilePath = &quot;` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretSecretKey + `&quot;
def credentialsFileName = &quot;backup-credentials&quot;
def bucketName = &quot;` + jenkins.Spec.BackupAmazonS3.BucketName + `&quot;
def bucketKey = &quot;` + jenkins.Spec.BackupAmazonS3.BucketPath + `&quot;
def region = &quot;` + jenkins.Spec.BackupAmazonS3.Region + `&quot;
def latestBackupFile = &quot;` + constants.BackupLatestFileName + `&quot;
def jenkinsHome = env.JENKINS_HOME
def latestBackupKey = &quot;${bucketKey}/${latestBackupFile}&quot;
def tmpBackupPath = &quot;/tmp/restore.tar.gz&quot;
boolean backupExists = true
def accessKey = new java.io.File(accessKeyFilePath).text
def secretKey = new java.io.File(secretKeyFilePath).text
sh &quot;touch ${env.WORKSPACE}/${credentialsFileName}&quot;
new java.io.File(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;).write(&quot;accessKey=${accessKey}\nsecretKey=${secretKey}\n&quot;)
stage(&apos;Check if backup exists&apos;) {
def s3 = AmazonS3ClientBuilder
.standard()
.withCredentials(new PropertiesFileCredentialsProvider(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;))
.withRegion(region)
.build()
try {
println s3.getObjectMetadata(bucketName, latestBackupKey)
} catch (AmazonS3Exception e) {
if (e.getStatusCode() == 404) {
println &quot;There is no backup ${bucketName}/${latestBackupKey}&quot;
backupExists = false
}
}
}
if (backupExists) {
stage(&apos;Download backup&apos;) {
def s3 = AmazonS3ClientBuilder
.standard()
.withCredentials(new PropertiesFileCredentialsProvider(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;))
.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(&apos;Unpack backup&apos;) {
sh &quot;tar -C ${jenkinsHome} -zxf ${tmpBackupPath}&quot;
}
stage(&apos;Reload Jenkins&apos;) {
jenkins.model.Jenkins.getInstance().reload()
}
sh &quot;rm ${tmpBackupPath}&quot;
sh &quot;rm ${env.WORKSPACE}/${credentialsFileName}&quot;
}
}</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(&apos;master&apos;) {
def accessKeyFilePath = &quot;` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretAccessKey + `&quot;
def secretKeyFilePath = &quot;` + resources.JenkinsBackupCredentialsVolumePath + `/` + constants.BackupAmazonS3SecretSecretKey + `&quot;
def credentialsFileName = &quot;backup-credentials&quot;
def bucketName = &quot;` + jenkins.Spec.BackupAmazonS3.BucketName + `&quot;
def bucketKey = &quot;` + jenkins.Spec.BackupAmazonS3.BucketPath + `&quot;
def region = &quot;` + jenkins.Spec.BackupAmazonS3.Region + `&quot;
def latestBackupFile = &quot;` + constants.BackupLatestFileName + `&quot;
def jenkinsHome = env.JENKINS_HOME
def backupTime = sh(script: &quot;date &apos;+%Y-%m-%d-%H-%M&apos;&quot;, returnStdout: true).trim()
def tmpBackupPath = &quot;/tmp/backup.tar.gz&quot;
def backupKey = &quot;${bucketKey}/build-history-${backupTime}.tar.gz&quot;
def latestBackupKey = &quot;${bucketKey}/${latestBackupFile}&quot;
def accessKey = new java.io.File(accessKeyFilePath).text
def secretKey = new java.io.File(secretKeyFilePath).text
sh &quot;touch ${env.WORKSPACE}/${credentialsFileName}&quot;
new java.io.File(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;).write(&quot;accessKey=${accessKey}\nsecretKey=${secretKey}\n&quot;)
stage(&apos;Create backup archive&apos;) {
println &quot;Creating backup archive to ${tmpBackupPath}&quot;
sh &quot;tar -C ${jenkinsHome} -z --exclude jobs/*/config.xml --exclude jobs/*/workspace* --exclude jobs/*/simulation.log -c config-history jobs -f ${tmpBackupPath}&quot;
}
stage(&apos;Upload backup&apos;) {
def s3 = AmazonS3ClientBuilder
.standard()
.withCredentials(new PropertiesFileCredentialsProvider(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;))
.withRegion(region)
.build()
println &quot;Uploading backup to ${bucketName}/${backupKey}&quot;
s3.putObject(bucketName, backupKey, new File(tmpBackupPath))
println s3.getObjectMetadata(bucketName, backupKey)
}
stage(&apos;Copy backup&apos;) {
def s3 = AmazonS3ClientBuilder
.standard()
.withCredentials(new PropertiesFileCredentialsProvider(&quot;${env.WORKSPACE}/${credentialsFileName}&quot;))
.withRegion(region)
.build()
println &quot;Coping backup ${bucketName}${backupKey} to ${bucketName}/${latestBackupKey}&quot;
s3.copyObject(bucketName, backupKey, bucketName, latestBackupKey)
println s3.getObjectMetadata(bucketName, latestBackupKey)
}
sh &quot;rm ${tmpBackupPath}&quot;
sh &quot;rm ${env.WORKSPACE}/${credentialsFileName}&quot;
}</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)),
},
}
}

View File

@ -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)
})
}
}

View File

@ -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{},
}
}

View File

@ -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{}
}

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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),
},
},
},
},
},
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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"
)

View File

@ -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

View File

@ -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
}

View File

@ -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)
}