Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-and-upload-artifact/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

# Package and upload artifact

Packages and uploads an artifact
Packages Docker images or files for every environment listed in the JSON config, using the AWS account, bucket, role, and repository data defined there. Docker sources are retagged and pushed to ECR so Terraform can deploy them to ECS. Files or folders are zipped if needed, copied to S3, and later published through the Terraform → Lambda → CloudFront flow.


## Usage

Expand Down
105 changes: 39 additions & 66 deletions package-and-upload-artifact/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: "Package and upload artifact"
description: Packages and uploads an artifact
description: >
Packages Docker images or files for every environment listed in the JSON config,
using the AWS account, bucket, role, and repository data defined there. Docker
sources are retagged and pushed to ECR so Terraform can deploy them to ECS. Files
or folders are zipped if needed, copied to S3, and later published through the
Terraform → Lambda → CloudFront flow.
inputs:
config:
description: "JSON-encoded config (.gp.cicd.json)"
Expand Down Expand Up @@ -42,10 +47,12 @@ runs:
output-env-credentials: false
output-credentials: true

- name: Package and upload artifact
- name: Upload artifact to S3
if: ${{ inputs.source-type == 'file' || inputs.source-type == 'folder' }}
shell: bash --noprofile --norc -euo pipefail {0}
id: upload
id: upload-s3-artifact
env:
ACTION_PATH: ${{ github.action_path }}
AWSCREDS: |
[profile dev]
aws_access_key_id=${{ steps.aws-credentials-dev.outputs.aws-access-key-id }}
Expand All @@ -62,72 +69,38 @@ runs:
SOURCE_TYPE: ${{ inputs.source-type }}
SOURCE_LOCATION: ${{ inputs.source-location }}
TAG: ${{ inputs.tag }}
run: |
tag="$TAG"
echo "$AWSCREDS" > /tmp/awscreds
export AWS_CONFIG_FILE="/tmp/awscreds"

if [ "$SOURCE_TYPE" = "folder" ]; then
(cd "$SOURCE_LOCATION" && zip -r ../archive.zip . ;)
SOURCE_LOCATION="archive.zip"
SOURCE_TYPE="file"
fi

if [ "$SOURCE_TYPE" = "file" ]; then
file_extension="$(echo "$SOURCE_LOCATION" | sed -n 's/^.*\.\(.*\)$/\1/p')"
tag="$tag.$file_extension"
fi

echo "$CONFIG" | jq -c '{dev,prod} | to_entries | .[]' | while read -r item; do (
if [ "$SOURCE_TYPE" = "file" ]; then
environment="$(echo "$item" | jq -e -r .key)"
account_id="$(echo "$item" | jq -e -r .value.accountId)"
bucket_name="$(echo "$item" | jq -e -r .value.artifactBucketName)"

echo "Uploading $SOURCE_LOCATION to S3 with key $tag in $environment"

export AWS_PROFILE="$environment"
aws s3 cp "$SOURCE_LOCATION" "s3://$bucket_name/$tag"
elif [ "$SOURCE_TYPE" = "docker-image" ]; then
environment="$(echo "$item" | jq -e -r .key)"
account_id="$(echo "$item" | jq -e -r .value.accountId)"
ecr_repository_name="$(echo "$item" | jq -e -r .value.artifactEcrRepositoryName)"
default_region="$(echo "$item" | jq -e -r .value.defaultRegion)"

export AWS_PROFILE="$environment"
login_password="$(aws ecr get-login-password --region "$default_region")"
echo "::add-mask::$login_password"
run: "$ACTION_PATH/scripts/upload-s3-artifact.sh"

ecr_repository_uri="$account_id.dkr.ecr.$default_region.amazonaws.com"
image_tag="$ecr_repository_uri/$ecr_repository_name:$tag"

echo "$login_password" | docker login --username AWS --password-stdin "$ecr_repository_uri"
echo "Tagging image with image tag: $image_tag"
docker tag "$SOURCE_LOCATION" "$ecr_repository_uri/$ecr_repository_name:$tag"
echo "Pushing image with tag: $image_tag"
docker push "$image_tag"
else
echo "Unrecognized source type '$SOURCE_TYPE' - skipping" >&2
fi
); done

rm /tmp/awscreds

