d5bc0be2fe
Incorporates 5 lessons from commit 216f3a4 into the plan:
- Connection keys/targets must use node names, not IDs
- HTTP auth must use Header Auth credential, not manual env vars
- Node names must be unique
- Use $('Node Name') after GraphQL chains, not $input.item.json
- Added validation checklist to verification section
Marks Task 2 as already completed (dead code removal done in hotfix).
Updates node counts from 193 to 181 baseline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
255 lines
14 KiB
Markdown
255 lines
14 KiB
Markdown
---
|
||
phase: 16-api-migration
|
||
plan: 06
|
||
type: execute
|
||
wave: 1
|
||
depends_on: []
|
||
files_modified: [n8n-workflow.json]
|
||
autonomous: true
|
||
gap_closure: true
|
||
|
||
must_haves:
|
||
truths:
|
||
- "Text command 'start/stop/restart <container>' queries containers via GraphQL, not Docker socket proxy"
|
||
- "Text command 'update <container>' queries containers via GraphQL, not Docker socket proxy"
|
||
- "Text command 'batch' queries containers via GraphQL, not Docker socket proxy"
|
||
- "Zero active Execute Command nodes with docker-socket-proxy references remain in n8n-workflow.json"
|
||
artifacts:
|
||
- path: "n8n-workflow.json"
|
||
provides: "Main workflow with all text command paths using GraphQL"
|
||
contains: "UNRAID_HOST"
|
||
key_links:
|
||
- from: "n8n-workflow.json (Query Containers for Action)"
|
||
to: "Unraid GraphQL API"
|
||
via: "POST to $env.UNRAID_HOST/graphql"
|
||
pattern: "UNRAID_HOST.*graphql"
|
||
- from: "n8n-workflow.json (Query Containers for Update)"
|
||
to: "Unraid GraphQL API"
|
||
via: "POST to $env.UNRAID_HOST/graphql"
|
||
pattern: "UNRAID_HOST.*graphql"
|
||
- from: "n8n-workflow.json (Query Containers for Batch)"
|
||
to: "Unraid GraphQL API"
|
||
via: "POST to $env.UNRAID_HOST/graphql"
|
||
pattern: "UNRAID_HOST.*graphql"
|
||
---
|
||
|
||
<objective>
|
||
Migrate the 3 remaining text command entry points in the main workflow from Docker socket proxy Execute Command nodes to Unraid GraphQL API queries.
|
||
|
||
Purpose: Close the verification gaps that block Phase 17 (docker-socket-proxy removal). The 3 text command paths (start/stop/restart, update, batch) still use Execute Command nodes with `curl` to the docker-socket-proxy. After this plan, ALL container operations in the main workflow use GraphQL -- zero Docker socket proxy dependencies remain.
|
||
|
||
Output: Updated n8n-workflow.json with 3 GraphQL query chains replacing 3 Execute Command nodes.
|
||
|
||
NOTE: Dead code removal (Task 2 originally) and orphan cleanup were already completed in commit 216f3a4. The current node count is 181, not 193. Only Task 1 remains.
|
||
</objective>
|
||
|
||
<critical_lessons>
|
||
Plans 16-02 through 16-05 introduced defects that required a hotfix (commit 216f3a4). Do NOT repeat these mistakes:
|
||
|
||
1. **Connection keys MUST use node NAMES, never node IDs.**
|
||
n8n resolves connections by node name. Using IDs as dictionary keys (e.g., `"http-get-container-for-action"`) creates orphaned wiring that silently fails at runtime.
|
||
- WRONG: `"connections": { "http-my-node-id": { "main": [...] } }`
|
||
- RIGHT: `"connections": { "My Node Display Name": { "main": [...] } }`
|
||
|
||
2. **Connection targets MUST also use node NAMES, never IDs.**
|
||
- WRONG: `{ "node": "code-normalizer-action", "type": "main", "index": 0 }`
|
||
- RIGHT: `{ "node": "Normalize GraphQL Response (Action)", "type": "main", "index": 0 }`
|
||
|
||
3. **GraphQL HTTP Request nodes MUST use Header Auth credential, NOT manual headers.**
|
||
Using `$env.UNRAID_API_KEY` as a manual header causes `Invalid CSRF token` / `UNAUTHENTICATED` errors. The correct config:
|
||
- `"authentication": "genericCredentialType"`
|
||
- `"genericAuthType": "httpHeaderAuth"`
|
||
- `"credentials": { "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }`
|
||
- Do NOT add `x-api-key` to `headerParameters` — the credential handles it.
|
||
Copy the exact auth config from any existing working node (e.g., "Get Container For Action").
|
||
|
||
4. **Node names MUST be unique.** Duplicate names cause connection ambiguity. n8n cannot distinguish which node a connection refers to.
|
||
|
||
5. **After a GraphQL query chain (HTTP → Normalizer → Registry), downstream Code nodes receive container item arrays, NOT upstream preparation data.** Use `$('Upstream Node Name').item.json` to reference data from before the chain. Using `$input.item.json` will give you a container object, not the preparation data.
|
||
</critical_lessons>
|
||
|
||
<execution_context>
|
||
@/home/luc/.claude/get-shit-done/workflows/execute-plan.md
|
||
@/home/luc/.claude/get-shit-done/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/STATE.md
|
||
@.planning/phases/16-api-migration/16-01-SUMMARY.md
|
||
@.planning/phases/16-api-migration/16-05-SUMMARY.md
|
||
@.planning/phases/16-api-migration/16-VERIFICATION.md
|
||
@n8n-workflow.json
|
||
@CLAUDE.md
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: Replace 3 Execute Command nodes with GraphQL query chains</name>
|
||
<files>n8n-workflow.json</files>
|
||
<action>
|
||
Replace 3 Execute Command nodes that use `curl` to docker-socket-proxy with GraphQL HTTP Request + Normalizer + Registry Update chains. Follow the exact same pattern established in Plan 16-05 (Task 1) for the 6 inline keyboard query paths.
|
||
|
||
**Node 1: "Docker List for Action" (id: exec-docker-list-action)**
|
||
|
||
Current: Execute Command node running `curl -s --max-time 5 'http://docker-socket-proxy:2375/v1.47/containers/json?all=true'`
|
||
Position: [1120, 400]
|
||
Connected FROM: "Parse Action Command"
|
||
Connected TO: "Prepare Action Match Input"
|
||
|
||
Replace with 3 nodes:
|
||
|
||
1a. **"Query Containers for Action"** — HTTP Request node (replaces Execute Command)
|
||
- type: n8n-nodes-base.httpRequest, typeVersion: 4.2
|
||
- method: POST
|
||
- url: `={{ $env.UNRAID_HOST }}/graphql`
|
||
- authentication: genericCredentialType, genericAuthType: httpHeaderAuth
|
||
- credentials: `{ "httpHeaderAuth": { "id": "unraid-api-key-credential-id", "name": "Unraid API Key" } }`
|
||
- Do NOT add manual x-api-key headers — the credential handles auth automatically
|
||
- sendBody: true, specifyBody: json
|
||
- jsonBody: `{"query": "query { docker { containers { id names state image status } } }"}`
|
||
- options: timeout: 15000
|
||
- position: [1120, 400]
|
||
- Copy the full node structure from an existing working node (e.g., "Get Container For Action") and only change name, id, position, and jsonBody
|
||
|
||
1b. **"Normalize Action Containers"** — Code node (GraphQL response normalizer)
|
||
- Inline normalizer code (same as Plan 16-01/16-05 pattern):
|
||
- Extract `data.docker.containers` from GraphQL response
|
||
- Map fields: id->Id, names->Names (add '/' prefix), state->State (RUNNING->running, STOPPED->exited, PAUSED->paused), image->Image, status->Status
|
||
- Handle GraphQL errors (check response.errors array)
|
||
- position: [1230, 400] (shift right to make room)
|
||
|
||
1c. **"Update Registry (Action)"** — Code node (Container ID Registry update)
|
||
- Inline registry update code (same as Plan 16-01/16-05 pattern):
|
||
- Read static data `_containerIdRegistry`, parse JSON
|
||
- Map each normalized container: name (strip '/') -> { name, unraidId: container.Id }
|
||
- Write back to static data with JSON.stringify (top-level assignment for persistence)
|
||
- Pass through all container items unchanged
|
||
- position: [1340, 400] (note: this is where "Prepare Action Match Input" currently sits)
|
||
|
||
**CRITICAL wiring change for "Prepare Action Match Input":**
|
||
- Move "Prepare Action Match Input" position to [1450, 400] (shift right to accommodate new nodes)
|
||
- Update its Code to read normalized containers instead of `stdout`:
|
||
- OLD: `const dockerOutput = $input.item.json.stdout;`
|
||
- NEW: `const containers = $input.all().map(item => item.json);` then `const dockerOutput = JSON.stringify(containers);`
|
||
- The matching sub-workflow (n8n-matching.json) expects `containerList` as a JSON string of the container array, so JSON.stringify the normalized array.
|
||
- Connection chain: Query Containers for Action -> Normalize Action Containers -> Update Registry (Action) -> Prepare Action Match Input -> Execute Action Match (unchanged)
|
||
|
||
**Node 2: "Docker List for Update" (id: exec-docker-list-update)**
|
||
|
||
Current: Execute Command node running same curl command
|
||
Position: [1120, 1000]
|
||
Connected FROM: "Parse Update Command"
|
||
Connected TO: "Prepare Update Match Input"
|
||
|
||
Replace with 3 nodes (same pattern):
|
||
|
||
2a. **"Query Containers for Update"** — HTTP Request node
|
||
- Same config as 1a, position: [1120, 1000]
|
||
|
||
2b. **"Normalize Update Containers"** — Code node
|
||
- Same normalizer code, position: [1230, 1000]
|
||
|
||
2c. **"Update Registry (Update)"** — Code node
|
||
- Same registry code, position: [1340, 1000]
|
||
|
||
**Update "Prepare Update Match Input":**
|
||
- Move position to [1450, 1000]
|
||
- Change Code from `$input.item.json.stdout` to `JSON.stringify($input.all().map(item => item.json))`
|
||
- Connection chain: Query Containers for Update -> Normalize Update Containers -> Update Registry (Update) -> Prepare Update Match Input -> Execute Update Match (unchanged)
|
||
|
||
**Node 3: "Get Containers for Batch" (id: exec-docker-list-batch)**
|
||
|
||
Current: Execute Command node running same curl command
|
||
Position: [1340, -300]
|
||
Connected FROM: "Is Batch Command"
|
||
Connected TO: "Prepare Batch Match Input"
|
||
|
||
Replace with 3 nodes (same pattern):
|
||
|
||
3a. **"Query Containers for Batch"** — HTTP Request node
|
||
- Same config as 1a, position: [1340, -300]
|
||
|
||
3b. **"Normalize Batch Containers"** — Code node
|
||
- Same normalizer code, position: [1450, -300]
|
||
|
||
3c. **"Update Registry (Batch)"** — Code node
|
||
- Same registry code, position: [1560, -300]
|
||
|
||
**Update "Prepare Batch Match Input":**
|
||
- Move position to [1670, -300]
|
||
- Change Code from `$input.item.json.stdout` to `JSON.stringify($input.all().map(item => item.json))`
|
||
- Connection chain: Is Batch Command [output 0] -> Query Containers for Batch -> Normalize Batch Containers -> Update Registry (Batch) -> Prepare Batch Match Input -> Execute Batch Match (unchanged)
|
||
|
||
**Connection updates in the connections object:**
|
||
- "Parse Action Command" target changes from "Docker List for Action" to "Query Containers for Action"
|
||
- "Parse Update Command" target changes from "Docker List for Update" to "Query Containers for Update"
|
||
- "Is Batch Command" output 0 target changes from "Get Containers for Batch" to "Query Containers for Batch"
|
||
- Add new connection entries for each 3-node chain (Query -> Normalize -> Registry -> Prepare)
|
||
- Remove old connection entries for deleted nodes
|
||
|
||
**Important:** Use the same inline normalizer and registry update Code exactly as implemented in Plan 16-05 Task 1. Copy the jsCode from any of the 6 existing normalizer/registry nodes already in n8n-workflow.json (e.g., find "Normalize GraphQL Response" or "Update Container Registry" nodes). Do NOT reference utility node templates from the main workflow -- sub-workflow pattern requires inline code (per Phase 16-01 decision).
|
||
</action>
|
||
<verify>
|
||
1. Search n8n-workflow.json for "docker-socket-proxy" -- should find ONLY the 2 infra-exclusion filter references in "Check Available Updates" (line ~2776) and "Prepare Update All Batch" (line ~3093) Code nodes which use `socket-proxy` as a container name pattern, NOT as an API endpoint
|
||
2. Search for "executeCommand" node type -- should find ZERO instances (all 3 Execute Command nodes removed)
|
||
3. Search for "Query Containers for Action", "Query Containers for Update", "Query Containers for Batch" -- all 3 must exist
|
||
4. Search for "Normalize Action Containers", "Normalize Update Containers", "Normalize Batch Containers" -- all 3 must exist
|
||
5. Search for "Update Registry (Action)", "Update Registry (Update)", "Update Registry (Batch)" -- all 3 must exist
|
||
6. Verify connections: "Parse Action Command" -> "Query Containers for Action", "Parse Update Command" -> "Query Containers for Update", "Is Batch Command" [0] -> "Query Containers for Batch"
|
||
7. Push workflow to n8n and verify HTTP 200 response
|
||
</verify>
|
||
<done>
|
||
All 3 text command entry points (action, update, batch) query containers via Unraid GraphQL API using the HTTP Request -> Normalizer -> Registry Update -> Prepare Match Input chain. Zero Execute Command nodes remain. Workflow pushes successfully to n8n.
|
||
</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: ALREADY COMPLETED — dead code and orphan removal</name>
|
||
<files>n8n-workflow.json</files>
|
||
<action>
|
||
SKIP THIS TASK — already completed in hotfix commit 216f3a4.
|
||
|
||
Removed 12 nodes: 6 dead code chains (Build/Execute/Parse Action Command × 2) and 6 orphan utility templates (GraphQL Response Normalizer, Container ID Registry, GraphQL Error Handler, Unraid API HTTP Template, Callback Token Encoder/Decoder). Node count went from 193 to 181.
|
||
|
||
The 2 remaining `socket-proxy` string references in "Check Available Updates" and "Prepare Update All Batch" are functional infrastructure exclusion filters — they will be addressed in Phase 17.
|
||
</action>
|
||
<verify>
|
||
Already verified. Node count is 181.
|
||
</verify>
|
||
<done>
|
||
Completed in prior hotfix.
|
||
</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<verification>
|
||
1. `grep -c "docker-socket-proxy" n8n-workflow.json` returns 2 (only infra-exclusion filter patterns, not API endpoints)
|
||
2. `grep -c "executeCommand" n8n-workflow.json` returns 0 (zero Execute Command nodes)
|
||
3. `grep -c "UNRAID_HOST" n8n-workflow.json` returns 12+ (9 existing GraphQL nodes + 3 new ones)
|
||
4. `grep "Query Containers for Action\|Query Containers for Update\|Query Containers for Batch" n8n-workflow.json` finds all 3 new query nodes
|
||
5. Workflow pushes to n8n successfully (HTTP 200)
|
||
6. All connection chains intact: Parse Command -> Query -> Normalize -> Registry -> Prepare Match -> Execute Match -> Route Result
|
||
7. **Connection integrity check:** All connection dictionary keys match actual node names (no node IDs as keys)
|
||
8. **Auth check:** All new HTTP Request nodes use `genericCredentialType` + `httpHeaderAuth` credential, NOT manual `x-api-key` headers
|
||
9. **Name uniqueness check:** No duplicate node names exist
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- Zero Execute Command nodes with docker-socket-proxy curl commands
|
||
- 3 new GraphQL HTTP Request + Normalizer + Registry Update chains for text command paths
|
||
- Total node count: 181 + 9 new - 3 removed = 187
|
||
- Workflow pushes to n8n successfully
|
||
- All text command paths route through GraphQL before reaching matching sub-workflow
|
||
- All new connection keys use node NAMES (not IDs)
|
||
- All new HTTP nodes use Header Auth credential (not $env.UNRAID_API_KEY)
|
||
- No duplicate node names introduced
|
||
- Phase 16 verification gaps closed: all 3 partial truths become fully verified
|
||
</success_criteria>
|
||
|
||
<output>
|
||
After completion, create `.planning/phases/16-api-migration/16-06-SUMMARY.md`
|
||
</output>
|