diff --git a/ansible/roles/nomad_client/templates/nomad.hcl.j2 b/ansible/roles/nomad_client/templates/nomad.hcl.j2 index ea13fd8..a84671e 100644 --- a/ansible/roles/nomad_client/templates/nomad.hcl.j2 +++ b/ansible/roles/nomad_client/templates/nomad.hcl.j2 @@ -3,6 +3,9 @@ data_dir = "/opt/nomad" client { enabled = true + options { + "docker.volumes.enabled" = true + } meta { {% for nomad_meta in nomad_meta_values %} "{{ nomad_meta.name }}" = "{{ nomad_meta.value }}" diff --git a/docker/jenkins/Dockerfile b/docker/jenkins/Dockerfile new file mode 100644 index 0000000..8150401 --- /dev/null +++ b/docker/jenkins/Dockerfile @@ -0,0 +1,77 @@ +FROM adoptopenjdk/openjdk11:alpine +# add ca-certificates package +RUN apk add --no-cache ca-certificates +# Copy masked.name root cert +COPY files/MaskedName_Root_CA.crt /usr/local/share/ca-certificates/MaskedName_Root_CA.crt +# update ca certs +RUN update-ca-certificates 2>/dev/null + +RUN apk add --no-cache \ + bash \ + coreutils \ + curl \ + git \ + git-lfs \ + openssh-client \ + tini \ + ttf-dejavu \ + tzdata \ + unzip + +ARG user=jenkins +ARG group=jenkins +ARG uid=15000 +ARG gid=15000 +ARG http_port=8080 +ARG agent_port=50000 +ARG JENKINS_HOME=/var/jenkins_home +ARG REF=/usr/share/jenkins/ref + +ENV JENKINS_HOME $JENKINS_HOME +ENV JENKINS_SLAVE_AGENT_PORT ${agent_port} +ENV REF $REF + +RUN mkdir -p $JENKINS_HOME \ + && chown ${uid}:${gid} $JENKINS_HOME \ + && addgroup -g ${gid} ${group} \ + && adduser -h "$JENKINS_HOME" -u ${uid} -G ${group} -s /bin/bash -D ${user} + +VOLUME $JENKINS_HOME + +RUN mkdir -p ${REF}/init.groovy.d + +ARG JENKINS_VERSION +ENV JENKINS_VERSION ${JENKINS_VERSION:-2.249.1} + +ARG JENKINS_SHA=a38ca485b3e2bae6ec69a34b6b34b674f4dd4baf04f20f8dce2d838d08ed5bd6 +ARG JENKINS_URL=https://repo.jenkins-ci.org/public/org/jenkins-ci/main/jenkins-war/${JENKINS_VERSION}/jenkins-war-${JENKINS_VERSION}.war +RUN curl -fsSL ${JENKINS_URL} -o /usr/share/jenkins/jenkins.war \ + && echo "${JENKINS_SHA} /usr/share/jenkins/jenkins.war" | sha256sum -c - + +ENV JENKINS_UC https://updates.jenkins.io +ENV JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental +ENV JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals +RUN chown -R ${user} "$JENKINS_HOME" "$REF" + +ARG PLUGIN_CLI_URL=https://github.com/jenkinsci/plugin-installation-manager-tool/releases/download/plugin-management-parent-pom-2.0.0/jenkins-plugin-manager-2.0.0.jar +RUN curl -fsSL ${PLUGIN_CLI_URL} -o /usr/lib/jenkins-plugin-manager.jar + +# for main web interface: +EXPOSE ${http_port} + +# will be used by attached slave agents: +EXPOSE ${agent_port} + +ENV COPY_REFERENCE_FILE_LOG $JENKINS_HOME/copy_reference_file.log + +USER ${user} + +COPY files/jenkins-support /usr/local/bin/jenkins-support +COPY files/jenkins.sh /usr/local/bin/jenkins.sh +COPY files/tini-shim.sh /bin/tini +COPY files/jenkins-plugin-cli.sh /bin/jenkins-plugin-cli + +ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"] + +# from a derived Dockerfile, can use `RUN install-plugins.sh active.txt` to setup $REF/plugins from a support bundle +COPY files/install-plugins.sh /usr/local/bin/install-plugins.sh diff --git a/docker/jenkins/files/MaskedName_Root_CA.crt b/docker/jenkins/files/MaskedName_Root_CA.crt new file mode 100755 index 0000000..31f76a9 --- /dev/null +++ b/docker/jenkins/files/MaskedName_Root_CA.crt @@ -0,0 +1,43 @@ +-----BEGIN CERTIFICATE----- +MIIDNTCCAh2gAwIBAgIUYp8xo5t2lJFP3SiD1fJirgGUQJ0wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAxMLbWFza2VkLm5hbWUwHhcNMjAwODI5MTkyMzEyWhcNMzAw +ODI3MTkyMzQyWjAWMRQwEgYDVQQDEwttYXNrZWQubmFtZTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAMI7oR+KHvvznfnaAXDMO5qpSTCAYCyfjFEohYJf +lOcnLONXb3f6sP5d1eltL+UTq0RVU5UP0aNW7hqDTa41MRw0JCDtB68yKdYq2hZf +97gA+lj3MEJU6RTAKLrg75GRh/AbNEIgwvPuHKW6hMbtwOyM9DFU//W3xpusalXy +RMFzAHfSDj9ci+UygUt9HINWd/SmMGG/8PghaRhfE44wRFMqYezeliIt2JIs43BV +7HqG0Oev9WPeXmiaZUYKQetHiQqR14Mxiv1IGzCmwwN+9b4tZtZTa58oM5dPXfbb +lrELQE5OsPaNtMtER3MgxovDN3VSCGH/O/GyaEWVanY5UF8CAwEAAaN7MHkwDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFBY8jW3fDVUp +URt1prhmDMjkVikgMB8GA1UdIwQYMBaAFBY8jW3fDVUpURt1prhmDMjkVikgMBYG +A1UdEQQPMA2CC21hc2tlZC5uYW1lMA0GCSqGSIb3DQEBCwUAA4IBAQAWQz4d3QzE +W8NGA16ZPamlVubOLB5DtZz2qrSrn3DeObLIDShInV3qtRlDx9HYJLTCA75Ket0J +NTsyMcTy2txd4I8hgdF30XJeEciN9wZ0mKEeP/YKDwe8V2XwWq4XYkDechlWHpZo +PfWcoLprKwVUI4HzaqkNmwcmMUI4xAsC+SLe1mrebseKm49oOwdQs/oPVLK+0nEp +RvD0aOvohILIa/2ZtKczvhB/L3fo5pg9Ex/0JDBdDHIedMabD3qn8Idse+P5Dfwa +Ju2Ctyb+n1TTPxRDMxs2cFbA5irr+2ARJd8jtGS+1fyxogjOWS1RR523F+qIS3su +KibGel+gFPpq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID0zCCArugAwIBAgIUM52uhXSeTCim1pmzucm/cnIgNp8wDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAxMLbWFza2VkLm5hbWUwHhcNMjAwODI5MTkyNzAwWhcNMjUw +ODI4MTkyNzMwWjAtMSswKQYDVQQDEyJtYXNrZWQubmFtZSBJbnRlcm1lZGlhdGUg +QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8LuGo+As +ICYWdJjBCY0snF/X+jF1tdcrQzNiRKESEb5dsDiy979bugCblPQDQ+g5WGqXX4pj +UyZZE3ZwhOufISlGK0ow1aMjqS+pFlQ85KRD/jUtLPRUJuQF+m2YwId/Mg6/B7Qk +d166uJkNxS+MGZCi2OYXeoivnOY7Q0Kj/0vIbc5Vt3kCRVg2ljLSQhoBd+85AHMR +jeRjZMeYEYF2HTVwrg4DrC/r00MVtDcNqs6+M7YZ/rzny73GvfJWfWoB1C4piZlg +fvUcSDL5HAhjiu5cSeIR7DTuVx7t4PoK6AqUkPygDtq1ZaLybXT7X6d072dR5AXO +nWFLPaaGJ979iwIDAQABo4IBADCB/TAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUIkhVYBaK9CcvXG8FM2jKVZ16oZAwHwYDVR0jBBgw +FoAUFjyNbd8NVSlRG3WmuGYMyORWKSAwUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUF +BzAChjVodHRwOi8vdmF1bHQuY29sdW1iaWEubWFza2VkLm5hbWU6ODIwMC92MS9w +a2lfcm9vdC9jYTBHBgNVHR8EQDA+MDygOqA4hjZodHRwOi8vdmF1bHQuY29sdW1i +aWEubWFza2VkLm5hbWU6ODIwMC92MS9wa2lfcm9vdC9jcmwwDQYJKoZIhvcNAQEL +BQADggEBAK6HMgR+hpwjZCmf5NszDSHr7dYKZXP4LrcHPWs94nLM33UZ572ubGHs +dKjRw8YD0cncrsypsYmEgR57U+DHkys394wkb7UOwy1Zvd5IIRXdP0cDylz0QzqM +APnQYN+ismkoljhk9ey0Qbo3CmPjM+UQcAxuZQtA4M+riC1+jkude1uYL0szC6Y9 +4KetfvbNkedSaV5yJaRKCBhRcC4/GjpBG/odQ/5AfBPAFjZqhcIJWBrVYbTQVC79 +hMA1iwWJPmT9LsjMSUfxFTPzxRnNXQiKFz5kT2OiS1nqh8aOcyU9YC928pkifNJV +KokuDezJFM7ie3d+EcBk1V9lHwOWdto= +-----END CERTIFICATE----- diff --git a/docker/jenkins/files/install-plugins.sh b/docker/jenkins/files/install-plugins.sh new file mode 100755 index 0000000..7f0ac9c --- /dev/null +++ b/docker/jenkins/files/install-plugins.sh @@ -0,0 +1,291 @@ +#!/bin/bash -eu + +# Resolve dependencies and download plugins given on the command line +# +# FROM jenkins +# RUN install-plugins.sh docker-slaves github-branch-source +# +# Environment variables: +# REF: directory with preinstalled plugins. Default: /usr/share/jenkins/ref/plugins +# JENKINS_WAR: full path to the jenkins.war. Default: /usr/share/jenkins/jenkins.war +# JENKINS_UC: url of the Update Center. Default: "" +# JENKINS_UC_EXPERIMENTAL: url of the Experimental Update Center for experimental versions of plugins. Default: "" +# JENKINS_INCREMENTALS_REPO_MIRROR: url of the incrementals repo mirror. Default: "" +# JENKINS_UC_DOWNLOAD: download url of the Update Center. Default: JENKINS_UC/download +# CURL_OPTIONS When downloading the plugins with curl. Curl options. Default: -sSfL +# CURL_CONNECTION_TIMEOUT When downloading the plugins with curl. Maximum time allowed for connection. Default: 20 +# CURL_RETRY When downloading the plugins with curl. Retry request if transient problems occur. Default: 3 +# CURL_RETRY_DELAY When downloading the plugins with curl. Wait time between retries. Default: 0 +# CURL_RETRY_MAX_TIME When downloading the plugins with curl. Retry only within this period. Default: 60 + +set -o pipefail + +echo "WARN: install-plugins.sh is deprecated, please switch to jenkins-plugin-cli" + +JENKINS_WAR=${JENKINS_WAR:-/usr/share/jenkins/jenkins.war} + +. /usr/local/bin/jenkins-support + +REF_DIR="${REF}/plugins" +FAILED="$REF_DIR/failed-plugins.txt" + +getLockFile() { + printf '%s' "$REF_DIR/${1}.lock" +} + +getArchiveFilename() { + printf '%s' "$REF_DIR/${1}.jpi" +} + +download() { + local plugin originalPlugin version lock ignoreLockFile url + plugin="$1" + version="${2:-latest}" + ignoreLockFile="${3:-}" + url="${4:-}" + lock="$(getLockFile "$plugin")" + + if [[ $ignoreLockFile ]] || mkdir "$lock" &>/dev/null; then + if ! doDownload "$plugin" "$version" "$url"; then + # some plugin don't follow the rules about artifact ID + # typically: docker-plugin + originalPlugin="$plugin" + plugin="${plugin}-plugin" + if ! doDownload "$plugin" "$version" "$url"; 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 + + resolveDependencies "$plugin" + fi +} + +doDownload() { + local plugin version url jpi + plugin="$1" + version="$2" + url="$3" + 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 [[ -n $url ]] ; then + echo "Will use url=$url" + elif [[ "$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 + # add a trailing ; so the \n gets added to the end + readarray -t "-d;" arrIN <<<"${version};"; + unset 'arrIN[-1]'; + 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" + # We actually want to allow variable value to be split into multiple options passed to curl. + # This is needed to allow long options and any options that take value. + # shellcheck disable=SC2086 + retry_command curl ${CURL_OPTIONS:--sSfL} --connect-timeout "${CURL_CONNECTION_TIMEOUT:-20}" --retry "${CURL_RETRY:-3}" --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 $? +} + +resolveDependencies() { + local plugin jpi dependencies + plugin="$1" + jpi="$(getArchiveFilename "$plugin")" + + dependencies="$(unzip -p "$jpi" META-INF/MANIFEST.MF | tr -d '\r' | tr '\n' '|' | sed -e 's#| ##g' | tr '|' '\n' | grep "^Plugin-Dependencies: " | sed -e 's#^Plugin-Dependencies: ##')" + + if [[ ! $dependencies ]]; then + echo " > $plugin has no dependencies" + return + fi + + echo " > $plugin depends on $dependencies" + + IFS=',' read -r -a array <<< "$dependencies" + + for d in "${array[@]}" + do + plugin="$(cut -d':' -f1 - <<< "$d")" + if [[ $d == *"resolution:=optional"* ]]; then + echo "Skipping optional dependency $plugin" + else + local pluginInstalled + if pluginInstalled="$(echo -e "${bundledPlugins}\n${installedPlugins}" | grep "^${plugin}:")"; then + pluginInstalled="${pluginInstalled//[$'\r']}" + local versionInstalled; versionInstalled=$(versionFromPlugin "${pluginInstalled}") + local minVersion; minVersion=$(versionFromPlugin "${d}") + if versionLT "${versionInstalled}" "${minVersion}"; then + echo "Upgrading bundled dependency $d ($minVersion > $versionInstalled)" + download "$plugin" & + else + echo "Skipping already installed dependency $d ($minVersion <= $versionInstalled)" + fi + else + download "$plugin" & + fi + fi + done + wait +} + +bundledPlugins() { + 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 + echo "war not found, installing all plugins: $JENKINS_WAR" + 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() { + if [[ -f "$JENKINS_WAR" ]]; then + local version major minor + version="$(java -jar "$JENKINS_WAR" --version)" + major="$(echo "$version" | cut -d '.' -f 1)" + minor="$(echo "$version" | cut -d '.' -f 2)" + echo "$major.$minor" + else + echo "" + fi +} + +main() { + local plugin 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 $JENKINS_WAR..." + bundledPlugins="$(bundledPlugins)" + + echo "Registering preinstalled plugins..." + installedPlugins="$(installedPlugins)" + + # Get the update center URL based on the jenkins version + jenkinsVersion="$(jenkinsMajorMinorVersion)" + jenkinsUcJson=$(curl "${CURL_OPTIONS:--sSfL}" -o /dev/null -w "%{url_effective}" "${JENKINS_UC}/update-center.json?version=${jenkinsVersion}") + if [ -n "${jenkinsUcJson}" ]; then + JENKINS_UC_LATEST=${jenkinsUcJson//update-center.json/} + echo "Using version-specific update center: $JENKINS_UC_LATEST..." + else + JENKINS_UC_LATEST= + fi + + echo "Downloading plugins..." + for plugin in "${plugins[@]}"; do + local reg='^([^:]+):?([^:]+)?:?([^:]+)?:?(http.+)?' + if [[ $plugin =~ $reg ]]; then + local pluginId="${BASH_REMATCH[1]}" + local version="${BASH_REMATCH[2]}" + local lock="${BASH_REMATCH[3]}" + local url="${BASH_REMATCH[4]}" + download "$pluginId" "$version" "${lock:-true}" "${url}" & + else + echo "Skipping the line '${plugin}' as it does not look like a reference to a plugin" + fi + 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" + find "$REF_DIR" -regex ".*.lock" | while read -r filepath; do + rm -r "$filepath" + done + +} + +main "$@" diff --git a/docker/jenkins/files/jenkins-plugin-cli.sh b/docker/jenkins/files/jenkins-plugin-cli.sh new file mode 100755 index 0000000..49ead73 --- /dev/null +++ b/docker/jenkins/files/jenkins-plugin-cli.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +java -jar /usr/lib/jenkins-plugin-manager.jar "$@" diff --git a/docker/jenkins/files/jenkins-support b/docker/jenkins/files/jenkins-support new file mode 100644 index 0000000..ffee320 --- /dev/null +++ b/docker/jenkins/files/jenkins-support @@ -0,0 +1,183 @@ +#!/bin/bash -eu + +: "${REF:="/usr/share/jenkins/ref"}" + +# compare if version1 < version2 +versionLT() { + local v1; v1=$(echo "$1" | cut -d '-' -f 1 ) + local q1; q1=$(echo "$1" | cut -s -d '-' -f 2- ) + local v2; v2=$(echo "$2" | cut -d '-' -f 1 ) + local q2; q2=$(echo "$2" | cut -s -d '-' -f 2- ) + if [ "$v1" = "$v2" ]; then + if [ "$q1" = "$q2" ]; then + return 1 + else + if [ -z "$q1" ]; then + return 1 + else + if [ -z "$q2" ]; then + return 0 + else + [ "$q1" = "$(echo -e "$q1\n$q2" | sort -V | head -n1)" ] + fi + fi + fi + else + [ "$v1" = "$(echo -e "$v1\n$v2" | sort -V | head -n1)" ] + fi +} + +# returns a plugin version from a plugin archive +get_plugin_version() { + local archive; archive=$1 + local version; version=$(unzip -p "$archive" META-INF/MANIFEST.MF | grep "^Plugin-Version: " | sed -e 's#^Plugin-Version: ##') + version=${version%%[[:space:]]} + echo "$version" +} + +# Copy files from /usr/share/jenkins/ref into $JENKINS_HOME +# So the initial JENKINS-HOME is set with expected content. +# Don't override, as this is just a reference setup, and use from UI +# can then change this, upgrade plugins, etc. +copy_reference_file() { + f="${1%/}" + b="${f%.override}" + rel="${b#"$REF/"}" + version_marker="${rel}.version_from_image" + dir=$(dirname "${rel}") + local action; + local reason; + local container_version; + local image_version; + local marker_version; + local log; log=false + if [[ ${rel} == plugins/*.jpi ]]; then + container_version=$(get_plugin_version "$JENKINS_HOME/${rel}") + image_version=$(get_plugin_version "${f}") + if [[ -e $JENKINS_HOME/${version_marker} ]]; then + marker_version=$(cat "$JENKINS_HOME/${version_marker}") + if versionLT "$marker_version" "$container_version"; then + if ( versionLT "$container_version" "$image_version" && [[ -n $PLUGINS_FORCE_UPGRADE ]]); then + action="UPGRADED" + reason="Manually upgraded version ($container_version) is older than image version $image_version" + log=true + else + action="SKIPPED" + reason="Installed version ($container_version) has been manually upgraded from initial version ($marker_version)" + log=true + fi + else + if [[ "$image_version" == "$container_version" ]]; then + action="SKIPPED" + reason="Version from image is the same as the installed version $image_version" + else + if versionLT "$image_version" "$container_version"; then + action="SKIPPED" + log=true + reason="Image version ($image_version) is older than installed version ($container_version)" + else + action="UPGRADED" + log=true + reason="Image version ($image_version) is newer than installed version ($container_version)" + fi + fi + fi + else + if [[ -n "$TRY_UPGRADE_IF_NO_MARKER" ]]; then + if [[ "$image_version" == "$container_version" ]]; then + action="SKIPPED" + reason="Version from image is the same as the installed version $image_version (no marker found)" + # Add marker for next time + echo "$image_version" > "$JENKINS_HOME/${version_marker}" + else + if versionLT "$image_version" "$container_version"; then + action="SKIPPED" + log=true + reason="Image version ($image_version) is older than installed version ($container_version) (no marker found)" + else + action="UPGRADED" + log=true + reason="Image version ($image_version) is newer than installed version ($container_version) (no marker found)" + fi + fi + fi + fi + if [[ ! -e $JENKINS_HOME/${rel} || "$action" == "UPGRADED" || $f = *.override ]]; then + action=${action:-"INSTALLED"} + log=true + mkdir -p "$JENKINS_HOME/${dir}" + cp -pr "${f}" "$JENKINS_HOME/${rel}"; + # pin plugins on initial copy + touch "$JENKINS_HOME/${rel}.pinned" + echo "$image_version" > "$JENKINS_HOME/${version_marker}" + reason=${reason:-$image_version} + else + action=${action:-"SKIPPED"} + fi + else + if [[ ! -e $JENKINS_HOME/${rel} || $f = *.override ]] + then + action="INSTALLED" + log=true + mkdir -p "$JENKINS_HOME/${dir}" + cp -pr "$(realpath "${f}")" "$JENKINS_HOME/${rel}"; + else + action="SKIPPED" + fi + fi + if [[ -n "$VERBOSE" || "$log" == "true" ]]; then + if [ -z "$reason" ]; then + echo "$action $rel" >> "$COPY_REFERENCE_FILE_LOG" + else + echo "$action $rel : $reason" >> "$COPY_REFERENCE_FILE_LOG" + fi + fi +} + +# Retries a command a configurable number of times with backoff. +# +# The retry count is given by ATTEMPTS (default 60), the initial backoff +# timeout is given by TIMEOUT in seconds (default 1.) +# +function retry_command() { + local max_attempts=${ATTEMPTS-3} + local timeout=${TIMEOUT-1} + local success_timeout=${SUCCESS_TIMEOUT-1} + local max_success_attempt=${SUCCESS_ATTEMPTS-1} + local attempt=0 + local success_attempt=0 + local exitCode=0 + + while (( attempt < max_attempts )) + do + set +e + "$@" + exitCode=$? + set -e + + if [[ $exitCode == 0 ]] + then + success_attempt=$(( success_attempt + 1 )) + if (( success_attempt >= max_success_attempt)) + then + break + else + sleep "$success_timeout" + continue + fi + fi + + echo "$(date -u '+%T') Failure ($exitCode) Retrying in $timeout seconds..." 1>&2 + sleep "$timeout" + success_attempt=0 + attempt=$(( attempt + 1 )) + timeout=$(( timeout )) + done + + if [[ $exitCode != 0 ]] + then + echo "$(date -u '+%T') Failed in the last attempt ($*)" 1>&2 + fi + + return $exitCode +} diff --git a/docker/jenkins/files/jenkins.sh b/docker/jenkins/files/jenkins.sh new file mode 100755 index 0000000..bd7bcc0 --- /dev/null +++ b/docker/jenkins/files/jenkins.sh @@ -0,0 +1,41 @@ +#! /bin/bash -e + +: "${JENKINS_WAR:="/usr/share/jenkins/jenkins.war"}" +: "${JENKINS_HOME:="/var/jenkins_home"}" +: "${COPY_REFERENCE_FILE_LOG:="${JENKINS_HOME}/copy_reference_file.log"}" +: "${REF:="/usr/share/jenkins/ref"}" +touch "${COPY_REFERENCE_FILE_LOG}" || { echo "Can not write to ${COPY_REFERENCE_FILE_LOG}. Wrong volume permissions?"; exit 1; } +echo "--- Copying files at $(date)" >> "$COPY_REFERENCE_FILE_LOG" +find "${REF}" \( -type f -o -type l \) -exec bash -c '. /usr/local/bin/jenkins-support; for arg; do copy_reference_file "$arg"; done' _ {} + + +# if `docker run` first argument start with `--` the user is passing jenkins launcher arguments +if [[ $# -lt 1 ]] || [[ "$1" == "--"* ]]; then + + # read JAVA_OPTS and JENKINS_OPTS into arrays to avoid need for eval (and associated vulnerabilities) + java_opts_array=() + while IFS= read -r -d '' item; do + java_opts_array+=( "$item" ) + done < <([[ $JAVA_OPTS ]] && xargs printf '%s\0' <<<"$JAVA_OPTS") + + readonly agent_port_property='jenkins.model.Jenkins.slaveAgentPort' + if [ -n "${JENKINS_SLAVE_AGENT_PORT:-}" ] && [[ "${JAVA_OPTS:-}" != *"${agent_port_property}"* ]]; then + java_opts_array+=( "-D${agent_port_property}=${JENKINS_SLAVE_AGENT_PORT}" ) + fi + + if [[ "$DEBUG" ]] ; then + java_opts_array+=( \ + '-Xdebug' \ + '-Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y' \ + ) + fi + + jenkins_opts_array=( ) + while IFS= read -r -d '' item; do + jenkins_opts_array+=( "$item" ) + done < <([[ $JENKINS_OPTS ]] && xargs printf '%s\0' <<<"$JENKINS_OPTS") + + exec java -Duser.home="$JENKINS_HOME" "${java_opts_array[@]}" -jar ${JENKINS_WAR} "${jenkins_opts_array[@]}" "$@" +fi + +# As argument is not jenkins, assume user want to run his own process, for example a `bash` shell to explore this image +exec "$@" diff --git a/docker/jenkins/files/tini-shim.sh b/docker/jenkins/files/tini-shim.sh new file mode 100755 index 0000000..8bfddd4 --- /dev/null +++ b/docker/jenkins/files/tini-shim.sh @@ -0,0 +1,17 @@ +#! /bin/bash +set -euo pipefail + +cat <= 2.107.2+) + +Now sleeping 2 minutes... +EOF + +sleep 120 + +exec tini "$@" diff --git a/nomad/jenkins.nomad b/nomad/jenkins.nomad new file mode 100644 index 0000000..de0ead3 --- /dev/null +++ b/nomad/jenkins.nomad @@ -0,0 +1,58 @@ +job "jenkins" { + datacenters = ["columbia"] + region = "global" + type = "service" + + update { + stagger = "30s" + max_parallel = 1 + } + + constraint { + attribute = "${meta.long_lived}" + value = "true" + } + + group "jenkins" { + count = 1 + + task "jenkins" { + driver = "docker" + config { + image = "docker.service.masked.name:8082/jenkins" + ports = ["https"] + volumes = [ + "/opt/jenkins_home:/var/jenkins_home" + ] + } + + env { + ROOT_URL = "${NOMAD_ADDR_https}" + } + + resources { + cpu = 2000 + memory = 2048 + } + } + + network { + port "https" { + to = 8080 + } + } + + service { + name = "jenkins" + port = "https" + + check { + name = "jenkins" + type = "tcp" + interval = "10s" + timeout = "2s" + address_mode = "driver" + } + } + } +}