plugins.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/bin/bash -eu
  2. # Resolve dependencies and download plugins given on the command line
  3. #
  4. # FROM jenkins
  5. # RUN install-plugins.sh docker-slaves github-branch-source
  6. #
  7. # Environment variables:
  8. # REF: directory with preinstalled plugins. Default: /usr/share/jenkins/ref/plugins
  9. # JENKINS_WAR: full path to the jenkins.war. Default: /usr/share/jenkins/jenkins.war
  10. # JENKINS_UC: url of the Update Center. Default: ""
  11. # JENKINS_UC_EXPERIMENTAL: url of the Experimental Update Center for experimental versions of plugins. Default: ""
  12. # JENKINS_INCREMENTALS_REPO_MIRROR: url of the incrementals repo mirror. Default: ""
  13. # JENKINS_UC_DOWNLOAD: download url of the Update Center. Default: JENKINS_UC/download
  14. # CURL_OPTIONS When downloading the plugins with curl. Curl options. Default: -sSfL
  15. # CURL_CONNECTION_TIMEOUT When downloading the plugins with curl. <seconds> Maximum time allowed for connection. Default: 20
  16. # CURL_RETRY When downloading the plugins with curl. Retry request if transient problems occur. Default: 3
  17. # CURL_RETRY_DELAY When downloading the plugins with curl. <seconds> Wait time between retries. Default: 0
  18. # CURL_RETRY_MAX_TIME When downloading the plugins with curl. <seconds> Retry only within this period. Default: 60
  19. set -o pipefail
  20. JENKINS_WAR=${JENKINS_WAR:-/usr/share/jenkins/jenkins.war}
  21. . /usr/local/bin/jenkins-support
  22. REF_DIR="${REF}/plugins"
  23. FAILED="$REF_DIR/failed-plugins.txt"
  24. getLockFile() {
  25. printf '%s' "$REF_DIR/${1}.lock"
  26. }
  27. getArchiveFilename() {
  28. printf '%s' "$REF_DIR/${1}.jpi"
  29. }
  30. download() {
  31. local plugin originalPlugin version lock ignoreLockFile url
  32. plugin="$1"
  33. version="${2:-latest}"
  34. ignoreLockFile="${3:-}"
  35. url="${4:-}"
  36. lock="$(getLockFile "$plugin")"
  37. if [[ $ignoreLockFile ]] || mkdir "$lock" &>/dev/null; then
  38. if ! doDownload "$plugin" "$version" "$url"; then
  39. # some plugin don't follow the rules about artifact ID
  40. # typically: docker-plugin
  41. originalPlugin="$plugin"
  42. plugin="${plugin}-plugin"
  43. if ! doDownload "$plugin" "$version" "$url"; then
  44. echo "Failed to download plugin: $originalPlugin or $plugin" >&2
  45. echo "Not downloaded: ${originalPlugin}" >> "$FAILED"
  46. return 1
  47. fi
  48. fi
  49. if ! checkIntegrity "$plugin"; then
  50. echo "Downloaded file is not a valid ZIP: $(getArchiveFilename "$plugin")" >&2
  51. echo "Download integrity: ${plugin}" >> "$FAILED"
  52. return 1
  53. fi
  54. resolveDependencies "$plugin"
  55. fi
  56. }
  57. doDownload() {
  58. local plugin version url jpi
  59. plugin="$1"
  60. version="$2"
  61. url="$3"
  62. jpi="$(getArchiveFilename "$plugin")"
  63. # If plugin already exists and is the same version do not download
  64. if test -f "$jpi" && unzip -p "$jpi" META-INF/MANIFEST.MF | tr -d '\r' | grep "^Plugin-Version: ${version}$" > /dev/null; then
  65. echo "Using provided plugin: $plugin"
  66. return 0
  67. fi
  68. if [[ -n $url ]] ; then
  69. echo "Will use url=$url"
  70. elif [[ "$version" == "latest" && -n "$JENKINS_UC_LATEST" ]]; then
  71. # If version-specific Update Center is available, which is the case for LTS versions,
  72. # use it to resolve latest versions.
  73. url="$JENKINS_UC_LATEST/latest/${plugin}.hpi"
  74. elif [[ "$version" == "experimental" && -n "$JENKINS_UC_EXPERIMENTAL" ]]; then
  75. # Download from the experimental update center
  76. url="$JENKINS_UC_EXPERIMENTAL/latest/${plugin}.hpi"
  77. elif [[ "$version" == incrementals* ]] ; then
  78. # Download from Incrementals repo: https://jenkins.io/blog/2018/05/15/incremental-deployment/
  79. # 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
  80. local groupId incrementalsVersion
  81. # add a trailing ; so the \n gets added to the end
  82. readarray -t "-d;" arrIN <<<"${version};";
  83. unset 'arrIN[-1]';
  84. groupId=${arrIN[1]}
  85. incrementalsVersion=${arrIN[2]}
  86. url="${JENKINS_INCREMENTALS_REPO_MIRROR}/$(echo "${groupId}" | tr '.' '/')/${plugin}/${incrementalsVersion}/${plugin}-${incrementalsVersion}.hpi"
  87. else
  88. JENKINS_UC_DOWNLOAD=${JENKINS_UC_DOWNLOAD:-"$JENKINS_UC/download"}
  89. url="$JENKINS_UC_DOWNLOAD/plugins/$plugin/$version/${plugin}.hpi"
  90. fi
  91. echo "Downloading plugin: $plugin from $url"
  92. # We actually want to allow variable value to be split into multiple options passed to curl.
  93. # This is needed to allow long options and any options that take value.
  94. # shellcheck disable=SC2086
  95. 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"
  96. return $?
  97. }
  98. checkIntegrity() {
  99. local plugin jpi
  100. plugin="$1"
  101. jpi="$(getArchiveFilename "$plugin")"
  102. unzip -t -qq "$jpi" >/dev/null
  103. return $?
  104. }
  105. resolveDependencies() {
  106. local plugin jpi dependencies
  107. plugin="$1"
  108. jpi="$(getArchiveFilename "$plugin")"
  109. 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: ##')"
  110. if [[ ! $dependencies ]]; then
  111. echo " > $plugin has no dependencies"
  112. return
  113. fi
  114. echo " > $plugin depends on $dependencies"
  115. IFS=',' read -r -a array <<< "$dependencies"
  116. for d in "${array[@]}"
  117. do
  118. plugin="$(cut -d':' -f1 - <<< "$d")"
  119. if [[ $d == *"resolution:=optional"* ]]; then
  120. echo "Skipping optional dependency $plugin"
  121. else
  122. local pluginInstalled
  123. if pluginInstalled="$(echo -e "${bundledPlugins}\n${installedPlugins}" | grep "^${plugin}:")"; then
  124. pluginInstalled="${pluginInstalled//[$'\r']}"
  125. local versionInstalled; versionInstalled=$(versionFromPlugin "${pluginInstalled}")
  126. local minVersion; minVersion=$(versionFromPlugin "${d}")
  127. if versionLT "${versionInstalled}" "${minVersion}"; then
  128. echo "Upgrading bundled dependency $d ($minVersion > $versionInstalled)"
  129. download "$plugin" &
  130. else
  131. echo "Skipping already installed dependency $d ($minVersion <= $versionInstalled)"
  132. fi
  133. else
  134. download "$plugin" &
  135. fi
  136. fi
  137. done
  138. wait
  139. }
  140. bundledPlugins() {
  141. if [ -f "$JENKINS_WAR" ]
  142. then
  143. TEMP_PLUGIN_DIR=/tmp/plugintemp.$$
  144. for i in $(jar tf "$JENKINS_WAR" | grep -E '[^detached-]plugins.*\..pi' | sort)
  145. do
  146. rm -fr $TEMP_PLUGIN_DIR
  147. mkdir -p $TEMP_PLUGIN_DIR
  148. PLUGIN=$(basename "$i"|cut -f1 -d'.')
  149. (cd $TEMP_PLUGIN_DIR;jar xf "$JENKINS_WAR" "$i";jar xvf "$TEMP_PLUGIN_DIR/$i" META-INF/MANIFEST.MF >/dev/null 2>&1)
  150. VER=$(grep -E -i Plugin-Version "$TEMP_PLUGIN_DIR/META-INF/MANIFEST.MF"|cut -d: -f2|sed 's/ //')
  151. echo "$PLUGIN:$VER"
  152. done
  153. rm -fr $TEMP_PLUGIN_DIR
  154. else
  155. echo "war not found, installing all plugins: $JENKINS_WAR"
  156. fi
  157. }
  158. versionFromPlugin() {
  159. local plugin=$1
  160. if [[ $plugin =~ .*:.* ]]; then
  161. echo "${plugin##*:}"
  162. else
  163. echo "latest"
  164. fi
  165. }
  166. installedPlugins() {
  167. for f in "$REF_DIR"/*.jpi; do
  168. echo "$(basename "$f" | sed -e 's/\.jpi//'):$(get_plugin_version "$f")"
  169. done
  170. }
  171. jenkinsMajorMinorVersion() {
  172. if [[ -f "$JENKINS_WAR" ]]; then
  173. local version major minor
  174. version="$(java -jar "$JENKINS_WAR" --version)"
  175. major="$(echo "$version" | cut -d '.' -f 1)"
  176. minor="$(echo "$version" | cut -d '.' -f 2)"
  177. echo "$major.$minor"
  178. else
  179. echo ""
  180. fi
  181. }
  182. main() {
  183. local plugin jenkinsVersion
  184. local plugins=()
  185. mkdir -p "$REF_DIR" || exit 1
  186. rm -f "$FAILED"
  187. # Read plugins from stdin or from the command line arguments
  188. if [[ ($# -eq 0) ]]; then
  189. while read -r line || [ "$line" != "" ]; do
  190. # Remove leading/trailing spaces, comments, and empty lines
  191. plugin=$(echo "${line}" | tr -d '\r' | sed -e 's/^[ \t]*//g' -e 's/[ \t]*$//g' -e 's/[ \t]*#.*$//g' -e '/^[ \t]*$/d')
  192. # Avoid adding empty plugin into array
  193. if [ ${#plugin} -ne 0 ]; then
  194. plugins+=("${plugin}")
  195. fi
  196. done
  197. else
  198. plugins=("$@")
  199. fi
  200. # Create lockfile manually before first run to make sure any explicit version set is used.
  201. echo "Creating initial locks..."
  202. for plugin in "${plugins[@]}"; do
  203. mkdir "$(getLockFile "${plugin%%:*}")"
  204. done
  205. echo "Analyzing war $JENKINS_WAR..."
  206. bundledPlugins="$(bundledPlugins)"
  207. echo "Registering preinstalled plugins..."
  208. installedPlugins="$(installedPlugins)"
  209. # Check if there's a version-specific update center, which is the case for LTS versions
  210. jenkinsVersion="$(jenkinsMajorMinorVersion)"
  211. if curl -fsL -o /dev/null "$JENKINS_UC/$jenkinsVersion"; then
  212. JENKINS_UC_LATEST="$JENKINS_UC/$jenkinsVersion"
  213. echo "Using version-specific update center: $JENKINS_UC_LATEST..."
  214. else
  215. JENKINS_UC_LATEST=
  216. fi
  217. echo "Downloading plugins..."
  218. for plugin in "${plugins[@]}"; do
  219. local reg='^([^:]+):?([^:]+)?:?([^:]+)?:?(http.+)?'
  220. if [[ $plugin =~ $reg ]]; then
  221. local pluginId="${BASH_REMATCH[1]}"
  222. local version="${BASH_REMATCH[2]}"
  223. local lock="${BASH_REMATCH[3]}"
  224. local url="${BASH_REMATCH[4]}"
  225. download "$pluginId" "$version" "${lock:-true}" "${url}" &
  226. else
  227. echo "Skipping the line '${plugin}' as it does not look like a reference to a plugin"
  228. fi
  229. done
  230. wait
  231. echo
  232. echo "WAR bundled plugins:"
  233. echo "${bundledPlugins}"
  234. echo
  235. echo "Installed plugins:"
  236. installedPlugins
  237. if [[ -f $FAILED ]]; then
  238. echo "Some plugins failed to download!" "$(<"$FAILED")" >&2
  239. exit 1
  240. fi
  241. echo "Cleaning up locks"
  242. find "$REF_DIR" -regex ".*.lock" | while read -r filepath; do
  243. rm -r "$filepath"
  244. done
  245. }
  246. main "$@"