| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 | 
							- #!/usr/bin/env bash
 
- set -Eeuo pipefail
 
- shopt -s dotglob
 
- # make sure we can GTFO
 
- trap 'echo >&2 Ctrl+C captured, exiting; exit 1' SIGINT
 
- # if bashbrew is missing, bail early with a sane error
 
- bashbrew --version > /dev/null
 
- usage() {
 
- 	cat <<-EOUSAGE
 
- 		usage: $0 [PR number] [repo[:tag]]
 
- 		   ie: $0 1024
 
- 		       $0 9001 debian php django
 
- 	EOUSAGE
 
- }
 
- # TODO flags parsing
 
- allFiles=
 
- listTarballContents=1
 
- findCopies='20%'
 
- uninterestingTarballContent=(
 
- 	# "config_diff_2017_01_07.log"
 
- 	'var/log/YaST2/'
 
- 	# "ks-script-mqmz_080.log"
 
- 	# "ks-script-ycfq606i.log"
 
- 	'var/log/anaconda/'
 
- 	# "2016-12-20/"
 
- 	'var/lib/yum/history/'
 
- 	'var/lib/dnf/history/'
 
- 	# "a/f8c032d2be757e1a70f00336b55c434219fee230-acl-2.2.51-12.el7-x86_64/var_uuid"
 
- 	'var/lib/yum/yumdb/'
 
- 	'var/lib/dnf/yumdb/'
 
- 	# "b42ff584.0"
 
- 	'etc/pki/tls/rootcerts/'
 
- 	# "09/401f736622f2c9258d14388ebd47900bbab126"
 
- 	'usr/lib/.build-id/'
 
- )
 
- # prints "$2$1$3$1...$N"
 
- join() {
 
- 	local sep="$1"; shift
 
- 	local out; printf -v out "${sep//%/%%}%s" "$@"
 
- 	echo "${out#$sep}"
 
- }
 
- uninterestingTarballGrep="^([.]?/)?($(join '|' "${uninterestingTarballContent[@]}"))"
 
- if [ "$#" -eq 0 ]; then
 
- 	usage >&2
 
- 	exit 1
 
- fi
 
- pull="$1" # PR number
 
- shift
 
- diffDir="$(readlink -f "$BASH_SOURCE")"
 
- diffDir="$(dirname "$diffDir")"
 
- tempDir="$(mktemp -d)"
 
- trap "rm -rf '$tempDir'" EXIT
 
- cd "$tempDir"
 
- git clone --quiet \
 
- 	https://github.com/docker-library/official-images.git \
 
- 	oi
 
- if [ "$pull" != '0' ]; then
 
- 	git -C oi fetch --quiet \
 
- 		origin "pull/$pull/merge":refs/heads/pull
 
- else
 
- 	git -C oi fetch --quiet --update-shallow \
 
- 		"$diffDir" HEAD:refs/heads/pull
 
- fi
 
- externalPins=
 
- if [ "$#" -eq 0 ]; then
 
- 	externalPins="$(git -C oi/.external-pins diff --no-renames --name-only HEAD...pull -- '*/**')"
 
- 	images="$(git -C oi/library diff --no-renames --name-only HEAD...pull -- .)"
 
- 	if [ -z "$images" ] && [ -z "$externalPins" ]; then
 
- 		exit 0
 
- 	fi
 
- 	images="$(xargs -rn1 basename <<<"$images")"
 
- 	set -- $images
 
- fi
 
- export BASHBREW_LIBRARY="$PWD/oi/library"
 
- : "${BASHBREW_ARCH:=amd64}" # TODO something smarter with arches
 
- export BASHBREW_ARCH
 
- # TODO something less hacky than "git archive" hackery, like a "bashbrew archive" or "bashbrew context" or something
 
- template='
 
- 	tempDir="$(mktemp -d)"
 
- 	{{- "\n" -}}
 
- 	{{- range $.Entries -}}
 
