@brutalsystems/dray 0.1.12 → 0.1.13
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 -1
- package/package.json +1 -1
- package/src/core/engine.js +16 -16
- package/src/primitives/kubectl.js +27 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ dray list # registered repos + targets
|
|
|
37
37
|
dray ship <repo>:<target> # deps?→build→push(SHA)→render+apply→rollout
|
|
38
38
|
dray ship <repo> # all enabled workloads in the repo (skips manual ones)
|
|
39
39
|
dray apply <repo>:<target> # render + apply manifests only
|
|
40
|
-
dray rollout <repo>:<target>
|
|
40
|
+
dray rollout <repo>:<target> # restart + wait; skips the restart if one is already rolling
|
|
41
41
|
dray status <repo> # running image SHA vs HEAD
|
|
42
42
|
dray rollback <repo>:<target> <sha>
|
|
43
43
|
dray publish <repo>[:<pilet>] # publish pilet(s) via sops exec-env
|
package/package.json
CHANGED
package/src/core/engine.js
CHANGED
|
@@ -52,25 +52,25 @@ async function execute(steps, { dryRun = false, allowDirty = false, deps } = {})
|
|
|
52
52
|
const vars = {}; for (const s of u.stamp) vars[s.var] = `${s.repoUri}:${sha}`;
|
|
53
53
|
const files = d.render.renderManifests(u.manifests, vars, u.repoPath, { dryRun });
|
|
54
54
|
if (files[0]) renderDirs.push(path.dirname(files[0]));
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
// Applying may or may not change the pod template; the rollout step below decides
|
|
56
|
+
// whether a restart is needed by asking the CLUSTER, not by us telling it from here.
|
|
57
|
+
// A flag set in this step cannot reach a separate `dray rollout` invocation.
|
|
58
|
+
for (const f of files) await d.kubectl.applyFile({ file: f, context: u.defaults.context, namespace: u.defaults.namespace, dryRun });
|
|
59
|
+
} else if (step.kind === 'rollout') {
|
|
60
|
+
// If the controller is already rolling this deployment -- because an apply just
|
|
61
|
+
// changed the pod template, in THIS process or a previous `dray apply` invocation --
|
|
62
|
+
// then `rollout restart` would start a second one, producing a second ReplicaSet and
|
|
63
|
+
// replacing every pod twice. Wait for the one in flight instead.
|
|
60
64
|
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
u.
|
|
65
|
-
if (!dryRun && u.workload && u.kind !== 'cronjob') {
|
|
65
|
+
// Conservative on doubt (dry run, cronjob, unreadable deployment): keep restarting,
|
|
66
|
+
// because a skipped restart silently does nothing at all.
|
|
67
|
+
let inFlight = false;
|
|
68
|
+
if (!dryRun && u.kind !== 'cronjob') {
|
|
66
69
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch { u._applyStartsRollout = false; }
|
|
70
|
+
inFlight = await d.kubectl.rolloutInProgress({ deployment: u.workload, context: u.defaults.context, namespace: u.defaults.namespace });
|
|
71
|
+
} catch { inFlight = false; }
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
} else if (step.kind === 'rollout') {
|
|
73
|
-
try { await d.kubectl.rollout({ deployment: u.workload, context: u.defaults.context, namespace: u.defaults.namespace, dryRun, restart: !u._applyStartsRollout }); }
|
|
73
|
+
try { await d.kubectl.rollout({ deployment: u.workload, context: u.defaults.context, namespace: u.defaults.namespace, dryRun, restart: !inFlight }); }
|
|
74
74
|
catch (err) { await d.kubectl.rolloutUndo({ deployment: u.workload, context: u.defaults.context, namespace: u.defaults.namespace, dryRun }); throw err; }
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -10,6 +10,32 @@ async function rollout({ deployment, context, namespace, dryRun, restart = true
|
|
|
10
10
|
return _run('kubectl', ['rollout', 'status', `deployment/${deployment}`, '--timeout=180s', ...ns(context, namespace)], { dryRun });
|
|
11
11
|
}
|
|
12
12
|
function rolloutUndo({ deployment, context, namespace, dryRun }) { return _run('kubectl', ['rollout', 'undo', `deployment/${deployment}`, ...ns(context, namespace)], { dryRun }); }
|
|
13
|
+
// Is the Deployment controller ALREADY rolling out a change?
|
|
14
|
+
//
|
|
15
|
+
// Whether a rollout is under way is a property of the cluster, not of this process. An
|
|
16
|
+
// earlier design inferred it in the apply step and passed it to the rollout step through a
|
|
17
|
+
// field on the unit -- which works for `dray ship` but silently fails for `dray apply &&
|
|
18
|
+
// dray rollout`, two separate invocations with no shared memory. That is the shape every CI
|
|
19
|
+
// pipeline uses, so every deploy there rolled twice. Ask the cluster instead.
|
|
20
|
+
//
|
|
21
|
+
// Unreadable or absent deployment -> false, i.e. keep restarting. Conservative in the same
|
|
22
|
+
// direction as before: a missed restart does nothing at all, which is the worse failure.
|
|
23
|
+
async function rolloutInProgress({ deployment, context, namespace }) {
|
|
24
|
+
const { code, stdout } = await _run('kubectl', ['get', `deployment/${deployment}`, '-o', 'json', ...ns(context, namespace)], { capture: true, allowFail: true });
|
|
25
|
+
if (code !== 0) return false;
|
|
26
|
+
let d;
|
|
27
|
+
try { d = JSON.parse(stdout); } catch { return false; }
|
|
28
|
+
const gen = d.metadata && d.metadata.generation;
|
|
29
|
+
const obs = d.status && d.status.observedGeneration;
|
|
30
|
+
if (typeof gen !== 'number') return false;
|
|
31
|
+
// The controller has not yet observed the spec we just applied.
|
|
32
|
+
if (gen !== obs) return true;
|
|
33
|
+
const want = (d.spec && typeof d.spec.replicas === 'number') ? d.spec.replicas : 1;
|
|
34
|
+
const updated = (d.status && d.status.updatedReplicas) || 0;
|
|
35
|
+
const available = (d.status && d.status.availableReplicas) || 0;
|
|
36
|
+
// Pods are still converging on the desired template.
|
|
37
|
+
return updated < want || available < want;
|
|
38
|
+
}
|
|
13
39
|
async function runningImage({ workload, kind, context, namespace }) {
|
|
14
40
|
const kindPath = kind === 'cronjob'
|
|
15
41
|
? ['cronjob/' + workload, '-o', 'jsonpath={.spec.jobTemplate.spec.template.spec.containers[0].image}']
|
|
@@ -17,4 +43,4 @@ async function runningImage({ workload, kind, context, namespace }) {
|
|
|
17
43
|
const { stdout } = await _run('kubectl', ['get', ...kindPath, ...ns(context, namespace)], { capture: true, allowFail: true });
|
|
18
44
|
return stdout.trim();
|
|
19
45
|
}
|
|
20
|
-
module.exports = { applyFile, rollout, rolloutUndo, runningImage, _withRun };
|
|
46
|
+
module.exports = { applyFile, rollout, rolloutUndo, runningImage, rolloutInProgress, _withRun };
|