From 134c29835894a7c4bc04909c729736791865619b Mon Sep 17 00:00:00 2001 From: Jakub Al-Khalili Date: Fri, 30 Aug 2019 15:36:46 +0200 Subject: [PATCH 1/3] #69 Add groovy script template rendering, fix pollSCM, improve pollSCM tests --- go.mod | 1 + go.sum | 2 + .../resources => internal/render}/render.go | 6 +- pkg/apis/jenkins/v1alpha2/jenkins_types.go | 21 ++ .../resources/init_configuration_configmap.go | 3 +- .../base/resources/scripts_configmap.go | 3 +- .../configuration/user/seedjobs/seedjobs.go | 196 ++++++++++++------ .../user/seedjobs/seedjobs_test.go | 5 +- .../configuration/user/seedjobs/validate.go | 51 +++++ .../user/seedjobs/validate_test.go | 98 +++++++++ 10 files changed, 313 insertions(+), 73 deletions(-) rename {pkg/controller/jenkins/configuration/base/resources => internal/render}/render.go (62%) diff --git a/go.mod b/go.mod index d998e0c2..5d172bb8 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.1 + github.com/robfig/cron v1.2.0 github.com/sergi/go-diff v1.0.0 // indirect github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.3.0 diff --git a/go.sum b/go.sum index 59c1ac8d..d5bc4fdc 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/pkg/controller/jenkins/configuration/base/resources/render.go b/internal/render/render.go similarity index 62% rename from pkg/controller/jenkins/configuration/base/resources/render.go rename to internal/render/render.go index ea2cd665..b37379cb 100644 --- a/pkg/controller/jenkins/configuration/base/resources/render.go +++ b/internal/render/render.go @@ -1,4 +1,4 @@ -package resources +package render import ( "bytes" @@ -7,8 +7,8 @@ import ( "github.com/pkg/errors" ) -// render executes a parsed template (go-template) with configuration from data -func render(template *template.Template, data interface{}) (string, error) { +// Render executes a parsed template (go-template) with configuration from data +func Render(template *template.Template, data interface{}) (string, error) { var buffer bytes.Buffer if err := template.Execute(&buffer, data); err != nil { return "", errors.WithStack(err) diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/pkg/apis/jenkins/v1alpha2/jenkins_types.go index 4afb27b2..6d295fad 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/pkg/apis/jenkins/v1alpha2/jenkins_types.go @@ -528,6 +528,27 @@ type SeedJob struct { // JenkinsCredentialType is the https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ credential type // +optional JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` + + // GitHubPushTrigger is used for GitHub web hooks + GitHubPushTrigger bool `json:"githubPushTrigger,omitempty"` + + // BuildPeriodically is setting build trigger + BuildPeriodically string `json:"buildPeriodically,omitempty"` + + // PollSCM is setting build trigger + PollSCM string `json:"pollSCM,omitempty"` + + // IgnoreMissingFiles is setting for Job DSL API plugin + IgnoreMissingFiles bool `json:"ignoreMissingFiles,omitempty"` + + // AdditionalClasspath is setting for Job DSL API plugin + AdditionalClasspath string `json:"additionalClasspath,omitempty"` + + // FailOnMissingPlugin is setting for Job DSL API plugin + FailOnMissingPlugin bool `json:"failOnMissingPlugin,omitempty"` + + // UnstableOnDeprecation is setting for Job DSL API plugin + UnstableOnDeprecation bool `json:"unstableOnDeprecation,omitempty"` } // Handler defines a specific action that should be taken diff --git a/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go b/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go index 05cd3d2e..81ccf4ea 100644 --- a/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go +++ b/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go @@ -2,6 +2,7 @@ package resources import ( "fmt" + "github.com/jenkinsci/kubernetes-operator/internal/render" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" "text/template" @@ -48,7 +49,7 @@ func buildCreateJenkinsOperatorUserGroovyScript() (*string, error) { OperatorUserCreatedFilePath: jenkinsHomePath + "/operatorUserCreated", } - output, err := render(createOperatorUserGroovyFmtTemplate, data) + output, err := render.Render(createOperatorUserGroovyFmtTemplate, data) if err != nil { return nil, err } diff --git a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go index 5003ac23..b71d2353 100644 --- a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go +++ b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go @@ -2,6 +2,7 @@ package resources import ( "fmt" + "github.com/jenkinsci/kubernetes-operator/internal/render" "text/template" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" @@ -287,7 +288,7 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, } - output, err := render(initBashTemplate, data) + output, err := render.Render(initBashTemplate, data) if err != nil { return nil, err } diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go index 3c79dbfa..1d45ffe3 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go @@ -6,7 +6,9 @@ import ( "encoding/base64" "fmt" "reflect" + "text/template" + "github.com/jenkinsci/kubernetes-operator/internal/render" "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/base/resources" @@ -39,8 +41,94 @@ const ( // AgentName is the name of seed job agent AgentName = "jnlp" + + creatingGroovyScriptName = "seed-job-groovy-script.groovy" ) +var seedJobGroovyScript = template.Must(template.New(creatingGroovyScriptName).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 static com.google.common.collect.Lists.newArrayList; + +Jenkins jenkins = Jenkins.instance + +def jobDslSeedName = " {{ .ID }} - {{ .SeedJobSuffix }}"; +def jobRef = jenkins.getItem(jobDslSeedName) + +def repoList = GitSCM.createRepoList(" {{ .RepositoryURL }} ", " {{ .CredentialID }} ") +def gitExtensions = [new CloneOption(true, true, ";", 10)] +def scm = new GitSCM( + repoList, + newArrayList(new BranchSpec(" {{ .RepositoryBranch }} ")), + false, + Collections.emptyList(), + null, + null, + gitExtensions +) + +def executeDslScripts = new ExecuteDslScripts() +executeDslScripts.setTargets(" {{ .Targets }} ") +executeDslScripts.setSandbox(false) +executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) +executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) +executeDslScripts.setLookupStrategy(LookupStrategy.SEED_JOB) +executeDslScripts.setAdditionalClasspath(" {{ .AdditionalClasspath }} ") +executeDslScripts.setFailOnMissingPlugin( {{ .FailOnMissingPlugin }} ) +executeDslScripts.setUnstableOnDeprecation( {{ .UnstableOnDeprecation }} ) +executeDslScripts.setIgnoreMissingFiles( {{ .IgnoreMissingFiles }} ) + +if (jobRef == null) { + jobRef = jenkins.createProject(FreeStyleProject, jobDslSeedName) +} + +jobRef.getBuildersList().clear() +jobRef.getBuildersList().add(executeDslScripts) +jobRef.setDisplayName("Seed Job from {{ .ID }}") +jobRef.setScm(scm) + +{{ $length := len .PollSCM }} {{ if gt $length 0 }} +jobRef.addTrigger(new SCMTrigger(" {{ .PollSCM }} ")) +{{ end }} + +{{ if .GitHubPushTrigger }} +jobRef.addTrigger(new GitHubPushTrigger()) +{{ end }} + +{{ $length := len .BuildPeriodically }} {{ if gt $length 0 }} +jobRef.addTrigger(new TimerTrigger(" {{ .BuildPeriodically }} ")) +{{ end}} +jobRef.setAssignedLabel(new LabelAtom("{{ .AgentName }}")) +jenkins.getQueue().schedule(jobRef) +`)) + // SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys type SeedJobs struct { jenkinsClient jenkinsclient.Jenkins @@ -112,7 +200,10 @@ func (s *SeedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err erro return true, err } - groovyScript := seedJobCreatingGroovyScript(seedJob) + groovyScript, err := seedJobCreatingGroovyScript(seedJob) + if err != nil { + return true, err + } hash := sha256.New() hash.Write([]byte(groovyScript)) @@ -316,72 +407,43 @@ func agentDeployment(jenkinsManifest *v1alpha2.Jenkins, namespace string, agentN } } -func seedJobCreatingGroovyScript(s v1alpha2.SeedJob) string { - return ` -import hudson.model.FreeStyleProject; -import hudson.plugins.git.GitSCM; -import hudson.plugins.git.BranchSpec; -import hudson.triggers.SCMTrigger; -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; +func seedJobCreatingGroovyScript(seedJob v1alpha2.SeedJob) (string, error) { + 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: AgentName, + } -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 + output, err := render.Render(seedJobGroovyScript, data) + if err != nil { + return "", err + } -import static com.google.common.collect.Lists.newArrayList - -Jenkins jenkins = Jenkins.instance - -def jobDslSeedName = "` + s.ID + `-` + constants.SeedJobSuffix + `"; -def jobRef = jenkins.getItem(jobDslSeedName) - -def repoList = GitSCM.createRepoList("` + s.RepositoryURL + `", "` + s.CredentialID + `") -def gitExtensions = [new CloneOption(true, true, ";", 10)] -def scm = new GitSCM( - repoList, - newArrayList(new BranchSpec("` + s.RepositoryBranch + `")), - false, - Collections.emptyList(), - null, - null, - gitExtensions -) - -def executeDslScripts = new ExecuteDslScripts() -executeDslScripts.setTargets("` + s.Targets + `") -executeDslScripts.setSandbox(false) -executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) -executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) -executeDslScripts.setLookupStrategy(LookupStrategy.SEED_JOB) - -if (jobRef == null) { - jobRef = jenkins.createProject(FreeStyleProject, jobDslSeedName) -} - -jobRef.getBuildersList().clear() -jobRef.getBuildersList().add(executeDslScripts) -jobRef.setDisplayName("` + fmt.Sprintf("Seed Job from %s", s.ID) + `") -jobRef.setScm(scm) - -jobRef.setAssignedLabel(new LabelAtom("` + AgentName + `")) - -jenkins.getQueue().schedule(jobRef) - -` + return output, nil } diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go index f7f06f78..0a1f5ee3 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs_test.go @@ -87,10 +87,13 @@ func TestEnsureSeedJobs(t *testing.T) { }, } + 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().ExecuteScript(seedJobCreatingGroovyScript(jenkins.Spec.SeedJobs[0])).AnyTimes() + jenkinsClient.EXPECT().ExecuteScript(seedJobCreatingScript).AnyTimes() seedJobClient := New(jenkinsClient, fakeClient, logger) diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/validate.go b/pkg/controller/jenkins/configuration/user/seedjobs/validate.go index fc42cbdd..a87971d0 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/validate.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/validate.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "github.com/robfig/cron" "strings" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" @@ -87,10 +88,60 @@ func (r *SeedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) (bool, error) { } } } + + if len(seedJob.BuildPeriodically) > 0 { + if ok := r.validateSchedule(seedJob, seedJob.BuildPeriodically, "buildPeriodically"); !ok { + valid = false + } + } + + if len(seedJob.PollSCM) > 0 { + if ok := r.validateSchedule(seedJob, seedJob.PollSCM, "pollSCM"); !ok { + valid = false + } + } + + if seedJob.GitHubPushTrigger { + if !r.validateGitHubPushTrigger(jenkins) { + valid = false + } + } } + return valid, nil } +func (r *SeedJobs) validateSchedule(job v1alpha2.SeedJob, str string, key string) bool { + _, err := cron.Parse(str) + if err != nil { + r.logger.V(log.VWarn).Info(fmt.Sprintf("`%s` schedule '%s' is invalid cron spec in `%s`", key, str, job.ID)) + return false + } + return true +} + +func (r *SeedJobs) validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) bool { + exists := false + for _, plugin := range jenkins.Spec.Master.BasePlugins { + if plugin.Name == "github" { + exists = true + } + } + + userExists := false + for _, plugin := range jenkins.Spec.Master.Plugins { + if plugin.Name == "github" { + userExists = true + } + } + + if !exists && !userExists { + r.logger.V(log.VWarn).Info("githubPushTrigger is set. This function requires `github` plugin installed in jenkins.Spec.Master.Plugins") + return false + } + return true +} + func (r *SeedJobs) validateIfIDIsUnique(seedJobs []v1alpha2.SeedJob) bool { ids := map[string]bool{} for _, seedJob := range seedJobs { diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go b/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go index 0eb01f05..da57f260 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go @@ -442,6 +442,104 @@ func TestValidateSeedJobs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, false, result) }) + t.Run("Invalid with wrong cron spec", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "jenkins-operator-e2e", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + BuildPeriodically: "invalid-cron-spec", + }, + }, + }, + } + + seedJobs := New(nil, fake.NewFakeClient(), logf.ZapLogger(false)) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + t.Run("Valid with good cron spec", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "jenkins-operator-e2e", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + BuildPeriodically: "1 2 3 4 5", + PollSCM: "1 2 3 4 5", + }, + }, + }, + } + + seedJobs := New(nil, fake.NewFakeClient(), logf.ZapLogger(false)) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + assert.Equal(t, true, result) + }) + t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "jenkins-operator-e2e", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + GitHubPushTrigger: true, + }, + }, + }, + } + + seedJobs := New(nil, fake.NewFakeClient(), logf.ZapLogger(false)) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + assert.Equal(t, false, result) + }) + t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "jenkins-operator-e2e", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + GitHubPushTrigger: true, + }, + }, + Master: v1alpha2.JenkinsMaster{ + Plugins: []v1alpha2.Plugin{ + {Name: "github", Version: "latest"}, + }, + }, + }, + } + + seedJobs := New(nil, fake.NewFakeClient(), logf.ZapLogger(false)) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + assert.Equal(t, true, result) + }) } func TestValidateIfIDIsUnique(t *testing.T) { From b0f1ee88cdd09d0366adf1656e4eca0c16d9239f Mon Sep 17 00:00:00 2001 From: Jakub Al-Khalili Date: Mon, 2 Sep 2019 14:10:59 +0200 Subject: [PATCH 2/3] #69 Improve schedule tests --- pkg/apis/jenkins/v1alpha2/jenkins_types.go | 7 +++++ .../resources/init_configuration_configmap.go | 4 +-- .../base/resources/scripts_configmap.go | 2 +- .../configuration/user/seedjobs/seedjobs.go | 28 +++++++++---------- .../configuration/user/seedjobs/validate.go | 8 +++--- .../user/seedjobs/validate_test.go | 8 +++--- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/pkg/apis/jenkins/v1alpha2/jenkins_types.go index 6d295fad..d20d834d 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/pkg/apis/jenkins/v1alpha2/jenkins_types.go @@ -530,24 +530,31 @@ type SeedJob struct { JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` // GitHubPushTrigger is used for GitHub web hooks + // +optional GitHubPushTrigger bool `json:"githubPushTrigger,omitempty"` // BuildPeriodically is setting build trigger + // +optional BuildPeriodically string `json:"buildPeriodically,omitempty"` // PollSCM is setting build trigger + // +optional PollSCM string `json:"pollSCM,omitempty"` // IgnoreMissingFiles is setting for Job DSL API plugin + // +optional IgnoreMissingFiles bool `json:"ignoreMissingFiles,omitempty"` // AdditionalClasspath is setting for Job DSL API plugin + // +optional AdditionalClasspath string `json:"additionalClasspath,omitempty"` // FailOnMissingPlugin is setting for Job DSL API plugin + // +optional FailOnMissingPlugin bool `json:"failOnMissingPlugin,omitempty"` // UnstableOnDeprecation is setting for Job DSL API plugin + // +optional UnstableOnDeprecation bool `json:"unstableOnDeprecation,omitempty"` } diff --git a/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go b/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go index 81ccf4ea..859ace6f 100644 --- a/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go +++ b/pkg/controller/jenkins/configuration/base/resources/init_configuration_configmap.go @@ -2,11 +2,11 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/internal/render" - "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" "text/template" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/internal/render" + "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go index b71d2353..dec95390 100644 --- a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go +++ b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go @@ -2,11 +2,11 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/internal/render" "text/template" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" + "github.com/jenkinsci/kubernetes-operator/internal/render" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go index 1d45ffe3..cacda77b 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/seedjobs.go @@ -45,7 +45,7 @@ const ( creatingGroovyScriptName = "seed-job-groovy-script.groovy" ) -var seedJobGroovyScript = template.Must(template.New(creatingGroovyScriptName).Parse(` +var seedJobGroovyScriptTemplate = template.Must(template.New(creatingGroovyScriptName).Parse(` import hudson.model.FreeStyleProject; import hudson.plugins.git.GitSCM; import hudson.plugins.git.BranchSpec; @@ -79,14 +79,14 @@ 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 }} ") +def repoList = GitSCM.createRepoList("{{ .RepositoryURL }}", "{{ .CredentialID }}") def gitExtensions = [new CloneOption(true, true, ";", 10)] def scm = new GitSCM( repoList, - newArrayList(new BranchSpec(" {{ .RepositoryBranch }} ")), + newArrayList(new BranchSpec("{{ .RepositoryBranch }}")), false, Collections.emptyList(), null, @@ -95,15 +95,15 @@ def scm = new GitSCM( ) def executeDslScripts = new ExecuteDslScripts() -executeDslScripts.setTargets(" {{ .Targets }} ") +executeDslScripts.setTargets("{{ .Targets }}") executeDslScripts.setSandbox(false) executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) executeDslScripts.setLookupStrategy(LookupStrategy.SEED_JOB) -executeDslScripts.setAdditionalClasspath(" {{ .AdditionalClasspath }} ") -executeDslScripts.setFailOnMissingPlugin( {{ .FailOnMissingPlugin }} ) -executeDslScripts.setUnstableOnDeprecation( {{ .UnstableOnDeprecation }} ) -executeDslScripts.setIgnoreMissingFiles( {{ .IgnoreMissingFiles }} ) +executeDslScripts.setAdditionalClasspath("{{ .AdditionalClasspath }}") +executeDslScripts.setFailOnMissingPlugin({{ .FailOnMissingPlugin }}) +executeDslScripts.setUnstableOnDeprecation({{ .UnstableOnDeprecation }}) +executeDslScripts.setIgnoreMissingFiles({{ .IgnoreMissingFiles }}) if (jobRef == null) { jobRef = jenkins.createProject(FreeStyleProject, jobDslSeedName) @@ -114,16 +114,16 @@ jobRef.getBuildersList().add(executeDslScripts) jobRef.setDisplayName("Seed Job from {{ .ID }}") jobRef.setScm(scm) -{{ $length := len .PollSCM }} {{ if gt $length 0 }} -jobRef.addTrigger(new SCMTrigger(" {{ .PollSCM }} ")) +{{ if .PollSCM }} +jobRef.addTrigger(new SCMTrigger("{{ .PollSCM }}")) {{ end }} {{ if .GitHubPushTrigger }} jobRef.addTrigger(new GitHubPushTrigger()) {{ end }} -{{ $length := len .BuildPeriodically }} {{ if gt $length 0 }} -jobRef.addTrigger(new TimerTrigger(" {{ .BuildPeriodically }} ")) +{{ if .BuildPeriodically }} +jobRef.addTrigger(new TimerTrigger("{{ .BuildPeriodically }}")) {{ end}} jobRef.setAssignedLabel(new LabelAtom("{{ .AgentName }}")) jenkins.getQueue().schedule(jobRef) @@ -440,7 +440,7 @@ func seedJobCreatingGroovyScript(seedJob v1alpha2.SeedJob) (string, error) { AgentName: AgentName, } - output, err := render.Render(seedJobGroovyScript, data) + output, err := render.Render(seedJobGroovyScriptTemplate, data) if err != nil { return "", err } diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/validate.go b/pkg/controller/jenkins/configuration/user/seedjobs/validate.go index a87971d0..39f3e85b 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/validate.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/validate.go @@ -5,7 +5,6 @@ import ( "crypto/x509" "encoding/pem" "fmt" - "github.com/robfig/cron" "strings" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" @@ -13,6 +12,7 @@ import ( "github.com/go-logr/logr" stackerr "github.com/pkg/errors" + "github.com/robfig/cron" "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -90,13 +90,13 @@ func (r *SeedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) (bool, error) { } if len(seedJob.BuildPeriodically) > 0 { - if ok := r.validateSchedule(seedJob, seedJob.BuildPeriodically, "buildPeriodically"); !ok { + if !r.validateSchedule(seedJob, seedJob.BuildPeriodically, "buildPeriodically") { valid = false } } if len(seedJob.PollSCM) > 0 { - if ok := r.validateSchedule(seedJob, seedJob.PollSCM, "pollSCM"); !ok { + if !r.validateSchedule(seedJob, seedJob.PollSCM, "pollSCM") { valid = false } } @@ -136,7 +136,7 @@ func (r *SeedJobs) validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) bool { } if !exists && !userExists { - r.logger.V(log.VWarn).Info("githubPushTrigger is set. This function requires `github` plugin installed in jenkins.Spec.Master.Plugins") + r.logger.V(log.VWarn).Info("githubPushTrigger is set. This function requires `github` plugin installed in .Spec.Master.Plugins because seed jobs Push Trigger function needs it") return false } return true diff --git a/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go b/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go index da57f260..864cafe9 100644 --- a/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go +++ b/pkg/controller/jenkins/configuration/user/seedjobs/validate_test.go @@ -463,7 +463,7 @@ func TestValidateSeedJobs(t *testing.T) { result, err := seedJobs.ValidateSeedJobs(jenkins) assert.NoError(t, err) - assert.Equal(t, false, result) + assert.False(t, result) }) t.Run("Valid with good cron spec", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ @@ -487,7 +487,7 @@ func TestValidateSeedJobs(t *testing.T) { result, err := seedJobs.ValidateSeedJobs(jenkins) assert.NoError(t, err) - assert.Equal(t, true, result) + assert.True(t, result) }) t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ @@ -510,7 +510,7 @@ func TestValidateSeedJobs(t *testing.T) { result, err := seedJobs.ValidateSeedJobs(jenkins) assert.NoError(t, err) - assert.Equal(t, false, result) + assert.False(t, result) }) t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ @@ -538,7 +538,7 @@ func TestValidateSeedJobs(t *testing.T) { result, err := seedJobs.ValidateSeedJobs(jenkins) assert.NoError(t, err) - assert.Equal(t, true, result) + assert.True(t, result) }) } From 833a95f2e811f7f2a150cd54c843362223771a49 Mon Sep 17 00:00:00 2001 From: Jakub Al-Khalili Date: Mon, 2 Sep 2019 14:56:28 +0200 Subject: [PATCH 3/3] #69 Add better documentation for Job DSL plugin and Git trigger settings --- pkg/apis/jenkins/v1alpha2/jenkins_types.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/pkg/apis/jenkins/v1alpha2/jenkins_types.go index d20d834d..ea7a6c8c 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/pkg/apis/jenkins/v1alpha2/jenkins_types.go @@ -533,27 +533,27 @@ type SeedJob struct { // +optional GitHubPushTrigger bool `json:"githubPushTrigger,omitempty"` - // BuildPeriodically is setting build trigger + // BuildPeriodically is setting for scheduled trigger // +optional BuildPeriodically string `json:"buildPeriodically,omitempty"` - // PollSCM is setting build trigger + // PollSCM is setting for polling changes in SCM // +optional PollSCM string `json:"pollSCM,omitempty"` - // IgnoreMissingFiles is setting for Job DSL API plugin + // IgnoreMissingFiles is setting for Job DSL API plugin to ignore files that miss // +optional IgnoreMissingFiles bool `json:"ignoreMissingFiles,omitempty"` - // AdditionalClasspath is setting for Job DSL API plugin + // AdditionalClasspath is setting for Job DSL API plugin to set Additional Classpath // +optional AdditionalClasspath string `json:"additionalClasspath,omitempty"` - // FailOnMissingPlugin is setting for Job DSL API plugin + // FailOnMissingPlugin is setting for Job DSL API plugin that fails job if required plugin is missing // +optional FailOnMissingPlugin bool `json:"failOnMissingPlugin,omitempty"` - // UnstableOnDeprecation is setting for Job DSL API plugin + // 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"` }