- 		{{- $arch := .HasArchitecture arch | ternary arch (.Architectures | first) -}}
 
- 		{{- /* cannot replace ArchDockerFroms with bashbrew fetch or the arch selector logic has to be duplicated 🥹*/ -}}
 
- 		{{- $froms := $.ArchDockerFroms $arch . -}}
 
- 		{{- $outDir := join "_" $.RepoName (.Tags | last) -}}
 
- 		git -C "{{ gitCache }}" archive --format=tar
 
- 		{{- " " -}}
 
- 		{{- "--prefix=" -}}
 
- 		{{- $outDir -}}
 
- 		{{- "/" -}}
 
- 		{{- " " -}}
 
- 		{{- .ArchGitCommit $arch -}}
 
- 		{{- ":" -}}
 
- 		{{- $dir := .ArchDirectory $arch -}}
 
- 		{{- (eq $dir ".") | ternary "" $dir -}}
 
- 		{{- "\n" -}}
 
- 		mkdir -p "$tempDir/{{- $outDir -}}" && echo "{{- .ArchBuilder $arch -}}" > "$tempDir/{{- $outDir -}}/.bashbrew-builder" && echo "{{- .ArchFile $arch -}}" > "$tempDir/{{- $outDir -}}/.bashbrew-file"
 
- 		{{- "\n" -}}
 
- 	{{- end -}}
 
- 	tar -cC "$tempDir" . && rm -rf "$tempDir"
 
- '
 
- _tar-t() {
 
- 	tar -t "$@" \
 
- 		| grep -vE "$uninterestingTarballGrep" \
 
- 		| sed -e 's!^[.]/!!' \
 
- 			-r \
 
- 			-e 's!([/.-]|^)((lib)?(c?python|py)-?)[0-9]+([.][0-9]+)?([/.-]|$)!\1\2XXX\6!g' \
 
- 		| sort
 
- }
 
- _jq() {
 
- 	if [ "$#" -eq 0 ]; then
 
- 		set -- '.'
 
- 	fi
 
- 	jq --tab -S "$@"
 
- }
 
