Code Monkey home page Code Monkey logo

gcr-cleaner's Introduction

GCR Cleaner

Caution

The functionality provied by this tool is now built directly into Artifact Registry! We are no longer accepting bug reports or feature requests.

GCR Cleaner deletes old container images in Docker Hub, Container Registry, Artifact Registry, or any Docker v2 registries. This can help reduce storage costs, especially in CI/CD environments where images are created and pushed frequently.

There are multiple deployment options for GCR Cleaner. Click on your preferred deployment option for a detailed guide:

For one-off tasks, you can also run GCR Cleaner locally:

docker run -it us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli

If you want gcr-cleaner to inherit the authentication from your local gcloud installation, you must mount the gcloud directory into the container:

docker run -v "${HOME}/.config/gcloud:/.config/gcloud" -it us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli

This is not an official Google product.

Container images

Pre-built container images are available at the following locations. We do not offer versioned container images.

asia-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner
europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner
us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner

Server Payload & parameters

⚠️ This section is for the server payload. If you are using the CLI tool, run gcr-cleaner -h to see the list of flags and their descriptions.

The payload is expected to be JSON with the following fields:

  • repos - List of the full names of the repositories to clean (e.g. ["us-docker.pkg.dev/project/my/repo", "gcr.io/my/repo"]. This field is required.

  • grace - Relative duration in which to ignore references. This value is specified as a time duration value like "5s" or "3h". If set, refs newer than the duration will not be deleted. If unspecified, the default is no grace period (all untagged image refs are deleted).

  • keep - If an integer is provided, it will always keep that minimum number of images. Note that it will not consider images inside the grace duration. GCR Cleaner attempts to keep the most recently created images, but there are some caveats. Some community tooling sets container creation time to a date back in 1980, which breaks the default sorting algorithm. As such, GCR Cleaner uses the following sorting algorithm for container images:

    • If either of the containers were created before Docker even existed, it sorts by the date the container was uploaded to the registry.

    • If two containers were created at the same timestamp, it sorts by the date the container was uploaded to the registry.

    • In all other situations, it sorts by the timestamp the container was created.

    This algorithm exists to preserve ordering for containers that are moved between registries.

  • tag_filter_any - If specified, any image with at least one tag that matches this given regular expression will be deleted. The image will be deleted even if it has other tags that do not match the given regular expression. The regular expressions are parsed according to the Go regexp package.

  • tag_filter_all - If specified, any image where all tags match this given regular expression will be deleted. The image will not be delete if it has other tags that do not match the given regular expression. The regular expressions are parsed according to the Go regexp package.

  • dry_run - If set to true, will not delete anything and outputs what would have been deleted.

  • recursive - If set to true, will recursively search all child repositories.

    NOTE! On Container Registry, you must grant additional permissions to the service account in order to query the registry. The most minimal permissions are roles/browser.

    NOTE! On Artifact Registry, you must grant additional permissions to the service account in order to query the registry. The most minimal permissions are roles/storage.objectViewer.

    WARNING! If the authenticated principal has access to many Container Registry or Artifact Registry repos, this will be very slow! This is because the Docker v2 API does not support server-side filtering, meaning GCR Cleaner must download a manifest of all repositories to which you have access and then do client-side filtering. The most granular filter is at the host layer, meaning GCR Cleaner will perform a list operation on gcr.io (for Container Registry) or us-docker.pkg.dev (for Artifact Registry), parse the response and do client-side filtering to match against the provided patterns, then start deleting. To re-iterate, this operation is not segmented by project - if the authenticated principal has access to 10,000 repos, the client will need to filter through 10,000 repos. The easiest way to mitigate this is to practice the Principle of Least Privilege and create a dedicated service account that has granular permissions on a subset of repositories.

Permissions

This section lists the minimum required permissions depending on the target cleanup system.

Artifact Registry

The service account running GCR cleaner must have roles/artifactregistry.repoAdmin or greater on the Artifact Registry repositories. Here is an example for setting that permissions via gcloud:

gcloud artifacts repositories add-iam-policy-binding "my-repo" \
  --project "my-project" \
  --location "us" \
  --member "serviceAccount:[email protected]" \
  --role "roles/artifactregistry.repoAdmin"

Container Registry

Container Registry stores images in Google Cloud Storage, so the service account running GCR Cleaner must have read and write permissions on the underlying Cloud Storage bucket. Here is an example for setting that permission via gsutil:

gsutil acl ch -u [email protected]:W gs://artifacts.my-project.appspot.com

To clean up Container Registry images hosted in specific regions, update the bucket name to include the region:

gs://eu.artifacts.my-project.appspot.com

If you plan on using the recursive functionality, you must also grant the service account "Browser" permissions:

gcloud projects add-iam-policy-binding "my-project" \
  --member "serviceAccount:[email protected]" \
  --role "roles/browser"

Debugging

By default, GCR Cleaner only emits user-level logging at the "info" level. More logs are available at the "debug" level. To configure the log level, set the GCRCLEANER_LOG environment variable to the desired log value:

export GCRCLEANER_LOG=debug

In debug mode, GCR Cleaner will print a lot of information, including its entire decision process for candidate deletion. If you open an issue, please include these debug logs as they are very helpful in finding and fixing any bugs.

Concurrency

By default, GCR Cleaner will attempt to perform operations in parallel. You can customize the concurrency with -concurrency on the CLI or by setting the environment variable GCRCLEANER_CONCURRENCY on the server. It defaults to 20.

gcr-cleaner's People

Contributors

anouarchattouna avatar cflewis avatar danielharr avatar dependabot[bot] avatar grumps avatar halamix2 avatar imjasonh avatar joeyslalom avatar jtmiclat avatar luanphantiki avatar maguro avatar mchmarny avatar mmikitka avatar nwhitehill avatar romanbelkov avatar sethvargo avatar steren avatar stevenacoffman avatar sytten avatar tbao2011 avatar winds6206 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gcr-cleaner's Issues

Tag filter is applied only to first tag

The current behaviour when using tag filter is to compare it against the first tag of each image.

This matters for use cases on images with multiple tags.

In my view, it is more intuitive to have an option to say it should match any of the tags on the image or all of the tags on the image.
If you care about backwards compatibility (i.e. this was a feature and not a bug), there could also be an option to only match the first tag...

In my particular use case, the image should be deleted only if the filter to matches all the tags on the image. I actually think this is the most sensible default for the library because it's the most strict (matches the least) which is a good idea when the effect of the operation is permanent deletion.

Recursive Feature Unavailable in Server

Expected:

cat >/tmp/prune.json <<EOF
{
    "grace": "2160h",
    "recursive": true,
    "repo": "gcr.io/myproject/fidc",
    "tag_filter": "7.1-dev",
    "dry_run": true
}
FOF
go run ./cmd/gcr-cleaner-server/main.go &
curl localhost:8080/http --data @/tmp/prune.json

To recursively clean gcr.io/myproject/fidc
Found:
gcr-cleaner server doesn't implement the recursive feature, only the cli bin does.

Delete purely on age rather than checking if it is untagged

Thanks for creating this! I've been looking for a solution for the image lifecycle for GCR for a while

For my use case, I build and tag images based on git commit hash so all images built will forever have a tag. It would be nice to allow an option that purely checks age when deleting an image for this use case

I can implement this if ever it seems like a good idea!

cannot unmarshal string into Go struct

Hi,
I'm sending payload:
{"repo":"gcr.io/project/repo","grace":"24h"}
And I'm getting:
failed to decode payload as JSON: json: cannot unmarshal string into Go struct field Payload.grace of type time.Duration
But if I send an integer grace it works as it should.

I don't know golang, but to the best of my knowledge, I think the issue it's you created function UnmarshalJSON to handle different Grace formats (and calling time.ParseDuration) but you never called UnmarshalJSON. Related to GH-7 issue

Anyway, just letting you know. Thanks for the tool

Add IAM policy binding for Artifact Registry to documentation

The GCR Cleaner Cloud Run logged a Permission "artifactregistry.repositories.downloadArtifacts" denied error when trying to clean my Artifact Registry repo (see full error below). Running this command fixed the error:

gcloud --project "$PROJECT_ID" artifacts repositories add-iam-policy-binding $MY_REPO_NAME --location=$MY_LOCATION --member="serviceAccount:gcr-cleaner@${PROJECT_ID}.iam.gserviceaccount.com" --role=roles/artifactregistry.writer

It seems like that IAM command should be part of the setup documentation.

GCR Cleaner still does not work for me, but that fixed the error message. I assume I am now having the problem reported in #74.

{
  "insertId": "6245f1fe000af6533473f82e",
  "jsonPayload": {
    "message": "failed to clean repo \"us-central1-docker.pkg.dev/image-processing-svc-temp51/image-processing-service\": failed to list tags for repo us-central1-docker.pkg.dev/image-processing-svc-temp51/image-processing-service: GET https://us-central1-docker.pkg.dev/v2/token?scope=repository%3Aimage-processing-svc-temp51%2Fimage-processing-service%3Apull&service=us-central1-docker.pkg.dev: DENIED: Permission \"artifactregistry.repositories.downloadArtifacts\" denied on resource \"projects/image-processing-svc-temp51/locations/us-central1/repositories/image-processing-service\" (or it may not exist)",
    "error": "failed to clean repo \"us-central1-docker.pkg.dev/image-processing-svc-temp51/image-processing-service\": failed to list tags for repo us-central1-docker.pkg.dev/image-processing-svc-temp51/image-processing-service: GET https://us-central1-docker.pkg.dev/v2/token?scope=repository%3Aimage-processing-svc-temp51%2Fimage-processing-service%3Apull&service=us-central1-docker.pkg.dev: DENIED: Permission \"artifactregistry.repositories.downloadArtifacts\" denied on resource \"projects/image-processing-svc-temp51/locations/us-central1/repositories/image-processing-service\" (or it may not exist)"
  },
  "resource": {
    "type": "cloud_run_revision",
    "labels": {
      "location": "us-central1",
      "project_id": "image-processing-svc-temp51",
      "configuration_name": "gcr-cleaner",
      "revision_name": "gcr-cleaner-00001-yex",
      "service_name": "gcr-cleaner"
    }
  },
  "timestamp": "2022-03-31T18:25:02Z",
  "severity": "ERROR",
  "labels": {
    "instanceId": "00bf4bf02d98d4f10ed31d4f605304c118580a30e8beff310306e5981a0233688e35bf26eeb54d50650102283d7c40971e0bb6113ed9a0eceb48392c669102a098"
  },
  "logName": "projects/image-processing-svc-temp51/logs/run.googleapis.com%2Fstderr",
  "receiveTimestamp": "2022-03-31T18:25:02.723968263Z"
}

run gcr-cleaner-cli as CronJob on GKE

Hello, i'm trying to run gcr-cleaner-cli as CronJob on GKE (version 1.19.13-gke.701) with workload identity enabled, but i have an issue with the authentication on artifact registry hosted on GCP.

The configuration step that i did to allow the pod to handle the artifact registry are the following:

  1. create a service account on GCP named gcr-cleaner-gcp
    1.a associate to this service account the role roles/artifactregistry.repoAdmin
    1.b associate to this service account the workload identity binded with a ServiceAccount create in the cluster (gcr-cleaner-cluster) with role roles/iam.workloadIdentityUser
  2. create a ServiceAccount in the cluster named gcr-cleaner-cluster
  3. create a CronJob running with serviceAccountName: gcr-cleaner-cluster

Maybe the terraform step and the k8s yaml are easiest to understand:

Terraform:

resource "google_service_account" "gcr_cleaner" {
  account_id   = "gcr-cleaner"
  display_name = "GCR Cleaner Account"
}

resource "google_artifact_registry_repository_iam_binding" "artifact_registry_admin" {
  provider = google-beta

  depends_on = [
    google_service_account.gcr_cleaner
  ]

  project    = data.google_client_config.current.project
  location   = data.google_client_config.current.region
  repository = "container-registry"

  role = "roles/artifactregistry.repoAdmin"

  members = [
    "serviceAccount:${google_service_account.gcr_cleaner.email}"
  ]
}

resource "google_service_account_iam_binding" "service_account_workload_indentity" {
  depends_on = [
    google_service_account.gcr_cleaner
  ]

  service_account_id = google_service_account.gcr_cleaner.name
  role               = "roles/iam.workloadIdentityUser"

  members = [
    "serviceAccount:project-id.svc.id.goog[default/${google_service_account.gcr_cleaner.account_id}]",
  ]
}

K8s yaml:

kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/instance: gcr-cleaner
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: gcr-cleaner
    app.kubernetes.io/version: 0.5.0
    helm.sh/chart: gcr-cleaner-0.1.5
  name: gcr-cleaner
  namespace: default
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: gcr-cleaner-prod
  labels:
    helm.sh/chart: gcr-cleaner-0.1.4
    app.kubernetes.io/name: gcr-cleaner
    app.kubernetes.io/instance: gcr-cleaner
    app.kubernetes.io/version: "0.5.0"
    app.kubernetes.io/managed-by: Helm
spec:
  schedule: 18 16 * * *
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 180
  suspend: false
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1      
  jobTemplate:
    metadata:
      labels:
        app.kubernetes.io/name: gcr-cleaner
        app.kubernetes.io/instance: gcr-cleaner
    spec:
      template:
        spec:
          serviceAccountName: gcr-cleaner
          containers:
            - name: gcr-cleaner
              image: "europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli:latest"
              imagePullPolicy: IfNotPresent
              args:
                - -repo
                - "europe-west1-docker.pkg.dev/project-id/container-registry"
                - -recursive
                - -allow-tagged
                - -tag-filter
                - "^v?(\\d+\\.?){3}-\\d+$"
                - -keep
                - "3"
                - -grace
                - "24h"
              env:
                - name: GCRCLEANER_TOKEN
                  value: "hard coded token get by  gcloud auth print-access-token"                
          restartPolicy: Never

Is there any chance to run gcr-cleaner-cli in this way ? I'm already using this strategy with node and python applications to authenticate them to other google services like Cloud Trace and Cloud Storage and it's work fine. At the moment to run a cleanup one time per week i'm updating the env var GCRCLEANER_TOKEN with an hard coded token ... but is a really dirty job 😭

Have a nice day 😃

Allow deletion of NPM artifacts

Currently, when specifying a GAR NPM repository, the CLI tool does not detect any repositories. I'm not sure if this is in scope for this project, but it would be nice to be able to clean GAR language repositories (maven, pypi, npm) using this tool.

Arguments:

image: us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli
command: [ "/bin/gcrcleaner" ]
args:
  - -repo
  - us-central1-docker.pkg.dev/$PROJECT/$NPM_REPOSITORY
  - -dry-run
  - -recursive

Output:

WARNING: Running in dry-run mode - nothing will actually be cleaned!
Deleting refs older than 2022-07-08T19:03:00Z on 0 repo(s)...

Thanks!

Run locally

How to run it locally?

I tried go run . and on postman

{
	"repo": "gcr.io/PROEJCT_ID/IMAGE"
}

I tried and it return me this error

{
  "error":"failed to clean: failed to list tags for repo gcr.io/PROJECT_ID/IMAGE: Get https://gcr.io/v2/token?scope=repository%3APROJECT_ID%2FfIMAGE%3Apull\u0026service=gcr.io: failed to read metadata: Get http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token: dial tcp 169.254.169.254:80: i/o timeout"
}

Reordering tags

Hi guys,
First of all thank you for this amazing tool.

I tested the gcr-clear-cli locally with dry-run flag and I noticed something weird:

Screenshot 2021-09-29 at 10 02 26

After running gcr-cleaner, all tags were reordered because the Uploaded timestamp was updated.
Unfortunately I don't have a screenshot of the order before running gcr-cleaner :(

Is this an expected behaviour?
Can I prevent this to happen?

To be sure I tried on another repo and the behaviour was not the same, the order of tags remained the same]

