Skip to content

Identify Starr, folder, and hook instances by map key - #729

Merged
davidnewhall merged 3 commits into
mainfrom
feat/instance-map-overlay
Sep 13, 2026
Merged

davidnewhall merged 3 commits into
mainfrom
feat/instance-map-overlay

Conversation

@davidnewhall

@davidnewhall davidnewhall commented Sep 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Starr apps, folders, webhooks, and command hooks are identified by a TOML/JSON map key instead of slice index, so deleting one instance cannot renumber the rest or steal UN_SONARR_0_*.
  • Dual-read legacy [[sonarr]] arrays as keys "0", "1", …. PUT writes the request body as the file document. Live is clone(fileConfig) + ParseENV (cnfg overlays existing map entries). Env-only slugs still appear on live after {}.
  • Replaces Identify Starr, folder, and hook instances by map key #728: no peel/keepPut of env-owned fields. A client that PUTs live overlay values persists them. Official UI must GET the file section, not /live.
  • Pins golift.io/cnfg to the map overlay commit (golift/cnfg#48).

Test plan

  • go test ./pkg/unpackerr/ ./pkg/configdef/
  • Load an existing [[sonarr]] file plus UN_SONARR_0_URL: instance key is 0, env overlays URL, file API key survives.
  • PUT /api/config/readarr with {} while UN_READARR_0_* is set: file has no Readarr, live still has the env instance, TOML does not gain the env URL or API key.
  • PUT a name-only stub lands in the file; live still has env URL/key.
  • Live GET blanks Starr apiKey / passwords / webhook token; file GET still shows file-stored keys.

… overlay the same slug after a PUT.

PUT writes the request body as the file document. Live is clone(fileConfig)+ParseENV; there is no peel of env-owned fields.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Legacy numeric instances are reordered and the new OpenAPI unions reject valid payloads.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Migrates configured instances from positional slices to stable, slug-keyed maps while retaining legacy array input compatibility.

Changes:

  • Adds InstanceMap JSON/TOML decoding and updates runtime consumers.
  • Preserves file/live environment-overlay behavior and redacts live secrets.
  • Updates configuration generation, API schemas, documentation, dependencies, and tests.
File summaries
File Description
pkg/unpackerr/webhook.go Uses keyed hook maps.
pkg/unpackerr/start_test.go Updates folder tests.
pkg/unpackerr/starrpoll.go Polls keyed Starr instances.
pkg/unpackerr/starrpoll_test.go Updates polling tests.
pkg/unpackerr/queue_actions_test.go Updates queue-action fixtures.
pkg/unpackerr/openapi.json Documents map-based APIs.
pkg/unpackerr/instancemap.go Implements instance maps.
pkg/unpackerr/instancemap_test.go Tests map decoding.
pkg/unpackerr/historyrestore_test.go Updates restoration fixtures.
pkg/unpackerr/folder.go Validates keyed folders.
pkg/unpackerr/configput.go Applies map-based PUTs.
pkg/unpackerr/configput_test.go Tests PUT and overlays.
pkg/unpackerr/configdump.go Dumps keyed instances.
pkg/unpackerr/configdump_test.go Updates dump tests.
pkg/unpackerr/configclone.go Deep-clones instance maps.
pkg/unpackerr/configapi.go Returns maps and redacts secrets.
pkg/unpackerr/configapi_test.go Updates config API tests.
pkg/unpackerr/cnfgfile.go Clarifies overlay behavior.
pkg/unpackerr/cnfgfile_test.go Tests map persistence.
pkg/unpackerr/apps.go Changes configuration fields to maps.
pkg/unpackerr/api_test.go Updates API fixtures.
pkg/configdef/validate.go Validates named section kinds.
pkg/configdef/start.go Defines named section kind.
pkg/configdef/live.go Renders named TOML tables.
pkg/configdef/live_test.go Tests named rendering.
pkg/configdef/help.go Updates repeatable environment help.
pkg/configdef/docusaurus.go Documents named headers.
pkg/configdef/definitions.yml Defines named instance sections.
pkg/configdef/config.go Generates named TOML headers.
pkg/configdef/compose.go Handles named environment entries.
INTERNALS.md Documents map architecture.
go.sum Updates dependency checksums.
go.mod Pins map-overlay cnfg.
examples/unpackerr.conf.example Shows named TOML instances.
examples/docker-compose.yml Refreshes generated output.
.github/copilot-instructions.md Updates map terminology.
Review details

Suppressed comments (1)

pkg/unpackerr/openapi.json:329

  • The PUT alternatives overlap as well: every map matches the generic object schema, and URL-only legacy arrays can match both Starr and hook arrays. Consequently valid request bodies fail oneOf validation in OpenAPI clients. This union needs anyOf.
  • Files reviewed: 35/36 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/unpackerr/instancemap.go
Comment thread pkg/unpackerr/openapi.json
Comment thread pkg/unpackerr/openapi.json Outdated
Comment thread pkg/unpackerr/configput_test.go

if err := unpackerr.validateApp(item.conf(), app); err != nil {
server := asStarr[T, P](item)
if err := unpackerr.validateApp(server.conf(), app); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One rough edge on this path: when env sets UN_SONARR_0_URL without UN_SONARR_0_API_KEY (the file supplies the key) and the user deletes the matching file entry, this rejects the whole PUT with the bare "provided application API Key is invalid, must be at least 32 characters" — nothing indicates it is the env-derived entry "0" and not something in their body. Reproduced: env URL-only, file [sonarr.0] + [sonarr.1], PUT {"1": …} → 400. Pre-PR the same op "succeeded" by stealing the env URL onto the surviving entry, so the rejection is the safe direction; wrapping the error with the key here (fmt.Errorf("%s instance %q: %w", app, key, err)) would make it self-explanatory. putHooks has the same shape.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping the error still 400s Save, which we cannot do.

A PUT-body row that omits apiKey because UN_*_API_KEY is set is filled by ParseENV and succeeds. Env overlay slugs that were not in the body and that startup would skip (URL without key, or the reverse) are dropped from live instead of failing the whole save. Remaining 400s now include the instance key (Sonarr instance "0": …). Same drop for incomplete env-only hooks.

Comment thread INTERNALS.md
### Env (`UN_*`)

Env overlays **live only**. They are not merged into `fileConfig`. Starr / folder / hook PUTs re-apply the overlay onto the live copy after the file-shaped body is written, so env-only list rows survive a save that omitted them; they still never land in `fileConfig` or the TOML. Env-only extra keys/roles exist at runtime until restart unless you add them in the PUT body. General scalars (`UN_INTERVAL`, `UN_PASSWORDS`, …) still overlay only at startup.
Env overlays **live only**. They are not merged into `fileConfig`. Starr / folder / hook PUTs write the request body as the file snapshot, then overlay live with `ParseENV` (cnfg now overlays existing map entries instead of replacing them). Env-only slugs keep polling. They land in `fileConfig` only if the PUT body included them. PUT `{}` (or a legacy `[]`) clears file instances; live still has `UN_SONARR_uhd_*` / `UN_READARR_0_*`. Env-only extra keys/roles exist at runtime until restart unless you add them in the PUT body. General scalars (`UN_INTERVAL`, `UN_PASSWORDS`, …) still overlay only at startup. The official UI must GET the file section (not `/live`) so it does not persist overlay values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One consequence worth a sentence here: renaming a TOML key that env references (e.g. [sonarr.1] → [sonarr.uhd]) does not move the env identity — after the PUT, live holds both "uhd" (file values, file key) and "1" (env values, env key), so the same server polls twice until the env var is renamed or cleared with UN_SONARR_1=. I verified this by experiment; fine if it's intended, but a client that renames keys in the UI without touching env will hit it.

@qwen-pr-bot qwen-pr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving.

Reviewed the full pull request at head 6587eda against base 4dc1ea7 (single commit). What it actually contains: (1) Starr/folder/webhook/cmdhook instances become slug-keyed InstanceMaps with dual-read of legacy [[section]] arrays as keys "0", "1", … in both TOML and JSON; (2) a new PUT contract — the request body is the file document, live is clone(fileConfig) + ParseENV, no env-peel safety net; (3) live GET now redacts instance secrets (Starr apiKey/HTTP/native passwords, hook token) even for *, while file GET keeps showing file-stored keys; (4) golift.io/cnfg pinned to the golift/cnfg#48 map-overlay commit; (5) the named kind in configdef, new OpenAPI schemas, regenerated examples, and the test-suite migration.

The renumber/steal fix is sound, and making the map key be the identity is the right core move: delete-safety falls out structurally (a sibling can never inherit a deleted slot's env var), and cnfg's overlay then does the work that #728 was approximating with peel/keepPut.

Executed validation (at head, in a disposable pod; no repo code committed or pushed):

  • go build ./... and go vet ./... — clean.
  • go test ./... — all packages pass.
  • go test -race ./pkg/unpackerr/ ./pkg/configdef/ — clean.
  • go test ./... inside the pinned golift.io/cnfg commit (6fc2ae3, tip of merged PR #48) — passes, including the empty-value entry-clear path (UN_…_KEY=).
  • Disposable harness (removed after use): a legacy two-entry [[sonarr]] file + UN_SONARR_0_URL/API_KEY + PUT {"1": …} → 200, the env slug survives live as its own entry, only [sonarr.1] is written, no env value reaches the TOML — settling the headline claim for the legacy-array path the repo tests only cover in named/readarr form. Same harness: keyless env slug → 400 (note inline), and a key rename that env references leaves a duplicate env identity (note inline).
  • The checked-in examples/unpackerr.conf.example matches the current configdef.ExampleTOML() output exactly, apart from the generator's timestamp footer. golangci-lint was not run locally (not in this pod); the public CI on head — gotest on ubuntu/macos/windows, golangci-lint on four OSes, Snyk — is all green as of this review.

Non-blocking notes are in the two inline comments. One housekeeping item that doesn't need a reply: cnfg is pinned as a pseudo-version of an untagged commit; bumping to a real v0.4.1 tag when it gets cut would be the only thing I'd change over time.

… hook objects stay valid.

Live GET redaction now covers Starr passwords and hook tokens; a webhook {} PUT keeps env-only rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
… URL or API key.

Save omits env-owned secrets and may omit a slug whose identifying URL is in env; ParseENV still fills complete rows, and incomplete extras are skipped the same way as startup.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes the persisted configuration format, runtime ownership paths, API contracts, and environment-overlay dependency behavior across the daemon.

Review details
  • Files reviewed: 36/37 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@davidnewhall
davidnewhall merged commit 50309c4 into main Sep 13, 2026
16 checks passed
@davidnewhall
davidnewhall deleted the feat/instance-map-overlay branch September 13, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants