package casc import ( "context" "crypto/sha256" "encoding/base64" "fmt" "sort" "strings" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha1" 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/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 } // New creates new instance of ConfigurationAsCode func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName string) *ConfigurationAsCode { return &ConfigurationAsCode{ jenkinsClient: jenkinsClient, k8sClient: k8sClient, logger: logger, jobName: jobName, } } // 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 *v1alpha1.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) } 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 } func (g *ConfigurationAsCode) calculateUserConfigurationSecretHash(userConfigurationSecret *corev1.Secret) string { hash := sha256.New() 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 `