#28 Change groovy client implementation
This commit is contained in:
parent
54454d9a02
commit
87fcc5f8a5
|
|
@ -1,6 +1,7 @@
|
||||||
package groovy
|
package groovy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -9,144 +10,227 @@ 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/jobs"
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
|
|
||||||
"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 (
|
// Groovy defines API for groovy secrets execution via jenkins job
|
||||||
jobHashParameterName = "hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Groovy defines API for groovy scripts execution via jenkins job
|
|
||||||
type Groovy struct {
|
type Groovy struct {
|
||||||
jenkinsClient jenkinsclient.Jenkins
|
k8sClient k8s.Client
|
||||||
k8sClient k8s.Client
|
logger logr.Logger
|
||||||
logger logr.Logger
|
jenkins *v1alpha2.Jenkins
|
||||||
jobName string
|
jenkinsClient jenkinsclient.Jenkins
|
||||||
scriptsPath string
|
configurationType string
|
||||||
|
customization v1alpha2.Customization
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates new instance of Groovy
|
// 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{
|
return &Groovy{
|
||||||
jenkinsClient: jenkinsClient,
|
jenkinsClient: jenkinsClient,
|
||||||
k8sClient: k8sClient,
|
k8sClient: k8sClient,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
jobName: jobName,
|
jenkins: jenkins,
|
||||||
scriptsPath: scriptsPath,
|
configurationType: configurationType,
|
||||||
|
customization: customization,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureJob configures jenkins job for executing groovy scripts
|
// EnsureSingle runs single groovy script
|
||||||
func (g *Groovy) ConfigureJob() error {
|
func (g *Groovy) EnsureSingle(source, name, hash, groovyScript string) (requeue bool, err error) {
|
||||||
_, created, err := g.jenkinsClient.CreateOrUpdateJob(fmt.Sprintf(configurationJobXMLFmt, g.scriptsPath), g.jobName)
|
if g.isGroovyScriptAlreadyApplied(source, name, hash) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := g.jenkinsClient.ExecuteScript(groovyScript)
|
||||||
if err != nil {
|
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))
|
g.jenkins.Status.AppliedGroovyScripts = append(g.jenkins.Status.AppliedGroovyScripts, v1alpha2.AppliedGroovyScript{
|
||||||
}
|
ConfigurationType: g.configurationType,
|
||||||
return nil
|
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
|
// WaitForSecretSynchronization runs groovy script which waits to synchronize secrets in pod by k8s
|
||||||
func (g *Groovy) Ensure(secretOrConfigMapData map[string]string, jenkins *v1alpha2.Jenkins) (bool, error) {
|
func (g *Groovy) WaitForSecretSynchronization(secretsPath string) (requeue bool, err error) {
|
||||||
jobsClient := jobs.New(g.jenkinsClient, g.k8sClient, g.logger)
|
if len(g.customization.Secret.Name) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
hash := g.calculateHash(secretOrConfigMapData)
|
secret := &corev1.Secret{}
|
||||||
done, err := jobsClient.EnsureBuildJob(g.jobName, hash, map[string]string{jobHashParameterName: hash}, jenkins, true)
|
err = g.k8sClient.Get(context.TODO(), types.NamespacedName{Name: g.customization.Secret.Name, Namespace: g.jenkins.ObjectMeta.Namespace}, secret)
|
||||||
if err != nil {
|
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()
|
hash := sha256.New()
|
||||||
|
|
||||||
var keys []string
|
var keys []string
|
||||||
for key := range secretOrConfigMapData {
|
for key := range 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, ".groovy") {
|
hash.Write([]byte(key))
|
||||||
hash.Write([]byte(key))
|
hash.Write([]byte(data[key]))
|
||||||
hash.Write([]byte(secretOrConfigMapData[key]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
const configurationJobXMLFmt = `<?xml version='1.1' encoding='UTF-8'?>
|
// AddSecretsLoaderToGroovyScript modify groovy scripts to load Kubernetes secrets into groovy map
|
||||||
<flow-definition plugin="workflow-job@2.31">
|
func AddSecretsLoaderToGroovyScript(secretsPath string) func(groovyScript string) string {
|
||||||
<actions/>
|
return func(groovyScript string) string {
|
||||||
<description></description>
|
if !strings.HasPrefix(groovyScript, importPrefix) {
|
||||||
<keepDependencies>false</keepDependencies>
|
return fmt.Sprintf(secretsLoaderGroovyScriptFmt, secretsPath) + groovyScript
|
||||||
<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 = '%s'
|
|
||||||
def expectedHash = params.hash
|
|
||||||
|
|
||||||
node('master') {
|
lines := strings.Split(groovyScript, "\n")
|
||||||
def scriptsText = sh(script: "ls ${scriptsPath} | grep .groovy | sort", returnStdout: true).trim()
|
importIndex := -1
|
||||||
def scripts = []
|
for i, line := range lines {
|
||||||
scripts.addAll(scriptsText.tokenize('\n'))
|
if !strings.HasPrefix(line, importPrefix) {
|
||||||
|
importIndex = i
|
||||||
stage('Synchronizing files') {
|
break
|
||||||
println "Synchronizing Kubernetes ConfigMaps to the Jenkins master pod."
|
}
|
||||||
println "This step may fail and will be retried in the next job build if necessary."
|
}
|
||||||
|
asdf := strings.Join(lines[:importIndex], "\n") + "\n\n" + fmt.Sprintf(secretsLoaderGroovyScriptFmt, secretsPath) + "\n\n" + strings.Join(lines[importIndex:], "\n")
|
||||||
|
|
||||||
def complete = false
|
return asdf
|
||||||
for(int i = 1; i <= 10; i++) {
|
}
|
||||||
def actualHash = calculateHash((String[])scripts, scriptsPath)
|
|
||||||
println "Expected hash '${expectedHash}', actual hash '${actualHash}', will retry"
|
|
||||||
if(expectedHash == actualHash) {
|
|
||||||
complete = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sleep 2
|
|
||||||
}
|
|
||||||
if(!complete) {
|
|
||||||
error("Timeout while synchronizing files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(script in scripts) {
|
|
||||||
stage(script) {
|
|
||||||
load "${scriptsPath}/${script}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonCPS
|
const importPrefix = "import "
|
||||||
def calculateHash(String[] scripts, String scriptsPath) {
|
|
||||||
def hash = java.security.MessageDigest.getInstance("SHA-256")
|
const secretsLoaderGroovyScriptFmt = `def secretsPath = '%s'
|
||||||
for(script in scripts) {
|
def secrets = [:]
|
||||||
hash.update(script.getBytes())
|
"ls ${secretsPath}".execute().text.eachLine {secrets[it] = new File("${secretsPath}/${it}").text}`
|
||||||
def fileLocation = java.nio.file.Paths.get("${scriptsPath}/${script}")
|
|
||||||
|
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)
|
def fileData = java.nio.file.Files.readAllBytes(fileLocation)
|
||||||
hash.update(fileData)
|
hash.update(fileData)
|
||||||
}
|
}
|
||||||
return Base64.getEncoder().encodeToString(hash.digest())
|
return Base64.getEncoder().encodeToString(hash.digest())
|
||||||
}</script>
|
}
|
||||||
<sandbox>false</sandbox>
|
|
||||||
</definition>
|
|
||||||
<triggers/>
|
|
||||||
<disabled>false</disabled>
|
|
||||||
</flow-definition>
|
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue