gar-cleanup is a Go binary that manages the lifecycle of container images stored in Google Artifact Registry (GAR). It replaces the deprecated gcr.io-based shell script with a production-grade, policy-driven tool designed to run as a scheduled Cloud Run Job.
The system provides:
- Policy-as-code image retention rules (YAML, reviewed via PR)
- Kubernetes guard to prevent deletion of images actively in use by workloads
- BigQuery audit trail for cost attribution and compliance
- Dry-run by default for safe operation
| Phase | Scope | Status |
|---|---|---|
| 1 – Core Engine | Go binary, Cobra CLI, policy engine, engine orchestration, dry-run mode | ✅ Done |
| 2 – K8s Guard | client-go integration; protect digests in use by Pods/RS | 🔲 Stub (NoopGuard + ClusterGuard interface ready) |
| 3 – Real GAR Client | Artifact Registry REST API calls via google.golang.org/api | 🔲 Stub (GARClient scaffolded, methods return nil) |
| 4 – BigQuery Audit | Stream deletion events to BQ for dashboards | 🔲 Stub (BigQueryWriter scaffolded, LogWriter active) |
| 5 – Multi-region | Iterate over multiple GAR locations and registries | 🔲 Planned |
cmd/gar-cleanup/
main.go ← Cobra CLI: `run` and `version` subcommands
internal/
policy/
policy.go ← YAML policy loading, tag regex protection, duration parsing
policy_test.go ← Policy validation and evaluation tests
registry/
registry.go ← Client interface (ListRepositories, ListImages, Delete, Untag)
gar.go ← GARClient stub (→ real API in phase 3) + FakeClient for tests
engine/
engine.go ← Orchestrates inventory → evaluation → delete → audit
engine_test.go ← Engine tests using FakeClient
k8s/
guard.go ← Guard interface, NoopGuard, ClusterGuard stub (→ client-go in phase 2)
audit/
audit.go ← Writer interface, LogWriter (active), BigQueryWriter stub (→ phase 4)
terraform/
main.tf ← Service account, Cloud Run Job, Scheduler, BigQuery table
variables.tf ← Input variables
outputs.tf ← Output values
policy.yaml ← Default policy (dry_run: true, keep: 10, max_age: 30d)
gcr-cleanup.sh ← Deprecated legacy shell script (replaced by this tool)
The policy file defaults to dry_run: true. Live deletions require an explicit --live flag and dry_run: false in the policy. This two-factor confirmation prevents accidental deletion.
policy.yaml is committed to the repository and changes go through pull request review. The policy is mounted into the Cloud Run Job at runtime (via Secret Manager volume in production).
The Engine accepts WithRegistry, WithGuard, and WithAuditor options so all external dependencies can be replaced with fakes in unit tests — no mocking frameworks required.
registry.Client, k8s.Guard, and audit.Writer are Go interfaces. The production implementations (GAR API, client-go, BigQuery) are replaced by stubs/fakes during testing without any changes to the engine.
The cleanup task has a defined start and end. Cloud Run Jobs provide exactly-once execution semantics, built-in retries, and native IAM integration — a better fit than a Deployment or Cloud Function.
Cloud Scheduler → HTTP POST → Cloud Run Job
↓
google_service_account "gar-cleanup"
↓
┌───────────────┼───────────────┐
↓ ↓ ↓
artifactregistry container.cluster bigquery.data
.repoAdmin Viewer Editor
Authentication uses Workload Identity — the Cloud Run Job runs as the gar-cleanup service account and receives short-lived tokens automatically. No key files are stored or rotated manually.
For local development, gcloud auth application-default login provides equivalent credentials.
LoadFile(path) → Policy{
keep: int // retain N most-recent images
max_age: duration // delete images older than this
protect_tags: []*regexp // never delete matching tags
delete_untagged: bool // delete digests with no tags
dry_run: bool // policy-level dry-run override
}
- K8s guard — if digest is in use by any Pod/RS,
actionK8sProtect - Untagged — if no tags and
delete_untagged: true,actionDelete - Protected tag — if any tag matches
protect_tags,actionRetain - Keep count — if rank <
keep,actionRetain - Max age — if pushed before cutoff,
actionDelete - Default —
actionRetain
| Signal | Implementation | Phase |
|---|---|---|
| Structured logs | log/slog JSON to Cloud Logging |
✅ Now |
| Audit events (dry-run) | LogWriter → Cloud Logging |
✅ Now |
| Audit events (live) | BigQueryWriter → BQ table |
Phase 4 |
| Metrics | Cloud Monitoring via Cloud Run built-ins | Phase 5 |
| Alerting | BQ scheduled query → Cloud Monitoring | Phase 5 |
All log lines include project, location, mode, repository, digest, and reason fields for easy filtering in Cloud Logging.