# $GITHUB_WORKFLOW_REF looks like this: <org>/<repo>/.github/workflows/<workflow-filename>@<git-reference>
workflow_filename="$(basename "${GITHUB_WORKFLOW_REF%%@*}")"
workflow_dispatch_url="$PARTIAL_WORKFLOW_DISPATCH_URL/$workflow_filename"

echo "tag=$tag" >> "$GITHUB_OUTPUT"
cat <<EOF >> "$GITHUB_STEP_SUMMARY"
Built and uploaded artifact with tag:
\`\`\`
$tag
\`\`\`
- name: Push Docker image to ECR
if: ${{ inputs.source-type == 'docker-image' }}
shell: bash --noprofile --norc -euo pipefail {0}
id: push-ecr-image
env:
ACTION_PATH: ${{ github.action_path }}
AWSCREDS: |
[profile dev]
aws_access_key_id=${{ steps.aws-credentials-dev.outputs.aws-access-key-id }}
aws_secret_access_key=${{ steps.aws-credentials-dev.outputs.aws-secret-access-key }}
aws_session_token=${{ steps.aws-credentials-dev.outputs.aws-session-token }}

---
[profile prod]
aws_access_key_id=${{ steps.aws-credentials-prod.outputs.aws-access-key-id }}
aws_secret_access_key=${{ steps.aws-credentials-prod.outputs.aws-secret-access-key }}
aws_session_token=${{ steps.aws-credentials-prod.outputs.aws-session-token }}
TZ: "Europe/Oslo"
CONFIG: ${{ inputs.config }}
PARTIAL_WORKFLOW_DISPATCH_URL: "${{ github.server_url}}/${{ github.repository }}/actions/workflows"
SOURCE_TYPE: ${{ inputs.source-type }}
SOURCE_LOCATION: ${{ inputs.source-location }}
TAG: ${{ inputs.tag }}
run: "$ACTION_PATH/scripts/push-ecr-image.sh"

_To manually deploy the artifact, copy the tag and pass it in through a [workflow dispatch]($workflow_dispatch_url)_
EOF
- name: Record artifact tag
shell: bash --noprofile --norc -euo pipefail {0}
id: upload
run: |
: "${ARTIFACT_TAG:?Missing ARTIFACT_TAG}"
printf 'tag=%s\n' "$ARTIFACT_TAG" >>"$GITHUB_OUTPUT"
- name: Store artifact tag in commit status
shell: bash --noprofile --norc -euo pipefail {0}
env:
Expand Down
60 changes: 60 additions & 0 deletions package-and-upload-artifact/lib/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Common helpers shared between artifact scripts.

log_info() {
printf '[INFO] %s\n' "$*"
}

log_error() {
printf '[ERROR] %s\n' "$*" >&2
}

die() {
log_error "$1"
exit 1
}

configure_aws() {
: "${AWSCREDS:?Missing AWSCREDS}"
aws_config_file="$(mktemp)"
printf '%s\n' "$AWSCREDS" >"$aws_config_file"
export AWS_CONFIG_FILE="$aws_config_file"
}

environment_defined() {
: "${CONFIG:?Missing CONFIG}"
local environment="$1"
printf '%s' "$CONFIG" | jq -e ".${environment} != null" >/dev/null 2>&1
}

environment_value() {
: "${CONFIG:?Missing CONFIG}"
local environment="$1" key="$2"
printf '%s' "$CONFIG" | jq -e -r ".${environment}.${key}"
}

write_github_summary() {
local final_tag="$1"
: "${PARTIAL_WORKFLOW_DISPATCH_URL:?Missing PARTIAL_WORKFLOW_DISPATCH_URL}"
: "${GITHUB_WORKFLOW_REF:?Missing GITHUB_WORKFLOW_REF}"
: "${GITHUB_OUTPUT:?Missing GITHUB_OUTPUT}"
: "${GITHUB_STEP_SUMMARY:?Missing GITHUB_STEP_SUMMARY}"
: "${GITHUB_ENV:?Missing GITHUB_ENV}"

local workflow_filename workflow_dispatch_url
workflow_filename="$(basename "${GITHUB_WORKFLOW_REF%%@*}")"
workflow_dispatch_url="$PARTIAL_WORKFLOW_DISPATCH_URL/$workflow_filename"

printf 'tag=%s\n' "$final_tag" >>"$GITHUB_OUTPUT"
printf 'ARTIFACT_TAG=%s\n' "$final_tag" >>"$GITHUB_ENV"
cat <<EOF >>"$GITHUB_STEP_SUMMARY"
Built and uploaded artifact with tag:
\`\`\`
$final_tag
\`\`\`

---

_To manually deploy the artifact, copy the tag and pass it in through a [workflow dispatch]($workflow_dispatch_url)_
EOF
}
60 changes: 60 additions & 0 deletions package-and-upload-artifact/scripts/push-ecr-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

: "${CONFIG:?Missing CONFIG}"
: "${SOURCE_TYPE:?Missing SOURCE_TYPE}"
: "${SOURCE_LOCATION:?Missing SOURCE_LOCATION}"
: "${TAG:?Missing TAG}"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
source "$repo_root/lib/common.sh"

source_type="$SOURCE_TYPE"
source_location="$SOURCE_LOCATION"
tag="$TAG"

cleanup() {
if [[ -n "${aws_config_file:-}" ]]; then
rm -f "$aws_config_file"
fi
}
trap cleanup EXIT

configure_aws

if [[ "$source_type" != "docker-image" ]]; then
die "push-ecr-image.sh only supports docker-image sources (received $source_type)"
fi

upload_image_artifact() {
local environment="$1"
local account_id ecr_repository_name default_region
local login_password ecr_repository_uri image_tag

account_id="$(environment_value "$environment" accountId)"
ecr_repository_name="$(environment_value "$environment" artifactEcrRepositoryName)"
default_region="$(environment_value "$environment" defaultRegion)"

export AWS_PROFILE="$environment"
login_password="$(aws ecr get-login-password --region "$default_region")"
printf '::add-mask::%s\n' "$login_password"

ecr_repository_uri="$account_id.dkr.ecr.$default_region.amazonaws.com"
image_tag="$ecr_repository_uri/$ecr_repository_name:$tag"

printf '%s\n' "$login_password" | docker login --username AWS --password-stdin "$ecr_repository_uri"
log_info "Tagging image with image tag: $image_tag"
docker tag "$source_location" "$image_tag"
log_info "Pushing image with tag: $image_tag"
docker push "$image_tag"
}

for environment in dev prod; do
if environment_defined "$environment"; then
upload_image_artifact "$environment"
fi
done

write_github_summary "$tag"
83 changes: 83 additions & 0 deletions package-and-upload-artifact/scripts/upload-s3-artifact.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

: "${CONFIG:?Missing CONFIG}"
: "${SOURCE_TYPE:?Missing SOURCE_TYPE}"
: "${SOURCE_LOCATION:?Missing SOURCE_LOCATION}"
: "${TAG:?Missing TAG}"

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
source "$repo_root/lib/common.sh"

source_type="$SOURCE_TYPE"
source_location="$SOURCE_LOCATION"
tag="$TAG"
package_tmp_dir=""

cleanup() {
if [[ -n "${aws_config_file:-}" ]]; then
rm -f "$aws_config_file"
fi
if [[ -n "$package_tmp_dir" && -d "$package_tmp_dir" ]]; then
rm -rf "$package_tmp_dir"
fi
}
trap cleanup EXIT

configure_aws

archive_folder_source() {
package_tmp_dir="$(mktemp -d)"
(cd "$source_location" && zip -r "$package_tmp_dir/archive.zip" .)
source_location="$package_tmp_dir/archive.zip"
source_type="file"
}

append_file_extension_suffix() {
local extension=""
if [[ "$source_location" == *.* ]]; then
extension="${source_location##*.}"
fi
if [[ -n "$extension" ]]; then
tag="$tag.$extension"
fi
}

upload_file_artifact() {
local environment="$1"
local bucket_name

bucket_name="$(environment_value "$environment" artifactBucketName)"

log_info "Uploading $source_location to S3 as $tag in $environment"

export AWS_PROFILE="$environment"
aws s3 cp "$source_location" "s3://$bucket_name/$tag"
}

case "$source_type" in
folder)
log_info "Compressing folder artifact into archive for upload"
archive_folder_source
;;
file) ;;
docker-image)
die "upload-s3-artifact.sh only supports file or folder sources"
;;
Comment thread
staticaland marked this conversation as resolved.
Outdated
*)
die "Unsupported source type: $source_type"
;;
esac

log_info "Preparing file artifact for upload to S3"
append_file_extension_suffix

for environment in dev prod; do
if environment_defined "$environment"; then
upload_file_artifact "$environment"
fi
done

write_github_summary "$tag"