Screenshot 2021-09-29 at 10 12 17

Some additional info:

  • running gcr-cleaner from main branch
  • running against private Google Container Registry eu.gcr.io
  • gcloud versions: Google Cloud SDK 358.0.0; beta 2021.09.17; bq 2.0.71; core 2021.09.17; gsutil 4.68

Bash script for anyone interested

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 <repo_name>

This script comes with NO WARRANTY. Use at your own risk

Unclear about CLI auth

So I see from the README this exists docker run -it us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli
but the auth it takes is a -token flag.

How would I get a valid token for my generated service account?

404 NOT_FOUND error on gcrcleaner server

After running all steps from documentation, the cloud run service is invoked but the server is returning a 404 NOT_FOUND error:

{
  "insertId": "tfdh11g24kbcwb",
  "jsonPayload": {
    "targetType": "HTTP",
    "jobName": "projects/my-test-project/locations/europe-west3/jobs/gcrclean-myimage",
    "url": "https://gcr-cleaner-uoq2gx4ada-uc.a.run.app/",
    "@type": "type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished",
    "status": "NOT_FOUND"
  },
  "httpRequest": {
    "status": 404
  },
  "resource": {
    "type": "cloud_scheduler_job",
    "labels": {
      "location": "europe-west3",
      "project_id": "my-test-project",
      "job_id": "gcrclean-myimage"
    }
  },
  "timestamp": "2021-04-02T09:05:29.742451978Z",
  "severity": "ERROR",
  "logName": "projects/my-test-project/logs/cloudscheduler.googleapis.com%2Fexecutions",
  "receiveTimestamp": "2021-04-02T09:05:29.742451978Z"
}

