diff --git a/pkg/controller/jenkins/configuration/user/casc/caac.go b/pkg/controller/jenkins/configuration/user/casc/caac.go index dd583164..cf855195 100644 --- a/pkg/controller/jenkins/configuration/user/casc/caac.go +++ b/pkg/controller/jenkins/configuration/user/casc/caac.go @@ -1,216 +1,50 @@ package casc import ( - "context" - "crypto/sha256" - "encoding/base64" "fmt" - "sort" "strings" "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/controller/jenkins/jobs" + "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/groovy" "github.com/go-logr/logr" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" k8s "sigs.k8s.io/controller-runtime/pkg/client" ) -const ( - userConfigurationHashParameterName = "userConfigurationHash" - userConfigurationSecretHashParameterName = "userConfigurationSecretHash" -) - // ConfigurationAsCode defines API which configures Jenkins with help Configuration as a code plugin type ConfigurationAsCode struct { - jenkinsClient jenkinsclient.Jenkins - k8sClient k8s.Client - logger logr.Logger - jobName string + groovyClient *groovy.Groovy } // New creates new instance of ConfigurationAsCode -func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName string) *ConfigurationAsCode { +func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jenkins *v1alpha2.Jenkins) *ConfigurationAsCode { return &ConfigurationAsCode{ - jenkinsClient: jenkinsClient, - k8sClient: k8sClient, - logger: logger, - jobName: jobName, + groovyClient: groovy.New(jenkinsClient, k8sClient, logger, jenkins, "user-casc", jenkins.Spec.ConfigurationAsCode.Customization), } } -// ConfigureJob configures jenkins job which configures Jenkins with help Configuration as a code plugin -func (g *ConfigurationAsCode) ConfigureJob() error { - _, created, err := g.jenkinsClient.CreateOrUpdateJob(configurationJobXMLFmt, g.jobName) - if err != nil { - return err - } - if created { - g.logger.Info(fmt.Sprintf("'%s' job has been created", g.jobName)) - } - return nil -} - // Ensure configures Jenkins with help Configuration as a code plugin -func (g *ConfigurationAsCode) Ensure(jenkins *v1alpha2.Jenkins) (bool, error) { - jobsClient := jobs.New(g.jenkinsClient, g.k8sClient, g.logger) - - configuration := &corev1.ConfigMap{} - ConfigMapNamespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetUserConfigurationConfigMapNameFromJenkins(jenkins)} - err := g.k8sClient.Get(context.TODO(), ConfigMapNamespaceName, configuration) - if err != nil { - return false, errors.WithStack(err) +func (c *ConfigurationAsCode) Ensure(jenkins *v1alpha2.Jenkins) (requeue bool, err error) { + requeue, err = c.groovyClient.WaitForSecretSynchronization(resources.ConfigurationAsCodeSecretVolumePath) + if err != nil || requeue { + return requeue, err } - secret := &corev1.Secret{} - secretNamespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetUserConfigurationSecretNameFromJenkins(jenkins)} - err = g.k8sClient.Get(context.TODO(), secretNamespaceName, secret) - if err != nil { - return false, errors.WithStack(err) - } - - userConfigurationSecretHash := g.calculateUserConfigurationSecretHash(secret) - userConfigurationHash := g.calculateUserConfigurationHash(configuration) - done, err := jobsClient.EnsureBuildJob( - g.jobName, - userConfigurationSecretHash+userConfigurationHash, - map[string]string{ - userConfigurationHashParameterName: userConfigurationHash, - userConfigurationSecretHashParameterName: userConfigurationSecretHash, - }, - jenkins, - true) - if err != nil { - return false, err - } - return done, nil + return c.groovyClient.Ensure(func(name string) bool { + return strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml") + }, func(groovyScript string) string { + return fmt.Sprintf(applyConfigurationAsCodeGroovyScriptFmt, groovyScript) + }) } -func (g *ConfigurationAsCode) calculateUserConfigurationSecretHash(userConfigurationSecret *corev1.Secret) string { - hash := sha256.New() +const applyConfigurationAsCodeGroovyScriptFmt = ` +def config = ''' +%s +''' +def stream = new ByteArrayInputStream(config.getBytes('UTF-8')) - var keys []string - for key := range userConfigurationSecret.Data { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - hash.Write([]byte(key)) - hash.Write([]byte(userConfigurationSecret.Data[key])) - } - return base64.StdEncoding.EncodeToString(hash.Sum(nil)) -} - -func (g *ConfigurationAsCode) calculateUserConfigurationHash(userConfiguration *corev1.ConfigMap) string { - hash := sha256.New() - - var keys []string - for key := range userConfiguration.Data { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - if strings.HasSuffix(key, ".yaml") { - hash.Write([]byte(key)) - hash.Write([]byte(userConfiguration.Data[key])) - } - } - return base64.StdEncoding.EncodeToString(hash.Sum(nil)) -} - -const configurationJobXMLFmt = ` - - - - false - - - - - - ` + userConfigurationSecretHashParameterName + ` - - - false - - - ` + userConfigurationHashParameterName + ` - - - false - - - - - - - false - - - false - +def source = new io.jenkins.plugins.casc.yaml.YamlSource(stream, io.jenkins.plugins.casc.yaml.YamlSource.READ_FROM_INPUTSTREAM) +io.jenkins.plugins.casc.ConfigurationAsCode.get().configureWith(source) ` diff --git a/pkg/controller/jenkins/configuration/user/reconcile.go b/pkg/controller/jenkins/configuration/user/reconcile.go index c172dfc2..13525a62 100644 --- a/pkg/controller/jenkins/configuration/user/reconcile.go +++ b/pkg/controller/jenkins/configuration/user/reconcile.go @@ -1,7 +1,7 @@ package user import ( - "context" + "strings" "time" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" @@ -10,14 +10,11 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" "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/constants" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/groovy" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs" "github.com/go-logr/logr" "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" k8s "sigs.k8s.io/controller-runtime/pkg/client" @@ -104,37 +101,35 @@ func (r *ReconcileUserConfiguration) ensureSeedJobs() (reconcile.Result, error) } func (r *ReconcileUserConfiguration) ensureUserConfiguration(jenkinsClient jenkinsclient.Jenkins) (reconcile.Result, error) { - configuration := &corev1.ConfigMap{} - namespaceName := types.NamespacedName{Namespace: r.jenkins.Namespace, Name: resources.GetUserConfigurationConfigMapNameFromJenkins(r.jenkins)} - err := r.k8sClient.Get(context.TODO(), namespaceName, configuration) + groovyClient := groovy.New(jenkinsClient, r.k8sClient, r.logger, r.jenkins, "user-groovy", r.jenkins.Spec.GroovyScripts.Customization) + + requeue, err := groovyClient.WaitForSecretSynchronization(resources.GroovyScriptsSecretVolumePath) if err != nil { - return reconcile.Result{}, errors.WithStack(err) + return reconcile.Result{}, err + } + if requeue { + return reconcile.Result{Requeue: true}, nil + } + requeue, err = groovyClient.Ensure(func(name string) bool { + return strings.HasSuffix(name, ".groovy") + }, func(groovyScript string) string { + // TODO load secrets to variables + return groovyScript + }) + if err != nil { + return reconcile.Result{}, err + } + if requeue { + return reconcile.Result{Requeue: true}, nil } - groovyClient := groovy.New(jenkinsClient, r.k8sClient, r.logger, constants.UserConfigurationJobName, resources.JenkinsUserConfigurationVolumePath) - err = groovyClient.ConfigureJob() + configurationAsCodeClient := casc.New(jenkinsClient, r.k8sClient, r.logger, r.jenkins) + requeue, err = configurationAsCodeClient.Ensure(r.jenkins) if err != nil { return reconcile.Result{}, err } - done, err := groovyClient.Ensure(configuration.Data, r.jenkins) - if err != nil { - return reconcile.Result{}, err - } - if !done { - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil - } - - configurationAsCodeClient := casc.New(jenkinsClient, r.k8sClient, r.logger, constants.UserConfigurationCASCJobName) - err = configurationAsCodeClient.ConfigureJob() - if err != nil { - return reconcile.Result{}, err - } - done, err = configurationAsCodeClient.Ensure(r.jenkins) - if err != nil { - return reconcile.Result{}, err - } - if !done { - return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil + if requeue { + return reconcile.Result{Requeue: true}, nil } return reconcile.Result{}, nil