I figured somebody else might need this.
I needed it to automate the (re)creation of the necessary services, jobs etc.
Also, artifact registry didn't work out of the box for me.
install.sh
#!/bin/bash
set -e
# As per https://github.com/GoogleCloudPlatform/gcr-cleaner
REPO_NAME=${1:?REPO_NAME expected as the 1st arg}
export PROJECT_ID="<project>"
export REPO_US="us-docker.pkg.dev/$PROJECT_ID/$REPO_NAME"
export REPO_EUROPE="europe-docker.pkg.dev/$PROJECT_ID/$REPO_NAME"
export REPO_ASIA="asia-docker.pkg.dev/$PROJECT_ID/$REPO_NAME"
REPOS_JSON_ARR="[\"${REPO_US}\", \"${REPO_EUROPE}\", \"${REPO_ASIA}\"]"
declare -a LOCATIONS_ARR=("us" "europe" "asia")
# Limiting resources to prevent billing surprises
MAX_RUNNER_INSTANCES="${#LOCATIONS_ARR[@]}"
RUNNER_MEMORY="128Mi"
REQUEST_TIMEOUT="300s"
SV_NAME="gcr-cleaner-$REPO_NAME"
SV_INVOKER_NAME="gcr-cleaner-invoker"
SV_EMAIL="$SV_NAME@$PROJECT_ID.iam.gserviceaccount.com"
SV_INVOKER_EMAIL="$SV_INVOKER_NAME@$PROJECT_ID.iam.gserviceaccount.com"
# Using a fixed version for security
GCR_CLEANER_IMAGE_URL="gcr.io/gcr-cleaner/gcr-cleaner@sha256:aaa9fd57f612605db869284d85780d5aa5eea6015addbd08faecbee986354614"
RUNNER_REGION="us-central1"
# Note 'us-central1' doesn't work for App Engine
APP_REGION="us-central"
# Commented: we run this each day to save costs
# SCHEDULE="0 8 * * 2"
SCHEDULE="0 8 * * *"
GRACE="24h"
KEEP=5
# Generic function for retrying (mostly useful for iam policies concurrent changes)
exec_with_retry_or_exit(){
i=0
tries=10
sleep_secs=5
while true
do
set +e
"$@"
code="$?"
set -e
if [ "$code" == 0 ]; then
break;
fi
i=$((i+1))
if [ "$i" -ge $tries ]; then
echo "-- Could not exec command after $tries tries"
exit 123;
fi
echo "-- Failed with $code. Sleeping $sleep_secs before retrying ($i/$tries)"
sleep "$sleep_secs"
done
}
echo "Enable necessary APIS"
gcloud services enable --project "$PROJECT_ID" \
appengine.googleapis.com \
cloudscheduler.googleapis.com \
run.googleapis.com
echo "Create $SV_EMAIL if not exists"
set +e
gcloud iam service-accounts describe "$SV_EMAIL"
code="$?"
set -e
if [ "$code" != 0 ]; then
gcloud iam service-accounts create "$SV_NAME" \
--project "$PROJECT_ID" \
--display-name "$SV_NAME"
fi
echo "Deploy gcr-cleaner via $SV_EMAIL"
gcloud --quiet run deploy "$SV_NAME" \
--async \
--project "$PROJECT_ID" \
--platform "managed" \
--service-account "$SV_EMAIL" \
--image "$GCR_CLEANER_IMAGE_URL" \
--region "$RUNNER_REGION" \
--max-instances="$MAX_RUNNER_INSTANCES" \
--memory="$RUNNER_MEMORY" \
--timeout "$REQUEST_TIMEOUT"
echo "Add roles/browser to $SV_EMAIL"
exec_with_retry_or_exit gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:$SV_EMAIL" \
--role "roles/browser" \
--condition "None"
echo "Add roles/artifactregistry.repoAdmin to $SV_EMAIL for '$REPO_NAME' repos"
# Commented: seems like the role condition for the resource name doesn't work on artifact registry. Using the dedicated command instead
# echo "Add roles/artifactregistry.repoAdmin to $SV_EMAIL"
# set +e
# gcloud projects add-iam-policy-binding "$PROJECT_ID" \
# --member "serviceAccount:$SV_EMAIL" \
# --role "roles/artifactregistry.repoAdmin" \
# --condition="^:^title=Res name ending with $REPO_NAME:expression=resource.name.endsWith('$REPO_NAME')"
# code="$?"
# set -e
# if [ "$code" != 0 ]; then
# echo "-- Role already added. Skipping this part"
# fi
# set -e
for loc in "${LOCATIONS_ARR[@]}"
do
echo "--- Adding for '$REPO_NAME' in '$loc'"
exec_with_retry_or_exit gcloud artifacts repositories add-iam-policy-binding "$REPO_NAME" --location="$loc" --role="roles/artifactregistry.repoAdmin" --member="serviceAccount:$SV_EMAIL" &
done
# Wait for the above parallel commands in the loop to finish
wait
echo "Create $SV_INVOKER_EMAIL if not exists"
set +e
gcloud iam service-accounts describe "$SV_INVOKER_EMAIL"
code="$?"
set -e
if [ "$code" != 0 ]; then
gcloud iam service-accounts create "$SV_INVOKER_NAME" \
--project "$PROJECT_ID" \
--display-name "$SV_INVOKER_NAME"
fi
echo "Give $SV_INVOKER_EMAIL permission to invoke the Cloud Run service"
exec_with_retry_or_exit gcloud run services add-iam-policy-binding "$SV_NAME" \
--project "$PROJECT_ID" \
--platform "managed" \
--region "$RUNNER_REGION" \
--member "serviceAccount:$SV_INVOKER_EMAIL" \
--role "roles/run.invoker"
echo "Create app to run job"
set +e
gcloud app create \
--project "$PROJECT_ID" \
--region "$APP_REGION" \
--quiet
code="$?"
set -e
if [ "$code" != 0 ]; then
echo "-- App already created. Skipping this part"
fi
echo "Get the URL of the Cloud Run service"
service_url="$(gcloud run services describe "$SV_NAME" --project "$PROJECT_ID" --platform "managed" --region "$RUNNER_REGION" --format 'value(status.url)')"
export SERVICE_URL="$service_url"
# job_name="gcrclean-$REGISTRY_REGION-$REPO_NAME"
job_name="gcrclean-$REPO_NAME"
echo "Create Cloud Scheduler HTTP job '$job_name' to invoke the function weekly"
set +e
gcloud scheduler jobs describe "$job_name"
code="$?"
set -e
if [ "$code" == 0 ]; then
echo "-- Job already created. Deleting exising one"
gcloud --quiet scheduler jobs delete "$job_name"
fi
# Specifying tag_filter_any "." because by default only untaggged images are deleted
# You can remove it to revert to the default behavior
exec_with_retry_or_exit gcloud scheduler jobs create http "$job_name" \
--project "$PROJECT_ID" \
--description "Cleanup $REPO_NAME" \
--uri "${SERVICE_URL}/http" \
--message-body "{\"repos\":${REPOS_JSON_ARR}, \"grace\":\"$GRACE\", \"keep\": $KEEP, \"recursive\": true, \"tag_filter_any\": \".\"}" \
--oidc-service-account-email "$SV_INVOKER_EMAIL" \
--schedule "$SCHEDULE" \
# Commented: Default is 'Etc/UTC'
# --time-zone="US/Eastern"
echo "Run now"
exec_with_retry_or_exit gcloud scheduler jobs run "$job_name" \
--project "$PROJECT_ID"
echo "Done! Check logs"
usage: ./install.sh
This script comes with NO WARRANTY. Use at your own risk
Hello
I figured somebody else might need this. I needed it to automate the (re)creation of the necessary services, jobs etc. Also, artifact registry didn't work out of the box for me.
install.sh
usage: ./install.sh
This script comes with NO WARRANTY. Use at your own risk