Skip to content

feat: add declarative cross-module script ordering via coder_script_order#27160

Draft
SasSwart wants to merge 1 commit into
sas/script-resource-address-on-timelinefrom
sas/script-order-deps-on-timeline
Draft

feat: add declarative cross-module script ordering via coder_script_order#27160
SasSwart wants to merge 1 commit into
sas/script-resource-address-on-timelinefrom
sas/script-order-deps-on-timeline

Conversation

@SasSwart

Copy link
Copy Markdown
Contributor

Overview

Adds a coder_script_order resource that lets templates declare ordering dependencies between coder_script resources across modules, and enforces them in the agent so a script waits for its declared predecessors before running. The ordering is also recorded in the unit graph so it shows up in coder exp sync timeline.

Target HCL:

coder_script_order "startup" {
  rule {
    run   = [coder_script.install.id]
    after = [coder_script.clone.id]
    state = "completes" # or "starts"
  }
}

A rule means: every script in run waits for every script in after to reach state ("completes" -> completed, "starts" -> started; default completes). Multiple coder_script_order blocks are additive.

This is the new tip of the sync-units stack (#27149#27150#27152#27153#27154 → this).

How it works (end to end)

  • proto: ScriptDependency {resource_address, required_status} added to provisionersdk Script and to the agent WorkspaceAgentScript.
  • provisioner (provisioner/terraform/resources.go): a new pass over coder_script_order resources resolves each rule's run/after coder_script.<name>.id references to the scripts' Terraform resource_address and records dependencies on the referenced run scripts. Unknown references and invalid state values are rejected with a clear error.
  • database: dependencies are stored denormalized as a jsonb column on workspace_agent_scripts (migration 000544). The agent consumes them by resource address, which avoids a UUID join table and insert-order resolution.
  • provisionerdserver / manifest / SDK: dependencies are persisted on insert and threaded back out through the agent manifest and codersdk.
  • agent (agent/agentscripts): each script is registered as a sync unit, the declared ordering edges are added, and run() gates execution by waiting until the script's unit is ready. unit.Manager gains WaitUntilReady (broadcast-based) for this.

Design notes

  • Fail-open timeout: a script waits for its dependencies bounded by a timeout (default 5m). On timeout it logs a warning and runs anyway, so a stuck or missing dependency cannot hang workspace startup forever. Missing/typo references resolve to an unregistered unit that never becomes ready and therefore hit this path.
  • "starts" state matches the dependency's status exactly. If a dependency advances startedcompleted before the dependent re-checks, a "starts" dependency can be momentarily unsatisfied; the fail-open timeout mitigates this. "completes" is the default and common case.
  • REST omits dependencies: db2sdk.go's WorkspaceAgentScript was intentionally left unchanged. The agent manifest is the only consumer of this field; it is not part of the REST workspace-agent representation.
  • Cron/repeat scripts: a completed unit that re-runs flips back to started, briefly making dependents "not ready". Acceptable for startup ordering.

Cross-repo dependency

The provisioner keys off the coder_script_order type name in Terraform state, so parsing is developed and tested here against tfjson/inline ConvertState fixtures. End-to-end CI that runs real Terraform needs the resource in the pinned provider, so full green depends on the companion terraform-provider-coder PR being released and a provider version bump. Kept as a draft until then.

Tests

  • agent/unit: WaitUntilReady (ready, blocks-then-unblocks, ctx cancel, unregistered).
  • agent/agentscripts: edges added from manifest, unmet dependency waits then unblocks, timeout fail-open.
  • provisioner/terraform: coder_script_orderDependencies (completes/starts/invalid/unknown reference).
  • coderd/provisionerdserver + coderd/agentapi + codersdk/agentsdk: round-trip of dependencies through DB, manifest, and proto conversion.
Implementation plan

Plan: declarative cross-module script ordering (coder_script_order)

Express dependencies between coder_script resources in Terraform and enforce them in the agent, so scripts wait for their declared predecessors before running and the ordering is visible in coder exp sync timeline.

Decisions (confirmed)

  1. Managed resource coder_script_order (not a data source).
  2. References use coder_script.<name>.id (Terraform-validated), resolved by Coder's provisioner to each script's resource_address (the unit name).
  3. Gate execution: the agent blocks a script from running until its declared dependencies reach the required state, in addition to recording the edges in the unit graph / timeline.

Semantics

  • A rule means: every script in run depends on every script in after reaching state. Edge direction in unit.Manager: AddDependency(run, after, requiredStatus) (run depends on after).
  • state mapping: "completes" -> unit.StatusComplete, "starts" -> unit.StatusStarted. Default "completes".
  • Cross-module works because the unit name is the Terraform resource_address (e.g. module.code-server.coder_script.code-server), already plumbed on the current stack.

PR 1 - terraform-provider-coder

Config-only managed resource, same pattern as coder_script/coder_env: scriptOrderResource() with a repeatable rule block (run, after, state with default completes and StringInSlice validation), registered in ResourcesMap, plus example and unit tests, make fmt/make gen.

PR 2 - coder/coder (this PR)

  • Provisioner parse: new pass over coder_script_order resolving run/after ids to resource addresses and appending proto.ScriptDependency to run scripts.
  • Proto: ScriptDependency/WorkspaceAgentScriptDependency messages + dependencies fields.
  • Database: jsonb dependencies column (migration 000544); insert/select carry it.
  • provisionerdserver: marshal dependencies to JSON on insert.
  • Manifest + SDK: unmarshal and expose dependencies; convert.go maps both directions.
  • Unit manager: WaitUntilReady broadcast wait primitive.
  • Runner: add edges in Init(), gate execution in run() with a bounded fail-open wait.

Risks / notes

  • Deadlock/hang mitigated by graph cycle detection (edge-add) + bounded wait timeout (fail-open default).
  • Missing/typo dependency resolves to an unregistered unit -> never ready -> hits the wait timeout -> logged, script proceeds.
  • Cron/repeat scripts: a completed unit re-running flips to started, briefly making dependents "not ready"; acceptable for startup ordering.

Generated by Coder Agents on behalf of @SasSwart.

…rder

Introduce a coder_script_order resource that lets templates declare ordering
dependencies between coder_script resources across modules. Each rule states
that the scripts in `run` must wait for the scripts in `after` to reach a
lifecycle state ("completes" -> completed, "starts" -> started).

The Terraform provider resource is added separately in
coder/terraform-provider-coder. This change wires the dependencies end to end:

- provisionersdk/agent protos carry ScriptDependency ({resource_address,
  required_status}) on scripts.
- The Terraform state converter reads coder_script_order rules and records
  dependencies on the referenced run scripts, keyed by resource address.
- provisionerdserver persists dependencies as a jsonb column on
  workspace_agent_scripts (migration 000544); the agent manifest exposes them.
- The agent's script runner registers each script as a sync unit, adds the
  declared ordering edges, and gates execution: a script blocks until its
  dependencies reach the required status, with a fail-open timeout so a stuck
  dependency cannot hang workspace startup.

The unit.Manager gains WaitUntilReady for broadcast-based readiness waiting.

Generated by Coder Agents on behalf of @SasSwart.
@github-actions

Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/reference/api/agents.md

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