Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: runwhen.com/v1
kind: GenerationRules
spec:
platform: gcp
generationRules:
- resourceTypes:
- gcp_artifactregistry_repositories
matchRules:
- type: pattern
pattern: ".+"
properties: ["name"]
mode: substring
slxs:
- baseName: gcp-artifact-registry-governance
qualifiers: ["project", "location", "repository"]
baseTemplateName: gcp-artifact-registry-governance
levelOfDetail: basic
outputItems:
- type: slx
- type: sli
- type: runbook
templateName: gcp-artifact-registry-governance-taskset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelIndicator
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
displayUnitsLong: OK
displayUnitsShort: ok
locations:
- {{default_location}}
description: Measures Artifact Registry governance health from cleanup policies, stale images, untagged manifests, and storage utilization.
codeBundle:
{% if repo_url %}
repoUrl: {{repo_url}}
{% else %}
repoUrl: https://github.com/runwhen-contrib/rw-cli-codecollection.git
{% endif %}
{% if ref %}
ref: {{ref}}
{% else %}
ref: main
{% endif %}
pathToRobot: codebundles/gcp-artifact-registry-governance/sli.robot
intervalStrategy: intermezzo
intervalSeconds: 300
configProvided:
- name: GCP_PROJECT_ID
value: "{{ match_resource.resource.project | default(match_resource.resource.project_id) }}"
- name: ARTIFACT_REGISTRY_LOCATION
value: "{{ match_resource.resource.location }}"
- name: ARTIFACT_REGISTRY_REPOSITORY
value: "{{ match_resource.resource.name }}"
- name: ARTIFACT_REGISTRY_LOCATIONS
value: "{{ match_resource.resource.location }}"
- name: ARTIFACT_REGISTRY_REPOSITORIES
value: "{{ match_resource.resource.name }}"
- name: STALE_IMAGE_THRESHOLD_DAYS
value: "{{ custom.stale_image_threshold_days | default('90') }}"
- name: UNTAGGED_IMAGE_THRESHOLD_DAYS
value: "{{ custom.untagged_image_threshold_days | default('30') }}"
- name: STORAGE_UTILIZATION_THRESHOLD_GB
value: "{{ custom.storage_utilization_threshold_gb | default('50') }}"
- name: MIN_TAGS_TO_KEEP
value: "{{ custom.min_tags_to_keep | default('5') }}"
secretsProvided:
{% if wb_version %}
{% include "gcp-auth.yaml" ignore missing %}
{% else %}
- name: gcp_credentials
workspaceKey: AUTH DETAILS NOT FOUND
{% endif %}
alertConfig:
tasks:
persona: eager-edgar
sessionTTL: 10m
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
apiVersion: runwhen.com/v1
kind: ServiceLevelX
metadata:
name: {{ slx_name }}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/gcp/cloud_run/cloud_run.svg
alias: {{ match_resource.resource.name }} Artifact Registry Governance
asMeasuredBy: Governance health of Artifact Registry repository lifecycle configuration and storage utilization.
configProvided:
- name: SLX_PLACEHOLDER
value: SLX_PLACEHOLDER
owners:
- {{ workspace.owner_email }}
statement: Artifact Registry repositories should enforce cleanup policies and keep storage aligned to required artifacts only.
additionalContext:
{% include "gcp-hierarchy.yaml" ignore missing %}
qualified_name: "{{ match_resource.qualified_name }}"
tags:
{% include "gcp-tags.yaml" ignore missing %}
- name: cloud
value: gcp
- name: service
value: artifact_registry
- name: scope
value: repository
- name: access
value: read-only
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
apiVersion: runwhen.com/v1
kind: Runbook
metadata:
name: {{slx_name}}
labels:
{% include "common-labels.yaml" %}
annotations:
{% include "common-annotations.yaml" %}
spec:
location: {{default_location}}
description: Inspects GCP Artifact Registry repositories for stale images, missing cleanup policies, legacy GCR usage, and storage utilization.
codeBundle:
{% if repo_url %}
repoUrl: {{repo_url}}
{% else %}
repoUrl: https://github.com/runwhen-contrib/rw-cli-codecollection.git
{% endif %}
{% if ref %}
ref: {{ref}}
{% else %}
ref: main
{% endif %}
pathToRobot: codebundles/gcp-artifact-registry-governance/runbook.robot
configProvided:
- name: GCP_PROJECT_ID
value: "{{ match_resource.resource.project | default(match_resource.resource.project_id) }}"
- name: ARTIFACT_REGISTRY_LOCATION
value: "{{ match_resource.resource.location }}"
- name: ARTIFACT_REGISTRY_REPOSITORY
value: "{{ match_resource.resource.name }}"
- name: ARTIFACT_REGISTRY_LOCATIONS
value: "{{ match_resource.resource.location }}"
- name: ARTIFACT_REGISTRY_REPOSITORIES
value: "{{ match_resource.resource.name }}"
- name: STALE_IMAGE_THRESHOLD_DAYS
value: "{{ custom.stale_image_threshold_days | default('90') }}"
- name: UNTAGGED_IMAGE_THRESHOLD_DAYS
value: "{{ custom.untagged_image_threshold_days | default('30') }}"
- name: STORAGE_UTILIZATION_THRESHOLD_GB
value: "{{ custom.storage_utilization_threshold_gb | default('50') }}"
- name: MIN_TAGS_TO_KEEP
value: "{{ custom.min_tags_to_keep | default('5') }}"
secretsProvided:
{% if wb_version %}
{% include "gcp-auth.yaml" ignore missing %}
{% else %}
- name: gcp_credentials
workspaceKey: AUTH DETAILS NOT FOUND
{% endif %}
52 changes: 52 additions & 0 deletions codebundles/gcp-artifact-registry-governance/.test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# GCP Artifact Registry Governance Test Infrastructure