BTW:

  • I'm having the same error on local server:
docker run -e GCRCLEANER_TOKEN="$(gcloud auth print-access-token)" -p 8080:8080 gcr.io/gcr-cleaner/gcr-cleaner
server is listening on 8080
curl -X POST 'http://127.0.0.1:8080' -d '{"repo": "eu.gcr.io/my-test-project/test/nginx"}'
404 page not found
  • No error using local cli:
docker run -e GCRCLEANER_TOKEN="$(gcloud auth print-access-token)" gcr.io/gcr-cleaner/gcr-cleaner-cli --repo eu.gcr.io/my-test-project/test/nginx --grace 24h
eu.gcr.io/my-test-project/test/nginx: deleting refs since 2021-04-01 09:29:19.0290284 +0000 UTC
eu.gcr.io/my-test-project/test/nginx: successfully deleted 0 refs%

Could you please tell if I'm missing any configuration and is there a way to debug server's incoming requests?

Best,

Support ignore tag flag

Awesome utility! Thank you very much.

I went and setup about 20 different jobs with the grace and the allow_tagged flags enabled, and it just ended up deleting everything 😅

So, would it be possible for both grace and allow_tagged to work together, or: an ignore_tag flag so, the latest tagged image isn't deleted.

Cheers

Recursive flag causes failure in Pub Sub endpoint

Setting the recursive option to true when triggering the GCR cleaner server from the Pub Sub causes it to fail. I've observed this when deployed in cloud run and also replicated it running the server locally.

Resulting log message:

