Skip to content

CI hygiene + Terraform remote state on R2 - #6

Merged
reecelikesramen merged 5 commits into
mainfrom
plan/ci-terraform
Sep 23, 2026
Merged

reecelikesramen merged 5 commits into
mainfrom
plan/ci-terraform

Conversation

@reecelikesramen

Copy link
Copy Markdown
Contributor

Parts B+C of the infra/CI plan.

  • Node 24, current action majors, pnpm 10 (checkout v7, setup-node v7 + cache, pnpm/action-setup v6). Fixes the deploy workflow: cloudflare/pages-action is gone (repo 404s — the astro-v7 push failures) — now cloudflare/wrangler-action@v4 pages deploy.
  • Terraform state on R2 (pywire-tfstate bucket, native S3 locking): backend block, committed lockfile, infra.auto.tfvars for non-secret vars (repo is public — emails/token stay secrets), infra/README.md runbook.
  • New workflows: infra-plan.yml (PR plan comments, push-to-main drift enforcement, weekly drift issue) and infra-apply.yml (dispatch-only, self-verifying).
  • Pages projects declared direct-upload — the GitHub connections are vestigial but cannot be detached via API; delete+recreate is the convergence path (see README).

Verified locally: migration to R2 done, plan identical pre/post migration, state pull from R2, locking works. First push to main after merge runs the drift job; dispatch Infra apply once to see the full loop.

node 20 -> 24, pnpm/action-setup v2 -> v6 (pnpm 10), checkout v7,
setup-node v7 with pnpm store cache. pages-action repo is gone (404);
deploy.yml switches to cloudflare/wrangler-action v4 'pages deploy'
(same secrets).
State moves to R2 (bucket pywire-tfstate, native S3 locking, TF >= 1.10):
- backend block in main.tf; .terraform.lock.hcl now committed (was
  gitignored — CI needs reproducible provider pins)
- infra.auto.tfvars carries the non-secret values for CI (public repo;
  token + emails stay in secrets)
- infra-plan.yml: PR plan comments (never fail the PR), push-to-main
  drift enforcement, weekly scheduled drift check -> issue
- infra-apply.yml: workflow_dispatch on main, self-verifying (plan must
  be empty after apply); both serialize on the terraform lock group
- infra/README.md: daily flow, local setup, secrets, break-glass

Follow-up (bootstrap): local 'terraform init -migrate-state' moves the
existing state into R2; the first apply also converges the known
pywire-docs Pages drift (repo renamed pywire-core -> pywire).
The GitHub connections on both Pages projects are vestigial (builds
disabled since 53218bf; deploys are GitHub Actions direct uploads), so
the config now matches reality: no source, just production_branch.

The live connections cannot be detached in place — the API silently
ignores PATCH source:null (verified) — so converging requires
delete+recreate, documented in infra/README.md. Provider stays pinned
at 5.16 by the lock: 5.24+ can't read this state's email_routing_settings.
@pywire pywire deleted a comment from github-actions Bot Sep 23, 2026
@github-actions

Copy link
Copy Markdown

Terraform plan — 📝 changes

cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=b465118aef02490e9f584146ce54f65b]
cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=ba2224a5004345f19ad66c5a71343976]
cloudflare_r2_bucket.cdn: Refreshing state... [id=pywire-cdn]
cloudflare_email_routing_settings.main: Refreshing state... [id=9f1c5e8c997d29163e81a24f7b26dc35]
cloudflare_dns_record.vscode_verification: Refreshing state... [id=8ac56f7d47b8d2401df848706ad88cdd]
cloudflare_pages_project.docs: Refreshing state... [id=pywire-docs]
cloudflare_ruleset.allow_llm_crawlers: Refreshing state... [id=375241922de4405a8b77b812e6e25730]
cloudflare_pages_project.landing: Refreshing state... [id=pywire-landing]
cloudflare_dns_record.nightly: Refreshing state... [id=6a2d6b6878cc8452e1e29dd1f30c6dd1]
cloudflare_email_routing_rule.maintainers_group: Refreshing state... [id=2f20558a55f04e10959ead934f397730]
cloudflare_email_routing_rule.individual_aliases["reece"]: Refreshing state... [id=d14078e65aea4106adf662dc46d54710]
cloudflare_email_routing_rule.individual_aliases["hello"]: Refreshing state... [id=1ff2a9d2add740089d4c7952455e4b56]
cloudflare_workers_script.router: Refreshing state... [id=pywire-router]
cloudflare_workers_route.catch_all: Refreshing state... [id=a70946d772474786ada02b87b9fd05cf]
cloudflare_workers_route.nightly: Refreshing state... [id=874d0be239a04de79226d64cd941e9bf]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # cloudflare_pages_project.docs will be updated in-place
  ~ resource "cloudflare_pages_project" "docs" {
      ~ build_config           = {
          + build_caching       = (known after apply)
          + web_analytics_tag   = (known after apply)
          + web_analytics_token = (sensitive value)
            # (3 unchanged attributes hidden)
        }
        id                     = "pywire-docs"
        name                   = "pywire-docs"
      - source                 = {
          - config = {
              - deployments_enabled            = true -> null
              - owner                          = "pywire" -> null
              - owner_id                       = "256868786" -> null
              - path_includes                  = [
                  - "*",
                ] -> null
              - pr_comments_enabled            = true -> null
              - preview_branch_includes        = [
                  - "*",
                ] -> null
              - preview_deployment_setting     = "none" -> null
              - production_branch              = "main" -> null
              - production_deployments_enabled = false -> null
              - repo_id                        = "1141348112" -> null
              - repo_name                      = "pywire-core" -> null
            } -> null
          - type   = "github" -> null
        } -> null
        # (13 unchanged attributes hidden)
    }

  # cloudflare_pages_project.landing will be updated in-place
  ~ resource "cloudflare_pages_project" "landing" {
      ~ build_config           = {
          + build_caching       = (known after apply)
          + web_analytics_tag   = (known after apply)
          + web_analytics_token = (sensitive value)
            # (3 unchanged attributes hidden)
        }
        id                     = "pywire-landing"
        name                   = "pywire-landing"
      - source                 = {
          - config = {
              - deployments_enabled            = true -> null
              - owner                          = "pywire" -> null
              - owner_id                       = "256868786" -> null
              - path_includes                  = [
                  - "*",
                ] -> null
              - pr_comments_enabled            = true -> null
              - preview_branch_includes        = [
                  - "*",
                ] -> null
              - preview_deployment_setting     = "none" -> null
              - production_branch              = "main" -> null
              - production_deployments_enabled = false -> null
              - repo_id                        = "1141395619" -> null
              - repo_name                      = "pywire.dev" -> null
            } -> null
          - type   = "github" -> null
        } -> null
        # (13 unchanged attributes hidden)
    }

Plan: 0 to add, 2 to change, 0 to destroy.

@reecelikesramen
reecelikesramen merged commit 4254a00 into main Sep 23, 2026
2 checks passed
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.

1 participant