350 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| package resources
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"text/template"
 | |
| 
 | |
| 	"github.com/jenkinsci/kubernetes-operator/internal/render"
 | |
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
 | |
| 	"github.com/jenkinsci/kubernetes-operator/pkg/constants"
 | |
| 
 | |
| 	corev1 "k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| )
 | |
| 
 | |
| const installPluginsCommand = "install-plugins.sh"
 | |
| 
 | |
| // bash scripts installs single jenkins plugin with specific 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
 | |
| 
 | |
| if [ "${DEBUG_JENKINS_OPERATOR}" == "true" ]; then
 | |
| 	echo "Printing debug messages - begin"
 | |
| 	id
 | |
| 	env
 | |
| 	ls -la {{ .JenkinsHomePath }}
 | |
| 	echo "Printing debug messages - end"
 | |
| else
 | |
|     echo "To print debug messages set environment variable 'DEBUG_JENKINS_OPERATOR' to 'true'"
 | |
| fi
 | |
| 
 | |
| # https://wiki.jenkins.io/display/JENKINS/Post-initialization+script
 | |
| mkdir -p {{ .JenkinsHomePath }}/init.groovy.d
 | |
| cp -n {{ .InitConfigurationPath }}/*.groovy {{ .JenkinsHomePath }}/init.groovy.d
 | |
| 
 | |
| mkdir -p {{ .JenkinsHomePath }}/scripts
 | |
| cp {{ .JenkinsScriptsVolumePath }}/*.sh {{ .JenkinsHomePath }}/scripts
 | |
| chmod +x {{ .JenkinsHomePath }}/scripts/*.sh
 | |
| 
 | |
| {{- $jenkinsHomePath := .JenkinsHomePath }}
 | |
| {{- $installPluginsCommand := .InstallPluginsCommand }}
 | |
| 
 | |
| echo "Installing plugins required by Operator - begin"
 | |
| cat > {{ .JenkinsHomePath }}/base-plugins << EOF
 | |
| {{ range $index, $plugin := .BasePlugins }}
 | |
| {{ $plugin.Name }}:{{ $plugin.Version }}{{if $plugin.DownloadURL}}:{{ $plugin.DownloadURL }}{{end}}
 | |
| {{ end }}
 | |
| EOF
 | |
| 
 | |
| if [[ -z "${OPENSHIFT_JENKINS_IMAGE_VERSION}" ]]; then
 | |
|   {{ $installPluginsCommand }} < {{ .JenkinsHomePath }}/base-plugins
 | |
| else
 | |
|   {{ $installPluginsCommand }} {{ .JenkinsHomePath }}/base-plugins
 | |
| fi
 | |
| echo "Installing plugins required by Operator - end"
 | |
| 
 | |
| echo "Installing plugins required by user - begin"
 | |
| cat > {{ .JenkinsHomePath }}/user-plugins << EOF
 | |
| {{ range $index, $plugin := .UserPlugins }}
 | |
| {{ $plugin.Name }}:{{ $plugin.Version }}{{if $plugin.DownloadURL}}:{{ $plugin.DownloadURL }}{{end}}
 | |
| {{ end }}
 | |
| EOF
 | |
| if [[ -z "${OPENSHIFT_JENKINS_IMAGE_VERSION}" ]]; then
 | |
|   {{ $installPluginsCommand }} < {{ .JenkinsHomePath }}/user-plugins
 | |
| else
 | |
|   {{ $installPluginsCommand }} {{ .JenkinsHomePath }}/user-plugins
 | |
| fi
 | |
| echo "Installing plugins required by user - end"
 | |
| `))
 | |
| 
 | |
| func buildConfigMapTypeMeta() metav1.TypeMeta {
 | |
| 	return metav1.TypeMeta{
 | |
| 		Kind:       "ConfigMap",
 | |
| 		APIVersion: "v1",
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) {
 | |
| 	data := struct {
 | |
| 		JenkinsHomePath          string
 | |
| 		InitConfigurationPath    string
 | |
| 		InstallPluginsCommand    string
 | |
| 		JenkinsScriptsVolumePath string
 | |
| 		BasePlugins              []v1alpha2.Plugin
 | |
| 		UserPlugins              []v1alpha2.Plugin
 | |
| 	}{
 | |
| 		JenkinsHomePath:          getJenkinsHomePath(jenkins),
 | |
| 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath,
 | |
| 		BasePlugins:              jenkins.Spec.Master.BasePlugins,
 | |
| 		UserPlugins:              jenkins.Spec.Master.Plugins,
 | |
| 		InstallPluginsCommand:    installPluginsCommand,
 | |
| 		JenkinsScriptsVolumePath: JenkinsScriptsVolumePath,
 | |
| 	}
 | |
| 
 | |
| 	output, err := render.Render(initBashTemplate, data)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &output, nil
 | |
| }
 | |
| 
 | |
| func getScriptsConfigMapName(jenkins *v1alpha2.Jenkins) string {
 | |
| 	return fmt.Sprintf("%s-scripts-%s", constants.OperatorName, jenkins.ObjectMeta.Name)
 | |
| }
 | |
| 
 | |
| // NewScriptsConfigMap builds Kubernetes config map used to store scripts
 | |
| func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins) (*corev1.ConfigMap, error) {
 | |
| 	meta.Name = getScriptsConfigMapName(jenkins)
 | |
| 
 | |
| 	initBashScript, err := buildInitBashScript(jenkins)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &corev1.ConfigMap{
 | |
| 		TypeMeta:   buildConfigMapTypeMeta(),
 | |
| 		ObjectMeta: meta,
 | |
| 		Data: map[string]string{
 | |
| 			InitScriptName:        *initBashScript,
 | |
| 			installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, getJenkinsHomePath(jenkins)),
 | |
| 		},
 | |
| 	}, nil
 | |
| }
 |