fix(infra): create the demo Workers through the scripts API - #19
Merged
Merged
Conversation
cloudflare_worker's create call returns 403 for this token even with Workers Scripts Write and Workers Editor. The router already works through cloudflare_workers_script, so create each demo Worker the same way with a placeholder, and ignore later changes because the Deploy Examples workflow owns the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUEPGc2SgMUHxx4Z28HS8v
Terraform plan — 📝 changescloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=ba2224a5004345f19ad66c5a71343976]
cloudflare_dns_record.docs_cname: Refreshing state... [id=e562a5382eacb08985eaba0d4907fb2c]
cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=b465118aef02490e9f584146ce54f65b]
cloudflare_r2_bucket.cdn: Refreshing state... [id=pywire-cdn]
cloudflare_email_routing_settings.main: Refreshing state... [id=9f1c5e8c997d29163e81a24f7b26dc35]
cloudflare_pages_project.landing: Refreshing state... [id=pywire-landing]
cloudflare_pages_project.docs: Refreshing state... [id=pywire-docs]
cloudflare_ruleset.allow_llm_crawlers: Refreshing state... [id=375241922de4405a8b77b812e6e25730]
cloudflare_dns_record.vscode_verification: Refreshing state... [id=8ac56f7d47b8d2401df848706ad88cdd]
cloudflare_dns_record.demo: Refreshing state... [id=b0bddc628bb491021da19902143848d8]
cloudflare_email_routing_rule.maintainers_group: Refreshing state... [id=2f20558a55f04e10959ead934f397730]
cloudflare_email_routing_rule.individual_aliases["hello"]: Refreshing state... [id=1ff2a9d2add740089d4c7952455e4b56]
cloudflare_email_routing_rule.individual_aliases["reece"]: Refreshing state... [id=d14078e65aea4106adf662dc46d54710]
cloudflare_workers_script.router: Refreshing state... [id=pywire-router]
cloudflare_pages_domain.docs: Refreshing state... [id=docs.pywire.dev]
cloudflare_pages_domain.landing: Refreshing state... [id=pywire.dev]
cloudflare_dns_record.nightly: Refreshing state... [id=6a2d6b6878cc8452e1e29dd1f30c6dd1]
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:
+ create
Terraform will perform the following actions:
# cloudflare_worker.demo["edge-stateless"] will no longer be managed by Terraform, but will not be destroyed
# (destroy = false is set in the configuration)
. resource "cloudflare_worker" "demo" {
id = "84506ce1127f41b89449e73c65944e56"
name = "pywire-demo-edge-stateless"
tags = []
# (8 unchanged attributes hidden)
}
# cloudflare_worker.demo["form-builder"] will no longer be managed by Terraform, but will not be destroyed
# (destroy = false is set in the configuration)
. resource "cloudflare_worker" "demo" {
id = "bee6fc294eb94d35ba9e6121e76abe51"
name = "pywire-demo-form-builder"
tags = []
# (8 unchanged attributes hidden)
}
# cloudflare_worker.demo["site"] will no longer be managed by Terraform, but will not be destroyed
# (destroy = false is set in the configuration)
. resource "cloudflare_worker" "demo" {
id = "bc954fb642f5457a900207a9b69eac6f"
name = "pywire-demo"
tags = []
# (8 unchanged attributes hidden)
}
# cloudflare_workers_route.demo["edge-stateless"] will be created
+ resource "cloudflare_workers_route" "demo" {
+ id = (known after apply)
+ pattern = "demo.pywire.dev/edge-stateless"
+ script = "pywire-demo-edge-stateless"
+ zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
}
# cloudflare_workers_route.demo["edge-stateless/*"] will be created
+ resource "cloudflare_workers_route" "demo" {
+ id = (known after apply)
+ pattern = "demo.pywire.dev/edge-stateless/*"
+ script = "pywire-demo-edge-stateless"
+ zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
}
# cloudflare_workers_route.demo["form-builder"] will be created
+ resource "cloudflare_workers_route" "demo" {
+ id = (known after apply)
+ pattern = "demo.pywire.dev/form-builder"
+ script = "pywire-demo-form-builder"
+ zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
}
# cloudflare_workers_route.demo["form-builder/*"] will be created
+ resource "cloudflare_workers_route" "demo" {
+ id = (known after apply)
+ pattern = "demo.pywire.dev/form-builder/*"
+ script = "pywire-demo-form-builder"
+ zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
}
# cloudflare_workers_route.demo["site"] will be created
+ resource "cloudflare_workers_route" "demo" {
+ id = (known after apply)
+ pattern = "demo.pywire.dev/*"
+ script = "pywire-demo"
+ zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
}
# cloudflare_workers_script.demo["edge-stateless"] will be created
+ resource "cloudflare_workers_script" "demo" {
+ account_id = "abd8226d8d910afcfa1d370097e6336a"
+ bindings = (known after apply)
+ compatibility_date = "2026-09-01"
+ compatibility_flags = (known after apply)
+ content = <<-EOT
export default {
fetch() {
return new Response("This demo hasn't been deployed yet.", { status: 503 });
},
};
EOT
+ created_on = (known after apply)
+ etag = (known after apply)
+ handlers = (known after apply)
+ has_assets = (known after apply)
+ has_modules = (known after apply)
+ id = (known after apply)
+ last_deployed_from = (known after apply)
+ logpush = false
+ main_module = "placeholder.js"
+ migration_tag = (known after apply)
+ modified_on = (known after apply)
+ named_handlers = (known after apply)
+ placement = (known after apply)
+ script_name = "pywire-demo-edge-stateless"
+ startup_time_ms = (known after apply)
+ tail_consumers = (known after apply)
+ usage_model = "standard"
}
# cloudflare_workers_script.demo["form-builder"] will be created
+ resource "cloudflare_workers_script" "demo" {
+ account_id = "abd8226d8d910afcfa1d370097e6336a"
+ bindings = (known after apply)
+ compatibility_date = "2026-09-01"
+ compatibility_flags = (known after apply)
+ content = <<-EOT
export default {
fetch() {
return new Response("This demo hasn't been deployed yet.", { status: 503 });
},
};
EOT
+ created_on = (known after apply)
+ etag = (known after apply)
+ handlers = (known after apply)
+ has_assets = (known after apply)
+ has_modules = (known after apply)
+ id = (known after apply)
+ last_deployed_from = (known after apply)
+ logpush = false
+ main_module = "placeholder.js"
+ migration_tag = (known after apply)
+ modified_on = (known after apply)
+ named_handlers = (known after apply)
+ placement = (known after apply)
+ script_name = "pywire-demo-form-builder"
+ startup_time_ms = (known after apply)
+ tail_consumers = (known after apply)
+ usage_model = "standard"
}
# cloudflare_workers_script.demo["site"] will be created
+ resource "cloudflare_workers_script" "demo" {
+ account_id = "abd8226d8d910afcfa1d370097e6336a"
+ bindings = (known after apply)
+ compatibility_date = "2026-09-01"
+ compatibility_flags = (known after apply)
+ content = <<-EOT
export default {
fetch() {
return new Response("This demo hasn't been deployed yet.", { status: 503 });
},
};
EOT
+ created_on = (known after apply)
+ etag = (known after apply)
+ handlers = (known after apply)
+ has_assets = (known after apply)
+ has_modules = (known after apply)
+ id = (known after apply)
+ last_deployed_from = (known after apply)
+ logpush = false
+ main_module = "placeholder.js"
+ migration_tag = (known after apply)
+ modified_on = (known after apply)
+ named_handlers = (known after apply)
+ placement = (known after apply)
+ script_name = "pywire-demo"
+ startup_time_ms = (known after apply)
+ tail_consumers = (known after apply)
+ usage_model = "standard"
}
Plan: 8 to add, 0 to change, 0 to destroy.
Warning: Some objects will no longer be managed by Terraform
If you apply this plan, Terraform will discard its tracking information for
the following objects, but it will not delete them:
- cloudflare_worker.demo["site"]
- cloudflare_worker.demo["edge-stateless"]
- cloudflare_worker.demo["form-builder"]
After applying this plan, Terraform will no longer manage these objects. You
will need to import them into Terraform to manage them again. |
Routes can't point at a code-less cloudflare_worker, which is why the second apply failed after creating them. Forget those resources with a removed block instead of destroying them, and let the placeholder scripts upload to the same names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUEPGc2SgMUHxx4Z28HS8v
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before: Infra apply can't finish the demo Workers. With Workers Scripts Write on the token,
cloudflare_workercreated the three Workers. Then every route failed with "Cannot configure a route for a Worker which does not exist": a Worker with no code doesn't count as a script for routes.After: each demo Worker is a
cloudflare_workers_script, the same resource the router uses. It starts as a placeholder that answers 503 "This demo hasn't been deployed yet.", so the routes apply before the first deploy. pywire/pywire's Deploy Examples workflow then uploads the real code, andignore_changes = allkeeps Terraform from undoing it. Terraform owns each Worker's existence and routes, and the workflow owns its code and settings.A
removedblock withdestroy = falsedrops the Workers created by the first apply from state without deleting them. The scripts then upload to the same names. The routes now reference the scripts. workers.dev and preview URLs stay off, because each example'swrangler.tomlsets them on deploy.🤖 Generated with Claude Code