#199 Generalize Jenkins container command validation
This commit is contained in:
		
							parent
							
								
									bf3a4bc02f
								
							
						
					
					
						commit
						bf07fa59ef
					
				|  | @ -237,6 +237,22 @@ func (in *Jenkins) DeepCopyObject() runtime.Object { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *JenkinsAPISettings) DeepCopyInto(out *JenkinsAPISettings) { | ||||||
|  | 	*out = *in | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsAPISettings.
 | ||||||
|  | func (in *JenkinsAPISettings) DeepCopy() *JenkinsAPISettings { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(JenkinsAPISettings) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // 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 *JenkinsList) DeepCopyInto(out *JenkinsList) { | func (in *JenkinsList) DeepCopyInto(out *JenkinsList) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | @ -376,6 +392,7 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
| 	in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) | 	in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) | ||||||
|  | 	out.JenkinsAPISettings = in.JenkinsAPISettings | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -484,11 +484,6 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O | ||||||
| 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod() | 	currentJenkinsMasterPod, err := r.getJenkinsMasterPod() | ||||||
| 	if err != nil && apierrors.IsNotFound(err) { | 	if err != nil && apierrors.IsNotFound(err) { | ||||||
| 		jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.Configuration.Jenkins) | 		jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.Configuration.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.Notifications <- event.Event{ | 		*r.Notifications <- event.Event{ | ||||||
| 			Jenkins: *r.Configuration.Jenkins, | 			Jenkins: *r.Configuration.Jenkins, | ||||||
| 			Phase:   event.PhaseBase, | 			Phase:   event.PhaseBase, | ||||||
|  |  | ||||||
|  | @ -69,6 +69,38 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) | ||||||
| 	return messages, nil | 	return messages, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterContainerCommand() []string { | ||||||
|  | 	masterContainer := r.Configuration.GetJenkinsMasterContainer() | ||||||
|  | 	if masterContainer == nil { | ||||||
|  | 		return []string{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	jenkinsOperatorInitScript := fmt.Sprintf("%s/%s && ", resources.JenkinsScriptsVolumePath, resources.InitScriptName) | ||||||
|  | 	correctCommand := []string{ | ||||||
|  | 		"bash", | ||||||
|  | 		"-c", | ||||||
|  | 		fmt.Sprintf("%s<optional-custom-command> && exec <command-which-start-jenkins>", jenkinsOperatorInitScript), | ||||||
|  | 	} | ||||||
|  | 	invalidCommandMessage := []string{fmt.Sprintf("spec.master.containers[%s].command is invalid, make sure it looks like '%v', otherwise the operator won't configure default user and install plugins. 'exec' is required to propagate signals to the Jenkins.", masterContainer.Name, correctCommand)} | ||||||
|  | 	if len(masterContainer.Command) != 3 { | ||||||
|  | 		return invalidCommandMessage | ||||||
|  | 	} | ||||||
|  | 	if masterContainer.Command[0] != correctCommand[0] { | ||||||
|  | 		return invalidCommandMessage | ||||||
|  | 	} | ||||||
|  | 	if masterContainer.Command[1] != correctCommand[1] { | ||||||
|  | 		return invalidCommandMessage | ||||||
|  | 	} | ||||||
|  | 	if !strings.HasPrefix(masterContainer.Command[2], jenkinsOperatorInitScript) { | ||||||
|  | 		return invalidCommandMessage | ||||||
|  | 	} | ||||||
|  | 	if !strings.Contains(masterContainer.Command[2], "exec") { | ||||||
|  | 		return invalidCommandMessage | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() ([]string, error) { | func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() ([]string, error) { | ||||||
| 	var messages []string | 	var messages []string | ||||||
| 	for _, sr := range r.Configuration.Jenkins.Spec.Master.ImagePullSecrets { | 	for _, sr := range r.Configuration.Jenkins.Spec.Master.ImagePullSecrets { | ||||||
|  |  | ||||||
|  | @ -840,3 +840,123 @@ func TestValidateCustomization(t *testing.T) { | ||||||
| 		assert.Equal(t, got, []string{"ConfigMap 'configmap-name' configured in spec.groovyScripts.configurations[0] not found"}) | 		assert.Equal(t, got, []string{"ConfigMap 'configmap-name' configured in spec.groovyScripts.configurations[0] not found"}) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestValidateJenkinsMasterContainerCommand(t *testing.T) { | ||||||
|  | 	log.SetupLogger(true) | ||||||
|  | 	t.Run("no Jenkins master container", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Empty(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("empty command", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					Containers: []v1alpha2.Container{ | ||||||
|  | 						{ | ||||||
|  | 							Name: resources.JenkinsMasterContainerName, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Len(t, got, 1) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("command has 3 lines but it's values are invalid", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					Containers: []v1alpha2.Container{ | ||||||
|  | 						{ | ||||||
|  | 							Name: resources.JenkinsMasterContainerName, | ||||||
|  | 							Command: []string{ | ||||||
|  | 								"invalid", | ||||||
|  | 								"invalid", | ||||||
|  | 								"invalid", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Len(t, got, 1) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("valid command", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					Containers: []v1alpha2.Container{ | ||||||
|  | 						{ | ||||||
|  | 							Name:    resources.JenkinsMasterContainerName, | ||||||
|  | 							Command: resources.GetJenkinsMasterContainerBaseCommand(), | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Len(t, got, 0) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("custom valid command", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					Containers: []v1alpha2.Container{ | ||||||
|  | 						{ | ||||||
|  | 							Name: resources.JenkinsMasterContainerName, | ||||||
|  | 							Command: []string{ | ||||||
|  | 								"bash", | ||||||
|  | 								"-c", | ||||||
|  | 								fmt.Sprintf("%s/%s && my-extra-command.sh && exec /sbin/tini -s -- /usr/local/bin/jenkins.sh", | ||||||
|  | 									resources.JenkinsScriptsVolumePath, resources.InitScriptName), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Len(t, got, 0) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("no exec command for the Jenkins", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					Containers: []v1alpha2.Container{ | ||||||
|  | 						{ | ||||||
|  | 							Name: resources.JenkinsMasterContainerName, | ||||||
|  | 							Command: []string{ | ||||||
|  | 								"bash", | ||||||
|  | 								"-c", | ||||||
|  | 								fmt.Sprintf("%s/%s && my-extra-command.sh && /sbin/tini -s -- /usr/local/bin/jenkins.sh", | ||||||
|  | 									resources.JenkinsScriptsVolumePath, resources.InitScriptName), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		baseReconcileLoop := New(configuration.Configuration{Jenkins: jenkins}, log.Log, client.JenkinsAPIConnectionSettings{}) | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validateJenkinsMasterContainerCommand() | ||||||
|  | 
 | ||||||
|  | 		assert.Len(t, got, 1) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -150,3 +150,12 @@ func (c *Configuration) Exec(podName, containerName string, command []string) (s | ||||||
| 
 | 
 | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // GetJenkinsMasterContainer returns the Jenkins master container from the CR
 | ||||||
|  | func (c *Configuration) GetJenkinsMasterContainer() *v1alpha2.Container { | ||||||
|  | 	if len(c.Jenkins.Spec.Master.Containers) > 0 { | ||||||
|  | 		// the first container is the Jenkins master, it is forced jenkins_controller.go
 | ||||||
|  | 		return &c.Jenkins.Spec.Master.Containers[0] | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue