n8n-nodes-docker-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +278 -0
- package/dist/credentials/DockerApi.credentials.d.ts +7 -0
- package/dist/credentials/DockerApi.credentials.js +125 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/nodes/Docker/Docker/docker.svg +25 -0
- package/dist/nodes/Docker/Docker.node.d.ts +5 -0
- package/dist/nodes/Docker/Docker.node.js +73 -0
- package/dist/nodes/Docker/actions/container/getLogs.operation.d.ts +3 -0
- package/dist/nodes/Docker/actions/container/getLogs.operation.js +82 -0
- package/dist/nodes/Docker/actions/container/list.operation.d.ts +3 -0
- package/dist/nodes/Docker/actions/container/list.operation.js +40 -0
- package/dist/nodes/Docker/actions/container/start.operation.d.ts +3 -0
- package/dist/nodes/Docker/actions/container/start.operation.js +21 -0
- package/dist/nodes/Docker/actions/container/stop.operation.d.ts +3 -0
- package/dist/nodes/Docker/actions/container/stop.operation.js +38 -0
- package/dist/nodes/Docker/actions/index.d.ts +3 -0
- package/dist/nodes/Docker/actions/index.js +25 -0
- package/dist/nodes/Docker/descriptions/container/getLogs.description.d.ts +2 -0
- package/dist/nodes/Docker/descriptions/container/getLogs.description.js +64 -0
- package/dist/nodes/Docker/descriptions/container/list.description.d.ts +2 -0
- package/dist/nodes/Docker/descriptions/container/list.description.js +66 -0
- package/dist/nodes/Docker/descriptions/container/start.description.d.ts +2 -0
- package/dist/nodes/Docker/descriptions/container/start.description.js +20 -0
- package/dist/nodes/Docker/descriptions/container/stop.description.d.ts +2 -0
- package/dist/nodes/Docker/descriptions/container/stop.description.js +46 -0
- package/dist/nodes/Docker/descriptions/index.d.ts +3 -0
- package/dist/nodes/Docker/descriptions/index.js +53 -0
- package/dist/nodes/Docker/helpers/accessGuard.d.ts +2 -0
- package/dist/nodes/Docker/helpers/accessGuard.js +13 -0
- package/dist/nodes/Docker/helpers/errorHandler.d.ts +1 -0
- package/dist/nodes/Docker/helpers/errorHandler.js +36 -0
- package/dist/nodes/Docker/helpers/normalizeContainer.d.ts +16 -0
- package/dist/nodes/Docker/helpers/normalizeContainer.js +71 -0
- package/dist/nodes/Docker/helpers/resolveContainer.d.ts +11 -0
- package/dist/nodes/Docker/helpers/resolveContainer.js +32 -0
- package/dist/utils/dockerClient.d.ts +3 -0
- package/dist/utils/dockerClient.js +31 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 n8n
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# ๐ณ n8n-nodes-docker-api
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
> Interact with Docker via direct API (no Portainer required) โ manage containers, stream logs, and automate infrastructure with ease.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## โจ Why this node?
|
|
13
|
+
|
|
14
|
+
Managing Docker via raw HTTP calls in n8n is painful and error-prone.
|
|
15
|
+
|
|
16
|
+
This node gives you:
|
|
17
|
+
|
|
18
|
+
* ๐ **Clean container listing** (normalized output)
|
|
19
|
+
* ๐ **Readable logs** (stdout/stderr separated, no binary noise)
|
|
20
|
+
* โก **Simple control** (start/stop containers safely)
|
|
21
|
+
* ๐ **Built-in access control** (read-only vs full control)
|
|
22
|
+
|
|
23
|
+
๐ Turn Docker into a **first-class automation tool inside n8n**
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ๐ Quick Start
|
|
28
|
+
|
|
29
|
+
1. Install the node:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install n8n-nodes-docker-api
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. Add **Docker API credentials**
|
|
36
|
+
3. Add the **Docker node** to your workflow
|
|
37
|
+
4. Choose an operation and execute
|
|
38
|
+
|
|
39
|
+
โ
No manual API calls needed
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ๐ฆ Installation
|
|
44
|
+
|
|
45
|
+
### Community Nodes (Recommended)
|
|
46
|
+
|
|
47
|
+
1. Go to **Settings โ Community Nodes**
|
|
48
|
+
2. Install: `n8n-nodes-docker-api`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### Manual Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install n8n-nodes-docker-api
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ๐งช Usage
|
|
61
|
+
|
|
62
|
+
### Docker Node in n8n
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
1. Add the **Docker node**
|
|
67
|
+
2. Select an operation:
|
|
68
|
+
|
|
69
|
+
* List Containers
|
|
70
|
+
* Get Container Logs
|
|
71
|
+
* Start Container
|
|
72
|
+
* Stop Container
|
|
73
|
+
3. Configure credentials
|
|
74
|
+
4. Execute the node
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Example: List Containers
|
|
79
|
+
|
|
80
|
+
* Operation: `List Containers`
|
|
81
|
+
* Optional filters:
|
|
82
|
+
|
|
83
|
+
* Name
|
|
84
|
+
* Status
|
|
85
|
+
* Show all containers
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## ๐ Credentials
|
|
90
|
+
|
|
91
|
+
### Credentials UI in n8n
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Connection Modes
|
|
98
|
+
|
|
99
|
+
#### ๐ข Unix Socket (Local)
|
|
100
|
+
|
|
101
|
+
* Default for local Docker setups
|
|
102
|
+
* Path: `/var/run/docker.sock`
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
#### ๐ TCP (Remote)
|
|
107
|
+
|
|
108
|
+
* Connect to remote Docker daemon
|
|
109
|
+
* Example:
|
|
110
|
+
|
|
111
|
+
* Host: `192.168.1.10`
|
|
112
|
+
* Port: `2375`
|
|
113
|
+
|
|
114
|
+
โ ๏ธ Requires Docker daemon configured with:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
-H tcp://0.0.0.0:2375
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
#### ๐ TLS (Coming in v2)
|
|
123
|
+
|
|
124
|
+
* Secure remote connection using certificates
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Access Modes
|
|
129
|
+
|
|
130
|
+
#### ๐ก Read Only (Recommended)
|
|
131
|
+
|
|
132
|
+
* โ
List containers
|
|
133
|
+
* โ
Get logs
|
|
134
|
+
* โ Cannot start/stop containers
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
#### ๐ด Full Control
|
|
139
|
+
|
|
140
|
+
* โ
All operations enabled
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## ๐ Supported Operations (v1)
|
|
145
|
+
|
|
146
|
+
| Operation | Description | Access |
|
|
147
|
+
| ------------------ | --------------------------------------- | ------------ |
|
|
148
|
+
| List Containers | Get containers with normalized output | Read Only |
|
|
149
|
+
| Get Container Logs | Logs with stream filtering & timestamps | Read Only |
|
|
150
|
+
| Start Container | Start stopped containers | Full Control |
|
|
151
|
+
| Stop Container | Stop running containers (with dry run) | Full Control |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## ๐ฅ Real Use Cases
|
|
156
|
+
|
|
157
|
+
### โป๏ธ Self-Healing Containers
|
|
158
|
+
|
|
159
|
+
Automatically restart crashed services:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
List Containers โ IF (status != running) โ Start Container
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### ๐จ Log-Based Alerting
|
|
168
|
+
|
|
169
|
+
Detect errors and notify:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Get Logs โ Search "ERROR" โ Send Slack alert
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### ๐ Container Monitoring
|
|
178
|
+
|
|
179
|
+
Track system health:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Schedule โ List Containers โ Aggregate โ Report
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## ๐ง Output Examples
|
|
188
|
+
|
|
189
|
+
### List Containers
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"id": "3025272c7592...",
|
|
194
|
+
"shortId": "3025272c7592",
|
|
195
|
+
"name": "qdrant",
|
|
196
|
+
"image": "qdrant/qdrant",
|
|
197
|
+
"status": "running",
|
|
198
|
+
"createdAt": "2026-03-13T23:19:38.000Z"
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### Logs
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"message": "Server started",
|
|
209
|
+
"stream": "stdout"
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ๐ Security
|
|
216
|
+
|
|
217
|
+
โ ๏ธ Docker access = **host-level control**
|
|
218
|
+
|
|
219
|
+
### Best Practices
|
|
220
|
+
|
|
221
|
+
* Use **Read Only mode** unless needed
|
|
222
|
+
* Avoid exposing Docker over public TCP
|
|
223
|
+
* Use a proxy like:
|
|
224
|
+
|
|
225
|
+
* `tecnativa/docker-socket-proxy`
|
|
226
|
+
* Restrict network access
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## ๐งช Testing
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
npm test
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## ๐บ๏ธ Roadmap
|
|
239
|
+
|
|
240
|
+
### v2
|
|
241
|
+
|
|
242
|
+
* Restart Container
|
|
243
|
+
* Remove Container
|
|
244
|
+
* Image operations (list/pull/remove)
|
|
245
|
+
* TLS support
|
|
246
|
+
* Container autocomplete
|
|
247
|
+
|
|
248
|
+
### v3
|
|
249
|
+
|
|
250
|
+
* Run container (ephemeral jobs)
|
|
251
|
+
* Execute commands in container
|
|
252
|
+
* Container stats
|
|
253
|
+
|
|
254
|
+
### v4
|
|
255
|
+
|
|
256
|
+
* Docker Trigger node (events)
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## โญ Contributing
|
|
261
|
+
|
|
262
|
+
PRs and ideas are welcome โ especially for new operations and improvements.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## ๐ License
|
|
267
|
+
|
|
268
|
+
MIT
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## ๐ฌ Support
|
|
273
|
+
|
|
274
|
+
Open an issue on GitHub for:
|
|
275
|
+
|
|
276
|
+
* Bugs
|
|
277
|
+
* Feature requests
|
|
278
|
+
* Questions
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DockerApi = void 0;
|
|
4
|
+
class DockerApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'dockerApi';
|
|
7
|
+
this.displayName = 'Docker API';
|
|
8
|
+
this.documentationUrl = 'https://github.com/YOUR_USERNAME/n8n-nodes-docker';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Connection Mode',
|
|
12
|
+
name: 'authMode',
|
|
13
|
+
type: 'options',
|
|
14
|
+
options: [
|
|
15
|
+
{
|
|
16
|
+
name: 'Unix Socket (Local)',
|
|
17
|
+
value: 'socket',
|
|
18
|
+
description: 'Connect to Docker via local Unix socket',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'TCP (Remote)',
|
|
22
|
+
value: 'tcp',
|
|
23
|
+
description: 'Connect to a remote Docker daemon over TCP',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'TLS (Secure Remote)',
|
|
27
|
+
value: 'tls',
|
|
28
|
+
description: 'Connect to a remote Docker daemon with TLS authentication',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
default: 'socket',
|
|
32
|
+
},
|
|
33
|
+
// --- Socket mode fields ---
|
|
34
|
+
{
|
|
35
|
+
displayName: 'Socket Path',
|
|
36
|
+
name: 'socketPath',
|
|
37
|
+
type: 'string',
|
|
38
|
+
default: '/var/run/docker.sock',
|
|
39
|
+
displayOptions: { show: { authMode: ['socket'] } },
|
|
40
|
+
description: 'Path to the Docker Unix socket',
|
|
41
|
+
},
|
|
42
|
+
// --- TCP mode fields ---
|
|
43
|
+
{
|
|
44
|
+
displayName: 'Host',
|
|
45
|
+
name: 'host',
|
|
46
|
+
type: 'string',
|
|
47
|
+
default: '',
|
|
48
|
+
placeholder: '192.168.1.100',
|
|
49
|
+
displayOptions: { show: { authMode: ['tcp', 'tls'] } },
|
|
50
|
+
description: 'IP address or hostname of the Docker host',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
displayName: 'Port',
|
|
54
|
+
name: 'port',
|
|
55
|
+
type: 'number',
|
|
56
|
+
default: 2375,
|
|
57
|
+
displayOptions: { show: { authMode: ['tcp'] } },
|
|
58
|
+
description: 'Docker daemon TCP port (default: 2375)',
|
|
59
|
+
},
|
|
60
|
+
// --- TLS mode fields (v2 implementation, schema defined now) ---
|
|
61
|
+
{
|
|
62
|
+
displayName: 'TLS Port',
|
|
63
|
+
name: 'tlsPort',
|
|
64
|
+
type: 'number',
|
|
65
|
+
default: 2376,
|
|
66
|
+
displayOptions: { show: { authMode: ['tls'] } },
|
|
67
|
+
description: 'Docker daemon TLS port (default: 2376)',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
displayName: 'CA Certificate',
|
|
71
|
+
name: 'ca',
|
|
72
|
+
type: 'string',
|
|
73
|
+
typeOptions: { rows: 4 },
|
|
74
|
+
default: '',
|
|
75
|
+
displayOptions: { show: { authMode: ['tls'] } },
|
|
76
|
+
description: 'TLS CA certificate (PEM format)',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
displayName: 'Client Certificate',
|
|
80
|
+
name: 'cert',
|
|
81
|
+
type: 'string',
|
|
82
|
+
typeOptions: { rows: 4 },
|
|
83
|
+
default: '',
|
|
84
|
+
displayOptions: { show: { authMode: ['tls'] } },
|
|
85
|
+
description: 'TLS client certificate (PEM format)',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
displayName: 'Client Key',
|
|
89
|
+
name: 'clientKey',
|
|
90
|
+
type: 'string',
|
|
91
|
+
typeOptions: { rows: 4, password: true },
|
|
92
|
+
default: '',
|
|
93
|
+
displayOptions: { show: { authMode: ['tls'] } },
|
|
94
|
+
description: 'TLS client private key (PEM format)',
|
|
95
|
+
},
|
|
96
|
+
// --- Access control ---
|
|
97
|
+
{
|
|
98
|
+
displayName: 'Access Mode',
|
|
99
|
+
name: 'accessMode',
|
|
100
|
+
type: 'options',
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
name: 'Read Only',
|
|
104
|
+
value: 'readonly',
|
|
105
|
+
description: 'Only list and inspect operations are allowed',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'Full Control',
|
|
109
|
+
value: 'full',
|
|
110
|
+
description: 'All operations including start, stop, and remove are allowed',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
default: 'readonly',
|
|
114
|
+
description: 'Controls which operations this credential permits',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
displayName: '',
|
|
118
|
+
name: 'securityNotice',
|
|
119
|
+
type: 'notice',
|
|
120
|
+
default: 'โ ๏ธ This credential connects to the Docker daemon. Full Control mode grants the ability to start, stop, and remove containers. Use Read Only mode unless write access is explicitly required.',
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.DockerApi = DockerApi;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 340 268">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.1.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 136) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1 {
|
|
11
|
+
fill: #2560ff;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st2 {
|
|
15
|
+
clip-path: url(#clippath);
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
<clipPath id="clippath">
|
|
19
|
+
<rect class="st0" width="339.5" height="268"/>
|
|
20
|
+
</clipPath>
|
|
21
|
+
</defs>
|
|
22
|
+
<g class="st2">
|
|
23
|
+
<path class="st1" d="M334,110.1c-8.3-5.6-30.2-8-46.1-3.7-.9-15.8-9-29.2-24-40.8l-5.5-3.7-3.7,5.6c-7.2,11-10.3,25.7-9.2,39,.8,8.2,3.7,17.4,9.2,24.1-20.7,12-39.8,9.3-124.3,9.3H0c-.4,19.1,2.7,55.8,26,85.6,2.6,3.3,5.4,6.5,8.5,9.6,19,19,47.6,32.9,90.5,33,65.4,0,121.4-35.3,155.5-120.8,11.2.2,40.8,2,55.3-26,.4-.5,3.7-7.4,3.7-7.4l-5.5-3.7h0ZM85.2,92.7h-36.7v36.7h36.7v-36.7ZM132.6,92.7h-36.7v36.7h36.7v-36.7ZM179.9,92.7h-36.7v36.7h36.7v-36.7ZM227.3,92.7h-36.7v36.7h36.7v-36.7ZM37.8,92.7H1.1v36.7h36.7v-36.7ZM85.2,46.3h-36.7v36.7h36.7v-36.7ZM132.6,46.3h-36.7v36.7h36.7v-36.7ZM179.9,46.3h-36.7v36.7h36.7v-36.7ZM179.9,0h-36.7v36.7h36.7V0Z"/>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Docker = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const dockerClient_1 = require("../../utils/dockerClient");
|
|
6
|
+
const descriptions_1 = require("./descriptions");
|
|
7
|
+
const actions_1 = require("./actions");
|
|
8
|
+
const accessGuard_1 = require("./helpers/accessGuard");
|
|
9
|
+
class Docker {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.description = {
|
|
12
|
+
displayName: 'Docker API',
|
|
13
|
+
name: 'docker',
|
|
14
|
+
icon: 'file:docker.svg',
|
|
15
|
+
group: ['transform'],
|
|
16
|
+
version: 1,
|
|
17
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
18
|
+
description: 'Interact with Docker via direct API (no Portainer required)',
|
|
19
|
+
defaults: {
|
|
20
|
+
name: 'Docker API',
|
|
21
|
+
},
|
|
22
|
+
inputs: ['main'],
|
|
23
|
+
outputs: ['main'],
|
|
24
|
+
credentials: [
|
|
25
|
+
{
|
|
26
|
+
name: 'dockerApi',
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
properties: [
|
|
31
|
+
{
|
|
32
|
+
displayName: 'Resource',
|
|
33
|
+
name: 'resource',
|
|
34
|
+
type: 'options',
|
|
35
|
+
noDataExpression: true,
|
|
36
|
+
options: [
|
|
37
|
+
{
|
|
38
|
+
name: 'Container',
|
|
39
|
+
value: 'container',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
default: 'container',
|
|
43
|
+
},
|
|
44
|
+
...descriptions_1.containerOperations,
|
|
45
|
+
...descriptions_1.containerFields,
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async execute() {
|
|
50
|
+
const items = this.getInputData();
|
|
51
|
+
const credentials = await this.getCredentials('dockerApi');
|
|
52
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
53
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
54
|
+
(0, accessGuard_1.enforceAccessMode)(credentials, operation);
|
|
55
|
+
const docker = (0, dockerClient_1.createDockerClient)(credentials);
|
|
56
|
+
const returnData = [];
|
|
57
|
+
for (let i = 0; i < items.length; i++) {
|
|
58
|
+
try {
|
|
59
|
+
const result = await actions_1.executeContainerOperation.call(this, docker, operation, i);
|
|
60
|
+
returnData.push(...result);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (this.continueOnFail()) {
|
|
64
|
+
returnData.push({ json: { error: error.message }, pairedItem: i });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex: i });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [returnData];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.Docker = Docker;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getContainerLogs = getContainerLogs;
|
|
4
|
+
const errorHandler_1 = require("../../helpers/errorHandler");
|
|
5
|
+
const resolveContainer_1 = require("../../helpers/resolveContainer");
|
|
6
|
+
/**
|
|
7
|
+
* Demultiplexes Docker log buffer format.
|
|
8
|
+
* Docker logs use an 8-byte header: [stream type (1 byte)][0,0,0,0][size (4 bytes)][payload]
|
|
9
|
+
* Stream type: 1 = stdout, 2 = stderr
|
|
10
|
+
*/
|
|
11
|
+
function demuxDockerLogs(buffer) {
|
|
12
|
+
const logs = [];
|
|
13
|
+
let offset = 0;
|
|
14
|
+
while (offset < buffer.length) {
|
|
15
|
+
// Need at least 8 bytes for header
|
|
16
|
+
if (offset + 8 > buffer.length) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
const streamType = buffer[offset];
|
|
20
|
+
// Bytes 1-4 are zeros
|
|
21
|
+
// Bytes 5-8 are the payload size (big-endian uint32)
|
|
22
|
+
const payloadSize = buffer.readUInt32BE(offset + 4);
|
|
23
|
+
// Validate we have enough data for the payload
|
|
24
|
+
if (offset + 8 + payloadSize > buffer.length) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
const payload = buffer.slice(offset + 8, offset + 8 + payloadSize);
|
|
28
|
+
const message = payload.toString('utf8');
|
|
29
|
+
// Split message into lines and add each line separately
|
|
30
|
+
const lines = message.split('\n').filter((line) => line.length > 0);
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
logs.push({
|
|
33
|
+
message: line,
|
|
34
|
+
stream: streamType === 1 ? 'stdout' : 'stderr',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
offset += 8 + payloadSize;
|
|
38
|
+
}
|
|
39
|
+
return logs;
|
|
40
|
+
}
|
|
41
|
+
async function getContainerLogs(docker, itemIndex) {
|
|
42
|
+
try {
|
|
43
|
+
const containerId = this.getNodeParameter('containerId', itemIndex);
|
|
44
|
+
const tail = this.getNodeParameter('tail', itemIndex, 100);
|
|
45
|
+
const timestamps = this.getNodeParameter('timestamps', itemIndex, false);
|
|
46
|
+
const stream = this.getNodeParameter('stream', itemIndex, 'both');
|
|
47
|
+
// Resolve name to ID first
|
|
48
|
+
const resolved = await (0, resolveContainer_1.resolveContainer)(docker, containerId);
|
|
49
|
+
const container = docker.getContainer(resolved.id);
|
|
50
|
+
const logOptions = {
|
|
51
|
+
stdout: stream === 'both' || stream === 'stdout',
|
|
52
|
+
stderr: stream === 'both' || stream === 'stderr',
|
|
53
|
+
tail: tail === 0 ? undefined : tail,
|
|
54
|
+
follow: false,
|
|
55
|
+
timestamps,
|
|
56
|
+
};
|
|
57
|
+
// Get logs as a Buffer (follow: false returns Buffer)
|
|
58
|
+
const logsBuffer = await container.logs(logOptions);
|
|
59
|
+
// Demultiplex the Docker log format
|
|
60
|
+
const allLogs = demuxDockerLogs(logsBuffer);
|
|
61
|
+
// Filter logs based on stream selection
|
|
62
|
+
let logs = allLogs;
|
|
63
|
+
if (stream === 'stdout') {
|
|
64
|
+
logs = allLogs.filter((log) => log.stream === 'stdout');
|
|
65
|
+
}
|
|
66
|
+
else if (stream === 'stderr') {
|
|
67
|
+
logs = allLogs.filter((log) => log.stream === 'stderr');
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
containerId: resolved.id,
|
|
71
|
+
shortId: resolved.id.substring(0, 12),
|
|
72
|
+
containerName: resolved.name,
|
|
73
|
+
logs,
|
|
74
|
+
lineCount: logs.length,
|
|
75
|
+
stream,
|
|
76
|
+
retrievedAt: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw new Error((0, errorHandler_1.translateDockerError)(error));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listContainers = listContainers;
|
|
4
|
+
const errorHandler_1 = require("../../helpers/errorHandler");
|
|
5
|
+
const normalizeContainer_1 = require("../../helpers/normalizeContainer");
|
|
6
|
+
/**
|
|
7
|
+
* Maps user-friendly status values to Docker API values
|
|
8
|
+
*/
|
|
9
|
+
const statusMap = {
|
|
10
|
+
stopped: 'exited',
|
|
11
|
+
};
|
|
12
|
+
async function listContainers(docker, itemIndex) {
|
|
13
|
+
try {
|
|
14
|
+
const showAll = this.getNodeParameter('showAll', itemIndex, false);
|
|
15
|
+
const filters = this.getNodeParameter('filters', itemIndex, {});
|
|
16
|
+
const includeLabels = this.getNodeParameter('includeLabels', itemIndex, true);
|
|
17
|
+
const listOptions = {
|
|
18
|
+
all: showAll,
|
|
19
|
+
filters: {},
|
|
20
|
+
};
|
|
21
|
+
// Build filters - map user-friendly values to Docker API values
|
|
22
|
+
if (filters.status && filters.status !== '') {
|
|
23
|
+
const dockerStatus = statusMap[filters.status.toLowerCase()] || filters.status;
|
|
24
|
+
listOptions.filters = {
|
|
25
|
+
status: [dockerStatus],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const containers = await docker.listContainers(listOptions);
|
|
29
|
+
// Normalize and filter by name if provided
|
|
30
|
+
let normalized = containers.map((c) => (0, normalizeContainer_1.normalizeContainerInfo)(c, includeLabels));
|
|
31
|
+
if (filters.name && filters.name !== '') {
|
|
32
|
+
const nameFilter = filters.name.toLowerCase();
|
|
33
|
+
normalized = normalized.filter((c) => c.name.toLowerCase().includes(nameFilter));
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error((0, errorHandler_1.translateDockerError)(error));
|
|
39
|
+
}
|
|
40
|
+
}
|