| author | |
| committer | |
| log | e74c668d786ffc14f40a9540cd3aede02ba5d62e |
| tree | f238bf92a017fbdee1d679f7649682303a2f81ab |
| parent | 4931d73af3e68d8706d7568b9af37c8397caffdb |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 85 insertions(+), 2 deletions(-)
.github/scripts/relay-state-output.test.cjs created+76| ... | @@ -0,0 +1,76 @@ | ||
| 1 | const assert = require("node:assert/strict"); | ||
| 2 | const { spawnSync } = require("node:child_process"); | ||
| 3 | const { mkdtempSync, readFileSync, rmSync, existsSync } = require("node:fs"); | ||
| 4 | const { tmpdir } = require("node:os"); | ||
| 5 | const { join } = require("node:path"); | ||
| 6 | const { test } = require("node:test"); | ||
| 7 | |||
| 8 | const workflow = readFileSync(join(__dirname, "../workflows/release.yml"), "utf8"); | ||
| 9 | const step = workflow.match( | ||
| 10 | /- name: Read production relay tracing config\n[\s\S]*? run: \|\n((?: .*\n|\n)+)/, | ||
| 11 | ); | ||
| 12 | assert.ok(step, "Could not find the relay state workflow step"); | ||
| 13 | const script = step[1].replace(/^ /gm, ""); | ||
| 14 | const config = { | ||
| 15 | clientTracingUrl: "https://example.invalid/traces", | ||
| 16 | clientTracingDataset: "fixture-dataset", | ||
| 17 | clientTracingToken: { __redacted__: "fixture-token" }, | ||
| 18 | }; | ||
| 19 | const json = JSON.stringify(config, null, 2); | ||
| 20 | |||
| 21 | function runStep(stdout, exitCode = 0) { | ||
| 22 | const runnerTemp = mkdtempSync(join(tmpdir(), "t3-relay-state-test-")); | ||
| 23 | try { | ||
| 24 | const result = spawnSync( | ||
| 25 | "bash", | ||
| 26 | ["-c", 'npx() { printf "%s\\n" "$FIXTURE_STDOUT"; return "$FIXTURE_EXIT"; }\n' + script], | ||
| 27 | { | ||
| 28 | encoding: "utf8", | ||
| 29 | env: { | ||
| 30 | PATH: process.env.PATH, | ||
| 31 | RUNNER_TEMP: runnerTemp, | ||
| 32 | FIXTURE_STDOUT: stdout, | ||
| 33 | FIXTURE_EXIT: String(exitCode), | ||
| 34 | }, | ||
| 35 | }, | ||
| 36 | ); | ||
| 37 | assert.ifError(result.error); | ||
| 38 | const envPath = join(runnerTemp, "relay-client-tracing.env"); | ||
| 39 | return { | ||
| 40 | ...result, | ||
| 41 | envFile: existsSync(envPath) ? readFileSync(envPath, "utf8") : undefined, | ||
| 42 | }; | ||
| 43 | } finally { | ||
| 44 | rmSync(runnerTemp, { recursive: true, force: true }); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | for (const prefix of [ | ||
| 49 | "", | ||
| 50 | "• Refreshing Cloudflare State Store credentials\n✓ Refreshing Cloudflare State Store credentials\n", | ||
| 51 | ]) { | ||
| 52 | test(`extracts tracing config ${prefix ? "after progress output" : "from plain JSON"}`, () => { | ||
| 53 | const result = runStep(prefix + json); | ||
| 54 | assert.equal(result.status, 0, result.stderr); | ||
| 55 | assert.equal(result.stdout, "::add-mask::fixture-token\n"); | ||
| 56 | assert.equal( | ||
| 57 | result.envFile, | ||
| 58 | "T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=https://example.invalid/traces\n" + | ||
| 59 | "T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=fixture-dataset\n" + | ||
| 60 | "T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=fixture-token\n", | ||
| 61 | ); | ||
| 62 | }); | ||
| 63 | } | ||
| 64 | |||
| 65 | for (const [name, stdout, exitCode] of [ | ||
| 66 | ["failed CLI even with valid JSON", json, 1], | ||
| 67 | ["missing JSON", "Refreshing credentials...", 0], | ||
| 68 | ["malformed JSON", "{not JSON", 0], | ||
| 69 | ["missing token", JSON.stringify({ ...config, clientTracingToken: null }, null, 2), 0], | ||
| 70 | ]) { | ||
| 71 | test(`rejects ${name} without writing config`, () => { | ||
| 72 | const result = runStep(stdout, exitCode); | ||
| 73 | assert.notEqual(result.status, 0); | ||
| 74 | assert.equal(result.envFile, undefined); | ||
| 75 | }); | ||
| 76 | } | ||
.github/workflows/release.yml+3-2| ... | @@ -314,14 +314,15 @@ jobs: | ... | @@ -314,14 +314,15 @@ jobs: |
| 314 | # The deployed stack's outputs, read from Alchemy's state store without | 314 | # The deployed stack's outputs, read from Alchemy's state store without |
| 315 | # planning or applying. Select the backend explicitly to avoid loading | 315 | # planning or applying. Select the backend explicitly to avoid loading |
| 316 | # the stack's unrelated provider credentials; suppress informational logs | 316 | # the stack's unrelated provider credentials; suppress informational logs |
| 317 | # so stdout stays valid JSON. Redacted values are persisted as | 317 | # and strip the credential-refresh progress prefix before parsing JSON. |
| 318 | # Redacted values are persisted as | ||
| 318 | # {"__redacted__": "<value>"}; the token is masked before it is written. | 319 | # {"__redacted__": "<value>"}; the token is masked before it is written. |
| 319 | - name: Read production relay tracing config | 320 | - name: Read production relay tracing config |
| 320 | shell: bash | 321 | shell: bash |
| 321 | working-directory: infra/relay | 322 | working-directory: infra/relay |
| 322 | run: | | 323 | run: | |
| 323 | set -euo pipefail | 324 | set -euo pipefail |
| 324 | output="$(npx alchemy state read T3CodeRelay/prod/output --backend cloudflare --log-level error --no-input)" | 325 | output="$(npx alchemy state read T3CodeRelay/prod/output --backend cloudflare --log-level error --no-input | sed -n '/^{/,$p')" |
| 325 | field() { | 326 | field() { |
| 326 | jq -er --arg key "$1" '.[$key] | if type == "object" then .__redacted__ else . end | select(. != null and . != "")' <<<"$output" \ | 327 | jq -er --arg key "$1" '.[$key] | if type == "object" then .__redacted__ else . end | select(. != null and . != "")' <<<"$output" \ |
| 327 | || { echo "Relay stack output is missing $1" >&2; exit 1; } | 328 | || { echo "Relay stack output is missing $1" >&2; exit 1; } |
scripts/release-smoke.ts+6| ... | @@ -188,6 +188,12 @@ function assertMissing(path: string, message: string): void { | ... | @@ -188,6 +188,12 @@ function assertMissing(path: string, message: string): void { |
| 188 | const tempRoot = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-release-smoke-")); | 188 | const tempRoot = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-release-smoke-")); |
| 189 | 189 | ||
| 190 | try { | 190 | try { |
| 191 | NodeChildProcess.execFileSync( | ||
| 192 | process.execPath, | ||
| 193 | ["--test", NodePath.resolve(repoRoot, ".github/scripts/relay-state-output.test.cjs")], | ||
| 194 | { stdio: "inherit" }, | ||
| 195 | ); | ||
| 196 | |||
| 191 | copyWorkspaceManifestFixture(tempRoot); | 197 | copyWorkspaceManifestFixture(tempRoot); |
| 192 | 198 | ||
| 193 | NodeChildProcess.execFileSync( | 199 | NodeChildProcess.execFileSync( |