@brutalsystems/dray 0.1.5 → 0.1.7
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 +14 -1
- package/package.json +1 -1
- package/src/cli/index.js +1 -0
- package/src/config/registry.js +19 -1
- package/src/core/engine.js +1 -1
- package/src/primitives/docker.js +11 -1
package/README.md
CHANGED
|
@@ -60,7 +60,11 @@ Global flags: `--dry-run` (print the plan, run nothing), `--allow-dirty`
|
|
|
60
60
|
// optional: inject --build-arg from an env file (e.g. VITE_* from .env.production),
|
|
61
61
|
// or from a sops-encrypted file decrypted in memory at build time (no plaintext on disk):
|
|
62
62
|
// "buildArgs": { "sopsEnvFile": "secrets.env", "prefix": "VITE_" }
|
|
63
|
-
"buildArgs": { "envFile": ".env.production", "prefix": "VITE_" }
|
|
63
|
+
"buildArgs": { "envFile": ".env.production", "prefix": "VITE_" },
|
|
64
|
+
// optional: also move a mutable <ecr>:latest tag onto every (non-dirty)
|
|
65
|
+
// push, so workloads that reference <ecr>:latest track the newest build
|
|
66
|
+
// without being re-shipped (e.g. rarely-shipped cronjobs sharing an image).
|
|
67
|
+
"latest": true },
|
|
64
68
|
// image from another repo (cloned to a tmp dir, built, pushed):
|
|
65
69
|
{ "name": "svc", "ecr": "svc", "source": { "git": "https://github.com/org/svc", "ref": "main" } }
|
|
66
70
|
],
|
|
@@ -91,3 +95,12 @@ Manifests reference managed images by a placeholder var — `${APP_IMAGE}`
|
|
|
91
95
|
substitutes `<ecr>:<gitSHA>` for that var across the listed files (container
|
|
92
96
|
image, env-var image refs, cronjob images — uniformly). A raw `kubectl apply`
|
|
93
97
|
of an unrendered manifest fails loud, so applies always go through dray.
|
|
98
|
+
|
|
99
|
+
An image with `"latest": true` additionally gets a mutable `<ecr>:latest` tag
|
|
100
|
+
moved onto each non-dirty push. This is for the opposite need: a workload that
|
|
101
|
+
should track the newest build of a shared image *without* being re-shipped
|
|
102
|
+
(e.g. a low-frequency cronjob sharing an image with a frequently-shipped
|
|
103
|
+
service). Reference `<ecr>:latest` literally in that manifest (with
|
|
104
|
+
`imagePullPolicy: Always`) instead of the `${..._IMAGE}` placeholder. You trade
|
|
105
|
+
away per-commit reproducibility/rollback for that workload — use it only where
|
|
106
|
+
that's the point.
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -3,6 +3,7 @@ async function run(argv) {
|
|
|
3
3
|
if (argv.length <= 2) { await require('./menu').menu(); return; }
|
|
4
4
|
const program = new Command();
|
|
5
5
|
program.name('dray').description('Convention-driven multi-repo deploy orchestrator');
|
|
6
|
+
program.version(require('../../package.json').version, '-v, --version', 'show dray version');
|
|
6
7
|
program.option('--dry-run', 'print planned commands, run nothing');
|
|
7
8
|
program.option('--allow-dirty', 'allow building from a dirty tree (tags :<sha>-dirty)');
|
|
8
9
|
for (const c of ['version', 'init', 'add', 'remove', 'reload', 'list',
|
package/src/config/registry.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
const fs = require('node:fs'); const path = require('node:path'); const { REGISTRY } = require('../constants');
|
|
2
|
+
const { loadRepoConfig } = require('./repo');
|
|
2
3
|
function loadRegistry(file = REGISTRY) { return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {}; }
|
|
3
4
|
function saveRegistry(file = REGISTRY, reg = {}) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, JSON.stringify(reg, null, 2)); }
|
|
4
5
|
function addRepo(file, { name, path: p, config }) { const r = loadRegistry(file); r[name] = { path: p, config, addedAt: new Date().toISOString() }; saveRegistry(file, r); return r[name]; }
|
|
5
6
|
function getRepo(file, name) { return loadRegistry(file)[name]; }
|
|
6
7
|
function removeRepo(file, name) { const r = loadRegistry(file); delete r[name]; saveRegistry(file, r); }
|
|
7
|
-
|
|
8
|
+
// The registry caches a copy of each repo's config. Auto-reload it when the
|
|
9
|
+
// live .dray/config.json is newer than that snapshot, so edits take effect
|
|
10
|
+
// without a manual `dray reload`. Falls back to the snapshot when the repo
|
|
11
|
+
// isn't checked out here (stat throws) or its config is momentarily invalid
|
|
12
|
+
// (loadRepoConfig throws) — a broken repo must not break `list`/`--all`.
|
|
13
|
+
function refreshEntry(entry) {
|
|
14
|
+
try {
|
|
15
|
+
const mtime = Math.floor(fs.statSync(path.join(entry.path, '.dray', 'config.json')).mtimeMs);
|
|
16
|
+
if (mtime <= (Date.parse(entry.addedAt) || 0)) return entry;
|
|
17
|
+
return { ...entry, config: loadRepoConfig(entry.path), addedAt: new Date(mtime).toISOString() };
|
|
18
|
+
} catch { return entry; }
|
|
19
|
+
}
|
|
20
|
+
function allRepos(file = REGISTRY) {
|
|
21
|
+
const reg = loadRegistry(file); let changed = false;
|
|
22
|
+
for (const [n, e] of Object.entries(reg)) { const f = refreshEntry(e); if (f !== e) { reg[n] = f; changed = true; } }
|
|
23
|
+
if (changed) saveRegistry(file, reg);
|
|
24
|
+
return reg;
|
|
25
|
+
}
|
|
8
26
|
module.exports = { loadRegistry, saveRegistry, addRepo, getRepo, removeRepo, allRepos };
|
package/src/core/engine.js
CHANGED
|
@@ -42,7 +42,7 @@ async function execute(steps, { dryRun = false, allowDirty = false, deps } = {})
|
|
|
42
42
|
else if (step.kind === 'build') await d.docker.buildImage({ ecrUri: u.repoUri, sha, dockerfile: u.image.dockerfile, context: u.image.context || '.', platform: u.defaults.platform, cwd, dryRun, buildArgs: await d.buildArgs.resolveBuildArgs(u.image, cwd, { dryRun }) });
|
|
43
43
|
else if (step.kind === 'push') {
|
|
44
44
|
if (!loggedIn) { await d.docker.ecrLogin({ account: u.defaults.account, region: u.defaults.region, profile: u.defaults.profile, dryRun }); await d.docker.ensureRepo({ ecr: u.image.ecr, account: u.defaults.account, region: u.defaults.region, profile: u.defaults.profile, dryRun }); loggedIn = true; }
|
|
45
|
-
await d.docker.pushImage({ ecrUri: u.repoUri, sha, dryRun });
|
|
45
|
+
await d.docker.pushImage({ ecrUri: u.repoUri, sha, latest: !!u.image.latest, dryRun });
|
|
46
46
|
} else if (step.kind === 'apply') {
|
|
47
47
|
const vars = {}; for (const s of u.stamp) vars[s.var] = `${s.repoUri}:${sha}`;
|
|
48
48
|
const files = d.render.renderManifests(u.manifests, vars, u.repoPath, { dryRun });
|
package/src/primitives/docker.js
CHANGED
|
@@ -6,7 +6,17 @@ function buildImage({ ecrUri, sha, dockerfile, context, platform, cwd, dryRun, b
|
|
|
6
6
|
args.push('--load', context);
|
|
7
7
|
return _run('docker', args, { cwd, dryRun });
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
// Push the immutable :<sha> tag. When the image opts into `latest: true`, also
|
|
10
|
+
// move a mutable :latest tag onto this build so workloads that reference
|
|
11
|
+
// <ecr>:latest (e.g. rarely-shipped cronjobs) track the newest build without
|
|
12
|
+
// being re-shipped. Never advance :latest for a dirty (`-dirty`) build.
|
|
13
|
+
async function pushImage({ ecrUri, sha, latest = false, dryRun }) {
|
|
14
|
+
await _run('docker', ['push', `${ecrUri}:${sha}`], { dryRun });
|
|
15
|
+
if (latest && !String(sha).endsWith('-dirty')) {
|
|
16
|
+
await _run('docker', ['tag', `${ecrUri}:${sha}`, `${ecrUri}:latest`], { dryRun });
|
|
17
|
+
await _run('docker', ['push', `${ecrUri}:latest`], { dryRun });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
function buildDeps(image, cwd, dryRun) {
|
|
11
21
|
const d = image.depsImage;
|
|
12
22
|
return _run('docker', ['buildx', 'build', '--platform', 'linux/arm64', '-f', d.dockerfile, '-t', d.tag, '--load', '.'], { cwd, dryRun });
|