#4 [WIP] Backup and restore
This commit is contained in:
		
							parent
							
								
									66e9512c80
								
							
						
					
					
						commit
						2d501b00d5
					
				|  | @ -25,6 +25,7 @@ import ( | ||||||
| 	sdkVersion "github.com/operator-framework/operator-sdk/version" | 	sdkVersion "github.com/operator-framework/operator-sdk/version" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"github.com/spf13/pflag" | 	"github.com/spf13/pflag" | ||||||
|  | 	"k8s.io/client-go/kubernetes" | ||||||
| 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client/config" | 	"sigs.k8s.io/controller-runtime/pkg/client/config" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/manager" | 	"sigs.k8s.io/controller-runtime/pkg/manager" | ||||||
|  | @ -107,8 +108,13 @@ func main() { | ||||||
| 		fatal(errors.Wrap(err, "failed to create manager"), *debug) | 		fatal(errors.Wrap(err, "failed to create manager"), *debug) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	clientSet, err := kubernetes.NewForConfig(cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fatal(errors.Wrap(err, "failed to create Kubernetes client set"), *debug) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// setup Jenkins controller
 | 	// setup Jenkins controller
 | ||||||
| 	if err := jenkins.Add(mgr, *local, *minikube, events); err != nil { | 	if err := jenkins.Add(mgr, *local, *minikube, events, *clientSet, *cfg); err != nil { | ||||||
| 		fatal(errors.Wrap(err, "failed to setup controllers"), *debug) | 		fatal(errors.Wrap(err, "failed to setup controllers"), *debug) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -8,6 +8,7 @@ require ( | ||||||
| 	github.com/coreos/prometheus-operator v0.26.0 // indirect | 	github.com/coreos/prometheus-operator v0.26.0 // indirect | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | ||||||
| 	github.com/docker/distribution v2.7.1+incompatible | 	github.com/docker/distribution v2.7.1+incompatible | ||||||
|  | 	github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect | ||||||
| 	github.com/emicklei/go-restful v2.8.1+incompatible // indirect | 	github.com/emicklei/go-restful v2.8.1+incompatible // indirect | ||||||
| 	github.com/go-logr/logr v0.1.0 | 	github.com/go-logr/logr v0.1.0 | ||||||
| 	github.com/go-logr/zapr v0.1.0 | 	github.com/go-logr/zapr v0.1.0 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -35,6 +35,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC | ||||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||||
| github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= | ||||||
| github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||||
|  | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= | ||||||
|  | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= | ||||||
| github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | ||||||
| github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | ||||||
| github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ type JenkinsSpec struct { | ||||||
| 	SeedJobs     []SeedJob     `json:"seedJobs,omitempty"` | 	SeedJobs     []SeedJob     `json:"seedJobs,omitempty"` | ||||||
| 	Service      Service       `json:"service,omitempty"` | 	Service      Service       `json:"service,omitempty"` | ||||||
| 	SlaveService Service       `json:"slaveService,omitempty"` | 	SlaveService Service       `json:"slaveService,omitempty"` | ||||||
|  | 	Backup       Backup        `json:"backup,omitempty"` | ||||||
|  | 	Restore      Restore       `json:"restore,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Container defines Kubernetes container attributes
 | // Container defines Kubernetes container attributes
 | ||||||
|  | @ -80,6 +82,9 @@ type JenkinsStatus struct { | ||||||
| 	BaseConfigurationCompletedTime *metav1.Time `json:"baseConfigurationCompletedTime,omitempty"` | 	BaseConfigurationCompletedTime *metav1.Time `json:"baseConfigurationCompletedTime,omitempty"` | ||||||
| 	UserConfigurationCompletedTime *metav1.Time `json:"userConfigurationCompletedTime,omitempty"` | 	UserConfigurationCompletedTime *metav1.Time `json:"userConfigurationCompletedTime,omitempty"` | ||||||
| 	Builds                         []Build      `json:"builds,omitempty"` | 	Builds                         []Build      `json:"builds,omitempty"` | ||||||
|  | 	RestoredBackup                 uint64       `json:"restoredBackup,omitempty"` | ||||||
|  | 	LastBackup                     uint64       `json:"lastBackup,omitempty"` | ||||||
|  | 	PendingBackup                  uint64       `json:"pendingBackup,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // BuildStatus defines type of Jenkins build job status
 | // BuildStatus defines type of Jenkins build job status
 | ||||||
|  | @ -154,7 +159,7 @@ var AllowedJenkinsCredentialMap = map[string]string{ | ||||||
| 	string(UsernamePasswordCredentialType):    "", | 	string(UsernamePasswordCredentialType):    "", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SeedJob defined configuration for seed jobs and deploy keys
 | // SeedJob defines configuration for seed jobs and deploy keys
 | ||||||
| type SeedJob struct { | type SeedJob struct { | ||||||
| 	ID                    string                `json:"id,omitempty"` | 	ID                    string                `json:"id,omitempty"` | ||||||
| 	CredentialID          string                `json:"credentialID,omitempty"` | 	CredentialID          string                `json:"credentialID,omitempty"` | ||||||
|  | @ -165,6 +170,22 @@ type SeedJob struct { | ||||||
| 	JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` | 	JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func init() { | // Handler defines a specific action that should be taken
 | ||||||
| 	SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) | type Handler struct { | ||||||
|  | 	// Exec specifies the action to take.
 | ||||||
|  | 	Exec *corev1.ExecAction `json:"exec,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Backup defines configuration of Jenkins backup
 | ||||||
|  | type Backup struct { | ||||||
|  | 	ContainerName string  `json:"containerName"` | ||||||
|  | 	Action        Handler `json:"action"` | ||||||
|  | 	Interval      uint64  `json:"interval"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Restore defines configuration of Jenkins backup restore
 | ||||||
|  | type Restore struct { | ||||||
|  | 	ContainerName string  `json:"containerName"` | ||||||
|  | 	Action        Handler `json:"action"` | ||||||
|  | 	RecoveryOnce  uint64  `json:"recoveryOnce,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -25,3 +25,7 @@ var ( | ||||||
| 
 | 
 | ||||||
| // GetObjectKind returns Jenkins object kind
 | // GetObjectKind returns Jenkins object kind
 | ||||||
| func (in *Jenkins) GetObjectKind() schema.ObjectKind { return in } | func (in *Jenkins) GetObjectKind() schema.ObjectKind { return in } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -360,9 +360,9 @@ func (r *ReconcileJenkinsBaseConfiguration) createService(meta metav1.ObjectMeta | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) getJenkinsMasterPod(meta metav1.ObjectMeta) (*corev1.Pod, error) { | func (r *ReconcileJenkinsBaseConfiguration) getJenkinsMasterPod(meta metav1.ObjectMeta) (*corev1.Pod, error) { | ||||||
| 	jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins) | 	jenkinsMasterPodName := resources.GetJenkinsMasterPodName(*r.jenkins) | ||||||
| 	currentJenkinsMasterPod := &corev1.Pod{} | 	currentJenkinsMasterPod := &corev1.Pod{} | ||||||
| 	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPod.Name, Namespace: jenkinsMasterPod.Namespace}, currentJenkinsMasterPod) | 	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPodName, Namespace: r.jenkins.Namespace}, currentJenkinsMasterPod) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err // don't wrap error
 | 		return nil, err // don't wrap error
 | ||||||
| 	} | 	} | ||||||
|  | @ -382,6 +382,8 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O | ||||||
| 		now := metav1.Now() | 		now := metav1.Now() | ||||||
| 		r.jenkins.Status = v1alpha2.JenkinsStatus{ | 		r.jenkins.Status = v1alpha2.JenkinsStatus{ | ||||||
| 			ProvisionStartTime: &now, | 			ProvisionStartTime: &now, | ||||||
|  | 			LastBackup:         r.jenkins.Status.LastBackup, | ||||||
|  | 			PendingBackup:      r.jenkins.Status.LastBackup, | ||||||
| 		} | 		} | ||||||
| 		err = r.updateResource(r.jenkins) | 		err = r.updateResource(r.jenkins) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -407,6 +409,11 @@ func isPodTerminating(pod corev1.Pod) bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool { | func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool { | ||||||
|  | 	if r.jenkins.Spec.Restore.RecoveryOnce != 0 { | ||||||
|  | 		r.logger.Info(fmt.Sprintf("spec.restore.recoveryOnce is set, recreating pod")) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if version.Version != r.jenkins.Status.OperatorVersion { | 	if version.Version != r.jenkins.Status.OperatorVersion { | ||||||
| 		r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v', recreating pod", | 		r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v', recreating pod", | ||||||
| 			r.jenkins.Status.OperatorVersion, version.Version)) | 			r.jenkins.Status.OperatorVersion, version.Version)) | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ const ( | ||||||
| 	// JenkinsMasterContainerName is the Jenkins master container name in pod
 | 	// JenkinsMasterContainerName is the Jenkins master container name in pod
 | ||||||
| 	JenkinsMasterContainerName = "jenkins-master" | 	JenkinsMasterContainerName = "jenkins-master" | ||||||
| 	// JenkinsHomeVolumeName is the Jenkins home volume name
 | 	// JenkinsHomeVolumeName is the Jenkins home volume name
 | ||||||
| 	JenkinsHomeVolumeName = "home" | 	JenkinsHomeVolumeName = "jenkins-home" | ||||||
| 	jenkinsPath           = "/var/jenkins" | 	jenkinsPath           = "/var/jenkins" | ||||||
| 	jenkinsHomePath       = jenkinsPath + "/home" | 	jenkinsHomePath       = jenkinsPath + "/home" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,191 @@ | ||||||
|  | package backuprestore | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | ||||||
|  | 	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/log" | ||||||
|  | 
 | ||||||
|  | 	"github.com/go-logr/logr" | ||||||
|  | 	"github.com/pkg/errors" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	"k8s.io/client-go/kubernetes" | ||||||
|  | 	"k8s.io/client-go/kubernetes/scheme" | ||||||
|  | 	"k8s.io/client-go/rest" | ||||||
|  | 	"k8s.io/client-go/tools/remotecommand" | ||||||
|  | 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // BackupAndRestore represents Jenkins backup and restore client
 | ||||||
|  | type BackupAndRestore struct { | ||||||
|  | 	config    rest.Config | ||||||
|  | 	k8sClient k8s.Client | ||||||
|  | 	clientSet kubernetes.Clientset | ||||||
|  | 
 | ||||||
|  | 	jenkinsClient jenkinsclient.Jenkins | ||||||
|  | 	logger        logr.Logger | ||||||
|  | 	jenkins       *v1alpha2.Jenkins | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // New returns Jenkins backup and restore client
 | ||||||
|  | func New(k8sClient k8s.Client, clientSet kubernetes.Clientset, jenkinsClient jenkinsclient.Jenkins, | ||||||
|  | 	logger logr.Logger, jenkins *v1alpha2.Jenkins, config rest.Config) *BackupAndRestore { | ||||||
|  | 	return &BackupAndRestore{k8sClient: k8sClient, clientSet: clientSet, jenkinsClient: jenkinsClient, logger: logger, jenkins: jenkins, config: config} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Validate validates backup and restore configuration
 | ||||||
|  | func (bar *BackupAndRestore) Validate() bool { | ||||||
|  | 	valid := true | ||||||
|  | 	allContainers := map[string]v1alpha2.Container{} | ||||||
|  | 	for _, container := range bar.jenkins.Spec.Master.Containers { | ||||||
|  | 		allContainers[container.Name] = container | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	restore := bar.jenkins.Spec.Restore | ||||||
|  | 	if len(restore.ContainerName) > 0 { | ||||||
|  | 		_, found := allContainers[restore.ContainerName] | ||||||
|  | 		if !found { | ||||||
|  | 			valid = false | ||||||
|  | 			bar.logger.V(log.VWarn).Info(fmt.Sprintf("restore container '%s' not found in CR spec.master.containers", restore.ContainerName)) | ||||||
|  | 		} | ||||||
|  | 		if restore.Action.Exec == nil { | ||||||
|  | 			valid = false | ||||||
|  | 			bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.restore.action.exec is not configured")) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	backup := bar.jenkins.Spec.Backup | ||||||
|  | 	if len(backup.ContainerName) > 0 { | ||||||
|  | 		_, found := allContainers[backup.ContainerName] | ||||||
|  | 		if !found { | ||||||
|  | 			valid = false | ||||||
|  | 			bar.logger.V(log.VWarn).Info(fmt.Sprintf("backup container '%s' not found in CR spec.master.containers", backup.ContainerName)) | ||||||
|  | 		} | ||||||
|  | 		if backup.Action.Exec == nil { | ||||||
|  | 			valid = false | ||||||
|  | 			bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.backup.action.exec is not configured")) | ||||||
|  | 		} | ||||||
|  | 		if backup.Interval == 0 { | ||||||
|  | 			valid = false | ||||||
|  | 			bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.backup.interval is not configured")) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(restore.ContainerName) > 0 && len(backup.ContainerName) == 0 { | ||||||
|  | 		valid = false | ||||||
|  | 		bar.logger.V(log.VWarn).Info("spec.backup.containerName is not configured") | ||||||
|  | 	} | ||||||
|  | 	if len(backup.ContainerName) > 0 && len(restore.ContainerName) == 0 { | ||||||
|  | 		valid = false | ||||||
|  | 		bar.logger.V(log.VWarn).Info("spec.restore.containerName is not configured") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Restore performs Jenkins restore backup operation
 | ||||||
|  | func (bar *BackupAndRestore) Restore() error { | ||||||
|  | 	jenkins := bar.jenkins | ||||||
|  | 	if jenkins.Status.RestoredBackup != 0 { | ||||||
|  | 		bar.logger.V(log.VDebug).Info("Skipping restore backup, backup already restored") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if jenkins.Status.LastBackup == 0 { | ||||||
|  | 		bar.logger.Info("Skipping restore backup") | ||||||
|  | 		if jenkins.Status.PendingBackup == 0 { | ||||||
|  | 			jenkins.Status.PendingBackup = 1 | ||||||
|  | 			return bar.k8sClient.Update(context.TODO(), jenkins) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var backupNumber uint64 | ||||||
|  | 	if jenkins.Spec.Restore.RecoveryOnce != 0 { | ||||||
|  | 		backupNumber = jenkins.Spec.Restore.RecoveryOnce | ||||||
|  | 	} else { | ||||||
|  | 		backupNumber = jenkins.Status.LastBackup | ||||||
|  | 	} | ||||||
|  | 	bar.logger.Info(fmt.Sprintf("Restoring backup '%d'", backupNumber)) | ||||||
|  | 	podName := resources.GetJenkinsMasterPodName(*jenkins) | ||||||
|  | 	command := jenkins.Spec.Restore.Action.Exec.Command | ||||||
|  | 	command = append(command, fmt.Sprintf("%d", backupNumber)) | ||||||
|  | 	_, _, err := bar.exec(podName, jenkins.Spec.Restore.ContainerName, command) | ||||||
|  | 
 | ||||||
|  | 	if err == nil { | ||||||
|  | 		jenkins.Spec.Restore.RecoveryOnce = 0 | ||||||
|  | 		jenkins.Status.RestoredBackup = backupNumber | ||||||
|  | 		jenkins.Status.PendingBackup = backupNumber + 1 | ||||||
|  | 		return bar.k8sClient.Update(context.TODO(), jenkins) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//TODO reload?
 | ||||||
|  | 	//TODO after 3 fails stop
 | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Backup performs Jenkins backup operation
 | ||||||
|  | func (bar *BackupAndRestore) Backup() error { | ||||||
|  | 	jenkins := bar.jenkins | ||||||
|  | 	if jenkins.Status.PendingBackup == jenkins.Status.LastBackup { | ||||||
|  | 		bar.logger.V(log.VDebug).Info("Skipping backup") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	backupNumber := jenkins.Status.PendingBackup | ||||||
|  | 	bar.logger.Info(fmt.Sprintf("Performing backup '%d'", backupNumber)) | ||||||
|  | 	podName := resources.GetJenkinsMasterPodName(*jenkins) | ||||||
|  | 	command := jenkins.Spec.Backup.Action.Exec.Command | ||||||
|  | 	command = append(command, fmt.Sprintf("%d", backupNumber)) | ||||||
|  | 	_, _, err := bar.exec(podName, jenkins.Spec.Backup.ContainerName, command) | ||||||
|  | 
 | ||||||
|  | 	if err == nil { | ||||||
|  | 		if jenkins.Status.RestoredBackup == 0 { | ||||||
|  | 			jenkins.Status.RestoredBackup = backupNumber | ||||||
|  | 		} | ||||||
|  | 		jenkins.Status.LastBackup = backupNumber | ||||||
|  | 		jenkins.Status.PendingBackup = backupNumber | ||||||
|  | 		return bar.k8sClient.Update(context.TODO(), jenkins) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	//TODO after 3 fails stop
 | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (bar *BackupAndRestore) exec(podName, containerName string, command []string) (stdout, stderr bytes.Buffer, err error) { | ||||||
|  | 	req := bar.clientSet.CoreV1().RESTClient().Post(). | ||||||
|  | 		Resource("pods"). | ||||||
|  | 		Name(podName). | ||||||
|  | 		Namespace(bar.jenkins.Namespace). | ||||||
|  | 		SubResource("exec") | ||||||
|  | 	req.VersionedParams(&corev1.PodExecOptions{ | ||||||
|  | 		Command:   command, | ||||||
|  | 		Container: containerName, | ||||||
|  | 		Stdin:     false, | ||||||
|  | 		Stdout:    true, | ||||||
|  | 		Stderr:    true, | ||||||
|  | 		TTY:       false, | ||||||
|  | 	}, scheme.ParameterCodec) | ||||||
|  | 
 | ||||||
|  | 	exec, err := remotecommand.NewSPDYExecutor(&bar.config, "POST", req.URL()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stdout, stderr, errors.Wrap(err, "pod exec error while creating Executor") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = exec.Stream(remotecommand.StreamOptions{ | ||||||
|  | 		Stdin:  nil, | ||||||
|  | 		Stdout: &stdout, | ||||||
|  | 		Stderr: &stderr, | ||||||
|  | 		Tty:    false, | ||||||
|  | 	}) | ||||||
|  | 	bar.logger.V(log.VDebug).Info(fmt.Sprintf("pod exec: stdout '%s' stderr '%s'", stdout.String(), stderr.String())) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stdout, stderr, errors.Wrapf(err, "pod exec error operation on stream: stdout '%s' stderr '%s'", stdout.String(), stderr.String()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | @ -7,6 +7,7 @@ import ( | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | ||||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | 	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/base/resources" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/backuprestore" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/casc" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/casc" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||||
|  | @ -17,6 +18,8 @@ import ( | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | 	"k8s.io/client-go/kubernetes" | ||||||
|  | 	"k8s.io/client-go/rest" | ||||||
| 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | 	"sigs.k8s.io/controller-runtime/pkg/reconcile" | ||||||
| ) | ) | ||||||
|  | @ -27,22 +30,27 @@ type ReconcileUserConfiguration struct { | ||||||
| 	jenkinsClient jenkinsclient.Jenkins | 	jenkinsClient jenkinsclient.Jenkins | ||||||
| 	logger        logr.Logger | 	logger        logr.Logger | ||||||
| 	jenkins       *v1alpha2.Jenkins | 	jenkins       *v1alpha2.Jenkins | ||||||
|  | 	clientSet     kubernetes.Clientset | ||||||
|  | 	config        rest.Config | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New create structure which takes care of user configuration
 | // New create structure which takes care of user configuration
 | ||||||
| func New(k8sClient k8s.Client, jenkinsClient jenkinsclient.Jenkins, logger logr.Logger, | func New(k8sClient k8s.Client, jenkinsClient jenkinsclient.Jenkins, logger logr.Logger, | ||||||
| 	jenkins *v1alpha2.Jenkins) *ReconcileUserConfiguration { | 	jenkins *v1alpha2.Jenkins, clientSet kubernetes.Clientset, config rest.Config) *ReconcileUserConfiguration { | ||||||
| 	return &ReconcileUserConfiguration{ | 	return &ReconcileUserConfiguration{ | ||||||
| 		k8sClient:     k8sClient, | 		k8sClient:     k8sClient, | ||||||
| 		jenkinsClient: jenkinsClient, | 		jenkinsClient: jenkinsClient, | ||||||
| 		logger:        logger, | 		logger:        logger, | ||||||
| 		jenkins:       jenkins, | 		jenkins:       jenkins, | ||||||
|  | 		clientSet:     clientSet, | ||||||
|  | 		config:        config, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Reconcile it's a main reconciliation loop for user supplied configuration
 | // Reconcile it's a main reconciliation loop for user supplied configuration
 | ||||||
| func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | ||||||
| 	// reconcile seed jobs
 | 	backupAndRestore := backuprestore.New(r.k8sClient, r.clientSet, r.jenkinsClient, r.logger, r.jenkins, r.config) | ||||||
|  | 
 | ||||||
| 	result, err := r.ensureSeedJobs() | 	result, err := r.ensureSeedJobs() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return reconcile.Result{}, err | 		return reconcile.Result{}, err | ||||||
|  | @ -51,6 +59,10 @@ func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | ||||||
| 		return result, nil | 		return result, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := backupAndRestore.Restore(); err != nil { | ||||||
|  | 		return reconcile.Result{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	result, err = r.ensureUserConfiguration(r.jenkinsClient) | 	result, err = r.ensureUserConfiguration(r.jenkinsClient) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return reconcile.Result{}, err | 		return reconcile.Result{}, err | ||||||
|  | @ -59,6 +71,11 @@ func (r *ReconcileUserConfiguration) Reconcile() (reconcile.Result, error) { | ||||||
| 		return result, nil | 		return result, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := backupAndRestore.Backup(); err != nil { | ||||||
|  | 		return reconcile.Result{}, err | ||||||
|  | 	} | ||||||
|  | 	//TODO backup Goroutine
 | ||||||
|  | 
 | ||||||
| 	return reconcile.Result{}, nil | 	return reconcile.Result{}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,11 +2,17 @@ package user | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/backuprestore" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Validate validates Jenkins CR Spec section
 | // Validate validates Jenkins CR Spec section
 | ||||||
| func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha2.Jenkins) (bool, error) { | func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha2.Jenkins) (bool, error) { | ||||||
|  | 	backupAndRestore := backuprestore.New(r.k8sClient, r.clientSet, r.jenkinsClient, r.logger, r.jenkins, r.config) | ||||||
|  | 	if ok := backupAndRestore.Validate(); !ok { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	seedJobs := seedjobs.New(r.jenkinsClient, r.k8sClient, r.logger) | 	seedJobs := seedjobs.New(r.jenkinsClient, r.k8sClient, r.logger) | ||||||
| 	return seedJobs.ValidateSeedJobs(*jenkins) | 	return seedJobs.ValidateSeedJobs(*jenkins) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -23,6 +23,8 @@ import ( | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	"k8s.io/apimachinery/pkg/util/intstr" | 	"k8s.io/apimachinery/pkg/util/intstr" | ||||||
|  | 	"k8s.io/client-go/kubernetes" | ||||||
|  | 	"k8s.io/client-go/rest" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"sigs.k8s.io/controller-runtime/pkg/controller" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||||
|  | @ -42,18 +44,20 @@ const ( | ||||||
| 
 | 
 | ||||||
| // Add creates a new Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller
 | // Add creates a new Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller
 | ||||||
| // and Start it when the Manager is Started.
 | // and Start it when the Manager is Started.
 | ||||||
| func Add(mgr manager.Manager, local, minikube bool, events event.Recorder) error { | func Add(mgr manager.Manager, local, minikube bool, events event.Recorder, clientSet kubernetes.Clientset, config rest.Config) error { | ||||||
| 	return add(mgr, newReconciler(mgr, local, minikube, events)) | 	return add(mgr, newReconciler(mgr, local, minikube, events, clientSet, config)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // newReconciler returns a new reconcile.Reconciler
 | // newReconciler returns a new reconcile.Reconciler
 | ||||||
| func newReconciler(mgr manager.Manager, local, minikube bool, events event.Recorder) reconcile.Reconciler { | func newReconciler(mgr manager.Manager, local, minikube bool, events event.Recorder, clientSet kubernetes.Clientset, config rest.Config) reconcile.Reconciler { | ||||||
| 	return &ReconcileJenkins{ | 	return &ReconcileJenkins{ | ||||||
| 		client:    mgr.GetClient(), | 		client:    mgr.GetClient(), | ||||||
| 		scheme:    mgr.GetScheme(), | 		scheme:    mgr.GetScheme(), | ||||||
| 		local:     local, | 		local:     local, | ||||||
| 		minikube:  minikube, | 		minikube:  minikube, | ||||||
| 		events:    events, | 		events:    events, | ||||||
|  | 		clientSet: clientSet, | ||||||
|  | 		config:    config, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -103,6 +107,8 @@ type ReconcileJenkins struct { | ||||||
| 	scheme          *runtime.Scheme | 	scheme          *runtime.Scheme | ||||||
| 	local, minikube bool | 	local, minikube bool | ||||||
| 	events          event.Recorder | 	events          event.Recorder | ||||||
|  | 	clientSet       kubernetes.Clientset | ||||||
|  | 	config          rest.Config | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Reconcile it's a main reconciliation loop which maintain desired state based on Jenkins.Spec
 | // Reconcile it's a main reconciliation loop which maintain desired state based on Jenkins.Spec
 | ||||||
|  | @ -181,7 +187,7 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg | ||||||
| 		r.events.Emit(jenkins, event.TypeNormal, reasonBaseConfigurationSuccess, "Base configuration completed") | 		r.events.Emit(jenkins, event.TypeNormal, reasonBaseConfigurationSuccess, "Base configuration completed") | ||||||
| 	} | 	} | ||||||
| 	// Reconcile user configuration
 | 	// Reconcile user configuration
 | ||||||
| 	userConfiguration := user.New(r.client, jenkinsClient, logger, jenkins) | 	userConfiguration := user.New(r.client, jenkinsClient, logger, jenkins, r.clientSet, r.config) | ||||||
| 
 | 
 | ||||||
| 	valid, err = userConfiguration.Validate(jenkins) | 	valid, err = userConfiguration.Validate(jenkins) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -334,13 +340,25 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | ||||||
| 	} | 	} | ||||||
| 	if len(jenkins.Spec.Master.Containers) > 1 { | 	if len(jenkins.Spec.Master.Containers) > 1 { | ||||||
| 		for i, container := range jenkins.Spec.Master.Containers[1:] { | 		for i, container := range jenkins.Spec.Master.Containers[1:] { | ||||||
| 			if setDefaultsForContainer(jenkins, i, logger.WithValues("container", container.Name)) { | 			if setDefaultsForContainer(jenkins, i+1, logger.WithValues("container", container.Name)) { | ||||||
| 				changed = true | 				changed = true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	if len(jenkins.Spec.Backup.ContainerName) > 0 && jenkins.Spec.Backup.Interval == 0 { | ||||||
|  | 		logger.Info("Setting default backup interval") | ||||||
|  | 		changed = true | ||||||
|  | 		jenkins.Spec.Backup.Interval = 30 | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if len(jenkins.Spec.Master.Containers) == 0 || len(jenkins.Spec.Master.Containers) == 1 { | ||||||
| 		jenkins.Spec.Master.Containers = []v1alpha2.Container{jenkinsContainer} | 		jenkins.Spec.Master.Containers = []v1alpha2.Container{jenkinsContainer} | ||||||
|  | 	} else { | ||||||
|  | 		noJenkinsContainers := jenkins.Spec.Master.Containers[1:] | ||||||
|  | 		containers := []v1alpha2.Container{jenkinsContainer} | ||||||
|  | 		containers = append(containers, noJenkinsContainers...) | ||||||
|  | 		jenkins.Spec.Master.Containers = containers | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if changed { | 	if changed { | ||||||
| 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue