#9 Add secret support in Configuration as Code plugin integration
This commit is contained in:
parent
ebf1163b28
commit
f817d2ad09
|
|
@ -213,7 +213,30 @@ You can verify if your pipelines were successfully configured in Jenkins Seed Jo
|
||||||
## Jenkins Customisation
|
## Jenkins Customisation
|
||||||
|
|
||||||
Jenkins can be customized using groovy scripts or configuration as code plugin. All custom configuration is stored in
|
Jenkins can be customized using groovy scripts or configuration as code plugin. All custom configuration is stored in
|
||||||
the **jenkins-operator-user-configuration-example** ConfigMap which is automatically created by **jenkins-operator**.
|
the **jenkins-operator-user-configuration-example** ConfigMap which is automatically created by **jenkins-operator**.
|
||||||
|
|
||||||
|
**jenkins-operator** creates **jenkins-operator-user-configuration-example** secret where user can store sensitive
|
||||||
|
information used for custom configuration. If you have entry in secret named `PASSWORD` then you can use it in
|
||||||
|
Configuration as Plugin as `adminAddress: "${PASSWORD}"`.
|
||||||
|
|
||||||
|
```
|
||||||
|
kubectl get secret jenkins-operator-user-configuration-example -o yaml
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
SECRET_JENKINS_ADMIN_ADDRESS: YXNkZgo=
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: 2019-03-03T11:54:36Z
|
||||||
|
labels:
|
||||||
|
app: jenkins-operator
|
||||||
|
jenkins-cr: example
|
||||||
|
watch: "true"
|
||||||
|
name: jenkins-operator-user-configuration-example
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
kubectl get configmap jenkins-operator-user-configuration-example -o yaml
|
kubectl get configmap jenkins-operator-user-configuration-example -o yaml
|
||||||
|
|
@ -243,6 +266,7 @@ data:
|
||||||
1-system-message.yaml: |2
|
1-system-message.yaml: |2
|
||||||
jenkins:
|
jenkins:
|
||||||
systemMessage: "Configuration as Code integration works!!!"
|
systemMessage: "Configuration as Code integration works!!!"
|
||||||
|
adminAddress: "${SECRET_JENKINS_ADMIN_ADDRESS}"
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,11 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod
|
||||||
}
|
}
|
||||||
r.logger.V(log.VDebug).Info("User configuration config map is present")
|
r.logger.V(log.VDebug).Info("User configuration config map is present")
|
||||||
|
|
||||||
|
if err := r.createUserConfigurationSecret(metaObject); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.logger.V(log.VDebug).Info("User configuration secret is present")
|
||||||
|
|
||||||
if err := r.createRBAC(metaObject); err != nil {
|
if err := r.createRBAC(metaObject); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +257,23 @@ func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(met
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationSecret(meta metav1.ObjectMeta) error {
|
||||||
|
currentSecret := &corev1.Secret{}
|
||||||
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: resources.GetUserConfigurationSecretNameFromJenkins(r.jenkins), Namespace: r.jenkins.Namespace}, currentSecret)
|
||||||
|
if err != nil && errors.IsNotFound(err) {
|
||||||
|
return stackerr.WithStack(r.k8sClient.Create(context.TODO(), resources.NewUserConfigurationSecret(r.jenkins)))
|
||||||
|
} else if err != nil {
|
||||||
|
return stackerr.WithStack(err)
|
||||||
|
}
|
||||||
|
valid := r.verifyLabelsForWatchedResource(currentSecret)
|
||||||
|
if !valid {
|
||||||
|
currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins)
|
||||||
|
return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentSecret))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) error {
|
func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) error {
|
||||||
serviceAccount := resources.NewServiceAccount(meta)
|
serviceAccount := resources.NewServiceAccount(meta)
|
||||||
err := r.createResource(serviceAccount)
|
err := r.createResource(serviceAccount)
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,14 @@ const (
|
||||||
JenkinsBaseConfigurationVolumePath = jenkinsPath + "/base-configuration"
|
JenkinsBaseConfigurationVolumePath = jenkinsPath + "/base-configuration"
|
||||||
|
|
||||||
jenkinsUserConfigurationVolumeName = "user-configuration"
|
jenkinsUserConfigurationVolumeName = "user-configuration"
|
||||||
// JenkinsUserConfigurationVolumePath is a path where are groovy scripts used to configure Jenkins
|
// JenkinsUserConfigurationVolumePath is a path where are groovy scripts and CasC configs used to configure Jenkins
|
||||||
// this scripts are provided by user
|
// this script is provided by user
|
||||||
JenkinsUserConfigurationVolumePath = jenkinsPath + "/user-configuration"
|
JenkinsUserConfigurationVolumePath = jenkinsPath + "/user-configuration"
|
||||||
|
|
||||||
|
userConfigurationSecretVolumeName = "user-configuration-secrets"
|
||||||
|
// UserConfigurationSecretVolumePath is a path where are secrets used for groovy scripts and CasC configs
|
||||||
|
UserConfigurationSecretVolumePath = jenkinsPath + "/user-configuration-secrets"
|
||||||
|
|
||||||
httpPortName = "http"
|
httpPortName = "http"
|
||||||
slavePortName = "slavelistener"
|
slavePortName = "slavelistener"
|
||||||
// HTTPPortInt defines Jenkins master HTTP port
|
// HTTPPortInt defines Jenkins master HTTP port
|
||||||
|
|
@ -121,6 +125,10 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
|
||||||
Name: "JAVA_OPTS",
|
Name: "JAVA_OPTS",
|
||||||
Value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true",
|
Value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "SECRETS", // https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/demos/kubernetes-secrets/README.md
|
||||||
|
Value: UserConfigurationSecretVolumePath,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Resources: jenkins.Spec.Master.Resources,
|
Resources: jenkins.Spec.Master.Resources,
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
|
@ -154,6 +162,11 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
|
||||||
MountPath: jenkinsOperatorCredentialsVolumePath,
|
MountPath: jenkinsOperatorCredentialsVolumePath,
|
||||||
ReadOnly: true,
|
ReadOnly: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: userConfigurationSecretVolumeName,
|
||||||
|
MountPath: UserConfigurationSecretVolumePath,
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -212,6 +225,14 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: userConfigurationSecretVolumeName,
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
Secret: &corev1.SecretVolumeSource{
|
||||||
|
SecretName: GetUserConfigurationSecretNameFromJenkins(jenkins),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,13 @@ func GetUserConfigurationConfigMapName(jenkinsCRName string) string {
|
||||||
|
|
||||||
// NewUserConfigurationConfigMap builds Kubernetes config map used to user configuration
|
// NewUserConfigurationConfigMap builds Kubernetes config map used to user configuration
|
||||||
func NewUserConfigurationConfigMap(jenkins *v1alpha1.Jenkins) *corev1.ConfigMap {
|
func NewUserConfigurationConfigMap(jenkins *v1alpha1.Jenkins) *corev1.ConfigMap {
|
||||||
meta := metav1.ObjectMeta{
|
|
||||||
Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
|
|
||||||
Namespace: jenkins.ObjectMeta.Namespace,
|
|
||||||
Labels: BuildLabelsForWatchedResources(jenkins),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &corev1.ConfigMap{
|
return &corev1.ConfigMap{
|
||||||
TypeMeta: buildConfigMapTypeMeta(),
|
TypeMeta: buildConfigMapTypeMeta(),
|
||||||
ObjectMeta: meta,
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: GetUserConfigurationConfigMapNameFromJenkins(jenkins),
|
||||||
|
Namespace: jenkins.ObjectMeta.Namespace,
|
||||||
|
Labels: BuildLabelsForWatchedResources(jenkins),
|
||||||
|
},
|
||||||
Data: map[string]string{
|
Data: map[string]string{
|
||||||
"1-configure-theme.groovy": configureTheme,
|
"1-configure-theme.groovy": configureTheme,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUserConfigurationSecretNameFromJenkins returns name of Kubernetes secret used to store jenkins operator credentials
|
||||||
|
func GetUserConfigurationSecretNameFromJenkins(jenkins *v1alpha1.Jenkins) string {
|
||||||
|
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkins.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserConfigurationSecretName returns name of Kubernetes secret used to store jenkins operator credentials
|
||||||
|
func GetUserConfigurationSecretName(jenkinsCRName string) string {
|
||||||
|
return fmt.Sprintf("%s-user-configuration-%s", constants.OperatorName, jenkinsCRName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserConfigurationSecret builds the Kubernetes secret resource which is used to store user sensitive data for Jenkins configuration
|
||||||
|
func NewUserConfigurationSecret(jenkins *v1alpha1.Jenkins) *corev1.Secret {
|
||||||
|
return &corev1.Secret{
|
||||||
|
TypeMeta: buildServiceTypeMeta(),
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: GetUserConfigurationSecretNameFromJenkins(jenkins),
|
||||||
|
Namespace: jenkins.ObjectMeta.Namespace,
|
||||||
|
Labels: BuildLabelsForWatchedResources(jenkins),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package casc
|
package casc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -9,14 +10,19 @@ import (
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1"
|
||||||
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/jobs"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"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"
|
k8s "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
jobHashParameterName = "hash"
|
userConfigurationHashParameterName = "userConfigurationHash"
|
||||||
|
userConfigurationSecretHashParameterName = "userConfigurationSecretHash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigurationAsCode defines API which configures Jenkins with help Configuration as a code plugin
|
// ConfigurationAsCode defines API which configures Jenkins with help Configuration as a code plugin
|
||||||
|
|
@ -25,23 +31,23 @@ type ConfigurationAsCode struct {
|
||||||
k8sClient k8s.Client
|
k8sClient k8s.Client
|
||||||
logger logr.Logger
|
logger logr.Logger
|
||||||
jobName string
|
jobName string
|
||||||
configsPath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates new instance of ConfigurationAsCode
|
// New creates new instance of ConfigurationAsCode
|
||||||
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName, configsPath string) *ConfigurationAsCode {
|
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName string) *ConfigurationAsCode {
|
||||||
return &ConfigurationAsCode{
|
return &ConfigurationAsCode{
|
||||||
jenkinsClient: jenkinsClient,
|
jenkinsClient: jenkinsClient,
|
||||||
k8sClient: k8sClient,
|
k8sClient: k8sClient,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
jobName: jobName,
|
jobName: jobName,
|
||||||
configsPath: configsPath,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureJob configures jenkins job which configures Jenkins with help Configuration as a code plugin
|
// ConfigureJob configures jenkins job which configures Jenkins with help Configuration as a code plugin
|
||||||
func (g *ConfigurationAsCode) ConfigureJob() error {
|
func (g *ConfigurationAsCode) ConfigureJob() error {
|
||||||
_, created, err := g.jenkinsClient.CreateOrUpdateJob(fmt.Sprintf(configurationJobXMLFmt, g.configsPath), g.jobName)
|
_, created, err := g.jenkinsClient.CreateOrUpdateJob(
|
||||||
|
fmt.Sprintf(configurationJobXMLFmt, resources.UserConfigurationSecretVolumePath, resources.JenkinsUserConfigurationVolumePath),
|
||||||
|
g.jobName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -52,29 +58,67 @@ func (g *ConfigurationAsCode) ConfigureJob() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure configures Jenkins with help Configuration as a code plugin
|
// Ensure configures Jenkins with help Configuration as a code plugin
|
||||||
func (g *ConfigurationAsCode) Ensure(secretOrConfigMapData map[string]string, jenkins *v1alpha1.Jenkins) (bool, error) {
|
func (g *ConfigurationAsCode) Ensure(jenkins *v1alpha1.Jenkins) (bool, error) {
|
||||||
jobsClient := jobs.New(g.jenkinsClient, g.k8sClient, g.logger)
|
jobsClient := jobs.New(g.jenkinsClient, g.k8sClient, g.logger)
|
||||||
|
|
||||||
hash := g.calculateHash(secretOrConfigMapData)
|
configuration := &corev1.ConfigMap{}
|
||||||
done, err := jobsClient.EnsureBuildJob(g.jobName, hash, map[string]string{jobHashParameterName: hash}, jenkins, true)
|
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetUserConfigurationConfigMapNameFromJenkins(jenkins)}
|
||||||
|
err := g.k8sClient.Get(context.TODO(), namespaceName, configuration)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := &corev1.Secret{}
|
||||||
|
namespaceName = types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetUserConfigurationSecretNameFromJenkins(jenkins)}
|
||||||
|
err = g.k8sClient.Get(context.TODO(), namespaceName, configuration)
|
||||||
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return done, nil
|
return done, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ConfigurationAsCode) calculateHash(secretOrConfigMapData map[string]string) string {
|
func (g *ConfigurationAsCode) calculateUserConfigurationSecretHash(userConfigurationSecret *corev1.Secret) string {
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
|
|
||||||
var keys []string
|
var keys []string
|
||||||
for key := range secretOrConfigMapData {
|
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)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if strings.HasSuffix(key, ".yaml") {
|
if strings.HasSuffix(key, ".yaml") {
|
||||||
hash.Write([]byte(key))
|
hash.Write([]byte(key))
|
||||||
hash.Write([]byte(secretOrConfigMapData[key]))
|
hash.Write([]byte(userConfiguration.Data[key]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||||
|
|
@ -90,9 +134,15 @@ const configurationJobXMLFmt = `<?xml version='1.1' encoding='UTF-8'?>
|
||||||
<hudson.model.ParametersDefinitionProperty>
|
<hudson.model.ParametersDefinitionProperty>
|
||||||
<parameterDefinitions>
|
<parameterDefinitions>
|
||||||
<hudson.model.StringParameterDefinition>
|
<hudson.model.StringParameterDefinition>
|
||||||
<name>` + jobHashParameterName + `</name>
|
<name>` + userConfigurationSecretHashParameterName + `</name>
|
||||||
<description></description>
|
<description/>
|
||||||
<defaultValue></defaultValue>
|
<defaultValue/>
|
||||||
|
<trim>false</trim>
|
||||||
|
</hudson.model.StringParameterDefinition>
|
||||||
|
<hudson.model.StringParameterDefinition>
|
||||||
|
<name>` + userConfigurationHashParameterName + `</name>
|
||||||
|
<description/>
|
||||||
|
<defaultValue/>
|
||||||
<trim>false</trim>
|
<trim>false</trim>
|
||||||
</hudson.model.StringParameterDefinition>
|
</hudson.model.StringParameterDefinition>
|
||||||
</parameterDefinitions>
|
</parameterDefinitions>
|
||||||
|
|
@ -101,28 +151,23 @@ const configurationJobXMLFmt = `<?xml version='1.1' encoding='UTF-8'?>
|
||||||
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61.1">
|
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61.1">
|
||||||
<script>import io.jenkins.plugins.casc.yaml.YamlSource;
|
<script>import io.jenkins.plugins.casc.yaml.YamlSource;
|
||||||
|
|
||||||
|
def secretsPath = '%s'
|
||||||
def configsPath = '%s'
|
def configsPath = '%s'
|
||||||
def expectedHash = params.hash
|
def userConfigurationSecretExpectedHash = params.` + userConfigurationSecretHashParameterName + `
|
||||||
|
def userConfigurationExpectedHash = params.` + userConfigurationHashParameterName + `
|
||||||
|
|
||||||
node('master') {
|
node('master') {
|
||||||
|
def secretsText = sh(script: "ls ${secretsPath} | grep .yaml | sort", returnStdout: true).trim()
|
||||||
|
def secrets = []
|
||||||
|
secrets.addAll(secretsText.tokenize('\n'))
|
||||||
|
|
||||||
def configsText = sh(script: "ls ${configsPath} | grep .yaml | sort", returnStdout: true).trim()
|
def configsText = sh(script: "ls ${configsPath} | grep .yaml | sort", returnStdout: true).trim()
|
||||||
def configs = []
|
def configs = []
|
||||||
configs.addAll(configsText.tokenize('\n'))
|
configs.addAll(configsText.tokenize('\n'))
|
||||||
|
|
||||||
stage('Synchronizing files') {
|
stage('Synchronizing files') {
|
||||||
def complete = false
|
synchronizeFiles(secretsPath, (String[])secrets, userConfigurationSecretExpectedHash)
|
||||||
for(int i = 1; i <= 10; i++) {
|
synchronizeFiles(configsPath, (String[])configs, userConfigurationExpectedHash)
|
||||||
def actualHash = calculateHash((String[])configs, configsPath)
|
|
||||||
println "Expected hash '${expectedHash}', actual hash '${actualHash}'"
|
|
||||||
if(expectedHash == actualHash) {
|
|
||||||
complete = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sleep 2
|
|
||||||
}
|
|
||||||
if(!complete) {
|
|
||||||
error("Timeout while synchronizing files")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for(config in configs) {
|
for(config in configs) {
|
||||||
|
|
@ -134,6 +179,23 @@ node('master') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def synchronizeFiles(String path, String[] files, String hash) {
|
||||||
|
def complete = false
|
||||||
|
for(int i = 1; i <= 10; i++) {
|
||||||
|
def actualHash = calculateHash(files, path)
|
||||||
|
println "Expected hash '${hash}', actual hash '${actualHash}', path '${path}'"
|
||||||
|
if(hash == actualHash) {
|
||||||
|
complete = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
if(!complete) {
|
||||||
|
error("Timeout while synchronizing files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@NonCPS
|
@NonCPS
|
||||||
def calculateHash(String[] configs, String configsPath) {
|
def calculateHash(String[] configs, String configsPath) {
|
||||||
def hash = java.security.MessageDigest.getInstance("SHA-256")
|
def hash = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|
|
||||||
|
|
@ -105,12 +105,12 @@ func (r *ReconcileUserConfiguration) ensureUserConfiguration(jenkinsClient jenki
|
||||||
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil
|
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
configurationAsCodeClient := casc.New(jenkinsClient, r.k8sClient, r.logger, constants.UserConfigurationCASCJobName, resources.JenkinsUserConfigurationVolumePath)
|
configurationAsCodeClient := casc.New(jenkinsClient, r.k8sClient, r.logger, constants.UserConfigurationCASCJobName)
|
||||||
err = configurationAsCodeClient.ConfigureJob()
|
err = configurationAsCodeClient.ConfigureJob()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, err
|
||||||
}
|
}
|
||||||
done, err = configurationAsCodeClient.Ensure(configuration.Data, r.jenkins)
|
done, err = configurationAsCodeClient.Ensure(r.jenkins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,11 @@ func TestConfiguration(t *testing.T) {
|
||||||
jenkinsCRName := "e2e"
|
jenkinsCRName := "e2e"
|
||||||
numberOfExecutors := 6
|
numberOfExecutors := 6
|
||||||
systemMessage := "Configuration as Code integration works!!!"
|
systemMessage := "Configuration as Code integration works!!!"
|
||||||
|
systemMessageEnvName := "SYSTEM_MESSAGE"
|
||||||
|
|
||||||
// base
|
// base
|
||||||
createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, systemMessage)
|
createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage)
|
||||||
|
createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName))
|
||||||
jenkins := createJenkinsCR(t, jenkinsCRName, namespace)
|
jenkins := createJenkinsCR(t, jenkinsCRName, namespace)
|
||||||
createDefaultLimitsForContainersInNamespace(t, namespace)
|
createDefaultLimitsForContainersInNamespace(t, namespace)
|
||||||
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
|
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
|
||||||
|
|
@ -49,6 +51,23 @@ func TestConfiguration(t *testing.T) {
|
||||||
verifyUserConfiguration(t, client, numberOfExecutors, systemMessage)
|
verifyUserConfiguration(t, client, numberOfExecutors, systemMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUserConfigurationSecret(t *testing.T, jenkinsCRName string, namespace string, systemMessageEnvName, systemMessage string) {
|
||||||
|
userConfiguration := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: resources.GetUserConfigurationSecretName(jenkinsCRName),
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
StringData: map[string]string{
|
||||||
|
systemMessageEnvName: systemMessage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("User configuration secret %+v", *userConfiguration)
|
||||||
|
if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createUserConfigurationConfigMap(t *testing.T, jenkinsCRName string, namespace string, numberOfExecutors int, systemMessage string) {
|
func createUserConfigurationConfigMap(t *testing.T, jenkinsCRName string, namespace string, numberOfExecutors int, systemMessage string) {
|
||||||
userConfiguration := &corev1.ConfigMap{
|
userConfiguration := &corev1.ConfigMap{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue