package e2e import ( "context" "fmt" "testing" "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" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" "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" ) 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" mySeedJob := seedJobConfig{ SeedJob: v1alpha1.SeedJob{ ID: "jenkins-operator", CredentialID: "jenkins-operator", JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, Targets: "cicd/jobs/*.jenkins", Description: "Jenkins Operator repository", RepositoryBranch: "master", RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", }, } volumes := []corev1.Volume{ { Name: "test-configmap", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ Name: resources.GetUserConfigurationConfigMapName(jenkinsCRName), }, }, }, }, { Name: "test-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: resources.GetUserConfigurationSecretName(jenkinsCRName), }, }, }, } // base createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName)) jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha1.SeedJob{mySeedJob.SeedJob}, volumes) createDefaultLimitsForContainersInNamespace(t, namespace) createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) waitForJenkinsBaseConfigurationToComplete(t, jenkins) verifyJenkinsMasterPodAttributes(t, jenkins) client := verifyJenkinsAPIConnection(t, jenkins) verifyPlugins(t, client, jenkins) // user waitForJenkinsUserConfigurationToComplete(t, jenkins) verifyUserConfiguration(t, client, numberOfExecutors, systemMessage) verifyJenkinsSeedJobs(t, client, []seedJobConfig{mySeedJob}) } 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) { 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), "2-casc.yaml": ` unclassified: location: url: http://external-jenkins-url:8080`, }, } 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) assert.Equal(t, jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) assert.Equal(t, jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector) assert.Equal(t, resources.JenkinsMasterContainerName, jenkinsPod.Spec.Containers[0].Name) assert.Equal(t, len(jenkins.Spec.Master.Containers)+1, len(jenkinsPod.Spec.Containers)) for _, actualContainer := range jenkinsPod.Spec.Containers { if actualContainer.Name == resources.JenkinsMasterContainerName { verifyContainer(t, resources.NewJenkinsMasterContainer(jenkins), actualContainer) continue } var expectedContainer *corev1.Container for _, jenkinsContainer := range jenkins.Spec.Master.Containers { if jenkinsContainer.Name == actualContainer.Name { tmp := resources.ConvertJenkinsContainerToKubernetesContainer(jenkinsContainer) expectedContainer = &tmp } } if expectedContainer == nil { t.Errorf("Container '%+v' not found in pod", actualContainer) continue } verifyContainer(t, *expectedContainer, actualContainer) } for _, expectedVolume := range jenkins.Spec.Master.Volumes { volumeFound := false for _, actualVolume := range jenkinsPod.Spec.Volumes { if expectedVolume.Name == actualVolume.Name { volumeFound = true assert.Equal(t, expectedVolume, actualVolume) } } if !volumeFound { t.Errorf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes) } } t.Log("Jenkins pod attributes are valid") } func verifyContainer(t *testing.T, expected corev1.Container, actual corev1.Container) { assert.Equal(t, expected.Args, actual.Args, expected.Name, expected.Name) assert.Equal(t, expected.Command, actual.Command, expected.Name) assert.Equal(t, expected.Env, actual.Env, expected.Name) assert.Equal(t, expected.EnvFrom, actual.EnvFrom, expected.Name) assert.Equal(t, expected.Image, actual.Image, expected.Name) assert.Equal(t, expected.ImagePullPolicy, actual.ImagePullPolicy, expected.Name) assert.Equal(t, expected.Lifecycle, actual.Lifecycle, expected.Name) assert.Equal(t, expected.LivenessProbe, actual.LivenessProbe, expected.Name) assert.Equal(t, expected.Ports, actual.Ports, expected.Name) assert.Equal(t, expected.ReadinessProbe, actual.ReadinessProbe, expected.Name) assert.Equal(t, expected.Resources, actual.Resources, expected.Name) assert.Equal(t, expected.SecurityContext, actual.SecurityContext, expected.Name) assert.Equal(t, expected.WorkingDir, actual.WorkingDir, expected.Name) if !base.CompareContainerVolumeMounts(expected, actual) { t.Errorf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", expected.Name, expected.VolumeMounts, actual.VolumeMounts) } } 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 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) }