From 23ebd73fa6580140c8ab2e968bfce0aa5d175ac2 Mon Sep 17 00:00:00 2001 From: "H.E. Pennypacker" <115990865+pennypacker-he@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:44:23 +0000 Subject: [PATCH] [minor] Write exact GCP smoke namespaces Encode complete GitHub run IDs once for fresh and upgrade writers while retaining the dual cleanup reader and existing non-GCP naming. --- Makefile | 2 +- ci/cloud-smoke.sh | 31 ++-- ci/gcp-upgrade-smoke-contract.sh | 11 +- ci/gcp-upgrade-smoke.sh | 26 +-- docs/runtime-contracts.md | 34 ++-- .../contracttest/cloud_smoke_cleanup_test.go | 40 ++++- internal/gcpcleanup/cleanup.go | 3 +- tests/smoke/gcp/main.tf | 1 + tests/smoke/gcp/variables.tf | 11 ++ .../smoke/modules/context/context.tftest.hcl | 168 ++++++++++++++++++ tests/smoke/modules/context/main.tf | 30 +++- tests/smoke/modules/context/variables.tf | 11 ++ 12 files changed, 318 insertions(+), 50 deletions(-) create mode 100644 tests/smoke/modules/context/context.tftest.hcl diff --git a/Makefile b/Makefile index 0bb855b..f5a6e20 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ $(CLOUD_COMPOSE_CI_BIN): $(GO_MODULE_FILES) $(GO_SOURCES) @mkdir -p "$(dir $(CLOUD_COMPOSE_CI_BIN))" go build -trimpath -o "$(CLOUD_COMPOSE_CI_BIN)" ./cmd/cloud-compose-ci -gcp-upgrade-smoke-contract: +gcp-upgrade-smoke-contract: cloud-compose-ci bash ci/gcp-upgrade-smoke-contract.sh artifact-install-contract: diff --git a/ci/cloud-smoke.sh b/ci/cloud-smoke.sh index 99c5c8c..87eee12 100755 --- a/ci/cloud-smoke.sh +++ b/ci/cloud-smoke.sh @@ -554,7 +554,8 @@ target_workdir() { } target_var_args() { - local root="$1" key_path="$2" target="$3" public_key provider template + local root="$1" key_path="$2" target="$3" run_id="$4" run_namespace="$5" + local public_key provider template local source_ref source_sha256 source_cache_key checksum_dir checksum_file archive_tmp provider="$(target_provider "$target")" @@ -603,8 +604,11 @@ target_var_args() { fi printf '%s\0%s\0' "-var" "cloud_compose_source_sha256=${source_sha256}" fi - if grep -q 'variable "smoke_run_id"' "$root/variables.tf" && [[ -n "$(smoke_run_id)" ]]; then - printf '%s\0%s\0' "-var" "smoke_run_id=$(smoke_run_id)" + if grep -q 'variable "smoke_run_id"' "$root/variables.tf" && [[ -n "$run_id" ]]; then + printf '%s\0%s\0' "-var" "smoke_run_id=${run_id}" + fi + if grep -q 'variable "smoke_run_namespace"' "$root/variables.tf" && [[ -n "$run_namespace" ]]; then + printf '%s\0%s\0' "-var" "smoke_run_namespace=${run_namespace}" fi if grep -q 'variable "gcp_project_id"' "$root/variables.tf"; then printf '%s\0%s\0' "-var" "gcp_project_id=${GCLOUD_PROJECT:-}" @@ -846,7 +850,7 @@ run_target() ( set -euo pipefail local target="$1" - local root workdir key_path home_dir output_json public_key run_id + local root workdir key_path home_dir output_json public_key run_id run_namespace local -a auto_args var_args root="$(target_root "$target")" @@ -861,10 +865,10 @@ run_target() ( ensure_key "$key_path" run_id="$(smoke_run_id)" - # Validate GCP cleanup ownership before Terraform can create resources. The - # reader phase intentionally keeps writing the legacy resource namespace. - gcp_run_namespace "$target" "$run_id" >/dev/null - mapfile -d '' -t var_args < <(target_var_args "$root" "$key_path" "$target") + # Compute outside process substitution so an invalid hosted run ID aborts + # before Terraform can create resources under an undiscoverable namespace. + run_namespace="$(gcp_run_namespace "$target" "$run_id")" + mapfile -d '' -t var_args < <(target_var_args "$root" "$key_path" "$target" "$run_id" "$run_namespace") auto_args=() if [[ -n "${GITHUB_ACTIONS:-}" || "${CLOUD_COMPOSE_SMOKE_AUTO_APPROVE:-}" == "true" ]]; then @@ -951,7 +955,7 @@ destroy_target() ( set -euo pipefail local target="$1" - local root workdir key_path destroy_status destroy_timeout cleanup_status + local root workdir key_path destroy_status destroy_timeout cleanup_status run_id run_namespace local -a auto_args var_args root="$(target_root "$target")" @@ -959,7 +963,12 @@ destroy_target() ( workdir="$(target_workdir "$target")" key_path="$workdir/id_ed25519" - mapfile -d '' -t var_args < <(target_var_args "$root" "$key_path" "$target") + run_id="$(smoke_run_id)" + run_namespace="" + if [[ -n "$run_id" ]]; then + run_namespace="$(gcp_run_namespace "$target" "$run_id")" + fi + mapfile -d '' -t var_args < <(target_var_args "$root" "$key_path" "$target" "$run_id" "$run_namespace") auto_args=() if [[ -n "${GITHUB_ACTIONS:-}" || "${CLOUD_COMPOSE_SMOKE_AUTO_APPROVE:-}" == "true" ]]; then @@ -986,7 +995,7 @@ destroy_target() ( fi cleanup_status=0 - provider_tag_cleanup "$target" "$(smoke_run_id)" || cleanup_status=$? + provider_tag_cleanup "$target" "$run_id" || cleanup_status=$? if [[ "$destroy_status" -ne 0 && "$cleanup_status" -eq 0 ]]; then echo "Provider tag cleanup completed for ${target} after Terraform destroy failed" diff --git a/ci/gcp-upgrade-smoke-contract.sh b/ci/gcp-upgrade-smoke-contract.sh index 7bdfca2..ccc4832 100755 --- a/ci/gcp-upgrade-smoke-contract.sh +++ b/ci/gcp-upgrade-smoke-contract.sh @@ -50,6 +50,15 @@ grep -Fq 'CLOUD_COMPOSE_SMOKE_RUN_ID must match GITHUB_RUN_ID in GitHub Actions' fail "hosted cleanup ownership is not bound to the actual GitHub run id" grep -Fq 'CLOUD_COMPOSE_SMOKE_RUN_ID must be set explicitly outside GitHub Actions' "$script" || fail "manual upgrade runs can accidentally reuse an implicit cleanup scope" +grep -Fq '"$runner" gcp namespace --run-id "$run_id"' "$script" || + fail "upgrade runner does not use the shared exact run-namespace codec" +grep -Fq 'name="cc-g-wp-${run_namespace}-up"' "$script" || + fail "upgrade resource names do not use the exact run namespace" +grep -Fq 'cloud-compose-gcp-upgrade-${run_namespace}' "$script" || + fail "upgrade working directories do not use the exact run namespace" +if grep -Fq 'sanitize_run_fragment' "$script"; then + fail "upgrade runner still truncates run IDs to the legacy namespace" +fi grep -Fq '/mnt/disks/data/.cloud-compose-upgrade-sentinel' "$script" || fail "upgrade runner omits the persistent data-disk sentinel" grep -Fq '/mnt/disks/volumes/.cloud-compose-upgrade-sentinel' "$script" || @@ -214,7 +223,7 @@ cleanup_log="$tmp/cleanup.log" set +e GITHUB_ACTIONS='' \ GITHUB_RUN_ID='' \ -CLOUD_COMPOSE_SMOKE_RUN_ID=contract-run \ +CLOUD_COMPOSE_SMOKE_RUN_ID=123456789 \ CLOUD_COMPOSE_UPGRADE_CURRENT_REF=HEAD \ GCLOUD_PROJECT=contract-project \ GCLOUD_NETWORK_PROJECT_ID=contract-project \ diff --git a/ci/gcp-upgrade-smoke.sh b/ci/gcp-upgrade-smoke.sh index 80d7896..72c405a 100755 --- a/ci/gcp-upgrade-smoke.sh +++ b/ci/gcp-upgrade-smoke.sh @@ -73,15 +73,15 @@ fail() { return 1 } -sanitize_run_fragment() { - local value="$1" +exact_run_namespace() { + local run_id="$1" runner - value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | cut -c1-8)" - if [[ -z "$value" ]]; then - fail "the smoke run id did not contain a usable name fragment" + runner="${CLOUD_COMPOSE_CI_BIN:-$repo_root/.bin/cloud-compose-ci}" + if [[ ! -x "$runner" ]]; then + fail "compiled CI runner is missing at ${runner}; run 'make cloud-compose-ci' first" return 1 fi - printf '%s\n' "$value" + "$runner" gcp namespace --run-id "$run_id" } upgrade_run_id() { @@ -832,7 +832,7 @@ run_upgrade() ( require_env GCLOUD_POWER_START_ROLE require_env GCLOUD_POWER_SUSPEND_ROLE - local requested_base current_ref current_sha base_sha run_id run_fragment name + local requested_base current_ref current_sha base_sha run_id run_namespace name local work_root old_source new_source state_path old_data new_data key_path public_key local runner_ipv4 runner_cidr region zone plan_file plan_json nonce local old_root new_root old_output new_output old_home new_home @@ -850,11 +850,11 @@ run_upgrade() ( [[ "$current_sha" =~ ^[0-9a-f]{40}$ ]] || fail "current upgrade ref did not resolve to a full commit" run_id="$(upgrade_run_id)" - run_fragment="$(sanitize_run_fragment "$run_id")" - name="cc-g-wp-${run_fragment}-up" + run_namespace="$(exact_run_namespace "$run_id")" + name="cc-g-wp-${run_namespace}-up" export CLOUD_COMPOSE_SMOKE_RUN_ID="$run_id" - work_root="${CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR:-${RUNNER_TEMP:-/tmp}/cloud-compose-gcp-upgrade-${run_fragment}}" + work_root="${CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR:-${RUNNER_TEMP:-/tmp}/cloud-compose-gcp-upgrade-${run_namespace}}" [[ "$work_root" == /* ]] || fail "CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR must be an absolute path" old_source="$work_root/source-0.10.2" new_source="$work_root/source-current" @@ -1008,13 +1008,13 @@ destroy_upgrade() { require_env GCLOUD_NETWORK_NAME require_env GCLOUD_SUBNETWORK_NAME - local current_sha run_id run_fragment work_root old_source new_source state_path old_data new_data + local current_sha run_id run_namespace work_root old_source new_source state_path old_data new_data current_sha="$(git -C "$repo_root" rev-parse --verify "${CLOUD_COMPOSE_UPGRADE_CURRENT_REF:-HEAD}^{commit}")" run_id="$(upgrade_run_id)" - run_fragment="$(sanitize_run_fragment "$run_id")" + run_namespace="$(exact_run_namespace "$run_id")" export CLOUD_COMPOSE_SMOKE_RUN_ID="$run_id" - work_root="${CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR:-${RUNNER_TEMP:-/tmp}/cloud-compose-gcp-upgrade-${run_fragment}}" + work_root="${CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR:-${RUNNER_TEMP:-/tmp}/cloud-compose-gcp-upgrade-${run_namespace}}" [[ "$work_root" == /* ]] || fail "CLOUD_COMPOSE_GCP_UPGRADE_WORKDIR must be an absolute path" old_source="$work_root/source-0.10.2" new_source="$work_root/source-current" diff --git a/docs/runtime-contracts.md b/docs/runtime-contracts.md index edc7200..801dc89 100644 --- a/docs/runtime-contracts.md +++ b/docs/runtime-contracts.md @@ -756,20 +756,26 @@ workflow, including cases where cancellation prevented the in-job destroy from finishing. Never allow pull-request branches in a cleanup environment's deployment policy. -GCP run namespaces require a staged compatibility change because the privileged -fallback always executes the default branch. The compiled runner recognizes the -legacy first-eight-character namespace and the reserved exact namespace that -encodes a canonical numeric run ID as nine fixed-width base36 characters. That -width preserves both separators and the random suffix for every supported GCP -template within the 21-character resource-name limit. In this reader phase the -fresh GCP driver calls the pure `cloud-compose-ci gcp namespace` command before -Terraform argument construction to require a non-empty canonical run ID no -larger than 44 bits, while deliberately continuing to write legacy names. Manual GCP -smoke applies must therefore set `CLOUD_COMPOSE_SMOKE_RUN_ID`; non-GCP smoke -naming and invocation remain unchanged. Merge the -dual-reader before changing the smoke Terraform writer. Retain the legacy reader -until all branches and resources created by the old writer have expired; never -introduce a new writer that the trusted fallback cannot discover. +GCP smoke writers encode the complete canonical numeric GitHub Actions run ID as +a fixed-width, nine-character lowercase base36 namespace. The fresh and upgrade +drivers obtain that value from the compiled `cloud-compose-ci gcp namespace` +command before Terraform can create resources. A manual fresh GCP smoke apply +must set `CLOUD_COMPOSE_SMOKE_RUN_ID`; an empty, malformed, noncanonical, or +greater-than-44-bit value fails before apply. Non-GCP smoke invocation remains +unchanged. The namespace width preserves both +separators and at least one random suffix character for every supported GCP +template within the 21-character resource-name limit. The original decimal run +ID remains the cleanup input and the source of compatibility tags; DigitalOcean +and Linode naming and invocation are unchanged. Manual GCP smoke applies must +set `CLOUD_COMPOSE_SMOKE_RUN_ID`; the smoke context validates that the supplied +namespace decodes to that same canonical, at-most-44-bit run ID. + +The privileged fallback always executes the default branch, so its compiled +runner remains a dual reader: it queries both the legacy first-eight-character +namespace and the exact namespace for a run. Retain the legacy reader until all +branches and resources created by the old writer have expired. A malformed, +noncanonical, or greater-than-44-bit run ID fails before apply rather than +creating a namespace that the trusted fallback cannot discover. Use separate provider identities for smoke and fallback cleanup: diff --git a/internal/contracttest/cloud_smoke_cleanup_test.go b/internal/contracttest/cloud_smoke_cleanup_test.go index 0ce25e2..6d9f04d 100644 --- a/internal/contracttest/cloud_smoke_cleanup_test.go +++ b/internal/contracttest/cloud_smoke_cleanup_test.go @@ -101,7 +101,7 @@ func TestCloudSmokeGCPApplyRequiresCanonicalRunID(t *testing.T) { driverPath := filepath.Join(root, "ci/cloud-smoke.sh") driver := readRepositoryFile(t, root, "ci/cloud-smoke.sh") - validation := `gcp_run_namespace "$target" "$run_id" >/dev/null` + validation := `run_namespace="$(gcp_run_namespace "$target" "$run_id")"` validationIndex := strings.Index(driver, validation) argumentsIndex := strings.Index(driver, `mapfile -d '' -t var_args < <(target_var_args`) applyIndex := strings.Index(driver, `terraform -chdir="$root" apply`) @@ -138,6 +138,44 @@ func TestCloudSmokeGCPApplyRequiresCanonicalRunID(t *testing.T) { } } +func TestCloudSmokeGCPWriterUsesExactRunNamespace(t *testing.T) { + t.Parallel() + root := repositoryRoot(t) + driver := readRepositoryFile(t, root, "ci/cloud-smoke.sh") + gcpVariables := readRepositoryFile(t, root, "tests/smoke/gcp/variables.tf") + gcpFixture := readRepositoryFile(t, root, "tests/smoke/gcp/main.tf") + contextVariables := readRepositoryFile(t, root, "tests/smoke/modules/context/variables.tf") + contextFixture := readRepositoryFile(t, root, "tests/smoke/modules/context/main.tf") + + for label, marker := range map[string]string{ + "compiled namespace command": `"$runner" gcp namespace --run-id "$run_id"`, + "captured raw run ID": `run_id="$(smoke_run_id)"`, + "captured exact namespace": `run_namespace="$(gcp_run_namespace "$target" "$run_id")"`, + "raw Terraform input": `"smoke_run_id=${run_id}"`, + "exact Terraform input": `"smoke_run_namespace=${run_namespace}"`, + "raw cleanup ownership": `provider_tag_cleanup "$target" "$run_id"`, + } { + requireContains(t, driver, marker, label) + } + + namespaceIndex := strings.Index(driver, `run_namespace="$(gcp_run_namespace "$target" "$run_id")"`) + argumentsIndex := strings.Index(driver, `mapfile -d '' -t var_args < <(target_var_args`) + if namespaceIndex < 0 || argumentsIndex < 0 || namespaceIndex >= argumentsIndex { + t.Fatal("fresh GCP smoke does not validate its exact namespace before Terraform argument process substitution") + } + + for label, text := range map[string]string{ + "GCP root variables": gcpVariables, + "context variables": contextVariables, + "GCP context plumbing": gcpFixture, + "context naming decision": contextFixture, + } { + requireContains(t, text, "smoke_run_namespace", label) + } + requireContains(t, contextFixture, `local.cloud_provider == "gcp" ? var.smoke_run_namespace : ""`, "GCP-only exact namespace selection") + requireContains(t, contextFixture, `local.smoke_run_id != "" ? "gha-run-${local.smoke_run_id}" : ""`, "legacy run-id cleanup tag") +} + func TestCloudSmokeGCPWrapperFailsClosedWithoutRunID(t *testing.T) { t.Parallel() root := repositoryRoot(t) diff --git a/internal/gcpcleanup/cleanup.go b/internal/gcpcleanup/cleanup.go index 431f388..5f9d987 100644 --- a/internal/gcpcleanup/cleanup.go +++ b/internal/gcpcleanup/cleanup.go @@ -104,7 +104,8 @@ type failureList struct { errors []error } -// NameFilter returns the legacy anchored resource-name filter shared with the current smoke Terraform fixture. +// NameFilter returns the legacy anchored resource-name filter retained for +// compatibility with smoke resources created before exact namespaces. func NameFilter(target, runID string) (string, error) { prefix, err := targetNamePrefix(target) if err != nil { diff --git a/tests/smoke/gcp/main.tf b/tests/smoke/gcp/main.tf index f547f82..afe57a1 100644 --- a/tests/smoke/gcp/main.tf +++ b/tests/smoke/gcp/main.tf @@ -27,6 +27,7 @@ module "context" { ssh_public_key = var.ssh_public_key operator_ssh_public_keys = var.operator_ssh_public_keys smoke_run_id = var.smoke_run_id + smoke_run_namespace = var.smoke_run_namespace docker_compose_branch = var.docker_compose_branch ingress_port = var.ingress_port } diff --git a/tests/smoke/gcp/variables.tf b/tests/smoke/gcp/variables.tf index ccb7c64..0bea916 100644 --- a/tests/smoke/gcp/variables.tf +++ b/tests/smoke/gcp/variables.tf @@ -75,6 +75,17 @@ variable "smoke_run_id" { description = "Optional GitHub Actions run id used to tag and name disposable smoke-test resources." } +variable "smoke_run_namespace" { + type = string + default = "" + description = "Optional output of cloud-compose-ci gcp namespace used in GCP disposable resource names." + + validation { + condition = var.smoke_run_namespace == "" || can(regex("^[0-9a-z]{9}$", var.smoke_run_namespace)) + error_message = "smoke_run_namespace must be empty or exactly nine lowercase base36 characters." + } +} + variable "docker_compose_branch" { type = string default = "main" diff --git a/tests/smoke/modules/context/context.tftest.hcl b/tests/smoke/modules/context/context.tftest.hcl new file mode 100644 index 0000000..6656d1a --- /dev/null +++ b/tests/smoke/modules/context/context.tftest.hcl @@ -0,0 +1,168 @@ +mock_provider "random" { + mock_resource "random_id" { + defaults = { + hex = "abcdef" + } + } +} + +variables { + cloud_provider = "gcp" + template = "wp" + ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFakeFakeFakeFakeFakeFakeFakeFakeFakeFakeFakeFake cloud-compose-test" + smoke_run_id = "123456789" + smoke_run_namespace = "00021i3v9" +} + +run "legacy_gcp_writer" { + command = apply + + variables { + smoke_run_namespace = "" + } + + assert { + condition = output.name == "cc-g-wp-12345678-abcd" + error_message = "The compatibility writer must retain the legacy first-eight-character GCP name when no exact namespace is supplied." + } +} + +run "exact_archivesspace_writer" { + command = apply + + variables { + template = "archivesspace" + } + + assert { + condition = output.name == "cc-g-as-00021i3v9-abc" + error_message = "ArchivesSpace did not retain the exact namespace and a random suffix within the GCP name limit." + } +} + +run "exact_ojs_writer" { + command = apply + + variables { + template = "ojs" + } + + assert { + condition = output.name == "cc-g-ojs-00021i3v9-ab" + error_message = "OJS did not retain the exact namespace and a random suffix within the GCP name limit." + } +} + +run "exact_isle_writer" { + command = apply + + variables { + template = "isle" + } + + assert { + condition = output.name == "cc-g-isle-00021i3v9-a" + error_message = "ISLE did not retain the exact namespace and one random nibble within the GCP name limit." + } +} + +run "exact_drupal_writer" { + command = apply + + variables { + template = "drupal" + } + + assert { + condition = output.name == "cc-g-dr-00021i3v9-abc" + error_message = "Drupal did not retain the exact namespace and a random suffix within the GCP name limit." + } +} + +run "exact_wordpress_writer" { + command = apply + + assert { + condition = output.name == "cc-g-wp-00021i3v9-abc" + error_message = "WordPress did not retain the exact namespace and a random suffix within the GCP name limit." + } + + assert { + condition = contains(output.tags, "gha-run-12345678") && !contains(output.tags, "gha-run-00021i3v9") + error_message = "GCP cleanup tags must retain the legacy run-id contract while readers support both naming generations." + } +} + +run "exact_omeka_s_writer" { + command = apply + + variables { + template = "omeka-s" + } + + assert { + condition = output.name == "cc-g-os-00021i3v9-abc" + error_message = "Omeka S did not retain the exact namespace and a random suffix within the GCP name limit." + } +} + +run "exact_omeka_classic_writer" { + command = apply + + variables { + template = "omeka-classic" + } + + assert { + condition = output.name == "cc-g-oc-00021i3v9-abc" + error_message = "Omeka Classic did not retain the exact namespace and a random suffix within the GCP name limit." + } +} + +run "digitalocean_ignores_gcp_namespace" { + command = apply + + variables { + cloud_provider = "digitalocean" + template = "wp" + } + + assert { + condition = output.name == "cc-do-wp-123456789-abcdef" + error_message = "The GCP-only exact namespace changed DigitalOcean naming." + } +} + +run "linode_ignores_gcp_namespace" { + command = apply + + variables { + cloud_provider = "linode" + template = "wp" + } + + assert { + condition = output.name == "cc-ln-wp-123456789-abcdef" + error_message = "The GCP-only exact namespace changed Linode naming." + } +} + +run "invalid_exact_namespace" { + command = plan + + variables { + smoke_run_namespace = "12345678Z" + } + + expect_failures = [var.smoke_run_namespace] +} + +run "mismatched_exact_namespace" { + command = plan + + variables { + smoke_run_namespace = "00021i3v8" + } + + expect_failures = [random_id.suffix] +} diff --git a/tests/smoke/modules/context/main.tf b/tests/smoke/modules/context/main.tf index b0bfbb9..b0646ee 100644 --- a/tests/smoke/modules/context/main.tf +++ b/tests/smoke/modules/context/main.tf @@ -11,6 +11,18 @@ terraform { resource "random_id" "suffix" { byte_length = local.cloud_provider == "gcp" ? 2 : 3 + + lifecycle { + precondition { + condition = local.smoke_run_namespace == "" || try( + tostring(parseint(var.smoke_run_id, 10)) == var.smoke_run_id && + parseint(var.smoke_run_id, 10) <= 17592186044415 && + parseint(local.smoke_run_namespace, 36) == parseint(var.smoke_run_id, 10), + false + ) + error_message = "smoke_run_namespace must be the cloud-compose-ci encoding of the canonical, at-most-44-bit smoke_run_id." + } + } } locals { @@ -32,14 +44,16 @@ locals { "omeka-classic" = "oc" } - smoke_run_id = substr(replace(lower(var.smoke_run_id), "/[^a-z0-9-]/", "-"), 0, local.cloud_provider == "gcp" ? 8 : 16) - name_limit = local.cloud_provider == "gcp" ? 21 : 46 - target = "${local.cloud_provider}-${local.template}" - name_prefix = "cc-${local.provider_prefixes[local.cloud_provider]}-${local.template_slugs[local.template]}" - name = substr(join("-", compact([local.name_prefix, local.smoke_run_id, random_id.suffix.hex])), 0, local.name_limit) - run_tag = local.smoke_run_id != "" ? "gha-run-${local.smoke_run_id}" : "" - tags = distinct(concat(var.tags, ["cloud-compose-smoke", local.target], local.run_tag != "" ? [local.run_tag] : [])) - ssh_keys = distinct(concat([var.ssh_public_key], var.operator_ssh_public_keys)) + smoke_run_id = substr(replace(lower(var.smoke_run_id), "/[^a-z0-9-]/", "-"), 0, local.cloud_provider == "gcp" ? 8 : 16) + smoke_run_namespace = local.cloud_provider == "gcp" ? var.smoke_run_namespace : "" + name_run_namespace = local.smoke_run_namespace != "" ? local.smoke_run_namespace : local.smoke_run_id + name_limit = local.cloud_provider == "gcp" ? 21 : 46 + target = "${local.cloud_provider}-${local.template}" + name_prefix = "cc-${local.provider_prefixes[local.cloud_provider]}-${local.template_slugs[local.template]}" + name = substr(join("-", compact([local.name_prefix, local.name_run_namespace, random_id.suffix.hex])), 0, local.name_limit) + run_tag = local.smoke_run_id != "" ? "gha-run-${local.smoke_run_id}" : "" + tags = distinct(concat(var.tags, ["cloud-compose-smoke", local.target], local.run_tag != "" ? [local.run_tag] : [])) + ssh_keys = distinct(concat([var.ssh_public_key], var.operator_ssh_public_keys)) runtime_base = { rootfs_archive_url = var.rootfs_archive_url rootfs_archive_sha256 = var.rootfs_archive_sha256 diff --git a/tests/smoke/modules/context/variables.tf b/tests/smoke/modules/context/variables.tf index a4710e0..09d1937 100644 --- a/tests/smoke/modules/context/variables.tf +++ b/tests/smoke/modules/context/variables.tf @@ -40,6 +40,17 @@ variable "smoke_run_id" { description = "Optional GitHub Actions run id used to tag and name disposable smoke-test resources." } +variable "smoke_run_namespace" { + type = string + default = "" + description = "Optional output of cloud-compose-ci gcp namespace used only in GCP disposable resource names." + + validation { + condition = var.smoke_run_namespace == "" || can(regex("^[0-9a-z]{9}$", var.smoke_run_namespace)) + error_message = "smoke_run_namespace must be empty or exactly nine lowercase base36 characters." + } +} + variable "docker_compose_branch" { type = string default = "main"