Skip to content

Commit

Permalink
Support multi platform check (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacome authored Jan 26, 2023
1 parent 0247ac1 commit dfc5d71
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 28 deletions.
85 changes: 78 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
test1:
name: Test-Action
name: Test Update Needed
runs-on: ubuntu-22.04
continue-on-error: true
steps:
Expand All @@ -20,14 +20,82 @@ jobs:
uses: ./
with:
base-image: library/nginx:1.21.0
image: nginx/nginx-ingress:1.12.0
image: nginx/nginx-ingress:2.1.0
- name: Get Test Output
run: echo "Workflow Docker Image ${{ steps.test.outputs.needs-updating }}"
- name: Check value
run: echo "Needs updating"
if: steps.test.outputs.needs-updating == 'true'
run: |
if [[ "${{ steps.test.outputs.needs-updating }}" != "true" ]]; then
exit 1
fi
test2:
name: Test-Action
name: Test Update Not Needed
runs-on: ubuntu-22.04
continue-on-error: true
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Test Action
id: test
uses: ./
with:
base-image: nginx:1.21.0
image: library/nginx:1.21.0
- name: Get Test Output
run: echo "Workflow Docker Image ${{ steps.test.outputs.needs-updating }}"
- name: Check value
run: |
if [[ "${{ steps.test.outputs.needs-updating }}" != "false" ]]; then
exit 1
fi
test3:
name: Test Update Needed on ARM64
runs-on: ubuntu-22.04
continue-on-error: true
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Test Action
id: test
uses: ./
with:
base-image: nginx:1.21.0
image: nginx/nginx-ingress:2.1.0
platforms: linux/arm64
- name: Get Test Output
run: echo "Workflow Docker Image ${{ steps.test.outputs.needs-updating }}"
- name: Check value
run: |
if [[ "${{ steps.test.outputs.needs-updating }}" != "true" ]]; then
exit 1
fi
test4:
name: Test Update Needed on multiple platforms
runs-on: ubuntu-22.04
continue-on-error: true
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Test Action
id: test
uses: ./
with:
base-image: nginx:1.21.0
image: nginx/nginx-ingress:2.1.0
platforms: linux/arm64,linux/amd64
- name: Get Test Output
run: echo "Workflow Docker Image ${{ steps.test.outputs.needs-updating }}"
- name: Check value
run: |
if [[ "${{ steps.test.outputs.needs-updating }}" != "true" ]]; then
exit 1
fi
test5:
name: Test Update Not Needed on multiple platforms
runs-on: ubuntu-22.04
continue-on-error: true
steps:
Expand All @@ -39,8 +107,11 @@ jobs:
with:
base-image: nginx:1.21.0
image: library/nginx:1.21.0
platforms: linux/arm64,linux/amd64
- name: Get Test Output
run: echo "Workflow Docker Image ${{ steps.test.outputs.needs-updating }}"
- name: Check value
run: echo "Does not need updating"
if: steps.test.outputs.needs-updating == 'false'
run: |
if [[ "${{ steps.test.outputs.needs-updating }}" != "false" ]]; then
exit 1
fi
6 changes: 5 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ inputs:
image:
description: "Docker image"
required: true
platforms:
description: "Platforms to check"
required: false
default: "linux/amd64"
outputs:
needs-updating:
description: "True or false"
Expand All @@ -18,7 +22,7 @@ runs:
steps:
- id: run-script
run: |
result=$(base=${{ inputs.base-image }} image=${{ inputs.image }} ${{ github.action_path }}/docker.sh)
result=$(base=${{ inputs.base-image }} image=${{ inputs.image }} platforms=${{ inputs.platforms }} ${{ github.action_path }}/docker.sh)
echo "result=${result}" >>$GITHUB_OUTPUT
shell: bash

Expand Down
122 changes: 102 additions & 20 deletions docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,132 @@ set -o errexit
set -o pipefail
if [ "${TRACE-0}" -eq 1 ]; then set -o xtrace; fi

error() {
# print error and exit
printf "Error: $1\n" >&2
exit 1

}

get_manifests() {
local repo=$1
local digest=$2
local token=$3

manifest_list=$(curl -sSL -H "Authorization: Bearer $token" \
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json" \
"https://index.docker.io/v2/${repo}/manifests/${digest}" 2>/dev/null)

if jq -e -r '.errors[0].code' <<<"$manifest_list" >/dev/null; then
error_code=$(jq -r '.errors[0].code' <<<"$manifest_list")
message=$(jq -r '.errors[0].message' <<<"$manifest_list")
error "Response from $repo\n code: $error_code\n message: $message"
fi

jq -r '[.manifests[] | select(.platform.architecture | contains ("unknown") | not) | {digest: .digest, platform: (.platform.os +"/"+ .platform.architecture)}]' <<<"$manifest_list"

}

get_layers() {
local repo=$1
local digest=$2
local token
token=$(get_token "$repo")
local token=$3

digestOutput=$(curl -H "Authorization: Bearer $token" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
digestOutput=$(curl -sSL -H "Authorization: Bearer $token" \
-H "Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.oci.image.manifest.v1+json" \
"https://index.docker.io/v2/${repo}/manifests/${digest}" 2>/dev/null)

if jq -e -r '.errors[0].code' <<<"$digestOutput" >/dev/null; then
jq -r '.errors[0].code' <<<"$digestOutput"
else
jq -r '[.layers[].digest]' <<<"$digestOutput"
error_code=$(jq -r '.errors[0].code' <<<"$digestOutput")
message=$(jq -r '.errors[0].message' <<<"$digestOutput")
error "Response from $repo\n code: $error_code\n message: $message"
fi

jq -r '[.layers[].digest]' <<<"$digestOutput"

}

get_token() {
local repo=$1
local url
url="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull"
curl "$url" 2>/dev/null | jq -r '.token'
curl -fsSL "$url" 2>/dev/null | jq -r '.token'
}

# if we get a "UNAUTHORIZED" response and the $base does not match a image with username -> fallback to a version with "library" as prefix
retry_if_necessary() {
check_if_library() {
local IMAGE_PATTERN_WITH_USERNAME="^.+/.+$"
local repo=$1
local digest=$2
local result
local token

result=$(get_layers "$repo" "$digest")
token=$(get_token "$repo")

code=$(curl --write-out %{http_code} -sSL --output /dev/null -H "Authorization: Bearer $token" \
"https://index.docker.io/v2/${repo}/tags/list" 2>/dev/null)

if [[ $code != 200 ]]; then

if ! [[ $repo =~ $IMAGE_PATTERN_WITH_USERNAME ]]; then
repo="library/$repo"

token=$(get_token "$repo")

if [[ $result == "UNAUTHORIZED" ]] && ! [[ $repo =~ $IMAGE_PATTERN_WITH_USERNAME ]] ; then
result=$(get_layers "library/$repo" "$digest")
code=$(curl --write-out %{http_code} -sSL --output /dev/null -H "Authorization: Bearer $token" \
"https://index.docker.io/v2/${repo}/tags/list" 2>/dev/null)

fi

if [[ $code != 200 ]]; then
error "Response code from $repo was $code"
fi
fi

echo "$result"
result=("$repo" "$token")
}

IFS=: read base base_tag <<<$base
IFS=: read image image_tag <<<$image

layers_base=$(retry_if_necessary $base ${base_tag:-latest})
layers_image=$(retry_if_necessary $image ${image_tag:-latest})
check_if_library "$base"
base_repo=${result[0]}
base_token=${result[1]}
manifests_base=$(get_manifests $base_repo ${base_tag:-latest} $base_token)

check_if_library "$image"
image_repo=${result[0]}
image_token=${result[1]}
manifests_image=$(get_manifests $image_repo ${image_tag:-latest} $image_token)

diff=false
# loop through plafforms split by comma
for platform in $(echo $platforms | tr ',' ' '); do
# get the digest for the platform
digest_base=$(jq -r ".[] | select(.platform == \"$platform\") | .digest" <<<"$manifests_base")

# if the digest is empty, then the platform is not present in the base image
if [ -z "$digest_base" ]; then
error "Platform $platform not found in the base image $base"
fi

# get the digest for the platform
digest_image=$(jq -r ".[] | select(.platform == \"$platform\") | .digest" <<<"$manifests_image")

# if the digest is empty, then the platform is not present in the image
if [ -z "$digest_image" ]; then
error "Platform $platform not found in the image $image"
fi

# get the layers for the base
layers_base=$(get_layers $base_repo $digest_base $base_token)

# get the layers for the image
layers_image=$(get_layers $image_repo $digest_image $image_token)

diff=$(jq '.base-.image | .!=[]' <<<"{\"base\": $layers_base, \"image\": $layers_image }")
if [[ "$diff" == "true" ]]; then
break
fi
done
jq '.base-.image | .!=[]' <<<"{\"base\": $layers_base, \"image\": $layers_image }"
echo "$diff"

0 comments on commit dfc5d71

Please sign in to comment.