Improve plugins management
This commit is contained in:
		
							parent
							
								
									8178e2315e
								
							
						
					
					
						commit
						e1aba3ed9f
					
				|  | @ -1,21 +1,5 @@ | ||||||
| // +build !ignore_autogenerated
 | // +build !ignore_autogenerated
 | ||||||
| 
 | 
 | ||||||
| /* |  | ||||||
| Copyright The Kubernetes Authors. |  | ||||||
| 
 |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
| 
 |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
| 
 |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| // Code generated by deepcopy-gen. DO NOT EDIT.
 | // Code generated by deepcopy-gen. DO NOT EDIT.
 | ||||||
| 
 | 
 | ||||||
| package v1alpha1 | package v1alpha1 | ||||||
|  | @ -49,6 +33,79 @@ func (in *Build) DeepCopy() *Build { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 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 | ||||||
|  | 	if in.Command != nil { | ||||||
|  | 		in, out := &in.Command, &out.Command | ||||||
|  | 		*out = make([]string, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.Args != nil { | ||||||
|  | 		in, out := &in.Args, &out.Args | ||||||
|  | 		*out = make([]string, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.Ports != nil { | ||||||
|  | 		in, out := &in.Ports, &out.Ports | ||||||
|  | 		*out = make([]v1.ContainerPort, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.EnvFrom != nil { | ||||||
|  | 		in, out := &in.EnvFrom, &out.EnvFrom | ||||||
|  | 		*out = make([]v1.EnvFromSource, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.Env != nil { | ||||||
|  | 		in, out := &in.Env, &out.Env | ||||||
|  | 		*out = make([]v1.EnvVar, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*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)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.LivenessProbe != nil { | ||||||
|  | 		in, out := &in.LivenessProbe, &out.LivenessProbe | ||||||
|  | 		*out = new(v1.Probe) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.ReadinessProbe != nil { | ||||||
|  | 		in, out := &in.ReadinessProbe, &out.ReadinessProbe | ||||||
|  | 		*out = new(v1.Probe) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.Lifecycle != nil { | ||||||
|  | 		in, out := &in.Lifecycle, &out.Lifecycle | ||||||
|  | 		*out = new(v1.Lifecycle) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.SecurityContext != nil { | ||||||
|  | 		in, out := &in.SecurityContext, &out.SecurityContext | ||||||
|  | 		*out = new(v1.SecurityContext) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container.
 | ||||||
|  | func (in *Container) DeepCopy() *Container { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(Container) | ||||||
|  | 	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 *Jenkins) DeepCopyInto(out *Jenkins) { | func (in *Jenkins) DeepCopyInto(out *Jenkins) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | @ -113,13 +170,7 @@ func (in *JenkinsList) DeepCopyObject() runtime.Object { | ||||||
| // 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 *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
| 	if in.NodeSelector != nil { | 	in.Container.DeepCopyInto(&out.Container) | ||||||
| 		in, out := &in.NodeSelector, &out.NodeSelector |  | ||||||
| 		*out = make(map[string]string, len(*in)) |  | ||||||
| 		for key, val := range *in { |  | ||||||
| 			(*out)[key] = val |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if in.Annotations != nil { | 	if in.Annotations != nil { | ||||||
| 		in, out := &in.Annotations, &out.Annotations | 		in, out := &in.Annotations, &out.Annotations | ||||||
| 		*out = make(map[string]string, len(*in)) | 		*out = make(map[string]string, len(*in)) | ||||||
|  | @ -127,23 +178,26 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||||
| 			(*out)[key] = val | 			(*out)[key] = val | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	in.Resources.DeepCopyInto(&out.Resources) | 	if in.NodeSelector != nil { | ||||||
| 	if in.Env != nil { | 		in, out := &in.NodeSelector, &out.NodeSelector | ||||||
| 		in, out := &in.Env, &out.Env | 		*out = make(map[string]string, len(*in)) | ||||||
| 		*out = make([]v1.EnvVar, len(*in)) | 		for key, val := range *in { | ||||||
|  | 			(*out)[key] = val | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.Containers != nil { | ||||||
|  | 		in, out := &in.Containers, &out.Containers | ||||||
|  | 		*out = make([]Container, len(*in)) | ||||||
| 		for i := range *in { | 		for i := range *in { | ||||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if in.LivenessProbe != nil { | 	if in.Volumes != nil { | ||||||
| 		in, out := &in.LivenessProbe, &out.LivenessProbe | 		in, out := &in.Volumes, &out.Volumes | ||||||
| 		*out = new(v1.Probe) | 		*out = make([]v1.Volume, len(*in)) | ||||||
| 		(*in).DeepCopyInto(*out) | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
| 		} | 		} | ||||||
| 	if in.ReadinessProbe != nil { |  | ||||||
| 		in, out := &in.ReadinessProbe, &out.ReadinessProbe |  | ||||||
| 		*out = new(v1.Probe) |  | ||||||
| 		(*in).DeepCopyInto(*out) |  | ||||||
| 	} | 	} | ||||||
| 	if in.OperatorPlugins != nil { | 	if in.OperatorPlugins != nil { | ||||||
| 		in, out := &in.OperatorPlugins, &out.OperatorPlugins | 		in, out := &in.OperatorPlugins, &out.OperatorPlugins | ||||||
|  |  | ||||||
|  | @ -38,6 +38,12 @@ type Container struct { | ||||||
| 	SecurityContext *corev1.SecurityContext     `json:"securityContext,omitempty"` | 	SecurityContext *corev1.SecurityContext     `json:"securityContext,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Plugin defines Jenkins plugin
 | ||||||
|  | type Plugin struct { | ||||||
|  | 	Name    string `json:"name"` | ||||||
|  | 	Version string `json:"version"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // JenkinsMaster defines the Jenkins master pod attributes and plugins,
 | // JenkinsMaster defines the Jenkins master pod attributes and plugins,
 | ||||||
| // every single change requires Jenkins master pod restart
 | // every single change requires Jenkins master pod restart
 | ||||||
| type JenkinsMaster struct { | type JenkinsMaster struct { | ||||||
|  | @ -49,10 +55,10 @@ type JenkinsMaster struct { | ||||||
| 	Containers   []Container       `json:"containers,omitempty"` | 	Containers   []Container       `json:"containers,omitempty"` | ||||||
| 	Volumes      []corev1.Volume   `json:"volumes,omitempty"` | 	Volumes      []corev1.Volume   `json:"volumes,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// OperatorPlugins contains plugins required by operator
 | 	// BasePlugins contains plugins required by operator
 | ||||||
| 	OperatorPlugins map[string][]string `json:"basePlugins,omitempty"` | 	BasePlugins []Plugin `json:"basePlugins,omitempty"` | ||||||
| 	// Plugins contains plugins required by user
 | 	// Plugins contains plugins required by user
 | ||||||
| 	Plugins map[string][]string `json:"plugins,omitempty"` | 	Plugins []Plugin `json:"plugins,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Service defines Kubernetes service attributes which Operator will manage
 | // Service defines Kubernetes service attributes which Operator will manage
 | ||||||
|  |  | ||||||
|  | @ -1,21 +1,5 @@ | ||||||
| // +build !ignore_autogenerated
 | // +build !ignore_autogenerated
 | ||||||
| 
 | 
 | ||||||
| /* |  | ||||||
| Copyright The Kubernetes Authors. |  | ||||||
| 
 |  | ||||||
| Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
| you may not use this file except in compliance with the License. |  | ||||||
| You may obtain a copy of the License at |  | ||||||
| 
 |  | ||||||
|     http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
| 
 |  | ||||||
| Unless required by applicable law or agreed to in writing, software |  | ||||||
| distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
| See the License for the specific language governing permissions and |  | ||||||
| limitations under the License. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| // Code generated by deepcopy-gen. DO NOT EDIT.
 | // Code generated by deepcopy-gen. DO NOT EDIT.
 | ||||||
| 
 | 
 | ||||||
| package v1alpha2 | package v1alpha2 | ||||||
|  | @ -49,6 +33,79 @@ func (in *Build) DeepCopy() *Build { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 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 | ||||||
|  | 	if in.Command != nil { | ||||||
|  | 		in, out := &in.Command, &out.Command | ||||||
|  | 		*out = make([]string, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.Args != nil { | ||||||
|  | 		in, out := &in.Args, &out.Args | ||||||
|  | 		*out = make([]string, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.Ports != nil { | ||||||
|  | 		in, out := &in.Ports, &out.Ports | ||||||
|  | 		*out = make([]v1.ContainerPort, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | 	if in.EnvFrom != nil { | ||||||
|  | 		in, out := &in.EnvFrom, &out.EnvFrom | ||||||
|  | 		*out = make([]v1.EnvFromSource, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.Env != nil { | ||||||
|  | 		in, out := &in.Env, &out.Env | ||||||
|  | 		*out = make([]v1.EnvVar, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*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)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if in.LivenessProbe != nil { | ||||||
|  | 		in, out := &in.LivenessProbe, &out.LivenessProbe | ||||||
|  | 		*out = new(v1.Probe) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.ReadinessProbe != nil { | ||||||
|  | 		in, out := &in.ReadinessProbe, &out.ReadinessProbe | ||||||
|  | 		*out = new(v1.Probe) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.Lifecycle != nil { | ||||||
|  | 		in, out := &in.Lifecycle, &out.Lifecycle | ||||||
|  | 		*out = new(v1.Lifecycle) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	if in.SecurityContext != nil { | ||||||
|  | 		in, out := &in.SecurityContext, &out.SecurityContext | ||||||
|  | 		*out = new(v1.SecurityContext) | ||||||
|  | 		(*in).DeepCopyInto(*out) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container.
 | ||||||
|  | func (in *Container) DeepCopy() *Container { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(Container) | ||||||
|  | 	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 *Jenkins) DeepCopyInto(out *Jenkins) { | func (in *Jenkins) DeepCopyInto(out *Jenkins) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | @ -120,61 +177,37 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||||
| 			(*out)[key] = val | 			(*out)[key] = val | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if in.Annotations != nil { | 	if in.NodeSelector != nil { | ||||||
| 		in, out := &in.Annotations, &out.Annotations | 		in, out := &in.NodeSelector, &out.NodeSelector | ||||||
| 		*out = make(map[string]string, len(*in)) | 		*out = make(map[string]string, len(*in)) | ||||||
| 		for key, val := range *in { | 		for key, val := range *in { | ||||||
| 			(*out)[key] = val | 			(*out)[key] = val | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	in.Resources.DeepCopyInto(&out.Resources) | 	if in.Containers != nil { | ||||||
| 	if in.Env != nil { | 		in, out := &in.Containers, &out.Containers | ||||||
| 		in, out := &in.Env, &out.Env | 		*out = make([]Container, len(*in)) | ||||||
| 		*out = make([]v1.EnvVar, len(*in)) |  | ||||||
| 		for i := range *in { | 		for i := range *in { | ||||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if in.LivenessProbe != nil { | 	if in.Volumes != nil { | ||||||
| 		in, out := &in.LivenessProbe, &out.LivenessProbe | 		in, out := &in.Volumes, &out.Volumes | ||||||
| 		*out = new(v1.Probe) | 		*out = make([]v1.Volume, len(*in)) | ||||||
| 		(*in).DeepCopyInto(*out) | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
| 		} | 		} | ||||||
| 	if in.ReadinessProbe != nil { |  | ||||||
| 		in, out := &in.ReadinessProbe, &out.ReadinessProbe |  | ||||||
| 		*out = new(v1.Probe) |  | ||||||
| 		(*in).DeepCopyInto(*out) |  | ||||||
| 	} | 	} | ||||||
| 	if in.OperatorPlugins != nil { | 	if in.BasePlugins != nil { | ||||||
| 		in, out := &in.OperatorPlugins, &out.OperatorPlugins | 		in, out := &in.BasePlugins, &out.BasePlugins | ||||||
| 		*out = make(map[string][]string, len(*in)) | 		*out = make([]Plugin, len(*in)) | ||||||
| 		for key, val := range *in { |  | ||||||
| 			var outVal []string |  | ||||||
| 			if val == nil { |  | ||||||
| 				(*out)[key] = nil |  | ||||||
| 			} else { |  | ||||||
| 				in, out := &val, &outVal |  | ||||||
| 				*out = make([]string, len(*in)) |  | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
| 			(*out)[key] = outVal |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if in.Plugins != nil { | 	if in.Plugins != nil { | ||||||
| 		in, out := &in.Plugins, &out.Plugins | 		in, out := &in.Plugins, &out.Plugins | ||||||
| 		*out = make(map[string][]string, len(*in)) | 		*out = make([]Plugin, len(*in)) | ||||||
| 		for key, val := range *in { |  | ||||||
| 			var outVal []string |  | ||||||
| 			if val == nil { |  | ||||||
| 				(*out)[key] = nil |  | ||||||
| 			} else { |  | ||||||
| 				in, out := &val, &outVal |  | ||||||
| 				*out = make([]string, len(*in)) |  | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
| 			(*out)[key] = outVal |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -247,6 +280,22 @@ func (in *JenkinsStatus) DeepCopy() *JenkinsStatus { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *Plugin) DeepCopyInto(out *Plugin) { | ||||||
|  | 	*out = *in | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plugin.
 | ||||||
|  | func (in *Plugin) DeepCopy() *Plugin { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(Plugin) | ||||||
|  | 	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 *SeedJob) DeepCopyInto(out *SeedJob) { | func (in *SeedJob) DeepCopyInto(out *SeedJob) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  |  | ||||||
|  | @ -106,7 +106,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki | ||||||
| 	} | 	} | ||||||
| 	r.logger.V(log.VDebug).Info("Jenkins API client set") | 	r.logger.V(log.VDebug).Info("Jenkins API client set") | ||||||
| 
 | 
 | ||||||
| 	ok, err := r.verifyPlugins(jenkinsClient, plugins.BasePluginsMap) | 	ok, err := r.verifyPlugins(jenkinsClient) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return reconcile.Result{}, nil, err | 		return reconcile.Result{}, nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -187,7 +187,7 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForVolumes() (reconcile.Result, | ||||||
| 	return reconcile.Result{}, nil | 	return reconcile.Result{}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsclient.Jenkins, basePlugins map[string][]plugins.Plugin) (bool, error) { | func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) { | ||||||
| 	allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins) | 	allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, stackerr.WithStack(err) | 		return false, stackerr.WithStack(err) | ||||||
|  | @ -201,46 +201,16 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsc | ||||||
| 	} | 	} | ||||||
| 	r.logger.V(log.VDebug).Info(fmt.Sprintf("Installed plugins '%+v'", installedPlugins)) | 	r.logger.V(log.VDebug).Info(fmt.Sprintf("Installed plugins '%+v'", installedPlugins)) | ||||||
| 
 | 
 | ||||||
| 	userPlugins := map[string][]plugins.Plugin{} |  | ||||||
| 	for rootPlugin, dependentPluginNames := range r.jenkins.Spec.Master.Plugins { |  | ||||||
| 		var dependentPlugins []plugins.Plugin |  | ||||||
| 		for _, pluginNameWithVersion := range dependentPluginNames { |  | ||||||
| 			plugin, err := plugins.New(pluginNameWithVersion) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return false, err |  | ||||||
| 			} |  | ||||||
| 			dependentPlugins = append(dependentPlugins, *plugin) |  | ||||||
| 		} |  | ||||||
| 		userPlugins[rootPlugin] = dependentPlugins |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	status := true | 	status := true | ||||||
| 	allRequiredPlugins := []map[string][]plugins.Plugin{basePlugins, userPlugins} | 	allRequiredPlugins := [][]v1alpha2.Plugin{r.jenkins.Spec.Master.BasePlugins, r.jenkins.Spec.Master.Plugins} | ||||||
| 	for _, requiredPlugins := range allRequiredPlugins { | 	for _, requiredPlugins := range allRequiredPlugins { | ||||||
| 		for rootPluginName, p := range requiredPlugins { | 		for _, plugin := range requiredPlugins { | ||||||
| 			rootPlugin, _ := plugins.New(rootPluginName) | 			if found, ok := isPluginInstalled(allPluginsInJenkins, plugin); !ok { | ||||||
| 			if found, ok := isPluginInstalled(allPluginsInJenkins, *rootPlugin); !ok { | 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", plugin, found)) | ||||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", rootPlugin, found)) |  | ||||||
| 				status = false | 				status = false | ||||||
| 			} | 			} | ||||||
| 			for _, requiredPlugin := range p { | 			if found, ok := isPluginVersionCompatible(allPluginsInJenkins, plugin); !ok { | ||||||
| 				if found, ok := isPluginInstalled(allPluginsInJenkins, requiredPlugin); !ok { | 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", plugin, found.Version)) | ||||||
| 					r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", requiredPlugin, found)) |  | ||||||
| 					status = false |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for rootPluginName, p := range userPlugins { |  | ||||||
| 		rootPlugin, _ := plugins.New(rootPluginName) |  | ||||||
| 		if found, ok := isPluginVersionCompatible(allPluginsInJenkins, *rootPlugin); !ok { |  | ||||||
| 			r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", rootPlugin, found.Version)) |  | ||||||
| 			status = false |  | ||||||
| 		} |  | ||||||
| 		for _, requiredPlugin := range p { |  | ||||||
| 			if found, ok := isPluginInstalled(allPluginsInJenkins, requiredPlugin); !ok { |  | ||||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", requiredPlugin, found.Version)) |  | ||||||
| 				status = false | 				status = false | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -249,7 +219,7 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsc | ||||||
| 	return status, nil | 	return status, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func isPluginVersionCompatible(plugins *gojenkins.Plugins, plugin plugins.Plugin) (gojenkins.Plugin, bool) { | func isPluginVersionCompatible(plugins *gojenkins.Plugins, plugin v1alpha2.Plugin) (gojenkins.Plugin, bool) { | ||||||
| 	p := plugins.Contains(plugin.Name) | 	p := plugins.Contains(plugin.Name) | ||||||
| 	if p == nil { | 	if p == nil { | ||||||
| 		return gojenkins.Plugin{}, false | 		return gojenkins.Plugin{}, false | ||||||
|  | @ -262,7 +232,7 @@ func isValidPlugin(plugin gojenkins.Plugin) bool { | ||||||
| 	return plugin.Active && plugin.Enabled && !plugin.Deleted | 	return plugin.Active && plugin.Enabled && !plugin.Deleted | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func isPluginInstalled(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (gojenkins.Plugin, bool) { | func isPluginInstalled(plugins *gojenkins.Plugins, requiredPlugin v1alpha2.Plugin) (gojenkins.Plugin, bool) { | ||||||
| 	p := plugins.Contains(requiredPlugin.Name) | 	p := plugins.Contains(requiredPlugin.Name) | ||||||
| 	if p == nil { | 	if p == nil { | ||||||
| 		return gojenkins.Plugin{}, false | 		return gojenkins.Plugin{}, false | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import ( | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" |  | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
| 
 | 
 | ||||||
| 	"github.com/bndr/gojenkins" | 	"github.com/bndr/gojenkins" | ||||||
|  | @ -167,19 +166,67 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 		pluginsInJenkins := &gojenkins.Plugins{ | 		pluginsInJenkins := &gojenkins.Plugins{ | ||||||
| 			Raw: &gojenkins.PluginResponse{}, | 			Raw: &gojenkins.PluginResponse{}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("happy, not empty base and user plugins", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name1", Version: "0.0.1"}}, | ||||||
|  | 					Plugins:     []v1alpha2.Plugin{{Name: "plugin-name2", Version: "0.0.1"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		r := ReconcileJenkinsBaseConfiguration{ | ||||||
|  | 			logger:  log.Log, | ||||||
|  | 			jenkins: jenkins, | ||||||
|  | 		} | ||||||
|  | 		pluginsInJenkins := &gojenkins.Plugins{ | ||||||
|  | 			Raw: &gojenkins.PluginResponse{ | ||||||
|  | 				Plugins: []gojenkins.Plugin{ | ||||||
|  | 					{ | ||||||
|  | 						ShortName: "plugin-name1", | ||||||
|  | 						Active:    true, | ||||||
|  | 						Deleted:   false, | ||||||
|  | 						Enabled:   true, | ||||||
|  | 						Version:   "0.0.1", | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						ShortName: "plugin-name2", | ||||||
|  | 						Active:    true, | ||||||
|  | 						Deleted:   false, | ||||||
|  | 						Enabled:   true, | ||||||
|  | 						Version:   "0.0.1", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		ctrl := gomock.NewController(t) | ||||||
|  | 		defer ctrl.Finish() | ||||||
|  | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
|  | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
|  | 
 | ||||||
|  | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, got) | 		assert.True(t, got) | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("happy, not empty base and empty user plugins", func(t *testing.T) { | 	t.Run("happy, not empty base and empty user plugins", func(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{} | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
| 		r := ReconcileJenkinsBaseConfiguration{ | 		r := ReconcileJenkinsBaseConfiguration{ | ||||||
| 			logger:  log.Log, | 			logger:  log.Log, | ||||||
| 			jenkins: jenkins, | 			jenkins: jenkins, | ||||||
|  | @ -197,15 +244,12 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{ |  | ||||||
| 			"plugin-name:0.0.1": {}, |  | ||||||
| 		} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, got) | 		assert.True(t, got) | ||||||
|  | @ -214,7 +258,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{ | 		jenkins := &v1alpha2.Jenkins{ | ||||||
| 			Spec: v1alpha2.JenkinsSpec{ | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
| 				Master: v1alpha2.JenkinsMaster{ | 				Master: v1alpha2.JenkinsMaster{ | ||||||
| 					Plugins: map[string][]string{"plugin-name:0.0.1": {}}, | 					Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -235,19 +279,24 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, got) | 		assert.True(t, got) | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("happy, plugin version doesn't matter for base plugins", func(t *testing.T) { | 	t.Run("happy, plugin version matter for base plugins", func(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{} | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
| 		r := ReconcileJenkinsBaseConfiguration{ | 		r := ReconcileJenkinsBaseConfiguration{ | ||||||
| 			logger:  log.Log, | 			logger:  log.Log, | ||||||
| 			jenkins: jenkins, | 			jenkins: jenkins, | ||||||
|  | @ -265,24 +314,21 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{ |  | ||||||
| 			"plugin-name:0.0.1": {}, |  | ||||||
| 		} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, got) | 		assert.False(t, got) | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("plugin version matter for user plugins", func(t *testing.T) { | 	t.Run("plugin version matter for user plugins", func(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{ | 		jenkins := &v1alpha2.Jenkins{ | ||||||
| 			Spec: v1alpha2.JenkinsSpec{ | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
| 				Master: v1alpha2.JenkinsMaster{ | 				Master: v1alpha2.JenkinsMaster{ | ||||||
| 					Plugins: map[string][]string{"plugin-name:0.0.2": {}}, | 					Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -303,19 +349,24 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, got) | 		assert.False(t, got) | ||||||
| 	}) | 	}) | ||||||
| 	t.Run("missing base plugin", func(t *testing.T) { | 	t.Run("missing base plugin", func(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{} | 		jenkins := &v1alpha2.Jenkins{ | ||||||
|  | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 				Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 					BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
| 		r := ReconcileJenkinsBaseConfiguration{ | 		r := ReconcileJenkinsBaseConfiguration{ | ||||||
| 			logger:  log.Log, | 			logger:  log.Log, | ||||||
| 			jenkins: jenkins, | 			jenkins: jenkins, | ||||||
|  | @ -325,15 +376,12 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				Plugins: []gojenkins.Plugin{}, | 				Plugins: []gojenkins.Plugin{}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{ |  | ||||||
| 			"plugin-name:0.0.2": {}, |  | ||||||
| 		} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, got) | 		assert.False(t, got) | ||||||
|  | @ -342,7 +390,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 		jenkins := &v1alpha2.Jenkins{ | 		jenkins := &v1alpha2.Jenkins{ | ||||||
| 			Spec: v1alpha2.JenkinsSpec{ | 			Spec: v1alpha2.JenkinsSpec{ | ||||||
| 				Master: v1alpha2.JenkinsMaster{ | 				Master: v1alpha2.JenkinsMaster{ | ||||||
| 					Plugins: map[string][]string{"plugin-name:0.0.2": {}}, | 					Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -355,13 +403,12 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | ||||||
| 				Plugins: []gojenkins.Plugin{}, | 				Plugins: []gojenkins.Plugin{}, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		basePlugins := map[string][]plugins.Plugin{} |  | ||||||
| 		ctrl := gomock.NewController(t) | 		ctrl := gomock.NewController(t) | ||||||
| 		defer ctrl.Finish() | 		defer ctrl.Finish() | ||||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||||
| 
 | 
 | ||||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | 		got, err := r.verifyPlugins(jenkinsClient) | ||||||
| 
 | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, got) | 		assert.False(t, got) | ||||||
|  |  | ||||||
|  | @ -94,7 +94,9 @@ def remove = { list -> | ||||||
| remove(jenkins.getExtensionList(RootAction.class)) | remove(jenkins.getExtensionList(RootAction.class)) | ||||||
| remove(jenkins.actions) | remove(jenkins.actions) | ||||||
| 
 | 
 | ||||||
| jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false) | if (jenkins.getDescriptor("jenkins.CLI") != null) { | ||||||
|  |     jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| jenkins.save() | jenkins.save() | ||||||
| ` | ` | ||||||
|  |  | ||||||
|  | @ -255,17 +255,11 @@ chmod +x {{ .JenkinsHomePath }}/scripts/*.sh | ||||||
| {{- $installPluginsCommand := .InstallPluginsCommand }} | {{- $installPluginsCommand := .InstallPluginsCommand }} | ||||||
| 
 | 
 | ||||||
| echo "Installing plugins required by Operator - begin" | echo "Installing plugins required by Operator - begin" | ||||||
| {{- range $rootPluginName, $plugins := .OperatorPlugins }} | {{ $installPluginsCommand }} {{ range $index, $plugin := .BasePlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | ||||||
| echo "Installing required plugins for '{{ $rootPluginName }}'" |  | ||||||
| {{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }} |  | ||||||
| {{- end }} |  | ||||||
| echo "Installing plugins required by Operator - end" | echo "Installing plugins required by Operator - end" | ||||||
| 
 | 
 | ||||||
| echo "Installing plugins required by user - begin" | echo "Installing plugins required by user - begin" | ||||||
| {{- range $rootPluginName, $plugins := .UserPlugins }} | {{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | ||||||
| echo "Installing required plugins for '{{ $rootPluginName }}'" |  | ||||||
| {{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }} |  | ||||||
| {{- end }} |  | ||||||
| echo "Installing plugins required by user - end" | echo "Installing plugins required by user - end" | ||||||
| 
 | 
 | ||||||
| /sbin/tini -s -- /usr/local/bin/jenkins.sh | /sbin/tini -s -- /usr/local/bin/jenkins.sh | ||||||
|  | @ -284,12 +278,12 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { | ||||||
| 		InitConfigurationPath    string | 		InitConfigurationPath    string | ||||||
| 		InstallPluginsCommand    string | 		InstallPluginsCommand    string | ||||||
| 		JenkinsScriptsVolumePath string | 		JenkinsScriptsVolumePath string | ||||||
| 		OperatorPlugins          map[string][]string | 		BasePlugins              []v1alpha2.Plugin | ||||||
| 		UserPlugins              map[string][]string | 		UserPlugins              []v1alpha2.Plugin | ||||||
| 	}{ | 	}{ | ||||||
| 		JenkinsHomePath:          jenkinsHomePath, | 		JenkinsHomePath:          jenkinsHomePath, | ||||||
| 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath, | 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath, | ||||||
| 		OperatorPlugins:          jenkins.Spec.Master.OperatorPlugins, | 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | ||||||
| 		UserPlugins:              jenkins.Spec.Master.Plugins, | 		UserPlugins:              jenkins.Spec.Master.Plugins, | ||||||
| 		InstallPluginsCommand:    installPluginsCommand, | 		InstallPluginsCommand:    installPluginsCommand, | ||||||
| 		JenkinsScriptsVolumePath: jenkinsScriptsVolumePath, | 		JenkinsScriptsVolumePath: jenkinsScriptsVolumePath, | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) { | 	if !r.validatePlugins(plugins.BasePlugins(), jenkins.Spec.Master.BasePlugins, jenkins.Spec.Master.Plugins) { | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -215,36 +215,60 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool | ||||||
| 	return valid | 	return valid | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(pluginsWithVersionSlice ...map[string][]string) bool { | func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) bool { | ||||||
| 	valid := true | 	valid := true | ||||||
| 	allPlugins := map[plugins.Plugin][]plugins.Plugin{} | 	allPlugins := map[plugins.Plugin][]plugins.Plugin{} | ||||||
| 
 | 
 | ||||||
| 	for _, pluginsWithVersions := range pluginsWithVersionSlice { | 	for _, jenkinsPlugin := range basePlugins { | ||||||
| 		for rootPluginName, dependentPluginNames := range pluginsWithVersions { | 		plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version) | ||||||
| 			rootPlugin, err := plugins.New(rootPluginName) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid root plugin name '%s'", rootPluginName)) | 			r.logger.V(log.VWarn).Info(err.Error()) | ||||||
| 			valid = false | 			valid = false | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 			var dependentPlugins []plugins.Plugin | 		if plugin != nil { | ||||||
| 			for _, pluginName := range dependentPluginNames { | 			allPlugins[*plugin] = []plugins.Plugin{} | ||||||
| 				if p, err := plugins.New(pluginName); err != nil { | 		} | ||||||
| 					r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid dependent plugin name '%s' in root plugin '%s'", pluginName, rootPluginName)) | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, jenkinsPlugin := range userPlugins { | ||||||
|  | 		plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version) | ||||||
|  | 		if err != nil { | ||||||
|  | 			r.logger.V(log.VWarn).Info(err.Error()) | ||||||
| 			valid = false | 			valid = false | ||||||
| 				} else { | 		} | ||||||
| 					dependentPlugins = append(dependentPlugins, *p) | 
 | ||||||
|  | 		if plugin != nil { | ||||||
|  | 			allPlugins[*plugin] = []plugins.Plugin{} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 			if rootPlugin != nil { | 	if !plugins.VerifyDependencies(allPlugins) { | ||||||
| 				allPlugins[*rootPlugin] = dependentPlugins | 		valid = false | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if valid { | 	if !r.verifyBasePlugins(requiredBasePlugins, basePlugins) { | ||||||
| 		return plugins.VerifyDependencies(allPlugins) | 		valid = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugins []plugins.Plugin, basePlugins []v1alpha2.Plugin) bool { | ||||||
|  | 	valid := true | ||||||
|  | 
 | ||||||
|  | 	for _, requiredBasePlugin := range requiredBasePlugins { | ||||||
|  | 		found := false | ||||||
|  | 		for _, basePlugin := range basePlugins { | ||||||
|  | 			if requiredBasePlugin.Name == basePlugin.Name { | ||||||
|  | 				found = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !found { | ||||||
|  | 			valid = false | ||||||
|  | 			r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s' in spec.master.basePlugins", requiredBasePlugin.Name)) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return valid | 	return valid | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
|  | @ -16,62 +18,117 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestValidatePlugins(t *testing.T) { | func TestValidatePlugins(t *testing.T) { | ||||||
| 	baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | 	log.SetupLogger(true) | ||||||
|  | 	baseReconcileLoop := New(nil, nil, log.Log, | ||||||
| 		nil, false, false) | 		nil, false, false) | ||||||
| 	t.Run("happy", func(t *testing.T) { | 	t.Run("empty", func(t *testing.T) { | ||||||
| 		plugins := map[string][]string{ | 		var requiredBasePlugins []plugins.Plugin | ||||||
| 			"valid-plugin-name:1.0": { | 		var basePlugins []v1alpha2.Plugin | ||||||
| 				"valid-plugin-name:1.0", | 		var userPlugins []v1alpha2.Plugin | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, true, got) |  | ||||||
| 	}) |  | ||||||
| 	t.Run("fail, no version in plugin name", func(t *testing.T) { |  | ||||||
| 		plugins := map[string][]string{ |  | ||||||
| 			"invalid-plugin-name": { |  | ||||||
| 				"invalid-plugin-name", |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, false, got) |  | ||||||
| 	}) |  | ||||||
| 	t.Run("fail, no version in root plugin name", func(t *testing.T) { |  | ||||||
| 		plugins := map[string][]string{ |  | ||||||
| 			"invalid-plugin-name": { |  | ||||||
| 				"invalid-plugin-name:1.0", |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, false, got) |  | ||||||
| 	}) |  | ||||||
| 	t.Run("fail, no version in plugin name", func(t *testing.T) { |  | ||||||
| 		plugins := map[string][]string{ |  | ||||||
| 			"invalid-plugin-name:1.0": { |  | ||||||
| 				"invalid-plugin-name", |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, false, got) |  | ||||||
| 	}) |  | ||||||
| 	t.Run("happy", func(t *testing.T) { |  | ||||||
| 		plugins := map[string][]string{ |  | ||||||
| 			"valid-plugin-name:1.0": { |  | ||||||
| 				"valid-plugin-name:1.0", |  | ||||||
| 				"valid-plugin-name2:1.0", |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, true, got) |  | ||||||
| 	}) |  | ||||||
| 	t.Run("hapy", func(t *testing.T) { |  | ||||||
| 		plugins := map[string][]string{ |  | ||||||
| 			"valid-plugin-name:1.0": {}, |  | ||||||
| 		} |  | ||||||
| 		got := baseReconcileLoop.validatePlugins(plugins) |  | ||||||
| 		assert.Equal(t, true, got) |  | ||||||
| 	}) |  | ||||||
| 
 | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("valid user plugin", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		var basePlugins []v1alpha2.Plugin | ||||||
|  | 		userPlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid user plugin name", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		var basePlugins []v1alpha2.Plugin | ||||||
|  | 		userPlugins := []v1alpha2.Plugin{{Name: "INVALID", Version: "0.0.1"}} | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid user plugin version", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		var basePlugins []v1alpha2.Plugin | ||||||
|  | 		userPlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "invalid"}} | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("valid base plugin", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid base plugin name", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "INVALID", Version: "0.0.1"}} | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid base plugin version", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "invalid"}} | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("valid user and base plugin version", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		userPlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid user and base plugin version", func(t *testing.T) { | ||||||
|  | 		var requiredBasePlugins []plugins.Plugin | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		userPlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.2"}} | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("required base plugin set with the same version", func(t *testing.T) { | ||||||
|  | 		requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("required base plugin set with different version", func(t *testing.T) { | ||||||
|  | 		requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		basePlugins := []v1alpha2.Plugin{{Name: "simple-plugin", Version: "0.0.2"}} | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.True(t, got) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("missign required base plugin", func(t *testing.T) { | ||||||
|  | 		requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}} | ||||||
|  | 		var basePlugins []v1alpha2.Plugin | ||||||
|  | 		var userPlugins []v1alpha2.Plugin | ||||||
|  | 
 | ||||||
|  | 		got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins) | ||||||
|  | 
 | ||||||
|  | 		assert.False(t, got) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | func TestValidateJenkinsMasterPodEnvs(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -262,15 +262,15 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | ||||||
| 			FailureThreshold:    int32(12), | 			FailureThreshold:    int32(12), | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if len(jenkins.Spec.Master.OperatorPlugins) == 0 { | 	if len(jenkins.Spec.Master.BasePlugins) == 0 { | ||||||
| 		logger.Info("Setting default operator plugins") | 		logger.Info("Setting default operator plugins") | ||||||
| 		changed = true | 		changed = true | ||||||
| 		jenkins.Spec.Master.OperatorPlugins = plugins.BasePlugins() | 		jenkins.Spec.Master.BasePlugins = basePlugins() | ||||||
| 	} | 	} | ||||||
| 	if len(jenkins.Status.OperatorVersion) > 0 && version.Version != jenkins.Status.OperatorVersion { | 	if len(jenkins.Status.OperatorVersion) > 0 && version.Version != jenkins.Status.OperatorVersion { | ||||||
| 		logger.Info("Setting default operator plugins after Operator version change") | 		logger.Info("Setting default operator plugins after Operator version change") | ||||||
| 		changed = true | 		changed = true | ||||||
| 		jenkins.Spec.Master.OperatorPlugins = plugins.BasePlugins() | 		jenkins.Spec.Master.BasePlugins = basePlugins() | ||||||
| 	} | 	} | ||||||
| 	if len(jenkins.Status.OperatorVersion) == 0 { | 	if len(jenkins.Status.OperatorVersion) == 0 { | ||||||
| 		logger.Info("Setting operator version") | 		logger.Info("Setting operator version") | ||||||
|  | @ -279,7 +279,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | ||||||
| 	} | 	} | ||||||
| 	if len(jenkins.Spec.Master.Plugins) == 0 { | 	if len(jenkins.Spec.Master.Plugins) == 0 { | ||||||
| 		changed = true | 		changed = true | ||||||
| 		jenkins.Spec.Master.Plugins = map[string][]string{"simple-theme-plugin:0.5.1": {}} | 		jenkins.Spec.Master.Plugins = []v1alpha2.Plugin{{Name: "simple-theme-plugin", Version: "0.5.1"}} | ||||||
| 	} | 	} | ||||||
| 	if isResourceRequirementsNotSet(jenkins.Spec.Master.Resources) { | 	if isResourceRequirementsNotSet(jenkins.Spec.Master.Resources) { | ||||||
| 		logger.Info("Setting default Jenkins master container resource requirements") | 		logger.Info("Setting default Jenkins master container resource requirements") | ||||||
|  | @ -365,3 +365,10 @@ func isResourceRequirementsNotSet(requirements corev1.ResourceRequirements) bool | ||||||
| 
 | 
 | ||||||
| 	return !limitCPUSet || !limitMemorySet || !requestCPUSet || !requestMemporySet | 	return !limitCPUSet || !limitMemorySet || !requestCPUSet || !requestMemporySet | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func basePlugins() (result []v1alpha2.Plugin) { | ||||||
|  | 	for _, value := range plugins.BasePlugins() { | ||||||
|  | 		result = append(result, v1alpha2.Plugin{Name: value.Name, Version: value.Version}) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,180 +1,29 @@ | ||||||
| package plugins | package plugins | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	aceEditorPlugin                     = "ace-editor:1.1" | 	configurationAsCodePlugin           = "configuration-as-code:1.19" | ||||||
| 	apacheComponentsClientPlugin        = "apache-httpcomponents-client-4-api:4.5.5-3.0" | 	configurationAsCodeSupportPlugin    = "configuration-as-code-support:1.19" | ||||||
| 	authenticationTokensPlugin          = "authentication-tokens:1.3" |  | ||||||
| 	branchAPIPlugin                     = "branch-api:2.5.2" |  | ||||||
| 	cloudBeesFolderPlugin               = "cloudbees-folder:6.8" |  | ||||||
| 	configurationAsCodePlugin           = "configuration-as-code:1.17" |  | ||||||
| 	configurationAsCodeSupportPlugin    = "configuration-as-code-support:1.17" |  | ||||||
| 	credentialsBindingPlugin            = "credentials-binding:1.18" |  | ||||||
| 	credentialsPlugin                   = "credentials:2.1.19" |  | ||||||
| 	displayURLAPIPlugin                 = "display-url-api:2.3.1" |  | ||||||
| 	dockerCommonsPlugin                 = "docker-commons:1.15" |  | ||||||
| 	dockerWorkflowPlugin                = "docker-workflow:1.18" |  | ||||||
| 	durableTaskPlugin                   = "durable-task:1.29" |  | ||||||
| 	gitClientPlugin                     = "git-client:2.7.7" |  | ||||||
| 	gitPlugin                           = "git:3.10.0" | 	gitPlugin                           = "git:3.10.0" | ||||||
| 	gitServerPlugin                     = "git-server:1.7" |  | ||||||
| 	handlebarsPlugin                    = "handlebars:1.1.1" |  | ||||||
| 	jackson2ADIPlugin                   = "jackson2-api:2.9.9" |  | ||||||
| 	jobDslPlugin                        = "job-dsl:1.74" | 	jobDslPlugin                        = "job-dsl:1.74" | ||||||
| 	jqueryDetachedPlugin                = "jquery-detached:1.2.1" |  | ||||||
| 	jschPlugin                          = "jsch:0.1.55" |  | ||||||
| 	junitPlugin                         = "junit:1.28" |  | ||||||
| 	kubernetesCredentialsPlugin         = "kubernetes-credentials:0.4.0" |  | ||||||
| 	kubernetesCredentialsProviderPlugin = "kubernetes-credentials-provider:0.12.1" | 	kubernetesCredentialsProviderPlugin = "kubernetes-credentials-provider:0.12.1" | ||||||
| 	kubernetesPlugin                    = "kubernetes:1.15.5" | 	kubernetesPlugin                    = "kubernetes:1.15.7" | ||||||
| 	lockableResourcesPlugin             = "lockable-resources:2.5" |  | ||||||
| 	mailerPlugin                        = "mailer:1.23" |  | ||||||
| 	matrixProjectPlugin                 = "matrix-project:1.14" |  | ||||||
| 	momentjsPlugin                      = "momentjs:1.1.1" |  | ||||||
| 	pipelineBuildStepPlugin             = "pipeline-build-step:2.9" |  | ||||||
| 	pipelineGraphAnalysisPlugin         = "pipeline-graph-analysis:1.10" |  | ||||||
| 	pipelineInputStepPlugin             = "pipeline-input-step:2.10" |  | ||||||
| 	pipelineMilestoneStepPlugin         = "pipeline-milestone-step:1.3.1" |  | ||||||
| 	pipelineModelAPIPlugin              = "pipeline-model-api:1.3.8" |  | ||||||
| 	pipelineModelDeclarativeAgentPlugin = "pipeline-model-declarative-agent:1.1.1" |  | ||||||
| 	pipelineModelDefinitionPlugin       = "pipeline-model-definition:1.3.8" |  | ||||||
| 	pipelineModelExtensionsPlugin       = "pipeline-model-extensions:1.3.8" |  | ||||||
| 	pipelineRestAPIPlugin               = "pipeline-rest-api:2.11" |  | ||||||
| 	pipelineStageStepPlugin             = "pipeline-stage-step:2.3" |  | ||||||
| 	pipelineStageTagsMetadataPlugin     = "pipeline-stage-tags-metadata:1.3.8" |  | ||||||
| 	pipelineStageViewPlugin             = "pipeline-stage-view:2.11" |  | ||||||
| 	plainCredentialsPlugin              = "plain-credentials:1.5" |  | ||||||
| 	scmAPIPlugin                        = "scm-api:2.4.1" |  | ||||||
| 	scriptSecurityPlugin                = "script-security:1.59" |  | ||||||
| 	sshCredentialsPlugin                = "ssh-credentials:1.16" |  | ||||||
| 	structsPlugin                       = "structs:1.19" |  | ||||||
| 	variantPlugin                       = "variant:1.2" |  | ||||||
| 	workflowAggregatorPlugin            = "workflow-aggregator:2.6" | 	workflowAggregatorPlugin            = "workflow-aggregator:2.6" | ||||||
| 	workflowAPIPlugin                   = "workflow-api:2.34" |  | ||||||
| 	workflowBasicStepsPlugin            = "workflow-basic-steps:2.16" |  | ||||||
| 	workflowCpsGlobalLibPlugin          = "workflow-cps-global-lib:2.13" |  | ||||||
| 	workflowCpsPlugin                   = "workflow-cps:2.69" |  | ||||||
| 	workflowDurableTaskStepPlugin       = "workflow-durable-task-step:2.30" |  | ||||||
| 	workflowJobPlugin                   = "workflow-job:2.32" | 	workflowJobPlugin                   = "workflow-job:2.32" | ||||||
| 	workflowMultibranchPlugin           = "workflow-multibranch:2.21" |  | ||||||
| 	workflowSCMStepPlugin               = "workflow-scm-step:2.7" |  | ||||||
| 	workflowStepAPIPlugin               = "workflow-step-api:2.19" |  | ||||||
| 	workflowSupportPlugin               = "workflow-support:3.3" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // BasePluginsMap contains plugins to install by operator
 | // basePluginsList contains plugins to install by operator
 | ||||||
| var BasePluginsMap = map[string][]Plugin{ | var basePluginsList = []Plugin{ | ||||||
| 	Must(New(kubernetesPlugin)).String(): { | 	Must(New(kubernetesPlugin)), | ||||||
| 		Must(New(apacheComponentsClientPlugin)), |  | ||||||
| 		Must(New(cloudBeesFolderPlugin)), |  | ||||||
| 		Must(New(credentialsPlugin)), |  | ||||||
| 		Must(New(durableTaskPlugin)), |  | ||||||
| 		Must(New(jackson2ADIPlugin)), |  | ||||||
| 		Must(New(kubernetesCredentialsPlugin)), |  | ||||||
| 		Must(New(plainCredentialsPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 		Must(New(variantPlugin)), |  | ||||||
| 		Must(New(workflowStepAPIPlugin)), |  | ||||||
| 	}, |  | ||||||
| 	Must(New(workflowJobPlugin)).String(): { |  | ||||||
| 		Must(New(scmAPIPlugin)), |  | ||||||
| 		Must(New(scriptSecurityPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 		Must(New(workflowAPIPlugin)), |  | ||||||
| 		Must(New(workflowStepAPIPlugin)), |  | ||||||
| 		Must(New(workflowSupportPlugin)), |  | ||||||
| 	}, |  | ||||||
| 	Must(New(workflowAggregatorPlugin)).String(): { |  | ||||||
| 		Must(New(aceEditorPlugin)), |  | ||||||
| 		Must(New(apacheComponentsClientPlugin)), |  | ||||||
| 		Must(New(authenticationTokensPlugin)), |  | ||||||
| 		Must(New(branchAPIPlugin)), |  | ||||||
| 		Must(New(cloudBeesFolderPlugin)), |  | ||||||
| 		Must(New(credentialsBindingPlugin)), |  | ||||||
| 		Must(New(credentialsPlugin)), |  | ||||||
| 		Must(New(displayURLAPIPlugin)), |  | ||||||
| 		Must(New(dockerCommonsPlugin)), |  | ||||||
| 		Must(New(dockerWorkflowPlugin)), |  | ||||||
| 		Must(New(durableTaskPlugin)), |  | ||||||
| 		Must(New(gitClientPlugin)), |  | ||||||
| 		Must(New(gitServerPlugin)), |  | ||||||
| 		Must(New(handlebarsPlugin)), |  | ||||||
| 		Must(New(jackson2ADIPlugin)), |  | ||||||
| 		Must(New(jqueryDetachedPlugin)), |  | ||||||
| 		Must(New(jschPlugin)), |  | ||||||
| 		Must(New(junitPlugin)), |  | ||||||
| 		Must(New(lockableResourcesPlugin)), |  | ||||||
| 		Must(New(mailerPlugin)), |  | ||||||
| 		Must(New(matrixProjectPlugin)), |  | ||||||
| 		Must(New(momentjsPlugin)), |  | ||||||
| 		Must(New(pipelineBuildStepPlugin)), |  | ||||||
| 		Must(New(pipelineGraphAnalysisPlugin)), |  | ||||||
| 		Must(New(pipelineInputStepPlugin)), |  | ||||||
| 		Must(New(pipelineMilestoneStepPlugin)), |  | ||||||
| 		Must(New(pipelineModelAPIPlugin)), |  | ||||||
| 		Must(New(pipelineModelDeclarativeAgentPlugin)), |  | ||||||
| 		Must(New(pipelineModelDefinitionPlugin)), |  | ||||||
| 		Must(New(pipelineModelExtensionsPlugin)), |  | ||||||
| 		Must(New(pipelineRestAPIPlugin)), |  | ||||||
| 		Must(New(pipelineStageStepPlugin)), |  | ||||||
| 		Must(New(pipelineStageTagsMetadataPlugin)), |  | ||||||
| 		Must(New(pipelineStageViewPlugin)), |  | ||||||
| 		Must(New(plainCredentialsPlugin)), |  | ||||||
| 		Must(New(scmAPIPlugin)), |  | ||||||
| 		Must(New(scriptSecurityPlugin)), |  | ||||||
| 		Must(New(sshCredentialsPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 		Must(New(workflowAPIPlugin)), |  | ||||||
| 		Must(New(workflowBasicStepsPlugin)), |  | ||||||
| 		Must(New(workflowCpsGlobalLibPlugin)), |  | ||||||
| 		Must(New(workflowCpsPlugin)), |  | ||||||
| 		Must(New(workflowDurableTaskStepPlugin)), |  | ||||||
| 	Must(New(workflowJobPlugin)), | 	Must(New(workflowJobPlugin)), | ||||||
| 		Must(New(workflowMultibranchPlugin)), | 	Must(New(workflowAggregatorPlugin)), | ||||||
| 		Must(New(workflowSCMStepPlugin)), | 	Must(New(gitPlugin)), | ||||||
| 		Must(New(workflowStepAPIPlugin)), | 	Must(New(jobDslPlugin)), | ||||||
| 		Must(New(workflowSupportPlugin)), | 	Must(New(configurationAsCodePlugin)), | ||||||
| 	}, |  | ||||||
| 	Must(New(gitPlugin)).String(): { |  | ||||||
| 		Must(New(apacheComponentsClientPlugin)), |  | ||||||
| 		Must(New(credentialsPlugin)), |  | ||||||
| 		Must(New(displayURLAPIPlugin)), |  | ||||||
| 		Must(New(gitClientPlugin)), |  | ||||||
| 		Must(New(jschPlugin)), |  | ||||||
| 		Must(New(junitPlugin)), |  | ||||||
| 		Must(New(mailerPlugin)), |  | ||||||
| 		Must(New(matrixProjectPlugin)), |  | ||||||
| 		Must(New(scmAPIPlugin)), |  | ||||||
| 		Must(New(scriptSecurityPlugin)), |  | ||||||
| 		Must(New(sshCredentialsPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 		Must(New(workflowAPIPlugin)), |  | ||||||
| 		Must(New(workflowSCMStepPlugin)), |  | ||||||
| 		Must(New(workflowStepAPIPlugin)), |  | ||||||
| 	}, |  | ||||||
| 	Must(New(jobDslPlugin)).String(): { |  | ||||||
| 		Must(New(scriptSecurityPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 	}, |  | ||||||
| 	Must(New(configurationAsCodePlugin)).String(): { |  | ||||||
| 	Must(New(configurationAsCodeSupportPlugin)), | 	Must(New(configurationAsCodeSupportPlugin)), | ||||||
| 	}, | 	Must(New(kubernetesCredentialsProviderPlugin)), | ||||||
| 	Must(New(kubernetesCredentialsProviderPlugin)).String(): { |  | ||||||
| 		Must(New(credentialsPlugin)), |  | ||||||
| 		Must(New(structsPlugin)), |  | ||||||
| 		Must(New(variantPlugin)), |  | ||||||
| 	}, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // BasePlugins returns map of plugins to install by operator
 | // BasePlugins returns list of plugins to install by operator
 | ||||||
| func BasePlugins() (plugins map[string][]string) { | func BasePlugins() []Plugin { | ||||||
| 	plugins = map[string][]string{} | 	return basePluginsList | ||||||
| 
 |  | ||||||
| 	for rootPluginName, dependentPlugins := range BasePluginsMap { |  | ||||||
| 		plugins[rootPluginName] = []string{} |  | ||||||
| 		for _, pluginName := range dependentPlugins { |  | ||||||
| 			plugins[rootPluginName] = append(plugins[rootPluginName], pluginName.String()) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package plugins | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
|  | @ -20,18 +21,52 @@ func (p Plugin) String() string { | ||||||
| 	return fmt.Sprintf("%s:%s", p.Name, p.Version) | 	return fmt.Sprintf("%s:%s", p.Name, p.Version) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	namePattern    = regexp.MustCompile(`^[0-9a-z-]+$`) | ||||||
|  | 	versionPattern = regexp.MustCompile(`^[0-9\\.]+$`) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // New creates plugin from string, for example "name-of-plugin:0.0.1"
 | // New creates plugin from string, for example "name-of-plugin:0.0.1"
 | ||||||
| func New(nameWithVersion string) (*Plugin, error) { | func New(nameWithVersion string) (*Plugin, error) { | ||||||
| 	val := strings.SplitN(nameWithVersion, ":", 2) | 	val := strings.SplitN(nameWithVersion, ":", 2) | ||||||
| 	if val == nil || len(val) != 2 { | 	if val == nil || len(val) != 2 { | ||||||
| 		return nil, errors.Errorf("invalid plugin format '%s'", nameWithVersion) | 		return nil, errors.Errorf("invalid plugin format '%s'", nameWithVersion) | ||||||
| 	} | 	} | ||||||
|  | 	name := val[0] | ||||||
|  | 	version := val[1] | ||||||
|  | 
 | ||||||
|  | 	if err := validatePlugin(name, version); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return &Plugin{ | 	return &Plugin{ | ||||||
| 		Name:    val[0], | 		Name:    name, | ||||||
| 		Version: val[1], | 		Version: version, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // NewPlugin creates plugin from name and version, for example "name-of-plugin:0.0.1"
 | ||||||
|  | func NewPlugin(name, version string) (*Plugin, error) { | ||||||
|  | 	if err := validatePlugin(name, version); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Plugin{ | ||||||
|  | 		Name:    name, | ||||||
|  | 		Version: version, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func validatePlugin(name, version string) error { | ||||||
|  | 	if ok := namePattern.MatchString(name); !ok { | ||||||
|  | 		return errors.Errorf("invalid plugin name '%s:%s', must follow pattern '%s'", name, version, namePattern.String()) | ||||||
|  | 	} | ||||||
|  | 	if ok := versionPattern.MatchString(version); !ok { | ||||||
|  | 		return errors.Errorf("invalid plugin version '%s:%s', must follow pattern '%s'", name, version, versionPattern.String()) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Must returns plugin from pointer and throws panic when error is set
 | // Must returns plugin from pointer and throws panic when error is set
 | ||||||
| func Must(plugin *Plugin, err error) Plugin { | func Must(plugin *Plugin, err error) Plugin { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -228,25 +228,16 @@ func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	requiredPlugins := []map[string][]string{plugins.BasePlugins(), jenkins.Spec.Master.Plugins} | 	for _, basePlugin := range plugins.BasePlugins() { | ||||||
| 	for _, p := range requiredPlugins { | 		if found, ok := isPluginValid(installedPlugins, basePlugin); !ok { | ||||||
| 		for rootPluginName, dependentPlugins := range p { | 			t.Fatalf("Invalid plugin '%s', actual '%+v'", basePlugin, found) | ||||||
| 			rootPlugin, err := plugins.New(rootPluginName) |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 			if found, ok := isPluginValid(installedPlugins, *rootPlugin); !ok { |  | ||||||
| 				t.Fatalf("Invalid plugin '%s', actual '%+v'", rootPlugin, found) |  | ||||||
| 			} |  | ||||||
| 			for _, pluginName := range dependentPlugins { |  | ||||||
| 				plugin, err := plugins.New(pluginName) |  | ||||||
| 				if err != nil { |  | ||||||
| 					t.Fatal(err) |  | ||||||
| 				} |  | ||||||
| 				if found, ok := isPluginValid(installedPlugins, *plugin); !ok { |  | ||||||
| 					t.Fatalf("Invalid plugin '%s', actual '%+v'", rootPlugin, found) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, userPlugin := range jenkins.Spec.Master.Plugins { | ||||||
|  | 		plugin := plugins.Plugin{Name: userPlugin.Name, Version: userPlugin.Version} | ||||||
|  | 		if found, ok := isPluginValid(installedPlugins, plugin); !ok { | ||||||
|  | 			t.Fatalf("Invalid plugin '%s', actual '%+v'", plugin, found) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -113,9 +113,9 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S | ||||||
| 						Image: "envoyproxy/envoy-alpine", | 						Image: "envoyproxy/envoy-alpine", | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				Plugins: map[string][]string{ | 				Plugins: []v1alpha2.Plugin{ | ||||||
| 					"audit-trail:2.4":           {}, | 					{Name: "audit-trail:", Version: "2.4"}, | ||||||
| 					"simple-theme-plugin:0.5.1": {}, | 					{Name: "simple-theme-plugin", Version: "0.5.1"}, | ||||||
| 				}, | 				}, | ||||||
| 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | ||||||
| 				Volumes:      volumes, | 				Volumes:      volumes, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue