#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
|
||||||
|
}
|
||||||
|
|
||||||
jenkins.Spec.Master.Containers = []v1alpha2.Container{jenkinsContainer}
|
if len(jenkins.Spec.Master.Containers) == 0 || len(jenkins.Spec.Master.Containers) == 1 {
|
||||||
|
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