Improve plugins management

This commit is contained in:
Tomasz Sęk 2019-06-11 19:51:49 +02:00
parent 8178e2315e
commit e1aba3ed9f
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
14 changed files with 554 additions and 469 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -94,7 +94,9 @@ def remove = { list ->
remove(jenkins.getExtensionList(RootAction.class)) remove(jenkins.getExtensionList(RootAction.class))
remove(jenkins.actions) remove(jenkins.actions)
if (jenkins.getDescriptor("jenkins.CLI") != null) {
jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false) jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false)
}
jenkins.save() jenkins.save()
` `

View File

@ -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,

View File

@ -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

View File

@ -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) {

View File

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

View File

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

View File

@ -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 {

View File

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

View File

@ -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,