Terraform provisions three Artifact Registry repositories in a GCP project:

| Repository | Purpose |
|------------|---------|
| `*-healthy-*` | Docker repo with cleanup policies (healthy scenario) |
| `*-nopolicy-*` | Docker repo without cleanup policies (missing policy scenario) |
| `*-maven-*` | Non-Docker repo for discovery-only validation |

## Prerequisites

- Terraform >= 1.5
- GCP project with billing enabled
- Credentials with permissions to enable APIs and create Artifact Registry repositories

## Setup

1. Copy credentials into `tf.secret` (not committed):

```bash
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json
export TF_VAR_project_id=my-gcp-project
```

2. Update `terraform/terraform.tfvars` with your project ID.

3. Build infrastructure:

```bash
task build-infra
```

4. Validate bundle structure:

```bash
task validate-structure
```

5. Cleanup:

```bash
task clean
```

## Test Scenarios

- **healthy_repository_with_cleanup_policy**: Use the healthy repository; expect zero cleanup-policy issues.
- **missing_cleanup_policy_stale_images**: Use the no-policy repository; expect cleanup policy and recommendation issues.
- **legacy_gcr_only**: Requires a project using legacy GCR without Artifact Registry (manual/project-specific).

Populate stale tags in test repos with `gcloud artifacts docker tags add` after pushing sample images if integration testing image age logic.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
version: "3"

tasks:
default:
desc: "Run complete test suite"
cmds:
- task: validate-structure

validate-structure:
desc: "Validate required CodeBundle files exist"
cmds:
- ./validate-all-tests.sh

clean:
desc: "Run cleanup tasks"
cmds:
- task: check-and-cleanup-terraform

build-infra:
desc: "Build test infrastructure"
cmds:
- task: build-terraform-infra

build-terraform-infra:
desc: "Run terraform apply"
dir: terraform
cmds:
- |
if [ -f "../tf.secret" ]; then
source ../tf.secret
fi
terraform init
terraform apply -auto-approve

check-and-cleanup-terraform:
desc: "Destroy Terraform resources when state exists"
dir: terraform
cmds:
- |
if [ -f "terraform.tfstate" ]; then
if [ -f "../tf.secret" ]; then
source ../tf.secret
fi
terraform destroy -auto-approve
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
required_version = ">= 1.5.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}

resource "google_project_service" "artifact_registry" {
project = var.project_id
service = "artifactregistry.googleapis.com"
disable_on_destroy = false
}

resource "google_project_service" "container_registry" {
project = var.project_id
service = "containerregistry.googleapis.com"
disable_on_destroy = false
}

# Healthy repository with cleanup policies configured
resource "google_artifact_registry_repository" "healthy" {
depends_on = [google_project_service.artifact_registry]
location = var.region
repository_id = "${var.codebundle}-healthy-${random_string.suffix.result}"
description = "Healthy Docker repo with cleanup policies"
format = "DOCKER"
cleanup_policy_dry_run = false

cleanup_policies {
id = "delete-untagged"
action = "DELETE"
condition {
tag_state = "UNTAGGED"
older_than = "2592000s"
}
}

cleanup_policies {
id = "keep-recent"
action = "KEEP"
most_recent_versions {
keep_count = 5
}
}
}

# Unhealthy repository without cleanup policies (intentional test gap)
resource "google_artifact_registry_repository" "missing_policy" {
depends_on = [google_project_service.artifact_registry]
location = var.region
repository_id = "${var.codebundle}-nopolicy-${random_string.suffix.result}"
description = "Docker repo missing cleanup policies for governance tests"
format = "DOCKER"
}

# Maven repository for discovery-only coverage
resource "google_artifact_registry_repository" "maven" {
depends_on = [google_project_service.artifact_registry]
location = var.region
repository_id = "${var.codebundle}-maven-${random_string.suffix.result}"
description = "Non-Docker repo for reduced task-set validation"
format = "MAVEN"
}

output "healthy_repository" {
value = google_artifact_registry_repository.healthy.repository_id
}

output "missing_policy_repository" {
value = google_artifact_registry_repository.missing_policy.repository_id
}

output "maven_repository" {
value = google_artifact_registry_repository.maven.repository_id
}

output "project_id" {
value = var.project_id
}

output "region" {
value = var.region
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "healthy_repository_name" {
description = "Healthy Docker repository with cleanup policies"
value = google_artifact_registry_repository.healthy.repository_id
}

output "missing_policy_repository_name" {
description = "Docker repository intentionally missing cleanup policies"
value = google_artifact_registry_repository.missing_policy.repository_id
}

output "maven_repository_name" {
description = "Non-Docker repository for discovery-only scenarios"
value = google_artifact_registry_repository.maven.repository_id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
null = {
source = "hashicorp/null"
version = "~> 3.2"
}
}
}

provider "google" {
project = var.project_id
region = var.region
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
project_id = "REPLACE_WITH_GCP_PROJECT_ID"
region = "us-central1"
Loading
Loading