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
/*
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.ReadinessProbe != nil {
in, out := &in.ReadinessProbe, &out.ReadinessProbe
*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.OperatorPlugins != nil {
in, out := &in.OperatorPlugins, &out.OperatorPlugins

View File

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

View File

@ -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,60 +177,36 @@ 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.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))
copy(*out, *in)
}
(*out)[key] = outVal
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.BasePlugins != nil {
in, out := &in.BasePlugins, &out.BasePlugins
*out = make([]Plugin, len(*in))
copy(*out, *in)
}
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))
copy(*out, *in)
}
(*out)[key] = outVal
}
*out = make([]Plugin, len(*in))
copy(*out, *in)
}
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

View File

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

View File

@ -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,46 +349,21 @@ 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{}
r := ReconcileJenkinsBaseConfiguration{
logger: log.Log,
jenkins: jenkins,
}
pluginsInJenkins := &gojenkins.Plugins{
Raw: &gojenkins.PluginResponse{
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)
assert.NoError(t, err)
assert.False(t, got)
})
t.Run("missing user plugin", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
Plugins: map[string][]string{"plugin-name:0.0.2": {}},
BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}},
},
},
}
@ -355,13 +376,39 @@ 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)
})
t.Run("missing user plugin", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}},
},
},
}
r := ReconcileJenkinsBaseConfiguration{
logger: log.Log,
jenkins: jenkins,
}
pluginsInJenkins := &gojenkins.Plugins{
Raw: &gojenkins.PluginResponse{
Plugins: []gojenkins.Plugin{},
},
}
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.False(t, got)

View File

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

View File

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

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
}
@ -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)
if err != nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid root plugin name '%s'", rootPluginName))
valid = false
}
for _, jenkinsPlugin := range basePlugins {
plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version)
if err != nil {
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))
valid = false
} else {
dependentPlugins = append(dependentPlugins, *p)
}
}
if rootPlugin != nil {
allPlugins[*rootPlugin] = dependentPlugins
}
if plugin != nil {
allPlugins[*plugin] = []plugins.Plugin{}
}
}
if valid {
return plugins.VerifyDependencies(allPlugins)
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
}
if plugin != nil {
allPlugins[*plugin] = []plugins.Plugin{}
}
}
if !plugins.VerifyDependencies(allPlugins) {
valid = false
}
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

View File

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

View File

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

View File

@ -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)),
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(configurationAsCodeSupportPlugin)),
},
Must(New(kubernetesCredentialsProviderPlugin)).String(): {
Must(New(credentialsPlugin)),
Must(New(structsPlugin)),
Must(New(variantPlugin)),
},
// basePluginsList contains plugins to install by operator
var basePluginsList = []Plugin{
Must(New(kubernetesPlugin)),
Must(New(workflowJobPlugin)),
Must(New(workflowAggregatorPlugin)),
Must(New(gitPlugin)),
Must(New(jobDslPlugin)),
Must(New(configurationAsCodePlugin)),
Must(New(configurationAsCodeSupportPlugin)),
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
}

View File

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

View File

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

View File

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