Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi platform check #18

Merged
merged 1 commit into from
Jan 26, 2023
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
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"