diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/pkg/apis/jenkins/v1alpha2/jenkins_types.go index 8ffcd533..cdf98c23 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/pkg/apis/jenkins/v1alpha2/jenkins_types.go @@ -483,31 +483,31 @@ type SeedJob struct { // GitHubPushTrigger is used for GitHub web hooks // +optional - GitHubPushTrigger bool `json:"githubPushTrigger,omitempty"` + GitHubPushTrigger bool `json:"githubPushTrigger"` // BuildPeriodically is setting for scheduled trigger // +optional - BuildPeriodically string `json:"buildPeriodically,omitempty"` + BuildPeriodically string `json:"buildPeriodically"` // PollSCM is setting for polling changes in SCM // +optional - PollSCM string `json:"pollSCM,omitempty"` + PollSCM string `json:"pollSCM"` // IgnoreMissingFiles is setting for Job DSL API plugin to ignore files that miss // +optional - IgnoreMissingFiles bool `json:"ignoreMissingFiles,omitempty"` + IgnoreMissingFiles bool `json:"ignoreMissingFiles"` // AdditionalClasspath is setting for Job DSL API plugin to set Additional Classpath // +optional - AdditionalClasspath string `json:"additionalClasspath,omitempty"` + AdditionalClasspath string `json:"additionalClasspath"` // FailOnMissingPlugin is setting for Job DSL API plugin that fails job if required plugin is missing // +optional - FailOnMissingPlugin bool `json:"failOnMissingPlugin,omitempty"` + FailOnMissingPlugin bool `json:"failOnMissingPlugin"` // UnstableOnDeprecation is setting for Job DSL API plugin that sets build status as unstable if build using deprecated features // +optional - UnstableOnDeprecation bool `json:"unstableOnDeprecation,omitempty"` + UnstableOnDeprecation bool `json:"unstableOnDeprecation"` } // Handler defines a specific action that should be taken diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go index e7b26984..3f983283 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go @@ -40,7 +40,7 @@ const ( JenkinsCredentialTypeLabelName = "jenkins.io/credentials-type" // AgentName is the name of seed job agent - AgentName = "jnlp" + AgentName = "seed-job-agent" creatingGroovyScriptName = "seed-job-groovy-script.groovy" ) @@ -79,7 +79,7 @@ import static com.google.common.collect.Lists.newArrayList; Jenkins jenkins = Jenkins.instance -def jobDslSeedName = "{{ .ID }} - {{ .SeedJobSuffix }}"; +def jobDslSeedName = "{{ .ID }}-{{ .SeedJobSuffix }}"; def jobRef = jenkins.getItem(jobDslSeedName) def repoList = GitSCM.createRepoList("{{ .RepositoryURL }}", "{{ .CredentialID }}") @@ -161,7 +161,7 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err err := s.k8sClient.Delete(context.TODO(), &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: jenkins.Namespace, - Name: AgentName, + Name: agentDeploymentName(*jenkins, AgentName), }, }) @@ -345,10 +345,14 @@ func (s SeedJobs) createAgent(jenkinsClient jenkinsclient.Jenkins, k8sClient cli return nil } -func agentDeployment(jenkinsManifest *v1alpha2.Jenkins, namespace string, agentName string, secret string) *apps.Deployment { +func agentDeploymentName(jenkins v1alpha2.Jenkins, agentName string) string { + return fmt.Sprintf("%s-%s", agentName, jenkins.Name) +} + +func agentDeployment(jenkins *v1alpha2.Jenkins, namespace string, agentName string, secret string) *apps.Deployment { return &apps.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: agentName, + Name: agentDeploymentName(*jenkins, agentName), Namespace: namespace, }, Spec: apps.DeploymentSpec{ @@ -362,9 +366,9 @@ func agentDeployment(jenkinsManifest *v1alpha2.Jenkins, namespace string, agentN { Name: "JENKINS_TUNNEL", Value: fmt.Sprintf("%s.%s:%d", - resources.GetJenkinsSlavesServiceName(jenkinsManifest), - jenkinsManifest.ObjectMeta.Namespace, - jenkinsManifest.Spec.SlaveService.Port), + resources.GetJenkinsSlavesServiceName(jenkins), + jenkins.ObjectMeta.Namespace, + jenkins.Spec.SlaveService.Port), }, { Name: "JENKINS_SECRET", @@ -377,9 +381,9 @@ func agentDeployment(jenkinsManifest *v1alpha2.Jenkins, namespace string, agentN { Name: "JENKINS_URL", Value: fmt.Sprintf("http://%s.%s:%d", - resources.GetJenkinsHTTPServiceName(jenkinsManifest), - jenkinsManifest.ObjectMeta.Namespace, - jenkinsManifest.Spec.Service.Port, + resources.GetJenkinsHTTPServiceName(jenkins), + jenkins.ObjectMeta.Namespace, + jenkins.Spec.Service.Port, ), }, { diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go index 0a1f5ee3..4e442c42 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go @@ -79,20 +79,19 @@ func TestEnsureSeedJobs(t *testing.T) { err = fakeClient.Create(ctx, jenkins) assert.NoError(t, err) - agentName := "jnlp" agentSecret := "test-secret" testNode := &gojenkins.Node{ Raw: &gojenkins.NodeResponse{ - DisplayName: agentName, + DisplayName: AgentName, }, } seedJobCreatingScript, err := seedJobCreatingGroovyScript(jenkins.Spec.SeedJobs[0]) assert.NoError(t, err) - jenkinsClient.EXPECT().GetNode(agentName).Return(nil, nil).AnyTimes() - jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).Return(testNode, nil).AnyTimes() - jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes() + jenkinsClient.EXPECT().GetNode(AgentName).Return(nil, nil).AnyTimes() + jenkinsClient.EXPECT().CreateNode(AgentName, 1, "The jenkins-operator generated agent", "/home/jenkins", AgentName).Return(testNode, nil).AnyTimes() + jenkinsClient.EXPECT().GetNodeSecret(AgentName).Return(agentSecret, nil).AnyTimes() jenkinsClient.EXPECT().ExecuteScript(seedJobCreatingScript).AnyTimes() seedJobClient := New(jenkinsClient, fakeClient, logger) @@ -104,7 +103,7 @@ func TestEnsureSeedJobs(t *testing.T) { assert.NoError(t, err) var agentDeployment appsv1.Deployment - err = fakeClient.Get(ctx, types.NamespacedName{Namespace: jenkins.Namespace, Name: agentName}, &agentDeployment) + err = fakeClient.Get(ctx, types.NamespacedName{Namespace: jenkins.Namespace, Name: agentDeploymentName(*jenkins, AgentName)}, &agentDeployment) assert.NoError(t, err) }) @@ -114,7 +113,6 @@ func TestEnsureSeedJobs(t *testing.T) { ctx := context.TODO() defer ctrl.Finish() - agentName := "test-agent" agentSecret := "test-secret" jenkins := jenkinsCustomResource() jenkins.Spec.SeedJobs = []v1alpha2.SeedJob{} @@ -124,15 +122,15 @@ func TestEnsureSeedJobs(t *testing.T) { err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) - jenkinsClient.EXPECT().GetNode(agentName).AnyTimes() - jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).AnyTimes() - jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes() + jenkinsClient.EXPECT().GetNode(AgentName).AnyTimes() + jenkinsClient.EXPECT().CreateNode(AgentName, 1, "The jenkins-operator generated agent", "/home/jenkins", AgentName).AnyTimes() + jenkinsClient.EXPECT().GetNodeSecret(AgentName).Return(agentSecret, nil).AnyTimes() seedJobsClient := New(jenkinsClient, fakeClient, nil) err = fakeClient.Create(ctx, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: agentName, + Name: agentDeploymentName(*jenkins, AgentName), Namespace: jenkins.Namespace, }, }) @@ -145,9 +143,9 @@ func TestEnsureSeedJobs(t *testing.T) { assert.NoError(t, err) var deployment appsv1.Deployment - err = fakeClient.Get(ctx, types.NamespacedName{Name: agentName, Namespace: jenkins.Namespace}, &deployment) + err = fakeClient.Get(ctx, types.NamespacedName{Namespace: jenkins.Namespace, Name: agentDeploymentName(*jenkins, AgentName)}, &deployment) - assert.False(t, errors.IsNotFound(err), "Agent deployment hasn't been deleted") + assert.True(t, errors.IsNotFound(err), "Agent deployment hasn't been deleted") }) } @@ -158,7 +156,6 @@ func TestCreateAgent(t *testing.T) { ctx := context.TODO() defer ctrl.Finish() - agentName := "test-agent" agentSecret := "test-secret" jenkins := jenkinsCustomResource() @@ -167,22 +164,22 @@ func TestCreateAgent(t *testing.T) { err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) - jenkinsClient.EXPECT().GetNode(agentName).AnyTimes() - jenkinsClient.EXPECT().CreateNode(agentName, 1, "The jenkins-operator generated agent", "/home/jenkins", agentName).AnyTimes() - jenkinsClient.EXPECT().GetNodeSecret(agentName).Return(agentSecret, nil).AnyTimes() + jenkinsClient.EXPECT().GetNode(AgentName).AnyTimes() + jenkinsClient.EXPECT().CreateNode(AgentName, 1, "The jenkins-operator generated agent", "/home/jenkins", AgentName).AnyTimes() + jenkinsClient.EXPECT().GetNodeSecret(AgentName).Return(agentSecret, nil).AnyTimes() seedJobsClient := New(jenkinsClient, fakeClient, nil) err = fakeClient.Create(ctx, &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: agentName, + Name: agentDeploymentName(*jenkins, AgentName), Namespace: jenkins.Namespace, }, }) assert.NoError(t, err) // when - err = seedJobsClient.createAgent(jenkinsClient, fakeClient, jenkinsCustomResource(), jenkins.Namespace, agentName) + err = seedJobsClient.createAgent(jenkinsClient, fakeClient, jenkinsCustomResource(), jenkins.Namespace, AgentName) // then assert.NoError(t, err) diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go index 0beae099..557fc1cf 100644 --- a/test/e2e/configuration_test.go +++ b/test/e2e/configuration_test.go @@ -40,6 +40,13 @@ func TestConfiguration(t *testing.T) { Description: "Jenkins Operator repository", RepositoryBranch: "master", RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + PollSCM: "1 1 1 1 1", + UnstableOnDeprecation: true, + BuildPeriodically: "1 1 1 1 1", + FailOnMissingPlugin: true, + IgnoreMissingFiles: true, + //AdditionalClasspath: can fail with the seed job agent + GitHubPushTrigger: true, }, } groovyScripts := v1alpha2.GroovyScripts{ diff --git a/test/e2e/jenkins.go b/test/e2e/jenkins.go index f6e91176..e842e9f1 100644 --- a/test/e2e/jenkins.go +++ b/test/e2e/jenkins.go @@ -124,6 +124,7 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S Plugins: []v1alpha2.Plugin{ {Name: "audit-trail", Version: "2.4"}, {Name: "simple-theme-plugin", Version: "0.5.1"}, + {Name: "github", Version: "1.29.4"}, }, NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, }, diff --git a/test/e2e/seedjobs_test.go b/test/e2e/seedjobs_test.go index 8e3d5421..c50b9e40 100644 --- a/test/e2e/seedjobs_test.go +++ b/test/e2e/seedjobs_test.go @@ -7,12 +7,15 @@ import ( "io/ioutil" "os" "testing" + "text/template" "time" + "github.com/jenkinsci/kubernetes-operator/internal/render" "github.com/jenkinsci/kubernetes-operator/internal/try" "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/configuration/user/seedjobs" + "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" framework "github.com/operator-framework/operator-sdk/pkg/test" "github.com/stretchr/testify/assert" @@ -113,6 +116,8 @@ func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, se assert.NoErrorf(t, err, "Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID) } + verifySeedJobProperties(t, jenkinsClient, seedJob) + for _, requireJobName := range seedJob.JobNames { err = try.Until(func() (end bool, err error) { _, err = jenkinsClient.GetJob(requireJobName) @@ -123,6 +128,46 @@ func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, se } } +func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) { + data := struct { + ID string + CredentialID string + Targets string + RepositoryBranch string + RepositoryURL string + GitHubPushTrigger bool + BuildPeriodically string + PollSCM string + IgnoreMissingFiles bool + AdditionalClasspath string + FailOnMissingPlugin bool + UnstableOnDeprecation bool + SeedJobSuffix string + AgentName string + }{ + ID: seedJob.ID, + CredentialID: seedJob.CredentialID, + Targets: seedJob.Targets, + RepositoryBranch: seedJob.RepositoryBranch, + RepositoryURL: seedJob.RepositoryURL, + GitHubPushTrigger: seedJob.GitHubPushTrigger, + BuildPeriodically: seedJob.BuildPeriodically, + PollSCM: seedJob.PollSCM, + IgnoreMissingFiles: seedJob.IgnoreMissingFiles, + AdditionalClasspath: seedJob.AdditionalClasspath, + FailOnMissingPlugin: seedJob.FailOnMissingPlugin, + UnstableOnDeprecation: seedJob.UnstableOnDeprecation, + SeedJobSuffix: constants.SeedJobSuffix, + AgentName: seedjobs.AgentName, + } + + groovyScript, err := render.Render(verifySeedJobPropertiesGroovyScriptTemplate, data) + assert.NoError(t, err, groovyScript) + + logs, err := jenkinsClient.ExecuteScript(groovyScript) + assert.NoError(t, err, logs, groovyScript) +} + func verifyIfJenkinsCredentialExists(jenkinsClient jenkinsclient.Jenkins, credentialName string) error { groovyScriptFmt := `import com.cloudbees.plugins.credentials.Credentials @@ -151,3 +196,100 @@ if(!found) { _, err := jenkinsClient.ExecuteScript(groovyScript) return err } + +var verifySeedJobPropertiesGroovyScriptTemplate = template.Must(template.New("test-e2e-verify-job-properties").Parse(` +import hudson.model.FreeStyleProject; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.BranchSpec; +import hudson.triggers.SCMTrigger; +import hudson.triggers.TimerTrigger; +import hudson.util.Secret; +import javaposse.jobdsl.plugin.*; +import jenkins.model.Jenkins; +import jenkins.model.JenkinsLocationConfiguration; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.domains.Domain; +import com.cloudbees.plugins.credentials.SystemCredentialsProvider; +import jenkins.model.JenkinsLocationConfiguration; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; +{{ if .GitHubPushTrigger }} +import com.cloudbees.jenkins.GitHubPushTrigger; +{{ end }} +import hudson.model.FreeStyleProject; +import hudson.model.labels.LabelAtom; +import hudson.plugins.git.BranchSpec; +import hudson.plugins.git.GitSCM; +import hudson.plugins.git.SubmoduleConfig; +import hudson.plugins.git.extensions.impl.CloneOption; +import javaposse.jobdsl.plugin.ExecuteDslScripts; +import javaposse.jobdsl.plugin.LookupStrategy; +import javaposse.jobdsl.plugin.RemovedJobAction; +import javaposse.jobdsl.plugin.RemovedViewAction; +import hudson.tasks.BuildStep; + +Jenkins jenkins = Jenkins.instance + +def jobDslSeedName = "{{ .ID }}-{{ .SeedJobSuffix }}" +def jobRef = jenkins.getItem(jobDslSeedName) + +if (jobRef == null) { + throw new Exception("Job with given name not found") +} + +if (!jobRef.getDisplayName().equals("Seed Job from {{ .ID }}")) { + throw new Exception("Display name is not equal") +} + +if (jobRef.getScm() == null) { + throw new Exception("No SCM found") +} + +if (jobRef.getScm().getBranches().find { val -> val.getName() == "{{ .RepositoryBranch }}" } == null) { + throw new Exception("Specified SCM branch not found") +} + +if(jobRef.getScm().getRepositories().find { it.getURIs().find { uri -> uri.toString().equals("https://github.com/jenkinsci/kubernetes-operator.git") } } == null) { + throw new Exception("Specified SCM repositories are invalid") +} + +{{ if .PollSCM }} +if (jobRef.getTriggers().find { key, val -> val.getClass().getSimpleName() == "SCMTrigger" && val.getSpec() == "{{ .PollSCM }}" } == null) { + throw new Exception("SCMTrigger not found but set") +} +{{ end }} + +{{ if .GitHubPushTrigger }} +if (jobRef.getTriggers().find { key, val -> val.getClass().getSimpleName() == "GitHubPushTrigger" } == null) { + throw new Exception("GitHubPushTrigger not found but set") +} +{{ end }} + +{{ if .BuildPeriodically }} +if (jobRef.getTriggers().find { key, val -> val.getClass().getSimpleName() == "TimerTrigger" && val.getSpec() == "{{ .BuildPeriodically }}" } == null) { + throw new Exception("BuildPeriodically not found but set") +} +{{ end }} + +for (BuildStep step : jobRef.getBuildersList()) { + if (!step.getTargets().equals("{{ .Targets }}")) { + throw new Exception("Targets are not equals'") + } + + if (!step.getAdditionalClasspath().equals(null)) { + throw new Exception("AdditionalClasspath is not equal") + } + + if (!step.isFailOnMissingPlugin().equals({{ .FailOnMissingPlugin }})) { + throw new Exception("FailOnMissingPlugin is not equal") + } + + if (!step.isUnstableOnDeprecation().equals({{ .UnstableOnDeprecation }})) { + throw new Exception("UnstableOnDeprecation is not equal") + } + + if (!step.isIgnoreMissingFiles().equals({{ .IgnoreMissingFiles }})) { + throw new Exception("IgnoreMissingFiles is not equal") + } +} +`))