diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 4b9f95ce..f71b32d3 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -377,6 +377,12 @@ type JenkinsMaster struct { // +optional LatestPlugins *bool `json:"latestPlugins,omitempty"` + // Allow to skip installation of both BasePlugins and Plugins. + // Requires using a custom image which includes the BasePlugins. + // Defaults to false. + // +optional + SkipPlugins *bool `json:"skipPlugins,omitempty"` + // DisableCSRFProtection allows you to toggle CSRF Protection on Jenkins DisableCSRFProtection bool `json:"disableCSRFProtection"` diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index c1cca2ab..095bcb76 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -361,6 +361,11 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { *out = new(bool) **out = **in } + if in.SkipPlugins != nil { + in, out := &in.SkipPlugins, &out.SkipPlugins + *out = new(bool) + **out = **in + } if in.HostAliases != nil { in, out := &in.HostAliases, &out.HostAliases *out = make([]corev1.HostAlias, len(*in)) diff --git a/chart/jenkins-operator/README.md b/chart/jenkins-operator/README.md index 58b2026b..528228a0 100644 --- a/chart/jenkins-operator/README.md +++ b/chart/jenkins-operator/README.md @@ -91,6 +91,7 @@ Kubernetes native operator which fully manages Jenkins on Kubernetes | jenkins.seedJobAgentImage | string | `""` | | | jenkins.seedJobs | list | `[]` | | | jenkins.serviceAccount.annotations | object | `{}` | | +| jenkins.skipPlugins | bool | `false` | | | jenkins.terminationGracePeriodSeconds | int | `30` | | | jenkins.tolerations | list | `[]` | | | jenkins.validateSecurityWarnings | bool | `false` | | diff --git a/chart/jenkins-operator/crds/jenkins-crd.yaml b/chart/jenkins-operator/crds/jenkins-crd.yaml index 2caa71d2..e30bc42a 100644 --- a/chart/jenkins-operator/crds/jenkins-crd.yaml +++ b/chart/jenkins-operator/crds/jenkins-crd.yaml @@ -1330,6 +1330,11 @@ spec: type: string type: object type: object + skipPlugins: + description: 'Allow to skip installation of both BasePlugins and + Plugins. Requires using a custom image which includes the + BasePlugins. Defaults to false.' + type: boolean terminationGracePeriodSeconds: description: 'Optional duration in seconds the pod needs to terminate gracefully.' type: integer diff --git a/chart/jenkins-operator/templates/jenkins.yaml b/chart/jenkins-operator/templates/jenkins.yaml index 0906891f..82372b71 100644 --- a/chart/jenkins-operator/templates/jenkins.yaml +++ b/chart/jenkins-operator/templates/jenkins.yaml @@ -115,6 +115,7 @@ spec: {{- with .Values.jenkins.hostAliases }} hostAliases: {{ toYaml . | nindent 4 }} {{- end }} + skipPlugins: {{ .Values.jenkins.skipPlugins }} {{- if .Values.jenkins.terminationGracePeriodSeconds }} terminationGracePeriodSeconds: {{ .Values.jenkins.terminationGracePeriodSeconds }} {{- end }} diff --git a/chart/jenkins-operator/values.yaml b/chart/jenkins-operator/values.yaml index e820b0af..47453c67 100644 --- a/chart/jenkins-operator/values.yaml +++ b/chart/jenkins-operator/values.yaml @@ -145,6 +145,11 @@ jenkins: # SeedJobAgentImage defines the image that will be used by the seed job agent. If not defined jenkins/inbound-agent:3248.v65ecb_254c298-6 will be used. seedJobAgentImage: "" + # skipPlugins allows to skip installation of both BasePlugins and Plugins. + # Requires using a custom image which includes the BasePlugins. + # Defaults to false. + skipPlugins: false + # Resource limit/request for Jenkins # See https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ for details resources: diff --git a/config/crd/bases/jenkins.io_jenkins.yaml b/config/crd/bases/jenkins.io_jenkins.yaml index 805036c3..db9a117c 100644 --- a/config/crd/bases/jenkins.io_jenkins.yaml +++ b/config/crd/bases/jenkins.io_jenkins.yaml @@ -1674,6 +1674,10 @@ spec: type: string type: object type: object + skipPlugins: + description: Allow to skip installation of both BasePlugins and Plugins. Requires using a custom + image which includes the BasePlugins. Defaults to false. + type: boolean terminationGracePeriodSeconds: description: |- The grace period is the duration in seconds after the processes running in the pod are sent diff --git a/pkg/configuration/base/plugin.go b/pkg/configuration/base/plugin.go index 41614ac9..8b9375eb 100644 --- a/pkg/configuration/base/plugin.go +++ b/pkg/configuration/base/plugin.go @@ -13,6 +13,9 @@ import ( ) func (r *JenkinsBaseConfigurationReconciler) verifyPlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) { + if r.Configuration.Jenkins.Spec.Master.SkipPlugins != nil && *r.Configuration.Jenkins.Spec.Master.SkipPlugins { + return true, nil + } allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins) if err != nil { return false, stackerr.WithStack(err) diff --git a/pkg/configuration/base/resources/scripts_configmap.go b/pkg/configuration/base/resources/scripts_configmap.go index b60aa50b..a04b58b5 100644 --- a/pkg/configuration/base/resources/scripts_configmap.go +++ b/pkg/configuration/base/resources/scripts_configmap.go @@ -2,6 +2,7 @@ package resources import ( "fmt" + "strings" "text/template" "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" @@ -14,6 +15,8 @@ import ( const installPluginsCommand = "jenkins-plugin-cli" +var requiredBasePlugins = []string{"configuration-as-code", "git", "job-dsl", "kubernetes", "kubernetes-credentials-provider", "workflow-aggregator"} + var initBashTemplate = template.Must(template.New(InitScriptName).Parse(`#!/usr/bin/env bash set -e set -x @@ -36,6 +39,7 @@ mkdir -p {{ .JenkinsHomePath }}/scripts cp {{ .JenkinsScriptsVolumePath }}/*.sh {{ .JenkinsHomePath }}/scripts chmod +x {{ .JenkinsHomePath }}/scripts/*.sh +{{if not .SkipPlugins }} {{- $jenkinsHomePath := .JenkinsHomePath }} {{- $installPluginsCommand := .InstallPluginsCommand }} @@ -58,6 +62,19 @@ EOF {{ $installPluginsCommand }} --verbose --latest {{ .LatestPlugins }} -f {{ .JenkinsHomePath }}/user-plugins.txt echo "Installing plugins required by user - end" +{{else}} +echo "Skipping installation if plugins" +echo "Checking if required base plugins are installed" +installedPlugins=$(jenkins-plugin-cli --list 2> /dev/null) +for plugin in {{ .RequiredBasePlugins }}; do + if ! echo "$installedPlugins" | grep -q "^$plugin "; then + echo "Required base plugin $plugin not installed, exiting" + exit 1 + else + echo "Found $plugin" + fi +done +{{end}} `)) func buildConfigMapTypeMeta() metav1.TypeMeta { @@ -73,22 +90,31 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { latestP = new(bool) *latestP = true } + skipPlugins := jenkins.Spec.Master.SkipPlugins + if skipPlugins == nil { + skipPlugins = new(bool) + *skipPlugins = false + } data := struct { JenkinsHomePath string InitConfigurationPath string InstallPluginsCommand string + RequiredBasePlugins string JenkinsScriptsVolumePath string BasePlugins []v1alpha2.Plugin UserPlugins []v1alpha2.Plugin LatestPlugins bool + SkipPlugins bool }{ JenkinsHomePath: getJenkinsHomePath(jenkins), InitConfigurationPath: jenkinsInitConfigurationVolumePath, BasePlugins: jenkins.Spec.Master.BasePlugins, UserPlugins: jenkins.Spec.Master.Plugins, InstallPluginsCommand: installPluginsCommand, + RequiredBasePlugins: strings.Join(requiredBasePlugins, " "), JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, LatestPlugins: *latestP, + SkipPlugins: *skipPlugins, } output, err := render.Render(initBashTemplate, data)