cloudfoundry/stratos

View on GitHub
deploy/ci/scripts/docker-image-out-asset

Summary

Maintainability
Test Coverage
#!/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