deploy/ci/scripts/docker-image-out-asset
#!/bin/bash
# vim: set ft=sh
set -e -u
exec 3>&1 # make stdout available as fd 3 for the result
exec 1>&2 # redirect all output to stderr for logging
source $(dirname $0)/common.sh
source=$1
if [ -z "$source" ]; then
echo "usage: $0 <path/to/source>"
exit 1
fi
# for jq
PATH=/usr/local/bin:$PATH
payload=$(mktemp /tmp/resource-in.XXXXXX)
cat > $payload <&0
cd $source
insecure_registries=$(jq -r '.source.insecure_registries // [] | join(" ")' < $payload)
registry_mirror=$(jq -r '.source.registry_mirror // ""' < $payload)
username=$(jq -r '.source.username // ""' < $payload)
password=$(jq -r '.source.password // ""' < $payload)
repository=$(jq -r '.source.repository // ""' < $payload)
ca_certs=$(jq -r '.source.ca_certs // []' < $payload)
client_certs=$(jq -r '.source.client_certs // []' < $payload)
max_concurrent_downloads=$(jq -r '.source.max_concurrent_downloads // 3' < $payload)
max_concurrent_uploads=$(jq -r '.source.max_concurrent_uploads // 3' < $payload)
export AWS_ACCESS_KEY_ID=$(jq -r '.source.aws_access_key_id // ""' < $payload)
export AWS_SECRET_ACCESS_KEY=$(jq -r '.source.aws_secret_access_key // ""' < $payload)
export AWS_SESSION_TOKEN=$(jq -r '.source.aws_session_token // ""' < $payload)
if private_registry "${repository}" ; then
registry="$(extract_registry "${repository}")"
else
registry=
fi
certs_to_file "$ca_certs"
set_client_certs "$client_certs"
start_docker \
"${max_concurrent_downloads}" \
"${max_concurrent_uploads}" \
"$insecure_registries" \
"$registry_mirror"
log_in "$username" "$password" "$registry"
tag_source=$(jq -r '.source.tag // "latest"' < $payload)
tag_params=$(jq -r '.params.tag_file // ""' < $payload)
# for backwards compatibility, check `tag` if `tag_file` is empty
if [ -z "$tag_params" ]; then
tag_params=$(jq -r '.params.tag // ""' < $payload)
fi
tag_prefix=$(jq -r '.params.tag_prefix // ""' < $payload)
additional_tags=$(jq -r '.params.additional_tags // ""' < $payload)
need_tag_as_latest=$(jq -r '.params.tag_as_latest // "false"' < $payload)
build_args=$(jq -r '.params.build_args // {}' < $payload)
build_args_file=$(jq -r '.params.build_args_file // ""' < $payload)
labels=$(jq -r '.params.labels // {}' < $payload)
labels_file=$(jq -r '.params.labels_file // ""' < $payload)
patch_base_reg=$(jq -r '.params.patch_base_reg // ""' < $payload)
patch_base_tag=$(jq -r '.params.patch_base_tag // ""' < $payload)
# Support squashing the layers
squash=$(jq -r '.params.squash // "false"' < $payload)
tag_name=""
if [ -n "$tag_params" ]; then
if [ ! -f "$tag_params" ]; then
echo "tag file '$tag_params' does not exist"
exit 1
fi
tag_name="${tag_prefix}$(cat $tag_params)"
else
tag_name="$tag_source"
fi
additional_tag_names=""
if [ -n "$additional_tags" ]; then
if [ ! -f "$additional_tags" ]; then
echo "additional tags file '$additional_tags' does not exist"
exit 1
fi
additional_tag_names="$(cat $additional_tags)"
fi
if [ -z "$repository" ]; then
echo "must specify repository"
exit 1
fi
load=$(jq -r '.params.load // ""' < $payload)
load_base=$(jq -r '.params.load_base // ""' < $payload)
load_bases=$(jq -r '.params.load_bases // empty' < $payload)
build=$(jq -r '.params.build // ""' < $payload)
cache=$(jq -r '.params.cache' < $payload)
cache_tag=$(jq -r ".params.cache_tag // \"${tag_name}\"" < $payload)
cache_from=$(jq -r '.params.cache_from // empty' < $payload)
dockerfile=$(jq -r ".params.dockerfile // \"${build}/Dockerfile\"" < $payload)
load_file=$(jq -r '.params.load_file // ""' < $payload)
load_repository=$(jq -r '.params.load_repository // ""' < $payload)
load_tag=$(jq -r '.params.load_tag // "latest"' < $payload)
import_file=$(jq -r '.params.import_file // ""' < $payload)
pull_repository=$(jq -r '.params.pull_repository // ""' < $payload)
pull_tag=$(jq -r '.params.pull_tag // "latest"' < $payload)
target_name=$(jq -r '.params.target_name // ""' < $payload)
prebuild_script=$(jq -r '.params.prebuild_script // ""' < $payload)
if [ -n "$load" ]; then
docker load -i "${load}/image"
docker tag $(cat "${load}/image-id") "${repository}:${tag_name}"
elif [ -n "$build" ]; then
if [ ! -f "$dockerfile" ]; then
echo "It doesn't appear that given Dockerfile: \"$dockerfile\" is a file"
exit 1
fi
# Squash support
squashArg=""
if [ "${squash}" == "true" ]; then
squashArg=" --squash "
fi
echo "Squash ${squash} => args: ${squashArg}"
# Patch Base registry in Dockerfile
# Must be '@' delimited
if [ -n "${patch_base_reg}" ]; then
sed -i "s@${patch_base_reg}@g" ${dockerfile}
fi
if [ -n "${prebuild_script}" ]; then
ROOT_DIR=$(pwd)
cd ${build}
./${prebuild_script}
cd ${ROOT_DIR}
fi
if [ -n "${patch_base_tag}" ]; then
sed -i "s@${patch_base_tag}@g" ${dockerfile}
fi
load_images=()
if [ -n "$load_base" ]; then
load_images+=("$load_base")
fi
for load_image in $(echo $load_bases | jq -r '.[]'); do
load_images+=("$load_image")
done
for load_image in $(echo $cache_from | jq -r '.[]'); do
load_images+=("$load_image")
done
for load_image in "${load_images[@]}"; do
docker load -i "${load_image}/image"
docker tag \
"$(cat "${load_image}/image-id")" \
"$(cat "${load_image}/repository"):$(cat "${load_image}/tag")"
done
cache_from_args=()
if [ "$cache" = "true" ]; then
if docker_pull "${repository}:${cache_tag}"; then
cache_from_args+=("--cache-from ${repository}:${cache_tag}")
fi
fi
if [ -n "$cache_from" ]; then
for cache_from_dir in $(echo $cache_from | jq -r '.[]'); do
cache_image="$(cat "${cache_from_dir}/repository")"
cache_tag="$(cat "${cache_from_dir}/tag")"
cache_from_args+=("--cache-from ${cache_image}:${cache_tag}")
done
fi
cache_from="${cache_from_args[@]}"
expanded_build_args=()
# propagate proxy settings to image builder
for proxy_var in http_proxy https_proxy no_proxy ; do
if [ -n "${!proxy_var:-}" ]; then
expanded_build_args+=("--build-arg")
expanded_build_args+=("${proxy_var}=${!proxy_var}")
fi
done
build_arg_keys=($(echo "$build_args" | jq -r 'keys | join(" ")'))
if [ "${#build_arg_keys[@]}" -gt 0 ]; then
for key in "${build_arg_keys[@]}"; do
value=$(echo "$build_args" | jq -r --arg "k" "$key" '.[$k]')
for var in BUILD_ID BUILD_NAME BUILD_JOB_NAME BUILD_PIPELINE_NAME BUILD_TEAM_NAME ATC_EXTERNAL_URL; do
value="${value//\$$var/${!var:-}}"
value="${value//\$\{$var\}/${!var:-}}"
done
expanded_build_args+=("--build-arg")
expanded_build_args+=("${key}=${value}")
done
fi
if [ -n "$build_args_file" ]; then
if jq . "$build_args_file" >/dev/null 2>&1; then
build_arg_keys=($(jq -r 'keys | join(" ")' "$build_args_file"))
if [ "${#build_arg_keys[@]}" -gt 0 ]; then
for key in "${build_arg_keys[@]}"; do
value=$(jq -r --arg "k" "$key" '.[$k]' "$build_args_file")
expanded_build_args+=("--build-arg")
expanded_build_args+=("${key}=${value}")
done
fi
else
echo "Failed to parse build_args_file ($build_args_file)"
exit 1
fi
fi
expanded_labels=()
label_keys=($(echo "$labels" | jq -r 'keys | join(" ")'))
if [ "${#label_keys[@]}" -gt 0 ]; then
for key in "${label_keys[@]}"; do
value=$(echo "$labels" | jq -r --arg "k" "$key" '.[$k]')
expanded_labels+=("--label")
expanded_labels+=("${key}=${value}")
done
fi
if [ -n "$labels_file" ]; then
labels_keys=($(jq -r 'keys | join(" ")' "$labels_file"))
if [ "${#labels_keys[@]}" -gt 0 ]; then
for key in "${labels_keys[@]}"; do
value=$(jq -r --arg "k" "$key" '.[$k]' "$labels_file")
expanded_labels+=("--label")
expanded_labels+=("${key}=${value}")
done
fi
fi
target=()
if [ -n "${target_name}" ]; then
target+=("--target")
target+=("${target_name}")
fi
ECR_REGISTRY_PATTERN='/[a-zA-Z0-9][a-zA-Z0-9_-]*\.dkr\.ecr\.[a-zA-Z0-9][a-zA-Z0-9_-]*\.amazonaws\.com(\.cn)?[^ ]*/'
ecr_images=$(egrep '^\s*FROM|^\s*ARG' ${dockerfile} | \
awk "match(\$0,${ECR_REGISTRY_PATTERN}){print substr(\$0, RSTART, RLENGTH)}" )
if [ -n "$ecr_images" ]; then
for ecr_image in $ecr_images
do
# pull will perform an authentication process needed for ECR
# there is an experimental endpoint to support long running sessions
# docker cli does not support it yet though
# see https://github.com/moby/moby/pull/32677
# and https://github.com/awslabs/amazon-ecr-credential-helper/issues/9
docker pull "${ecr_image}"
done
fi
docker build ${squashArg} -t "${repository}:${tag_name}" "${target[@]}" "${expanded_build_args[@]}" "${expanded_labels[@]}" -f "$dockerfile" $cache_from "$build"
elif [ -n "$load_file" ]; then
if [ -n "$load_repository" ]; then
docker load -i "$load_file"
docker tag "${load_repository}:${load_tag}" "${repository}:${tag_name}"
else
echo "must specify load_repository param"
exit 1
fi
elif [ -n "$import_file" ]; then
cat "$import_file" | docker import - "${repository}:${tag_name}"
elif [ -n "$pull_repository" ]; then
docker pull "${pull_repository}:${pull_tag}"
docker tag "${pull_repository}:${pull_tag}" "${repository}:${tag_name}"
else
echo "must specify build, load, load_file, import_file, or pull_repository params"
exit 1
fi
image_id="$(image_from_tag "$repository" "$tag_name")"
# afaict there's no clean way to get the digest after a push. docker prints
# this line at the end at least:
#
# (tagname): digest: (digest) size: (size)
#
# so just parse it out
# careful to not let 'tee' mask exit status
{
if ! docker push "${repository}:${tag_name}"; then
touch /tmp/push-failed
fi
} | tee push-output
if [ -e /tmp/push-failed ]; then
exit 1
fi
digest="$(grep 'digest' push-output | awk '{print $3}')"
if [ "$need_tag_as_latest" = "true" ] && [ "${tag_name}" != "latest" ]; then
docker tag "${repository}:${tag_name}" "${repository}:latest"
docker push "${repository}:latest"
echo "${repository}:${tag_name} tagged as latest"
fi
if [ -n "$additional_tag_names" ] ; then
for additional_tag in $additional_tag_names; do
docker tag "${repository}:${tag_name}" "${repository}:${additional_tag}"
docker push "${repository}:${additional_tag}"
echo "${repository}:${tag_name} tagged as ${additional_tag}"
done
fi
jq -n "{
version: {
digest: $(echo $digest | jq -R .)
},
metadata: [
{ name: \"image\", value: $(echo $image_id | head -c 12 | jq -R .) }
]
}" >&3