Repository navigation
Conversation
Templates have no way to declare script ordering because the provider
does not define the data source Coder already reads. This adds it.
- New data source with one or more rule blocks. Each rule has run and
after selector lists, an optional requires (default success) and an
optional phase (start or stop, no default).
- Validation covers structure and enum values only: at least one rule,
at least one item in each list, no empty strings, requires in
{success, completion}, phase in {start, stop}. Selector strings are
passed through unchanged for Coder to resolve after plan.
- phase has no default on purpose. Coder infers the phase when it is
unset and warns when inference filtered a module selector. A default
would hide both.
- Tests: schema test over four rules, eleven rejection cases, and a
test that pins the attribute names Coder decodes from the plan JSON.
- Example and generated docs. The description notes that Terraform does
not check selectors, that values must be known at plan time, and that
older Coder versions ignore the data source.
- coder_script description now says scripts run in parallel unless a
coder_script_order orders them.
Replaces draft #536, which predates the phase attribute.
Refs https://linear.app/codercom/issue/PLAT-542
|
/coder-agents-review Please limit the review to P0, P1, P2 and P3 issues only. |
|
/coder-agents-review Please limit the review to P0, P1, P2 and P3 issues only. |
|
Chat: Review posted | View chat Review history
deep-review v0.13.0 | Round 1 | Last posted: Round 1, 3 findings (1 P2, 2 P3), COMMENT. Review Finding inventoryFinding inventory: PR #550Findings
Contested and acknowledgedNone. Round logRound 1Netero: no findings, one Note. Panel (round 1, full): ging-go, ryosuke, melody, pariston, mafuuu, bisky, gon, leorio, kite (wildcard). 1 P2, 2 P3 posted. 3 Nit and 2 Note dropped per the author's P0-P3 request. 1 out of scope. CRF-1 set to P2 over Pariston's P1: keep-argument for P1 is that every deployment silently ignores the rules, including About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
There was a problem hiding this comment.
This PR adds the coder_script_order data source, its docs and example, and tests that pin its schema. Findings: 1 P2, 2 P3. Per your request, findings below P3 are not posted.
Out of scope (needs a ticket or explicit acceptance by a human):
- coder/coder
provisioner/terraform/scriptorder.go:754:resolveScriptOrderhas no non-test caller on coder/coder main (9ce4f2f358), so no Coder build decodescoder_script_orderat template import or orders scripts in the agent. Until that lands, a released provider exposes a data source that no deployment acts on.
🤖 This review was automatically generated with Coder Agents.
Both descriptions now use the provider's standard callout form and name Coder v2.39 as the first version that orders scripts. Older versions ignore the data source.
run and after each name the other attribute so the generated page reads correctly in alphabetical order. The phase description now says Coder rejects a module-only rule that spans both phases instead of implying omission always works.
TL;DR
Coder v2.39 will order startup and shutdown scripts, but templates have no way to declare the order because the Terraform provider does not define the
coder_script_orderdata source. This PR adds it. Template authors can write ordering rules, Terraform checks the basic structure duringterraform plan, and Coder v2.39 and later resolves the selectors and validates the rest when the template is imported. Older Coder versions ignore the data source and run scripts in parallel; both descriptions carry the provider's standard version callout.Replaces draft #536, which predates the
phaseattribute.Refs https://linear.app/codercom/issue/PLAT-542
Implementation
provider/script_order.go: new data source with one or moreruleblocks. Each rule hasrunandafterselector lists, an optionalrequiresthat defaults tosuccess, and an optionalphasewith no default.phasehas no default on purpose. Coder infers the phase when it is unset and warns when inference filtered a module selector. A default would hide both behaviours.SchemaVersionis 0, since this is a new type. feat: add coder_script_order data source #536 used 1.provider/provider.go: registers the data source.examples/data-sources/coder_script_order/data-source.tf: three rules covering defaults,requires = "completion", and an explicitphase.docs/data-sources/script_order.md: generated. The description notes that Terraform does not check selectors, that all values must be known at plan time, and carries the-> ... only available in Coder v2.39 and later.callout.provider/script.goanddocs/resources/script.md: thecoder_scriptdescription now says scripts run in parallel unless acoder_script_orderorders them, with the same v2.39 callout.Expected Output
runandafterlists, passesterraform plan. The plan holds every selector exactly as written,requiresset tosuccesswhere omitted, andphaseunset where omitted.ruleblock fails with "Insufficient rule blocks".runorafterfails with "The argument ... is required".run = []orafter = []fails with "requires 1 item minimum".runorafterfails with "to not be an empty string", naming the index.requiresother thansuccessorcompletionfails, including an explicit empty string. The match is case-sensitive.phaseother thanstartorstopfails. The match is case-sensitive.rule,run,after,requiresandphase, which is what Coder's provisioner decodes.Testing
TestScriptOrderplans four rules and asserts twenty state attributes: list lengths and items, therequiresdefault,phasestaying empty when omitted, an indexed selector, a module selector, and an invalid selector passed through unchanged.TestScriptOrderValidationhas eleven rejection cases with the exact Terraform error for each: no rules, missingrunorafter, empty lists, empty selector strings, invalidrequires, emptyrequires, invalidphase, and wrong-casephase.TestScriptOrderContractreads the schema and pins the attribute names Coder decodes, therequiresdefault, and the absence of aphasedefault.TestExamplesnow plans the new example file.requires = ""case pins what the SDK does today (rejection) so an SDK upgrade cannot change it unnoticed.go test ./provider/passes (full suite, 48s).Manual Test
Run on a laptop against this branch at
a35da93, Terraform v1.15.7, provider built withmake buildand wired in through adev_overridesentry in~/.terraformrc. No Coder deployment is involved: the data source is evaluated entirely by Terraform duringplan, so everything this PR adds is visible interraform show -jsonandterraform validate. All 22 scenarios passed. No product defects found.rule(min 1) withrun,afterrequired,requires,phaseoptionalrequiresdefaults tosuccess,phaseleft unsetdynamic "rule"withformat()expands into plain rulescount = 0on the data source removes it;count = 1gives an indexed addressruleblock is rejectedrunis rejectedafter = []is rejectedrequires = "always"is rejectedrequires = ""is rejected, by bothvalidateandplanphase = "both"is rejectedphase = "Start"is rejected (case-sensitive)" "is accepted; rejecting it is Coder's jobafter,phase,requires,runphaseserializes as"", notnullmake genproduces no diff against the committed docsgo test ./provider/passesdev_overridesline removedTwo facts worth knowing from the run:
prior_state.values, notplanned_values. The commands below read from there.phasecomes through as the empty string. Coder treats""as "infer the phase", so this is the form the Coder-side fixtures should expect.Shell helpers used throughout
1. Build and dev override
~/.terraformrc:No
.terraform/or lock file was created; nothing was downloaded.2. Schema Terraform sees
{ "attrs": ["id"], "rule_min": 1, "rule_attrs": [ {"key": "after", "required": true, "optional": null}, {"key": "phase", "required": null, "optional": true}, {"key": "requires", "required": null, "optional": true}, {"key": "run", "required": true, "optional": null} ] }3. Minimal rule
[{ "address": "data.coder_script_order.bootstrap", "values": { "id": "7d914bf5-15c5-441c-aed0-6e44893ae00b", "rule": [ {"after": ["coder_script.clone_repo"], "phase": "", "requires": "success", "run": ["coder_script.install_tools"]} ] } }]4. Four rules covering every attribute state
{"after":["coder_script.clone_repo","coder_script.authenticate"],"phase":"","requires":"success","run":["coder_script.install_tools","coder_script.configure_shell"]} {"after":["module.bootstrap"],"phase":"","requires":"completion","run":["coder_script.setup[\"api\"]"]} {"after":["module.checkout"],"phase":"stop","requires":"success","run":["module.application"]} {"after":["also not valid"],"phase":"","requires":"success","run":["not valid selector syntax"]}5. Selectors the provider cannot judge are passed through
{"after":["module.git_clone[0]","data.coder_script.x","coder_script.clone_rpo"],"phase":"","requires":"success","run":["coder_agent.main","coder_script.*","module.a.coder_script.b"]}Every one of these is rejected by Coder at template import. The provider accepting them is intended: it has no view of the rest of the template, and a stricter check here could reject selectors Coder accepts.
6. dynamic rule block with format()
{"after":["coder_script.clone[\"api\"]"],"phase":"","requires":"success","run":["coder_script.install[\"api\"]"]} {"after":["coder_script.clone[\"worker\"]"],"phase":"","requires":"success","run":["coder_script.install[\"worker\"]"]}7. count on the data source
[{"address":"data.coder_script_order.optional[0]","rule":{"after":["coder_script.b"],"phase":"","requires":"success","run":["coder_script.a"]}}]8. Data source inside a child module
{"address":"data.coder_script_order.repo","rule":{"after":["module.git_clone"],"phase":"","requires":"success","run":["coder_script.install_tools"]}} {"address":"module.git_clone.data.coder_script_order.internal","rule":{"after":["coder_script.clone"],"phase":"","requires":"success","run":["coder_script.configure_git"]}}9. No rule block
10. Missing run
11. Empty after list
12. Empty string inside a list
13. Unknown requires
14. Explicit empty requires
Both
validateandplanreject it. The default applies only when the attribute is absent; a present empty string goes to the validator. This agrees with theEmptyRequiresunit test.15. Unknown phase
16. Wrong-case phase
17. Whitespace-only selector is accepted
{"after":["coder_script.b"],"phase":"","requires":"success","run":[" "]} 1The check is "not empty", matching the RFC. Coder rejects
" "at import as invalid address syntax.18. Attribute names in the plan JSON
{"top":["id","rule"],"rule":["after","phase","requires","run"],"all_rules_same_keys":1}These are the names Coder's provisioner decodes. Every rule carries the same key set, so an omitted attribute still appears as a key with its default or empty value.
19. How an unset phase is serialized
{"phase":"","phase_type":"string"} ["string"]20. Docs regenerate cleanly
21. Full provider test suite
22. Cleanup
git statusprinted nothing. Terraform on the laptop uses the released provider again.