Skip to content

fix(circleci_project): in-place updates no longer cascade replacement; fix pr_only_branch_overrides quoting#106

Open
james-crowley wants to merge 2 commits into
CircleCI-Public:mainfrom
james-crowley:fix/project-update-cascade
Open

fix(circleci_project): in-place updates no longer cascade replacement; fix pr_only_branch_overrides quoting#106
james-crowley wants to merge 2 commits into
CircleCI-Public:mainfrom
james-crowley:fix/project-update-cascade

Conversation

@james-crowley

Copy link
Copy Markdown

What this fixes

Two bugs in circleci_project that combine to make in-place updates unsafe.

1. Cascading replacement on benign edits

Changing any field on a circleci_project (e.g. auto_cancel_builds = false) plans as a destructive cascade:

Plan: 4 to add, 6 to change, 4 to destroy.

# circleci_context_restriction.X            must be replaced
# circleci_project_environment_variable.Y   must be replaced
# circleci_webhook.Z                        must be replaced

Any resource referencing circleci_project.X.id or .slug (context restrictions, project env vars, webhooks scoped to the project) gets destroyed and recreated. Project env vars carry secrets, so this silently drops customer secrets on apply.

Cause: Computed attributes including id and slug had no UseStateForUnknown plan modifier, so the framework marked them Unknown during plan whenever any other field changed. Dependents whose RequiresReplace fields reference the project then had to replace.

Fix: add UseStateForUnknown to all Computed attributes. Same pattern is already used in runner_resource_class_resource.go in this repo.

2. pr_only_branch_overrides elements get double-quoted (Fixes #59)

pr_only_branch_overrides = ["main"]

…fails apply with:

Error: Provider produced inconsistent result after apply
.pr_only_branch_overrides[0]: was cty.StringVal("main"),
but now cty.StringVal("\"main\"").

Cause: Create and Update extract list elements via branch.String(), which returns the Terraform-syntax representation (literally "main" including the quotes). The quoted strings were sent to the API and stored.

Fix: use elem.(types.String).ValueString() instead — the same pattern used for individual string attributes in this provider.

#61 proposed the same extraction fix bundled with a ListAttributeSetAttribute schema change. This PR keeps the existing list type to avoid breaking existing HCL; the type-shape decision can be a separate follow-up.

Why both in one PR

Fix 1 alone exposes Fix 2: removing the cascade means Update actually runs (it didn't before, because every change destroyed and recreated), which immediately trips the quoting bug. Shipping Fix 1 without Fix 2 just moves the failure from "destructive plan" to "apply error".

Testing

Tested locally against a CircleCI standalone organization with a project, two project env vars, a context restriction, and a webhook.

Before: toggling auto_cancel_builds plans as 4 add / 6 change / 4 destroy. Forcing past that hits the pr_only_branch_overrides 400.

After: same toggle plans as 0 add / 1 change / 0 destroy, applies clean, re-plan reports No changes. Setting pr_only_branch_overrides = ["main", "develop"] and applying stores the values verbatim with no embedded quotes.

Migration note

Existing projects whose API state has the quoted form stored (["\"main\""]) will continue to read that way until cleaned up. Setting pr_only_branch_overrides explicitly in HCL and applying once overwrites the API state via the corrected Update path.

Closes #59.

…dates

The circleci_project resource declares id, slug, organization_name,
organization_slug, vcs_info_url, vcs_info_provider, vcs_info_default_branch
and the seven Optional+Computed boolean settings as Computed without any
plan modifiers. By terraform-plugin-framework default, every Computed
attribute is marked Unknown during plan when any other field on the
resource changes, so the provider can recompute it after apply.

In practice id and slug are stable across in-place updates -- only a
RequiresReplace forces a new value. With them marked Unknown in plan,
dependent resources whose RequiresReplace fields reference the project
get cascaded into replacement:

  - circleci_context_restriction.value     = circleci_project.X.id
  - circleci_project_environment_variable.project_slug
                                           = circleci_project.X.slug
  - circleci_webhook.scope_id              = circleci_project.X.id

A single auto_cancel_builds=false flip on the project plans as
"4 to add, 6 to change, 4 to destroy" and destroys all project-scoped
secrets and the project webhook on apply. Customers running terraform
apply -auto-approve in CI lose secrets without warning.

Add UseStateForUnknown plan modifiers so the framework keeps the prior
state values for these Computed attributes during in-place updates. The
same pattern is used elsewhere in this provider (e.g.
runner_resource_class_resource.go), so this aligns the project resource
with the existing convention.

After this change the same auto_cancel_builds flip plans as
"0 to add, 1 to change, 0 to destroy".
The Create and Update paths for circleci_project iterate
plan.PROnlyBranchOverrides.Elements() and call .String() on each
element. attr.Value.String() returns the Terraform-syntax
representation, which for a string includes the surrounding double
quotes -- "main" becomes the literal seven-character string `"main"`.

The quoted strings are then sent to the CircleCI API and stored
verbatim. On the next refresh the API returns the quoted form, the
framework compares it to the planned value, and apply fails with:

  Error: Provider produced inconsistent result after apply

  .pr_only_branch_overrides[0]: was cty.StringVal("main"),
  but now cty.StringVal("\"main\"").

Replace .String() with the standard
elem.(types.String).ValueString() pattern (with a defensive type
assertion to surface unexpected element types via diagnostics rather
than a panic). This is the same idiom used for individual string
attributes elsewhere in the provider (plan.X.ValueString()), now
applied to list-element extraction.

Fixes CircleCI-Public#59.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

circleci_project with pr_only_branch_overrides input fails to provision

1 participant