- copy-tar() {
 
- 	local src="$1"; shift
 
- 	local dst="$1"; shift
 
- 	if [ -n "$allFiles" ]; then
 
- 		mkdir -p "$dst"
 
- 		cp -al "$src"/*/ "$dst/"
 
- 		return
 
- 	fi
 
- 	local d indexes=() dockerfiles=()
 
- 	for d in "$src"/*/.bashbrew-file; do
 
- 		[ -f "$d" ] || continue
 
- 		local bf; bf="$(< "$d")"
 
- 		local dDir; dDir="$(dirname "$d")"
 
- 		local builder; builder="$(< "$dDir/.bashbrew-builder")"
 
- 		if [ "$builder" = 'oci-import' ]; then
 
- 			indexes+=( "$dDir/$bf" )
 
- 		else
 
- 			dockerfiles+=( "$dDir/$bf" )
 
- 			if [ "$bf" = 'Dockerfile' ]; then
 
- 				# if "Dockerfile.builder" exists, let's check that too (busybox, hello-world)
 
- 				if [ -f "$dDir/$bf.builder" ]; then
 
- 					dockerfiles+=( "$dDir/$bf.builder" )
 
- 				fi
 
- 			fi
 
- 		fi
 
- 		rm "$d" "$dDir/.bashbrew-builder" # remove the ".bashbrew-*" files we created
 
- 	done
 
- 	# now that we're done with our globbing needs, let's disable globbing so it doesn't give us wrong answers
 
- 	local -
 
- 	set -o noglob
 
- 	for i in "${indexes[@]}"; do
 
- 		local iName; iName="$(basename "$i")"
 
- 		local iDir; iDir="$(dirname "$i")"
 
- 		local iDirName; iDirName="$(basename "$iDir")"
 
- 		local iDst="$dst/$iDirName"
 
- 		mkdir -p "$iDst"
 
- 		_jq . "$i" > "$iDst/$iName"
 
- 		local digest
 
- 		digest="$(jq -r --arg name "$iName" '
 
- 			if $name == "index.json" then
 
- 				.manifests[0].digest
 
- 			else
 
- 				.digest
 
- 			end
 
- 		' "$i")"
 
- 		local blob="blobs/${digest//://}"
 
- 		local blobDir; blobDir="$(dirname "$blob")"
 
- 		local manifest="$iDir/$blob"
 
- 		mkdir -p "$iDst/$blobDir"
 
- 		_jq . "$manifest" > "$iDst/$blob"
 
- 		local configDigest; configDigest="$(jq -r '.config.digest' "$manifest")"
 
- 		local blob="blobs/${configDigest//://}"
 
- 		local blobDir; blobDir="$(dirname "$blob")"
 
- 		local config="$iDir/$blob"
 
- 		mkdir -p "$iDst/$blobDir"
 
- 		_jq . "$config" > "$iDst/$blob"
 
- 		local layers
 
- 		layers="$(jq -r '[ .layers[].digest | @sh ] | join(" ")' "$manifest")"
 
- 		eval "layers=( $layers )"
 
- 		local layerDigest
 
- 		for layerDigest in "${layers[@]}"; do
 
- 			local blob="blobs/${layerDigest//://}"
 
- 			local blobDir; blobDir="$(dirname "$blob")"
 
- 			local layer="$iDir/$blob"
 
- 			mkdir -p "$iDst/$blobDir"
 
- 			_tar-t -f "$layer" > "$iDst/$blob  'tar -t'"
 
- 		done
 
- 	done
 
- 	for d in "${dockerfiles[@]}"; do
 
- 		local dDir; dDir="$(dirname "$d")"
 
- 		local dDirName; dDirName="$(basename "$dDir")"
 
- 		# TODO choke on "syntax" parser directive
 
- 		# TODO handle "escape" parser directive reasonably
 
- 		local flatDockerfile; flatDockerfile="$(
 
- 			gawk '
 
- 				BEGIN { line = "" }
 
- 				/^[[:space:]]*#/ {
 
- 					gsub(/^[[:space:]]+/, "")
 
- 					print
 
- 					next
 
- 				}
 
- 				{
 
- 					if (match($0, /^(.*)(\\[[:space:]]*)$/, m)) {
 
- 						line = line m[1]
 
- 						next
 
- 					}
 
- 					print line $0
 
- 					line = ""
 
- 				}
 
- 			' "$d"
 
- 		)"
 
- 		local IFS=$'\n'
 
- 		local copyAddContext; copyAddContext="$(awk '
 
- 			toupper($1) == "COPY" || toupper($1) == "ADD" {
 
- 				for (i = 2; i < NF; i++) {
 
- 					if ($i ~ /^--from=/) {
 
- 						next
 
- 					}
 
- 					# COPY and ADD options
 
- 					if ($i ~ /^--(chown|chmod|link|parents|exclude)=/) {
 
- 						continue
 
- 					}
 
- 					# additional ADD options
 
- 					if ($i ~ /^--(keep-git-dir|checksum)=/) {
 
- 						continue
 
- 					}
 
- 					for ( ; i < NF; i++) {
 
- 						print $i
 
- 					}
 
- 				}
 
- 			}
 
- 		' <<<"$flatDockerfile")"
 
- 		local dBase; dBase="$(basename "$d")"
 
