From cca7e13dc5d432e03d807d804eb05b6114977509 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Wed, 16 Sep 2026 00:07:43 +0200 Subject: [PATCH] feat: a domain input for T Cloud Public scope (ENG-821) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The action forwarded account, region, project and stack-export one-to-one and had no `domain`, so an OTC simulation from CI could only reach `--domain` through `extra-args` — which this action's own comment reserves for flags added to the CLI after a release, not for a first-class scope value. And the input alone would not have been enough: nothing customer-facing said the value exists. The README documented account/region/project and no OTC at all, so a customer discovered the requirement by hitting a failed submission. The README now carries the input, the recovery-path table that explains why OTC needs it more often than AWS needs `account` (one path before the flag, against three for AWS region and three for AWS account — and only a value WRITTEN in the provider block reaches the plan, so `OS_DOMAIN_NAME` in the job environment carries nothing), and the one thing the tooling cannot check: the value is compared byte for byte against the connector's, case not folded and name not resolved to id, because a mismatch produces a run that succeeds, shadows nothing, and describes a world that does not exist. Not in scope: `allow-mock-domain` as an input. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- action.yml | 20 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe9f03c..d541173 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ below. | `account` | | AWS account the resources deploy to. | | `region` | | AWS region. | | `project` | | GCP project. | +| `domain` | | T Cloud Public domain: the 32-hex id or the account name. Must match your connector's spelling exactly — see below. | | `stack-export` | | `pulumi stack export` document, to recover the account/region a preview cannot carry. A scope hint — its resources are **not** simulated. | | `allow-mock-account` | `false` | Submit with no resolved account. Identities are then derived from a placeholder, so nothing matches your real inventory. Not for a gate. | @@ -133,8 +134,53 @@ quietly turn off the gate you added it for. A Terraform plan carries its provider configuration, so `account` and `region` are often recoverable from the plan alone. A **Pulumi preview carries none** — -if you simulate a preview, pass `account`/`region` (and `project` for GCP), or -the resources are identified against the wrong scope. +if you simulate a preview, pass `account`/`region` (and `project` for GCP, or +`domain` for T Cloud Public), or the resources are identified against the wrong +scope. + +The split is plan vs preview, not provider. What differs per provider is how +many ways a *plan* can state the value: + +| Value | Where it can come from, in order | +|---|---| +| AWS region | the plan's provider config, a per-resource hint, then `region` | +| AWS account | an `assume_role` role ARN in the provider config, `stack-export`, then `account` | +| GCP project | a constant `project` in the `google` provider block, then `project` | +| T Cloud Public domain | a constant `domain_id`/`domain_name` in the `opentelekomcloud` provider block, then `domain`. **That is all.** | + +And only a value **written in the provider block** reaches the plan. A provider +configured from the environment — `AWS_REGION`, `OS_DOMAIN_NAME`, both commonly +repo secrets in CI — carries nothing into `terraform show -json`. AWS has two +other paths to fall back on; OTC has none, so an OTC plan in CI needs its input +more often than an AWS plan needs `account`. + +### `domain` must match your connector, byte for byte + +A T Cloud Public connector's `domain` scope holds **whatever you typed when you +created it** — the 32-hex domain id *or* the account name +(`OTC00000000001000000000`). Linro never rewrites one form into the other, and +every resource's identity is derived from the spelling it holds. + +So a simulation passing the other spelling composes rows that shadow nothing: +every simulated resource lands *beside* its live twin instead of over it, the +diff shows everything as new, and **nothing errors**. The run succeeds and +describes a world that does not exist. + +Read the value off the connector — do not retype what you think you configured: + +```yaml + - uses: linro-io/simulation-github-action@v1 + with: + plan: infra/plan.json + server: ${{ vars.LINRO_SERVER }} + token: ${{ secrets.LINRO_TOKEN }} + # Exactly as the connector shows it. `OS_DOMAIN_NAME` in the job's + # environment configures the provider; it does NOT reach the plan. + domain: ${{ vars.LINRO_OTC_DOMAIN }} +``` + +The CLI compares the value literally: it does not fold case and it does not +resolve a name to an id, because doing either would mean calling OTC. ## Several sources, one simulation diff --git a/action.yml b/action.yml index ee5a5d3..f293671 100644 --- a/action.yml +++ b/action.yml @@ -88,6 +88,14 @@ inputs: # A Terraform plan carries its provider config, so account/region are often # recoverable from the plan itself. A Pulumi preview carries none, so these # are how a preview gets scoped at all. + # + # The split is plan vs preview, not provider — but how many ways a PLAN can + # state a value does differ. AWS region has three (plan-wide, per-resource, + # the flag) and AWS account has three (an assume_role ARN, stack-export, the + # flag); an OTC domain has exactly one before the flag, a constant + # domain_id/domain_name in the provider block. Both AWS and OTC lose the + # provider-block value when it comes from the environment, which in CI it + # usually does; AWS has somewhere else to look and OTC has not. account: description: AWS account id the resources deploy to. required: false @@ -100,6 +108,16 @@ inputs: description: GCP project id the resources deploy to. required: false default: "" + domain: + description: >- + T Cloud Public domain the resources deploy to: the 32-hex domain id or + the account name (e.g. OTC00000000001000000000). It must be spelt exactly + as your connector's domain scope is — the value is never rewritten from + one form to the other, so read it off the connector rather than retyping + it. An OTC provider block configured from OS_DOMAIN_NAME carries nothing + into the plan, which is why this is needed more often than `account` is. + required: false + default: "" stack-export: description: >- Path to a `pulumi stack export` document, used to recover the AWS account @@ -310,6 +328,7 @@ runs: INPUT_ACCOUNT: ${{ inputs.account }} INPUT_REGION: ${{ inputs.region }} INPUT_PROJECT: ${{ inputs.project }} + INPUT_DOMAIN: ${{ inputs.domain }} INPUT_STACK_EXPORT: ${{ inputs.stack-export }} INPUT_ALLOW_MOCK_ACCOUNT: ${{ inputs.allow-mock-account }} INPUT_LABEL: ${{ inputs.label }} @@ -407,6 +426,7 @@ runs: if [ -n "$INPUT_ACCOUNT" ]; then args+=(--account "$INPUT_ACCOUNT"); fi if [ -n "$INPUT_REGION" ]; then args+=(--region "$INPUT_REGION"); fi if [ -n "$INPUT_PROJECT" ]; then args+=(--project "$INPUT_PROJECT"); fi + if [ -n "$INPUT_DOMAIN" ]; then args+=(--domain "$INPUT_DOMAIN"); fi if [ -n "$INPUT_STACK_EXPORT" ]; then args+=(--stack-export "$INPUT_STACK_EXPORT"); fi if [ -n "$INPUT_PROJECT_DIR" ]; then args+=(--project-dir "$INPUT_PROJECT_DIR"); fi if [ -n "$INPUT_LABEL" ]; then args+=(--label "$INPUT_LABEL"); fi