diff --git a/Makefile b/Makefile index 79037f82..d2e66626 100644 --- a/Makefile +++ b/Makefile @@ -211,7 +211,7 @@ run: build ## Run the executable, you can use EXTRA_ARGS kubectl apply -f deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml kubectl config use-context $(KUBECTL_CONTEXT) @echo "Watching '$(WATCH_NAMESPACE)' namespace" - build/_output/bin/jenkins-operator $(EXTRA_ARGS) + build/_output/bin/jenkins-operator --local $(EXTRA_ARGS) .PHONY: clean clean: ## Cleanup any build binaries or packages diff --git a/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml b/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml index 8cfab62f..51277624 100644 --- a/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml +++ b/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml @@ -4,14 +4,9 @@ metadata: name: example spec: master: - securityContext: - runAsUser: 1001 containers: - name: jenkins-master image: jenkins/jenkins:lts - command: - - bash - - "/var/jenkins/scripts/init.sh" imagePullPolicy: Always livenessProbe: failureThreshold: 12 diff --git a/openshit-jenkins.yaml b/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml similarity index 100% rename from openshit-jenkins.yaml rename to deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/pkg/apis/jenkins/v1alpha2/jenkins_types.go index 3ef1c1b7..16463fe5 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/pkg/apis/jenkins/v1alpha2/jenkins_types.go @@ -156,10 +156,12 @@ type JenkinsMaster struct { NodeSelector map[string]string `json:"nodeSelector,omitempty"` // SecurityContext that applies to all the containers of the Jenkins - // Master. As per kubernetes specification, it can be overidden + // Master. As per kubernetes specification, it can be overridden // for each container individually. // +optional - // Defaults to: nil + // Defaults to: + // runAsUser: 1000 + // fsGroup: 1000 SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` // List of containers belonging to the pod. diff --git a/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go index 3b88f0bc..845a0e40 100644 --- a/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go @@ -53,6 +53,7 @@ func (in *Build) DeepCopy() *Build { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Container) DeepCopyInto(out *Container) { *out = *in + in.Resources.DeepCopyInto(&out.Resources) if in.Command != nil { in, out := &in.Command, &out.Command *out = make([]string, len(*in)) @@ -82,7 +83,6 @@ func (in *Container) DeepCopyInto(out *Container) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.Resources.DeepCopyInto(&out.Resources) if in.VolumeMounts != nil { in, out := &in.VolumeMounts, &out.VolumeMounts *out = make([]v1.VolumeMount, len(*in)) @@ -222,6 +222,11 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { (*out)[key] = val } } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } if in.Containers != nil { in, out := &in.Containers, &out.Containers *out = make([]Container, len(*in)) diff --git a/pkg/controller/jenkins/configuration/base/reconcile.go b/pkg/controller/jenkins/configuration/base/reconcile.go index 3811978c..3eac48b5 100644 --- a/pkg/controller/jenkins/configuration/base/reconcile.go +++ b/pkg/controller/jenkins/configuration/base/reconcile.go @@ -388,6 +388,11 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) if err != nil && errors.IsNotFound(err) { jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins) + if !reflect.DeepEqual(jenkinsMasterPod.Spec.Containers[0].Command, resources.GetJenkinsMasterContainerBaseCommand()) { + r.logger.Info(fmt.Sprintf("spec.master.containers[%s].command has been overridden make sure the command looks like: '%v', otherwise the operator won't configure default user and install plugins", + resources.JenkinsMasterContainerName, []string{"bash", "-c", fmt.Sprintf("%s/%s && && /sbin/tini -s -- /usr/local/bin/jenkins.sh", + resources.JenkinsScriptsVolumePath, resources.InitScriptName)})) + } r.logger.Info(fmt.Sprintf("Creating a new Jenkins Master Pod %s/%s", jenkinsMasterPod.Namespace, jenkinsMasterPod.Name)) err = r.createResource(jenkinsMasterPod) if err != nil { @@ -475,6 +480,12 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa return true } + if !reflect.DeepEqual(r.jenkins.Spec.Master.SecurityContext, currentJenkinsMasterPod.Spec.SecurityContext) { + r.logger.Info(fmt.Sprintf("Jenkins pod security context has changed, actual '%+v' required '%+v', recreating pod", + currentJenkinsMasterPod.Spec.SecurityContext, r.jenkins.Spec.Master.SecurityContext)) + return true + } + if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) { r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v', recreating pod", currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector)) @@ -572,10 +583,10 @@ func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Co r.logger.Info(fmt.Sprintf("Resources have changed to '%+v' in container '%s', recreating pod", expected.Resources, expected.Name)) return true } -/* if !reflect.DeepEqual(expected.SecurityContext, actual.SecurityContext) { + if !reflect.DeepEqual(expected.SecurityContext, actual.SecurityContext) { r.logger.Info(fmt.Sprintf("Security context has changed to '%+v' in container '%s', recreating pod", expected.SecurityContext, expected.Name)) return true - }*/ + } if !reflect.DeepEqual(expected.WorkingDir, actual.WorkingDir) { r.logger.Info(fmt.Sprintf("Working directory has changed to '%+v' in container '%s', recreating pod", expected.WorkingDir, expected.Name)) return true diff --git a/pkg/controller/jenkins/configuration/base/resources/pod.go b/pkg/controller/jenkins/configuration/base/resources/pod.go index 403b7509..6c9b35d3 100644 --- a/pkg/controller/jenkins/configuration/base/resources/pod.go +++ b/pkg/controller/jenkins/configuration/base/resources/pod.go @@ -19,8 +19,10 @@ const ( jenkinsHomePath = jenkinsPath + "/home" jenkinsScriptsVolumeName = "scripts" - jenkinsScriptsVolumePath = jenkinsPath + "/scripts" - initScriptName = "init.sh" + // JenkinsScriptsVolumePath is a path where are scripts used to configure Jenkins + JenkinsScriptsVolumePath = jenkinsPath + "/scripts" + // InitScriptName is the init script name which configures init.groovy.d, scripts and install plugins + InitScriptName = "init.sh" jenkinsOperatorCredentialsVolumeName = "operator-credentials" jenkinsOperatorCredentialsVolumePath = jenkinsPath + "/operator-credentials" @@ -55,8 +57,18 @@ func buildPodTypeMeta() metav1.TypeMeta { } } -// GetJenkinsMasterPodBaseEnvs returns Jenkins master pod envs required by operator -func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { +// GetJenkinsMasterContainerBaseCommand returns default Jenkins master container command +func GetJenkinsMasterContainerBaseCommand() []string { + return []string{ + "bash", + "-c", + fmt.Sprintf("%s/%s && /sbin/tini -s -- /usr/local/bin/jenkins.sh", + JenkinsScriptsVolumePath, InitScriptName), + } +} + +// GetJenkinsMasterContainerBaseEnvs returns Jenkins master pod envs required by operator +func GetJenkinsMasterContainerBaseEnvs() []corev1.EnvVar { return []corev1.EnvVar{ { Name: "JENKINS_HOME", @@ -77,6 +89,7 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode + var scriptsVolumeDefaultMode int32 = 0777 return []corev1.Volume{ { Name: JenkinsHomeVolumeName, @@ -88,7 +101,7 @@ func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { Name: jenkinsScriptsVolumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ - DefaultMode: &configMapVolumeSourceDefaultMode, + DefaultMode: &scriptsVolumeDefaultMode, LocalObjectReference: corev1.LocalObjectReference{ Name: getScriptsConfigMapName(jenkins), }, @@ -159,7 +172,7 @@ func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount { }, { Name: jenkinsScriptsVolumeName, - MountPath: jenkinsScriptsVolumePath, + MountPath: JenkinsScriptsVolumePath, ReadOnly: true, }, { @@ -193,7 +206,7 @@ func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount { // NewJenkinsMasterContainer returns Jenkins master Kubernetes container func NewJenkinsMasterContainer(jenkins *v1alpha2.Jenkins) corev1.Container { jenkinsContainer := jenkins.Spec.Master.Containers[0] - envs := GetJenkinsMasterPodBaseEnvs() + envs := GetJenkinsMasterContainerBaseEnvs() envs = append(envs, jenkinsContainer.Env...) return corev1.Container{ diff --git a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go index c9949b4a..5003ac23 100644 --- a/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go +++ b/pkg/controller/jenkins/configuration/base/resources/scripts_configmap.go @@ -239,7 +239,7 @@ main() { main "$@" ` -var initBashTemplate = template.Must(template.New(initScriptName).Parse(`#!/usr/bin/env bash +var initBashTemplate = template.Must(template.New(InitScriptName).Parse(`#!/usr/bin/env bash set -e set -x @@ -261,8 +261,6 @@ echo "Installing plugins required by Operator - end" echo "Installing plugins required by user - begin" {{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} echo "Installing plugins required by user - end" - -/sbin/tini -s -- /usr/local/bin/jenkins.sh `)) func buildConfigMapTypeMeta() metav1.TypeMeta { @@ -286,7 +284,7 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { BasePlugins: jenkins.Spec.Master.BasePlugins, UserPlugins: jenkins.Spec.Master.Plugins, InstallPluginsCommand: installPluginsCommand, - JenkinsScriptsVolumePath: jenkinsScriptsVolumePath, + JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, } output, err := render(initBashTemplate, data) @@ -314,7 +312,7 @@ func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins) (*co TypeMeta: buildConfigMapTypeMeta(), ObjectMeta: meta, Data: map[string]string{ - initScriptName: *initBashScript, + InitScriptName: *initBashScript, installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, jenkinsHomePath), }, }, nil diff --git a/pkg/controller/jenkins/configuration/base/validate.go b/pkg/controller/jenkins/configuration/base/validate.go index db75e01d..906b2f3a 100644 --- a/pkg/controller/jenkins/configuration/base/validate.go +++ b/pkg/controller/jenkins/configuration/base/validate.go @@ -194,7 +194,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(contai } func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { - baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() + baseEnvs := resources.GetJenkinsMasterContainerBaseEnvs() baseEnvNames := map[string]string{} for _, env := range baseEnvs { baseEnvNames[env.Name] = env.Value diff --git a/pkg/controller/jenkins/jenkins_controller.go b/pkg/controller/jenkins/jenkins_controller.go index 9cefa4e2..d19203cf 100644 --- a/pkg/controller/jenkins/jenkins_controller.go +++ b/pkg/controller/jenkins/jenkins_controller.go @@ -325,6 +325,11 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo FailureThreshold: int32(12), } } + if len(jenkinsContainer.Command) == 0 { + logger.Info("Setting default Jenkins container command") + changed = true + jenkinsContainer.Command = resources.GetJenkinsMasterContainerBaseCommand() + } if len(jenkins.Spec.Master.BasePlugins) == 0 { logger.Info("Setting default operator plugins") changed = true @@ -404,6 +409,17 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo jenkins.Spec.Master.Containers = containers } + if jenkins.Spec.Master.SecurityContext == nil { + logger.Info("Setting default Jenkins master security context") + changed = true + var id int64 = 1000 + securityContext := corev1.PodSecurityContext{ + RunAsUser: &id, + FSGroup: &id, + } + jenkins.Spec.Master.SecurityContext = &securityContext + } + if changed { return errors.WithStack(r.client.Update(context.TODO(), jenkins)) }