- 		local files=(
 
- 			"$dBase"
 
- 			$copyAddContext
 
- 			# some extra files which are likely interesting if they exist, but no big loss if they do not
 
- 			' .dockerignore' # will be used automatically by "docker build"
 
- 			' *.manifest' # debian/ubuntu "package versions" list
 
- 			' *.ks' # fedora "kickstart" (rootfs build script)
 
- 			' build*.txt' # ubuntu "build-info.txt", debian "build-command.txt"
 
- 			# usefulness yet to be proven:
 
- 			#' *.log'
 
- 			#' {MD5,SHA1,SHA256}SUMS'
 
- 			#' *.{md5,sha1,sha256}'
 
- 			# (the space prefix is removed below and is used to ignore non-matching globs so that bad "Dockerfile" entries appropriately lead to failure)
 
- 		)
 
- 		unset IFS
 
- 		mkdir -p "$dst/$dDirName"
 
- 		local f origF failureMatters
 
- 		for origF in "${files[@]}"; do
 
- 			f="${origF# }" # trim off leading space (indicates we don't care about failure)
 
- 			[ "$f" = "$origF" ] && failureMatters=1 || failureMatters=
 
- 			local globbed
 
- 			# "find: warning: -path ./xxx/ will not match anything because it ends with /."
 
- 			local findGlobbedPath="${f%/}"
 
- 			findGlobbedPath="${findGlobbedPath#./}"
 
- 			local globbedStr; globbedStr="$(cd "$dDir" && find -path "./$findGlobbedPath")"
 
- 			local -a globbed=( $globbedStr )
 
- 			if [ "${#globbed[@]}" -eq 0 ]; then
 
- 				globbed=( "$f" )
 
- 			fi
 
- 			local g
 
- 			for g in "${globbed[@]}"; do
 
- 				local srcG="$dDir/$g" dstG="$dst/$dDirName/$g"
 
- 				if [ -z "$failureMatters" ] && [ ! -e "$srcG" ]; then
 
- 					continue
 
- 				fi
 
- 				local gDir; gDir="$(dirname "$dstG")"
 
- 				mkdir -p "$gDir"
 
- 				cp -alT "$srcG" "$dstG"
 
- 				if [ -n "$listTarballContents" ]; then
 
- 					case "$g" in
 
- 						*.tar.* | *.tgz)
 
- 							if [ -s "$dstG" ]; then
 
- 								_tar-t -f "$dstG" > "$dstG  'tar -t'"
 
- 							fi
 
- 							;;
 
- 					esac
 
- 				fi
 
- 			done
 
- 		done
 
- 	done
 
- }
 
- # a "bashbrew cat" template that gives us the last / "least specific" tags for the arguments
 
- # (in other words, this is "bashbrew list --uniq" but last instead of first)
 
- templateLastTags='
 
- 	{{- range .TagEntries -}}
 
- 		{{- $.RepoName -}}
 
- 		{{- ":" -}}
 
- 		{{- .Tags | last -}}
 
- 		{{- "\n" -}}
 
- 	{{- end -}}
 
- '
 
