ninedeploy 0.16.8 → 0.16.10
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 +17 -0
- package/dist/commands/autoscale.d.ts +38 -0
- package/dist/commands/autoscale.d.ts.map +1 -0
- package/dist/commands/autoscale.js +176 -0
- package/dist/commands/autoscale.js.map +1 -0
- package/dist/commands/canary.d.ts +37 -0
- package/dist/commands/canary.d.ts.map +1 -0
- package/dist/commands/canary.js +230 -0
- package/dist/commands/canary.js.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/commands/autoscale.ts +201 -0
- package/src/commands/canary.ts +245 -0
- package/src/index.ts +45 -0
package/README.md
CHANGED
|
@@ -164,6 +164,23 @@ ninedeploy status-pages publish|unpublish|rm <id>
|
|
|
164
164
|
ninedeploy status-pages notice <id> "Maintenance at 22:00" # --clear removes it
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
+
### 🐤 Canary Releases (0.16.9)
|
|
168
|
+
```bash
|
|
169
|
+
ninedeploy canary show web [--json] # Configuration, and the running canary: step, share, 5xx %, p95
|
|
170
|
+
ninedeploy canary set web --steps 10,50 --max-p95 800 # Options not given keep their value; --max-p95 off stops judging it
|
|
171
|
+
ninedeploy canary off web # The next deploy switches all traffic at once
|
|
172
|
+
ninedeploy canary promote web [--deployment <id>] # Skip the remaining steps; stop one with `deploys cancel`
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 📈 Autoscaling (0.16.10)
|
|
176
|
+
```bash
|
|
177
|
+
ninedeploy autoscale show web [--json] # Bounds, metric and target, containers serving, last value, desired count, last change
|
|
178
|
+
ninedeploy autoscale set web --max 4 --target 70 # Turn it on (cpu by default); --metric memory|rps, --min, --up-seconds, --down-seconds
|
|
179
|
+
ninedeploy autoscale set web --metric rps --target 200 # Options not given keep their value; rps needs traffic analytics
|
|
180
|
+
ninedeploy autoscale off web # The next deploy runs the fixed replicas again
|
|
181
|
+
```
|
|
182
|
+
Turning it on applies fully at the next deploy, which creates the spare containers.
|
|
183
|
+
|
|
167
184
|
---
|
|
168
185
|
|
|
169
186
|
## 📄 License
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type AutoscaleConfig, type AutoscaleConfigInput, type AutoscaleMetric } from '@ninedeploy/schemas';
|
|
2
|
+
import type { NineDeployClient } from '../client.js';
|
|
3
|
+
export interface AutoscaleSetOptions {
|
|
4
|
+
min?: string;
|
|
5
|
+
max?: string;
|
|
6
|
+
metric?: string;
|
|
7
|
+
target?: string;
|
|
8
|
+
upSeconds?: string;
|
|
9
|
+
downSeconds?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The fields the user gave, as a partial autoscale configuration; null after
|
|
14
|
+
* printing why one is not valid. Ranges are left to the schema.
|
|
15
|
+
*/
|
|
16
|
+
export declare function autoscaleOptions(opts: AutoscaleSetOptions): Partial<AutoscaleConfigInput> | null;
|
|
17
|
+
/**
|
|
18
|
+
* What `autoscale set` sends. A service that has a configuration keeps the
|
|
19
|
+
* values not given (the PATCH replaces the whole object). Without one, only
|
|
20
|
+
* the given values go and the server fills in the defaults; `--max` and
|
|
21
|
+
* `--target` have none, so they are named when missing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function autoscaleBody(current: AutoscaleConfig | null | undefined, given: Partial<AutoscaleConfigInput>): {
|
|
24
|
+
body: AutoscaleConfigInput;
|
|
25
|
+
} | {
|
|
26
|
+
missing: string[];
|
|
27
|
+
};
|
|
28
|
+
/** A metric value with its unit: `70%` for cpu and memory, `120 req/s` for rps. */
|
|
29
|
+
export declare function fmtMetric(metric: AutoscaleMetric, v: number): string;
|
|
30
|
+
/** `ninedeploy autoscale show <service> [--json]` */
|
|
31
|
+
export declare function autoscaleShow(client: NineDeployClient, ref: string, opts?: {
|
|
32
|
+
json?: boolean;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
/** `ninedeploy autoscale set <service> [...]` */
|
|
35
|
+
export declare function autoscaleSet(client: NineDeployClient, ref: string, opts?: AutoscaleSetOptions): Promise<void>;
|
|
36
|
+
/** `ninedeploy autoscale off <service>` */
|
|
37
|
+
export declare function autoscaleOff(client: NineDeployClient, ref: string): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=autoscale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoscale.d.ts","sourceRoot":"","sources":["../../src/commands/autoscale.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAuBrD,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAsBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAqBhG;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC3C,KAAK,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACnC;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAKxD;AAOD,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpE;AA2BD,qDAAqD;AACrD,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBvH;AAED,iDAAiD;AACjD,wBAAsB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BvH;AAED,2CAA2C;AAC3C,wBAAsB,YAAY,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASvF"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { AUTOSCALE_METRICS, autoscaleConfig, } from '@ninedeploy/schemas';
|
|
2
|
+
import { c, error, fmtTime, header, info, kv, spinner, success } from '../lib/format.js';
|
|
3
|
+
import { fmtSeconds } from './canary.js';
|
|
4
|
+
import { plain } from './sources.js';
|
|
5
|
+
import { resolveServiceRef } from './uptime.js';
|
|
6
|
+
/**
|
|
7
|
+
* 0.16.10 autoscaling:
|
|
8
|
+
*
|
|
9
|
+
* ninedeploy autoscale show <service> [--json]
|
|
10
|
+
* ninedeploy autoscale set <service> [--min N] [--max N] [--metric cpu|memory|rps] [--target X]
|
|
11
|
+
* [--up-seconds N] [--down-seconds N] [--json]
|
|
12
|
+
* ninedeploy autoscale off <service>
|
|
13
|
+
*
|
|
14
|
+
* `<service>` is an id, a slug or a name. Turning it on applies fully at the
|
|
15
|
+
* next deploy, which creates the spare containers.
|
|
16
|
+
*/
|
|
17
|
+
const message = (err) => plain(err instanceof Error ? err.message : String(err));
|
|
18
|
+
/** Skip the spinner for `--json` so stdout stays parseable. */
|
|
19
|
+
const load = (json, msg, fn) => (json ? fn() : spinner(msg, fn));
|
|
20
|
+
/** Flag names for the schema fields, so a validation failure names what the user typed. */
|
|
21
|
+
const FLAG = {
|
|
22
|
+
minReplicas: '--min',
|
|
23
|
+
maxReplicas: '--max',
|
|
24
|
+
metric: '--metric',
|
|
25
|
+
target: '--target',
|
|
26
|
+
scaleUpSeconds: '--up-seconds',
|
|
27
|
+
scaleDownSeconds: '--down-seconds',
|
|
28
|
+
};
|
|
29
|
+
const NUMBERS = [
|
|
30
|
+
['min', 'minReplicas'],
|
|
31
|
+
['max', 'maxReplicas'],
|
|
32
|
+
['target', 'target'],
|
|
33
|
+
['upSeconds', 'scaleUpSeconds'],
|
|
34
|
+
['downSeconds', 'scaleDownSeconds'],
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* The fields the user gave, as a partial autoscale configuration; null after
|
|
38
|
+
* printing why one is not valid. Ranges are left to the schema.
|
|
39
|
+
*/
|
|
40
|
+
export function autoscaleOptions(opts) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [opt, field] of NUMBERS) {
|
|
43
|
+
const raw = opts[opt];
|
|
44
|
+
if (typeof raw !== 'string')
|
|
45
|
+
continue;
|
|
46
|
+
const n = Number(raw);
|
|
47
|
+
if (raw.trim() === '' || !Number.isFinite(n)) {
|
|
48
|
+
error(`${FLAG[field]} must be a number`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
out[field] = n;
|
|
52
|
+
}
|
|
53
|
+
if (opts.metric !== undefined) {
|
|
54
|
+
const metric = opts.metric.trim().toLowerCase();
|
|
55
|
+
if (!AUTOSCALE_METRICS.includes(metric)) {
|
|
56
|
+
error(`--metric must be one of ${AUTOSCALE_METRICS.join(', ')}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
out.metric = metric;
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* What `autoscale set` sends. A service that has a configuration keeps the
|
|
65
|
+
* values not given (the PATCH replaces the whole object). Without one, only
|
|
66
|
+
* the given values go and the server fills in the defaults; `--max` and
|
|
67
|
+
* `--target` have none, so they are named when missing.
|
|
68
|
+
*/
|
|
69
|
+
export function autoscaleBody(current, given) {
|
|
70
|
+
if (current)
|
|
71
|
+
return { body: { ...current, ...given } };
|
|
72
|
+
const missing = ['maxReplicas', 'target'].filter((f) => given[f] === undefined).map((f) => FLAG[f]);
|
|
73
|
+
if (missing.length > 0)
|
|
74
|
+
return { missing };
|
|
75
|
+
return { body: given };
|
|
76
|
+
}
|
|
77
|
+
/** A zod failure as one line, each issue led by the flag it is about. */
|
|
78
|
+
function validationMessage(err) {
|
|
79
|
+
return err.issues.map((i) => `${FLAG[String(i.path[0])] ?? 'autoscale'}: ${i.message}`).join('; ');
|
|
80
|
+
}
|
|
81
|
+
/** A metric value with its unit: `70%` for cpu and memory, `120 req/s` for rps. */
|
|
82
|
+
export function fmtMetric(metric, v) {
|
|
83
|
+
return metric === 'rps' ? `${v} req/s` : `${v}%`;
|
|
84
|
+
}
|
|
85
|
+
const containers = (n) => `${n} container${n === 1 ? '' : 's'}`;
|
|
86
|
+
function printConfig(cfg) {
|
|
87
|
+
if (!cfg) {
|
|
88
|
+
kv('Autoscaling', c.gray('off: the service runs its fixed replicas'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
kv('Bounds', `${cfg.minReplicas}–${cfg.maxReplicas} containers`);
|
|
92
|
+
kv('Metric', `${cfg.metric}, target ${fmtMetric(cfg.metric, cfg.target)} per container`);
|
|
93
|
+
kv('Scale up after', fmtSeconds(cfg.scaleUpSeconds));
|
|
94
|
+
kv('Scale down after', fmtSeconds(cfg.scaleDownSeconds));
|
|
95
|
+
}
|
|
96
|
+
function printStatus(st) {
|
|
97
|
+
console.log();
|
|
98
|
+
kv('Serving', containers(st.replicas));
|
|
99
|
+
if (!st.enabled)
|
|
100
|
+
return;
|
|
101
|
+
const metric = st.metric ?? 'cpu';
|
|
102
|
+
kv('Last value', st.value === null || st.value === undefined ? c.gray('not read yet') : `${fmtMetric(metric, st.value)} per container`);
|
|
103
|
+
kv('Desired', st.desired === null || st.desired === undefined ? null : containers(st.desired));
|
|
104
|
+
if (st.holding)
|
|
105
|
+
info(`Not scaling now: ${plain(st.holding)}.`);
|
|
106
|
+
const ev = st.lastEvent;
|
|
107
|
+
kv('Last change', ev ? `${ev.from} → ${ev.to} containers, ${plain(ev.reason)}, ${fmtTime(ev.at)}` : c.gray('none yet'));
|
|
108
|
+
}
|
|
109
|
+
/** `ninedeploy autoscale show <service> [--json]` */
|
|
110
|
+
export async function autoscaleShow(client, ref, opts = {}) {
|
|
111
|
+
try {
|
|
112
|
+
const id = await resolveServiceRef(client, ref);
|
|
113
|
+
if (id === null)
|
|
114
|
+
return;
|
|
115
|
+
const [svc, status] = await load(opts.json, 'Loading autoscaling', () => Promise.all([client.services.get(id), client.services.autoscale(id)]));
|
|
116
|
+
if (opts.json) {
|
|
117
|
+
console.log(JSON.stringify({ config: svc.autoscale ?? null, status }, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
header(`Autoscaling: ${plain(svc.name)}`);
|
|
121
|
+
printConfig(svc.autoscale);
|
|
122
|
+
printStatus(status);
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
error(message(err));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** `ninedeploy autoscale set <service> [...]` */
|
|
130
|
+
export async function autoscaleSet(client, ref, opts = {}) {
|
|
131
|
+
const given = autoscaleOptions(opts);
|
|
132
|
+
if (given === null)
|
|
133
|
+
return;
|
|
134
|
+
try {
|
|
135
|
+
const id = await resolveServiceRef(client, ref);
|
|
136
|
+
if (id === null)
|
|
137
|
+
return;
|
|
138
|
+
const svc = await client.services.get(id);
|
|
139
|
+
const built = autoscaleBody(svc.autoscale, given);
|
|
140
|
+
if ('missing' in built) {
|
|
141
|
+
error(`Autoscaling is off for this service: give ${built.missing.join(' and ')} to turn it on.`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const parsed = autoscaleConfig.safeParse(built.body);
|
|
145
|
+
if (!parsed.success) {
|
|
146
|
+
error(validationMessage(parsed.error));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const body = built.body;
|
|
150
|
+
const updated = await load(opts.json, 'Saving the autoscaling configuration', () => client.services.update(id, { autoscale: body }));
|
|
151
|
+
if (opts.json) {
|
|
152
|
+
console.log(JSON.stringify(updated.autoscale ?? null, null, 2));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
success(`Autoscaling on for ${plain(updated.name)}.`);
|
|
156
|
+
printConfig(updated.autoscale);
|
|
157
|
+
info('It applies fully at the next deploy, which creates the spare containers.');
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
error(message(err));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** `ninedeploy autoscale off <service>` */
|
|
164
|
+
export async function autoscaleOff(client, ref) {
|
|
165
|
+
try {
|
|
166
|
+
const id = await resolveServiceRef(client, ref);
|
|
167
|
+
if (id === null)
|
|
168
|
+
return;
|
|
169
|
+
const updated = await spinner('Turning autoscaling off', () => client.services.update(id, { autoscale: null }));
|
|
170
|
+
success(`Autoscaling off for ${plain(updated.name)}. The containers serving now keep running until the next deploy, which runs the fixed replicas again.`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
error(message(err));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=autoscale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoscale.js","sourceRoot":"","sources":["../../src/commands/autoscale.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type CanaryConfig, type CanaryConfigInput } from '@ninedeploy/schemas';
|
|
2
|
+
import type { NineDeployClient } from '../client.js';
|
|
3
|
+
export interface CanarySetOptions {
|
|
4
|
+
steps?: string;
|
|
5
|
+
stepSeconds?: string;
|
|
6
|
+
maxErrorRate?: string;
|
|
7
|
+
maxP95?: string;
|
|
8
|
+
minRequests?: string;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The fields the user gave, as a partial canary configuration; null after
|
|
13
|
+
* printing why one is not valid. Ranges are left to the schema.
|
|
14
|
+
*/
|
|
15
|
+
export declare function canaryOptions(opts: CanarySetOptions): Partial<CanaryConfigInput> | null;
|
|
16
|
+
/**
|
|
17
|
+
* What `canary set` sends. A service that has a configuration keeps the
|
|
18
|
+
* values not given (the PATCH replaces the whole object). Without one, only
|
|
19
|
+
* the given values go, and the server fills in the rest; `steps` has no
|
|
20
|
+
* server default, so the documented default (10, 50) is sent when not given.
|
|
21
|
+
*/
|
|
22
|
+
export declare function canaryBody(current: CanaryConfig | null | undefined, given: Partial<CanaryConfigInput>): CanaryConfigInput;
|
|
23
|
+
/** Seconds as `1m 20s`. */
|
|
24
|
+
export declare function fmtSeconds(total: number): string;
|
|
25
|
+
/** `ninedeploy canary show <service> [--json]` */
|
|
26
|
+
export declare function canaryShow(client: NineDeployClient, ref: string, opts?: {
|
|
27
|
+
json?: boolean;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
/** `ninedeploy canary set <service> [...]` */
|
|
30
|
+
export declare function canarySet(client: NineDeployClient, ref: string, opts?: CanarySetOptions): Promise<void>;
|
|
31
|
+
/** `ninedeploy canary off <service>` */
|
|
32
|
+
export declare function canaryOff(client: NineDeployClient, ref: string): Promise<void>;
|
|
33
|
+
/** `ninedeploy canary promote <service> [--deployment <id>]` */
|
|
34
|
+
export declare function canaryPromote(client: NineDeployClient, ref: string, opts?: {
|
|
35
|
+
deployment?: string;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=canary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.d.ts","sourceRoot":"","sources":["../../src/commands/canary.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAGvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAwBrD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAqBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAgCvF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAGzH;AAOD,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAwCD,kDAAkD;AAClD,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBpH;AAED,8CAA8C;AAC9C,wBAAsB,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBjH;AAED,wCAAwC;AACxC,wBAAsB,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpF;AAED,gEAAgE;AAChE,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B5H"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { CANARY_DEFAULT_STEPS, canaryConfig, } from '@ninedeploy/schemas';
|
|
2
|
+
import { c, error, header, info, kv, spinner, success } from '../lib/format.js';
|
|
3
|
+
import { plain } from './sources.js';
|
|
4
|
+
import { resolveServiceRef } from './uptime.js';
|
|
5
|
+
/**
|
|
6
|
+
* 0.16.9 canary releases:
|
|
7
|
+
*
|
|
8
|
+
* ninedeploy canary show <service> [--json]
|
|
9
|
+
* ninedeploy canary set <service> [--steps 10,50] [--step-seconds 120] [--max-error-rate 5]
|
|
10
|
+
* [--max-p95 <ms>|off] [--min-requests 20] [--json]
|
|
11
|
+
* ninedeploy canary off <service>
|
|
12
|
+
* ninedeploy canary promote <service> [--deployment <id>]
|
|
13
|
+
*
|
|
14
|
+
* `<service>` is an id, a slug or a name. Stopping a running canary is
|
|
15
|
+
* `ninedeploy deploys cancel <serviceId> <deployId>`: all traffic goes back
|
|
16
|
+
* to the previous release.
|
|
17
|
+
*/
|
|
18
|
+
const message = (err) => plain(err instanceof Error ? err.message : String(err));
|
|
19
|
+
/** Skip the spinner for `--json` so stdout stays parseable. */
|
|
20
|
+
const load = (json, msg, fn) => (json ? fn() : spinner(msg, fn));
|
|
21
|
+
/** Flag names for the schema fields, so a validation failure names what the user typed. */
|
|
22
|
+
const FLAG = {
|
|
23
|
+
steps: '--steps',
|
|
24
|
+
stepSeconds: '--step-seconds',
|
|
25
|
+
maxErrorRate: '--max-error-rate',
|
|
26
|
+
maxP95Ms: '--max-p95',
|
|
27
|
+
minRequests: '--min-requests',
|
|
28
|
+
};
|
|
29
|
+
/** A number option; null after printing why not. */
|
|
30
|
+
function numberOption(raw, flag) {
|
|
31
|
+
const n = Number(raw);
|
|
32
|
+
if (raw.trim() === '' || !Number.isFinite(n)) {
|
|
33
|
+
error(`${flag} must be a number`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The fields the user gave, as a partial canary configuration; null after
|
|
40
|
+
* printing why one is not valid. Ranges are left to the schema.
|
|
41
|
+
*/
|
|
42
|
+
export function canaryOptions(opts) {
|
|
43
|
+
const out = {};
|
|
44
|
+
if (opts.steps !== undefined) {
|
|
45
|
+
const parts = opts.steps.split(',').map((s) => s.trim());
|
|
46
|
+
const steps = parts.map(Number);
|
|
47
|
+
if (parts.some((p) => p === '') || steps.some((n) => !Number.isFinite(n))) {
|
|
48
|
+
error('--steps must be percentages separated by commas, for example 10,50');
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
out.steps = steps;
|
|
52
|
+
}
|
|
53
|
+
const numbers = [
|
|
54
|
+
['stepSeconds', 'stepSeconds'],
|
|
55
|
+
['maxErrorRate', 'maxErrorRate'],
|
|
56
|
+
['minRequests', 'minRequests'],
|
|
57
|
+
];
|
|
58
|
+
for (const [opt, field] of numbers) {
|
|
59
|
+
const raw = opts[opt];
|
|
60
|
+
if (typeof raw !== 'string')
|
|
61
|
+
continue;
|
|
62
|
+
const n = numberOption(raw, FLAG[field]);
|
|
63
|
+
if (n === null)
|
|
64
|
+
return null;
|
|
65
|
+
out[field] = n;
|
|
66
|
+
}
|
|
67
|
+
if (opts.maxP95 !== undefined) {
|
|
68
|
+
if (opts.maxP95.trim().toLowerCase() === 'off')
|
|
69
|
+
out.maxP95Ms = null;
|
|
70
|
+
else {
|
|
71
|
+
const n = numberOption(opts.maxP95, '--max-p95');
|
|
72
|
+
if (n === null)
|
|
73
|
+
return null;
|
|
74
|
+
out.maxP95Ms = n;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* What `canary set` sends. A service that has a configuration keeps the
|
|
81
|
+
* values not given (the PATCH replaces the whole object). Without one, only
|
|
82
|
+
* the given values go, and the server fills in the rest; `steps` has no
|
|
83
|
+
* server default, so the documented default (10, 50) is sent when not given.
|
|
84
|
+
*/
|
|
85
|
+
export function canaryBody(current, given) {
|
|
86
|
+
if (current)
|
|
87
|
+
return { ...current, ...given };
|
|
88
|
+
return { ...given, steps: given.steps ?? [...CANARY_DEFAULT_STEPS] };
|
|
89
|
+
}
|
|
90
|
+
/** A zod failure as one line, each issue led by the flag it is about. */
|
|
91
|
+
function validationMessage(err) {
|
|
92
|
+
return err.issues.map((i) => `${FLAG[String(i.path[0])] ?? 'canary'}: ${i.message}`).join('; ');
|
|
93
|
+
}
|
|
94
|
+
/** Seconds as `1m 20s`. */
|
|
95
|
+
export function fmtSeconds(total) {
|
|
96
|
+
const s = Math.max(0, Math.round(total));
|
|
97
|
+
const m = Math.floor(s / 60);
|
|
98
|
+
return m > 0 ? `${m}m ${s % 60}s` : `${s}s`;
|
|
99
|
+
}
|
|
100
|
+
function printConfig(cfg) {
|
|
101
|
+
if (!cfg) {
|
|
102
|
+
kv('Canary', c.gray('off: a deploy switches all traffic at once'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
kv('Steps', cfg.steps.map((s) => `${s}%`).join(', '));
|
|
106
|
+
kv('Step length', fmtSeconds(cfg.stepSeconds));
|
|
107
|
+
kv('Max 5xx rate', `${cfg.maxErrorRate}%`);
|
|
108
|
+
kv('Max p95', cfg.maxP95Ms === null ? c.gray('not judged') : `${cfg.maxP95Ms} ms`);
|
|
109
|
+
kv('Min requests', cfg.minRequests);
|
|
110
|
+
}
|
|
111
|
+
function stats(s) {
|
|
112
|
+
if (!s)
|
|
113
|
+
return c.gray('—');
|
|
114
|
+
const rate = s.errorRate === null ? '—' : `${s.errorRate}%`;
|
|
115
|
+
const p95 = s.p95Ms === null ? '—' : `${s.p95Ms} ms`;
|
|
116
|
+
return `${s.requests} requests, 5xx ${rate}, p95 ${p95}`;
|
|
117
|
+
}
|
|
118
|
+
function printStatus(st, now) {
|
|
119
|
+
console.log();
|
|
120
|
+
if (!st.active) {
|
|
121
|
+
info('No canary is running.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(` ${c.bold('Running canary')}`);
|
|
125
|
+
kv('Deployment', `#${st.deploymentId}`);
|
|
126
|
+
kv('Step', `${st.step}/${st.steps}`);
|
|
127
|
+
kv('Traffic', `${st.weight}% to the new release`);
|
|
128
|
+
if (st.stepEndsAt)
|
|
129
|
+
kv('Step ends in', fmtSeconds((Date.parse(st.stepEndsAt) - now) / 1000));
|
|
130
|
+
kv('New release', stats(st.canary));
|
|
131
|
+
kv('Previous', stats(st.stable));
|
|
132
|
+
if (st.measured === false) {
|
|
133
|
+
info('Traffic analytics is off: steps are judged on the healthcheck only.');
|
|
134
|
+
}
|
|
135
|
+
if (st.promoting)
|
|
136
|
+
info('Promote requested: all traffic is switching to the new release.');
|
|
137
|
+
}
|
|
138
|
+
/** `ninedeploy canary show <service> [--json]` */
|
|
139
|
+
export async function canaryShow(client, ref, opts = {}) {
|
|
140
|
+
try {
|
|
141
|
+
const id = await resolveServiceRef(client, ref);
|
|
142
|
+
if (id === null)
|
|
143
|
+
return;
|
|
144
|
+
const [svc, status] = await load(opts.json, 'Loading the canary', () => Promise.all([client.services.get(id), client.deploys.canary(id)]));
|
|
145
|
+
if (opts.json) {
|
|
146
|
+
console.log(JSON.stringify({ config: svc.canary ?? null, status }, null, 2));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
header(`Canary releases: ${plain(svc.name)}`);
|
|
150
|
+
printConfig(svc.canary);
|
|
151
|
+
printStatus(status, Date.now());
|
|
152
|
+
console.log();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
error(message(err));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** `ninedeploy canary set <service> [...]` */
|
|
159
|
+
export async function canarySet(client, ref, opts = {}) {
|
|
160
|
+
const given = canaryOptions(opts);
|
|
161
|
+
if (given === null)
|
|
162
|
+
return;
|
|
163
|
+
try {
|
|
164
|
+
const id = await resolveServiceRef(client, ref);
|
|
165
|
+
if (id === null)
|
|
166
|
+
return;
|
|
167
|
+
const svc = await client.services.get(id);
|
|
168
|
+
const body = canaryBody(svc.canary, given);
|
|
169
|
+
const parsed = canaryConfig.safeParse(body);
|
|
170
|
+
if (!parsed.success) {
|
|
171
|
+
error(validationMessage(parsed.error));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const updated = await load(opts.json, 'Saving the canary configuration', () => client.services.update(id, { canary: body }));
|
|
175
|
+
if (opts.json) {
|
|
176
|
+
console.log(JSON.stringify(updated.canary ?? null, null, 2));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
success(`Canary releases on for ${plain(updated.name)}. The next deploy uses them.`);
|
|
180
|
+
printConfig(updated.canary);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
error(message(err));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** `ninedeploy canary off <service>` */
|
|
187
|
+
export async function canaryOff(client, ref) {
|
|
188
|
+
try {
|
|
189
|
+
const id = await resolveServiceRef(client, ref);
|
|
190
|
+
if (id === null)
|
|
191
|
+
return;
|
|
192
|
+
const updated = await spinner('Turning canary releases off', () => client.services.update(id, { canary: null }));
|
|
193
|
+
success(`Canary releases off for ${plain(updated.name)}. A deploy switches all traffic at once.`);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
error(message(err));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** `ninedeploy canary promote <service> [--deployment <id>]` */
|
|
200
|
+
export async function canaryPromote(client, ref, opts = {}) {
|
|
201
|
+
let depId;
|
|
202
|
+
if (opts.deployment !== undefined) {
|
|
203
|
+
const n = Number(opts.deployment);
|
|
204
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
205
|
+
error('--deployment must be a positive integer');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
depId = n;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const id = await resolveServiceRef(client, ref);
|
|
212
|
+
if (id === null)
|
|
213
|
+
return;
|
|
214
|
+
if (depId === undefined) {
|
|
215
|
+
const status = await client.deploys.canary(id);
|
|
216
|
+
if (!status.active || status.deploymentId === undefined) {
|
|
217
|
+
error('No canary is running on this service.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
depId = status.deploymentId;
|
|
221
|
+
}
|
|
222
|
+
const target = depId;
|
|
223
|
+
await spinner('Promoting the canary', () => client.deploys.promoteCanary(id, target));
|
|
224
|
+
success(`Deployment #${target}: all traffic is switching to the new release.`);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
error(message(err));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=canary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary.js","sourceRoot":"","sources":["../../src/commands/canary.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,8 @@ import { panelBackupDecryptAction, panelBackupListAction, panelBackupNowAction,
|
|
|
48
48
|
import { serversList, serversRoles, servicesPlacement, servicesSwarm, servicesTransfers, sourcesAllowOnNodes, sourcesHostKeys, sourcesPinHostKeys, sourcesResetHostKeys, swarmInit, swarmJoin, swarmLeave, swarmSetEnabled, swarmStatus, volumesCreate, } from './commands/multiNode.js';
|
|
49
49
|
// ── end 0.16 T8 ──
|
|
50
50
|
import { monitorsAdd, monitorsCheck, monitorsList, monitorsRemove, monitorsSetEnabled, monitorsShow, statusPagesAdd, statusPagesList, statusPagesNotice, statusPagesRemove, statusPagesSetPublished, } from './commands/uptime.js';
|
|
51
|
+
import { canaryOff, canaryPromote, canarySet, canaryShow } from './commands/canary.js';
|
|
52
|
+
import { autoscaleOff, autoscaleSet, autoscaleShow } from './commands/autoscale.js';
|
|
51
53
|
const program = new Command();
|
|
52
54
|
program
|
|
53
55
|
.name('ninedeploy')
|
|
@@ -988,6 +990,47 @@ statusPagesCmd.command('rm <id>')
|
|
|
988
990
|
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
989
991
|
.action((id, opts) => statusPagesRemove(getClient(), id, opts));
|
|
990
992
|
/* v8 ignore stop */
|
|
993
|
+
// ── 0.16.9: canary releases ──────────────────────────────────────────────
|
|
994
|
+
const canaryCmd = program.command('canary').description('Canary releases: shift traffic to a new release in steps');
|
|
995
|
+
/* v8 ignore start -- exercised by test/canary.test.ts. */
|
|
996
|
+
canaryCmd.command('show <service>')
|
|
997
|
+
.description('The canary configuration and the canary running now')
|
|
998
|
+
.option('--json', 'Print raw JSON')
|
|
999
|
+
.action((service, opts) => canaryShow(getClient(), service, opts));
|
|
1000
|
+
canaryCmd.command('set <service>')
|
|
1001
|
+
.description('Turn canary releases on or change them (docker services); options not given keep their value')
|
|
1002
|
+
.option('--steps <list>', 'Percent of requests per step, rising, 1-99, up to 5 (default 10,50)')
|
|
1003
|
+
.option('--step-seconds <n>', 'Seconds per step (30-3600, default 120)')
|
|
1004
|
+
.option('--max-error-rate <percent>', 'Highest share of 5xx responses the new release may have (default 5)')
|
|
1005
|
+
.option('--max-p95 <ms>', 'Highest p95 response time in ms, or off (default off)')
|
|
1006
|
+
.option('--min-requests <n>', 'Fewer requests in a step are not judged (default 20)')
|
|
1007
|
+
.option('--json', 'Print the stored configuration as JSON')
|
|
1008
|
+
.action((service, opts) => canarySet(getClient(), service, opts));
|
|
1009
|
+
canaryCmd.command('off <service>').description('Turn canary releases off').action((service) => canaryOff(getClient(), service));
|
|
1010
|
+
canaryCmd.command('promote <service>')
|
|
1011
|
+
.description('Skip the remaining steps and send all traffic to the new release now')
|
|
1012
|
+
.option('--deployment <id>', 'The deployment to promote (default: the one running)')
|
|
1013
|
+
.action((service, opts) => canaryPromote(getClient(), service, opts));
|
|
1014
|
+
/* v8 ignore stop */
|
|
1015
|
+
// ── 0.16.10: autoscaling ─────────────────────────────────────────────────
|
|
1016
|
+
const autoscaleCmd = program.command('autoscale').description('Autoscaling: run between a minimum and a maximum number of containers, set by load');
|
|
1017
|
+
/* v8 ignore start -- exercised by test/autoscale.test.ts. */
|
|
1018
|
+
autoscaleCmd.command('show <service>')
|
|
1019
|
+
.description('The autoscaling configuration and what it is doing now')
|
|
1020
|
+
.option('--json', 'Print raw JSON')
|
|
1021
|
+
.action((service, opts) => autoscaleShow(getClient(), service, opts));
|
|
1022
|
+
autoscaleCmd.command('set <service>')
|
|
1023
|
+
.description('Turn autoscaling on or change it (docker services); options not given keep their value')
|
|
1024
|
+
.option('--min <n>', 'Containers it never goes below (1-10, default 1)')
|
|
1025
|
+
.option('--max <n>', 'Containers it never goes above (2-10, above --min; required to turn it on)')
|
|
1026
|
+
.option('--metric <metric>', 'cpu, memory or rps (default cpu)')
|
|
1027
|
+
.option('--target <value>', 'Value per container to keep near: percent for cpu/memory, requests per second for rps (required to turn it on)')
|
|
1028
|
+
.option('--up-seconds <n>', 'How long the need must last before adding containers (30-3600, default 60)')
|
|
1029
|
+
.option('--down-seconds <n>', 'How long the need must last before removing containers (60-3600, default 300)')
|
|
1030
|
+
.option('--json', 'Print the stored configuration as JSON')
|
|
1031
|
+
.action((service, opts) => autoscaleSet(getClient(), service, opts));
|
|
1032
|
+
autoscaleCmd.command('off <service>').description('Turn autoscaling off').action((service) => autoscaleOff(getClient(), service));
|
|
1033
|
+
/* v8 ignore stop */
|
|
991
1034
|
deploys.command('watch <serviceId> <deployId>').description('Stream a deployment\'s build logs live').action((svcId, depId) => deploysWatch(svcId, depId));
|
|
992
1035
|
// ── Diagnostics ───────────────────────────────────────────────────────────
|
|
993
1036
|
program
|