feat(16-03): replace 5-step Docker update with single updateContainer GraphQL mutation
- Replace Docker API container lookup with GraphQL containers query - Add GraphQL Response Normalizer and Container ID Registry update - Replace 5-step update flow (stop/remove/create/start) with single updateContainer mutation - 60-second timeout for large image pulls (was 600s for docker pull) - ImageId comparison determines update success (not digest comparison) - Preserve all 15 messaging nodes (Format/Check/Send/Return) - Remove Docker socket proxy dependencies (zero references) - Remove Execute Command node (docker pull eliminated) - Reduce from 34 to 29 nodes (~15% reduction)
This commit is contained in:
+275
-369
@@ -75,26 +75,45 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"url": "http://docker-socket-proxy:2375/v1.47/containers/json?all=true",
|
"method": "POST",
|
||||||
|
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||||
|
"authentication": "none",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-api-key",
|
||||||
|
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={\"query\": \"query { docker { containers { id names state image imageId } } }\"}",
|
||||||
"options": {
|
"options": {
|
||||||
"timeout": 5000
|
"timeout": 15000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "http-get-containers",
|
"id": "http-query-containers",
|
||||||
"name": "Get All Containers",
|
"name": "Query All Containers",
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
560,
|
560,
|
||||||
400
|
400
|
||||||
]
|
],
|
||||||
|
"onError": "continueRegularOutput"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Find container by name and resolve ID\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerName = triggerData.containerName;\nconst containers = $input.all();\n\n// Normalize function to strip leading slash\nconst normalizeName = (name) => name.replace(/^\\//, '').toLowerCase();\nconst searchName = normalizeName(containerName);\n\n// Find matching container\nlet matched = null;\nfor (const item of containers) {\n const c = item.json;\n if (c.Names && c.Names.length > 0) {\n const cName = normalizeName(c.Names[0]);\n if (cName === searchName || cName.includes(searchName)) {\n matched = c;\n break;\n }\n }\n}\n\nif (!matched) {\n throw new Error(`Container '${containerName}' not found`);\n}\n\nreturn {\n json: {\n ...triggerData,\n containerId: matched.Id\n }\n};"
|
"jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));\n"
|
||||||
},
|
},
|
||||||
"id": "code-resolve-id",
|
"id": "code-normalize-response",
|
||||||
"name": "Resolve Container ID",
|
"name": "Normalize GraphQL Response",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
@@ -103,57 +122,99 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"id": "code-update-registry",
|
||||||
"method": "GET",
|
"name": "Update Container ID Registry",
|
||||||
"url": "=http://docker-socket-proxy:2375/containers/{{ $json.containerId }}/json",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-inspect-container",
|
|
||||||
"name": "Inspect Container",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
460,
|
|
||||||
300
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Parse container config and prepare for pull\nconst inspectData = $input.item.json;\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerId = triggerData.containerId;\nconst containerName = triggerData.containerName;\nconst chatId = triggerData.chatId;\nconst messageId = triggerData.messageId;\nconst responseMode = triggerData.responseMode;\nconst correlationId = triggerData.correlationId || '';\n\n// Extract image info\nlet imageName = inspectData.Config.Image;\nconst currentImageId = inspectData.Image;\n\n// CRITICAL: Ensure image has a tag, otherwise Docker pulls ALL tags!\nif (imageName && !imageName.includes(':') && !imageName.includes('@')) {\n imageName = imageName + ':latest';\n}\n\n// Extract config for recreation\nconst containerConfig = inspectData.Config;\nconst hostConfig = inspectData.HostConfig;\nconst networkSettings = inspectData.NetworkSettings;\n\n// Get current version from labels or image digest\nconst labels = containerConfig.Labels || {};\nconst currentVersion = labels['org.opencontainers.image.version']\n || labels['version']\n || currentImageId.substring(7, 19);\n\nreturn {\n json: {\n containerId,\n containerName,\n chatId,\n messageId,\n responseMode,\n imageName,\n currentImageId,\n currentVersion,\n containerConfig,\n hostConfig,\n networkSettings,\n correlationId\n }\n};"
|
|
||||||
},
|
|
||||||
"id": "code-parse-config",
|
|
||||||
"name": "Parse Container Config",
|
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
680,
|
880,
|
||||||
300
|
400
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"mode": "runOnceForAllItems",
|
||||||
|
"jsCode": "// Container ID Registry - Update action only\nconst registry = $getWorkflowStaticData('global');\nif (!registry._containerIdMap) {\n registry._containerIdMap = JSON.stringify({});\n}\n\nconst containers = $input.all().map(item => item.json);\nconst containerMap = JSON.parse(registry._containerIdMap);\n\n// Update map from normalized containers\nfor (const container of containers) {\n const name = (container.Names?.[0] || '').replace(/^\\//, '').toLowerCase();\n if (name && container.Id) {\n containerMap[name] = {\n name: name,\n unraidId: container.Id,\n timestamp: Date.now()\n };\n }\n}\n\nregistry._containerIdMap = JSON.stringify(containerMap);\nregistry._lastRefresh = Date.now();\n\n// Pass through all containers\nreturn containers.map(c => ({ json: c }));\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Find container by name and resolve ID\nconst triggerData = $('When executed by another workflow').item.json;\nconst containerName = triggerData.containerName;\nconst containers = $input.all();\n\n// Normalize function to strip leading slash\nconst normalizeName = (name) => name.replace(/^\\//, '').toLowerCase();\nconst searchName = normalizeName(containerName);\n\n// Find matching container\nlet matched = null;\nfor (const item of containers) {\n const c = item.json;\n if (c.Names && c.Names.length > 0) {\n const cName = normalizeName(c.Names[0]);\n if (cName === searchName || cName.includes(searchName)) {\n matched = c;\n break;\n }\n }\n}\n\nif (!matched) {\n throw new Error(`Container '${containerName}' not found`);\n}\n\nreturn {\n json: {\n ...triggerData,\n containerId: matched.Id,\n unraidId: matched.Id, // Full PrefixedID for GraphQL mutation\n currentImageId: matched.imageId || '', // For later comparison\n currentImage: matched.image || ''\n }\n};\n"
|
||||||
|
},
|
||||||
|
"id": "code-resolve-id",
|
||||||
|
"name": "Resolve Container ID",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
400
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"command": "=curl -s --max-time 600 -X POST 'http://docker-socket-proxy:2375/v1.47/images/create?fromImage={{ encodeURIComponent($json.imageName) }}' | tail -c 10000",
|
"method": "POST",
|
||||||
|
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||||
|
"authentication": "none",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-api-key",
|
||||||
|
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={{ {\"query\": \"query { docker { containers(filter: { id: \\\"\" + $json.containerId + \"\\\" }) { id names state image imageId } } }\"} }}",
|
||||||
"options": {
|
"options": {
|
||||||
"timeout": 660
|
"timeout": 15000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "exec-pull-image",
|
"id": "http-query-single",
|
||||||
"name": "Pull Image",
|
"name": "Query Single Container",
|
||||||
"type": "n8n-nodes-base.executeCommand",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 1,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
900,
|
560,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"onError": "continueRegularOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// GraphQL Response Normalizer - Transform Unraid GraphQL to Docker API contract\n// Input: $input.item.json = raw GraphQL response\n// Output: Array of normalized containers\n\nconst response = $input.item.json;\n\n// Validation: Check for GraphQL errors\nif (response.errors && response.errors.length > 0) {\n const messages = response.errors.map(e => e.message).join('; ');\n throw new Error(`GraphQL error: ${messages}`);\n}\n\n// Validation: Check response structure\nif (!response.data?.docker?.containers) {\n throw new Error('Invalid GraphQL response structure: missing data.docker.containers');\n}\n\n// State mapping: Unraid UPPERCASE -> Docker lowercase\nconst stateMap = {\n 'RUNNING': 'running',\n 'STOPPED': 'exited', // Docker convention: stopped = exited\n 'PAUSED': 'paused'\n};\n\nfunction normalizeState(unraidState) {\n return stateMap[unraidState] || unraidState.toLowerCase();\n}\n\n// Transform each container\nconst containers = response.data.docker.containers;\nconst normalized = containers.map(container => {\n const dockerState = normalizeState(container.state);\n \n return {\n // Core fields matching Docker API contract\n Id: container.id, // Keep full PrefixedID (registry handles translation)\n Names: container.names, // Already has '/' prefix (Phase 14 verified)\n State: dockerState, // Normalized lowercase state\n Status: dockerState, // Docker has separate Status field\n Image: '', // Not available in basic query\n \n // Debug field: preserve original Unraid ID\n _unraidId: container.id\n };\n});\n\n// Return as array of items (n8n multi-item output format)\nreturn normalized.map(container => ({ json: container }));\n"
|
||||||
|
},
|
||||||
|
"id": "code-normalize-single",
|
||||||
|
"name": "Normalize Single Container",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Check pull response for errors\nconst stdout = $input.item.json.stdout || '';\nconst prevData = $('Parse Container Config').item.json;\n\n// Docker pull streams JSON objects, check for error messages\nif (stdout.includes('\"message\"') && (stdout.includes('toomanyrequests') || stdout.includes('error') || stdout.includes('denied'))) {\n // Extract error message\n let errorMsg = 'Pull failed';\n try {\n const match = stdout.match(/\"message\"\\s*:\\s*\"([^\"]+)\"/);\n if (match) errorMsg = match[1];\n } catch (e) {}\n \n return {\n json: {\n pullError: true,\n errorMessage: errorMsg.substring(0, 100),\n ...prevData\n }\n };\n}\n\n// Success - pass through data\nreturn {\n json: {\n pullError: false,\n ...prevData\n }\n};"
|
"jsCode": "// Capture pre-update state from input\nconst data = $input.item.json;\nconst triggerData = $('When executed by another workflow').item.json;\n\n// Check if we have container data already (from Resolve path) or need to extract (from direct ID path)\nlet unraidId, containerName, currentImageId, currentImage;\n\nif (data.unraidId) {\n // From Resolve Container ID path\n unraidId = data.unraidId;\n containerName = data.containerName;\n currentImageId = data.currentImageId;\n currentImage = data.currentImage;\n} else if (data.Id) {\n // From Query Single Container path (normalized)\n unraidId = data.Id;\n containerName = (data.Names?.[0] || '').replace(/^\\//, '');\n currentImageId = data.imageId || '';\n currentImage = data.image || '';\n} else {\n throw new Error('No container data found');\n}\n\nreturn {\n json: {\n unraidId,\n containerName,\n currentImageId,\n currentImage,\n chatId: triggerData.chatId,\n messageId: triggerData.messageId,\n responseMode: triggerData.responseMode,\n correlationId: triggerData.correlationId || ''\n }\n};\n"
|
||||||
},
|
},
|
||||||
"id": "code-check-pull",
|
"id": "code-capture-state",
|
||||||
"name": "Check Pull Response",
|
"name": "Capture Pre-Update State",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
920,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Build GraphQL updateContainer mutation\nconst data = $input.item.json;\nreturn {\n json: {\n ...data,\n query: `mutation { docker { updateContainer(id: \"${data.unraidId}\") { id state image imageId } } }`\n }\n};\n"
|
||||||
|
},
|
||||||
|
"id": "code-build-mutation",
|
||||||
|
"name": "Build Update Mutation",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
@@ -161,6 +222,54 @@
|
|||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ $env.UNRAID_HOST }}/graphql",
|
||||||
|
"authentication": "none",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-api-key",
|
||||||
|
"value": "={{ $env.UNRAID_API_KEY }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={{ {\"query\": $json.query} }}",
|
||||||
|
"options": {
|
||||||
|
"timeout": 60000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "http-update-container",
|
||||||
|
"name": "Update Container",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [
|
||||||
|
1320,
|
||||||
|
300
|
||||||
|
],
|
||||||
|
"onError": "continueRegularOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Handle updateContainer mutation response\nconst response = $input.item.json;\nconst prevData = $('Capture Pre-Update State').item.json;\n\n// Check for GraphQL errors\nif (response.errors) {\n const error = response.errors[0];\n return {\n json: {\n success: false,\n error: true,\n errorMessage: error.message || 'Update failed',\n ...prevData\n }\n };\n}\n\n// Extract updated container from response\nconst updated = response.data?.docker?.updateContainer;\nif (!updated) {\n return {\n json: {\n success: false,\n error: true,\n errorMessage: 'No response from update mutation',\n ...prevData\n }\n };\n}\n\n// Compare imageId to determine if update happened\nconst newImageId = updated.imageId || '';\nconst oldImageId = prevData.currentImageId || '';\nconst wasUpdated = (newImageId !== oldImageId);\n\nreturn {\n json: {\n success: true,\n needsUpdate: wasUpdated,\n updated: wasUpdated,\n containerName: prevData.containerName,\n currentVersion: prevData.currentImage,\n newVersion: updated.image,\n currentImageId: oldImageId,\n newImageId: newImageId,\n chatId: prevData.chatId,\n messageId: prevData.messageId,\n responseMode: prevData.responseMode,\n correlationId: prevData.correlationId\n }\n};\n"
|
||||||
|
},
|
||||||
|
"id": "code-handle-response",
|
||||||
|
"name": "Handle Update Response",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1520,
|
||||||
|
300
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"conditions": {
|
"conditions": {
|
||||||
@@ -171,12 +280,12 @@
|
|||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "pull-success",
|
"id": "is-success",
|
||||||
"leftValue": "={{ $json.pullError }}",
|
"leftValue": "={{ $json.error }}",
|
||||||
"rightValue": false,
|
"rightValue": true,
|
||||||
"operator": {
|
"operator": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"operation": "equals"
|
"operation": "notEquals"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -184,45 +293,15 @@
|
|||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "if-pull-success",
|
"id": "if-update-success",
|
||||||
"name": "Check Pull Success",
|
"name": "Check Update Success",
|
||||||
"type": "n8n-nodes-base.if",
|
"type": "n8n-nodes-base.if",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
1340,
|
1720,
|
||||||
300
|
300
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "GET",
|
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/images/{{ encodeURIComponent($json.imageName) }}/json",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-inspect-new-image",
|
|
||||||
"name": "Inspect New Image",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
1560,
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Compare old and new image IDs\nconst newImage = $input.item.json;\nconst prevData = $('Check Pull Success').item.json;\nconst currentImageId = prevData.currentImageId;\n\nconst newImageId = newImage.Id;\n\nif (currentImageId === newImageId) {\n // No update needed\n return {\n json: {\n needsUpdate: false,\n ...prevData\n }\n };\n}\n\n// Extract new version from labels\nconst labels = newImage.Config?.Labels || {};\nconst newVersion = labels['org.opencontainers.image.version']\n || labels['version']\n || newImageId.substring(7, 19);\n\nreturn {\n json: {\n needsUpdate: true,\n newImageId,\n newVersion,\n ...prevData\n }\n};"
|
|
||||||
},
|
|
||||||
"id": "code-compare-digests",
|
|
||||||
"name": "Compare Digests",
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
1780,
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"conditions": {
|
"conditions": {
|
||||||
@@ -233,7 +312,7 @@
|
|||||||
},
|
},
|
||||||
"conditions": [
|
"conditions": [
|
||||||
{
|
{
|
||||||
"id": "needs-update",
|
"id": "was-updated",
|
||||||
"leftValue": "={{ $json.needsUpdate }}",
|
"leftValue": "={{ $json.needsUpdate }}",
|
||||||
"rightValue": true,
|
"rightValue": true,
|
||||||
"operator": {
|
"operator": {
|
||||||
@@ -246,126 +325,26 @@
|
|||||||
},
|
},
|
||||||
"options": {}
|
"options": {}
|
||||||
},
|
},
|
||||||
"id": "if-needs-update",
|
"id": "if-was-updated",
|
||||||
"name": "Check If Update Needed",
|
"name": "Check If Updated",
|
||||||
"type": "n8n-nodes-base.if",
|
"type": "n8n-nodes-base.if",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
2000,
|
1920,
|
||||||
200
|
200
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"method": "POST",
|
"jsCode": "// Format update success result\nconst data = $('Handle Update Response').item.json;\nconst containerName = data.containerName;\nconst currentVersion = data.currentVersion;\nconst newVersion = data.newVersion;\n\nconst message = `<b>${containerName}</b> updated: ${currentVersion} \\u2192 ${newVersion}`;\n\nreturn {\n json: {\n success: true,\n updated: true,\n message,\n oldDigest: currentVersion,\n newDigest: newVersion,\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode,\n containerName: containerName,\n correlationId: data.correlationId || ''\n }\n};\n"
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/containers/{{ $json.containerId }}/stop?t=10",
|
|
||||||
"options": {
|
|
||||||
"timeout": 15000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-stop-container",
|
|
||||||
"name": "Stop Container",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
2220,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"onError": "continueRegularOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/containers/{{ $('Check If Update Needed').item.json.containerId }}",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-remove-container",
|
|
||||||
"name": "Remove Container",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
2440,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"onError": "continueRegularOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Build container create request body from saved config\nconst prevData = $('Check If Update Needed').item.json;\nconst config = prevData.containerConfig;\nconst hostConfig = prevData.hostConfig;\nconst networkSettings = prevData.networkSettings;\nconst containerName = prevData.containerName;\n\n// Build NetworkingConfig from NetworkSettings\nconst networks = {};\nfor (const [name, netConfig] of Object.entries(networkSettings.Networks || {})) {\n networks[name] = {\n IPAMConfig: netConfig.IPAMConfig,\n Links: netConfig.Links,\n Aliases: netConfig.Aliases\n };\n}\n\nconst createBody = {\n ...config,\n HostConfig: hostConfig,\n NetworkingConfig: {\n EndpointsConfig: networks\n }\n};\n\n// Remove fields that shouldn't be in create request\ndelete createBody.Hostname;\ndelete createBody.Domainname;\n\nreturn {\n json: {\n createBody,\n containerName,\n ...prevData\n }\n};"
|
|
||||||
},
|
|
||||||
"id": "code-build-create-body",
|
|
||||||
"name": "Build Create Body",
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
2660,
|
|
||||||
100
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/containers/create?name={{ encodeURIComponent($json.containerName) }}",
|
|
||||||
"sendBody": true,
|
|
||||||
"specifyBody": "json",
|
|
||||||
"jsonBody": "={{ JSON.stringify($json.createBody) }}",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-create-container",
|
|
||||||
"name": "Create Container",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
2880,
|
|
||||||
100
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Parse create response and extract new container ID\nconst response = $input.item.json;\nconst prevData = $('Build Create Body').item.json;\n\nif (response.message) {\n // Error response from Docker\n return {\n json: {\n createError: true,\n errorMessage: response.message,\n ...prevData\n }\n };\n}\n\nreturn {\n json: {\n createError: false,\n newContainerId: response.Id,\n ...prevData\n }\n};"
|
|
||||||
},
|
|
||||||
"id": "code-parse-create",
|
|
||||||
"name": "Parse Create Response",
|
|
||||||
"type": "n8n-nodes-base.code",
|
|
||||||
"typeVersion": 2,
|
|
||||||
"position": [
|
|
||||||
3100,
|
|
||||||
100
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "POST",
|
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/containers/{{ $json.newContainerId }}/start",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-start-container",
|
|
||||||
"name": "Start Container",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
3320,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"onError": "continueRegularOutput"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"jsCode": "// Format update success result and clean up old image\nconst prevData = $('Parse Create Response').item.json;\nconst containerName = prevData.containerName;\nconst currentVersion = prevData.currentVersion;\nconst newVersion = prevData.newVersion;\nconst currentImageId = prevData.currentImageId;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `<b>${containerName}</b> updated: ${currentVersion} \\u2192 ${newVersion}`;\n\nreturn {\n json: {\n success: true,\n updated: true,\n message,\n oldDigest: currentVersion,\n newDigest: newVersion,\n currentImageId,\n chatId,\n messageId,\n responseMode,\n containerName,\n correlationId\n }\n};"
|
|
||||||
},
|
},
|
||||||
"id": "code-format-success",
|
"id": "code-format-success",
|
||||||
"name": "Format Update Success",
|
"name": "Format Update Success",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
3540,
|
1960,
|
||||||
100
|
200
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -429,8 +408,8 @@
|
|||||||
"type": "n8n-nodes-base.switch",
|
"type": "n8n-nodes-base.switch",
|
||||||
"typeVersion": 3.2,
|
"typeVersion": 3.2,
|
||||||
"position": [
|
"position": [
|
||||||
3760,
|
2180,
|
||||||
100
|
200
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -447,8 +426,8 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
3980,
|
2400,
|
||||||
0
|
100
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -466,8 +445,8 @@
|
|||||||
"type": "n8n-nodes-base.telegram",
|
"type": "n8n-nodes-base.telegram",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
"position": [
|
"position": [
|
||||||
3980,
|
2400,
|
||||||
200
|
300
|
||||||
],
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": {
|
||||||
@@ -476,24 +455,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"parameters": {
|
|
||||||
"method": "DELETE",
|
|
||||||
"url": "=http://docker-socket-proxy:2375/v1.47/images/{{ $('Format Update Success').item.json.currentImageId }}?force=false",
|
|
||||||
"options": {
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "http-remove-old-image-success",
|
|
||||||
"name": "Remove Old Image (Success)",
|
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
|
||||||
"typeVersion": 4.2,
|
|
||||||
"position": [
|
|
||||||
4200,
|
|
||||||
100
|
|
||||||
],
|
|
||||||
"onError": "continueRegularOutput"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Return final success result\nconst data = $('Format Update Success').item.json;\nreturn {\n json: {\n success: true,\n updated: true,\n message: data.message,\n oldDigest: data.oldDigest,\n newDigest: data.newDigest,\n correlationId: data.correlationId || ''\n }\n};"
|
"jsCode": "// Return final success result\nconst data = $('Format Update Success').item.json;\nreturn {\n json: {\n success: true,\n updated: true,\n message: data.message,\n oldDigest: data.oldDigest,\n newDigest: data.newDigest,\n correlationId: data.correlationId || ''\n }\n};"
|
||||||
@@ -503,21 +464,21 @@
|
|||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
4420,
|
2620,
|
||||||
100
|
200
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format 'already up to date' result\nconst prevData = $('Check If Update Needed').item.json;\nconst containerName = prevData.containerName;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `<b>${containerName}</b> is already up to date`;\n\nreturn {\n json: {\n success: true,\n updated: false,\n message,\n chatId,\n messageId,\n responseMode,\n containerName,\n correlationId\n }\n};"
|
"jsCode": "// Format 'already up to date' result\nconst data = $('Handle Update Response').item.json;\nconst containerName = data.containerName;\n\nconst message = `<b>${containerName}</b> is already up to date`;\n\nreturn {\n json: {\n success: true,\n updated: false,\n message,\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode,\n containerName: containerName,\n correlationId: data.correlationId || ''\n }\n};\n"
|
||||||
},
|
},
|
||||||
"id": "code-format-no-update",
|
"id": "code-format-no-update",
|
||||||
"name": "Format No Update Needed",
|
"name": "Format No Update Needed",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
2220,
|
1960,
|
||||||
300
|
400
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -581,8 +542,8 @@
|
|||||||
"type": "n8n-nodes-base.switch",
|
"type": "n8n-nodes-base.switch",
|
||||||
"typeVersion": 3.2,
|
"typeVersion": 3.2,
|
||||||
"position": [
|
"position": [
|
||||||
2440,
|
2180,
|
||||||
300
|
400
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -599,8 +560,8 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
2660,
|
2400,
|
||||||
200
|
400
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -618,8 +579,8 @@
|
|||||||
"type": "n8n-nodes-base.telegram",
|
"type": "n8n-nodes-base.telegram",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
"position": [
|
"position": [
|
||||||
2660,
|
2400,
|
||||||
400
|
500
|
||||||
],
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": {
|
||||||
@@ -637,21 +598,21 @@
|
|||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
2880,
|
2620,
|
||||||
300
|
400
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "// Format pull error result\nconst prevData = $('Check Pull Success').item.json;\nconst containerName = prevData.containerName;\nconst errorMessage = prevData.errorMessage;\nconst chatId = prevData.chatId;\nconst messageId = prevData.messageId;\nconst responseMode = prevData.responseMode;\nconst correlationId = prevData.correlationId || '';\n\nconst message = `Failed to update <b>${containerName}</b>: ${errorMessage}`;\n\nreturn {\n json: {\n success: false,\n updated: false,\n message,\n error: {\n workflow: 'n8n-update',\n node: 'Pull Image',\n message: errorMessage,\n httpCode: null,\n rawResponse: errorMessage\n },\n correlationId,\n chatId,\n messageId,\n responseMode,\n containerName\n }\n};"
|
"jsCode": "// Format update error result\nconst data = $('Handle Update Response').item.json;\nconst containerName = data.containerName;\nconst errorMessage = data.errorMessage;\n\nconst message = `Failed to update <b>${containerName}</b>: ${errorMessage}`;\n\nreturn {\n json: {\n success: false,\n updated: false,\n message,\n error: {\n workflow: 'n8n-update',\n node: 'Update Container',\n message: errorMessage,\n httpCode: null,\n rawResponse: errorMessage\n },\n correlationId: data.correlationId || '',\n chatId: data.chatId,\n messageId: data.messageId,\n responseMode: data.responseMode,\n containerName: containerName\n }\n};\n"
|
||||||
},
|
},
|
||||||
"id": "code-format-pull-error",
|
"id": "code-format-pull-error",
|
||||||
"name": "Format Pull Error",
|
"name": "Format Update Error",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
1560,
|
1540,
|
||||||
400
|
500
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -715,8 +676,8 @@
|
|||||||
"type": "n8n-nodes-base.switch",
|
"type": "n8n-nodes-base.switch",
|
||||||
"typeVersion": 3.2,
|
"typeVersion": 3.2,
|
||||||
"position": [
|
"position": [
|
||||||
1780,
|
1760,
|
||||||
400
|
500
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -733,8 +694,8 @@
|
|||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [
|
"position": [
|
||||||
2000,
|
1980,
|
||||||
300
|
500
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -752,8 +713,8 @@
|
|||||||
"type": "n8n-nodes-base.telegram",
|
"type": "n8n-nodes-base.telegram",
|
||||||
"typeVersion": 1.2,
|
"typeVersion": 1.2,
|
||||||
"position": [
|
"position": [
|
||||||
2000,
|
1980,
|
||||||
500
|
600
|
||||||
],
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"telegramApi": {
|
"telegramApi": {
|
||||||
@@ -771,8 +732,8 @@
|
|||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [
|
"position": [
|
||||||
2220,
|
2200,
|
||||||
400
|
500
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -792,21 +753,65 @@
|
|||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Inspect Container",
|
"node": "Query Single Container",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Get All Containers",
|
"node": "Query All Containers",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Get All Containers": {
|
"Query Single Container": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Normalize Single Container",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Normalize Single Container": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Capture Pre-Update State",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Query All Containers": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Normalize GraphQL Response",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Normalize GraphQL Response": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Update Container ID Registry",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Update Container ID Registry": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -821,102 +826,62 @@
|
|||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Inspect Container",
|
"node": "Capture Pre-Update State",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Inspect Container": {
|
"Capture Pre-Update State": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Parse Container Config",
|
"node": "Build Update Mutation",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Parse Container Config": {
|
"Build Update Mutation": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Pull Image",
|
"node": "Update Container",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Pull Image": {
|
"Update Container": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Check Pull Response",
|
"node": "Handle Update Response",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Check Pull Response": {
|
"Handle Update Response": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Check Pull Success",
|
"node": "Check Update Success",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Check Pull Success": {
|
"Check If Updated": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Inspect New Image",
|
"node": "Format Update Success",
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Format Pull Error",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Inspect New Image": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Compare Digests",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Compare Digests": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Check If Update Needed",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Check If Update Needed": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Stop Container",
|
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
@@ -930,72 +895,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Stop Container": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Remove Container",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Remove Container": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Build Create Body",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Build Create Body": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Create Container",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Create Container": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Parse Create Response",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Parse Create Response": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Start Container",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Start Container": {
|
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Format Update Success",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Format Update Success": {
|
"Format Update Success": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
@@ -1018,7 +917,7 @@
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Remove Old Image (Success)",
|
"node": "Return Success",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
@@ -1036,7 +935,7 @@
|
|||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"node": "Remove Old Image (Success)",
|
"node": "Return Success",
|
||||||
"type": "main",
|
"type": "main",
|
||||||
"index": 0
|
"index": 0
|
||||||
}
|
}
|
||||||
@@ -1044,17 +943,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Send Text Success": {
|
"Send Text Success": {
|
||||||
"main": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"node": "Remove Old Image (Success)",
|
|
||||||
"type": "main",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Remove Old Image (Success)": {
|
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -1123,7 +1011,7 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Format Pull Error": {
|
"Format Update Error": {
|
||||||
"main": [
|
"main": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -1180,6 +1068,24 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"Check Update Success": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Check If Updated",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Format Update Error",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
Reference in New Issue
Block a user