Allow configure SecurityContext and Jenkins master container command
This commit is contained in:
		
							parent
							
								
									3573b9acd6
								
							
						
					
					
						commit
						3b26e1c5ba
					
				
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								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 apply -f deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml | ||||||
| 	kubectl config use-context $(KUBECTL_CONTEXT) | 	kubectl config use-context $(KUBECTL_CONTEXT) | ||||||
| 	@echo "Watching '$(WATCH_NAMESPACE)' namespace" | 	@echo "Watching '$(WATCH_NAMESPACE)' namespace" | ||||||
| 	build/_output/bin/jenkins-operator $(EXTRA_ARGS) | 	build/_output/bin/jenkins-operator --local $(EXTRA_ARGS) | ||||||
| 
 | 
 | ||||||
| .PHONY: clean | .PHONY: clean | ||||||
| clean: ## Cleanup any build binaries or packages
 | clean: ## Cleanup any build binaries or packages
 | ||||||
|  |  | ||||||
|  | @ -4,14 +4,9 @@ metadata: | ||||||
|   name: example |   name: example | ||||||
| spec: | spec: | ||||||
|   master: |   master: | ||||||
|     securityContext: |  | ||||||
|       runAsUser: 1001 |  | ||||||
|     containers: |     containers: | ||||||
|     - name: jenkins-master |     - name: jenkins-master | ||||||
|       image: jenkins/jenkins:lts |       image: jenkins/jenkins:lts | ||||||
|       command: |  | ||||||
|       - bash |  | ||||||
|       - "/var/jenkins/scripts/init.sh" |  | ||||||
|       imagePullPolicy: Always |       imagePullPolicy: Always | ||||||
|       livenessProbe: |       livenessProbe: | ||||||
|         failureThreshold: 12 |         failureThreshold: 12 | ||||||
|  |  | ||||||
|  | @ -156,10 +156,12 @@ type JenkinsMaster struct { | ||||||
| 	NodeSelector map[string]string `json:"nodeSelector,omitempty"` | 	NodeSelector map[string]string `json:"nodeSelector,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// SecurityContext that applies to all the containers of the Jenkins
 | 	// 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.
 | 	// for each container individually.
 | ||||||
| 	// +optional
 | 	// +optional
 | ||||||
| 	// Defaults to: nil
 | 	// Defaults to:
 | ||||||
|  | 	// runAsUser: 1000
 | ||||||
|  | 	// fsGroup: 1000
 | ||||||
| 	SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` | 	SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// List of containers belonging to the pod.
 | 	// List of containers belonging to the pod.
 | ||||||
|  |  | ||||||
|  | @ -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.
 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
| func (in *Container) DeepCopyInto(out *Container) { | func (in *Container) DeepCopyInto(out *Container) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | 	in.Resources.DeepCopyInto(&out.Resources) | ||||||
| 	if in.Command != nil { | 	if in.Command != nil { | ||||||
| 		in, out := &in.Command, &out.Command | 		in, out := &in.Command, &out.Command | ||||||
| 		*out = make([]string, len(*in)) | 		*out = make([]string, len(*in)) | ||||||
|  | @ -82,7 +83,6 @@ func (in *Container) DeepCopyInto(out *Container) { | ||||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	in.Resources.DeepCopyInto(&out.Resources) |  | ||||||
| 	if in.VolumeMounts != nil { | 	if in.VolumeMounts != nil { | ||||||
| 		in, out := &in.VolumeMounts, &out.VolumeMounts | 		in, out := &in.VolumeMounts, &out.VolumeMounts | ||||||
| 		*out = make([]v1.VolumeMount, len(*in)) | 		*out = make([]v1.VolumeMount, len(*in)) | ||||||
|  | @ -222,6 +222,11 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||||
| 			(*out)[key] = val | 			(*out)[key] = val | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	if in.SecurityContext != nil { | ||||||
|  | 		in, out := &in.SecurityContext, &out.SecurityContext | ||||||
|  | 		*out = new(v1.PodSecurityContext) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
| 	if in.Containers != nil { | 	if in.Containers != nil { | ||||||
| 		in, out := &in.Containers, &out.Containers | 		in, out := &in.Containers, &out.Containers | ||||||
| 		*out = make([]Container, len(*in)) | 		*out = make([]Container, len(*in)) | ||||||
|  |  | ||||||
|  | @ -388,6 +388,11 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O | ||||||
| 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) | 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) | ||||||
| 	if err != nil && errors.IsNotFound(err) { | 	if err != nil && errors.IsNotFound(err) { | ||||||
| 		jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins) | 		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 && <custom-command-here> && /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)) | 		r.logger.Info(fmt.Sprintf("Creating a new Jenkins Master Pod %s/%s", jenkinsMasterPod.Namespace, jenkinsMasterPod.Name)) | ||||||
| 		err = r.createResource(jenkinsMasterPod) | 		err = r.createResource(jenkinsMasterPod) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -475,6 +480,12 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa | ||||||
| 		return true | 		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) { | 	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", | 		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)) | 			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)) | 		r.logger.Info(fmt.Sprintf("Resources have changed to '%+v' in container '%s', recreating pod", expected.Resources, expected.Name)) | ||||||
| 		return true | 		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)) | 		r.logger.Info(fmt.Sprintf("Security context has changed to '%+v' in container '%s', recreating pod", expected.SecurityContext, expected.Name)) | ||||||
| 		return true | 		return true | ||||||
| 	}*/ | 	} | ||||||
| 	if !reflect.DeepEqual(expected.WorkingDir, actual.WorkingDir) { | 	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)) | 		r.logger.Info(fmt.Sprintf("Working directory has changed to '%+v' in container '%s', recreating pod", expected.WorkingDir, expected.Name)) | ||||||
| 		return true | 		return true | ||||||
|  |  | ||||||
|  | @ -19,8 +19,10 @@ const ( | ||||||
| 	jenkinsHomePath       = jenkinsPath + "/home" | 	jenkinsHomePath       = jenkinsPath + "/home" | ||||||
| 
 | 
 | ||||||
| 	jenkinsScriptsVolumeName = "scripts" | 	jenkinsScriptsVolumeName = "scripts" | ||||||
| 	jenkinsScriptsVolumePath = jenkinsPath + "/scripts" | 	// JenkinsScriptsVolumePath is a path where are scripts used to configure Jenkins
 | ||||||
| 	initScriptName           = "init.sh" | 	JenkinsScriptsVolumePath = jenkinsPath + "/scripts" | ||||||
|  | 	// InitScriptName is the init script name which configures init.groovy.d, scripts and install plugins
 | ||||||
|  | 	InitScriptName = "init.sh" | ||||||
| 
 | 
 | ||||||
| 	jenkinsOperatorCredentialsVolumeName = "operator-credentials" | 	jenkinsOperatorCredentialsVolumeName = "operator-credentials" | ||||||
| 	jenkinsOperatorCredentialsVolumePath = jenkinsPath + "/operator-credentials" | 	jenkinsOperatorCredentialsVolumePath = jenkinsPath + "/operator-credentials" | ||||||
|  | @ -55,8 +57,18 @@ func buildPodTypeMeta() metav1.TypeMeta { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetJenkinsMasterPodBaseEnvs returns Jenkins master pod envs required by operator
 | // GetJenkinsMasterContainerBaseCommand returns default Jenkins master container command
 | ||||||
| func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { | 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{ | 	return []corev1.EnvVar{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:  "JENKINS_HOME", | 			Name:  "JENKINS_HOME", | ||||||
|  | @ -77,6 +89,7 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar { | ||||||
| func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { | func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { | ||||||
| 	configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode | 	configMapVolumeSourceDefaultMode := corev1.ConfigMapVolumeSourceDefaultMode | ||||||
| 	secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode | 	secretVolumeSourceDefaultMode := corev1.SecretVolumeSourceDefaultMode | ||||||
|  | 	var scriptsVolumeDefaultMode int32 = 0777 | ||||||
| 	return []corev1.Volume{ | 	return []corev1.Volume{ | ||||||
| 		{ | 		{ | ||||||
| 			Name: JenkinsHomeVolumeName, | 			Name: JenkinsHomeVolumeName, | ||||||
|  | @ -88,7 +101,7 @@ func GetJenkinsMasterPodBaseVolumes(jenkins *v1alpha2.Jenkins) []corev1.Volume { | ||||||
| 			Name: jenkinsScriptsVolumeName, | 			Name: jenkinsScriptsVolumeName, | ||||||
| 			VolumeSource: corev1.VolumeSource{ | 			VolumeSource: corev1.VolumeSource{ | ||||||
| 				ConfigMap: &corev1.ConfigMapVolumeSource{ | 				ConfigMap: &corev1.ConfigMapVolumeSource{ | ||||||
| 					DefaultMode: &configMapVolumeSourceDefaultMode, | 					DefaultMode: &scriptsVolumeDefaultMode, | ||||||
| 					LocalObjectReference: corev1.LocalObjectReference{ | 					LocalObjectReference: corev1.LocalObjectReference{ | ||||||
| 						Name: getScriptsConfigMapName(jenkins), | 						Name: getScriptsConfigMapName(jenkins), | ||||||
| 					}, | 					}, | ||||||
|  | @ -159,7 +172,7 @@ func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount { | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:      jenkinsScriptsVolumeName, | 			Name:      jenkinsScriptsVolumeName, | ||||||
| 			MountPath: jenkinsScriptsVolumePath, | 			MountPath: JenkinsScriptsVolumePath, | ||||||
| 			ReadOnly:  true, | 			ReadOnly:  true, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
|  | @ -193,7 +206,7 @@ func GetJenkinsMasterContainerBaseVolumeMounts() []corev1.VolumeMount { | ||||||
| // NewJenkinsMasterContainer returns Jenkins master Kubernetes container
 | // NewJenkinsMasterContainer returns Jenkins master Kubernetes container
 | ||||||
| func NewJenkinsMasterContainer(jenkins *v1alpha2.Jenkins) corev1.Container { | func NewJenkinsMasterContainer(jenkins *v1alpha2.Jenkins) corev1.Container { | ||||||
| 	jenkinsContainer := jenkins.Spec.Master.Containers[0] | 	jenkinsContainer := jenkins.Spec.Master.Containers[0] | ||||||
| 	envs := GetJenkinsMasterPodBaseEnvs() | 	envs := GetJenkinsMasterContainerBaseEnvs() | ||||||
| 	envs = append(envs, jenkinsContainer.Env...) | 	envs = append(envs, jenkinsContainer.Env...) | ||||||
| 
 | 
 | ||||||
| 	return corev1.Container{ | 	return corev1.Container{ | ||||||
|  |  | ||||||
|  | @ -239,7 +239,7 @@ main() { | ||||||
| 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 -e | ||||||
| set -x | set -x | ||||||
| 
 | 
 | ||||||
|  | @ -261,8 +261,6 @@ echo "Installing plugins required by Operator - end" | ||||||
| echo "Installing plugins required by user - begin" | echo "Installing plugins required by user - begin" | ||||||
| {{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | {{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | ||||||
| echo "Installing plugins required by user - end" | echo "Installing plugins required by user - end" | ||||||
| 
 |  | ||||||
| /sbin/tini -s -- /usr/local/bin/jenkins.sh |  | ||||||
| `)) | `)) | ||||||
| 
 | 
 | ||||||
| func buildConfigMapTypeMeta() metav1.TypeMeta { | func buildConfigMapTypeMeta() metav1.TypeMeta { | ||||||
|  | @ -286,7 +284,7 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { | ||||||
| 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | ||||||
| 		UserPlugins:              jenkins.Spec.Master.Plugins, | 		UserPlugins:              jenkins.Spec.Master.Plugins, | ||||||
| 		InstallPluginsCommand:    installPluginsCommand, | 		InstallPluginsCommand:    installPluginsCommand, | ||||||
| 		JenkinsScriptsVolumePath: jenkinsScriptsVolumePath, | 		JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	output, err := render(initBashTemplate, data) | 	output, err := render(initBashTemplate, data) | ||||||
|  | @ -314,7 +312,7 @@ func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins) (*co | ||||||
| 		TypeMeta:   buildConfigMapTypeMeta(), | 		TypeMeta:   buildConfigMapTypeMeta(), | ||||||
| 		ObjectMeta: meta, | 		ObjectMeta: meta, | ||||||
| 		Data: map[string]string{ | 		Data: map[string]string{ | ||||||
| 			initScriptName:        *initBashScript, | 			InitScriptName:        *initBashScript, | ||||||
| 			installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, jenkinsHomePath), | 			installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, jenkinsHomePath), | ||||||
| 		}, | 		}, | ||||||
| 	}, nil | 	}, nil | ||||||
|  |  | ||||||
|  | @ -194,7 +194,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(contai | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool { | ||||||
| 	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs() | 	baseEnvs := resources.GetJenkinsMasterContainerBaseEnvs() | ||||||
| 	baseEnvNames := map[string]string{} | 	baseEnvNames := map[string]string{} | ||||||
| 	for _, env := range baseEnvs { | 	for _, env := range baseEnvs { | ||||||
| 		baseEnvNames[env.Name] = env.Value | 		baseEnvNames[env.Name] = env.Value | ||||||
|  |  | ||||||
|  | @ -325,6 +325,11 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | ||||||
| 			FailureThreshold:    int32(12), | 			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 { | 	if len(jenkins.Spec.Master.BasePlugins) == 0 { | ||||||
| 		logger.Info("Setting default operator plugins") | 		logger.Info("Setting default operator plugins") | ||||||
| 		changed = true | 		changed = true | ||||||
|  | @ -404,6 +409,17 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | ||||||
| 		jenkins.Spec.Master.Containers = containers | 		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 { | 	if changed { | ||||||
| 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | 		return errors.WithStack(r.client.Update(context.TODO(), jenkins)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue