Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
56 changes: 56 additions & 0 deletions .changeset/promote-image-ecr-cosign-refs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
"promote-image-ecr": minor
---

Stop passing skopeo transport URLs to cosign.

The action copies with `cosign copy`, but the script still built image references
in skopeo's form, `docker://<registry>/<repo>:<tag>`. cosign parses references
with go-containerregistry, which reads everything before the first `/` as the
registry: it took `docker:` as the host and tried to reach `https://docker/v2/`,
so every promotion failed on DNS before a registry was contacted.

Error: Get "https://docker/v2/": dial tcp: lookup docker on 127.0.0.53:53: server misbehaving

This affected both the single-image and the images-matrix paths, and every
released version, so no promotion this action ran has ever succeeded.

The rest is the remainder of the same unfinished skopeo-to-cosign migration.

`copy-signatures` is removed. It was documented as a switch to skopeo, but no
skopeo path exists and the script never read the value, so setting it to `false`
did not change what the action did. `cosign copy` always carries signatures and
attestations. No caller in the org passes it.

`SKOPEO_ARGS` and the `SRC_PASS`/`DST_PASS` environment variables are removed
from the copy step. `SKOPEO_ARGS` was populated from `inputs.skopeo-additional-args`,
an input this action does not declare, so it always expanded to empty. The two
passwords were what skopeo's `--creds` flags needed; cosign authenticates from
the Docker config that `amazon-ecr-login` writes, and nothing read them. They no
longer enter the step environment.

Three fixes to the images-matrix path, which was unusable beyond the reference
bug above:

- The loop read from a pipe, so it ran in a subshell. Its `IMAGE_COUNT` was
discarded and the run always reported `All 0 images copied successfully!`,
and the failure `exit 1` left the subshell rather than the script. It now
reads from a process substitution.
- `sed -i "4i\"` is rejected by GNU sed as `i` with no text after it, so the
summary write failed once copying had succeeded. The total is now inserted by
rewriting the file. The heading it writes was `** Total Images Promoted:**`,
which Markdown does not render as bold; the stray space is gone.
- `action.yaml` described the `images` objects with camelCase keys
(`sourceRepository`), while the script and the README both use snake_case
(`source_repository`). Following the documented casing produced nulls. The
description now matches.

The `promoted-images` output returns the promotions it promised. It read
`/tmp/promotion-results/promoted-images.json`, a file the script never writes;
results land in `promotion-results.json`. The output was `[]` on every run,
including successful ones. It now reads the `.promotions` array.

The README's examples were not runnable: they passed underscore input names
(`source_role_arn`) that the action does not declare, all of which are
kebab-case, and pointed `uses:` at a local `./.github/actions/promote-image`
path that does not exist in a consuming repo.
64 changes: 29 additions & 35 deletions actions/promote-image-ecr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,19 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4

- name: Promote image
uses: ./.github/actions/promote-image
uses: smartcontractkit/.github/actions/promote-image-ecr@promote-image-ecr/0.5.0
with:
source_aws_region: eu-west-1
destination_aws_region: us-east-1
source_role_arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination_role_arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source_registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination_registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
source_repository: my-app
destination_repository: my-app
source_tag: v1.0.0
destination_tag: v1.0.0
source-aws-region: eu-west-1
destination-aws-region: us-east-1
source-role-arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination-role-arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source-registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination-registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
source-repository: my-app
destination-repository: my-app
source-tag: v1.0.0
destination-tag: v1.0.0
```

### Multiple Images Promotion (Matrix)
Expand All @@ -101,17 +99,15 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4

- name: Promote multiple images
uses: ./.github/actions/promote-image
uses: smartcontractkit/.github/actions/promote-image-ecr@promote-image-ecr/0.5.0
with:
source_aws_region: eu-west-1
destination_aws_region: us-east-1
source_role_arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination_role_arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source_registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination_registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
source-aws-region: eu-west-1
destination-aws-region: us-east-1
source-role-arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination-role-arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source-registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination-registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
images: |
[
{
Expand Down Expand Up @@ -157,21 +153,19 @@ jobs:
- { repo: "app2", tag: "v2.0.0" }
- { repo: "service-x", tag: "sha-abc123" }
steps:
- uses: actions/checkout@v4

- name: Promote ${{ matrix.image.repo }}
uses: ./.github/actions/promote-image
uses: smartcontractkit/.github/actions/promote-image-ecr@promote-image-ecr/0.5.0
with:
source_aws_region: eu-west-1
destination_aws_region: us-east-1
source_role_arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination_role_arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source_registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination_registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
source_repository: ${{ matrix.image.repo }}
destination_repository: ${{ matrix.image.repo }}
source_tag: ${{ matrix.image.tag }}
destination_tag: ${{ matrix.image.tag }}
source-aws-region: eu-west-1
destination-aws-region: us-east-1
source-role-arn: arn:aws:iam::111111111111:role/github-actions-ecr-read
destination-role-arn: arn:aws:iam::222222222222:role/github-actions-ecr-write
source-registry: 111111111111.dkr.ecr.eu-west-1.amazonaws.com
destination-registry: 222222222222.dkr.ecr.us-east-1.amazonaws.com
source-repository: ${{ matrix.image.repo }}
destination-repository: ${{ matrix.image.repo }}
source-tag: ${{ matrix.image.tag }}
destination-tag: ${{ matrix.image.tag }}
```

## Copy Tool
Expand Down
22 changes: 6 additions & 16 deletions actions/promote-image-ecr/action.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Promote image between ECR registries
description: |
Promote an image from one ECR registry to another, using cosign (default) or skopeo.
Promote an image from one ECR registry to another, using cosign.
The action will assume the provided IAM roles to access the source and destination registries.

inputs:
Expand Down Expand Up @@ -55,17 +55,11 @@ inputs:
required: false
images:
description: |
JSON array of images to promote. Each object should have: sourceRepository, destinationRepository, sourceTag, destinationTag.
Example: [{"sourceRepository":"app1","destinationRepository":"app1","sourceTag":"v1.0","destinationTag":"v1.0"}]
If provided, this takes precedence over individual sourceRepository/destinationRepository/sourceTag/destinationTag inputs.
JSON array of images to promote. Each object should have: source_repository, destination_repository, source_tag, destination_tag.
Example: [{"source_repository":"app1","destination_repository":"app1","source_tag":"v1.0","destination_tag":"v1.0"}]
If provided, this takes precedence over the individual source-repository/destination-repository/source-tag/destination-tag inputs.
required: false
default: ""
copy-signatures:
description:
Use cosign to copy images (includes signatures and attestations). Set to
'false' to use skopeo instead.
required: false
default: "true"

outputs:
promoted-images:
Expand Down Expand Up @@ -112,13 +106,9 @@ runs:
SOURCE_TAG: ${{ inputs.source-tag }}
DESTINATION_TAG: ${{ inputs.destination-tag }}
IMAGES_JSON: ${{ inputs.images }}
SKOPEO_ARGS: ${{ inputs.skopeo-additional-args }}
SOURCE_AWS_REGION: ${{ inputs.source-aws-region || inputs.aws-region }}
DESTINATION_AWS_REGION:
${{ inputs.destination-aws-region || inputs.aws-region }}
SRC_PASS: ${{ steps.src.outputs.password }}
DST_PASS: ${{ steps.dst.outputs.password }}
COPY_SIGNATURES: ${{ inputs.copy-signatures }}
ACTION_PATH: ${{ github.action_path }}
run: |
"${ACTION_PATH}/scripts/promote-images.sh"
Expand All @@ -144,8 +134,8 @@ runs:
if: always()
shell: bash
run: |
if [ -f /tmp/promotion-results/promoted-images.json ]; then
echo "promoted-images=$(cat /tmp/promotion-results/promoted-images.json)" >> $GITHUB_OUTPUT
if [ -f /tmp/promotion-results/promotion-results.json ]; then
echo "promoted-images=$(jq -c '.promotions' /tmp/promotion-results/promotion-results.json)" >> $GITHUB_OUTPUT
else
echo "promoted-images=[]" >> $GITHUB_OUTPUT
fi
83 changes: 51 additions & 32 deletions actions/promote-image-ecr/scripts/promote-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ mkdir -p /tmp/promotion-results
RESULTS_FILE="/tmp/promotion-results/promotion-summary.md"
RESULTS_JSON="/tmp/promotion-results/promotion-results.json"

echo "# Image Promotion Results" > "$RESULTS_FILE"
echo "" >> "$RESULTS_FILE"
echo "**Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> "$RESULTS_FILE"
echo "**Copy Tool:** cosign" >> "$RESULTS_FILE"
echo "" >> "$RESULTS_FILE"
{
echo "# Image Promotion Results"
echo ""
echo "**Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "**Copy Tool:** cosign"
echo ""
} > "$RESULTS_FILE"

# Initialize JSON results
echo '{"promotions": []}' > "$RESULTS_JSON"
Expand Down Expand Up @@ -41,21 +43,23 @@ write_markdown_result() {
else
emoji="❌"
fi
echo "### $emoji ${repo}" >> "$RESULTS_FILE"
echo "" >> "$RESULTS_FILE"
echo "- **Source:** \`${repo}:${src_tag}\`" >> "$RESULTS_FILE"
echo "- **Destination:** \`${dst_repo}:${dst_tag}\`" >> "$RESULTS_FILE"
if [[ -n "$src_region" ]]; then
echo "- **Source Region:** \`${src_region}\`" >> "$RESULTS_FILE"
fi
if [[ -n "$dst_region" ]]; then
echo "- **Destination Region:** \`${dst_region}\`" >> "$RESULTS_FILE"
fi
if [[ "$status" == "success" ]]; then
echo "- **Duration:** ${duration}s" >> "$RESULTS_FILE"
fi
echo "- **Status:** ${status^}" >> "$RESULTS_FILE"
echo "" >> "$RESULTS_FILE"
{
echo "### $emoji ${repo}"
echo ""
echo "- **Source:** \`${repo}:${src_tag}\`"
echo "- **Destination:** \`${dst_repo}:${dst_tag}\`"
if [[ -n "$src_region" ]]; then
echo "- **Source Region:** \`${src_region}\`"
fi
if [[ -n "$dst_region" ]]; then
echo "- **Destination Region:** \`${dst_region}\`"
fi
if [[ "$status" == "success" ]]; then
echo "- **Duration:** ${duration}s"
fi
echo "- **Status:** ${status^}"
echo ""
} >> "$RESULTS_FILE"
}

# Function to append promotion result to JSON
Expand Down Expand Up @@ -88,19 +92,29 @@ write_promotion_json() {
if [[ -n "$IMAGES_JSON" ]]; then
# Process multiple images
echo "Processing multiple images from matrix..."
echo "" >> "$RESULTS_FILE"
echo "## Promoted Images" >> "$RESULTS_FILE"
echo "" >> "$RESULTS_FILE"
{
echo ""
echo "## Promoted Images"
echo ""
} >> "$RESULTS_FILE"

if ! jq -e 'type == "array"' >/dev/null 2>&1 <<<"$IMAGES_JSON"; then
echo "::error::images input must be a JSON array"
exit 1
fi

# Read from a process substitution, not a pipe: a piped `while` runs in a
# subshell, so IMAGE_COUNT below would be discarded and the failure `exit 1`
# would leave the subshell rather than the script.
IMAGE_COUNT=0
echo "$IMAGES_JSON" | jq -c '.[]' | while read -r image; do
while read -r image; do
SRC_REPO=$(echo "$image" | jq -r '.source_repository')
DST_REPO=$(echo "$image" | jq -r '.destination_repository')
SRC_TAG=$(echo "$image" | jq -r '.source_tag')
DST_TAG=$(echo "$image" | jq -r '.destination_tag')

SRC="docker://${SOURCE_REGISTRY}/${SRC_REPO}:${SRC_TAG}"
DST="docker://${DESTINATION_REGISTRY}/${DST_REPO}:${DST_TAG}"
SRC="${SOURCE_REGISTRY}/${SRC_REPO}:${SRC_TAG}"
DST="${DESTINATION_REGISTRY}/${DST_REPO}:${DST_TAG}"

echo "-> Copying ${SRC} to ${DST}"

Expand All @@ -127,17 +141,22 @@ if [[ -n "$IMAGES_JSON" ]]; then

exit 1
fi
done
done < <(jq -c '.[]' <<<"$IMAGES_JSON")
echo "----------------------------------------"
echo "All ${IMAGE_COUNT} images copied successfully!"

# Add summary at the top
sed -i "3i\\** Total Images Promoted:** ${IMAGE_COUNT}" "$RESULTS_FILE"
sed -i "4i\\" "$RESULTS_FILE"
# Add the total just under the title block. Done by rewriting the file rather
# than with `sed -i "4i\\"`, which GNU sed rejects: `i` with nothing after it.
{
head -n 2 "$RESULTS_FILE"
echo "**Total Images Promoted:** ${IMAGE_COUNT}"
echo ""
tail -n +3 "$RESULTS_FILE"
} > "${RESULTS_FILE}.tmp" && mv "${RESULTS_FILE}.tmp" "$RESULTS_FILE"
else
# Process single image
SRC="docker://${SOURCE_REGISTRY}/${SOURCE_REPOSITORY}:${SOURCE_TAG}"
DST="docker://${DESTINATION_REGISTRY}/${DESTINATION_REPOSITORY}:${DESTINATION_TAG}"
SRC="${SOURCE_REGISTRY}/${SOURCE_REPOSITORY}:${SOURCE_TAG}"
DST="${DESTINATION_REGISTRY}/${DESTINATION_REPOSITORY}:${DESTINATION_TAG}"

echo "-> Copying ${SRC} to ${DST}"

Expand Down
Loading