{"error":"failed to list child repositories for \"\": failed to fetch all repositories from registry europe-west2-docker.pkg.dev: context canceled","message":"failed to clean","severity":"ERROR","time":"2022-03-18T08:54:36Z"}

I believe this is because in the Pub Sub handler, the server.clean function is handed off to a goroutine while the original request is completed, closing the request context while server.clean is still working. However, the call to gcrremote.Catalog in cleaner.ListChildRepositories requires the context to still be open to succeed.

Tag image releases

It would be very helpful if images were tagged with their version. Currently, latest is the only tag.

Screen Shot 2021-11-01 at 11 44 06 AM

Library function uses fmt.Printf

The current behaviour is that when dryRun is true, the Clean function has the side-effect of printing many lines of output about what images and tags will be deleted.

As a user of the library, this is pretty annoying because I may not want to display this output in my own tools.

I think we can refactor the library code to communicate this information as a return value of the function and the executables in this project can use that value to print the same output as before.

The workaround for when you need no output or different output is an approach such as github.com/PumpkinSeed/cage.

ARM Architecture not supported

TL;DR

ARM Architecture not supported

Expected behavior

Docker container to run with ARM based architecture

Observed behavior

Docker container does not run with ARM based architecture

Debug log output

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested


### Additional information

_No response_

Add tag-filter-none (or tag-filter-exclude)

Currently, we have automation that tags all of our container images with the git commit SHA1 of the source code that generated them. We would like to clean up old and unused ones. We know which git commit SHA1 is currently deployed, and would like to exclude it from being deleted. grace gives us some protection, as we just pick a generous number and then hopefully our release cadence insures that we are safe. If we stop working in the repo for more than a month, someone has to remember to turn off the cleaner.

So for now we are doing the equivalent of this:

gcr-cleaner-cli -grace 720h -repo gcr.io/khan-internal-services/districts-jobs-roster -allow-tagged -keep "0" -tag-filter-any '.*'

Since golang doesn't support negative lookaheads in regex, we can instead use -tag-filter-all and play games with a non-match regex (this is a handy utility for that) and anything that isn't exactly the git commit SHA1. For the git SHA1 15852b1a96a5126f3fc83ba48437c98cf0768ad5 :

gcr-cleaner-cli -grace 720h -repo gcr.io/khan-internal-services/districts-jobs-roster -allow-tagged -keep "0" -tag-filter-any '^([^1]|1(1|5(852b1(5|a96a515))*(1|8(1|5(1|2(1|b1(1|a(1|9(1|6(1|a(1|51(1|2(1|6(1|f(1|3(1|f(1|c(1|8(1|3(1|b(1|a(1|4(1|8(1|4(1|3(1|7(1|c(1|9(1|8(1|c(1|f(1|0(1|7(1|6(1|8(1|a(1|d1))))))))))))))))))))))))))))))))))))*([^15]|5(852b1(5|a96a515))*([^18]|8([^15]|5([^12]|2([^1b]|b([^1]|1([^15a]|a([^19]|9([^16]|6([^1a]|a([^15]|5([^1]|1([^125]|2([^16]|6([^1f]|f([^13]|3([^1f]|f([^1c]|c([^18]|8([^13]|3([^1b]|b([^1a]|a([^14]|4([^18]|8([^14]|4([^13]|3([^17]|7([^1c]|c([^19]|9([^18]|8([^1c]|c([^1f]|f([^01]|0([^17]|7([^16]|6([^18]|8([^1a]|a([^1d]|d[^15])))))))))))))))))))))))))))))))))))))))*(1(1|5(852b1(5|a96a515))*(1|8(1|5(1|2(1|b1(1|a(1|9(1|6(1|a(1|51(1|2(1|6(1|f(1|3(1|f(1|c(1|8(1|3(1|b(1|a(1|4(1|8(1|4(1|3(1|7(1|c(1|9(1|8(1|c(1|f(1|0(1|7(1|6(1|8(1|a(1|d1))))))))))))))))))))))))))))))))))))*(5(852b1(5|a96a515))*(8(5?|52(b?|b1(a(9?|96((a5?)?|a51(2(6?|6f(3?|3f(c(8((3(b?|ba(4(8?|84(3?|37(c?|c9(8?|8c(f?|f0(7?|768?)))))))?))?|3ba48437c98cf0768ad?))?)?)))?)))?)))?)?)?$'

This seems... yucky and vaguely horrifying so we would rather have the capability to exclude certain tags using tag-filter-none (or tag-filter-exclude).

Feature Request: Integrate with GCP Cloud Asset Inventory ListAssets to filter images currently being used

TL;DR

GCP Cloud Asset Inventory allows you to list all container image urls currently being used by Pods in GKE and potentially other services (Cloud Run and App Engine?) in a GCP organization or individual GCP project. Much like the ability to filter out images in gcr-cleaner by allowlisted tags, this integration would allow for the option of filtering out images currently being used in GKE, Cloud Run, etc.

Expected behavior

No response

Observed behavior

No response

Debug log output

No response

Additional information

Here’s a simple gcloud command to demonstrate listing all currently used container image urls for all GKE Pods in your organization. Take this data, put it in a simple hash set, and filter out the container images from gcr-cleaner that should be preserved.

gcloud beta asset list --organization=$ORGANIZATION_ID --asset-types='k8s.io/Pod' --content-type='resource' --format="value(resource.data.spec.containers.image)"

Useful Cloud Asset Inventory API links to get started

https://cloud.google.com/asset-inventory/docs/libraries#client-libraries-usage-go
https://pkg.go.dev/cloud.google.com/go/asset/apiv1#Client.ListAssets
https://cloud.google.com/asset-inventory/docs/supported-asset-types

Recursive Flag Doesn't Work

Expected:

  • -recursive -repo gcr.io/foo/bar/ to remove untagged in gcr.io/foo/bar/ && gcr.io/foo/bar/a

Found:

  • -recursive -repo gcr.io/foo/bar/ to remove untagged in gc.io/foo/bar only

I'm looking at using this project to replace some custom scripts and when testing this project I discovered that the -recursive wasn't working correctly. I don't see a great way to provide logs without running actual deletes.

The bug is here: https://github.com/sethvargo/gcr-cleaner/blob/0b89326a00e41dbe7831da1db49e58c84e223742/pkg/gcrcleaner/cleaner.go#L187

It's comparing a prefix with the domain, e.g. the cli argument value but a _catalog or Catalog wont return a list of repositories with a hostname, just the "path" portion.

Support keep x latest tag base on tag pattern

