Skip to content

unifi_device_ports

What: build unifi_device_ports in the provider fork, release 0.56.0-fzymgc.2, then adopt switch-main’s ports in tf/unifi as an import with 0 changes (#2118). Design: docs/engineering/specs/2026-09-19-unifi-device-ports-design.md. Decisions D1 to D10 there are agreed and are not reopened here. Riskiest assumption: that the demo-mode controller in the fork’s acceptance suite has a simulated switch that accepts port_overrides writes. Without one, the write path is proven only by unit tests until the first real port change.

1. Decisions you’ll probably want to tweak

Section titled “1. Decisions you’ll probably want to tweak”
# Decision Alternative Cost of changing later
P1 The go-unifi fork starts at ead99009383e, the commit the provider fork already pins. The only change is one exported raw-request method Base it on go-unifi v1.34.1 High now: the provider fork does not build on v1.34.x (DeviceRadioTable.AssistedRoaming*; memory 1ky2wjft08). Moving to v1.34.x is separate fork maintenance, and the raw method rebases onto it trivially
P2 Test fixtures are synthetic, shaped like switch-main’s 22 entries (same key set, LAGs, and the trunk pattern) but with invented names and IDs Commit the real dump with x_ keys stripped The fork repo is public, and the real dump names hosts and network IDs. A synthetic fixture tests the same key-preservation property
P3 Release -fzymgc.2 by hand, following docs/operations/terraform-provider-fork.md Build the #2119 release automation first #2119 stays independent. A manual release is about a 15-minute runbook
P4 Adoption scope is switch-main only Adopt every switch at once The other switches use port profiles (AllMainDefault), and profile policy (D8) is not decided yet
Unknown Default Pivot signal
Does the demo controller simulate a switch whose ports can be written? Proceed. The existing unifi_device acceptance tests use simulated device 00:27:22:00:00:02 No port table, or the PUT is rejected. Then cover the write path with httptest against the synthetic fixture, and state in the PR that the controller write is not proven
What does the controller do with forward (spec §5)? The acceptance test decides Whatever it shows goes into mergePortOverrides
Does the controller accept {"port_overrides": []} from Delete? Yes for switches. #438 only saw gateways reject null 400 from the controller. Then Delete writes each entry reset to defaults instead
Does UseStateForUnknown hold per map element? Yes, because nested plan modifiers resolve the prior state by path An import plan shows “known after apply” on keys left out of config
Can the agent-pool run install -fzymgc.2? Yes, as it did for -fzymgc.1 init fails. Check the registry upload and the platform hashes

Each phase is one PR in its repository. Phases run in order.

  • Create the GitHub fork fzymgc-house/go-unifi from ubiquiti-community/go-unifi, with branch fzymgc at ead99009383e. This is an external action and needs Sean’s go-ahead, as the provider fork did.
  • unifi/unifi.go: add an exported Do(ctx, method, apiPath string, reqBody, respBody any) error as a thin wrapper over do. Session, CSRF and the cloud-connector path handling stay in do.
  • Test: an httptest round-trip of GET and PUT through Do, using a response with keys the structs do not model. The test asserts that the raw bytes survive.
  • Keep the module path unchanged. The provider consumes the fork through replace.

Phase 2: provider fork (fzymgc-house/terraform-provider-unifi, branch fzymgc)

Section titled “Phase 2: provider fork (fzymgc-house/terraform-provider-unifi, branch fzymgc)”
  1. go.mod: replace github.com/ubiquiti-community/go-unifi => github.com/fzymgc-house/go-unifi <pseudo-version>. go build ./... and the existing unit suite pass unchanged.

  2. unifi/device_ports_raw.go: getDeviceRaw(site, mac) returns the device _id and the raw port_overrides as []map[string]any. putPortOverrides(site, id, entries) sends {"port_overrides": entries} and nothing else.

  3. unifi/device_ports_merge.go: a pure function mergePortOverrides(live, declared).

    • It keeps every key not declared.
    • It sets declared keys.
    • It drops entries whose index is not declared.
    • It applies the §5 forward rule.

    Unit tests use the synthetic fixture (P2), which contains unmodelled keys (lag_idx, stp_edge_state, and an invented x_future_key). They assert:

    • Updating one key on port 12 changes only that key, and the other 21 entries are byte-equal.
    • Leaving a port out drops it.
    • A new port starts from {}.
    • LAG members never get their own entry.
  4. unifi/device_ports_resource.go: implements the schema from spec §3 and the lifecycle from spec §4.

    • The schema is a MapNestedAttribute. Every per-port attribute is Optional and Computed with UseStateForUnknown, and enums are validated.
    • Create refuses a device that already has entries.
    • Import is by MAC.
    • Register the resource in provider.go, and add a docs template and an example.
  5. Acceptance tests (TF_ACC=1, demo controller):

    • create, then update one key, then import with an empty plan, then delete;
    • the forward reconciliation from spec §5;
    • unknown-key survival: seed a raw key with Do, update another key, and read back;
    • Delete writes [].
  6. unifi/device_resource.go: mark port_override with a DeprecationMessage pointing to unifi_device_ports. In a separate commit, route the inline block’s update path through mergePortOverrides, so the block stops dropping unmodelled keys (spec §6.3).

  7. Update CHANGELOG.md, then release v0.56.0-fzymgc.2 for linux_arm64 and darwin_arm64, signed with the Vault key. Publish with scripts/publish-hcp-registry.sh (P3).

  1. tf/unifi/versions.tf: pin 0.56.0-fzymgc.2. Refresh .terraform.lock.hcl for both platforms.
  2. Read switch-main’s live entries with the View Only credential again. The probe script from quick task 260919-jrm is the starting point. Write tf/unifi/ports.tf with unifi_device_ports.switch_main:
    • all 22 entries from that read, with networks referenced as unifi_network.*.id;
    • prevent_destroy;
    • an import block keyed by MAC in main.tf.
  3. Acceptance: a speculative plan prints exactly Plan: 1 to import, 0 to add, 0 to change, 0 to destroy. There is no ignore_changes.
  4. Docs:
    • docs/operations/unifi.md: take ports out of “Not yet managed”, and add “Changing a port” (a PR to ports.tf, reviewed with the plan).
    • docs/operations/terraform-provider-fork.md: add the go-unifi fork and its replace.
  5. Open the PR (Sean merges). After the merge, the workspace auto-applies the import-only run. Close #2118.

Phase 4: upstream (each post on Sean’s go-ahead)

Section titled “Phase 4: upstream (each post on Sean’s go-ahead)”
  • go-unifi: the Do method.
  • Provider: unifi_device_ports plus the deprecation, and separately the inline-block field fix. Link the probe evidence, and explain why this resource is authoritative on purpose while #266 fixed an accidental whole-list replace.