- _metadata-files() {
 
- 	if [ "$#" -gt 0 ]; then
 
- 		bashbrew list "$@" 2>>temp/_bashbrew.err | sort -uV > temp/_bashbrew-list || :
 
- 		bashbrew cat --format '{{ range .Entries }}{{ range .Architectures }}{{ . }}{{ "\n" }}{{ end }}{{ end }}' "$@" 2>>temp/_bashbrew.err | sort -u > temp/_bashbrew-arches || :
 
- 		"$diffDir/_bashbrew-cat-sorted.sh" "$@" 2>>temp/_bashbrew.err > temp/_bashbrew-cat || :
 
- 		# piping "bashbrew list" first so that .TagEntries is filled up (keeping "templateLastTags" simpler)
 
- 		# sorting that by version number so it's ~stable
 
- 		# then doing --build-order on that, which is a "stable sort"
 
- 		# then redoing that list back into "templateLastTags" so we get the tags we want listed (not the tags "--uniq" chooses)
 
- 		bashbrew list --uniq "$@" \
 
- 			| xargs -r bashbrew cat --format "$templateLastTags" \
 
- 			| sort -V \
 
- 			| xargs -r bashbrew list --uniq --build-order 2>>temp/_bashbrew.err \
 
- 			| xargs -r bashbrew cat --format "$templateLastTags" 2>>temp/_bashbrew.err \
 
- 			> temp/_bashbrew-list-build-order || :
 
- 		# oci images can't be fetched with ArchDockerFroms
 
- 		# todo: use each first arch instead of current arch
 
- 		bashbrew fetch --arch-filter "$@"
 
- 		script="$(bashbrew cat --format "$template" "$@")"
 
- 		mkdir tar
 
- 		( eval "$script" | tar -xiC tar )
 
- 		copy-tar tar temp
 
- 		rm -rf tar
 
- 		# TODO we should *also* validate that our lists ended up non-empty 😬
 
- 		cat >&2 temp/_bashbrew.err
 
- 	fi
 
- 	if [ -n "$externalPins" ] && command -v crane &> /dev/null; then
 
- 		local file
 
- 		for file in $externalPins; do
 
- 			[ -e "oi/$file" ] || continue
 
- 			local pin digest dir
 
- 			pin="$("$diffDir/.external-pins/tag.sh" "$file")"
 
- 			digest="$(< "oi/$file")"
 
- 			dir="temp/$file"
 
- 			mkdir -p "$dir"
 
- 			bashbrew remote arches --json "$pin@$digest" | _jq > "$dir/bashbrew.json"
 
- 			local manifests manifest
 
- 			manifests="$(jq -r '
 
- 				[ (
 
- 					.arches
 
- 					| if has(env.BASHBREW_ARCH) then
 
- 						.[env.BASHBREW_ARCH]
 
- 					else
 
- 						.[keys_unsorted | first]
 
- 					end
 
- 				)[].digest | @sh ]
 
- 				| join(" ")
 
- 			' "$dir/bashbrew.json")"
 
- 			eval "manifests=( $manifests )"
 
- 			for manifest in "${manifests[@]}"; do
 
- 				crane manifest "$pin@$manifest" | _jq > "$dir/manifest-${manifest//:/_}.json"
 
- 				local config
 
- 				config="$(jq -r '.config.digest' "$dir/manifest-${manifest//:/_}.json")"
 
- 				crane blob "$pin@$config" | _jq > "$dir/manifest-${manifest//:/_}-config.json"
 
- 			done
 
- 		done
 
- 	fi
 
- }
 
- mkdir temp
 
- git -C temp init --quiet
 
- git -C temp config user.name 'Bogus'
 
- git -C temp config user.email 'bogus@bogus'
 
- # handle "new-image" PRs gracefully
 
- for img; do touch "$BASHBREW_LIBRARY/$img"; [ -s "$BASHBREW_LIBRARY/$img" ] || echo 'Maintainers: New Image! :D (@docker-library-bot)' > "$BASHBREW_LIBRARY/$img"; done
 
- _metadata-files "$@"
 
- git -C temp add . || :
 
- git -C temp commit --quiet --allow-empty -m 'initial' || :
 
- git -C oi clean --quiet --force
 
- git -C oi checkout --quiet pull
 
- # handle "deleted-image" PRs gracefully :(
 
- for img; do touch "$BASHBREW_LIBRARY/$img"; [ -s "$BASHBREW_LIBRARY/$img" ] || echo 'Maintainers: Deleted Image D: (@docker-library-bot)' > "$BASHBREW_LIBRARY/$img"; done
 
- git -C temp rm --quiet -rf . || :
 
- _metadata-files "$@"
 
- git -C temp add .
 
- git -C temp diff \
 
- 	--find-copies-harder \
 
- 	--find-copies="$findCopies" \
 
- 	--find-renames="$findCopies" \
 
- 	--ignore-blank-lines \
 
- 	--ignore-space-at-eol \
 
- 	--ignore-space-change \
 
- 	--irreversible-delete \
 
- 	--minimal \
 
- 	--staged
 
 
  |