Hi, great utility, thank you very much for creating this 👍

Is there a way that we can support to keep x latest tags base on some pattern, for repos that hold images for several mainstream releases ?

eg: Images built base on alpine and debian
Consider following tag:

  • 1.14.1-stretch, 1.14.2-stretch, 1.14.3-stretch, 1.14.4-stretch
  • 1.14.1-alpine, 1.14.2-alpine, 1.14.3-alpine, 1.14.4-alpine,
  • 1.13.9-stretch, 1.13.10-stretch, 1.13.11-stretch, 1.13.12-stretch,
  • 1.13.9-alpine, 1.13.10-alpine, 1.13.11-alpine, 1.13.12-alpine,

I would want to always keep 2 latest tags for each release stream: 1.14.x-stretch, 1.14.x-alpine, 1.13.x-stretch, 1.13.x-alpine

WDYT ?

CI CD examples?

I think most people collect old images because they push to gcr in a ci cd pipeline. It would be awesome to have minimum examples to run this container in CLI mode for github actions and maybe also travis or gitlab ci

Multiple Repos

Hi, thanks for this cool repo.

Is there a way to create one scheduler to clean images for multiple repos? This would reduce the cost as each job costs 0.1$ and this job is daily or weekly.

so instead of passing one repo, we pass multiple repos?

README.md Step 3 advises enabling App Engine -- is this necessary?

Based on my reading of the description of the project, it's not clear to me why enabling the App Engine API for running gcr-cleaner is necessary. It seems like Scheduler and Cloud Run should be sufficient.

If I am understanding correctly, I'd be happy to submit the small PR to remove it from README.md step 3.

[Question] Running using community terraform module / cloud run result

TL;DR

After running it using cloudrun & cloudscheduler, I can't find any useful logs which show what's going on.
For instance when I enabled dry-run, I couldn't find the output! I only see that job has succeeded!

Expected behavior

Getting the stdout

Observed behavior

unuseful logs

Debug log output

No response

Additional information

No response

Doc: Support for artifacts from other regions

Thanks for providing the docker image and guide for cleaning up gcr images, really appreciate it!

It took me some time to figure out how to make gcr-cleaner work with images that are hosted on eu.gcr.io.

I thought it might be helpful to share this for others who are using regions other than us:

  • Change gs://.artifacts.{PROJECT_ID}.appspot.com to gs://${ARTIFACTS_REGION}.artifacts.{PROJECT_ID}.appspot.com, where ${ARTIFACTS_REGION} is replaced with your region, e.g. eu.
  • The path to the registry containers (aka repo) must include the region too. ${ARTIFACTS_REGION}.gcr.io/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}"

I've created a shell script with which you have to only replace the variables at the top of the file and then run it. It supports defining multiple images under the same repository. Find the gist here

docs: tag_filter_any does not mention it will also match images without tags

For some time, I thought I would need to run two scheduled jobs. One job to remove images without tags and another one where I would make use of the tag_filter_any or tag_filter_all to delete tagged images that match a certain pattern in the tag name.

However, I see in the source code that untagged images are always removed.

If this is correct I think this might be valuable information to add in the documentation.

404 Return Code when Cloud Run Invoked

Hello, I have followed all instructions of setting up GCR cleaner with the project, but I keep getting a 404 when I pass in repos to be cleaned.

I am using Artifact Registry to store docker images in the us-east1 location
I removed the default network, and created another default network with only ssh ingress and private Google Services enabled

I used the gcloud --impersonate command with the service account created to make sure the service account has access and it does list all the images.

These are the permissions for the gcr-cleaner-docker-registry service account attached to the Cloud Run Service

gcloud projects get-iam-policy <project-id> --flatten="bindings[].members" --format='table(bindings.role)' --filter="bindings.members:gcr-cleaner-docker-registry@<project-id>.iam.gserviceaccount.com"
ROLE
roles/artifactregistry.repoAdmin
roles/storage.objectViewer

The Cloud invoker part works fine, since the requests do invoke the Cloud Run Service

The following is the JSON object that I was posting to the Cloud Run Service

{
	"repos": [
		"us-east1-docker.pkg.dev/<project-id>/docker-registry"
	]
}

I tried also adding the image to the URL above thinking I might have to specify each and every image, but got the same result.

I have also added the policy binding to docker-registry for the service account.

This is the log from Cloud Run Service

2022-05-15T16:31:25.865828Z POST 404 704 B 1 ms insomnia/2022.1.1 https://<url>/

Any pointers, where I might be going wrong?

CLI: Keep parameter cannot follow allow-tagged

First off, thanks for this utility. It's amazing!

I recently used gcr-cleaner to clean out some images, but noticed that more things were deleted than I expected. I had a safety net of 5 images to keep yet each repo I ran it against only kept 2 or 3 images.

It made me wonder if for some reason keep was just being assigned to the default value, regardless of what was passed in through the CLI. I made a minimal code reproduction to see if my hunch was correct:

package main

import (
	"flag"
	"fmt"
)

var (
	allowTaggedPtr = flag.Bool("allow-tagged", false, "Delete tagged images")
	keepPtr        = flag.Int("keep", 0, "Minimum to keep")
)

func main() {
	flag.Parse()
	allowTagged, keep := *allowTaggedPtr, *keepPtr
	fmt.Printf("Allow Tagged: %t, Keep: %d\n", allowTagged, keep)
}

I ran the test and keep seemed to work just fine:

go-arg-test|⇒ go run main.go -keep 5 -allow-tagged true
Allow Tagged: true, Keep: 5

However, when I swap the arguments, the value of keep is completely ignored:

go-arg-test|⇒ go run main.go -allow-tagged true -keep 5
Allow Tagged: true, Keep: 0

I added and removed arguments and it seems to me that keep only has problems when it follows allow-tagged. I thought it might be the kebab casing, but even renaming the arg allowTagged didn't help.

Going back to gcr-cleaner itself, I was able to confirm that this minimal reproduction held true when I ran it against another repo. If keep came first, it acknowledged the value.

dry_run not working

Hi Team,

First of all thanks for this tool, it is helpful.

I was trying the dry_run but nothing is written in the log exporter, is there any special configuration to set?

{"repo":"gcr.io/project-name/xxxx", "dry_run":true}

v0.7.1 release?

Hello.

On Jan 11 there was a commit that fixed an output bug. I was wondering if we could get this released as a new bugfix version. I recently tried to use the tool and was very confused with my dry_run outputs showing not only multiple commits but occasionally showing commits that had labels on them still.

Additionally, I would like to request a slight change to the fix. I have left the details in #72.

cannot unmarshal string into Go struct field

Hitting this error when i post the following params

{
	"repo": "asia.gcr.io/PROJECT_ID/IMAGE",
	"grace": "10h",
	"allow_tagged": true
}
{"error":"failed to decode payload as JSON: json: cannot unmarshal string into Go struct field Payload.grace of type time.Duration"}

gcr-cleaner doesn't work out of the box

Job configuration:

gcloud scheduler jobs create http "gcrclean-batch-server" \
  --project ${PROJECT_ID} \
  --description "Cleanup ${REPO}" \
  --uri "${SERVICE_URL}/http" \
  --message-body "{\"repo\":\"${REPO}\", \"allow_tagged\":true, \"grace\":\"480h\"}" \
  --oidc-service-account-email "gcr-cleaner-invoker@${PROJECT_ID}.iam.gserviceaccount.com" \
  --schedule "0 0 * * *" \
  --time-zone="US/Eastern"

Google Cloud error message:

{
textPayload: "The request failed because either the HTTP response was malformed or connection to the instance had an error."
insertId: "id"
httpRequest: {
requestMethod: "POST"
requestUrl: "example.url"
requestSize: "1311"
status: 503
responseSize: "664"
userAgent: "Google-Cloud-Scheduler"
latency: "0.379463365s"
protocol: "HTTP/1.1"
}
resource: {
type: "cloud_run_revision"
labels: {5}
}
timestamp: "2021-04-06T20:31:53.024408Z"
severity: "ERROR"
labels: {1}
logName: "projects/example-internal/logs/run.googleapis.com%2Frequests"
trace: "projects/example-internal/traces/bdc53a52ab28121e4819fb006a9f6fe5"
receiveTimestamp: "2021-04-06T20:31:53.027902663Z"
}
http: panic serving 169.254.8.129:38751: runtime error: index out of range [0] with length 0
goroutine 13 [running]:
net/http.(*conn).serve.func1(0xc00019e0a0)
	net/http/server.go:1801 +0x147
panic(0x74c000, 0xc0003dd440)
	runtime/panic.go:975 +0x47a
github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner.(*Cleaner).shouldDelete(0xc00000c200, 0x18c7a5d0, 0xc0002623c0, 0x34, 0x3019d7c0, 0xed7c0af6b, 0x98ec60, 0x3019d7c0, 0xed7c0b02f, 0x98ec60, ...)
	github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner/cleaner.go:165 +0x1b8
github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner.(*Cleaner).Clean(0xc00000c200, 0xc0001a4060, 0x2c, 0x326499c2, 0xed7e45e38, 0x0, 0x1, 0x0, 0xc00019e1e0, 0x0, ...)
	github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner/cleaner.go:82 +0x82c
github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner.(*Server).clean(0xc00000e058, 0x7d6a20, 0xc0000663c0, 0x7d4480, 0xc000184120, 0xc000014225, 0xc0001a4000, 0x0, 0x0)
	github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner/server.go:142 +0x51a
github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner.(*Server).HTTPHandler.func1(0x7d97e0, 0xc0001840e0, 0xc000058700)
	github.com/sethvargo/gcr-cleaner/pkg/gcrcleaner/server.go:94 +0x5b
net/http.HandlerFunc.ServeHTTP(0xc0000101a0, 0x7d97e0, 0xc0001840e0, 0xc000058700)
	net/http/server.go:2042 +0x44
net/http.(*ServeMux).ServeHTTP(0xc000066200, 0x7d97e0, 0xc0001840e0, 0xc000058700)
	net/http/server.go:2417 +0x1ad
net/http.serverHandler.ServeHTTP(0xc000184000, 0x7d97e0, 0xc0001840e0, 0xc000058700)
	net/http/server.go:2843 +0xa3
net/http.(*conn).serve(0xc00019e0a0, 0x7d9ea0, 0xc000066340)
	net/http/server.go:1925 +0x8ad
created by net/http.(*Server).Serve
	net/http/server.go:2969 +0x36c

Any idea what's going on here?

Add a dry-run flag

It would be useful to have a dry-run flag of some form that will scan the repo and print out the list of images that it would prune without actually pruning. This would be valuable when initially setting this up to ensure that you have not misconfigured one of the fields. A misconfiguration could result in deleting images used in Production which would not be good.

Example

gcr-cleaner-cli -repo gcr.io/my-repo -dry-run

json_key authentication not supported

I Do the following in a github action. Looks like json_key authentication is not supported. Is there a way around this?

 - uses: docker/login-action@v2
        with:
          registry: us-central1-docker.pkg.dev
          username: _json_key
          password: ${{ secrets.GOOGLE_SVC_ACCOUNT_JSON }}
  - uses: 'docker://us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli'
        with:
          args: >-
            -repo=us-central1-docker.pkg.dev/<myrepo>
            -keep=3
            -tag-filter-all=dev.+$

gives me the error
"failed to setup auther: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information."

Can't launch gcr-cleaner on local for tests

TL;DR

Hello, I tried to launch gcr-cleaner on local to test it but then, it says i'm not authentified while i am authentified with gcloud. Does it need another form of authentication ?

Expected behavior

I tried to launch the command
docker run -it us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli -dry-run -repo eu.gcr.io/registry
and would suppose to see the plugin working but not.

Observed behavior

It says me i'm not connected and shows this error :
failed to setup auther: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

Debug log output

No response

Additional information

No response

Exempt most recent ref image from cleanup?

What do you think about a flag to exempt the most recent image from cleanup?

We find that with some of our images, the cadence of updates varies, especially over long holidays 😏.

Using recursive is not working when deployed as a Cloud Run service and invoked via Cloud Scheduler

I have one repository test having two child repositories nginx & nginx2 in my project my-test-project:

gcloud container images list --repository eu.gcr.io/my-test-project --format json | jq -r '.[].name'
eu.gcr.io/my-test-project/testgcloud container images list --repository eu.gcr.io/my-test-project/test --format json | jq -r '.[].name'
eu.gcr.io/my-test-project/test/nginx
eu.gcr.io/my-test-project/test/nginx2

Each child repository has one untagged image:

