dokku-compose 0.3.6 → 0.5.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/dist/index.js +711 -487
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,6 +27,9 @@ var ChecksSchema = z.union([
|
|
|
27
27
|
skipped: z.array(z.string()).optional()
|
|
28
28
|
}).catchall(z.union([z.string(), z.number(), z.boolean()]))
|
|
29
29
|
]);
|
|
30
|
+
var GitSchema = z.object({
|
|
31
|
+
deploy_branch: z.string().optional()
|
|
32
|
+
});
|
|
30
33
|
var AppSchema = z.object({
|
|
31
34
|
domains: z.union([z.array(z.string()), z.literal(false)]).optional(),
|
|
32
35
|
links: z.array(z.string()).optional(),
|
|
@@ -61,12 +64,26 @@ var AppSchema = z.object({
|
|
|
61
64
|
build: z.array(z.string()).optional(),
|
|
62
65
|
deploy: z.array(z.string()).optional(),
|
|
63
66
|
run: z.array(z.string()).optional()
|
|
64
|
-
}).optional()
|
|
67
|
+
}).optional(),
|
|
68
|
+
git: GitSchema.optional()
|
|
69
|
+
});
|
|
70
|
+
var ServiceBackupAuthSchema = z.object({
|
|
71
|
+
access_key_id: z.string(),
|
|
72
|
+
secret_access_key: z.string(),
|
|
73
|
+
region: z.string(),
|
|
74
|
+
signature_version: z.string(),
|
|
75
|
+
endpoint: z.string()
|
|
76
|
+
});
|
|
77
|
+
var ServiceBackupSchema = z.object({
|
|
78
|
+
schedule: z.string(),
|
|
79
|
+
bucket: z.string(),
|
|
80
|
+
auth: ServiceBackupAuthSchema
|
|
65
81
|
});
|
|
66
82
|
var ServiceSchema = z.object({
|
|
67
83
|
plugin: z.string(),
|
|
68
84
|
version: z.string().optional(),
|
|
69
|
-
image: z.string().optional()
|
|
85
|
+
image: z.string().optional(),
|
|
86
|
+
backup: ServiceBackupSchema.optional()
|
|
70
87
|
});
|
|
71
88
|
var PluginSchema = z.object({
|
|
72
89
|
url: z.string().url(),
|
|
@@ -83,18 +100,23 @@ var ConfigSchema = z.object({
|
|
|
83
100
|
domains: z.union([z.array(z.string()), z.literal(false)]).optional(),
|
|
84
101
|
env: EnvMapSchema.optional(),
|
|
85
102
|
nginx: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
|
|
86
|
-
logs: z.record(z.string(), z.union([z.string(), z.number()])).optional()
|
|
103
|
+
logs: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
|
|
104
|
+
git: GitSchema.optional()
|
|
87
105
|
});
|
|
88
106
|
function parseConfig(raw) {
|
|
89
107
|
return ConfigSchema.parse(raw);
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
// src/core/config.ts
|
|
111
|
+
function interpolateEnvVars(content) {
|
|
112
|
+
return content.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? "");
|
|
113
|
+
}
|
|
93
114
|
function loadConfig(filePath) {
|
|
94
115
|
if (!fs.existsSync(filePath)) {
|
|
95
116
|
throw new Error(`Config file not found: ${filePath}`);
|
|
96
117
|
}
|
|
97
|
-
const
|
|
118
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
119
|
+
const raw = yaml.load(interpolateEnvVars(content));
|
|
98
120
|
return parseConfig(raw);
|
|
99
121
|
}
|
|
100
122
|
|
|
@@ -153,6 +175,69 @@ function createRunner(opts = {}) {
|
|
|
153
175
|
};
|
|
154
176
|
}
|
|
155
177
|
|
|
178
|
+
// src/core/context.ts
|
|
179
|
+
function createContext(runner) {
|
|
180
|
+
const cache = /* @__PURE__ */ new Map();
|
|
181
|
+
const commands = [];
|
|
182
|
+
return {
|
|
183
|
+
commands,
|
|
184
|
+
runner,
|
|
185
|
+
query(...args) {
|
|
186
|
+
const key = args.join("\0");
|
|
187
|
+
if (!cache.has(key)) {
|
|
188
|
+
cache.set(key, runner.query(...args));
|
|
189
|
+
}
|
|
190
|
+
return cache.get(key);
|
|
191
|
+
},
|
|
192
|
+
check(...args) {
|
|
193
|
+
return runner.check(...args);
|
|
194
|
+
},
|
|
195
|
+
async run(...args) {
|
|
196
|
+
commands.push(args);
|
|
197
|
+
await runner.run(...args);
|
|
198
|
+
},
|
|
199
|
+
close() {
|
|
200
|
+
return runner.close();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/core/change.ts
|
|
206
|
+
function computeChange(before, after) {
|
|
207
|
+
if (before === null || before === void 0 || after === null || after === void 0) {
|
|
208
|
+
return { before, after, changed: before !== after };
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
211
|
+
const beforeSet = new Set(before);
|
|
212
|
+
const afterSet = new Set(after);
|
|
213
|
+
const added = after.filter((x) => !beforeSet.has(x));
|
|
214
|
+
const removed = before.filter((x) => !afterSet.has(x));
|
|
215
|
+
return {
|
|
216
|
+
before,
|
|
217
|
+
after,
|
|
218
|
+
changed: added.length > 0 || removed.length > 0,
|
|
219
|
+
added,
|
|
220
|
+
removed
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (typeof before === "object" && typeof after === "object") {
|
|
224
|
+
const b = before;
|
|
225
|
+
const a = after;
|
|
226
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(a)]);
|
|
227
|
+
const added = {};
|
|
228
|
+
const removed = [];
|
|
229
|
+
const modified = {};
|
|
230
|
+
for (const key of allKeys) {
|
|
231
|
+
if (!(key in b)) added[key] = a[key];
|
|
232
|
+
else if (!(key in a)) removed.push(key);
|
|
233
|
+
else if (String(b[key]) !== String(a[key])) modified[key] = a[key];
|
|
234
|
+
}
|
|
235
|
+
const changed = Object.keys(added).length > 0 || removed.length > 0 || Object.keys(modified).length > 0;
|
|
236
|
+
return { before, after, changed, added, removed, modified };
|
|
237
|
+
}
|
|
238
|
+
return { before, after, changed: before !== after };
|
|
239
|
+
}
|
|
240
|
+
|
|
156
241
|
// src/core/logger.ts
|
|
157
242
|
import chalk from "chalk";
|
|
158
243
|
function logAction(context, message) {
|
|
@@ -165,68 +250,416 @@ function logSkip() {
|
|
|
165
250
|
console.log(`... ${chalk.yellow("already configured")}`);
|
|
166
251
|
}
|
|
167
252
|
|
|
168
|
-
// src/
|
|
169
|
-
async function
|
|
170
|
-
|
|
171
|
-
logAction(
|
|
172
|
-
if (
|
|
173
|
-
|
|
253
|
+
// src/core/reconcile.ts
|
|
254
|
+
async function reconcile(resource, ctx, target, desired) {
|
|
255
|
+
if (desired === void 0) return;
|
|
256
|
+
logAction(target, `${resource.key}`);
|
|
257
|
+
if (resource.forceApply) {
|
|
258
|
+
await resource.onChange(ctx, target, { before: void 0, after: desired, changed: true });
|
|
259
|
+
logDone();
|
|
174
260
|
return;
|
|
175
261
|
}
|
|
176
|
-
await
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
async function destroyApp(runner, app) {
|
|
180
|
-
const exists = await runner.check("apps:exists", app);
|
|
181
|
-
logAction(app, "Destroying app");
|
|
182
|
-
if (!exists) {
|
|
262
|
+
const before = await resource.read(ctx, target);
|
|
263
|
+
const change = computeChange(before, desired);
|
|
264
|
+
if (!change.changed) {
|
|
183
265
|
logSkip();
|
|
184
266
|
return;
|
|
185
267
|
}
|
|
186
|
-
await
|
|
268
|
+
await resource.onChange(ctx, target, change);
|
|
187
269
|
logDone();
|
|
188
270
|
}
|
|
189
|
-
async function exportApps(runner) {
|
|
190
|
-
const output = await runner.query("apps:list");
|
|
191
|
-
return output.split("\n").map((s) => s.trim()).filter(
|
|
192
|
-
(s) => s && !s.startsWith("=====>")
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
271
|
|
|
196
|
-
// src/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
272
|
+
// src/resources/lifecycle.ts
|
|
273
|
+
var Apps = {
|
|
274
|
+
key: "_app",
|
|
275
|
+
read: async (ctx, target) => {
|
|
276
|
+
return ctx.check("apps:exists", target);
|
|
277
|
+
},
|
|
278
|
+
onChange: async (ctx, target, { after }) => {
|
|
279
|
+
if (after) {
|
|
280
|
+
await ctx.run("apps:create", target);
|
|
281
|
+
} else {
|
|
282
|
+
await ctx.run("apps:destroy", target, "--force");
|
|
283
|
+
}
|
|
206
284
|
}
|
|
207
|
-
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/resources/parsers.ts
|
|
288
|
+
function parseReport(raw, namespace) {
|
|
289
|
+
const result = {};
|
|
290
|
+
const prefix = new RegExp(`^${namespace}\\s+`, "i");
|
|
291
|
+
for (const line of raw.split("\n")) {
|
|
292
|
+
if (line.trimStart().startsWith("=====>")) continue;
|
|
293
|
+
const colonIdx = line.indexOf(":");
|
|
294
|
+
if (colonIdx === -1) continue;
|
|
295
|
+
const rawKey = line.slice(0, colonIdx).trim();
|
|
296
|
+
if (!rawKey) continue;
|
|
297
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
298
|
+
const stripped = rawKey.replace(prefix, "");
|
|
299
|
+
const key = stripped.toLowerCase().replace(/\s+/g, "-");
|
|
300
|
+
if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
|
|
301
|
+
if (!value) continue;
|
|
302
|
+
result[key] = value;
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
208
305
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
306
|
+
function parseBulkReport(raw, namespace) {
|
|
307
|
+
const result = /* @__PURE__ */ new Map();
|
|
308
|
+
const sections = raw.split(/(?=^=====> )/m).filter((s) => s.trim());
|
|
309
|
+
for (const section of sections) {
|
|
310
|
+
const headerEnd = section.indexOf("\n");
|
|
311
|
+
if (headerEnd === -1) continue;
|
|
312
|
+
const header = section.slice(0, headerEnd);
|
|
313
|
+
const match = header.match(/^=====> (.+?)\s+\S+\s+information/);
|
|
314
|
+
if (!match) continue;
|
|
315
|
+
const app = match[1];
|
|
316
|
+
result.set(app, parseReport(section, namespace));
|
|
215
317
|
}
|
|
216
|
-
|
|
318
|
+
return result;
|
|
217
319
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return
|
|
320
|
+
|
|
321
|
+
// src/resources/lists.ts
|
|
322
|
+
function splitWords(raw) {
|
|
323
|
+
return raw.split(/\s+/).map((s) => s.trim()).filter(Boolean);
|
|
324
|
+
}
|
|
325
|
+
function splitLines(raw) {
|
|
326
|
+
return raw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
225
327
|
}
|
|
328
|
+
var Ports = {
|
|
329
|
+
key: "ports",
|
|
330
|
+
read: async (ctx, target) => {
|
|
331
|
+
const raw = await ctx.query("ports:report", target, "--ports-map");
|
|
332
|
+
return splitWords(raw);
|
|
333
|
+
},
|
|
334
|
+
readAll: async (ctx) => {
|
|
335
|
+
const raw = await ctx.query("ports:report");
|
|
336
|
+
const bulk = parseBulkReport(raw, "ports");
|
|
337
|
+
const result = /* @__PURE__ */ new Map();
|
|
338
|
+
for (const [app, report] of bulk) {
|
|
339
|
+
result.set(app, report["map"] ? splitWords(report["map"]) : []);
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
},
|
|
343
|
+
onChange: async (ctx, target, change) => {
|
|
344
|
+
await ctx.run("ports:set", target, ...change.after);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var Domains = {
|
|
348
|
+
key: "domains",
|
|
349
|
+
read: async (ctx, target) => {
|
|
350
|
+
const raw = await ctx.query("domains:report", target, "--domains-app-vhosts");
|
|
351
|
+
return splitLines(raw);
|
|
352
|
+
},
|
|
353
|
+
readAll: async (ctx) => {
|
|
354
|
+
const raw = await ctx.query("domains:report");
|
|
355
|
+
const bulk = parseBulkReport(raw, "domains");
|
|
356
|
+
const result = /* @__PURE__ */ new Map();
|
|
357
|
+
for (const [app, report] of bulk) {
|
|
358
|
+
result.set(app, report["app-vhosts"] ? splitWords(report["app-vhosts"]) : []);
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
},
|
|
362
|
+
onChange: async (ctx, target, { added, removed }) => {
|
|
363
|
+
for (const d of removed) await ctx.run("domains:remove", target, d);
|
|
364
|
+
for (const d of added) await ctx.run("domains:add", target, d);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
var Storage = {
|
|
368
|
+
key: "storage",
|
|
369
|
+
read: async (ctx, target) => {
|
|
370
|
+
const raw = await ctx.query("storage:report", target, "--storage-mounts");
|
|
371
|
+
return splitLines(raw);
|
|
372
|
+
},
|
|
373
|
+
readAll: async (ctx) => {
|
|
374
|
+
const raw = await ctx.query("storage:report");
|
|
375
|
+
const bulk = parseBulkReport(raw, "storage");
|
|
376
|
+
const result = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const [app, report] of bulk) {
|
|
378
|
+
result.set(app, report["mounts"] ? splitLines(report["mounts"]) : []);
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
},
|
|
382
|
+
onChange: async (ctx, target, { added, removed }) => {
|
|
383
|
+
for (const m of removed) await ctx.run("storage:unmount", target, m);
|
|
384
|
+
for (const m of added) await ctx.run("storage:mount", target, m);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/resources/properties.ts
|
|
389
|
+
function propertyResource(opts) {
|
|
390
|
+
return {
|
|
391
|
+
key: opts.key,
|
|
392
|
+
async read(ctx, target) {
|
|
393
|
+
const raw = await ctx.query(`${opts.namespace}:report`, target);
|
|
394
|
+
return parseReport(raw, opts.namespace);
|
|
395
|
+
},
|
|
396
|
+
async readAll(ctx) {
|
|
397
|
+
const raw = await ctx.query(`${opts.namespace}:report`);
|
|
398
|
+
return parseBulkReport(raw, opts.namespace);
|
|
399
|
+
},
|
|
400
|
+
async onChange(ctx, target, change) {
|
|
401
|
+
for (const [key, value] of Object.entries({ ...change.added, ...change.modified })) {
|
|
402
|
+
await ctx.run(opts.setCmd, target, key, String(value));
|
|
403
|
+
}
|
|
404
|
+
if (opts.afterChange) {
|
|
405
|
+
for (const cmd of opts.afterChange) {
|
|
406
|
+
await ctx.run(cmd, target);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
var Nginx = propertyResource({
|
|
413
|
+
key: "nginx",
|
|
414
|
+
namespace: "nginx",
|
|
415
|
+
setCmd: "nginx:set",
|
|
416
|
+
afterChange: ["proxy:build-config"]
|
|
417
|
+
});
|
|
418
|
+
var Logs = propertyResource({
|
|
419
|
+
key: "logs",
|
|
420
|
+
namespace: "logs",
|
|
421
|
+
setCmd: "logs:set"
|
|
422
|
+
});
|
|
423
|
+
var Registry = propertyResource({
|
|
424
|
+
key: "registry",
|
|
425
|
+
namespace: "registry",
|
|
426
|
+
setCmd: "registry:set"
|
|
427
|
+
});
|
|
428
|
+
var Scheduler = {
|
|
429
|
+
key: "scheduler",
|
|
430
|
+
async read(ctx, target) {
|
|
431
|
+
const raw = await ctx.query("scheduler:report", target);
|
|
432
|
+
const report = parseReport(raw, "scheduler");
|
|
433
|
+
return report["selected"] ?? "";
|
|
434
|
+
},
|
|
435
|
+
async readAll(ctx) {
|
|
436
|
+
const raw = await ctx.query("scheduler:report");
|
|
437
|
+
const bulk = parseBulkReport(raw, "scheduler");
|
|
438
|
+
const result = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const [app, report] of bulk) {
|
|
440
|
+
result.set(app, report["selected"] ?? "");
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
},
|
|
444
|
+
async onChange(ctx, target, change) {
|
|
445
|
+
await ctx.run("scheduler:set", target, "selected", change.after);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/resources/toggle.ts
|
|
450
|
+
var Proxy = {
|
|
451
|
+
key: "proxy",
|
|
452
|
+
read: async (ctx, target) => {
|
|
453
|
+
const raw = await ctx.query("proxy:report", target, "--proxy-enabled");
|
|
454
|
+
return raw.trim() === "true";
|
|
455
|
+
},
|
|
456
|
+
readAll: async (ctx) => {
|
|
457
|
+
const raw = await ctx.query("proxy:report");
|
|
458
|
+
const bulk = parseBulkReport(raw, "proxy");
|
|
459
|
+
const result = /* @__PURE__ */ new Map();
|
|
460
|
+
for (const [app, report] of bulk) {
|
|
461
|
+
result.set(app, report["enabled"] === "true");
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
},
|
|
465
|
+
onChange: async (ctx, target, { after }) => {
|
|
466
|
+
await ctx.run(after ? "proxy:enable" : "proxy:disable", target);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/resources/config.ts
|
|
471
|
+
var MANAGED_KEYS_VAR = "DOKKU_COMPOSE_MANAGED_KEYS";
|
|
472
|
+
var Config = {
|
|
473
|
+
key: "env",
|
|
474
|
+
read: async (ctx, target) => {
|
|
475
|
+
const managedRaw = await ctx.query("config:get", target, MANAGED_KEYS_VAR);
|
|
476
|
+
const managedKeys = managedRaw.trim() ? managedRaw.trim().split(",").filter(Boolean) : [];
|
|
477
|
+
const result = {};
|
|
478
|
+
if (managedKeys.length > 0) {
|
|
479
|
+
const raw = await ctx.query("config:export", target, "--format", "shell");
|
|
480
|
+
for (const line of raw.split("\n")) {
|
|
481
|
+
const match = line.match(/^export\s+(\w+)=['"]?(.*?)['"]?$/);
|
|
482
|
+
if (match && managedKeys.includes(match[1])) {
|
|
483
|
+
result[match[1]] = match[2];
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
},
|
|
489
|
+
onChange: async (ctx, target, change) => {
|
|
490
|
+
const { added, removed, modified } = change;
|
|
491
|
+
if (removed.length > 0) {
|
|
492
|
+
await ctx.run("config:unset", "--no-restart", target, ...removed);
|
|
493
|
+
}
|
|
494
|
+
const toSet = { ...added, ...modified };
|
|
495
|
+
const allDesiredKeys = Object.keys(change.after);
|
|
496
|
+
const managedValue = allDesiredKeys.join(",");
|
|
497
|
+
if (Object.keys(toSet).length > 0 || removed.length > 0) {
|
|
498
|
+
const pairs = Object.entries(change.after).map(([k, v]) => `${k}=${v}`);
|
|
499
|
+
await ctx.run(
|
|
500
|
+
"config:set",
|
|
501
|
+
"--no-restart",
|
|
502
|
+
target,
|
|
503
|
+
...pairs,
|
|
504
|
+
`${MANAGED_KEYS_VAR}=${managedValue}`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// src/resources/certs.ts
|
|
511
|
+
var Certs = {
|
|
512
|
+
key: "ssl",
|
|
513
|
+
read: async (ctx, target) => {
|
|
514
|
+
const raw = await ctx.query("certs:report", target, "--ssl-enabled");
|
|
515
|
+
return raw.trim() === "true";
|
|
516
|
+
},
|
|
517
|
+
readAll: async (ctx) => {
|
|
518
|
+
const raw = await ctx.query("certs:report");
|
|
519
|
+
const bulk = parseBulkReport(raw, "ssl");
|
|
520
|
+
const result = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const [app, report] of bulk) {
|
|
522
|
+
result.set(app, report["enabled"] === "true");
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
},
|
|
526
|
+
onChange: async (ctx, target, { before, after }) => {
|
|
527
|
+
if (after === false && before) {
|
|
528
|
+
await ctx.run("certs:remove", target);
|
|
529
|
+
}
|
|
530
|
+
if (after && typeof after === "object") {
|
|
531
|
+
await ctx.run("certs:add", target, after.certfile, after.keyfile);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// src/resources/builder.ts
|
|
537
|
+
var Builder = {
|
|
538
|
+
key: "build",
|
|
539
|
+
forceApply: true,
|
|
540
|
+
read: async () => ({}),
|
|
541
|
+
onChange: async (ctx, target, { after }) => {
|
|
542
|
+
if (after.dockerfile)
|
|
543
|
+
await ctx.run("builder-dockerfile:set", target, "dockerfile-path", after.dockerfile);
|
|
544
|
+
if (after.app_json)
|
|
545
|
+
await ctx.run("app-json:set", target, "appjson-path", after.app_json);
|
|
546
|
+
if (after.context)
|
|
547
|
+
await ctx.run("builder:set", target, "build-dir", after.context);
|
|
548
|
+
if (after.args) {
|
|
549
|
+
for (const [key, value] of Object.entries(after.args)) {
|
|
550
|
+
await ctx.run("docker-options:add", target, "build", `--build-arg ${key}=${value}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// src/resources/docker-options.ts
|
|
557
|
+
var DockerOptions = {
|
|
558
|
+
key: "docker_options",
|
|
559
|
+
forceApply: true,
|
|
560
|
+
read: async () => ({}),
|
|
561
|
+
onChange: async (ctx, target, { after }) => {
|
|
562
|
+
for (const phase of ["build", "deploy", "run"]) {
|
|
563
|
+
const opts = after[phase];
|
|
564
|
+
if (!opts || opts.length === 0) continue;
|
|
565
|
+
await ctx.run("docker-options:clear", target, phase);
|
|
566
|
+
for (const opt of opts) {
|
|
567
|
+
await ctx.run("docker-options:add", target, phase, opt);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/resources/git.ts
|
|
574
|
+
var Git = {
|
|
575
|
+
key: "git",
|
|
576
|
+
read: async (ctx, target) => {
|
|
577
|
+
const report = await ctx.query("git:report", target, "--git-deploy-branch");
|
|
578
|
+
return { deploy_branch: report.trim() || void 0 };
|
|
579
|
+
},
|
|
580
|
+
readAll: async (ctx) => {
|
|
581
|
+
const raw = await ctx.query("git:report");
|
|
582
|
+
const bulk = parseBulkReport(raw, "git");
|
|
583
|
+
const result = /* @__PURE__ */ new Map();
|
|
584
|
+
for (const [app, report] of bulk) {
|
|
585
|
+
result.set(app, { deploy_branch: report["deploy-branch"] || void 0 });
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
},
|
|
589
|
+
onChange: async (ctx, target, { after }) => {
|
|
590
|
+
if (after.deploy_branch) {
|
|
591
|
+
await ctx.run("git:set", target, "deploy-branch", after.deploy_branch);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// src/resources/checks.ts
|
|
597
|
+
var Checks = {
|
|
598
|
+
key: "checks",
|
|
599
|
+
forceApply: true,
|
|
600
|
+
read: async () => ({}),
|
|
601
|
+
onChange: async (ctx, target, { after }) => {
|
|
602
|
+
if (after === false) {
|
|
603
|
+
await ctx.run("checks:disable", target);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (after.disabled && after.disabled.length > 0) {
|
|
607
|
+
await ctx.run("checks:disable", target, ...after.disabled);
|
|
608
|
+
}
|
|
609
|
+
if (after.skipped && after.skipped.length > 0) {
|
|
610
|
+
await ctx.run("checks:skip", target, ...after.skipped);
|
|
611
|
+
}
|
|
612
|
+
for (const [key, value] of Object.entries(after)) {
|
|
613
|
+
if (key === "disabled" || key === "skipped") continue;
|
|
614
|
+
await ctx.run("checks:set", target, key, String(value));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// src/resources/network.ts
|
|
620
|
+
var Networks = {
|
|
621
|
+
key: "networks",
|
|
622
|
+
read: async (ctx, target) => {
|
|
623
|
+
const raw = await ctx.query("network:report", target, "--network-attach-post-deploy");
|
|
624
|
+
return raw.trim() ? raw.trim().split(/\s+/) : [];
|
|
625
|
+
},
|
|
626
|
+
readAll: async (ctx) => {
|
|
627
|
+
const raw = await ctx.query("network:report");
|
|
628
|
+
const bulk = parseBulkReport(raw, "network");
|
|
629
|
+
const result = /* @__PURE__ */ new Map();
|
|
630
|
+
for (const [app, report] of bulk) {
|
|
631
|
+
result.set(app, report["attach-post-deploy"] ? report["attach-post-deploy"].split(/\s+/) : []);
|
|
632
|
+
}
|
|
633
|
+
return result;
|
|
634
|
+
},
|
|
635
|
+
onChange: async (ctx, target, { after }) => {
|
|
636
|
+
await ctx.run("network:set", target, "attach-post-deploy", ...after);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
var NetworkProps = {
|
|
640
|
+
key: "network",
|
|
641
|
+
forceApply: true,
|
|
642
|
+
read: async () => ({}),
|
|
643
|
+
onChange: async (ctx, target, { after }) => {
|
|
644
|
+
if (after.attach_post_create !== void 0 && after.attach_post_create !== false) {
|
|
645
|
+
const nets = Array.isArray(after.attach_post_create) ? after.attach_post_create : [after.attach_post_create];
|
|
646
|
+
await ctx.run("network:set", target, "attach-post-create", ...nets);
|
|
647
|
+
}
|
|
648
|
+
if (after.initial_network !== void 0 && after.initial_network !== false) {
|
|
649
|
+
await ctx.run("network:set", target, "initial-network", after.initial_network);
|
|
650
|
+
}
|
|
651
|
+
if (after.bind_all_interfaces !== void 0) {
|
|
652
|
+
await ctx.run("network:set", target, "bind-all-interfaces", String(after.bind_all_interfaces));
|
|
653
|
+
}
|
|
654
|
+
if (after.tld !== void 0 && after.tld !== false) {
|
|
655
|
+
await ctx.run("network:set", target, "tld", after.tld);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
226
659
|
|
|
227
660
|
// src/modules/plugins.ts
|
|
228
|
-
async function ensurePlugins(
|
|
229
|
-
const listOutput = await
|
|
661
|
+
async function ensurePlugins(ctx, plugins) {
|
|
662
|
+
const listOutput = await ctx.query("plugin:list");
|
|
230
663
|
const installedNames = new Set(
|
|
231
664
|
listOutput.split("\n").map((line) => line.trim().split(/\s+/)[0]).filter(Boolean)
|
|
232
665
|
);
|
|
@@ -236,514 +669,311 @@ async function ensurePlugins(runner, plugins) {
|
|
|
236
669
|
logSkip();
|
|
237
670
|
continue;
|
|
238
671
|
}
|
|
239
|
-
await
|
|
672
|
+
await ctx.run("plugin:install", config.url, "--name", name);
|
|
240
673
|
logDone();
|
|
241
674
|
}
|
|
242
675
|
}
|
|
243
676
|
|
|
244
677
|
// src/modules/network.ts
|
|
245
|
-
async function ensureNetworks(
|
|
678
|
+
async function ensureNetworks(ctx, networks) {
|
|
246
679
|
for (const net of networks) {
|
|
247
680
|
logAction("network", `Creating ${net}`);
|
|
248
|
-
const exists = await
|
|
681
|
+
const exists = await ctx.check("network:exists", net);
|
|
249
682
|
if (exists) {
|
|
250
683
|
logSkip();
|
|
251
684
|
continue;
|
|
252
685
|
}
|
|
253
|
-
await
|
|
686
|
+
await ctx.run("network:create", net);
|
|
254
687
|
logDone();
|
|
255
688
|
}
|
|
256
689
|
}
|
|
257
|
-
async function ensureAppNetworks(runner, app, networks) {
|
|
258
|
-
if (!networks || networks.length === 0) return;
|
|
259
|
-
logAction(app, "Setting networks");
|
|
260
|
-
await runner.run("network:set", app, "attach-post-deploy", ...networks);
|
|
261
|
-
logDone();
|
|
262
|
-
}
|
|
263
|
-
async function ensureAppNetwork(runner, app, network) {
|
|
264
|
-
if (!network) return;
|
|
265
|
-
if (network.attach_post_create !== void 0 && network.attach_post_create !== false) {
|
|
266
|
-
const nets = Array.isArray(network.attach_post_create) ? network.attach_post_create : [network.attach_post_create];
|
|
267
|
-
await runner.run("network:set", app, "attach-post-create", ...nets);
|
|
268
|
-
}
|
|
269
|
-
if (network.initial_network !== void 0 && network.initial_network !== false) {
|
|
270
|
-
await runner.run("network:set", app, "initial-network", network.initial_network);
|
|
271
|
-
}
|
|
272
|
-
if (network.bind_all_interfaces !== void 0) {
|
|
273
|
-
await runner.run("network:set", app, "bind-all-interfaces", String(network.bind_all_interfaces));
|
|
274
|
-
}
|
|
275
|
-
if (network.tld !== void 0 && network.tld !== false) {
|
|
276
|
-
await runner.run("network:set", app, "tld", network.tld);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
690
|
var DOCKER_BUILTIN_NETWORKS = /* @__PURE__ */ new Set(["bridge", "host", "none"]);
|
|
280
|
-
async function exportNetworks(
|
|
281
|
-
const output = await
|
|
691
|
+
async function exportNetworks(ctx) {
|
|
692
|
+
const output = await ctx.query("network:list");
|
|
282
693
|
return output.split("\n").map((s) => s.trim()).filter((s) => s && !s.startsWith("=====>") && !DOCKER_BUILTIN_NETWORKS.has(s));
|
|
283
694
|
}
|
|
284
|
-
async function exportAppNetwork(runner, app) {
|
|
285
|
-
const output = await runner.query("network:report", app);
|
|
286
|
-
if (!output) return void 0;
|
|
287
|
-
const result = {};
|
|
288
|
-
const lines = output.split("\n");
|
|
289
|
-
for (const line of lines) {
|
|
290
|
-
const [key, ...valueParts] = line.split(":").map((s) => s.trim());
|
|
291
|
-
const value = valueParts.join(":").trim();
|
|
292
|
-
if (!key || !value) continue;
|
|
293
|
-
if (key === "Network attach post deploy") {
|
|
294
|
-
result.networks = value.split(" ").filter(Boolean);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
298
|
-
}
|
|
299
695
|
|
|
300
696
|
// src/modules/services.ts
|
|
301
|
-
|
|
697
|
+
import { createHash as createHash2 } from "crypto";
|
|
698
|
+
function backupHashKey(serviceName) {
|
|
699
|
+
return "DOKKU_COMPOSE_BACKUP_HASH_" + serviceName.toUpperCase().replace(/-/g, "_");
|
|
700
|
+
}
|
|
701
|
+
function computeBackupHash(backup) {
|
|
702
|
+
return createHash2("sha256").update(JSON.stringify(backup)).digest("hex");
|
|
703
|
+
}
|
|
704
|
+
async function ensureServices(ctx, services) {
|
|
302
705
|
for (const [name, config] of Object.entries(services)) {
|
|
303
706
|
logAction("services", `Ensuring ${name}`);
|
|
304
|
-
const exists = await
|
|
707
|
+
const exists = await ctx.check(`${config.plugin}:exists`, name);
|
|
305
708
|
if (exists) {
|
|
306
709
|
logSkip();
|
|
307
710
|
continue;
|
|
308
711
|
}
|
|
309
|
-
|
|
712
|
+
const args = [`${config.plugin}:create`, name];
|
|
713
|
+
if (config.image) args.push("--image", config.image);
|
|
714
|
+
if (config.version) args.push("--image-version", config.version);
|
|
715
|
+
await ctx.run(...args);
|
|
716
|
+
logDone();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function ensureServiceBackups(ctx, services) {
|
|
720
|
+
for (const [name, config] of Object.entries(services)) {
|
|
721
|
+
if (!config.backup) continue;
|
|
722
|
+
logAction("services", `Configuring backup for ${name}`);
|
|
723
|
+
const hashKey = backupHashKey(name);
|
|
724
|
+
const desiredHash = computeBackupHash(config.backup);
|
|
725
|
+
const storedHash = await ctx.query("config:get", "--global", hashKey);
|
|
726
|
+
if (storedHash === desiredHash) {
|
|
727
|
+
logSkip();
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
const { schedule, bucket, auth } = config.backup;
|
|
731
|
+
await ctx.run(`${config.plugin}:backup-deauth`, name);
|
|
732
|
+
await ctx.run(
|
|
733
|
+
`${config.plugin}:backup-auth`,
|
|
734
|
+
name,
|
|
735
|
+
auth.access_key_id,
|
|
736
|
+
auth.secret_access_key,
|
|
737
|
+
auth.region,
|
|
738
|
+
auth.signature_version,
|
|
739
|
+
auth.endpoint
|
|
740
|
+
);
|
|
741
|
+
await ctx.run(`${config.plugin}:backup-schedule`, name, schedule, bucket);
|
|
742
|
+
await ctx.run("config:set", "--global", `${hashKey}=${desiredHash}`);
|
|
310
743
|
logDone();
|
|
311
744
|
}
|
|
312
745
|
}
|
|
313
|
-
async function ensureAppLinks(
|
|
746
|
+
async function ensureAppLinks(ctx, app, desiredLinks, allServices) {
|
|
314
747
|
const desiredSet = new Set(desiredLinks);
|
|
315
748
|
for (const [serviceName, serviceConfig] of Object.entries(allServices)) {
|
|
316
|
-
const isLinked = await
|
|
749
|
+
const isLinked = await ctx.check(`${serviceConfig.plugin}:linked`, serviceName, app);
|
|
317
750
|
const isDesired = desiredSet.has(serviceName);
|
|
318
751
|
if (isDesired && !isLinked) {
|
|
319
752
|
logAction(app, `Linking ${serviceName}`);
|
|
320
|
-
await
|
|
753
|
+
await ctx.run(`${serviceConfig.plugin}:link`, serviceName, app, "--no-restart");
|
|
321
754
|
logDone();
|
|
322
755
|
} else if (!isDesired && isLinked) {
|
|
323
756
|
logAction(app, `Unlinking ${serviceName}`);
|
|
324
|
-
await
|
|
757
|
+
await ctx.run(`${serviceConfig.plugin}:unlink`, serviceName, app, "--no-restart");
|
|
325
758
|
logDone();
|
|
326
759
|
}
|
|
327
760
|
}
|
|
328
761
|
}
|
|
329
|
-
async function destroyAppLinks(
|
|
762
|
+
async function destroyAppLinks(ctx, app, links, allServices) {
|
|
330
763
|
for (const serviceName of links) {
|
|
331
764
|
const config = allServices[serviceName];
|
|
332
765
|
if (!config) continue;
|
|
333
|
-
const isLinked = await
|
|
766
|
+
const isLinked = await ctx.check(`${config.plugin}:linked`, serviceName, app);
|
|
334
767
|
if (isLinked) {
|
|
335
|
-
await
|
|
768
|
+
await ctx.run(`${config.plugin}:unlink`, serviceName, app, "--no-restart");
|
|
336
769
|
}
|
|
337
770
|
}
|
|
338
771
|
}
|
|
339
|
-
async function destroyServices(
|
|
772
|
+
async function destroyServices(ctx, services) {
|
|
340
773
|
for (const [name, config] of Object.entries(services)) {
|
|
341
774
|
logAction("services", `Destroying ${name}`);
|
|
342
|
-
const exists = await
|
|
775
|
+
const exists = await ctx.check(`${config.plugin}:exists`, name);
|
|
343
776
|
if (!exists) {
|
|
344
777
|
logSkip();
|
|
345
778
|
continue;
|
|
346
779
|
}
|
|
347
|
-
await
|
|
780
|
+
await ctx.run(`${config.plugin}:destroy`, name, "--force");
|
|
348
781
|
logDone();
|
|
349
782
|
}
|
|
350
783
|
}
|
|
351
|
-
async function exportServices(
|
|
784
|
+
async function exportServices(_ctx) {
|
|
352
785
|
return {};
|
|
353
786
|
}
|
|
354
|
-
async function exportAppLinks(
|
|
787
|
+
async function exportAppLinks(ctx, app, services) {
|
|
355
788
|
const linked = [];
|
|
356
789
|
for (const [serviceName, config] of Object.entries(services)) {
|
|
357
|
-
const isLinked = await
|
|
790
|
+
const isLinked = await ctx.check(`${config.plugin}:linked`, serviceName, app);
|
|
358
791
|
if (isLinked) linked.push(serviceName);
|
|
359
792
|
}
|
|
360
793
|
return linked;
|
|
361
794
|
}
|
|
362
795
|
|
|
363
|
-
// src/modules/
|
|
364
|
-
async function
|
|
365
|
-
logAction(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (current === enabled) {
|
|
369
|
-
logSkip();
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
if (enabled) {
|
|
373
|
-
await runner.run("proxy:enable", app);
|
|
796
|
+
// src/modules/domains.ts
|
|
797
|
+
async function ensureGlobalDomains(ctx, domains) {
|
|
798
|
+
logAction("global", "Configuring domains");
|
|
799
|
+
if (domains === false) {
|
|
800
|
+
await ctx.run("domains:clear-global");
|
|
374
801
|
} else {
|
|
375
|
-
await
|
|
802
|
+
await ctx.run("domains:set-global", ...domains);
|
|
376
803
|
}
|
|
377
804
|
logDone();
|
|
378
805
|
}
|
|
379
|
-
async function exportAppProxy(runner, app) {
|
|
380
|
-
const raw = await runner.query("proxy:report", app, "--proxy-enabled");
|
|
381
|
-
const enabled = raw.trim() === "true";
|
|
382
|
-
if (enabled) return void 0;
|
|
383
|
-
return { enabled };
|
|
384
|
-
}
|
|
385
806
|
|
|
386
|
-
// src/modules/
|
|
387
|
-
async function
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
const desired = [...ports].sort();
|
|
392
|
-
if (JSON.stringify(current) === JSON.stringify(desired)) {
|
|
393
|
-
logSkip();
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
await runner.run("ports:set", app, ...ports);
|
|
397
|
-
logDone();
|
|
398
|
-
}
|
|
399
|
-
async function exportAppPorts(runner, app) {
|
|
400
|
-
const raw = await runner.query("ports:report", app, "--ports-map");
|
|
401
|
-
const ports = raw.split(/\s+/).map((s) => s.trim()).filter(Boolean);
|
|
402
|
-
if (ports.length === 0) return void 0;
|
|
403
|
-
return ports;
|
|
807
|
+
// src/modules/config.ts
|
|
808
|
+
async function ensureGlobalConfig(ctx, env) {
|
|
809
|
+
if (env === false) return;
|
|
810
|
+
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
811
|
+
await ctx.run("config:set", "--global", ...pairs);
|
|
404
812
|
}
|
|
405
813
|
|
|
406
|
-
// src/modules/
|
|
407
|
-
async function
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const enabled = enabledRaw.trim() === "true";
|
|
411
|
-
if (ssl === false) {
|
|
412
|
-
if (!enabled) {
|
|
413
|
-
logSkip();
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
await runner.run("certs:remove", app);
|
|
417
|
-
logDone();
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (ssl === true) {
|
|
421
|
-
if (enabled) {
|
|
422
|
-
logSkip();
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
logSkip();
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
if (enabled) {
|
|
429
|
-
logSkip();
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
await runner.run("certs:add", app, ssl.certfile, ssl.keyfile);
|
|
433
|
-
logDone();
|
|
434
|
-
}
|
|
435
|
-
async function exportAppCerts(runner, app) {
|
|
436
|
-
const raw = await runner.query("certs:report", app, "--ssl-enabled");
|
|
437
|
-
const enabled = raw.trim() === "true";
|
|
438
|
-
if (!enabled) return void 0;
|
|
439
|
-
return true;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// src/modules/storage.ts
|
|
443
|
-
async function ensureAppStorage(runner, app, storage) {
|
|
444
|
-
logAction(app, "Configuring storage");
|
|
445
|
-
const currentRaw = await runner.query("storage:report", app, "--storage-mounts");
|
|
446
|
-
const current = currentRaw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
447
|
-
const desired = new Set(storage);
|
|
448
|
-
const currentSet = new Set(current);
|
|
449
|
-
const toUnmount = current.filter((m) => !desired.has(m));
|
|
450
|
-
const toMount = storage.filter((m) => !currentSet.has(m));
|
|
451
|
-
if (toUnmount.length === 0 && toMount.length === 0) {
|
|
452
|
-
logSkip();
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
for (const mount of toUnmount) {
|
|
456
|
-
await runner.run("storage:unmount", app, mount);
|
|
457
|
-
}
|
|
458
|
-
for (const mount of toMount) {
|
|
459
|
-
await runner.run("storage:mount", app, mount);
|
|
814
|
+
// src/modules/logs.ts
|
|
815
|
+
async function ensureGlobalLogs(ctx, logs) {
|
|
816
|
+
for (const [key, value] of Object.entries(logs)) {
|
|
817
|
+
await ctx.run("logs:set", "--global", key, String(value));
|
|
460
818
|
}
|
|
461
|
-
logDone();
|
|
462
|
-
}
|
|
463
|
-
async function exportAppStorage(runner, app) {
|
|
464
|
-
const raw = await runner.query("storage:report", app, "--storage-mounts");
|
|
465
|
-
const mounts = raw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
466
|
-
if (mounts.length === 0) return void 0;
|
|
467
|
-
return mounts;
|
|
468
819
|
}
|
|
469
820
|
|
|
470
821
|
// src/modules/nginx.ts
|
|
471
|
-
async function
|
|
822
|
+
async function ensureGlobalNginx(ctx, nginx) {
|
|
472
823
|
for (const [key, value] of Object.entries(nginx)) {
|
|
473
|
-
await
|
|
824
|
+
await ctx.run("nginx:set", "--global", key, String(value));
|
|
474
825
|
}
|
|
475
826
|
}
|
|
476
|
-
async function ensureGlobalNginx(runner, nginx) {
|
|
477
|
-
for (const [key, value] of Object.entries(nginx)) {
|
|
478
|
-
await runner.run("nginx:set", "--global", key, String(value));
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
async function exportAppNginx(runner, app) {
|
|
482
|
-
const raw = await runner.query("nginx:report", app);
|
|
483
|
-
if (!raw) return void 0;
|
|
484
|
-
const result = {};
|
|
485
|
-
for (const line of raw.split("\n")) {
|
|
486
|
-
const match = line.match(/^\s*Nginx\s+(.+?):\s*(.+?)\s*$/);
|
|
487
|
-
if (match) {
|
|
488
|
-
const key = match[1].toLowerCase().replace(/\s+/g, "-");
|
|
489
|
-
if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
|
|
490
|
-
const value = match[2].trim();
|
|
491
|
-
if (!value) continue;
|
|
492
|
-
result[key] = value;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
496
|
-
}
|
|
497
827
|
|
|
498
|
-
// src/
|
|
499
|
-
async function
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
await
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
await
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
await runner.run("logs:set", "--global", key, String(value));
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
async function exportAppLogs(runner, app) {
|
|
536
|
-
const raw = await runner.query("logs:report", app);
|
|
537
|
-
if (!raw) return void 0;
|
|
538
|
-
return void 0;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// src/modules/registry.ts
|
|
542
|
-
async function ensureAppRegistry(runner, app, registry) {
|
|
543
|
-
for (const [key, value] of Object.entries(registry)) {
|
|
544
|
-
await runner.run("registry:set", app, key, String(value));
|
|
828
|
+
// src/commands/up.ts
|
|
829
|
+
async function runUp(ctx, config, appFilter) {
|
|
830
|
+
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
831
|
+
if (config.plugins) await ensurePlugins(ctx, config.plugins);
|
|
832
|
+
if (config.domains !== void 0) await ensureGlobalDomains(ctx, config.domains);
|
|
833
|
+
if (config.env !== void 0) await ensureGlobalConfig(ctx, config.env);
|
|
834
|
+
if (config.logs !== void 0) await ensureGlobalLogs(ctx, config.logs);
|
|
835
|
+
if (config.nginx !== void 0) await ensureGlobalNginx(ctx, config.nginx);
|
|
836
|
+
if (config.networks) await ensureNetworks(ctx, config.networks);
|
|
837
|
+
if (config.services) await ensureServices(ctx, config.services);
|
|
838
|
+
if (config.services) await ensureServiceBackups(ctx, config.services);
|
|
839
|
+
for (const app of apps) {
|
|
840
|
+
const appConfig = config.apps[app];
|
|
841
|
+
if (!appConfig) continue;
|
|
842
|
+
await reconcile(Apps, ctx, app, true);
|
|
843
|
+
await reconcile(Domains, ctx, app, appConfig.domains);
|
|
844
|
+
await reconcile(Networks, ctx, app, appConfig.networks);
|
|
845
|
+
await reconcile(NetworkProps, ctx, app, appConfig.network);
|
|
846
|
+
await reconcile(Proxy, ctx, app, appConfig.proxy?.enabled);
|
|
847
|
+
await reconcile(Ports, ctx, app, appConfig.ports);
|
|
848
|
+
if (config.services) {
|
|
849
|
+
await ensureAppLinks(ctx, app, appConfig.links ?? [], config.services);
|
|
850
|
+
}
|
|
851
|
+
await reconcile(Certs, ctx, app, appConfig.ssl);
|
|
852
|
+
await reconcile(Storage, ctx, app, appConfig.storage);
|
|
853
|
+
await reconcile(Nginx, ctx, app, appConfig.nginx);
|
|
854
|
+
await reconcile(Checks, ctx, app, appConfig.checks);
|
|
855
|
+
await reconcile(Logs, ctx, app, appConfig.logs);
|
|
856
|
+
await reconcile(Registry, ctx, app, appConfig.registry);
|
|
857
|
+
await reconcile(Scheduler, ctx, app, appConfig.scheduler);
|
|
858
|
+
await reconcile(Config, ctx, app, appConfig.env);
|
|
859
|
+
await reconcile(Builder, ctx, app, appConfig.build);
|
|
860
|
+
await reconcile(Git, ctx, app, appConfig.git ?? config.git);
|
|
861
|
+
await reconcile(DockerOptions, ctx, app, appConfig.docker_options);
|
|
545
862
|
}
|
|
546
863
|
}
|
|
547
|
-
async function exportAppRegistry(runner, app) {
|
|
548
|
-
const raw = await runner.query("registry:report", app);
|
|
549
|
-
if (!raw) return void 0;
|
|
550
|
-
return void 0;
|
|
551
|
-
}
|
|
552
864
|
|
|
553
|
-
// src/modules/
|
|
554
|
-
async function
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (
|
|
865
|
+
// src/modules/apps.ts
|
|
866
|
+
async function destroyApp(ctx, app) {
|
|
867
|
+
const exists = await ctx.check("apps:exists", app);
|
|
868
|
+
logAction(app, "Destroying app");
|
|
869
|
+
if (!exists) {
|
|
558
870
|
logSkip();
|
|
559
871
|
return;
|
|
560
872
|
}
|
|
561
|
-
await
|
|
873
|
+
await ctx.run("apps:destroy", app, "--force");
|
|
562
874
|
logDone();
|
|
563
875
|
}
|
|
564
|
-
async function
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
return scheduler;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// src/modules/config.ts
|
|
572
|
-
var MANAGED_KEYS_VAR = "DOKKU_COMPOSE_MANAGED_KEYS";
|
|
573
|
-
async function ensureAppConfig(runner, app, env) {
|
|
574
|
-
logAction(app, "Configuring env vars");
|
|
575
|
-
if (env === false) {
|
|
576
|
-
logSkip();
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const prevManagedRaw = await runner.query("config:get", app, MANAGED_KEYS_VAR);
|
|
580
|
-
const prevManaged = prevManagedRaw.trim() ? prevManagedRaw.trim().split(",").filter(Boolean) : [];
|
|
581
|
-
const desiredKeys = Object.keys(env);
|
|
582
|
-
const toUnset = prevManaged.filter((k) => !desiredKeys.includes(k));
|
|
583
|
-
if (toUnset.length > 0) {
|
|
584
|
-
await runner.run("config:unset", "--no-restart", app, ...toUnset);
|
|
585
|
-
}
|
|
586
|
-
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
587
|
-
const newManagedKeys = desiredKeys.join(",");
|
|
588
|
-
await runner.run(
|
|
589
|
-
"config:set",
|
|
590
|
-
"--no-restart",
|
|
591
|
-
app,
|
|
592
|
-
...pairs,
|
|
593
|
-
`${MANAGED_KEYS_VAR}=${newManagedKeys}`
|
|
876
|
+
async function exportApps(ctx) {
|
|
877
|
+
const output = await ctx.query("apps:list");
|
|
878
|
+
return output.split("\n").map((s) => s.trim()).filter(
|
|
879
|
+
(s) => s && !s.startsWith("=====>")
|
|
594
880
|
);
|
|
595
|
-
logDone();
|
|
596
|
-
}
|
|
597
|
-
async function ensureGlobalConfig(runner, env) {
|
|
598
|
-
if (env === false) return;
|
|
599
|
-
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
600
|
-
await runner.run("config:set", "--global", ...pairs);
|
|
601
|
-
}
|
|
602
|
-
async function exportAppConfig(runner, app) {
|
|
603
|
-
const raw = await runner.query("config:export", app, "--format", "shell");
|
|
604
|
-
if (!raw) return void 0;
|
|
605
|
-
const result = {};
|
|
606
|
-
for (const line of raw.split("\n")) {
|
|
607
|
-
const match = line.match(/^export\s+(\w+)=['"]?(.*?)['"]?$/);
|
|
608
|
-
if (match && match[1] !== MANAGED_KEYS_VAR) {
|
|
609
|
-
result[match[1]] = match[2];
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// src/modules/builder.ts
|
|
616
|
-
async function ensureAppBuilder(runner, app, build) {
|
|
617
|
-
if (build.dockerfile) {
|
|
618
|
-
await runner.run("builder-dockerfile:set", app, "dockerfile-path", build.dockerfile);
|
|
619
|
-
}
|
|
620
|
-
if (build.app_json) {
|
|
621
|
-
await runner.run("app-json:set", app, "appjson-path", build.app_json);
|
|
622
|
-
}
|
|
623
|
-
if (build.context) {
|
|
624
|
-
await runner.run("builder:set", app, "build-dir", build.context);
|
|
625
|
-
}
|
|
626
|
-
if (build.args && Object.keys(build.args).length > 0) {
|
|
627
|
-
for (const [key, value] of Object.entries(build.args)) {
|
|
628
|
-
await runner.run("docker-options:add", app, "build", `--build-arg ${key}=${value}`);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// src/modules/docker-options.ts
|
|
634
|
-
async function ensureAppDockerOptions(runner, app, options) {
|
|
635
|
-
const phases = ["build", "deploy", "run"];
|
|
636
|
-
for (const phase of phases) {
|
|
637
|
-
const opts = options[phase];
|
|
638
|
-
if (!opts || opts.length === 0) continue;
|
|
639
|
-
await runner.run("docker-options:clear", app, phase);
|
|
640
|
-
for (const opt of opts) {
|
|
641
|
-
await runner.run("docker-options:add", app, phase, opt);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// src/commands/up.ts
|
|
647
|
-
async function runUp(runner, config, appFilter) {
|
|
648
|
-
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
649
|
-
if (config.plugins) await ensurePlugins(runner, config.plugins);
|
|
650
|
-
if (config.domains !== void 0) await ensureGlobalDomains(runner, config.domains);
|
|
651
|
-
if (config.env !== void 0) await ensureGlobalConfig(runner, config.env);
|
|
652
|
-
if (config.logs !== void 0) await ensureGlobalLogs(runner, config.logs);
|
|
653
|
-
if (config.nginx !== void 0) await ensureGlobalNginx(runner, config.nginx);
|
|
654
|
-
if (config.networks) await ensureNetworks(runner, config.networks);
|
|
655
|
-
if (config.services) await ensureServices(runner, config.services);
|
|
656
|
-
for (const app of apps) {
|
|
657
|
-
const appConfig = config.apps[app];
|
|
658
|
-
if (!appConfig) continue;
|
|
659
|
-
await ensureApp(runner, app);
|
|
660
|
-
await ensureAppDomains(runner, app, appConfig.domains);
|
|
661
|
-
if (config.services) await ensureAppLinks(runner, app, appConfig.links ?? [], config.services);
|
|
662
|
-
await ensureAppNetworks(runner, app, appConfig.networks);
|
|
663
|
-
await ensureAppNetwork(runner, app, appConfig.network);
|
|
664
|
-
if (appConfig.proxy) await ensureAppProxy(runner, app, appConfig.proxy.enabled);
|
|
665
|
-
if (appConfig.ports) await ensureAppPorts(runner, app, appConfig.ports);
|
|
666
|
-
if (appConfig.ssl !== void 0) await ensureAppCerts(runner, app, appConfig.ssl);
|
|
667
|
-
if (appConfig.storage) await ensureAppStorage(runner, app, appConfig.storage);
|
|
668
|
-
if (appConfig.nginx) await ensureAppNginx(runner, app, appConfig.nginx);
|
|
669
|
-
if (appConfig.checks !== void 0) await ensureAppChecks(runner, app, appConfig.checks);
|
|
670
|
-
if (appConfig.logs) await ensureAppLogs(runner, app, appConfig.logs);
|
|
671
|
-
if (appConfig.registry) await ensureAppRegistry(runner, app, appConfig.registry);
|
|
672
|
-
if (appConfig.scheduler) await ensureAppScheduler(runner, app, appConfig.scheduler);
|
|
673
|
-
if (appConfig.env !== void 0) await ensureAppConfig(runner, app, appConfig.env);
|
|
674
|
-
if (appConfig.build) await ensureAppBuilder(runner, app, appConfig.build);
|
|
675
|
-
if (appConfig.docker_options) await ensureAppDockerOptions(runner, app, appConfig.docker_options);
|
|
676
|
-
}
|
|
677
881
|
}
|
|
678
882
|
|
|
679
883
|
// src/commands/down.ts
|
|
680
|
-
async function runDown(
|
|
884
|
+
async function runDown(ctx, config, appFilter, opts) {
|
|
681
885
|
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
682
886
|
for (const app of apps) {
|
|
683
887
|
const appConfig = config.apps[app];
|
|
684
888
|
if (!appConfig) continue;
|
|
685
889
|
if (config.services && appConfig.links) {
|
|
686
|
-
await destroyAppLinks(
|
|
890
|
+
await destroyAppLinks(ctx, app, appConfig.links, config.services);
|
|
687
891
|
}
|
|
688
|
-
await destroyApp(
|
|
892
|
+
await destroyApp(ctx, app);
|
|
689
893
|
}
|
|
690
894
|
if (config.services) {
|
|
691
|
-
await destroyServices(
|
|
895
|
+
await destroyServices(ctx, config.services);
|
|
692
896
|
}
|
|
693
897
|
if (config.networks) {
|
|
694
898
|
for (const net of config.networks) {
|
|
695
899
|
logAction("network", `Destroying ${net}`);
|
|
696
|
-
const exists = await
|
|
900
|
+
const exists = await ctx.check("network:exists", net);
|
|
697
901
|
if (!exists) {
|
|
698
902
|
logSkip();
|
|
699
903
|
continue;
|
|
700
904
|
}
|
|
701
|
-
await
|
|
905
|
+
await ctx.run("network:destroy", net, "--force");
|
|
702
906
|
logDone();
|
|
703
907
|
}
|
|
704
908
|
}
|
|
705
909
|
}
|
|
706
910
|
|
|
911
|
+
// src/resources/index.ts
|
|
912
|
+
var NETWORKING_RESOURCES = [
|
|
913
|
+
Domains,
|
|
914
|
+
Networks,
|
|
915
|
+
NetworkProps,
|
|
916
|
+
Proxy,
|
|
917
|
+
Ports
|
|
918
|
+
];
|
|
919
|
+
var CONFIG_RESOURCES = [
|
|
920
|
+
Certs,
|
|
921
|
+
Storage,
|
|
922
|
+
Nginx,
|
|
923
|
+
Checks,
|
|
924
|
+
Logs,
|
|
925
|
+
Registry,
|
|
926
|
+
Scheduler,
|
|
927
|
+
Config
|
|
928
|
+
];
|
|
929
|
+
var BUILD_RESOURCES = [
|
|
930
|
+
Builder,
|
|
931
|
+
Git,
|
|
932
|
+
DockerOptions
|
|
933
|
+
];
|
|
934
|
+
var ALL_APP_RESOURCES = [
|
|
935
|
+
...NETWORKING_RESOURCES,
|
|
936
|
+
...CONFIG_RESOURCES,
|
|
937
|
+
...BUILD_RESOURCES
|
|
938
|
+
];
|
|
939
|
+
|
|
707
940
|
// src/commands/export.ts
|
|
708
|
-
async function runExport(
|
|
941
|
+
async function runExport(ctx, opts) {
|
|
709
942
|
const config = { apps: {} };
|
|
710
|
-
const versionOutput = await
|
|
943
|
+
const versionOutput = await ctx.query("version");
|
|
711
944
|
const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
712
945
|
if (versionMatch) config.dokku = { version: versionMatch[1] };
|
|
713
|
-
const apps = opts.appFilter?.length ? opts.appFilter : await exportApps(
|
|
714
|
-
const networks = await exportNetworks(
|
|
946
|
+
const apps = opts.appFilter?.length ? opts.appFilter : await exportApps(ctx);
|
|
947
|
+
const networks = await exportNetworks(ctx);
|
|
715
948
|
if (networks.length > 0) config.networks = networks;
|
|
716
|
-
const services = await exportServices(
|
|
949
|
+
const services = await exportServices(ctx);
|
|
717
950
|
if (Object.keys(services).length > 0) config.services = services;
|
|
951
|
+
const prefetched = /* @__PURE__ */ new Map();
|
|
952
|
+
await Promise.all(
|
|
953
|
+
ALL_APP_RESOURCES.filter((r) => !r.forceApply && !r.key.startsWith("_") && r.readAll).map(async (r) => {
|
|
954
|
+
prefetched.set(r.key, await r.readAll(ctx));
|
|
955
|
+
})
|
|
956
|
+
);
|
|
718
957
|
for (const app of apps) {
|
|
719
958
|
const appConfig = {};
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const registry = await exportAppRegistry(runner, app);
|
|
739
|
-
if (registry && Object.keys(registry).length) appConfig.registry = registry;
|
|
740
|
-
const scheduler = await exportAppScheduler(runner, app);
|
|
741
|
-
if (scheduler) appConfig.scheduler = scheduler;
|
|
742
|
-
const networkCfg = await exportAppNetwork(runner, app);
|
|
743
|
-
if (networkCfg?.networks?.length) appConfig.networks = networkCfg.networks;
|
|
744
|
-
if (networkCfg?.network) appConfig.network = networkCfg.network;
|
|
745
|
-
const env = await exportAppConfig(runner, app);
|
|
746
|
-
if (env && Object.keys(env).length) appConfig.env = env;
|
|
959
|
+
for (const resource of ALL_APP_RESOURCES) {
|
|
960
|
+
if (resource.key.startsWith("_")) continue;
|
|
961
|
+
if (resource.forceApply) continue;
|
|
962
|
+
const bulk = prefetched.get(resource.key);
|
|
963
|
+
const value = bulk ? bulk.get(app) : await resource.read(ctx, app);
|
|
964
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
965
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
966
|
+
if (typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) continue;
|
|
967
|
+
if (resource.key === "proxy") {
|
|
968
|
+
appConfig.proxy = { enabled: value };
|
|
969
|
+
} else {
|
|
970
|
+
appConfig[resource.key] = value;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (Object.keys(services).length > 0) {
|
|
974
|
+
const links = await exportAppLinks(ctx, app, services);
|
|
975
|
+
if (links.length > 0) appConfig.links = links;
|
|
976
|
+
}
|
|
747
977
|
config.apps[app] = appConfig;
|
|
748
978
|
}
|
|
749
979
|
return config;
|
|
@@ -751,52 +981,45 @@ async function runExport(runner, opts) {
|
|
|
751
981
|
|
|
752
982
|
// src/commands/diff.ts
|
|
753
983
|
import chalk2 from "chalk";
|
|
754
|
-
function computeDiff(
|
|
984
|
+
async function computeDiff(ctx, config) {
|
|
755
985
|
const result = { apps: {}, services: {}, inSync: true };
|
|
756
|
-
|
|
757
|
-
|
|
986
|
+
const prefetched = /* @__PURE__ */ new Map();
|
|
987
|
+
await Promise.all(
|
|
988
|
+
ALL_APP_RESOURCES.filter((r) => !r.forceApply && !r.key.startsWith("_") && r.readAll).map(async (r) => {
|
|
989
|
+
prefetched.set(r.key, await r.readAll(ctx));
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
for (const [app, appConfig] of Object.entries(config.apps)) {
|
|
758
993
|
const appDiff = {};
|
|
759
|
-
const
|
|
760
|
-
"
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
"
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const c = currentApp[feature];
|
|
777
|
-
if (d === void 0) continue;
|
|
778
|
-
const dStr = JSON.stringify(d);
|
|
779
|
-
const cStr = JSON.stringify(c);
|
|
780
|
-
if (c === void 0) {
|
|
781
|
-
appDiff[feature] = { status: "missing", desired: d, current: void 0 };
|
|
782
|
-
result.inSync = false;
|
|
783
|
-
} else if (dStr !== cStr) {
|
|
784
|
-
appDiff[feature] = { status: "changed", desired: d, current: c };
|
|
994
|
+
for (const resource of ALL_APP_RESOURCES) {
|
|
995
|
+
if (resource.key.startsWith("_")) continue;
|
|
996
|
+
if (resource.forceApply) continue;
|
|
997
|
+
let desired;
|
|
998
|
+
if (resource.key === "proxy") {
|
|
999
|
+
desired = appConfig.proxy?.enabled;
|
|
1000
|
+
} else {
|
|
1001
|
+
desired = appConfig[resource.key];
|
|
1002
|
+
}
|
|
1003
|
+
if (desired === void 0) continue;
|
|
1004
|
+
const bulk = prefetched.get(resource.key);
|
|
1005
|
+
const current = bulk ? bulk.get(app) : await resource.read(ctx, app);
|
|
1006
|
+
const change = computeChange(current, desired);
|
|
1007
|
+
if (!change.changed) {
|
|
1008
|
+
appDiff[resource.key] = { status: "in-sync", desired, current };
|
|
1009
|
+
} else if (current === null || current === void 0 || Array.isArray(current) && current.length === 0 || typeof current === "object" && Object.keys(current).length === 0) {
|
|
1010
|
+
appDiff[resource.key] = { status: "missing", desired, current };
|
|
785
1011
|
result.inSync = false;
|
|
786
1012
|
} else {
|
|
787
|
-
appDiff[
|
|
1013
|
+
appDiff[resource.key] = { status: "changed", desired, current };
|
|
1014
|
+
result.inSync = false;
|
|
788
1015
|
}
|
|
789
1016
|
}
|
|
790
1017
|
result.apps[app] = appDiff;
|
|
791
1018
|
}
|
|
792
|
-
for (const [svc] of Object.entries(
|
|
793
|
-
const exists =
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
result.inSync = false;
|
|
797
|
-
} else {
|
|
798
|
-
result.services[svc] = { status: "in-sync" };
|
|
799
|
-
}
|
|
1019
|
+
for (const [svc, svcConfig] of Object.entries(config.services ?? {})) {
|
|
1020
|
+
const exists = await ctx.check(`${svcConfig.plugin}:exists`, svc);
|
|
1021
|
+
result.services[svc] = { status: exists ? "in-sync" : "missing" };
|
|
1022
|
+
if (!exists) result.inSync = false;
|
|
800
1023
|
}
|
|
801
1024
|
return result;
|
|
802
1025
|
}
|
|
@@ -921,14 +1144,15 @@ function makeRunner(opts) {
|
|
|
921
1144
|
program.command("up [apps...]").description("Create/update apps and services to match config").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--dry-run", "Print commands without executing").option("--fail-fast", "Stop on first error").action(async (apps, opts) => {
|
|
922
1145
|
const config = loadConfig(opts.file);
|
|
923
1146
|
const runner = makeRunner(opts);
|
|
1147
|
+
const ctx = createContext(runner);
|
|
924
1148
|
try {
|
|
925
|
-
await runUp(
|
|
1149
|
+
await runUp(ctx, config, apps);
|
|
926
1150
|
if (opts.dryRun) {
|
|
927
1151
|
console.log("\n# Commands that would run:");
|
|
928
1152
|
for (const cmd of runner.dryRunLog) console.log(`dokku ${cmd}`);
|
|
929
1153
|
}
|
|
930
1154
|
} finally {
|
|
931
|
-
await
|
|
1155
|
+
await ctx.close();
|
|
932
1156
|
}
|
|
933
1157
|
});
|
|
934
1158
|
program.command("down [apps...]").description("Destroy apps and services (requires --force)").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--force", "Required to destroy apps").action(async (apps, opts) => {
|
|
@@ -938,10 +1162,11 @@ program.command("down [apps...]").description("Destroy apps and services (requir
|
|
|
938
1162
|
}
|
|
939
1163
|
const config = loadConfig(opts.file);
|
|
940
1164
|
const runner = makeRunner({});
|
|
1165
|
+
const ctx = createContext(runner);
|
|
941
1166
|
try {
|
|
942
|
-
await runDown(
|
|
1167
|
+
await runDown(ctx, config, apps, { force: true });
|
|
943
1168
|
} finally {
|
|
944
|
-
await
|
|
1169
|
+
await ctx.close();
|
|
945
1170
|
}
|
|
946
1171
|
});
|
|
947
1172
|
program.command("validate [file]").description("Validate dokku-compose.yml without touching the server").action((file = "dokku-compose.yml") => {
|
|
@@ -962,8 +1187,9 @@ ${result.errors.length} error(s), ${result.warnings.length} warning(s)`);
|
|
|
962
1187
|
});
|
|
963
1188
|
program.command("export").description("Export server state to dokku-compose.yml format").option("--app <app>", "Export only a specific app").option("-o, --output <path>", "Write to file instead of stdout").action(async (opts) => {
|
|
964
1189
|
const runner = makeRunner({});
|
|
1190
|
+
const ctx = createContext(runner);
|
|
965
1191
|
try {
|
|
966
|
-
const result = await runExport(
|
|
1192
|
+
const result = await runExport(ctx, {
|
|
967
1193
|
appFilter: opts.app ? [opts.app] : void 0
|
|
968
1194
|
});
|
|
969
1195
|
const out = yaml3.dump(result, { lineWidth: 120 });
|
|
@@ -974,22 +1200,20 @@ program.command("export").description("Export server state to dokku-compose.yml
|
|
|
974
1200
|
process.stdout.write(out);
|
|
975
1201
|
}
|
|
976
1202
|
} finally {
|
|
977
|
-
await
|
|
1203
|
+
await ctx.close();
|
|
978
1204
|
}
|
|
979
1205
|
});
|
|
980
1206
|
program.command("diff").description("Show what is out of sync between config and server").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--verbose", "Show git-style +/- diff").action(async (opts) => {
|
|
981
1207
|
const desired = loadConfig(opts.file);
|
|
982
1208
|
const runner = makeRunner({});
|
|
1209
|
+
const ctx = createContext(runner);
|
|
983
1210
|
try {
|
|
984
|
-
const
|
|
985
|
-
appFilter: Object.keys(desired.apps)
|
|
986
|
-
});
|
|
987
|
-
const diff = computeDiff(desired, current);
|
|
1211
|
+
const diff = await computeDiff(ctx, desired);
|
|
988
1212
|
const output = opts.verbose ? formatVerbose(diff) : formatSummary(diff);
|
|
989
1213
|
process.stdout.write(output);
|
|
990
1214
|
process.exit(diff.inSync ? 0 : 1);
|
|
991
1215
|
} finally {
|
|
992
|
-
await
|
|
1216
|
+
await ctx.close();
|
|
993
1217
|
}
|
|
994
1218
|
});
|
|
995
1219
|
program.command("ps [apps...]").description("Show status of configured apps").option("-f, --file <path>", "Config file", "dokku-compose.yml").action(async (apps, opts) => {
|