@flisk-sh/flisk-import 0.8.0 → 0.10.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/README.md +1 -0
- package/dist/detectors/compose.js +43 -2
- package/dist/merge.js +1 -0
- package/dist/write.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ flisk import . --yes # write service.json files and project setup
|
|
|
12
12
|
|
|
13
13
|
- **Reads one file:** the root `docker-compose.yml`, or `compose.yaml`, `compose.yml` or `docker-compose.yaml` when that is absent. Not override files, nested Compose files, Dockerfiles on their own, `package.json`, or Procfiles.
|
|
14
14
|
- **Writes:** one bring-your-own `service.json` per Compose service, plus `.flisk/project.json` if the repo is not a Flisk project yet. It never edits the Compose file or overwrites a `service.json` without `--force`.
|
|
15
|
+
- **Keeps the install's data:** each named volume gets `name`, the Docker volume `docker compose up` created (`<project>_<volume>`, its own `name:`, or an `external` volume), so `flisk up` mounts the same data with no copy (needs the Flisk release with flisk#2964).
|
|
15
16
|
- **Keeps:** the service names (`db` stays `db`, so it stays the hostname), the build context and Dockerfile (or the image), the container port as written (3000, 80, 5432), `depends_on`, and an HTTP health path. Databases and brokers are private, with no proxy route.
|
|
16
17
|
- **Runtime keys:** named volumes and bind mounts inside the project (`volumes`), `cap_add`, `cap_drop`, `init`, `read_only`, `user`, `security_opt` and `shm_size` (`container`), extra published ports (`extraPorts`, on 127.0.0.1 only), `network_mode: service:<name>` (`sidecarOf`) and `restart`.
|
|
17
18
|
- **Environment:** values that cannot be secrets go into the committed `service.json` `env`: numbers, `true`/`false`, another imported service's address without a user, password or query (`http://invidious:3000`, `redis:6379`), and pure `${VAR}` references. Every other name, and any name that looks like a credential (`PASSWORD`, `TOKEN`, `KEY`, ...), goes into `secrets.required`, with its literal value copied to the project `.env`, which is added to `.gitignore`. Values in `.env` already are kept.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { dirname, isAbsolute, join, normalize } from "node:path/posix";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, normalize } from "node:path/posix";
|
|
3
3
|
import { isAlias, isMap, parseDocument, visit } from "yaml";
|
|
4
4
|
import { HEALTH_CHECK_PATH_PATTERN } from "../flisk.js";
|
|
5
5
|
import { registerDetector } from "../registry.js";
|
|
@@ -46,6 +46,7 @@ const TRANSLATED_KEYS = new Set([
|
|
|
46
46
|
"restart",
|
|
47
47
|
"command",
|
|
48
48
|
"entrypoint",
|
|
49
|
+
"models",
|
|
49
50
|
// Flisk gives every service its own stack network and container name, so these change nothing it needs.
|
|
50
51
|
"networks",
|
|
51
52
|
"expose",
|
|
@@ -188,6 +189,26 @@ function volumeOf(entry, composeDir) {
|
|
|
188
189
|
return undefined;
|
|
189
190
|
return { source: resolved, target, ...(readOnly ? { readOnly } : {}), bind };
|
|
190
191
|
}
|
|
192
|
+
/** Compose's project name: top-level `name`, else the directory name, lowercased with other characters dropped. */
|
|
193
|
+
function projectNameOf(root, absPath) {
|
|
194
|
+
const raw = isObj(root) && typeof root.name === "string" && !root.name.includes("$")
|
|
195
|
+
? root.name
|
|
196
|
+
: basename(dirname(absPath));
|
|
197
|
+
const name = raw.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
198
|
+
return /^[a-z0-9]/.test(name) ? name : undefined;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The Docker name of a named volume, as `docker compose up` created it: its own `name:`, the key itself when it is
|
|
202
|
+
* `external`, else `<project>_<key>`. Undefined when the name uses Compose variables.
|
|
203
|
+
*/
|
|
204
|
+
function dockerVolumeName(key, volumes, project) {
|
|
205
|
+
const def = isObj(volumes) ? volumes[key] : undefined;
|
|
206
|
+
if (isObj(def) && typeof def.name === "string")
|
|
207
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(def.name) ? def.name : undefined;
|
|
208
|
+
if (isObj(def) && (def.external === true || isObj(def.external)))
|
|
209
|
+
return key;
|
|
210
|
+
return project ? `${project}_${key}` : undefined;
|
|
211
|
+
}
|
|
191
212
|
function envNames(environment) {
|
|
192
213
|
if (Array.isArray(environment)) {
|
|
193
214
|
return environment
|
|
@@ -290,6 +311,20 @@ function healthProbeOf(healthcheck) {
|
|
|
290
311
|
...(retries ? { retries } : {}),
|
|
291
312
|
};
|
|
292
313
|
}
|
|
314
|
+
/** The models a service uses, joined to their top-level definitions. A model without a literal `model` is skipped. */
|
|
315
|
+
function modelsOf(uses, defined) {
|
|
316
|
+
const names = Array.isArray(uses) ? uses : isObj(uses) ? Object.keys(uses) : [];
|
|
317
|
+
const out = [];
|
|
318
|
+
for (const name of names) {
|
|
319
|
+
const def = isObj(defined) && typeof name === "string" ? defined[name] : undefined;
|
|
320
|
+
if (typeof name === "string" &&
|
|
321
|
+
isObj(def) &&
|
|
322
|
+
typeof def.model === "string" &&
|
|
323
|
+
/^[A-Za-z0-9_.-]+$/.test(name))
|
|
324
|
+
out.push({ name, model: def.model });
|
|
325
|
+
}
|
|
326
|
+
return out.length ? out : undefined;
|
|
327
|
+
}
|
|
293
328
|
function healthPathOf(healthcheck) {
|
|
294
329
|
if (!isObj(healthcheck) || !Array.isArray(healthcheck.test))
|
|
295
330
|
return undefined;
|
|
@@ -343,6 +378,7 @@ registerDetector({
|
|
|
343
378
|
if (!services || !isMap(doc.get("services", true))) {
|
|
344
379
|
return { candidates, skips: [...skips, { ...base, reason: "no `services:` map" }] };
|
|
345
380
|
}
|
|
381
|
+
const project = projectNameOf(root, absPath);
|
|
346
382
|
for (const [name, raw] of Object.entries(services)) {
|
|
347
383
|
if (!isObj(raw)) {
|
|
348
384
|
skips.push({ ...base, reason: `service "${name}" is not a map` });
|
|
@@ -417,6 +453,7 @@ registerDetector({
|
|
|
417
453
|
? Object.keys(raw.depends_on)
|
|
418
454
|
: [];
|
|
419
455
|
const command = commandOf(raw.command);
|
|
456
|
+
const models = modelsOf(raw.models, isObj(root) ? root.models : undefined);
|
|
420
457
|
const containerCommand = containerCommandOf(raw.command);
|
|
421
458
|
const containerEntrypoint = containerCommandOf(raw.entrypoint);
|
|
422
459
|
const healthProbe = healthProbeOf(raw.healthcheck);
|
|
@@ -461,8 +498,11 @@ registerDetector({
|
|
|
461
498
|
const volumes = [];
|
|
462
499
|
for (const entry of Array.isArray(raw.volumes) ? raw.volumes : []) {
|
|
463
500
|
const volume = volumeOf(entry, composeDir);
|
|
501
|
+
const dockerName = volume && !volume.bind
|
|
502
|
+
? dockerVolumeName(volume.source, isObj(root) ? root.volumes : undefined, project)
|
|
503
|
+
: undefined;
|
|
464
504
|
if (volume)
|
|
465
|
-
volumes.push(volume);
|
|
505
|
+
volumes.push(dockerName ? { ...volume, name: dockerName } : volume);
|
|
466
506
|
else
|
|
467
507
|
skips.push({
|
|
468
508
|
...base,
|
|
@@ -508,6 +548,7 @@ registerDetector({
|
|
|
508
548
|
...(command ? { command } : {}),
|
|
509
549
|
...(containerCommand ? { containerCommand } : {}),
|
|
510
550
|
...(containerEntrypoint ? { containerEntrypoint } : {}),
|
|
551
|
+
...(models ? { models } : {}),
|
|
511
552
|
...(healthProbe ? { healthCheck: healthProbe } : {}),
|
|
512
553
|
...(selectedPort !== undefined ? { port: selectedPort } : {}),
|
|
513
554
|
...(healthCheckPath ? { healthCheckPath } : {}),
|
package/dist/merge.js
CHANGED
|
@@ -73,6 +73,7 @@ function mergeGroup(name, dir, members, resolve) {
|
|
|
73
73
|
...firstOf(ordered, "restart"),
|
|
74
74
|
...firstOf(ordered, "containerCommand"),
|
|
75
75
|
...firstOf(ordered, "containerEntrypoint"),
|
|
76
|
+
...firstOf(ordered, "models"),
|
|
76
77
|
...firstOf(ordered, "healthCheck"),
|
|
77
78
|
...(ordered.find((c) => c.sidecarOf)?.sidecarOf
|
|
78
79
|
? { sidecarOf: resolve(ordered.find((c) => c.sidecarOf)?.sidecarOf) }
|
package/dist/write.js
CHANGED
|
@@ -271,14 +271,17 @@ export function planWrites(plan, force) {
|
|
|
271
271
|
...(secretNames.length ? { secrets: { required: secretNames } } : {}),
|
|
272
272
|
...(service.restart ? { restart: service.restart } : {}),
|
|
273
273
|
...(service.container ? { container: service.container } : {}),
|
|
274
|
+
...(service.models ? { models: service.models } : {}),
|
|
274
275
|
...(extraPorts.length ? { extraPorts } : {}),
|
|
275
276
|
...(sidecarOf ? { sidecarOf } : {}),
|
|
276
277
|
...(volumes.length
|
|
277
278
|
? {
|
|
278
|
-
|
|
279
|
+
// `name` keeps the Docker volume the Compose install already has, so its data is used with no copy.
|
|
280
|
+
volumes: volumes.map(({ source, target, readOnly, name }) => ({
|
|
279
281
|
source,
|
|
280
282
|
target,
|
|
281
283
|
...(readOnly ? { readOnly } : {}),
|
|
284
|
+
...(name ? { name } : {}),
|
|
282
285
|
})),
|
|
283
286
|
}
|
|
284
287
|
: {}),
|
package/package.json
CHANGED