diff --git a/scripts/check-terraform-isolation.mjs b/scripts/check-terraform-isolation.mjs index d289f9a7..484b6a50 100644 --- a/scripts/check-terraform-isolation.mjs +++ b/scripts/check-terraform-isolation.mjs @@ -33,6 +33,13 @@ export function isTerraformPath(file) { return file.startsWith("terraform/"); } +// Markdown under terraform/ does not queue an HCP VCS run (workspace triggers +// are terraform/live/dev/** and terraform/live/modules/**), so it is not a +// Terraform change for the mixed-PR check. +export function isTerraformInfrastructurePath(file) { + return isTerraformPath(file) && !file.endsWith(".md"); +} + export function mayAccompanyTerraform(file) { if (isTerraformPath(file)) return true; if (file.endsWith(".md")) return true; @@ -47,7 +54,7 @@ export function mayAccompanyTerraform(file) { */ export function classifyChangedFiles(files) { const unique = [...new Set(files.map((file) => file.trim()).filter(Boolean))].sort(); - const terraform = unique.filter(isTerraformPath); + const terraform = unique.filter(isTerraformInfrastructurePath); const application = unique.filter((file) => !mayAccompanyTerraform(file)); return { terraform, diff --git a/scripts/check-terraform-isolation.test.mjs b/scripts/check-terraform-isolation.test.mjs index 7fdac71b..8d3fa662 100644 --- a/scripts/check-terraform-isolation.test.mjs +++ b/scripts/check-terraform-isolation.test.mjs @@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url"; import { OVERRIDE_LABEL, classifyChangedFiles, + isTerraformInfrastructurePath, mayAccompanyTerraform, } from "./check-terraform-isolation.mjs"; @@ -72,6 +73,18 @@ test("terraform-only and application-only changes are not mixed", () => { assert.equal(classifyChangedFiles([]).mixed, false); }); +test("terraform documentation does not mix with application or workflow changes", () => { + assert.equal(isTerraformInfrastructurePath("terraform/README.md"), false); + assert.equal(isTerraformInfrastructurePath("terraform/live/dev/main.tf"), true); + assert.equal( + classifyChangedFiles(["terraform/README.md", ".github/workflows/ci.yaml"]).mixed, + false, + ); + const docsOnly = runGate(["terraform/README.md", ".github/workflows/ci.yaml"]); + assert.equal(docsOnly.status, 0, docsOnly.stdout + docsOnly.stderr); + assert.match(docsOnly.stdout, /PASS/); +}); + test("terraform plus application is mixed and lists the offending files", () => { const result = classifyChangedFiles([ "terraform/live/dev/main.tf", @@ -115,11 +128,7 @@ test("removing the override fails a mixed change that was previously green", () const previouslyGreen = runGate(files, { TERRAFORM_ISOLATION_OVERRIDE: "true", }); - assert.equal( - previouslyGreen.status, - 0, - previouslyGreen.stdout + previouslyGreen.stderr, - ); + assert.equal(previouslyGreen.status, 0, previouslyGreen.stdout + previouslyGreen.stderr); assert.match(previouslyGreen.stdout, /WARNING/); // CI sets TERRAFORM_ISOLATION_OVERRIDE from contains(...labels), which is @@ -128,11 +137,7 @@ test("removing the override fails a mixed change that was previously green", () const afterLabelRemoved = runGate(files, { TERRAFORM_ISOLATION_OVERRIDE: "false", }); - assert.equal( - afterLabelRemoved.status, - 1, - afterLabelRemoved.stdout + afterLabelRemoved.stderr, - ); + assert.equal(afterLabelRemoved.status, 1, afterLabelRemoved.stdout + afterLabelRemoved.stderr); assert.match(afterLabelRemoved.stdout, /FAIL/); assert.match(afterLabelRemoved.stdout, /deploy\.yml/); }); diff --git a/terraform/README.md b/terraform/README.md index 8522c080..b1814740 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -226,7 +226,9 @@ the labels. Push-to-`dev` releases return behind the repository variable labels currently on the PR; the other CI jobs skip on those events so a label change does not re-run the suite. Removing the label fails a mixed PR that had previously passed with the override, so a stale green check - cannot merge. The label is the approval record. The override is temporary: + cannot merge. Markdown under `terraform/` does not count as a Terraform + change for this gate; it does not match the workspace trigger patterns. + The label is the approval record. The override is temporary: a follow-up PR after PR C removes the label path from the checker and workflow so the gate has no exception. - **Every Terraform merge produces a VCS run.** A human confirms or discards