Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 27 additions & 1 deletion .github/workflows/dockerhub-visibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,27 @@ name: Docker Hub Visibility
# already went out private.
on:
workflow_call:
inputs:
strict:
description: 'Fail the job, and any release gated on it, when an image cannot be made public.'
type: boolean
default: false
secrets:
DOCKER_USERNAME:
required: true
DOCKERHUB_IMAGE_PUSH_TOKEN:
required: true
# Changing visibility is an administrative operation the push token is not
# scoped for (it answers 403 on the write). Set this to an organization access
# token with repo:admin to let the job actually fix an image.
DOCKERHUB_REPO_ADMIN_TOKEN:
required: false
workflow_dispatch:
inputs:
strict:
description: 'Fail the run when an image cannot be made public.'
type: boolean
default: true

permissions:
contents: read
Expand All @@ -35,7 +50,18 @@ jobs:
persist-credentials: false

- name: Ensure Docker Hub repositories are public
id: visibility
# Non-strict callers report the problem without blocking the release: while the
# credential lacks repo:admin every run would fail here, and a red gate that
# cannot publish anything is worse than a release with a loud warning.
continue-on-error: ${{ !inputs.strict }}
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_IMAGE_PUSH_TOKEN }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_REPO_ADMIN_TOKEN || secrets.DOCKERHUB_IMAGE_PUSH_TOKEN }}
run: ./scripts/ensure-dockerhub-public.sh

- name: Warn that images are not public
if: steps.visibility.outcome == 'failure'
run: |
echo "::warning::Released images are not all public (see the step above and the job summary). \
Give DOCKERHUB_REPO_ADMIN_TOKEN an organization access token with repo:admin, then set strict: true in release.yml to gate releases on this."
Comment thread
coderabbitai[bot] marked this conversation as resolved.
16 changes: 12 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,24 @@ permissions:
packages: write

jobs:
# Gate the build on Docker Hub visibility: repositories are created on first push
# with the organization's private default, so without this an image is only public
# if someone remembered to flip it by hand. Runs before the push so a brand-new
# image never exists private, and derives its image list from the
# Check Docker Hub visibility before the build: repositories are created on first
# push with the organization's private default, so without this an image is only
# public if someone remembered to flip it by hand. Runs before the push so a
# brand-new image never exists private, and derives its image list from the
# gitops_yaml_key_mappings below.
#
# strict: false until DOCKERHUB_REPO_ADMIN_TOKEN holds an organization access token
# with repo:admin. The push token can read visibility but not change it (403), so a
# strict gate today would only block every release without publishing anything. Flip
# to true once the token is in place and this becomes a real gate.
dockerhub-visibility:
uses: ./.github/workflows/dockerhub-visibility.yml
with:
strict: false
secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_IMAGE_PUSH_TOKEN: ${{ secrets.DOCKERHUB_IMAGE_PUSH_TOKEN }}
DOCKERHUB_REPO_ADMIN_TOKEN: ${{ secrets.DOCKERHUB_REPO_ADMIN_TOKEN }}

pipeline:
needs: dockerhub-visibility
Expand Down
47 changes: 37 additions & 10 deletions scripts/ensure-dockerhub-public.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
# existing private ones. Run it before the images are pushed so a first release never
# lands private.
#
# Env: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (Docker Hub PAT), optional DOCKERHUB_NAMESPACE
# and RELEASE_WORKFLOW.
# Changing visibility is an administrative operation: DOCKERHUB_TOKEN must carry
# repo:admin (an organization access token, or an owner's credentials). A push token is
# enough to read visibility but answers 403 on the write, which this script reports as
# such instead of leaving the image quietly private.
#
# Env: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, optional DOCKERHUB_NAMESPACE and
# RELEASE_WORKFLOW.

set -euo pipefail

Expand Down Expand Up @@ -90,6 +95,16 @@ if [ "$status" != "200" ] || [ -z "$TOKEN" ]; then
fi

failed=0
unresolved=""

# fail_repo <repo> <message>
# Records a repository this run could not leave public, so the job summary names the
# work left to do instead of burying it in the step log.
fail_repo() {
echo "error: $1: $2" >&2
unresolved="${unresolved}- \`$1\`: $2"$'\n'
failed=1
}

while read -r image; do
[ -n "$image" ] || continue
Expand All @@ -109,17 +124,19 @@ while read -r image; do
status=$(hub_call PATCH "${API}/repositories/${repo}/" '{"is_private": false}')
if [ "$status" = "200" ]; then
echo "${repo}: was private, now public"
elif [ "$status" = "403" ]; then
# Reading visibility only needs a push token; changing it does not. This is
# the credential telling us so, not a transient failure.
fail_repo "$repo" "still private: the Docker Hub token lacks repo:admin (HTTP 403)"
else
echo "error: ${repo}: could not make public (HTTP ${status})" >&2
failed=1
fail_repo "$repo" "could not make public (HTTP ${status})"
fi
;;
false)
echo "${repo}: already public"
;;
*)
echo "error: ${repo}: response did not report is_private" >&2
failed=1
fail_repo "$repo" "response did not report is_private"
;;
esac
;;
Expand All @@ -129,16 +146,26 @@ while read -r image; do
status=$(hub_call POST "${API}/repositories/" "$payload")
if [ "$status" = "201" ]; then
echo "${repo}: created as public"
elif [ "$status" = "403" ]; then
fail_repo "$repo" "does not exist and the Docker Hub token lacks repo:admin to create it (HTTP 403)"
else
echo "error: ${repo}: could not create (HTTP ${status})" >&2
failed=1
fail_repo "$repo" "could not create (HTTP ${status})"
fi
;;
*)
echo "error: ${repo}: unexpected response (HTTP ${status})" >&2
failed=1
fail_repo "$repo" "unexpected response (HTTP ${status})"
;;
esac
done <<<"$images"

if [ -n "$unresolved" ] && [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
echo "### Images still not public"
echo
printf '%s' "$unresolved"
echo
echo "Anonymous \`docker pull\` and \`helm install\` of the midaz chart fail while this holds."
} >>"$GITHUB_STEP_SUMMARY"
fi

exit "$failed"
Loading