#199 Generalize Jenkins container command validation

This commit is contained in:
Tomasz Sęk 2020-01-18 19:11:27 +01:00
parent bf3a4bc02f
commit bf07fa59ef
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
5 changed files with 178 additions and 5 deletions

View File

@ -237,6 +237,22 @@ func (in *Jenkins) DeepCopyObject() runtime.Object {
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.
func (in *JenkinsList) DeepCopyInto(out *JenkinsList) {
*out = *in
@ -376,6 +392,7 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) {
copy(*out, *in)
}
in.ServiceAccount.DeepCopyInto(&out.ServiceAccount)
out.JenkinsAPISettings = in.JenkinsAPISettings
return
}

View File

@ -484,11 +484,6 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O
currentJenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil && apierrors.IsNotFound(err) {
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{
Jenkins: *r.Configuration.Jenkins,
Phase: event.PhaseBase,

View File

@ -69,6 +69,38 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins)
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) {
var messages []string
for _, sr := range r.Configuration.Jenkins.Spec.Master.ImagePullSecrets {

View File

@ -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"})
})
}
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)
})
}

View File

@ -150,3 +150,12 @@ func (c *Configuration) Exec(podName, containerName string, command []string) (s
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
}