我有一个模块,可以创建 lambda 所需的所有基础设施,包括存储图像的 ECR:
resource "aws_ecr_repository" "image_storage" {
name = "${var.project}/${var.environment}/lambda"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_lambda_function" "executable" {
function_name = var.function_name
image_uri = "${aws_ecr_repository.image_storage.repository_url}:latest"
package_type = "Image"
role = aws_iam_role.lambda.arn
}
这个问题当然是它失败了,因为当 aws_lambda_function 运行时,存储库在那里,但图像不在那里:图像是使用我的 CI/CD 上传的。
所以这是一个先有鸡还是先有蛋的问题。 Terraform 应该仅用于基础设施,因此我不能/不应该使用它来上传图像(即使是虚拟图像),但我无法实例化基础设施,除非在存储库和 lambda 创建步骤之间上传图像。
我能想到的唯一解决方案是与 lambda 分开创建 ECR,然后以某种方式将其链接为我的 lambda 中的现有 aws 资源,但这似乎有点笨拙。
有什么建议吗?
我最终使用了以下解决方案,其中上传虚拟图像作为部分资源创建。
resource "aws_ecr_repository" "listing" {
name = "myLambda"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
provisioner "local-exec" {
command = <<-EOT
docker pull alpine
docker tag alpine dummy_container
docker push dummy_container
EOT
}
}
基于 @przemek-lach 的答案加上 @halloei 的评论,我想发布一个完全工作的 ECR 存储库,该存储库配备了虚拟图像
data "aws_ecr_authorization_token" "token" {}
resource "aws_ecr_repository" "repository" {
name = "lambda-${local.name}-${local.environment}"
image_tag_mutability = "MUTABLE"
tags = local.common_tags
image_scanning_configuration {
scan_on_push = true
}
lifecycle {
ignore_changes = all
}
provisioner "local-exec" {
# This is a 1-time execution to put a dummy image into the ECR repo, so
# terraform provisioning works on the lambda function. Otherwise there is
# a chicken-egg scenario where the lambda can't be provisioned because no
# image exists in the ECR
command = <<EOF
docker login ${data.aws_ecr_authorization_token.token.proxy_endpoint} -u AWS -p ${data.aws_ecr_authorization_token.token.password}
docker pull alpine
docker tag alpine ${aws_ecr_repository.repository.repository_url}:SOME_TAG
docker push ${aws_ecr_repository.repository.repository_url}:SOME_TAG
EOF
}
}
这完全是先有鸡还是先有蛋的问题。
下面是一个解决方案,其唯一依赖项是
curl
;
它使用 docker pull && docker push
来完成 curl
docker 的 API v2 规范。您还需要 aws
cli 作为依赖项,按照 fetchEcrToken
的要求(请参阅 helpers.sh
)。
最终的 terraform 用法如下所示:
locals {
repo_name = "my-repo"
ecr_fqdn = replace(aws_ecr_repository.this.repository_url, "//.*$/", "") # remove everything after first slash
}
resource "aws_ecr_repository" "this" {
name = local.repo_name
image_tag_mutability = "MUTABLE"
}
module "ecr_repo_image" {
source = "./ecr_curl"
name = "my-project"
pull_ecr_is_public = true
pull_repo_fqdn = "public.ecr.aws"
pull_repo_name = "lambda/provided"
pull_image_tag = "al2-x86_64"
push_ecr_is_public = false
push_repo_fqdn = local.ecr_fqdn
push_repo_name = local.repo_name
push_image_tag = "latest"
}
resource "terraform_data" "ecr_repo_image" {
triggers_replace = [
aws_ecr_repository.this.repository_url,
]
provisioner "local-exec" {
command = module.ecr_repo_image.command
}
}
以下是实现上述目标所需的依赖项。
locals {
pull_then_push_path = "${path.module}/pull_then_push.sh"
helpers_path = "${path.module}/helpers.sh"
download_dir_path = "${path.module}/${var.name}-image"
}
output "command" {
value = <<-EOF
source '${local.helpers_path}'
PULL_CURL_AUTH_HEADER=$(IS_PUBLIC='${var.pull_ecr_is_public}' curlAuthHeader) \
PULL_REPO_FQDN='${var.pull_repo_fqdn}' \
PULL_REPO_NAME='${var.pull_repo_name}' \
PULL_IMAGE_TAG='${var.pull_image_tag}' \
PULL_DOWNLOAD_DIR_PATH='${local.download_dir_path}' \
PUSH_CURL_AUTH_HEADER=$(IS_PUBLIC='${var.push_ecr_is_public}' curlAuthHeader) \
PUSH_REPO_FQDN='${var.push_repo_fqdn}' \
PUSH_REPO_NAME='${var.push_repo_name}' \
PUSH_IMAGE_TAG='${var.push_image_tag}' \
'${local.pull_then_push_path}'
EOF
}
variable "name" {
description = "The name to uniquely identify the module's instance, e.g. my-lambda-function"
type = string
}
variable "pull_ecr_is_public" {
description = "If the ECR repo we're pulling from is public (vs. private)"
type = bool
}
variable "pull_repo_fqdn" {
description = "The FQDN of the ECR repo we're pulling from, e.g. public.ecr.aws"
type = string
}
variable "pull_repo_name" {
description = "The name of the ECR repo we're pulling from, e.g. my-repo"
type = string
}
variable "pull_image_tag" {
description = "The tag of the image we're pulling, e.g. latest"
type = string
}
variable "push_ecr_is_public" {
description = "If the ECR repo we're pushing to is public (vs. private)"
type = bool
}
variable "push_repo_fqdn" {
description = "The FQDN of the ECR repo we're pushing to, e.g. 012345678910.dkr.ecr.<region-name>.amazonaws.com"
type = string
}
variable "push_repo_name" {
description = "The name of the ECR repo we're pushing to, e.g. my-repo"
type = string
}
variable "push_image_tag" {
description = "The tag of the image we're pushing, e.g. latest"
type = string
}
fetchEcrToken() {
# https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html#registry_auth_http
# https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html#registry_auth_http
base_ecr_command='ecr'
query='authorizationData[].authorizationToken'
if [[ "$IS_PUBLIC" == 'true' ]]; then
base_ecr_command='ecr-public'
query='authorizationData.authorizationToken'
elif [[ "$IS_PUBLIC" != 'false' ]]; then
echo "fetchEcrToken: expected IS_PUBLIC to be 'true' or 'false' but received '$IS_PUBLIC'" >&2
return 1
fi
ecr_token=$(aws "$base_ecr_command" get-authorization-token --output text --query "$query")
if [[ -z "$ecr_token" || "$ecr_token" == 'None' ]]; then
echo 'fetchEcrToken: Failed to fetch ecr_token' >&2
return 1
fi
echo "$ecr_token"
}
curlAuthHeader() {
token_type='Basic'
if [[ "$IS_PUBLIC" == 'true' ]]; then
token_type='Bearer'
elif [[ "$IS_PUBLIC" != 'false' ]]; then
echo "curlAuthHeader: expected IS_PUBLIC to be 'true' or 'false' but received '$IS_PUBLIC'" >&2
return 1
fi
token=$(IS_PUBLIC="$IS_PUBLIC" fetchEcrToken)
echo "Authorization: $token_type $token"
}
curlWithAuthHeader() {
auth_params=()
if [[ -n "$CURL_AUTH_HEADER" ]]; then
auth_params+='-H'
auth_params+="$CURL_AUTH_HEADER"
fi
curl "${auth_params[@]}" "$@"
}
curlStatus() {
curl_output="$1"
if [[ -z "$curl_output" ]]; then
echo 'curlStatus: no $1 for curl_output' >&2
return 1
fi
echo "$curl_output" | grep '^< HTTP/[0-9\.]* ' | tail -n 1 | awk '{print $3}'
}
repoUrl() {
if [[ -z "$REPO_FQDN" ]]; then
echo 'repoUrl: no value for REPO_FQDN' >&2
return 1
fi
if [[ -z "$REPO_NAME" ]]; then
echo 'repoUrl: no value for REPO_NAME' >&2
return 1
fi
echo "https://$REPO_FQDN/v2/$REPO_NAME"
}
var_is_defined() {
var_name="$1"
if [[ -z "$var_name" ]]; then
echo 'var_is_defined: no $1 for var_name' >&2
return 1
fi
if set | grep -q "^$var_name="; then
echo 'true'
else
echo 'false'
fi
}
#!/bin/sh
set -e
##
# `docker pull` from a repo and then `docker push` to another repo with just curl
# https://distribution.github.io/distribution/spec/api/
#
# Example usage:
# source './helpers.sh'
# PULL_CURL_AUTH_HEADER=$(IS_PUBLIC='true' curlAuthHeader) \
# PULL_REPO_FQDN='public.ecr.aws' \
# PULL_REPO_NAME='lambda/provided' \
# PULL_IMAGE_TAG='al2-x86_64' \
# PULL_DOWNLOAD_DIR_PATH='/path/to/download/image/contents' \
# PUSH_CURL_AUTH_HEADER=$(IS_PUBLIC='false' curlAuthHeader) \
# PUSH_REPO_FQDN='123456789012.dkr.ecr.us-east-1.amazonaws.com' \
# PUSH_REPO_NAME='my-repo' \
# PUSH_IMAGE_TAG='latest' \
# ./pull_then_push.sh
##
##
# Initialize
##
SCRIPT_NAME="${0##*/}" # https://stackoverflow.com/a/192699
dir_path() {
# https://stackoverflow.com/a/43919044
a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a"; pwd)
echo "$BINDIR"
}
DIR_PATH=`dir_path`
source "$DIR_PATH/helpers.sh"
##
# Assert inputs
##
_pull_curl_auth_header_is_defined=$(var_is_defined 'PULL_CURL_AUTH_HEADER')
if [[ $_pull_curl_auth_header_is_defined == 'false' ]]; then
echo "$SCRIPT_NAME: \$PULL_CURL_AUTH_HEADER is undefined. If no auth header required then set PULL_CURL_AUTH_HEADER=''" >&2
exit 1
elif [[ $_pull_curl_auth_header_is_defined != 'true' ]]; then
echo "$SCRIPT_NAME: unexpected value of '$_pull_curl_auth_header_is_defined' for _pull_curl_auth_header_is_defined" >&2
exit 1
fi
if [[ -z "$PULL_REPO_FQDN" ]]; then
echo "$SCRIPT_NAME: no value for \$PULL_REPO_FQDN" >&2
exit 1
fi
if [[ -z "$PULL_REPO_NAME" ]]; then
echo "$SCRIPT_NAME: no value for \$PULL_REPO_NAME" >&2
exit 1
fi
if [[ -z "$PULL_IMAGE_TAG" ]]; then
echo "$SCRIPT_NAME: no value for \$PULL_IMAGE_TAG" >&2
exit 1
fi
if [[ -z "$PULL_DOWNLOAD_DIR_PATH" ]]; then
echo "$SCRIPT_NAME: no value for \$PULL_DOWNLOAD_DIR_PATH" >&2
exit 1
fi
_push_curl_auth_header_is_defined=$(var_is_defined 'PUSH_CURL_AUTH_HEADER')
if [[ $_push_curl_auth_header_is_defined == 'false' ]]; then
echo "$SCRIPT_NAME: \$PUSH_CURL_AUTH_HEADER is undefined. If no auth header required then set PUSH_CURL_AUTH_HEADER=''" >&2
exit 1
elif [[ $_push_curl_auth_header_is_defined != 'true' ]]; then
echo "$SCRIPT_NAME: unexpected value of '$_push_curl_auth_header_is_defined' for _push_curl_auth_header_is_defined" >&2
exit 1
fi
if [[ -z "$PUSH_REPO_FQDN" ]]; then
echo "$SCRIPT_NAME: no value for \$PUSH_REPO_FQDN" >&2
exit 1
fi
if [[ -z "$PUSH_REPO_NAME" ]]; then
echo "$SCRIPT_NAME: no value for \$PUSH_REPO_NAME" >&2
exit 1
fi
if [[ -z "$PUSH_IMAGE_TAG" ]]; then
echo "$SCRIPT_NAME: no value for \$PUSH_IMAGE_TAG" >&2
exit 1
fi
##
# Pull image
##
export CURL_AUTH_HEADER="$PULL_CURL_AUTH_HEADER"
pull_info=$(
REPO_FQDN="$PULL_REPO_FQDN" \
REPO_NAME="$PULL_REPO_NAME" \
IMAGE_TAG="$PULL_IMAGE_TAG" \
DOWNLOAD_DIR_PATH="$PULL_DOWNLOAD_DIR_PATH" \
"$DIR_PATH/pull.sh"
)
##
# Push image
##
export CURL_AUTH_HEADER="$PUSH_CURL_AUTH_HEADER"
push_info=$(
REPO_FQDN="$PUSH_REPO_FQDN" \
REPO_NAME="$PUSH_REPO_NAME" \
IMAGE_TAG="$PUSH_IMAGE_TAG" \
LAYER_PATHS=$(find "$PULL_DOWNLOAD_DIR_PATH/layers" -type f) \
CONFIG_PATH="$PULL_DOWNLOAD_DIR_PATH/config.json" \
"$DIR_PATH/push.sh"
)
##
# Clean up
##
unset CURL_AUTH_HEADER
rm -rf "$PULL_DOWNLOAD_DIR_PATH"
#!/bin/sh
set -e
##
# `docker pull` from repo with just curl
# https://distribution.github.io/distribution/spec/api/
#
# Example usage:
# export REPO_FQDN='889087436126.dkr.ecr.us-east-1.amazonaws.com'
# export REPO_NAME='points-ninja-invite-bot-beta'
# export IMAGE_TAG='latest'
# export DOWNLOAD_DIR_PATH='/path/to/download/image/contents'
# ./pull.sh
##
##
# Initialize
##
SCRIPT_NAME="${0##*/}" # https://stackoverflow.com/a/192699
dir_path() {
# https://stackoverflow.com/a/43919044
a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a"; pwd)
echo "$BINDIR"
}
DIR_PATH=`dir_path`
source "$DIR_PATH/helpers.sh"
##
# Assert inputs
##
if [[ -z "$REPO_FQDN" ]]; then
echo "$SCRIPT_NAME: no value for \$REPO_FQDN" >&2
exit 1
fi
if [[ -z "$REPO_NAME" ]]; then
echo "$SCRIPT_NAME: no value for \$REPO_NAME" >&2
exit 1
fi
if [[ -z "$IMAGE_TAG" ]]; then
echo "$SCRIPT_NAME: no value for \$IMAGE_TAG" >&2
exit 1
fi
if [[ -z "$DOWNLOAD_DIR_PATH" ]]; then
echo "$SCRIPT_NAME: no value for \$DOWNLOAD_DIR_PATH" >&2
exit 1
fi
##
# Helper functions
##
jqFormatFile() {
file_path="$1"
if [[ -z "$file_path" ]]; then
echo 'jqFormat: no $1 for file_path' >&2
return 1
fi
jq_json=$(cat "$file_path" | jq)
echo "$jq_json" > "$file_path"
}
digestValue() {
digest="$1"
if [[ -z "$digest" ]]; then
echo 'digestValue: no $1 for digest' >&2
return 1
fi
echo "${digest##*:}" # echo everything after the :
}
fetchManifest() {
echo 'fetchManifest: starting' >&2
if [[ -z "$REPO_URL" ]]; then
echo 'fetchManifest: no value for $REPO_URL' >&2
return 1
fi
if [[ -z "$IMAGE_TAG" ]]; then
echo 'fetchManifest: no value for $IMAGE_TAG' >&2
return 1
fi
curl_output=$(
curlWithAuthHeader -L -v \
"$REPO_URL/manifests/$IMAGE_TAG" \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '200' ]]; then
echo "fetchManifest: curl returned http status code of '$status_code'" >&2
return 1
fi
line_num=$(echo "$curl_output" | grep -n '"schemaVersion": 2' | awk '{print $1}')
line_num=$(( ${line_num%:*} - 1 ))
echo "$curl_output" | tail -n "+$line_num"
echo 'fetchManifest: complete' >&2
}
downloadLayer() {
if [[ -z "$REPO_URL" ]]; then
echo 'downloadLayer: no value for $REPO_URL' >&2
return 1
fi
digest="$1"
if [[ -z "$digest" ]]; then
echo 'downloadLayer: no $1 for digest' >&2
return 1
fi
file_path="$2"
if [[ -z "$file_path" ]]; then
echo 'downloadLayer: no $2 for file_path' >&2
return 1
fi
url="$REPO_URL/blobs/$digest"
echo "downloadLayer: starting downloading '$url' to '$file_path'" >&2
curl_output=$(
curlWithAuthHeader -L -v \
--output "$file_path" \
"$url" \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '200' ]]; then
echo "downloadLayer: curl returned http status code of '$status_code'" >&2
return 1
fi
echo "downloadLayer: complete downloading '$url' to '$file_path'" >&2
}
download_image() {
dir_path="$1"
if [[ -z "$dir_path" ]]; then
echo 'download_image: no $1 for dir_path' >&2
return 1
fi
mkdir -p "$dir_path"
rm -rf "$dir_path"/*.json
manifest_json=$(fetchManifest)
manifest_file_path="$dir_path/manifest-pull.json"
echo "$manifest_json" > "$manifest_file_path"
config_digest=$(echo "$manifest_json" | jq -r '.config.digest')
if [[ -z "$config_digest" || "$config_digest" == 'null' ]]; then
echo 'download_image: failed to parse config_digest from manifest' >&2
return 1
fi
config_file_path="$dir_path/config.json"
downloadLayer "$config_digest" "$config_file_path"
jqFormatFile "$config_file_path"
layers_dir_path="$dir_path/layers"
mkdir -p "$layers_dir_path"
rm -rf "$layers_dir_path"/*.blob
layer_digests=$(echo "$manifest_json" | jq -r '.layers | .[] | .digest')
echo "$layer_digests" | while read layer_digest ; do
layer_file_path="$layers_dir_path/$(digestValue "$layer_digest").blob"
downloadLayer "$layer_digest" "$layer_file_path"
done
echo "download_image: completed download to '$dir_path'" >&2
quote='"'
echo "{ ${quote}manifest_file_path${quote}: ${quote}$manifest_file_path${quote}, ${quote}config_file_path${quote}: ${quote}$config_file_path${quote}, ${quote}layers_dir_path${quote}: ${quote}$layers_dir_path${quote} }" > jq
}
##
# Configure global variables
##
REPO_URL=$(REPO_FQDN="$REPO_FQDN" REPO_NAME="$REPO_NAME" repoUrl)
##
# Do the work
##
download_image "$DOWNLOAD_DIR_PATH"
#!/bin/sh
set -e
##
# `docker push` to repo with just curl
# https://distribution.github.io/distribution/spec/api/
# https://stackoverflow.com/a/59901770
#
# Example usage:
# export REPO_FQDN='889087436126.dkr.ecr.us-east-1.amazonaws.com'
# export REPO_NAME='points-ninja-invite-bot-beta'
# export IMAGE_TAG='latest'
# export LAYER_PATHS='./assets/layer.blob'
# export CONFIG_PATH='./assets/config.json'
# ./push.sh
##
##
# Initialize
##
SCRIPT_NAME="${0##*/}" # https://stackoverflow.com/a/192699
dir_path() {
# https://stackoverflow.com/a/43919044
a="/$0"; a="${a%/*}"; a="${a:-.}"; a="${a##/}/"; BINDIR=$(cd "$a"; pwd)
echo "$BINDIR"
}
DIR_PATH=`dir_path`
source "$DIR_PATH/helpers.sh"
##
# Assert inputs
##
if [[ -z "$REPO_FQDN" ]]; then
echo "$SCRIPT_NAME: no value for \$REPO_FQDN" >&2
exit 1
fi
if [[ -z "$REPO_NAME" ]]; then
echo "$SCRIPT_NAME: no value for \$REPO_NAME" >&2
exit 1
fi
if [[ -z "$IMAGE_TAG" ]]; then
echo "$SCRIPT_NAME: no value for \$IMAGE_TAG" >&2
exit 1
fi
if [[ -z "$LAYER_PATHS" ]]; then
echo "$SCRIPT_NAME: no value for \$LAYER_PATHS" >&2
exit 1
fi
if [[ -z "$CONFIG_PATH" ]]; then
echo "$SCRIPT_NAME: no value for \$CONFIG_PATH" >&2
exit 1
fi
echo "$LAYER_PATHS" | while read layer_path ; do
if [[ ! -f "$layer_path" ]]; then
echo "$SCRIPT_NAME: no file exists at layer_path of '$layer_path'" >&2
exit 1
fi
done
if [[ ! -f "$CONFIG_PATH" ]]; then
echo "$SCRIPT_NAME: no file exists at CONFIG_PATH='$CONFIG_PATH'" >&2
exit 1
fi
##
# Helper functions
##
fileSize() {
file_path="$1"
if [[ -z "$file_path" ]]; then
echo 'fileSize: no $1 for file_path' >&2
return 1
fi
echo $(( `wc -c < "$file_path"` ))
}
fileDigest() {
file_path="$1"
if [[ -z "$file_path" ]]; then
echo 'fileDigest: no $1 for file_path' >&2
return 1
fi
echo "sha256:$(sha256sum "$file_path" | cut -d ' ' -f1)"
}
initiateUpload() {
if [[ -z "$REPO_URL" ]]; then
echo 'initiateUpload: no value for $REPO_URL' >&2
return 1
fi
echo 'initiateUpload: starting' >&2
curl_output=$(
curlWithAuthHeader -X POST -siL -v \
-H "Connection: close" \
"$REPO_URL/blobs/uploads" \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '202' ]]; then
echo "initiateUpload: curl returned http status code of '$status_code'" >&2
return 1
fi
location=$(
echo "$curl_output" \
| grep Location \
| sed '2q;d' \
| cut -d: -f2- \
| tr -d ' ' \
| tr -d '\r'
)
if [[ -z "$location" ]]; then
echo 'initiateUpload: Failed to parse Location from curl_output' >&2
return 1
fi
echo 'initiateUpload: complete' >&2
echo "$location"
}
patchLayer() {
echo 'patchLayer: starting' >&2
if [[ -z "$file_path" ]]; then
echo 'patchLayer: no value for $file_path' >&2
return 1
fi
if [[ -z "$file_size" ]]; then
echo 'patchLayer: no value for $file_size' >&2
return 1
fi
if [[ -z "$target_location" ]]; then
echo 'patchLayer: no value for $target_location' >&2
return 1
fi
curl_output=$(
curlWithAuthHeader -X PATCH -v \
-H "Content-Type: application/octet-stream" \
-H "Content-Length: $file_size" \
-H "Connection: close" \
--data-binary @"$file_path" \
$target_location \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '201' ]]; then
echo "patchLayer: curl returned http status code of '$status_code'" >&2
return 1
fi
location=$(
echo "$curl_output" \
| grep 'Location' \
| cut -d: -f2- \
| tr -d ' ' \
| tr -d '\r'
)
if [[ -z "$location" ]]; then
echo 'patchLayer: Failed to parse location from curl_output' >&2
return 1
fi
echo 'patchLayer: complete' >&2
echo "$location"
}
putLayer() {
echo 'putLayer: starting' >&2
if [[ -z "$file_digest" ]]; then
echo 'putLayer: no value for $file_digest' >&2
return 1
fi
if [[ -z "$target_location" ]]; then
echo 'putLayer: no value for $target_location' >&2
return 1
fi
curl_output=$(
curlWithAuthHeader -X PUT -v \
-H "Content-Type: application/octet-stream" \
-H "Content-Length: 0" \
-H "Connection: close" \
"$target_location?digest=$file_digest" \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '201' ]]; then
echo "putLayer: curl returned http status code of '$status_code'" >&2
return 1
fi
echo 'putLayer: complete' >&2
}
uploadManifest() {
if [[ -z "$REPO_URL" ]]; then
echo 'uploadManifest: no value for $REPO_URL' >&2
return 1
fi
if [[ -z "$IMAGE_TAG" ]]; then
echo 'uploadManifest: no value for $IMAGE_TAG' >&2
return 1
fi
manifest_path="$1"
if [[ -z "$manifest_path" ]]; then
echo 'uploadManifest: no $1 for manifest_path' >&2
return 1
fi
echo "uploadManifest: starting '$manifest_path'" >&2
size=$((`fileSize "$manifest_path"` - 1))
curl_output=$(
curlWithAuthHeader -X PUT -vvv \
-H "Content-Type: application/vnd.docker.distribution.manifest.v2+json" \
-H "Content-Length: $size" \
-H "Connection: close" \
-d "$(cat "$manifest_path")" \
"$REPO_URL/manifests/$IMAGE_TAG" \
2>&1
)
status_code=$(curlStatus "$curl_output")
if [[ "$status_code" != '201' ]]; then
echo "uploadManifest: curl returned http status code of '$status_code'" >&2
return 1
fi
echo "uploadManifest: complete '$manifest_path'" >&2
}
uploadImage() {
if [[ -z "$CONFIG_PATH" ]]; then
echo 'uploadImage: no value for $CONFIG_PATH' >&2
return 1
fi
if [[ -z "$LAYER_PATHS" ]]; then
echo 'uploadImage: no value for $LAYER_PATHS' >&2
return 1
fi
echo "uploadImage: starting config at '$CONFIG_PATH'" >&2
config_size=$(fileSize "$CONFIG_PATH")
config_digest=$(fileDigest "$CONFIG_PATH")
patch_location=$(initiateUpload)
put_location=$(
file_path="$CONFIG_PATH" \
file_size="$config_size" \
target_location="$patch_location" \
patchLayer
)
file_digest="$config_digest" target_location="$put_location" putLayer
echo "uploadImage: complete config at '$CONFIG_PATH'" >&2
echo 'uploadImage: starting layers' >&2
layers_json='[]'
while read layer_path ; do
echo "uploadImage: starting layer at '$layer_path'" >&2
layer_size=$(fileSize "$layer_path")
layer_digest=$(fileDigest "$layer_path")
patch_location=$(initiateUpload)
put_location=$(
file_path="$layer_path" \
file_size="$layer_size" \
target_location="$patch_location" \
patchLayer
)
file_digest="$layer_digest" target_location="$put_location" putLayer
layers_json=$(echo "$layers_json" | jq "$(cat <<-EOF
. += [{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": $layer_size,
"digest": "$layer_digest"
}]
EOF
)")
echo "uploadImage: complete layer at '$layer_path'" >&2
done <<< "$LAYER_PATHS"
echo 'uploadImage: complete layers' >&2
echo 'uploadImage: starting manifest' >&2
manifest_content="$(cat <<-EOF
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": $config_size,
"digest": "$config_digest"
},
"layers": $layers_json
}
EOF
)"
manifest_path="$(dirname "$CONFIG_PATH")/manifest-push.json"
echo "$manifest_content" | jq > "$manifest_path"
uploadManifest "$manifest_path"
echo 'uploadImage: complete manifest' >&2
}
##
# Configure global variables
##
REPO_URL=$(REPO_FQDN="$REPO_FQDN" REPO_NAME="$REPO_NAME" repoUrl)
##
# Do the work
##
uploadImage