307 lines
10 KiB
Go
307 lines
10 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/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/configuration/user/seedjobs"
|
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
|
|
|
|
"github.com/bndr/gojenkins"
|
|
framework "github.com/operator-framework/operator-sdk/pkg/test"
|
|
"github.com/stretchr/testify/assert"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
)
|
|
|
|
func TestConfiguration(t *testing.T) {
|
|
t.Parallel()
|
|
namespace, ctx := setupTest(t)
|
|
// Deletes test namespace
|
|
defer ctx.Cleanup()
|
|
|
|
jenkinsCRName := "e2e"
|
|
numberOfExecutors := 6
|
|
systemMessage := "Configuration as Code integration works!!!"
|
|
systemMessageEnvName := "SYSTEM_MESSAGE"
|
|
jenkinsCredentialName := "kubernetes-credentials-provider-plugin"
|
|
|
|
// base
|
|
createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage)
|
|
createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName))
|
|
jenkins := createJenkinsCR(t, jenkinsCRName, namespace)
|
|
createDefaultLimitsForContainersInNamespace(t, namespace)
|
|
createKubernetesCredentialsProviderSecret(t, namespace, jenkinsCredentialName)
|
|
waitForJenkinsBaseConfigurationToComplete(t, jenkins)
|
|
|
|
verifyJenkinsMasterPodAttributes(t, jenkins)
|
|
client := verifyJenkinsAPIConnection(t, jenkins)
|
|
verifyPlugins(t, client, jenkins)
|
|
|
|
// user
|
|
waitForJenkinsUserConfigurationToComplete(t, jenkins)
|
|
verifyJenkinsSeedJobs(t, client, jenkins)
|
|
verifyUserConfiguration(t, client, numberOfExecutors, systemMessage)
|
|
verifyIfJenkinsCredentialExists(t, client, jenkinsCredentialName)
|
|
}
|
|
|
|
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 createKubernetesCredentialsProviderSecret(t *testing.T, namespace, name string) {
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
Annotations: map[string]string{
|
|
"jenkins.io/credentials-description": "credentials from Kubernetes",
|
|
},
|
|
Labels: map[string]string{
|
|
"jenkins.io/credentials-type": "usernamePassword",
|
|
},
|
|
},
|
|
StringData: map[string]string{
|
|
"username": "user",
|
|
"password": "pass",
|
|
},
|
|
}
|
|
|
|
t.Logf("Secret for Kubernetes credentials provider plugin %+v", *secret)
|
|
if err := framework.Global.Client.Create(context.TODO(), secret, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func createUserConfigurationConfigMap(t *testing.T, jenkinsCRName string, namespace string, numberOfExecutors int, systemMessage string) {
|
|
userConfiguration := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: resources.GetUserConfigurationConfigMapName(jenkinsCRName),
|
|
Namespace: namespace,
|
|
},
|
|
Data: map[string]string{
|
|
"1-set-executors.groovy": fmt.Sprintf(`
|
|
import jenkins.model.Jenkins
|
|
|
|
Jenkins.instance.setNumExecutors(%d)
|
|
Jenkins.instance.save()`, numberOfExecutors),
|
|
"1-casc.yaml": fmt.Sprintf(`
|
|
jenkins:
|
|
systemMessage: "%s"`, systemMessage),
|
|
},
|
|
}
|
|
|
|
t.Logf("User configuration %+v", *userConfiguration)
|
|
if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) {
|
|
limitRange := &corev1.LimitRange{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "e2e",
|
|
Namespace: namespace,
|
|
},
|
|
Spec: corev1.LimitRangeSpec{
|
|
Limits: []corev1.LimitRangeItem{
|
|
{
|
|
Type: corev1.LimitTypeContainer,
|
|
DefaultRequest: map[corev1.ResourceName]resource.Quantity{
|
|
corev1.ResourceCPU: resource.MustParse("1"),
|
|
corev1.ResourceMemory: resource.MustParse("1Gi"),
|
|
},
|
|
Default: map[corev1.ResourceName]resource.Quantity{
|
|
corev1.ResourceCPU: resource.MustParse("4"),
|
|
corev1.ResourceMemory: resource.MustParse("4Gi"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Logf("LimitRange %+v", *limitRange)
|
|
if err := framework.Global.Client.Create(context.TODO(), limitRange, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha1.Jenkins) {
|
|
jenkinsPod := getJenkinsMasterPod(t, jenkins)
|
|
jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name)
|
|
|
|
for key, value := range jenkins.Spec.Master.Annotations {
|
|
if jenkinsPod.ObjectMeta.Annotations[key] != value {
|
|
t.Fatalf("Invalid Jenkins pod annotation expected '%+v', actual '%+v'", jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations)
|
|
}
|
|
}
|
|
|
|
if jenkinsPod.Spec.Containers[0].Image != jenkins.Spec.Master.Image {
|
|
t.Fatalf("Invalid jenkins pod image expected '%s', actual '%s'", jenkins.Spec.Master.Image, jenkinsPod.Spec.Containers[0].Image)
|
|
}
|
|
|
|
if !reflect.DeepEqual(jenkinsPod.Spec.Containers[0].Resources, jenkins.Spec.Master.Resources) {
|
|
t.Fatalf("Invalid jenkins pod continer resources expected '%+v', actual '%+v'", jenkins.Spec.Master.Resources, jenkinsPod.Spec.Containers[0].Resources)
|
|
}
|
|
|
|
if !reflect.DeepEqual(jenkinsPod.Spec.NodeSelector, jenkins.Spec.Master.NodeSelector) {
|
|
t.Fatalf("Invalid jenkins pod node selector expected '%+v', actual '%+v'", jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector)
|
|
}
|
|
|
|
requiredEnvs := resources.GetJenkinsMasterPodBaseEnvs()
|
|
requiredEnvs = append(requiredEnvs, jenkins.Spec.Master.Env...)
|
|
if !reflect.DeepEqual(jenkinsPod.Spec.Containers[0].Env, requiredEnvs) {
|
|
t.Fatalf("Invalid jenkins pod continer resources expected '%+v', actual '%+v'", requiredEnvs, jenkinsPod.Spec.Containers[0].Env)
|
|
}
|
|
|
|
t.Log("Jenkins pod attributes are valid")
|
|
}
|
|
|
|
func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) {
|
|
installedPlugins, err := jenkinsClient.GetPlugins(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
requiredPlugins := []map[string][]string{plugins.BasePlugins(), jenkins.Spec.Master.Plugins}
|
|
for _, p := range requiredPlugins {
|
|
for rootPluginName, dependentPlugins := range p {
|
|
rootPlugin, err := plugins.New(rootPluginName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if found, ok := isPluginValid(installedPlugins, *rootPlugin); !ok {
|
|
t.Fatalf("Invalid plugin '%s', actual '%+v'", rootPlugin, found)
|
|
}
|
|
for _, pluginName := range dependentPlugins {
|
|
plugin, err := plugins.New(pluginName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if found, ok := isPluginValid(installedPlugins, *plugin); !ok {
|
|
t.Fatalf("Invalid plugin '%s', actual '%+v'", rootPlugin, found)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
t.Log("All plugins have been installed")
|
|
}
|
|
|
|
func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*gojenkins.Plugin, bool) {
|
|
p := plugins.Contains(requiredPlugin.Name)
|
|
if p == nil {
|
|
return p, false
|
|
}
|
|
|
|
if !p.Active || !p.Enabled || p.Deleted {
|
|
return p, false
|
|
}
|
|
|
|
return p, requiredPlugin.Version == p.Version
|
|
}
|
|
|
|
func verifyJenkinsSeedJobs(t *testing.T, client jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) {
|
|
t.Logf("Attempting to get configure seed job status '%v'", seedjobs.ConfigureSeedJobsName)
|
|
|
|
configureSeedJobs, err := client.GetJob(seedjobs.ConfigureSeedJobsName)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, configureSeedJobs)
|
|
build, err := configureSeedJobs.GetLastSuccessfulBuild()
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, build)
|
|
|
|
seedJobName := "jenkins-operator-configure-seed-job"
|
|
t.Logf("Attempting to verify if seed job has been created '%v'", seedJobName)
|
|
seedJob, err := client.GetJob(seedJobName)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, seedJob)
|
|
|
|
build, err = seedJob.GetLastSuccessfulBuild()
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, build)
|
|
|
|
err = framework.Global.Client.Get(context.TODO(), types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}, jenkins)
|
|
assert.NoError(t, err, "couldn't get jenkins custom resource")
|
|
assert.NotNil(t, jenkins.Status.Builds)
|
|
assert.NotEmpty(t, jenkins.Status.Builds)
|
|
|
|
jobCreatedByDSLPluginName := "build-jenkins-operator"
|
|
err = wait.Poll(time.Second*10, time.Minute*2, func() (bool, error) {
|
|
t.Logf("Attempting to verify if job '%s' has been created ", jobCreatedByDSLPluginName)
|
|
seedJob, err := client.GetJob(jobCreatedByDSLPluginName)
|
|
if err != nil || seedJob == nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
})
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) {
|
|
checkConfigurationViaGroovyScript := fmt.Sprintf(`
|
|
if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) {
|
|
throw new Exception("Configuration via groovy scripts failed")
|
|
}`, amountOfExecutors)
|
|
logs, err := jenkinsClient.ExecuteScript(checkConfigurationViaGroovyScript)
|
|
assert.NoError(t, err, logs)
|
|
|
|
checkConfigurationAsCode := fmt.Sprintf(`
|
|
if (!"%s".equals(Jenkins.instance.systemMessage)) {
|
|
throw new Exception("Configuration as code failed")
|
|
}`, systemMessage)
|
|
logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode)
|
|
assert.NoError(t, err, logs)
|
|
}
|
|
|
|
func verifyIfJenkinsCredentialExists(t *testing.T, jenkinsClient jenkinsclient.Jenkins, credentialName string) {
|
|
groovyScriptFmt := `import com.cloudbees.plugins.credentials.Credentials
|
|
|
|
Set<Credentials> allCredentials = new HashSet<Credentials>();
|
|
|
|
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
|
|
com.cloudbees.plugins.credentials.Credentials.class
|
|
);
|
|
|
|
allCredentials.addAll(creds)
|
|
|
|
Jenkins.instance.getAllItems(com.cloudbees.hudson.plugins.folder.Folder.class).each{ f ->
|
|
creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
|
|
com.cloudbees.plugins.credentials.Credentials.class, f)
|
|
allCredentials.addAll(creds)
|
|
}
|
|
|
|
def found = false
|
|
for (c in allCredentials) {
|
|
if("%s".equals(c.id)) found = true
|
|
}
|
|
if(!found) {
|
|
throw new Exception("Expected credential not found")
|
|
}`
|
|
groovyScript := fmt.Sprintf(groovyScriptFmt, credentialName)
|
|
logs, err := jenkinsClient.ExecuteScript(groovyScript)
|
|
assert.NoError(t, err, logs)
|
|
}
|