gcloud container images list-tags eu.gcr.io/my-test-project
Listed 0 items.gcloud container images list-tags eu.gcr.io/my-test-project/test
Listed 0 items.gcloud container images list-tags eu.gcr.io/my-test-project/test/nginx
DIGEST        TAGS    TIMESTAMP
6dd48dba5945  latest  2021-10-01T18:22:48
42bba58a1c5a          2021-04-13T21:20:40gcloud container images list-tags eu.gcr.io/my-test-project/test/nginx2
DIGEST        TAGS  TIMESTAMP
aa3bfd8050fd        2021-11-19T16:56:55
42bba58a1c5a  0.1   2021-04-13T21:20:40

I deployed the stack following the setup:

export PROJECT_ID="my-test-project"gcloud services enable --project "${PROJECT_ID}" \                                                                                                                                                                                 ─╯
  appengine.googleapis.com \
  cloudscheduler.googleapis.com \
  run.googleapis.comgcloud iam service-accounts create "gcr-cleaner" \                                                                                                                                                                                 ─╯
  --project "${PROJECT_ID}" \
  --display-name "gcr-cleaner"gcloud --quiet run deploy "gcr-cleaner" \                                                                                                                                                                                          ─╯
  --async \
  --project ${PROJECT_ID} \
  --platform "managed" \
  --service-account "gcr-cleaner@${PROJECT_ID}.iam.gserviceaccount.com" \
  --image "europe-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner" \
  --region "europe-north1" \
  --timeout "60s"gsutil acl ch -u gcr-cleaner@${PROJECT_ID}.iam.gserviceaccount.com:W gs://eu.artifacts.${PROJECT_ID}.appspot.comgcloud iam service-accounts create "gcr-cleaner-invoker" \                                                                                                                                                                         ─╯
  --project "${PROJECT_ID}" \
  --display-name "gcr-cleaner-invoker"gcloud run services add-iam-policy-binding "gcr-cleaner" \                                                                                                                                                                         ─╯
  --project "${PROJECT_ID}" \
  --platform "managed" \
  --region "europe-north1" \
  --member "serviceAccount:gcr-cleaner-invoker@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role "roles/run.invoker"gcloud app create \                                                                                                                                                                                                                ─╯
  --project "${PROJECT_ID}" \
  --region "europe-west3" \
  --quietexport SERVICE_URL=$(gcloud run services describe gcr-cleaner --project "${PROJECT_ID}" --platform "managed" --region "europe-north1" --format 'value(status.url)')export REPO="eu.gcr.io/${PROJECT_ID}"gcloud scheduler jobs create http "gcrclean-myimage" \                                                                                                                                                                             ─╯
  --project ${PROJECT_ID} \
  --description "Cleanup ${REPO}" \
  --uri "${SERVICE_URL}/http" \
  --message-body "{\"repo\":\"${REPO}\", \"recursive\":true}" \
  --oidc-service-account-email "gcr-cleaner-invoker@${PROJECT_ID}.iam.gserviceaccount.com" \
  --schedule "0 2 * * 5" \
  --time-zone="Europe/Helsinki"

So I added "recursive":true to the payload --message-body "{\"repo\":\"${REPO}\", \"recursive\":true}", then I manually launched the job.
The job has a Success status, and nothing special from cloud run logs:

Info
2021-11-19T16:50:56.701466Z
Cloud Run gcr-cleaner {@type: type.googleapis.com/google.cloud.audit.AuditLog, resourceName: namespaces/my-test-project/services/gcr-cleaner, response: {…}, serviceName: run.googleapis.com, status: {…}}
Default
2021-11-19T17:02:01.455463Z
deleting refs for eu.gcr.io/my-test-project since 2021-11-19 17:02:01.455315674 +0000 UTC
Info
2021-11-19T17:02:02.780201Z
POST200 727 B 1.3 s Google-Cloud-Scheduler https://gcr-cleaner-ascdkoub3a-lz.a.run.app/http
Default
2021-11-19T17:02:08.255180Z
server is listening on 8080

However, no images have been deleted:

gcloud container images list-tags eu.gcr.io/my-test-project/test/nginx
DIGEST        TAGS    TIMESTAMP
6dd48dba5945  latest  2021-10-01T18:22:48
42bba58a1c5a          2021-04-13T21:20:40gcloud container images list-tags eu.gcr.io/my-test-project/test/nginx2
DIGEST        TAGS  TIMESTAMP
aa3bfd8050fd        2021-11-19T16:56:55
42bba58a1c5a  0.1   2021-04-13T21:20:40

Could you please have a look and tell me what was misconfigured or if this is a bug somewhere?
Best!

[Question] Keep By Tag?

Hello!

I have just discovered this tool.

Is it possible to keep all images with a specific tag? For example, I want GCR Cleaner to delete all images except the image with the "latest" tag.

What command do I pass to make that happen? If possible?

Too many images deleted from registry

Hello, is not the first time that happens, but to be honest i'm not able to reproduce the issue. I tried many times with --dry-run option and the keep and grace parameters are always respected. On the same registry i'm using three different kind of tag version-alpha, version-rc, version-feature-name and for production images just version.

Every day i'm running gcr-cleaner as kubernets's CronJob with the following parameters:

-repo europe-west1-docker.pkg.dev/project-id/container-registry -recursive -tag-filter-all .*-alpha\..* -keep 3 -grace 24h

On the machine-events-srv folder yesterday there were 3 images:

1.4.0-alpha.1-212 built N/A
1.4.0-alpha.2-2 built at 03/03/22 16:33 with kaniko (cache enabled)
1.4.0-alpha.3-3 built at 03/21/22 14:30 with kaniko (cache enabled)

and with the last run of gcr-cleaner alla images has been removed:

2022-04-06 18:18:06.707 CEST Deleting refs since 2022-04-05T16:18:06Z on 60 repo(s)...
2022-04-06 18:18:06.707 CEST {}
....
2022-04-06 18:18:08.200 CEST europe-west1-docker.pkg.dev/project-id/container-registry/machine-events-srv
2022-04-06 18:18:08.524 CEST ✓ 1.4.0-alpha.1-212
2022-04-06 18:18:08.524 CEST ✓ 1.4.0-alpha.2-2
2022-04-06 18:18:08.524 CEST ✓ 1.4.0-alpha.3-3
2022-04-06 18:18:08.524 CEST ✓ sha256:29137bc3f368a7d47d854f70b53b18c713bafcb46b2f9beb7d1d1955fc15d217
2022-04-06 18:18:08.524 CEST ✓ sha256:c4c9b09141b84f595093676a1f86ad57c874272b9d6111e78fd10ab10bd0f0e3
2022-04-06 18:18:08.524 CEST ✓ sha256:c4c9b09141b84f595093676a1f86ad57c874272b9d6111e78fd10ab10bd0f0e3
2022-04-06 18:18:08.524 CEST {}
....

