#28 Change groovy client implementation

This commit is contained in:
Tomasz Sęk 2019-06-30 23:16:03 +02:00
parent 54454d9a02
commit 87fcc5f8a5
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
2 changed files with 690 additions and 99 deletions

View File

@ -1,6 +1,7 @@
package groovy
import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
@ -9,144 +10,227 @@ import (
"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/jobs"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
"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 (
jobHashParameterName = "hash"
)
// Groovy defines API for groovy scripts execution via jenkins job
// Groovy defines API for groovy secrets execution via jenkins job
type Groovy struct {
jenkinsClient jenkinsclient.Jenkins
k8sClient k8s.Client
logger logr.Logger
jobName string
scriptsPath string
k8sClient k8s.Client
logger logr.Logger
jenkins *v1alpha2.Jenkins
jenkinsClient jenkinsclient.Jenkins
configurationType string
customization v1alpha2.Customization
}
// New creates new instance of Groovy
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jobName, scriptsPath string) *Groovy {
func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.Logger, jenkins *v1alpha2.Jenkins, configurationType string, customization v1alpha2.Customization) *Groovy {
return &Groovy{
jenkinsClient: jenkinsClient,
k8sClient: k8sClient,
logger: logger,
jobName: jobName,
scriptsPath: scriptsPath,
jenkinsClient: jenkinsClient,
k8sClient: k8sClient,
logger: logger,
jenkins: jenkins,
configurationType: configurationType,
customization: customization,
}
}
// ConfigureJob configures jenkins job for executing groovy scripts
func (g *Groovy) ConfigureJob() error {
_, created, err := g.jenkinsClient.CreateOrUpdateJob(fmt.Sprintf(configurationJobXMLFmt, g.scriptsPath), g.jobName)
// EnsureSingle runs single groovy script
func (g *Groovy) EnsureSingle(source, name, hash, groovyScript string) (requeue bool, err error) {
if g.isGroovyScriptAlreadyApplied(source, name, hash) {
return false, nil
}
logs, err := g.jenkinsClient.ExecuteScript(groovyScript)
if err != nil {
return err
if _, ok := err.(*jenkinsclient.GroovyScriptExecutionFailed); ok {
g.logger.V(log.VWarn).Info(fmt.Sprintf("%s Source '%s' Name '%s' groovy script execution failed, logs :\n%s", g.configurationType, source, name, logs))
}
return true, err
}
if created {
g.logger.Info(fmt.Sprintf("'%s' job has been created", g.jobName))
}
return nil
g.jenkins.Status.AppliedGroovyScripts = append(g.jenkins.Status.AppliedGroovyScripts, v1alpha2.AppliedGroovyScript{
ConfigurationType: g.configurationType,
Source: source,
Name: name,
Hash: hash,
})
return true, g.k8sClient.Update(context.TODO(), g.jenkins)
}
// Ensure executes groovy script and verifies jenkins job status according to reconciliation loop lifecycle
func (g *Groovy) Ensure(secretOrConfigMapData map[string]string, jenkins *v1alpha2.Jenkins) (bool, error) {
jobsClient := jobs.New(g.jenkinsClient, g.k8sClient, g.logger)
// WaitForSecretSynchronization runs groovy script which waits to synchronize secrets in pod by k8s
func (g *Groovy) WaitForSecretSynchronization(secretsPath string) (requeue bool, err error) {
if len(g.customization.Secret.Name) == 0 {
return false, nil
}
hash := g.calculateHash(secretOrConfigMapData)
done, err := jobsClient.EnsureBuildJob(g.jobName, hash, map[string]string{jobHashParameterName: hash}, jenkins, true)
secret := &corev1.Secret{}
err = g.k8sClient.Get(context.TODO(), types.NamespacedName{Name: g.customization.Secret.Name, Namespace: g.jenkins.ObjectMeta.Namespace}, secret)
if err != nil {
return false, err
return true, errors.WithStack(err)
}
return done, nil
toCalculate := map[string]string{}
for secretKey, secretValue := range secret.Data {
toCalculate[secretKey] = string(secretValue)
}
hash := g.calculateHash(toCalculate)
name := "synchronizing-secret.groovy"
if g.isGroovyScriptAlreadyApplied(g.customization.Secret.Name, name, hash) {
return false, nil
}
g.logger.Info(fmt.Sprintf("%s Secret '%s' running synchronization", g.configurationType, secret.Name))
return g.EnsureSingle(g.customization.Secret.Name, name, hash, fmt.Sprintf(synchronizeSecretsGroovyScriptFmt, secretsPath, hash))
}
func (g *Groovy) calculateHash(secretOrConfigMapData map[string]string) string {
// Ensure runs all groovy scripts configured in customization structure
func (g *Groovy) Ensure(filter func(name string) bool, updateGroovyScript func(groovyScript string) string) (requeue bool, err error) {
secret := &corev1.Secret{}
if len(g.customization.Secret.Name) > 0 {
err := g.k8sClient.Get(context.TODO(), types.NamespacedName{Name: g.customization.Secret.Name, Namespace: g.jenkins.ObjectMeta.Namespace}, secret)
if err != nil {
return true, err
}
}
for _, configMapRef := range g.customization.Configurations {
configMap := &corev1.ConfigMap{}
err := g.k8sClient.Get(context.TODO(), types.NamespacedName{Name: configMapRef.Name, Namespace: g.jenkins.ObjectMeta.Namespace}, configMap)
if err != nil {
return true, errors.WithStack(err)
}
var names []string
for name := range configMap.Data {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
groovyScript := updateGroovyScript(configMap.Data[name])
if !filter(name) {
g.logger.V(log.VDebug).Info(fmt.Sprintf("Skipping %s ConfigMap '%s' name '%s'", g.configurationType, configMap.Name, name))
continue
}
hash := g.calculateCustomizationHash(*secret, name, groovyScript)
if g.isGroovyScriptAlreadyApplied(configMap.Name, name, hash) {
continue
}
g.logger.Info(fmt.Sprintf("%s ConfigMap '%s' name '%s' running groovy script", g.configurationType, configMap.Name, name))
requeue, err := g.EnsureSingle(configMap.Name, name, hash, groovyScript)
if err != nil || requeue {
return requeue, err
}
}
}
return false, nil
}
func (g *Groovy) calculateCustomizationHash(secret corev1.Secret, key, groovyScript string) string {
toCalculate := map[string]string{}
for secretKey, secretValue := range secret.Data {
toCalculate[secretKey] = string(secretValue)
}
toCalculate[key] = groovyScript
return g.calculateHash(toCalculate)
}
func (g *Groovy) isGroovyScriptAlreadyApplied(source, name, hash string) bool {
for _, appliedGroovyScript := range g.jenkins.Status.AppliedGroovyScripts {
if appliedGroovyScript.ConfigurationType == g.configurationType && appliedGroovyScript.Hash == hash &&
appliedGroovyScript.Name == name && appliedGroovyScript.Source == source {
return true
}
}
return false
}
func (g *Groovy) calculateHash(data map[string]string) string {
hash := sha256.New()
var keys []string
for key := range secretOrConfigMapData {
for key := range data {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
if strings.HasSuffix(key, ".groovy") {
hash.Write([]byte(key))
hash.Write([]byte(secretOrConfigMapData[key]))
}
hash.Write([]byte(key))
hash.Write([]byte(data[key]))
}
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
const configurationJobXMLFmt = `<?xml version='1.1' encoding='UTF-8'?>
<flow-definition plugin="workflow-job@2.31">
<actions/>
<description></description>
<keepDependencies>false</keepDependencies>
<properties>
<org.jenkinsci.plugins.workflow.job.properties.DisableConcurrentBuildsJobProperty/>
<hudson.model.ParametersDefinitionProperty>
<parameterDefinitions>
<hudson.model.StringParameterDefinition>
<name>` + jobHashParameterName + `</name>
<description></description>
<defaultValue></defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
</parameterDefinitions>
</hudson.model.ParametersDefinitionProperty>
</properties>
<definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61">
<script>def scriptsPath = &apos;%s&apos;
def expectedHash = params.hash
// AddSecretsLoaderToGroovyScript modify groovy scripts to load Kubernetes secrets into groovy map
func AddSecretsLoaderToGroovyScript(secretsPath string) func(groovyScript string) string {
return func(groovyScript string) string {
if !strings.HasPrefix(groovyScript, importPrefix) {
return fmt.Sprintf(secretsLoaderGroovyScriptFmt, secretsPath) + groovyScript
}
node(&apos;master&apos;) {
def scriptsText = sh(script: &quot;ls ${scriptsPath} | grep .groovy | sort&quot;, returnStdout: true).trim()
def scripts = []
scripts.addAll(scriptsText.tokenize(&apos;\n&apos;))
lines := strings.Split(groovyScript, "\n")
importIndex := -1
for i, line := range lines {
if !strings.HasPrefix(line, importPrefix) {
importIndex = i
break
}
}
asdf := strings.Join(lines[:importIndex], "\n") + "\n\n" + fmt.Sprintf(secretsLoaderGroovyScriptFmt, secretsPath) + "\n\n" + strings.Join(lines[importIndex:], "\n")
stage(&apos;Synchronizing files&apos;) {
println &quot;Synchronizing Kubernetes ConfigMaps to the Jenkins master pod.&quot;
println &quot;This step may fail and will be retried in the next job build if necessary.&quot;
def complete = false
for(int i = 1; i &lt;= 10; i++) {
def actualHash = calculateHash((String[])scripts, scriptsPath)
println &quot;Expected hash &apos;${expectedHash}&apos;, actual hash &apos;${actualHash}&apos;, will retry&quot;
if(expectedHash == actualHash) {
complete = true
break
}
sleep 2
}
if(!complete) {
error(&quot;Timeout while synchronizing files&quot;)
}
}
for(script in scripts) {
stage(script) {
load &quot;${scriptsPath}/${script}&quot;
}
}
return asdf
}
}
@NonCPS
def calculateHash(String[] scripts, String scriptsPath) {
def hash = java.security.MessageDigest.getInstance(&quot;SHA-256&quot;)
for(script in scripts) {
hash.update(script.getBytes())
def fileLocation = java.nio.file.Paths.get(&quot;${scriptsPath}/${script}&quot;)
const importPrefix = "import "
const secretsLoaderGroovyScriptFmt = `def secretsPath = '%s'
def secrets = [:]
"ls ${secretsPath}".execute().text.eachLine {secrets[it] = new File("${secretsPath}/${it}").text}`
const synchronizeSecretsGroovyScriptFmt = `
def secretsPath = '%s'
def expectedHash = '%s'
println "Synchronizing Kubernetes Secret to the Jenkins master pod, timeout 60 seconds."
def complete = false
for(int i = 1; i <= 30; i++) {
def fileList = "ls ${secretsPath}".execute()
def secrets = []
fileList .text.eachLine {secrets.add(it)}
println "Mounted secrets: ${secrets}"
def actualHash = calculateHash((String[])secrets, secretsPath)
println "Expected hash '${expectedHash}', actual hash '${actualHash}', will retry"
if(expectedHash == actualHash) {
complete = true
break
}
sleep 2000
}
if(!complete) {
throw new Exception("Timeout while synchronizing files")
}
def calculateHash(String[] secrets, String secretsPath) {
def hash = java.security.MessageDigest.getInstance("SHA-256")
for(secret in secrets) {
hash.update(secret.getBytes())
def fileLocation = java.nio.file.Paths.get("${secretsPath}/${secret}")
def fileData = java.nio.file.Files.readAllBytes(fileLocation)
hash.update(fileData)
}
return Base64.getEncoder().encodeToString(hash.digest())
}</script>
<sandbox>false</sandbox>
</definition>
<triggers/>
<disabled>false</disabled>
</flow-definition>
}
`

View File

@ -0,0 +1,507 @@
package groovy
import (
"context"
"fmt"
"strings"
"testing"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/golang/mock/gomock"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func TestGroovy_EnsureSingle(t *testing.T) {
log.SetupLogger(true)
configurationType := "test-conf-type"
emptyCustomization := v1alpha2.Customization{}
hash := "hash"
groovyScript := "groovy-script"
groovyScriptName := "groovy-script-name"
source := "source"
ctx := context.TODO()
jenkinsName := "jenkins"
namespace := "default"
t.Run("execute script and save status", func(t *testing.T) {
// given
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript).Return("logs", nil)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, emptyCustomization)
// when
requeue, err := groovyClient.EnsureSingle(source, groovyScriptName, hash, groovyScript)
// then
require.NoError(t, err)
assert.True(t, requeue)
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, hash, jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, source, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
t.Run("no execute script", func(t *testing.T) {
// given
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
Status: v1alpha2.JenkinsStatus{
AppliedGroovyScripts: []v1alpha2.AppliedGroovyScript{
{
ConfigurationType: configurationType,
Source: source,
Name: groovyScriptName,
Hash: hash,
},
},
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, emptyCustomization)
// when
requeue, err := groovyClient.EnsureSingle(source, groovyScriptName, hash, groovyScript)
// then
require.NoError(t, err)
assert.False(t, requeue)
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, hash, jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, source, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
t.Run("execute script fails", func(t *testing.T) {
// given
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript).Return("fail logs", &jenkinsclient.GroovyScriptExecutionFailed{})
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, emptyCustomization)
// when
requeue, err := groovyClient.EnsureSingle(source, groovyScriptName, hash, groovyScript)
// then
require.Error(t, err)
assert.True(t, requeue)
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 0, len(jenkins.Status.AppliedGroovyScripts))
})
}
func TestGroovy_Ensure(t *testing.T) {
log.SetupLogger(true)
configurationType := "test-conf-type"
groovyScript := "groovy-script"
groovyScriptName := "groovy-script-name.groovy"
ctx := context.TODO()
jenkinsName := "jenkins"
namespace := "default"
configMapName := "config-map-name"
secretName := "secret-name"
allGroovyScriptsFunc := func(name string) bool {
return true
}
noUpdateGroovyScript := func(groovyScript string) string {
return groovyScript
}
t.Run("select groovy files with .groovy extension", func(t *testing.T) {
// given
groovyScriptExtension := ".groovy"
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
customization := v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: configMapName,
},
},
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Namespace: namespace,
},
Data: map[string]string{
groovyScriptName: groovyScript,
"to-ommit": "to-ommit",
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
err = fakeClient.Create(ctx, configMap)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript).Return("logs", nil)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, customization)
onlyGroovyFilesFunc := func(name string) bool {
return strings.HasSuffix(name, groovyScriptExtension)
}
// when
requeue, err := groovyClient.Ensure(onlyGroovyFilesFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.True(t, requeue)
requeue, err = groovyClient.Ensure(onlyGroovyFilesFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.False(t, requeue)
// then
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, "qoXeeh4ia+KXhT01lYNxe+oxByDf8dfT2npP9fgzjbk=", jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, configMapName, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
t.Run("change groovy script", func(t *testing.T) {
// given
groovyScriptSuffix := "suffix"
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
customization := v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: configMapName,
},
},
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Namespace: namespace,
},
Data: map[string]string{
groovyScriptName: groovyScript,
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
err = fakeClient.Create(ctx, configMap)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript+groovyScriptSuffix).Return("logs", nil)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, customization)
updateGroovyFunc := func(groovyScript string) string {
return groovyScript + groovyScriptSuffix
}
// when
requeue, err := groovyClient.Ensure(allGroovyScriptsFunc, updateGroovyFunc)
require.NoError(t, err)
assert.True(t, requeue)
requeue, err = groovyClient.Ensure(allGroovyScriptsFunc, updateGroovyFunc)
require.NoError(t, err)
assert.False(t, requeue)
// then
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, "TgTpV3nDxMNMM93t6jgni0UHa7C+uL+D+BLcW3a7b6M=", jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, configMapName, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
t.Run("execute script without secret and save status", func(t *testing.T) {
// given
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
customization := v1alpha2.Customization{
Configurations: []v1alpha2.ConfigMapRef{
{
Name: configMapName,
},
},
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Namespace: namespace,
},
Data: map[string]string{
groovyScriptName: groovyScript,
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
err = fakeClient.Create(ctx, configMap)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript).Return("logs", nil)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, customization)
// when
requeue, err := groovyClient.Ensure(allGroovyScriptsFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.True(t, requeue)
requeue, err = groovyClient.Ensure(allGroovyScriptsFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.False(t, requeue)
// then
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, "qoXeeh4ia+KXhT01lYNxe+oxByDf8dfT2npP9fgzjbk=", jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, configMapName, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
t.Run("execute script with secret and save status", func(t *testing.T) {
// given
jenkins := &v1alpha2.Jenkins{
ObjectMeta: metav1.ObjectMeta{
Name: jenkinsName,
Namespace: namespace,
},
}
customization := v1alpha2.Customization{
Secret: v1alpha2.SecretRef{Name: secretName},
Configurations: []v1alpha2.ConfigMapRef{
{
Name: configMapName,
},
},
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configMapName,
Namespace: namespace,
},
Data: map[string]string{
groovyScriptName: groovyScript,
},
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
Data: map[string][]byte{
"SECRET_KEY": []byte("secret-value"),
},
}
err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme)
require.NoError(t, err)
fakeClient := fake.NewFakeClient()
err = fakeClient.Create(ctx, jenkins)
require.NoError(t, err)
err = fakeClient.Create(ctx, secret)
require.NoError(t, err)
err = fakeClient.Create(ctx, configMap)
require.NoError(t, err)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := jenkinsclient.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().ExecuteScript(groovyScript).Return("logs", nil)
groovyClient := New(jenkinsClient, fakeClient, log.Log, jenkins, configurationType, customization)
// when
requeue, err := groovyClient.Ensure(allGroovyScriptsFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.True(t, requeue)
requeue, err = groovyClient.Ensure(allGroovyScriptsFunc, noUpdateGroovyScript)
require.NoError(t, err)
assert.False(t, requeue)
// then
err = fakeClient.Get(ctx, types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, jenkins)
require.NoError(t, err)
assert.Equal(t, 1, len(jenkins.Status.AppliedGroovyScripts))
assert.Equal(t, configurationType, jenkins.Status.AppliedGroovyScripts[0].ConfigurationType)
assert.Equal(t, "em9pjw9mUheUpPRCJWD2Dww+80YQPoHCZbzzKZZw4lo=", jenkins.Status.AppliedGroovyScripts[0].Hash)
assert.Equal(t, configMapName, jenkins.Status.AppliedGroovyScripts[0].Source)
assert.Equal(t, groovyScriptName, jenkins.Status.AppliedGroovyScripts[0].Name)
})
}
func TestGroovy_isGroovyScriptAlreadyApplied(t *testing.T) {
log.SetupLogger(true)
emptyCustomization := v1alpha2.Customization{}
configurationType := "test-conf-type"
t.Run("found", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Status: v1alpha2.JenkinsStatus{
AppliedGroovyScripts: []v1alpha2.AppliedGroovyScript{
{
ConfigurationType: configurationType,
Source: "source",
Name: "name",
Hash: "hash",
},
},
},
}
groovyClient := New(nil, nil, log.Log, jenkins, configurationType, emptyCustomization)
got := groovyClient.isGroovyScriptAlreadyApplied("source", "name", "hash")
assert.True(t, got)
})
t.Run("not found", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Status: v1alpha2.JenkinsStatus{
AppliedGroovyScripts: []v1alpha2.AppliedGroovyScript{
{
ConfigurationType: configurationType,
Source: "source",
Name: "name",
Hash: "hash",
},
},
},
}
groovyClient := New(nil, nil, log.Log, jenkins, configurationType, emptyCustomization)
got := groovyClient.isGroovyScriptAlreadyApplied("source", "not-exist", "hash")
assert.False(t, got)
})
t.Run("empty Jenkins status", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{}
groovyClient := New(nil, nil, log.Log, jenkins, configurationType, emptyCustomization)
got := groovyClient.isGroovyScriptAlreadyApplied("source", "name", "hash")
assert.False(t, got)
})
}
func TestAddSecretsLoaderToGroovyScript(t *testing.T) {
secretsPath := "/var/jenkins/groovy-scripts-secrets"
secretsLoader := fmt.Sprintf(secretsLoaderGroovyScriptFmt, secretsPath)
t.Run("without imports", func(t *testing.T) {
groovyScript := "println 'Simple groovy script"
updater := AddSecretsLoaderToGroovyScript(secretsPath)
got := updater(groovyScript)
assert.Equal(t, secretsLoader+groovyScript, got)
})
t.Run("with imports", func(t *testing.T) {
groovyScript := `import com.foo.bar
import com.foo.bar2
println 'Simple groovy script'`
imports := `import com.foo.bar
import com.foo.bar2`
tail := `println 'Simple groovy script'`
update := AddSecretsLoaderToGroovyScript(secretsPath)
got := update(groovyScript)
assert.Equal(t, imports+"\n\n"+secretsLoader+"\n\n"+tail, got)
})
t.Run("with imports and separate section", func(t *testing.T) {
groovyScript := `import com.foo.bar
import com.foo.bar2
println 'Simple groovy script'`
imports := `import com.foo.bar
import com.foo.bar2`
tail := `println 'Simple groovy script'`
update := AddSecretsLoaderToGroovyScript(secretsPath)
got := update(groovyScript)
assert.Equal(t, imports+"\n\n"+secretsLoader+"\n\n\n"+tail, got)
})
}