Install base plugins

This commit is contained in:
Tomasz Sęk
2018-12-25 17:10:56 +01:00
parent f3093cdbbc
commit af9f7f4e76
13 changed files with 1004 additions and 60 deletions
@@ -9,8 +9,10 @@ import (
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
jenkinsclient "github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/client"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base/resources"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin"
"github.com/VirtusLab/jenkins-operator/pkg/log"
"github.com/bndr/gojenkins"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@@ -22,6 +24,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
fetchAllPlugins = 1
)
// ReconcileJenkinsBaseConfiguration defines values required for Jenkins base configuration
type ReconcileJenkinsBaseConfiguration struct {
client client.Client
@@ -46,11 +52,6 @@ func New(client client.Client, scheme *runtime.Scheme, logger logr.Logger,
// Reconcile takes care of base configuration
func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (*reconcile.Result, jenkinsclient.Jenkins, error) {
if !r.validate(r.jenkins) {
r.logger.V(log.VWarn).Info("Please correct Jenkins CR")
return &reconcile.Result{}, nil, nil
}
metaObject := resources.NewResourceObjectMeta(r.jenkins)
if err := r.createOperatorCredentialsSecret(metaObject); err != nil {
@@ -97,9 +98,65 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (*reconcile.Result, jenk
}
r.logger.V(log.VDebug).Info("Jenkins API client set")
ok, err := r.verifyBasePlugins(jenkinsClient)
if err != nil {
return &reconcile.Result{}, nil, err
}
if !ok {
r.logger.V(log.VWarn).Info("Please correct Jenkins CR (spec.master.plugins)")
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(metaObject)
if err != nil {
return &reconcile.Result{}, nil, err
}
if err := r.client.Delete(context.TODO(), currentJenkinsMasterPod); err != nil {
return &reconcile.Result{}, nil, err
}
}
return nil, jenkinsClient, nil
}
func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) {
allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins)
if err != nil {
return false, err
}
var installedPlugins []string
for _, jenkinsPlugin := range allPluginsInJenkins.Raw.Plugins {
if !jenkinsPlugin.Deleted {
installedPlugins = append(installedPlugins, plugin.Plugin{Name: jenkinsPlugin.ShortName, Version: jenkinsPlugin.Version}.String())
}
}
r.logger.V(log.VDebug).Info(fmt.Sprintf("Installed plugins '%+v'", installedPlugins))
status := true
for rootPluginName, p := range plugin.BasePluginsMap {
rootPlugin, _ := plugin.New(rootPluginName)
if found, ok := isPluginInstalled(allPluginsInJenkins, *rootPlugin); !ok {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s', actual '%+v'", rootPlugin, 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
}
}
}
return status, nil
}
func isPluginInstalled(plugins *gojenkins.Plugins, requiredPlugin plugin.Plugin) (gojenkins.Plugin, bool) {
p := plugins.Contains(requiredPlugin.Name)
if p == nil {
return gojenkins.Plugin{}, false
}
return *p, p.Active && p.Enabled && !p.Deleted
}
func (r *ReconcileJenkinsBaseConfiguration) createOperatorCredentialsSecret(meta metav1.ObjectMeta) error {
found := &corev1.Secret{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: resources.GetOperatorCredentialsSecretName(r.jenkins), Namespace: r.jenkins.ObjectMeta.Namespace}, found)
@@ -11,6 +11,234 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const installPluginsCommand = "install-plugins.sh"
// bash scripts installs single jenkins plugin with concrete version
const installPluginsBashFmt = `#!/bin/bash -eu
# Resolve dependencies and download plugins given on the command line
#
# FROM jenkins
# RUN install-plugins.sh docker-slaves github-branch-source
set -o pipefail
REF_DIR=${REF:-%s/plugins}
FAILED="$REF_DIR/failed-plugins.txt"
. /usr/local/bin/jenkins-support
getLockFile() {
printf '%%s' "$REF_DIR/${1}.lock"
}
getArchiveFilename() {
printf '%%s' "$REF_DIR/${1}.jpi"
}
download() {
local plugin originalPlugin version lock ignoreLockFile
plugin="$1"
version="${2:-latest}"
ignoreLockFile="${3:-}"
lock="$(getLockFile "$plugin")"
if [[ $ignoreLockFile ]] || mkdir "$lock" &>/dev/null; then
if ! doDownload "$plugin" "$version"; then
# some plugin don't follow the rules about artifact ID
# typically: docker-plugin
originalPlugin="$plugin"
plugin="${plugin}-plugin"
if ! doDownload "$plugin" "$version"; then
echo "Failed to download plugin: $originalPlugin or $plugin" >&2
echo "Not downloaded: ${originalPlugin}" >> "$FAILED"
return 1
fi
fi
if ! checkIntegrity "$plugin"; then
echo "Downloaded file is not a valid ZIP: $(getArchiveFilename "$plugin")" >&2
echo "Download integrity: ${plugin}" >> "$FAILED"
return 1
fi
fi
}
doDownload() {
local plugin version url jpi
plugin="$1"
version="$2"
jpi="$(getArchiveFilename "$plugin")"
# If plugin already exists and is the same version do not download
if test -f "$jpi" && unzip -p "$jpi" META-INF/MANIFEST.MF | tr -d '\r' | grep "^Plugin-Version: ${version}$" > /dev/null; then
echo "Using provided plugin: $plugin"
return 0
fi
if [[ "$version" == "latest" && -n "$JENKINS_UC_LATEST" ]]; then
# If version-specific Update Center is available, which is the case for LTS versions,
# use it to resolve latest versions.
url="$JENKINS_UC_LATEST/latest/${plugin}.hpi"
elif [[ "$version" == "experimental" && -n "$JENKINS_UC_EXPERIMENTAL" ]]; then
# Download from the experimental update center
url="$JENKINS_UC_EXPERIMENTAL/latest/${plugin}.hpi"
elif [[ "$version" == incrementals* ]] ; then
# Download from Incrementals repo: https://jenkins.io/blog/2018/05/15/incremental-deployment/
# Example URL: https://repo.jenkins-ci.org/incrementals/org/jenkins-ci/plugins/workflow/workflow-support/2.19-rc289.d09828a05a74/workflow-support-2.19-rc289.d09828a05a74.hpi
local groupId incrementalsVersion
arrIN=(${version//;/ })
groupId=${arrIN[1]}
incrementalsVersion=${arrIN[2]}
url="${JENKINS_INCREMENTALS_REPO_MIRROR}/$(echo "${groupId}" | tr '.' '/')/${plugin}/${incrementalsVersion}/${plugin}-${incrementalsVersion}.hpi"
else
JENKINS_UC_DOWNLOAD=${JENKINS_UC_DOWNLOAD:-"$JENKINS_UC/download"}
url="$JENKINS_UC_DOWNLOAD/plugins/$plugin/$version/${plugin}.hpi"
fi
echo "Downloading plugin: $plugin from $url"
retry_command curl "${CURL_OPTIONS:--sSfL}" --connect-timeout "${CURL_CONNECTION_TIMEOUT:-20}" --retry "${CURL_RETRY:-5}" --retry-delay "${CURL_RETRY_DELAY:-0}" --retry-max-time "${CURL_RETRY_MAX_TIME:-60}" "$url" -o "$jpi"
return $?
}
checkIntegrity() {
local plugin jpi
plugin="$1"
jpi="$(getArchiveFilename "$plugin")"
unzip -t -qq "$jpi" >/dev/null
return $?
}
bundledPlugins() {
local JENKINS_WAR=/usr/share/jenkins/jenkins.war
if [ -f $JENKINS_WAR ]
then
TEMP_PLUGIN_DIR=/tmp/plugintemp.$$
for i in $(jar tf $JENKINS_WAR | grep -E '[^detached-]plugins.*\..pi' | sort)
do
rm -fr $TEMP_PLUGIN_DIR
mkdir -p $TEMP_PLUGIN_DIR
PLUGIN=$(basename "$i"|cut -f1 -d'.')
(cd $TEMP_PLUGIN_DIR;jar xf "$JENKINS_WAR" "$i";jar xvf "$TEMP_PLUGIN_DIR/$i" META-INF/MANIFEST.MF >/dev/null 2>&1)
VER=$(grep -E -i Plugin-Version "$TEMP_PLUGIN_DIR/META-INF/MANIFEST.MF"|cut -d: -f2|sed 's/ //')
echo "$PLUGIN:$VER"
done
rm -fr $TEMP_PLUGIN_DIR
else
rm -f "$TEMP_ALREADY_INSTALLED"
echo "ERROR file not found: $JENKINS_WAR"
exit 1
fi
}
versionFromPlugin() {
local plugin=$1
if [[ $plugin =~ .*:.* ]]; then
echo "${plugin##*:}"
else
echo "latest"
fi
}
installedPlugins() {
for f in "$REF_DIR"/*.jpi; do
echo "$(basename "$f" | sed -e 's/\.jpi//'):$(get_plugin_version "$f")"
done
}
jenkinsMajorMinorVersion() {
local JENKINS_WAR
JENKINS_WAR=/usr/share/jenkins/jenkins.war
if [[ -f "$JENKINS_WAR" ]]; then
local version major minor
version="$(java -jar /usr/share/jenkins/jenkins.war --version)"
major="$(echo "$version" | cut -d '.' -f 1)"
minor="$(echo "$version" | cut -d '.' -f 2)"
echo "$major.$minor"
else
echo "ERROR file not found: $JENKINS_WAR"
return 1
fi
}
main() {
local plugin pluginVersion jenkinsVersion
local plugins=()
mkdir -p "$REF_DIR" || exit 1
rm -f "$FAILED"
# Read plugins from stdin or from the command line arguments
if [[ ($# -eq 0) ]]; then
while read -r line || [ "$line" != "" ]; do
# Remove leading/trailing spaces, comments, and empty lines
plugin=$(echo "${line}" | tr -d '\r' | sed -e 's/^[ \t]*//g' -e 's/[ \t]*$//g' -e 's/[ \t]*#.*$//g' -e '/^[ \t]*$/d')
# Avoid adding empty plugin into array
if [ ${#plugin} -ne 0 ]; then
plugins+=("${plugin}")
fi
done
else
plugins=("$@")
fi
# Create lockfile manually before first run to make sure any explicit version set is used.
echo "Creating initial locks..."
for plugin in "${plugins[@]}"; do
mkdir "$(getLockFile "${plugin%%:*}")"
done
echo "Analyzing war..."
bundledPlugins="$(bundledPlugins)"
echo "Registering preinstalled plugins..."
installedPlugins="$(installedPlugins)"
# Check if there's a version-specific update center, which is the case for LTS versions
jenkinsVersion="$(jenkinsMajorMinorVersion)"
if curl -fsL -o /dev/null "$JENKINS_UC/$jenkinsVersion"; then
JENKINS_UC_LATEST="$JENKINS_UC/$jenkinsVersion"
echo "Using version-specific update center: $JENKINS_UC_LATEST..."
else
JENKINS_UC_LATEST=
fi
echo "Downloading plugins..."
for plugin in "${plugins[@]}"; do
pluginVersion=""
if [[ $plugin =~ .*:.* ]]; then
pluginVersion=$(versionFromPlugin "${plugin}")
plugin="${plugin%%:*}"
fi
download "$plugin" "$pluginVersion" "true" &
done
wait
echo
echo "WAR bundled plugins:"
echo "${bundledPlugins}"
echo
echo "Installed plugins:"
installedPlugins
if [[ -f $FAILED ]]; then
echo "Some plugins failed to download!" "$(<"$FAILED")" >&2
exit 1
fi
echo "Cleaning up locks"
rm -r "$REF_DIR"/*.lock
}
main "$@"
`
var initBashTemplate = template.Must(template.New(initScriptName).Parse(`#!/usr/bin/env bash
set -e
set -x
@@ -19,18 +247,19 @@ set -x
mkdir -p {{ .JenkinsHomePath }}/init.groovy.d
cp -n {{ .BaseConfigurationPath }}/*.groovy {{ .JenkinsHomePath }}/init.groovy.d
touch {{ .JenkinsHomePath }}/plugins.txt
cat > {{ .JenkinsHomePath }}/plugins.txt <<EOL
credentials:2.1.18
ssh-credentials:1.14
job-dsl:1.70
git:3.9.1
workflow-cps:2.61
workflow-job:2.30
workflow-aggregator:2.6
EOL
mkdir -p {{ .JenkinsHomePath }}/scripts
cp {{ .JenkinsScriptsVolumePath }}/*.sh {{ .JenkinsHomePath }}/scripts
chmod +x {{ .JenkinsHomePath }}/scripts/*.sh
/usr/local/bin/install-plugins.sh < {{ .JenkinsHomePath }}/plugins.txt
{{- $jenkinsHomePath := .JenkinsHomePath }}
{{- $installPluginsCommand := .InstallPluginsCommand }}
echo "Installing plugins - begin"
{{- range $rootPluginName, $plugins := .Plugins }}
echo "Installing required plugins for '{{ $rootPluginName }}'"
{{ $jenkinsHomePath }}/scripts/{{ $installPluginsCommand }} {{ $rootPluginName }} {{ range $index, $plugin := $plugins }}{{ . }} {{ end }}
{{- end }}
echo "Installing plugins - end"
/sbin/tini -s -- /usr/local/bin/jenkins.sh
`))
@@ -42,13 +271,19 @@ func buildConfigMapTypeMeta() metav1.TypeMeta {
}
}
func buildInitBashScript() (*string, error) {
func buildInitBashScript(pluginsToInstall map[string][]string) (*string, error) {
data := struct {
JenkinsHomePath string
BaseConfigurationPath string
JenkinsHomePath string
BaseConfigurationPath string
InstallPluginsCommand string
JenkinsScriptsVolumePath string
Plugins map[string][]string
}{
JenkinsHomePath: jenkinsHomePath,
BaseConfigurationPath: jenkinsBaseConfigurationVolumePath,
JenkinsHomePath: jenkinsHomePath,
BaseConfigurationPath: jenkinsBaseConfigurationVolumePath,
Plugins: pluginsToInstall,
InstallPluginsCommand: installPluginsCommand,
JenkinsScriptsVolumePath: jenkinsScriptsVolumePath,
}
output, err := render.Render(initBashTemplate, data)
@@ -67,7 +302,7 @@ func getScriptsConfigMapName(jenkins *virtuslabv1alpha1.Jenkins) string {
func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenkins) (*corev1.ConfigMap, error) {
meta.Name = getScriptsConfigMapName(jenkins)
initBashScript, err := buildInitBashScript()
initBashScript, err := buildInitBashScript(jenkins.Spec.Master.Plugins)
if err != nil {
return nil, err
}
@@ -76,7 +311,8 @@ func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *virtuslabv1alpha1.Jenk
TypeMeta: buildConfigMapTypeMeta(),
ObjectMeta: meta,
Data: map[string]string{
initScriptName: *initBashScript,
initScriptName: *initBashScript,
installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, jenkinsHomePath),
},
}, nil
}
@@ -1,9 +1,12 @@
package base
import (
"fmt"
"regexp"
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin"
"github.com/VirtusLab/jenkins-operator/pkg/log"
docker "github.com/docker/distribution/reference"
)
@@ -12,16 +15,52 @@ var (
dockerImageRegexp = regexp.MustCompile(`^` + docker.TagRegexp.String() + `$`)
)
func (r *ReconcileJenkinsBaseConfiguration) validate(jenkins *virtuslabv1alpha1.Jenkins) bool {
// Validate validates Jenkins CR Spec.master section
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *virtuslabv1alpha1.Jenkins) bool {
if jenkins.Spec.Master.Image == "" {
r.logger.V(0).Info("Image not set")
r.logger.V(log.VWarn).Info("Image not set")
return false
}
if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) {
r.logger.V(0).Info("Invalid image")
r.logger.V(log.VWarn).Info("Invalid image")
return false
}
if !r.validatePlugins(jenkins.Spec.Master.Plugins) {
return false
}
return true
}
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(plugins map[string][]string) bool {
valid := true
allPlugins := map[string][]plugin.Plugin{}
for rootPluginName, dependentPluginNames := range plugins {
if _, err := plugin.New(rootPluginName); err != nil {
r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid root plugin name '%s'", rootPluginName))
valid = false
}
dependentPlugins := []plugin.Plugin{}
for _, pluginName := range dependentPluginNames {
if p, err := plugin.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)
}
}
allPlugins[rootPluginName] = dependentPlugins
}
if valid {
return plugin.VerifyDependencies(allPlugins)
}
return valid
}
@@ -0,0 +1,58 @@
package base
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
func TestValidatePlugins(t *testing.T) {
data := []struct {
plugins map[string][]string
expectedResult bool
}{
{
plugins: map[string][]string{
"valid-plugin-name:1.0": {
"valid-plugin-name:1.0",
},
},
expectedResult: true,
},
{
plugins: map[string][]string{
"invalid-plugin-name": {
"invalid-plugin-name",
},
},
expectedResult: false,
},
{
plugins: map[string][]string{
"valid-plugin-name:1.0": {
"valid-plugin-name:1.0",
"valid-plugin-name2:1.0",
},
},
expectedResult: true,
},
{
plugins: map[string][]string{
"valid-plugin-name:1.0": {},
},
expectedResult: true,
},
}
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
nil, false, false)
for index, testingData := range data {
t.Run(fmt.Sprintf("Testing %d plugins set", index), func(t *testing.T) {
result := baseReconcileLoop.validatePlugins(testingData.plugins)
assert.Equal(t, testingData.expectedResult, result)
})
}
}
@@ -6,6 +6,7 @@ import (
virtuslabv1alpha1 "github.com/VirtusLab/jenkins-operator/pkg/apis/virtuslab/v1alpha1"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/base"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/configuration/user"
"github.com/VirtusLab/jenkins-operator/pkg/controller/jenkins/plugin"
"github.com/VirtusLab/jenkins-operator/pkg/log"
"github.com/go-logr/logr"
@@ -95,8 +96,17 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul
return reconcile.Result{}, err
}
err = r.setDefaults(jenkins)
if err != nil {
return reconcile.Result{}, err
}
// Reconcile base configuration
baseConfiguration := base.New(r.client, r.scheme, logger, jenkins, r.local, r.minikube)
if !baseConfiguration.Validate(jenkins) {
logger.V(log.VWarn).Info("Please correct Jenkins CR")
return reconcile.Result{}, nil // don't requeue
}
result, jenkinsClient, err := baseConfiguration.Reconcile()
if err != nil {
return reconcile.Result{}, err
@@ -137,3 +147,11 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul
func (r *ReconcileJenkins) buildLogger(jenkinsName string) logr.Logger {
return log.Log.WithValues("cr", jenkinsName)
}
func (r *ReconcileJenkins) setDefaults(jenkins *virtuslabv1alpha1.Jenkins) error {
if len(jenkins.Spec.Master.Plugins) == 0 {
jenkins.Spec.Master.Plugins = plugin.BasePlugins()
return r.client.Update(context.TODO(), jenkins)
}
return nil
}
@@ -0,0 +1,115 @@
package plugin
// 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
}
// BasePluginsMap contains plugins to install by operator
var BasePluginsMap = map[string][]Plugin{
Must(New("kubernetes:1.13.8")).String(): {
Must(New("apache-httpcomponents-client-4-api:4.5.5-3.0")),
Must(New("cloudbees-folder:6.7")),
Must(New("credentials:2.1.18")),
Must(New("durable-task:1.28")),
Must(New("jackson2-api:2.9.8")),
Must(New("kubernetes-credentials:0.4.0")),
Must(New("plain-credentials:1.5")),
Must(New("structs:1.17")),
Must(New("variant:1.1")),
Must(New("workflow-step-api:2.17")),
},
Must(New("workflow-job:2.31")).String(): {
Must(New("scm-api:2.3.0")),
Must(New("script-security:1.49")),
Must(New("structs:1.17")),
Must(New("workflow-api:2.33")),
Must(New("workflow-step-api:2.17")),
Must(New("workflow-support:2.24")),
},
Must(New("workflow-aggregator:2.6")).String(): {
Must(New("ace-editor:1.1")),
Must(New("apache-httpcomponents-client-4-api:4.5.5-3.0")),
Must(New("authentication-tokens:1.3")),
Must(New("branch-api:2.1.2")),
Must(New("cloudbees-folder:6.7")),
Must(New("credentials-binding:1.17")),
Must(New("credentials:2.1.18")),
Must(New("display-url-api:2.3.0")),
Must(New("docker-commons:1.13")),
Must(New("docker-workflow:1.17")),
Must(New("durable-task:1.28")),
Must(New("git-client:2.7.4")),
Must(New("git-server:1.7")),
Must(New("handlebars:1.1.1")),
Must(New("jackson2-api:2.9.8")),
Must(New("jquery-detached:1.2.1")),
Must(New("jsch:0.1.54.2")),
Must(New("junit:1.26.1")),
Must(New("lockable-resources:2.3")),
Must(New("mailer:1.22")),
Must(New("matrix-project:1.13")),
Must(New("momentjs:1.1.1")),
Must(New("pipeline-build-step:2.7")),
Must(New("pipeline-graph-analysis:1.9")),
Must(New("pipeline-input-step:2.9")),
Must(New("pipeline-milestone-step:1.3.1")),
Must(New("pipeline-model-api:1.3.4")),
Must(New("pipeline-model-declarative-agent:1.1.1")),
Must(New("pipeline-model-definition:1.3.4")),
Must(New("pipeline-model-extensions:1.3.4")),
Must(New("pipeline-rest-api:2.10")),
Must(New("pipeline-stage-step:2.3")),
Must(New("pipeline-stage-tags-metadata:1.3.4")),
Must(New("pipeline-stage-view:2.10")),
Must(New("plain-credentials:1.5")),
Must(New("scm-api:2.3.0")),
Must(New("script-security:1.49")),
Must(New("ssh-credentials:1.14")),
Must(New("structs:1.17")),
Must(New("workflow-api:2.33")),
Must(New("workflow-basic-steps:2.13")),
Must(New("workflow-cps-global-lib:2.12")),
Must(New("workflow-cps:2.61")),
Must(New("workflow-durable-task-step:2.27")),
Must(New("workflow-job:2.31")),
Must(New("workflow-multibranch:2.20")),
Must(New("workflow-scm-step:2.7")),
Must(New("workflow-step-api:2.17")),
Must(New("workflow-support:2.24")),
},
Must(New("git:3.9.1")).String(): {
Must(New("apache-httpcomponents-client-4-api:4.5.5-3.0")),
Must(New("credentials:2.1.18")),
Must(New("display-url-api:2.3.0")),
Must(New("git-client:2.7.4")),
Must(New("jsch:0.1.54.2")),
Must(New("junit:1.26.1")),
Must(New("mailer:1.22")),
Must(New("matrix-project:1.13")),
Must(New("scm-api:2.3.0")),
Must(New("script-security:1.49")),
Must(New("ssh-credentials:1.14")),
Must(New("structs:1.17")),
Must(New("workflow-api:2.33")),
Must(New("workflow-scm-step:2.7")),
Must(New("workflow-step-api:2.17")),
},
Must(New("job-dsl:1.70")).String(): {
Must(New("script-security:1.49")),
Must(New("structs:1.17")),
},
Must(New("jobConfigHistory:2.19")).String(): {},
Must(New("configuration-as-code:1.4")).String(): {
Must(New("configuration-as-code-support:1.4")),
},
}
+91
View File
@@ -0,0 +1,91 @@
package plugin
import (
"fmt"
"strings"
"github.com/VirtusLab/jenkins-operator/pkg/log"
)
// Plugin represents jenkins plugin
type Plugin struct {
Name string `json:"name"`
Version string `json:"version"`
rootPluginNameAndVersion string
}
func (p Plugin) String() string {
return fmt.Sprintf("%s:%s", p.Name, p.Version)
}
// 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, fmt.Errorf("invalid plugin format '%s'", nameWithVersion)
}
return &Plugin{
Name: val[0],
Version: val[1],
}, nil
}
// Must returns plugin from pointer and throws panic when error is set
func Must(plugin *Plugin, err error) Plugin {
if err != nil {
panic(err)
}
return *plugin
}
// VerifyDependencies checks if all plugins have compatible versions
func VerifyDependencies(values ...map[string][]Plugin) bool {
// key - plugin name, value array of versions
allPlugins := make(map[string][]Plugin)
valid := true
for _, value := range values {
if value != nil {
for rootPluginNameAndVersion, plugins := range value {
if rootPlugin, err := New(rootPluginNameAndVersion); err != nil {
valid = false
} else {
allPlugins[rootPlugin.Name] = append(allPlugins[rootPlugin.Name], Plugin{
Name: rootPlugin.Name,
Version: rootPlugin.Version,
rootPluginNameAndVersion: rootPluginNameAndVersion})
}
for _, plugin := range plugins {
allPlugins[plugin.Name] = append(allPlugins[plugin.Name], Plugin{
Name: plugin.Name,
Version: plugin.Version,
rootPluginNameAndVersion: rootPluginNameAndVersion})
}
}
}
}
for pluginName, versions := range allPlugins {
if len(versions) == 1 {
continue
}
for _, firstVersion := range versions {
for _, secondVersion := range versions {
if firstVersion.Version != secondVersion.Version {
log.Log.V(log.VWarn).Info(fmt.Sprintf("Plugin '%s' requires version '%s' but plugin '%s' requires '%s' for plugin '%s'",
firstVersion.rootPluginNameAndVersion,
firstVersion.Version,
secondVersion.rootPluginNameAndVersion,
secondVersion.Version,
pluginName,
))
valid = false
}
}
}
}
return valid
}
@@ -0,0 +1,90 @@
package plugin
import (
"fmt"
"github.com/VirtusLab/jenkins-operator/pkg/log"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVerifyDependencies(t *testing.T) {
data := []struct {
basePlugins map[string][]Plugin
extraPlugins map[string][]Plugin
expectedResult bool
}{
{
basePlugins: map[string][]Plugin{
"first-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
},
expectedResult: true,
},
{
basePlugins: map[string][]Plugin{
"first-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
"second-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
},
expectedResult: true,
},
{
basePlugins: map[string][]Plugin{
"first-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
},
extraPlugins: map[string][]Plugin{
"second-root-plugin:2.0.0": {
Must(New("first-plugin:0.0.1")),
},
},
expectedResult: true,
},
{
basePlugins: map[string][]Plugin{
"first-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
"first-root-plugin:2.0.0": {
Must(New("first-plugin:0.0.2")),
},
},
expectedResult: false,
},
{
basePlugins: map[string][]Plugin{
"first-root-plugin:1.0.0": {
Must(New("first-plugin:0.0.1")),
},
},
extraPlugins: map[string][]Plugin{
"first-root-plugin:2.0.0": {
Must(New("first-plugin:0.0.2")),
},
},
expectedResult: false,
},
{
basePlugins: map[string][]Plugin{
"invalid-plugin-name": {},
},
expectedResult: false,
},
}
debug := false
log.SetupLogger(&debug)
for index, testingData := range data {
t.Run(fmt.Sprintf("Testing %d data", index), func(t *testing.T) {
result := VerifyDependencies(testingData.basePlugins, testingData.extraPlugins)
assert.Equal(t, testingData.expectedResult, result)
})
}
}