Do you have any suggestions ? Is there something wrong with my cleanup strategy ?

Not deleting image versions in artifact registry

I have setup gcr-cleaner in Cloud Run using us-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner. The Cloud Scheduler job is passing the following payload:

{"dry_run":false,"keep":1,"recursive":true,"repos":"us-central1-docker.pkg.dev/my-project/my-repo"}

When executed I see the following info/debug logs:

{
  "insertId": "6244a38d000d46f4fc3176b4",
  "jsonPayload": {
    "repos": [
      "us-central1-docker.pkg.dev/my-project/my-repo",
      "us-central1-docker.pkg.dev/my-project/my-repo/my-image"
    ],
    "since": "2022-03-30T18:38:05.663332949Z",
    "message": "deleting refs"
  },
  "resource": {
    "type": "cloud_run_revision",
    "labels": {
      "project_id": "my-project",
      "location": "us-central1",
      "configuration_name": "gcr-cleaner",
      "revision_name": "gcr-cleaner-5gwql",
      "service_name": "gcr-cleaner"
    }
  },
  "timestamp": "2022-03-30T18:38:05Z",
  "severity": "INFO",
  "labels": {
    "instanceId": "00bf4bf02d8c102af0b010b37017868833bc2c7cbb65d488b4575d79e0465be497d6206d279f9acaa7f8451534764f72b401fd3ada22dd770632deee5644"
  },
  "logName": "projects/my-project/logs/run.googleapis.com%2Fstdout",
  "receiveTimestamp": "2022-03-30T18:38:05.874229456Z"
}
{
  "insertId": "6244a38d000d4721a31029b8",
  "jsonPayload": {
    "message": "deleting refs for repo",
    "repo": "us-central1-docker.pkg.dev/my-project/my-repo"
  },
  "resource": {
    "type": "cloud_run_revision",
    "labels": {
      "project_id": "my-project",
      "location": "us-central1",
      "service_name": "gcr-cleaner",
      "revision_name": "gcr-cleaner-5gwql",
      "configuration_name": "gcr-cleaner"
    }
  },
  "timestamp": "2022-03-30T18:38:05Z",
  "severity": "DEBUG",
  "labels": {
    "instanceId": "00bf4bf02d8c102af0b010b37017868833bc2c7cbb65d488b4575d79e0465be497d6206d279f9acaa7f8451534764f72b401fd3ada22dd770632deee5644"
  },
  "logName": "projects/my-project/logs/run.googleapis.com%2Fstdout",
  "receiveTimestamp": "2022-03-30T18:38:05.874229456Z"
}
{
  "insertId": "6244a38d000f36260cc14f75",
  "jsonPayload": {
    "message": "deleting refs for repo",
    "repo": "us-central1-docker.pkg.dev/my-project/my-repo/my-image"
  },
  "resource": {
    "type": "cloud_run_revision",
    "labels": {
      "revision_name": "gcr-cleaner-5gwql",
      "project_id": "my-proj",
      "configuration_name": "gcr-cleaner",
      "location": "us-central1",
      "service_name": "gcr-cleaner"
    }
  },
  "timestamp": "2022-03-30T18:38:05Z",
  "severity": "DEBUG",
  "labels": {
    "instanceId": "00bf4bf02d8c102af0b010b37017868833bc2c7cbb65d488b4575d79e0465be497d6206d279f9acaa7f8451534764f72b401fd3ada22dd770632deee5644"
  },
  "logName": "projects/my-project/logs/run.googleapis.com%2Fstdout",
  "receiveTimestamp": "2022-03-30T18:38:06.207204185Z"
}
{
  "insertId": "1t7yjlafjlvxp9",
  "jsonPayload": {
    "url": "https://gcr-cleaner-ip4job3fda-uc.a.run.app/http",
    "targetType": "HTTP",
    "@type": "type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished",
    "jobName": "projects/my-project/locations/us-central1/jobs/gcr-job"
  },
  "httpRequest": {
    "status": 200
  },
  "resource": {
    "type": "cloud_scheduler_job",
    "labels": {
      "job_id": "gcr-job",
      "location": "us-central1",
      "project_id": "my-project"
    }
  },
  "timestamp": "2022-03-30T18:38:06.127451233Z",
  "severity": "INFO",
  "logName": "projects/my-project/logs/cloudscheduler.googleapis.com%2Fexecutions",
  "receiveTimestamp": "2022-03-30T18:38:06.127451233Z"
}

I have three versions in us-central1-docker.pkg.dev/my-project/my-repo/my-image. One is tagged latest and the other two are untagged. Even with the argument to keep:1 and the logs showing 200 OK none of the images are being pruned. Any help would be appreciated.

"keep" did not retain newest images

Contrary to my expectations, the "keep" parameter did not retain the newest X images. The remaining images are from random dates.

Is there a regex expression to use in "tag_filter_any", so that it keeps the images with the "latest" tag? My tags look something like:

(b095b33c-9b58-aaaa-bbbb-xxxxxxxxxxxxxxx) (latest)
(xnsdfnwe4-wr5f-9b58-bbbb-xxxxxxxxxxxxxxx)
(kkfgn8jf8j4-q4fg-aaaa-1233-xxxxxxxxxxxxxxx)
etc.

Thanks for the great package! My artifact registry had grown into the hundreds of GB.

permissions to use recursive parameter

I have created sa using

gcloud artifacts repositories add-iam-policy-binding "${REPO_NAME}" \
    --project "${PROJECT_ID}" \
    --location "${LOCATION}" \
    --member "serviceAccount:gcr-cleaner@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role "roles/artifactregistry.repoAdmin"

however, if I try to remove docker images and provide only path to repo "europe-west2-docker.pkg.dev/**project_id**/python" with parameter "recursive" it returns nothing, however if I grant sa "gcr-cleaner" role "roles/storage.objectViewer" or permission "resourcemanager.projects.get" then it works.
Is it by design ? how is it possible to limit access to this user ?

Better logging for deleted images

I'm finding that the logging info, in particular when using pubsub is limited. It's hard to tell if the process actually completed. It will log a start message but then nothing else. I'm not sure if this happened due #42 or not.

It would also be good if there was some stats around what was deleted besides just a count, e.g how much space has been saved.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.