GoogleCloudPlatform / gcr-cleaner

Delete untagged image refs in Google Container Registry or Artifact Registry
Apache License 2.0
805 stars 112 forks source link

Bash script for anyone interested #66

Closed xucian closed 2 years ago

xucian commented 2 years ago

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

#!/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

sethvargo commented 2 years ago

Thanks!