Improve plugins management
This commit is contained in:
		
							parent
							
								
									8178e2315e
								
							
						
					
					
						commit
						e1aba3ed9f
					
				|  | @ -1,21 +1,5 @@ | |||
| // +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.
 | ||||
| 
 | ||||
| package v1alpha1 | ||||
|  | @ -49,6 +33,79 @@ func (in *Build) DeepCopy() *Build { | |||
| 	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.
 | ||||
| func (in *Jenkins) DeepCopyInto(out *Jenkins) { | ||||
| 	*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.
 | ||||
| func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||
| 	*out = *in | ||||
| 	if in.NodeSelector != nil { | ||||
| 		in, out := &in.NodeSelector, &out.NodeSelector | ||||
| 		*out = make(map[string]string, len(*in)) | ||||
| 		for key, val := range *in { | ||||
| 			(*out)[key] = val | ||||
| 		} | ||||
| 	} | ||||
| 	in.Container.DeepCopyInto(&out.Container) | ||||
| 	if in.Annotations != nil { | ||||
| 		in, out := &in.Annotations, &out.Annotations | ||||
| 		*out = make(map[string]string, len(*in)) | ||||
|  | @ -127,23 +178,26 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | |||
| 			(*out)[key] = val | ||||
| 		} | ||||
| 	} | ||||
| 	in.Resources.DeepCopyInto(&out.Resources) | ||||
| 	if in.Env != nil { | ||||
| 		in, out := &in.Env, &out.Env | ||||
| 		*out = make([]v1.EnvVar, len(*in)) | ||||
| 	if in.NodeSelector != nil { | ||||
| 		in, out := &in.NodeSelector, &out.NodeSelector | ||||
| 		*out = make(map[string]string, 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 { | ||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||
| 		} | ||||
| 	} | ||||
| 	if in.LivenessProbe != nil { | ||||
| 		in, out := &in.LivenessProbe, &out.LivenessProbe | ||||
| 		*out = new(v1.Probe) | ||||
| 		(*in).DeepCopyInto(*out) | ||||
| 	if in.Volumes != nil { | ||||
| 		in, out := &in.Volumes, &out.Volumes | ||||
| 		*out = make([]v1.Volume, len(*in)) | ||||
| 		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 { | ||||
| 		in, out := &in.OperatorPlugins, &out.OperatorPlugins | ||||
|  |  | |||
|  | @ -38,6 +38,12 @@ type Container struct { | |||
| 	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,
 | ||||
| // every single change requires Jenkins master pod restart
 | ||||
| type JenkinsMaster struct { | ||||
|  | @ -49,10 +55,10 @@ type JenkinsMaster struct { | |||
| 	Containers   []Container       `json:"containers,omitempty"` | ||||
| 	Volumes      []corev1.Volume   `json:"volumes,omitempty"` | ||||
| 
 | ||||
| 	// OperatorPlugins contains plugins required by operator
 | ||||
| 	OperatorPlugins map[string][]string `json:"basePlugins,omitempty"` | ||||
| 	// BasePlugins contains plugins required by operator
 | ||||
| 	BasePlugins []Plugin `json:"basePlugins,omitempty"` | ||||
| 	// 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
 | ||||
|  |  | |||
|  | @ -1,21 +1,5 @@ | |||
| // +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.
 | ||||
| 
 | ||||
| package v1alpha2 | ||||
|  | @ -49,6 +33,79 @@ func (in *Build) DeepCopy() *Build { | |||
| 	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.
 | ||||
| func (in *Jenkins) DeepCopyInto(out *Jenkins) { | ||||
| 	*out = *in | ||||
|  | @ -120,61 +177,37 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | |||
| 			(*out)[key] = val | ||||
| 		} | ||||
| 	} | ||||
| 	if in.Annotations != nil { | ||||
| 		in, out := &in.Annotations, &out.Annotations | ||||
| 	if in.NodeSelector != nil { | ||||
| 		in, out := &in.NodeSelector, &out.NodeSelector | ||||
| 		*out = make(map[string]string, len(*in)) | ||||
| 		for key, val := range *in { | ||||
| 			(*out)[key] = val | ||||
| 		} | ||||
| 	} | ||||
| 	in.Resources.DeepCopyInto(&out.Resources) | ||||
| 	if in.Env != nil { | ||||
| 		in, out := &in.Env, &out.Env | ||||
| 		*out = make([]v1.EnvVar, len(*in)) | ||||
| 	if in.Containers != nil { | ||||
| 		in, out := &in.Containers, &out.Containers | ||||
| 		*out = make([]Container, 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.Volumes != nil { | ||||
| 		in, out := &in.Volumes, &out.Volumes | ||||
| 		*out = make([]v1.Volume, len(*in)) | ||||
| 		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 { | ||||
| 		in, out := &in.OperatorPlugins, &out.OperatorPlugins | ||||
| 		*out = make(map[string][]string, 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)) | ||||
| 	if in.BasePlugins != nil { | ||||
| 		in, out := &in.BasePlugins, &out.BasePlugins | ||||
| 		*out = make([]Plugin, len(*in)) | ||||
| 		copy(*out, *in) | ||||
| 	} | ||||
| 			(*out)[key] = outVal | ||||
| 		} | ||||
| 	} | ||||
| 	if in.Plugins != nil { | ||||
| 		in, out := &in.Plugins, &out.Plugins | ||||
| 		*out = make(map[string][]string, 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)) | ||||
| 		*out = make([]Plugin, len(*in)) | ||||
| 		copy(*out, *in) | ||||
| 	} | ||||
| 			(*out)[key] = outVal | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
|  | @ -247,6 +280,22 @@ func (in *JenkinsStatus) DeepCopy() *JenkinsStatus { | |||
| 	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.
 | ||||
| func (in *SeedJob) DeepCopyInto(out *SeedJob) { | ||||
| 	*out = *in | ||||
|  |  | |||
|  | @ -106,7 +106,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki | |||
| 	} | ||||
| 	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 { | ||||
| 		return reconcile.Result{}, nil, err | ||||
| 	} | ||||
|  | @ -187,7 +187,7 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForVolumes() (reconcile.Result, | |||
| 	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) | ||||
| 	if err != nil { | ||||
| 		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)) | ||||
| 
 | ||||
| 	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 | ||||
| 	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 rootPluginName, p := range requiredPlugins { | ||||
| 			rootPlugin, _ := plugins.New(rootPluginName) | ||||
| 			if found, ok := isPluginInstalled(allPluginsInJenkins, *rootPlugin); !ok { | ||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", rootPlugin, found)) | ||||
| 		for _, plugin := range requiredPlugins { | ||||
| 			if found, ok := isPluginInstalled(allPluginsInJenkins, plugin); !ok { | ||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", plugin, found)) | ||||
| 				status = false | ||||
| 			} | ||||
| 			for _, requiredPlugin := range p { | ||||
| 				if found, ok := isPluginInstalled(allPluginsInJenkins, requiredPlugin); !ok { | ||||
| 					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)) | ||||
| 			if found, ok := isPluginVersionCompatible(allPluginsInJenkins, plugin); !ok { | ||||
| 				r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", plugin, found.Version)) | ||||
| 				status = false | ||||
| 			} | ||||
| 		} | ||||
|  | @ -249,7 +219,7 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsc | |||
| 	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) | ||||
| 	if p == nil { | ||||
| 		return gojenkins.Plugin{}, false | ||||
|  | @ -262,7 +232,7 @@ func isValidPlugin(plugin gojenkins.Plugin) bool { | |||
| 	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) | ||||
| 	if p == nil { | ||||
| 		return gojenkins.Plugin{}, false | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ import ( | |||
| 	"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/configuration/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||
| 
 | ||||
| 	"github.com/bndr/gojenkins" | ||||
|  | @ -167,19 +166,67 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | |||
| 		pluginsInJenkins := &gojenkins.Plugins{ | ||||
| 			Raw: &gojenkins.PluginResponse{}, | ||||
| 		} | ||||
| 		basePlugins := map[string][]plugins.Plugin{} | ||||
| 		ctrl := gomock.NewController(t) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		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.True(t, got) | ||||
| 	}) | ||||
| 	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{ | ||||
| 			logger:  log.Log, | ||||
| 			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) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		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) | ||||
|  | @ -214,7 +258,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | |||
| 		jenkins := &v1alpha2.Jenkins{ | ||||
| 			Spec: v1alpha2.JenkinsSpec{ | ||||
| 				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) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		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, plugin version doesn't matter for base plugins", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha2.Jenkins{} | ||||
| 	t.Run("happy, plugin version matter for base plugins", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha2.Jenkins{ | ||||
| 			Spec: v1alpha2.JenkinsSpec{ | ||||
| 				Master: v1alpha2.JenkinsMaster{ | ||||
| 					BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		} | ||||
| 		r := ReconcileJenkinsBaseConfiguration{ | ||||
| 			logger:  log.Log, | ||||
| 			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) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		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) | ||||
| 		assert.False(t, got) | ||||
| 	}) | ||||
| 	t.Run("plugin version matter for user plugins", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha2.Jenkins{ | ||||
| 			Spec: v1alpha2.JenkinsSpec{ | ||||
| 				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) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||
| 
 | ||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | ||||
| 		got, err := r.verifyPlugins(jenkinsClient) | ||||
| 
 | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.False(t, got) | ||||
| 	}) | ||||
| 	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{ | ||||
| 			logger:  log.Log, | ||||
| 			jenkins: jenkins, | ||||
|  | @ -325,15 +376,12 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | |||
| 				Plugins: []gojenkins.Plugin{}, | ||||
| 			}, | ||||
| 		} | ||||
| 		basePlugins := map[string][]plugins.Plugin{ | ||||
| 			"plugin-name:0.0.2": {}, | ||||
| 		} | ||||
| 		ctrl := gomock.NewController(t) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||
| 
 | ||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | ||||
| 		got, err := r.verifyPlugins(jenkinsClient) | ||||
| 
 | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.False(t, got) | ||||
|  | @ -342,7 +390,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { | |||
| 		jenkins := &v1alpha2.Jenkins{ | ||||
| 			Spec: v1alpha2.JenkinsSpec{ | ||||
| 				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{}, | ||||
| 			}, | ||||
| 		} | ||||
| 		basePlugins := map[string][]plugins.Plugin{} | ||||
| 		ctrl := gomock.NewController(t) | ||||
| 		defer ctrl.Finish() | ||||
| 		jenkinsClient := client.NewMockJenkins(ctrl) | ||||
| 		jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil) | ||||
| 
 | ||||
| 		got, err := r.verifyPlugins(jenkinsClient, basePlugins) | ||||
| 		got, err := r.verifyPlugins(jenkinsClient) | ||||
| 
 | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.False(t, got) | ||||
|  |  | |||
|  | @ -94,7 +94,9 @@ def remove = { list -> | |||
| remove(jenkins.getExtensionList(RootAction.class)) | ||||
| 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() | ||||
| ` | ||||
|  |  | |||
|  | @ -255,17 +255,11 @@ chmod +x {{ .JenkinsHomePath }}/scripts/*.sh | |||
| {{- $installPluginsCommand := .InstallPluginsCommand }} | ||||
| 
 | ||||
| echo "Installing plugins required by Operator - begin" | ||||
| {{- range $rootPluginName, $plugins := .OperatorPlugins }} | ||||
| echo "Installing required plugins for '{{ $rootPluginName }}'" | ||||
| {{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }} | ||||
| {{- end }} | ||||
| {{ $installPluginsCommand }} {{ range $index, $plugin := .BasePlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | ||||
| echo "Installing plugins required by Operator - end" | ||||
| 
 | ||||
| echo "Installing plugins required by user - begin" | ||||
| {{- range $rootPluginName, $plugins := .UserPlugins }} | ||||
| echo "Installing required plugins for '{{ $rootPluginName }}'" | ||||
| {{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }} | ||||
| {{- end }} | ||||
| {{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }} | ||||
| echo "Installing plugins required by user - end" | ||||
| 
 | ||||
| /sbin/tini -s -- /usr/local/bin/jenkins.sh | ||||
|  | @ -284,12 +278,12 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { | |||
| 		InitConfigurationPath    string | ||||
| 		InstallPluginsCommand    string | ||||
| 		JenkinsScriptsVolumePath string | ||||
| 		OperatorPlugins          map[string][]string | ||||
| 		UserPlugins              map[string][]string | ||||
| 		BasePlugins              []v1alpha2.Plugin | ||||
| 		UserPlugins              []v1alpha2.Plugin | ||||
| 	}{ | ||||
| 		JenkinsHomePath:          jenkinsHomePath, | ||||
| 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath, | ||||
| 		OperatorPlugins:          jenkins.Spec.Master.OperatorPlugins, | ||||
| 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | ||||
| 		UserPlugins:              jenkins.Spec.Master.Plugins, | ||||
| 		InstallPluginsCommand:    installPluginsCommand, | ||||
| 		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 | ||||
| 	} | ||||
| 
 | ||||
|  | @ -215,36 +215,60 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool | |||
| 	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 | ||||
| 	allPlugins := map[plugins.Plugin][]plugins.Plugin{} | ||||
| 
 | ||||
| 	for _, pluginsWithVersions := range pluginsWithVersionSlice { | ||||
| 		for rootPluginName, dependentPluginNames := range pluginsWithVersions { | ||||
| 			rootPlugin, err := plugins.New(rootPluginName) | ||||
| 	for _, jenkinsPlugin := range basePlugins { | ||||
| 		plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version) | ||||
| 		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 | ||||
| 		} | ||||
| 
 | ||||
| 			var dependentPlugins []plugins.Plugin | ||||
| 			for _, pluginName := range dependentPluginNames { | ||||
| 				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)) | ||||
| 		if plugin != nil { | ||||
| 			allPlugins[*plugin] = []plugins.Plugin{} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	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 | ||||
| 				} else { | ||||
| 					dependentPlugins = append(dependentPlugins, *p) | ||||
| 		} | ||||
| 
 | ||||
| 		if plugin != nil { | ||||
| 			allPlugins[*plugin] = []plugins.Plugin{} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 			if rootPlugin != nil { | ||||
| 				allPlugins[*rootPlugin] = dependentPlugins | ||||
| 			} | ||||
| 		} | ||||
| 	if !plugins.VerifyDependencies(allPlugins) { | ||||
| 		valid = false | ||||
| 	} | ||||
| 
 | ||||
| 	if valid { | ||||
| 		return plugins.VerifyDependencies(allPlugins) | ||||
| 	if !r.verifyBasePlugins(requiredBasePlugins, basePlugins) { | ||||
| 		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 | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ import ( | |||
| 
 | ||||
| 	"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/plugins" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||
| 
 | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"k8s.io/api/core/v1" | ||||
|  | @ -16,62 +18,117 @@ import ( | |||
| ) | ||||
| 
 | ||||
| func TestValidatePlugins(t *testing.T) { | ||||
| 	baseReconcileLoop := New(nil, nil, logf.ZapLogger(false), | ||||
| 	log.SetupLogger(true) | ||||
| 	baseReconcileLoop := New(nil, nil, log.Log, | ||||
| 		nil, false, false) | ||||
| 	t.Run("happy", func(t *testing.T) { | ||||
| 		plugins := map[string][]string{ | ||||
| 			"valid-plugin-name:1.0": { | ||||
| 				"valid-plugin-name:1.0", | ||||
| 			}, | ||||
| 		} | ||||
| 		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) | ||||
| 	}) | ||||
| 	t.Run("empty", func(t *testing.T) { | ||||
| 		var requiredBasePlugins []plugins.Plugin | ||||
| 		var basePlugins []v1alpha2.Plugin | ||||
| 		var userPlugins []v1alpha2.Plugin | ||||
| 
 | ||||
| 		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) { | ||||
|  |  | |||
|  | @ -262,15 +262,15 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins, logger logr.Lo | |||
| 			FailureThreshold:    int32(12), | ||||
| 		} | ||||
| 	} | ||||
| 	if len(jenkins.Spec.Master.OperatorPlugins) == 0 { | ||||
| 	if len(jenkins.Spec.Master.BasePlugins) == 0 { | ||||
| 		logger.Info("Setting default operator plugins") | ||||
| 		changed = true | ||||
| 		jenkins.Spec.Master.OperatorPlugins = plugins.BasePlugins() | ||||
| 		jenkins.Spec.Master.BasePlugins = basePlugins() | ||||
| 	} | ||||
| 	if len(jenkins.Status.OperatorVersion) > 0 && version.Version != jenkins.Status.OperatorVersion { | ||||
| 		logger.Info("Setting default operator plugins after Operator version change") | ||||
| 		changed = true | ||||
| 		jenkins.Spec.Master.OperatorPlugins = plugins.BasePlugins() | ||||
| 		jenkins.Spec.Master.BasePlugins = basePlugins() | ||||
| 	} | ||||
| 	if len(jenkins.Status.OperatorVersion) == 0 { | ||||
| 		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 { | ||||
| 		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) { | ||||
| 		logger.Info("Setting default Jenkins master container resource requirements") | ||||
|  | @ -365,3 +365,10 @@ func isResourceRequirementsNotSet(requirements corev1.ResourceRequirements) bool | |||
| 
 | ||||
| 	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 | ||||
| 
 | ||||
| const ( | ||||
| 	aceEditorPlugin                     = "ace-editor:1.1" | ||||
| 	apacheComponentsClientPlugin        = "apache-httpcomponents-client-4-api:4.5.5-3.0" | ||||
| 	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" | ||||
| 	configurationAsCodePlugin           = "configuration-as-code:1.19" | ||||
| 	configurationAsCodeSupportPlugin    = "configuration-as-code-support:1.19" | ||||
| 	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" | ||||
| 	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" | ||||
| 	kubernetesPlugin                    = "kubernetes:1.15.5" | ||||
| 	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" | ||||
| 	kubernetesPlugin                    = "kubernetes:1.15.7" | ||||
| 	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" | ||||
| 	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
 | ||||
| var BasePluginsMap = map[string][]Plugin{ | ||||
| 	Must(New(kubernetesPlugin)).String(): { | ||||
| 		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)), | ||||
| // basePluginsList contains plugins to install by operator
 | ||||
| var basePluginsList = []Plugin{ | ||||
| 	Must(New(kubernetesPlugin)), | ||||
| 	Must(New(workflowJobPlugin)), | ||||
| 		Must(New(workflowMultibranchPlugin)), | ||||
| 		Must(New(workflowSCMStepPlugin)), | ||||
| 		Must(New(workflowStepAPIPlugin)), | ||||
| 		Must(New(workflowSupportPlugin)), | ||||
| 	}, | ||||
| 	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(workflowAggregatorPlugin)), | ||||
| 	Must(New(gitPlugin)), | ||||
| 	Must(New(jobDslPlugin)), | ||||
| 	Must(New(configurationAsCodePlugin)), | ||||
| 	Must(New(configurationAsCodeSupportPlugin)), | ||||
| 	}, | ||||
| 	Must(New(kubernetesCredentialsProviderPlugin)).String(): { | ||||
| 		Must(New(credentialsPlugin)), | ||||
| 		Must(New(structsPlugin)), | ||||
| 		Must(New(variantPlugin)), | ||||
| 	}, | ||||
| 	Must(New(kubernetesCredentialsProviderPlugin)), | ||||
| } | ||||
| 
 | ||||
| // BasePlugins returns map of plugins to install by operator
 | ||||
| func BasePlugins() (plugins map[string][]string) { | ||||
| 	plugins = map[string][]string{} | ||||
| 
 | ||||
| 	for rootPluginName, dependentPlugins := range BasePluginsMap { | ||||
| 		plugins[rootPluginName] = []string{} | ||||
| 		for _, pluginName := range dependentPlugins { | ||||
| 			plugins[rootPluginName] = append(plugins[rootPluginName], pluginName.String()) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| // BasePlugins returns list of plugins to install by operator
 | ||||
| func BasePlugins() []Plugin { | ||||
| 	return basePluginsList | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package plugins | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"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) | ||||
| } | ||||
| 
 | ||||
| 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"
 | ||||
| func New(nameWithVersion string) (*Plugin, error) { | ||||
| 	val := strings.SplitN(nameWithVersion, ":", 2) | ||||
| 	if val == nil || len(val) != 2 { | ||||
| 		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{ | ||||
| 		Name:    val[0], | ||||
| 		Version: val[1], | ||||
| 		Name:    name, | ||||
| 		Version: version, | ||||
| 	}, 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
 | ||||
| func Must(plugin *Plugin, err error) Plugin { | ||||
| 	if err != nil { | ||||
|  |  | |||
|  | @ -228,25 +228,16 @@ func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v | |||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	requiredPlugins := []map[string][]string{plugins.BasePlugins(), jenkins.Spec.Master.Plugins} | ||||
| 	for _, p := range requiredPlugins { | ||||
| 		for rootPluginName, dependentPlugins := range p { | ||||
| 			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 _, basePlugin := range plugins.BasePlugins() { | ||||
| 		if found, ok := isPluginValid(installedPlugins, basePlugin); !ok { | ||||
| 			t.Fatalf("Invalid plugin '%s', actual '%+v'", basePlugin, 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", | ||||
| 					}, | ||||
| 				}, | ||||
| 				Plugins: map[string][]string{ | ||||
| 					"audit-trail:2.4":           {}, | ||||
| 					"simple-theme-plugin:0.5.1": {}, | ||||
| 				Plugins: []v1alpha2.Plugin{ | ||||
| 					{Name: "audit-trail:", Version: "2.4"}, | ||||
| 					{Name: "simple-theme-plugin", Version: "0.5.1"}, | ||||
| 				}, | ||||
| 				NodeSelector: map[string]string{"kubernetes.io/hostname": "minikube"}, | ||||
| 				Volumes:      volumes, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue