build.sh 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. #!/bin/bash
  2. set -e
  3. # so we can have fancy stuff like !(pattern)
  4. shopt -s extglob
  5. dir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
  6. library="$dir/../library"
  7. src="$dir/src"
  8. logs="$dir/logs"
  9. namespaces='library stackbrew'
  10. library="$(readlink -f "$library")"
  11. src="$(readlink -f "$src")"
  12. logs="$(readlink -f "$logs")"
  13. # arg handling: all args are [repo|repo:tag]
  14. usage() {
  15. cat <<EOUSAGE
  16. usage: $0 [options] [repo[:tag] ...]
  17. ie: $0 --all
  18. $0 debian ubuntu:12.04
  19. This script builds the docker images specified using the git repositories
  20. specified in the library files.
  21. options:
  22. --help, -h, -? Print this help message
  23. --all Builds all docker repos specified in library
  24. --no-clone Don't pull the git repos
  25. --no-build Don't build, just echo what would have built
  26. --library="$library"
  27. Where to find repository manifest files
  28. --src="$src"
  29. Where to store the cloned git repositories
  30. --logs="$logs"
  31. Where to store the build logs
  32. --namespaces="$namespaces"
  33. Space separated list of namespaces to tag images in after
  34. building
  35. EOUSAGE
  36. }
  37. opts="$(getopt -o 'h?' --long 'help,all,no-clone,no-build,library:,src:,logs:,namespaces:' -- "$@" || { usage >&2 && false; })"
  38. eval set -- "$opts"
  39. doClone=1
  40. doBuild=1
  41. buildAll=
  42. while true; do
  43. flag=$1
  44. shift
  45. case "$flag" in
  46. --help|-h|'-?')
  47. usage
  48. exit 0
  49. ;;
  50. --all) buildAll=1 ;;
  51. --no-clone) doClone= ;;
  52. --no-build) doBuild= ;;
  53. --library) library="$1" && shift ;;
  54. --src) src="$1" && shift ;;
  55. --logs) logs="$1" && shift ;;
  56. --namespaces) namespaces="$1" && shift ;;
  57. --)
  58. break
  59. ;;
  60. *)
  61. {
  62. echo "error: unknown flag: $flag"
  63. usage
  64. } >&2
  65. exit 1
  66. ;;
  67. esac
  68. done
  69. repos=()
  70. if [ "$buildAll" ]; then
  71. repos=( "$library"/!(MAINTAINERS) )
  72. fi
  73. repos+=( "$@" )
  74. repos=( "${repos[@]%/}" )
  75. if [ "${#repos[@]}" -eq 0 ]; then
  76. echo >&2 'error: no repos specified'
  77. usage >&2
  78. exit 1
  79. fi
  80. # globals for handling the repo queue and repo info parsed from library
  81. queue=()
  82. declare -A repoGitRepo=()
  83. declare -A repoGitRef=()
  84. declare -A repoGitDir=()
  85. logDir="$logs/build-$(date +'%Y-%m-%d--%H-%M-%S')"
  86. mkdir -p "$logDir"
  87. latestLogDir="$logs/latest" # this gets shiny symlinks to the latest buildlog for each repo we've seen since the creation of the logs dir
  88. mkdir -p "$latestLogDir"
  89. didFail=
  90. # gather all the `repo:tag` combos to build
  91. for repoTag in "${repos[@]}"; do
  92. repo="${repoTag%%:*}"
  93. tag="${repoTag#*:}"
  94. [ "$repo" != "$tag" ] || tag=
  95. if [ "$repo" = 'http' -o "$repo" = 'https' ] && [[ "$tag" == //* ]]; then
  96. # IT'S A URL!
  97. repoUrl="$repo:${tag%:*}"
  98. repo="$(basename "$repoUrl")"
  99. if [ "${tag##*:}" != "$tag" ]; then
  100. tag="${tag##*:}"
  101. else
  102. tag=
  103. fi
  104. repoTag="${repo}${tag:+:$tag}"
  105. echo "$repoTag ($repoUrl)" >> "$logDir/repos.txt"
  106. cmd=( curl -sSL --compressed "$repoUrl" )
  107. else
  108. if [ -f "$repo" ]; then
  109. repoFile="$repo"
  110. repo="$(basename "$repoFile")"
  111. repoTag="${repo}${tag:+:$tag}"
  112. else
  113. repoFile="$library/$repo"
  114. fi
  115. repoFile="$(readlink -f "$repoFile")"
  116. echo "$repoTag ($repoFile)" >> "$logDir/repos.txt"
  117. cmd=( cat "$repoFile" )
  118. fi
  119. if [ "${repoGitRepo[$repoTag]}" ]; then
  120. queue+=( "$repoTag" )
  121. continue
  122. fi
  123. # parse the repo library file
  124. IFS=$'\n'
  125. repoTagLines=( $("${cmd[@]}" | grep -vE '^#|^\s*$') )
  126. unset IFS
  127. tags=()
  128. for line in "${repoTagLines[@]}"; do
  129. tag="$(echo "$line" | awk -F ': +' '{ print $1 }')"
  130. repoDir="$(echo "$line" | awk -F ': +' '{ print $2 }')"
  131. gitUrl="${repoDir%%@*}"
  132. commitDir="${repoDir#*@}"
  133. gitRef="${commitDir%% *}"
  134. gitDir="${commitDir#* }"
  135. if [ "$gitDir" = "$commitDir" ]; then
  136. gitDir=
  137. fi
  138. gitRepo="${gitUrl#*://}"
  139. gitRepo="${gitRepo%/}"
  140. gitRepo="${gitRepo%.git}"
  141. gitRepo="${gitRepo%/}"
  142. gitRepo="$src/$gitRepo"
  143. if [ -z "$doClone" ]; then
  144. if [ "$doBuild" -a ! -d "$gitRepo" ]; then
  145. echo >&2 "error: directory not found: $gitRepo"
  146. exit 1
  147. fi
  148. else
  149. if [ ! -d "$gitRepo" ]; then
  150. mkdir -p "$(dirname "$gitRepo")"
  151. echo "Cloning '$gitUrl' into '$gitRepo' ..."
  152. git clone -q "$gitUrl" "$gitRepo"
  153. echo 'Cloned successfully!'
  154. else
  155. # if we don't have the "ref" specified, "git fetch" in the hopes that we get it
  156. if ! (
  157. cd "$gitRepo"
  158. git rev-parse --verify "${gitRef}^{commit}" &> /dev/null
  159. ); then
  160. (
  161. cd "$gitRepo"
  162. git fetch -q --all
  163. git fetch -q --tags
  164. )
  165. fi
  166. fi
  167. # disable any automatic garbage collection too, just to help make sure we keep our dangling commit objects
  168. ( cd "$gitRepo" && git config gc.auto 0 )
  169. fi
  170. repoGitRepo[$repo:$tag]="$gitRepo"
  171. repoGitRef[$repo:$tag]="$gitRef"
  172. repoGitDir[$repo:$tag]="$gitDir"
  173. tags+=( "$repo:$tag" )
  174. done
  175. if [ "$repo" = "$repoTag" ]; then
  176. # add all tags we just parsed
  177. queue+=( "${tags[@]}" )
  178. else
  179. queue+=( "$repoTag" )
  180. fi
  181. done
  182. set -- "${queue[@]}"
  183. while [ "$#" -gt 0 ]; do
  184. repoTag="$1"
  185. gitRepo="${repoGitRepo[$repoTag]}"
  186. gitRef="${repoGitRef[$repoTag]}"
  187. gitDir="${repoGitDir[$repoTag]}"
  188. shift
  189. if [ -z "$gitRepo" ]; then
  190. echo >&2 'Unknown repo:tag:' "$repoTag"
  191. didFail=1
  192. continue
  193. fi
  194. echo "Processing $repoTag ..."
  195. thisLog="$logDir/build-$repoTag.log"
  196. touch "$thisLog"
  197. ln -sf "$thisLog" "$latestLogDir/$(basename "$thisLog")"
  198. if ! ( cd "$gitRepo" && git rev-parse --verify "${gitRef}^{commit}" &> /dev/null ); then
  199. echo "- failed; invalid ref: $gitRef"
  200. didFail=1
  201. continue
  202. fi
  203. dockerfilePath="$gitDir/Dockerfile"
  204. dockerfilePath="${dockerfilePath#/}" # strip leading "/" (for when gitDir is '') because "git show" doesn't like it
  205. if ! dockerfile="$(cd "$gitRepo" && git show "$gitRef":"$dockerfilePath")"; then
  206. echo "- failed; missing '$dockerfilePath' at '$gitRef' ?"
  207. didFail=1
  208. continue
  209. fi
  210. IFS=$'\n'
  211. froms=( $(echo "$dockerfile" | awk 'toupper($1) == "FROM" { print $2 ~ /:/ ? $2 : $2":latest" }') )
  212. unset IFS
  213. for from in "${froms[@]}"; do
  214. for queuedRepoTag in "$@"; do
  215. if [ "$from" = "$queuedRepoTag" ]; then
  216. # a "FROM" in this image is being built later in our queue, so let's bail on this image for now and come back later
  217. echo "- deferred; FROM $from"
  218. set -- "$@" "$repoTag"
  219. continue 3
  220. fi
  221. done
  222. done
  223. if [ "$doBuild" ]; then
  224. (
  225. set -x
  226. cd "$gitRepo"
  227. git reset -q HEAD
  228. git checkout -q -- .
  229. git clean -dfxq
  230. git checkout -q "$gitRef" --
  231. cd "$gitRepo/$gitDir"
  232. "$dir/git-set-mtimes"
  233. ) &>> "$thisLog"
  234. if ! (
  235. set -x
  236. docker build -t "$repoTag" "$gitRepo/$gitDir"
  237. ) &>> "$thisLog"; then
  238. echo "- failed; see $thisLog"
  239. didFail=1
  240. continue
  241. fi
  242. for namespace in $namespaces; do
  243. ( set -x; docker tag "$repoTag" "$namespace/$repoTag" ) &>> "$thisLog"
  244. done
  245. fi
  246. done
  247. [ -z "$didFail" ]