Terraform GCP Artifact Registry cleanup policies and a Rust census of what will delete
Dry-run evaluates and deletes nothing. Declare the rules and the flag, then ask the API.
<!-- hal:authoritative:yaml -->
A repository without a DELETE policy keeps everything forever. A repository with dry_run true evaluates the same rules and deletes nothing. Declare both the rules and the dry-run flag, then ask the API what each repository actually has.
§I. Frame
Yesterday's Cloud Ops counted Pub/Sub subscriptions against a 31-day deletion clock. Tonight is TF day. The cloud-native referent is GCP Artifact Registry: the regional package shelf that holds Docker images, Maven artifacts, and the rest of the format enum.
Cloud Run 09-23 already pushed images from ${region}-docker.pkg.dev/${project}/apps/.... That lesson stopped at the image string. It did not declare the repository, did not attach cleanup policies, and did not grant a CI reader. ECR 09-26 taught registry-level scanning on AWS. Those two shelves are adjacent and not the same claim.
The problem for today: declare a Docker Artifact Registry repository the current way in Terraform (format, mode, cleanup policies, IAM reader), then build a read-only Rust census that reports format, mode, dry-run, and how many DELETE versus KEEP policies each repository carries.
§II. Three owners on one repository
Retention Owner (named technique). Ask which field owns deletion before asking what the policy ID is called.
- Format and mode belong to create-time fields on
google_artifact_registry_repository.format = "DOCKER"cannot becomeMAVENlater.mode = "STANDARD_REPOSITORY"is the ordinary store;VIRTUAL_REPOSITORYandREMOTE_REPOSITORYare different shelves. - Cleanup belongs to
cleanup_policiesblocks on the same resource, pluscleanup_policy_dry_run. DELETE conditions match untagged or aged versions. KEEP rules protect a minimum recent set. Whencleanup_policy_dry_run = true, the pipeline evaluates and refuses to delete. - Pull rights belong to a separate address:
google_artifact_registry_repository_iam_member. Creating the repository does not grantroles/artifactregistry.readerto CI.
If you mix up the owners, a green plan can describe retention Google will never enforce, or a repo nobody can pull.
§III. The Terraform
This module is the teaching referent. Validate it with terraform init -backend=false and terraform validate against hashicorp/google ~> 6.0. Do not apply from this fire; no project mutations tonight.
resource "google_artifact_registry_repository" "apps" {
location = var.region
repository_id = "apps"
description = "Docker images for services"
format = "DOCKER"
mode = "STANDARD_REPOSITORY"
cleanup_policy_dry_run = false
cleanup_policies {
id = "delete-untagged"
action = "DELETE"
condition {
tag_state = "UNTAGGED"
older_than = "2592000s" # 30 days
}
}
cleanup_policies {
id = "keep-minimum"
action = "KEEP"
most_recent_versions {
keep_count = 10
}
}
}
resource "google_artifact_registry_repository_iam_member" "ci_reader" {
project = var.project
location = google_artifact_registry_repository.apps.location
repository = google_artifact_registry_repository.apps.name
role = "roles/artifactregistry.reader"
member = "serviceAccount:${var.ci_runner_sa}"
}
Fact one. cleanup_policy_dry_run = true is the silent no-op. Reviewers who only count policy blocks will miss it.
Fact two. KEEP and DELETE cooperate. A KEEP of the ten most recent versions can protect tagged releases while DELETE clears untagged layers older than thirty days.
Fact three. The IAM member is its own plan row. Bind the CI runner SA as reader. Do not grant allUsers reader on a private apps repo.
Fact four. Format is immutable. Changing DOCKER to something else forces a replace. That is a Cert adjacency for tonight's lifecycle lesson; do not paper over it with ignore_changes on format.
Bundle file: artifact-registry.tf next to this lesson.
§IV. The Rust census
Automation is a cargo script, not a Python CLI. The Google Cloud Rust client google-cloud-artifactregistry-v1 exposes ArtifactRegistry::list_repositories. Each Repository carries format, mode, cleanup_policies, cleanup_policy_dry_run, and size_bytes.
let client = ArtifactRegistry::builder().build().await?;
let parent = format!("projects/{project}/locations/{location}");
let mut repos = client.list_repositories().set_parent(&parent).by_item();
while let Some(r) = repos.next().await.transpose()? {
// classify: id, format.name(), mode.name(), dry_run,
// count DELETE vs KEEP in cleanup_policies, size_bytes
}
Full script: artifact-registry-census.rs (nightly -Zscript). Nix wrapper: artifact-registry-census.nix (writeBashBin).
Illustrative output (ADC on the lab Mac is stale with invalid_grant as of this fire; the lab must run gcloud auth application-default login and gcloud auth login before a live census):
apps format=DOCKER mode=STANDARD_REPOSITORY dry_run=false policies=2 (delete=1 keep=1) size_bytes=0
legacy format=DOCKER mode=STANDARD_REPOSITORY dry_run=true policies=1 (delete=1 keep=0) size_bytes=4096 ATTENTION
scratch format=PYTHON mode=STANDARD_REPOSITORY dry_run=false policies=0 (delete=0 keep=0) size_bytes=0 ATTENTION
summary location=us-central1 repositories=3 dry_run=1 bare=1 attention=2
Flag ATTENTION when dry_run is true or when policies is zero. Those two states are the retention bugs the census exists to find.
§V. How to run
# Validate the referent (no apply)
terraform init -backend=false
terraform validate
# Census (needs working ADC)
cargo +nightly -Zscript ./artifact-registry-census.rs "$PROJECT" us-central1
# or: nix-build -E 'with import <nixpkgs> {}; callPackage ./artifact-registry-census.nix {}'
If ADC refresh fails with invalid_grant, stop. Re-auth is a human step on the lab Mac. Do not paste tokens into the lesson tree.
§VI. Close
Artifact Registry is the GCP package shelf. Tonight's Ops claim is three owners on one repository: immutable format/mode, cleanup policies plus dry-run, and a separate IAM reader. The Rust census reads what Google reports, not what the HCL hoped.
Paired Dev: Rust TF integration tests (std::process + serde_json) that apply and destroy a provider-free fixture. Paired Cert: lifecycle meta-arguments when format changes force a replace.
Related
- Tome: Brikman 3e p.89 (provider credentials via environment / ADC) — referenced
- Bootcamp: GCP PCA Notes (Artifact Registry) — referenced; tfpro Lab 15 (lifecycle retention adjacency) — referenced
- Prior Ops: Pub/Sub expiry 09-28 · ECR scan 09-26 · Cloud Run 09-23 · SG/NACL 09-20