Improve plugins management

This commit is contained in:
Tomasz Sęk
2019-06-11 19:51:49 +02:00
parent 8178e2315e
commit e1aba3ed9f
14 changed files with 554 additions and 469 deletions
@@ -106,7 +106,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki
}
r.logger.V(log.VDebug).Info("Jenkins API client set")
ok, err := r.verifyPlugins(jenkinsClient, plugins.BasePluginsMap)
ok, err := r.verifyPlugins(jenkinsClient)
if err != nil {
return reconcile.Result{}, nil, err
}
@@ -187,7 +187,7 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForVolumes() (reconcile.Result,
return reconcile.Result{}, nil
}
func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsclient.Jenkins, basePlugins map[string][]plugins.Plugin) (bool, error) {
func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) {
allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins)
if err != nil {
return false, stackerr.WithStack(err)
@@ -201,46 +201,16 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsc
}
r.logger.V(log.VDebug).Info(fmt.Sprintf("Installed plugins '%+v'", installedPlugins))
userPlugins := map[string][]plugins.Plugin{}
for rootPlugin, dependentPluginNames := range r.jenkins.Spec.Master.Plugins {
var dependentPlugins []plugins.Plugin
for _, pluginNameWithVersion := range dependentPluginNames {
plugin, err := plugins.New(pluginNameWithVersion)
if err != nil {
return false, err
}
dependentPlugins = append(dependentPlugins, *plugin)
}
userPlugins[rootPlugin] = dependentPlugins
}
status := true
allRequiredPlugins := []map[string][]plugins.Plugin{basePlugins, userPlugins}
allRequiredPlugins := [][]v1alpha2.Plugin{r.jenkins.Spec.Master.BasePlugins, r.jenkins.Spec.Master.Plugins}
for _, requiredPlugins := range allRequiredPlugins {
for rootPluginName, p := range requiredPlugins {
rootPlugin, _ := plugins.New(rootPluginName)
if found, ok := isPluginInstalled(allPluginsInJenkins, *rootPlugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", rootPlugin, found))
for _, plugin := range requiredPlugins {
if found, ok := isPluginInstalled(allPluginsInJenkins, plugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", plugin, found))
status = false
}
for _, requiredPlugin := range p {
if found, ok := isPluginInstalled(allPluginsInJenkins, requiredPlugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", requiredPlugin, found))
status = false
}
}
}
}
for rootPluginName, p := range userPlugins {
rootPlugin, _ := plugins.New(rootPluginName)
if found, ok := isPluginVersionCompatible(allPluginsInJenkins, *rootPlugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", rootPlugin, found.Version))
status = false
}
for _, requiredPlugin := range p {
if found, ok := isPluginInstalled(allPluginsInJenkins, requiredPlugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", requiredPlugin, found.Version))
if found, ok := isPluginVersionCompatible(allPluginsInJenkins, plugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Incompatible plugin '%s' version, actual '%+v'", plugin, found.Version))
status = false
}
}
@@ -249,7 +219,7 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsc
return status, nil
}
func isPluginVersionCompatible(plugins *gojenkins.Plugins, plugin plugins.Plugin) (gojenkins.Plugin, bool) {
func isPluginVersionCompatible(plugins *gojenkins.Plugins, plugin v1alpha2.Plugin) (gojenkins.Plugin, bool) {
p := plugins.Contains(plugin.Name)
if p == nil {
return gojenkins.Plugin{}, false
@@ -262,7 +232,7 @@ func isValidPlugin(plugin gojenkins.Plugin) bool {
return plugin.Active && plugin.Enabled && !plugin.Deleted
}
func isPluginInstalled(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (gojenkins.Plugin, bool) {
func isPluginInstalled(plugins *gojenkins.Plugins, requiredPlugin v1alpha2.Plugin) (gojenkins.Plugin, bool) {
p := plugins.Contains(requiredPlugin.Name)
if p == nil {
return gojenkins.Plugin{}, false
@@ -6,7 +6,6 @@ import (
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
"github.com/jenkinsci/kubernetes-operator/pkg/log"
"github.com/bndr/gojenkins"
@@ -167,19 +166,67 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) {
pluginsInJenkins := &gojenkins.Plugins{
Raw: &gojenkins.PluginResponse{},
}
basePlugins := map[string][]plugins.Plugin{}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil)
got, err := r.verifyPlugins(jenkinsClient, basePlugins)
got, err := r.verifyPlugins(jenkinsClient)
assert.NoError(t, err)
assert.True(t, got)
})
t.Run("happy, not empty base and user plugins", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name1", Version: "0.0.1"}},
Plugins: []v1alpha2.Plugin{{Name: "plugin-name2", Version: "0.0.1"}},
},
},
}
r := ReconcileJenkinsBaseConfiguration{
logger: log.Log,
jenkins: jenkins,
}
pluginsInJenkins := &gojenkins.Plugins{
Raw: &gojenkins.PluginResponse{
Plugins: []gojenkins.Plugin{
{
ShortName: "plugin-name1",
Active: true,
Deleted: false,
Enabled: true,
Version: "0.0.1",
},
{
ShortName: "plugin-name2",
Active: true,
Deleted: false,
Enabled: true,
Version: "0.0.1",
},
},
},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil)
got, err := r.verifyPlugins(jenkinsClient)
assert.NoError(t, err)
assert.True(t, got)
})
t.Run("happy, not empty base and empty user plugins", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{}
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}},
},
},
}
r := ReconcileJenkinsBaseConfiguration{
logger: log.Log,
jenkins: jenkins,
@@ -197,15 +244,12 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) {
},
},
}
basePlugins := map[string][]plugins.Plugin{
"plugin-name:0.0.1": {},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil)
got, err := r.verifyPlugins(jenkinsClient, basePlugins)
got, err := r.verifyPlugins(jenkinsClient)
assert.NoError(t, err)
assert.True(t, got)
@@ -214,7 +258,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
Plugins: map[string][]string{"plugin-name:0.0.1": {}},
Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}},
},
},
}
@@ -235,19 +279,24 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) {
},
},
}
basePlugins := map[string][]plugins.Plugin{}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil)
got, err := r.verifyPlugins(jenkinsClient, basePlugins)
got, err := r.verifyPlugins(jenkinsClient)
assert.NoError(t, err)
assert.True(t, got)
})
t.Run("happy, plugin version doesn't matter for base plugins", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{}
t.Run("happy, plugin version matter for base plugins", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
BasePlugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.1"}},
},
},
}
r := ReconcileJenkinsBaseConfiguration{
logger: log.Log,
jenkins: jenkins,
@@ -265,24 +314,21 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) {
},
},
}
basePlugins := map[string][]plugins.Plugin{
"plugin-name:0.0.1": {},
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
jenkinsClient := client.NewMockJenkins(ctrl)
jenkinsClient.EXPECT().GetPlugins(fetchAllPlugins).Return(pluginsInJenkins, nil)
got, err := r.verifyPlugins(jenkinsClient, basePlugins)
got, err := r.verifyPlugins(jenkinsClient)
assert.NoError(t, err)
assert.True(t, got)
assert.False(t, got)
})
t.Run("plugin version matter for user plugins", func(t *testing.T) {
jenkins := &v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
Master: v1alpha2.JenkinsMaster{
Plugins: map[string][]string{"plugin-name:0.0.2": {}},
Plugins: []v1alpha2.Plugin{{Name: "plugin-name", Version: "0.0.2"}},
},
},
}
@@ -303,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)
@@ -94,7 +94,9 @@ def remove = { list ->
remove(jenkins.getExtensionList(RootAction.class))
remove(jenkins.actions)
jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false)
if (jenkins.getDescriptor("jenkins.CLI") != null) {
jenkins.getDescriptor("jenkins.CLI").get().setEnabled(false)
}
jenkins.save()
`
@@ -255,17 +255,11 @@ chmod +x {{ .JenkinsHomePath }}/scripts/*.sh
{{- $installPluginsCommand := .InstallPluginsCommand }}
echo "Installing plugins required by Operator - begin"
{{- range $rootPluginName, $plugins := .OperatorPlugins }}
echo "Installing required plugins for '{{ $rootPluginName }}'"
{{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }}
{{- end }}
{{ $installPluginsCommand }} {{ range $index, $plugin := .BasePlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }}
echo "Installing plugins required by Operator - end"
echo "Installing plugins required by user - begin"
{{- range $rootPluginName, $plugins := .UserPlugins }}
echo "Installing required plugins for '{{ $rootPluginName }}'"
{{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }}
{{- end }}
{{ $installPluginsCommand }} {{ range $index, $plugin := .UserPlugins }}{{ $plugin.Name }}:{{ $plugin.Version }} {{ end }}
echo "Installing plugins required by user - end"
/sbin/tini -s -- /usr/local/bin/jenkins.sh
@@ -284,12 +278,12 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) {
InitConfigurationPath string
InstallPluginsCommand string
JenkinsScriptsVolumePath string
OperatorPlugins map[string][]string
UserPlugins map[string][]string
BasePlugins []v1alpha2.Plugin
UserPlugins []v1alpha2.Plugin
}{
JenkinsHomePath: jenkinsHomePath,
InitConfigurationPath: jenkinsInitConfigurationVolumePath,
OperatorPlugins: jenkins.Spec.Master.OperatorPlugins,
BasePlugins: jenkins.Spec.Master.BasePlugins,
UserPlugins: jenkins.Spec.Master.Plugins,
InstallPluginsCommand: installPluginsCommand,
JenkinsScriptsVolumePath: jenkinsScriptsVolumePath,
@@ -42,7 +42,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins)
}
}
if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) {
if !r.validatePlugins(plugins.BasePlugins(), jenkins.Spec.Master.BasePlugins, jenkins.Spec.Master.Plugins) {
return false, nil
}
@@ -215,36 +215,60 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool
return valid
}
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(pluginsWithVersionSlice ...map[string][]string) bool {
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) bool {
valid := true
allPlugins := map[plugins.Plugin][]plugins.Plugin{}
for _, pluginsWithVersions := range pluginsWithVersionSlice {
for rootPluginName, dependentPluginNames := range pluginsWithVersions {
rootPlugin, err := plugins.New(rootPluginName)
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
@@ -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) {
+11 -4
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
}
+16 -167
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
}
+37 -2
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 {