@yadurajfleetos/cli 0.9.1 → 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/dist/commands/up.js +12 -1
- package/dist/progress.js +99 -3
- package/package.json +1 -1
package/dist/commands/up.js
CHANGED
|
@@ -147,7 +147,18 @@ async function deployOne(service, opts) {
|
|
|
147
147
|
const result = (await request('POST', `/services/${service.id}/deploy`, {
|
|
148
148
|
body: { gitSha: opts.gitSha, contextId },
|
|
149
149
|
})).body;
|
|
150
|
-
walker.finish(
|
|
150
|
+
// Deliberately not walker.finish().
|
|
151
|
+
//
|
|
152
|
+
// The control plane answers as soon as a node is chosen and builds
|
|
153
|
+
// afterwards, so this response arrives before the build starts.
|
|
154
|
+
// Finishing here marked build, push, schedule and container as "not
|
|
155
|
+
// needed" — while the build ran for three minutes — and left a silent
|
|
156
|
+
// counter that reads as a hang. The steps are real; only the reply is
|
|
157
|
+
// early. Progress keeps driving the ladder until the phases are
|
|
158
|
+
// genuinely done.
|
|
159
|
+
walker.advance(2, `scheduled onto ${result.placedOn.name}`);
|
|
160
|
+
await progress.untilSettled({ deadlineMs: 45 * 60_000 });
|
|
161
|
+
walker.finish();
|
|
151
162
|
return result;
|
|
152
163
|
}
|
|
153
164
|
finally {
|
package/dist/progress.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* a persistently unreachable control plane as an error.
|
|
15
15
|
*/
|
|
16
16
|
import { request, CliError, EXIT } from './api.js';
|
|
17
|
+
import { bar } from './ui.js';
|
|
17
18
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
18
19
|
/** A failure reason can be a build log tail; a one-line error gets one line of it. */
|
|
19
20
|
export const firstLine = (text) => text.split('\n')[0].trim().slice(0, 200);
|
|
@@ -30,7 +31,51 @@ export function progressLine(p) {
|
|
|
30
31
|
return undefined;
|
|
31
32
|
const counter = p.step && p.ofSteps ? `${p.step}/${p.ofSteps} ` : '';
|
|
32
33
|
const platform = p.platform ? `${p.platform.replace(/^linux\//, '')} ` : '';
|
|
33
|
-
|
|
34
|
+
// Emulation is the answer to "why is this taking so long", and a build that
|
|
35
|
+
// takes three minutes instead of twenty seconds is almost always this. Said
|
|
36
|
+
// once, on the line already being drawn, rather than as a separate warning.
|
|
37
|
+
const how = p.emulated ? ' (emulated — slow)' : '';
|
|
38
|
+
return `${counter}${platform}${p.detail}${how}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A duration a person reads, not a step suffix.
|
|
42
|
+
*
|
|
43
|
+
* ui.ts has `duration`, which is dim, colour-wrapped, prefixed with a space
|
|
44
|
+
* and renders three minutes as "180s" — right for the end of a finished step,
|
|
45
|
+
* wrong for a sentence about how long is left.
|
|
46
|
+
*/
|
|
47
|
+
function human(ms) {
|
|
48
|
+
const total = Math.max(0, Math.round(ms / 1000));
|
|
49
|
+
if (total < 60)
|
|
50
|
+
return `${total}s`;
|
|
51
|
+
const minutes = Math.floor(total / 60);
|
|
52
|
+
const seconds = total % 60;
|
|
53
|
+
if (minutes < 60)
|
|
54
|
+
return seconds ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
55
|
+
const hours = Math.floor(minutes / 60);
|
|
56
|
+
return `${hours}h ${minutes % 60}m`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Elapsed against what this service usually takes.
|
|
60
|
+
*
|
|
61
|
+
* Only from real history: on a first deploy there is nothing honest to say,
|
|
62
|
+
* and a bar filled from a number nobody measured is the same lie as the "not
|
|
63
|
+
* needed" it replaces. Overrunning is shown as overrunning rather than parked
|
|
64
|
+
* at the end of the bar — a deploy that is genuinely slow today is exactly
|
|
65
|
+
* when somebody needs to know.
|
|
66
|
+
*/
|
|
67
|
+
export function etaLine(p, now = Date.now()) {
|
|
68
|
+
if (!p.typicalMs || !p.since)
|
|
69
|
+
return undefined;
|
|
70
|
+
const elapsed = now - new Date(p.since).getTime();
|
|
71
|
+
if (elapsed < 0)
|
|
72
|
+
return undefined;
|
|
73
|
+
const fraction = Math.min(1, elapsed / p.typicalMs);
|
|
74
|
+
const left = p.typicalMs - elapsed;
|
|
75
|
+
const tail = left > 0
|
|
76
|
+
? `~${human(left)} left`
|
|
77
|
+
: `${human(-left)} over the usual ${human(p.typicalMs)}`;
|
|
78
|
+
return `${bar(fraction)} ${human(elapsed)} · ${tail}`;
|
|
34
79
|
}
|
|
35
80
|
/**
|
|
36
81
|
* Poll `/progress` in the background while something else is being awaited.
|
|
@@ -45,6 +90,11 @@ export function follow(serviceId, sink, opts = {}) {
|
|
|
45
90
|
let stopped = false;
|
|
46
91
|
let misses = 0;
|
|
47
92
|
let wake = null;
|
|
93
|
+
/** Resolves once the deploy has left the phases this ladder draws. */
|
|
94
|
+
let settled = () => { };
|
|
95
|
+
const settledWhen = new Promise((resolve) => {
|
|
96
|
+
settled = resolve;
|
|
97
|
+
});
|
|
48
98
|
const rest = (ms) => new Promise((resolve) => {
|
|
49
99
|
const timer = setTimeout(resolve, ms);
|
|
50
100
|
wake = () => {
|
|
@@ -60,8 +110,24 @@ export function follow(serviceId, sink, opts = {}) {
|
|
|
60
110
|
try {
|
|
61
111
|
const progress = await fetchProgress(serviceId);
|
|
62
112
|
misses = 0;
|
|
63
|
-
if (progress)
|
|
113
|
+
if (progress) {
|
|
64
114
|
sink(progress);
|
|
115
|
+
// `deploying` means the build and push are behind us and the node
|
|
116
|
+
// has been told; everything after that is the rollout, which the
|
|
117
|
+
// caller reports on separately.
|
|
118
|
+
// `status` carries the phase: queued → building → pushing →
|
|
119
|
+
// scheduling → deploying. Anything at or past `deploying` means the
|
|
120
|
+
// build and push are behind us; the rest is the rollout, which the
|
|
121
|
+
// caller reports on separately.
|
|
122
|
+
if (progress.status === 'deploying' || progress.status === 'running')
|
|
123
|
+
settled();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// No progress row at all means the build phases are over: the row is
|
|
127
|
+
// dropped once a deploy leaves them, and a deploy that never wrote
|
|
128
|
+
// one had nothing to build.
|
|
129
|
+
settled();
|
|
130
|
+
}
|
|
65
131
|
}
|
|
66
132
|
catch {
|
|
67
133
|
// A control plane that predates the endpoint answers 404 every time, so
|
|
@@ -73,12 +139,35 @@ export function follow(serviceId, sink, opts = {}) {
|
|
|
73
139
|
}
|
|
74
140
|
}
|
|
75
141
|
})();
|
|
142
|
+
// The loop ending for any reason — including a control plane with no
|
|
143
|
+
// progress endpoint — has to release anybody waiting, or a missing feature
|
|
144
|
+
// becomes a hang.
|
|
145
|
+
void loop.then(() => settled()).catch(() => settled());
|
|
76
146
|
return {
|
|
77
147
|
stop: async () => {
|
|
78
148
|
stopped = true;
|
|
79
149
|
wake?.();
|
|
80
150
|
await loop.catch(() => { });
|
|
81
151
|
},
|
|
152
|
+
/**
|
|
153
|
+
* Wait until the build phases are done.
|
|
154
|
+
*
|
|
155
|
+
* Bounded: a deploy whose progress never arrives must not hold the ladder
|
|
156
|
+
* open for ever. On the deadline this returns rather than throwing — the
|
|
157
|
+
* caller's own wait is what decides whether the deploy failed, and two
|
|
158
|
+
* things reporting the same failure is worse than one.
|
|
159
|
+
*/
|
|
160
|
+
untilSettled: async ({ deadlineMs = 45 * 60_000 } = {}) => {
|
|
161
|
+
let timer;
|
|
162
|
+
await Promise.race([
|
|
163
|
+
settledWhen,
|
|
164
|
+
new Promise((resolve) => {
|
|
165
|
+
timer = setTimeout(resolve, deadlineMs);
|
|
166
|
+
}),
|
|
167
|
+
]);
|
|
168
|
+
if (timer)
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
},
|
|
82
171
|
};
|
|
83
172
|
}
|
|
84
173
|
/** Ladder steps for a deploy, in the order the control plane reports them. */
|
|
@@ -140,7 +229,14 @@ export function phaseWalker(l, steps = DEPLOY_STEPS) {
|
|
|
140
229
|
// poll, and it belongs on the step that decided it.
|
|
141
230
|
advance(target, at === 0 ? (p.nodeName ?? undefined) : undefined);
|
|
142
231
|
}
|
|
143
|
-
|
|
232
|
+
// The build line, or the estimate when there is no build line to show.
|
|
233
|
+
//
|
|
234
|
+
// Both on one row rather than two: the ladder redraws a fixed region and
|
|
235
|
+
// a row that appears and disappears makes the whole block jump. During a
|
|
236
|
+
// build the sub-step is the more useful of the two — it is proof of
|
|
237
|
+
// movement — and the estimate carries the rest of the wait, when the
|
|
238
|
+
// node is pulling an image and nothing is being logged at all.
|
|
239
|
+
const line = progressLine(p) ?? etaLine(p);
|
|
144
240
|
if (line && at < steps.length)
|
|
145
241
|
l.detail(steps[at].key, line);
|
|
146
242
|
},
|