@cat-indev/catops-cli 0.0.1-alpha.5 → 0.0.1-alpha.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 +267 -182
- package/dist/core/Notifier.d.ts +16 -1
- package/dist/core/Notifier.js +26 -1
- package/dist/core/Notifier.js.map +1 -1
- package/dist/core/messages.d.ts +89 -0
- package/dist/core/messages.js +130 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/types.d.ts +8 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# devops-cli
|
|
2
2
|
|
|
3
|
-
Framework
|
|
3
|
+
Framework para pipelines DevOps, escrito en **TypeScript** (100% usable desde JavaScript puro), empaquetado como librería npm instalable en cualquier proyecto.
|
|
4
|
+
|
|
5
|
+
Trae:
|
|
6
|
+
|
|
7
|
+
- Un **ExecutionContext** compartido (`flags`, `params`, `env`, `vars`, `results`, `logger`, `services`, `notifier`) para que ninguna task tenga que recibir parámetros manualmente.
|
|
8
|
+
- **14 servicios** listos (`shell`, `docker`, `git`, `kubectl`, `helm`, `npm`, `archive`, `terraform`, `ansible`, `argocd`, `tekton`, `oc`, `az`, `azdo`).
|
|
9
|
+
- **Menús interactivos** con navegación anidada y **selección automática por flag** (para correr pipelines sin prompts, ideal para CI).
|
|
10
|
+
- **retry / timeout / dryRun** en cada comando de shell.
|
|
11
|
+
- Validación cíclica de rollouts de Kubernetes/OpenShift (`waitForDeployment`, `waitForDeploymentGroup`).
|
|
12
|
+
- **Callbacks de éxito/error por tarea** + un sistema de **notificaciones clasificadas por área de TI**, con mensajes personalizables y senders (`log`, `file`, `http`, `webhook`, `websocket`).
|
|
4
13
|
|
|
5
14
|
## Instalación
|
|
6
15
|
|
|
@@ -16,10 +25,10 @@ npm install @miorg/devops-cli
|
|
|
16
25
|
|
|
17
26
|
```bash
|
|
18
27
|
# Dentro del repo de devops-cli
|
|
19
|
-
npm pack # genera devops-cli
|
|
28
|
+
npm pack # genera devops-cli-<version>.tgz
|
|
20
29
|
|
|
21
30
|
# Dentro del proyecto que lo va a consumir
|
|
22
|
-
npm install /ruta/a/devops-cli
|
|
31
|
+
npm install /ruta/a/devops-cli-<version>.tgz
|
|
23
32
|
```
|
|
24
33
|
|
|
25
34
|
**Opción C — enlazado local con `npm link` (para desarrollar la librería y el proyecto que la consume al mismo tiempo):**
|
|
@@ -40,7 +49,13 @@ npm install git+https://github.com/tu-org/devops-cli.git
|
|
|
40
49
|
|
|
41
50
|
Cualquiera de las 4 deja disponibles dos cosas en el proyecto consumidor:
|
|
42
51
|
|
|
43
|
-
1. La librería
|
|
52
|
+
1. La librería, tanto desde TS como desde JS puro:
|
|
53
|
+
```typescript
|
|
54
|
+
import { Context, Menu, services, type MenuDefinition } from "devops-cli";
|
|
55
|
+
```
|
|
56
|
+
```javascript
|
|
57
|
+
const { Context, Menu, services } = require("devops-cli");
|
|
58
|
+
```
|
|
44
59
|
2. El binario: `npx devops-cli` (o `devops-cli` si lo instalaste global con `-g`).
|
|
45
60
|
|
|
46
61
|
## Publicar una nueva versión
|
|
@@ -50,63 +65,41 @@ npm version patch # o minor / major
|
|
|
50
65
|
npm publish # agrega --access public si usas un scope (@miorg/devops-cli)
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Une dos piezas que ya tenías:
|
|
68
|
+
`prepublishOnly` corre el build y los tests automáticamente antes de publicar.
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
2. Los **servicios de shell** (`shell`, `docker`, `git`, `kubectl`, `helm`, `npm`, `archive`), integrados **tal cual** los diste, sin reescribir su lógica.
|
|
70
|
+
## TypeScript
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
## Estructura
|
|
72
|
+
Todo `src/` está escrito en TypeScript, con `strict: true`. `npm run build` compila a `dist/` (JS + `.d.ts` + source maps por archivo) — eso es lo único que se publica (ver `files` en `package.json`).
|
|
63
73
|
|
|
64
74
|
```
|
|
65
75
|
src/
|
|
66
76
|
core/
|
|
67
|
-
Context.
|
|
68
|
-
Menu.
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
Context.ts -> ExecutionContext singleton (Context.current() / Context.parseArgv())
|
|
78
|
+
Menu.ts -> Menu.render() con navegación anidada + selección automática por flag
|
|
79
|
+
Notifier.ts -> clasificación de errores por área + canales + senders
|
|
80
|
+
classifiers.ts -> fábricas de ErrorClassifier: byCommand, byPattern
|
|
81
|
+
messages.ts -> fábricas de ErrorMessageFormatter: byPattern, byCommand, byRule
|
|
82
|
+
senders.ts -> fábricas de Sender: log, file, http, webhook, websocket
|
|
83
|
+
prompt.ts -> ctx.ask / ctx.confirm / ctx.select (sin dependencias externas)
|
|
84
|
+
logger.ts -> logger usado por Context y por shell.ts
|
|
85
|
+
types.ts -> tipos compartidos (MenuDefinition, ExecOptions, NotificationEvent, ...)
|
|
71
86
|
services/
|
|
72
|
-
shell.
|
|
73
|
-
docker.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
terraform.js -> init/plan/apply/destroy/output/validate/fmt
|
|
80
|
-
ansible.js -> playbook/adhoc/vaultEncrypt/vaultDecrypt/galaxyInstall
|
|
81
|
-
argocd.js -> login/appSync/appGet/appWait/appSet/appList/appRollback
|
|
82
|
-
tekton.js -> pipelineStart/pipelinerunList/pipelinerunLogs/taskStart/taskrunLogs
|
|
83
|
-
oc.js -> login/project/apply/get/rollout/newApp/startBuild/logs
|
|
84
|
-
az.js -> loginServicePrincipal/acrBuild/webappDeploy/aksGetCredentials/...
|
|
85
|
-
azdo.js -> logging commands de Azure Pipelines (##vso)
|
|
86
|
-
index.js -> registra todos los servicios anteriores
|
|
87
|
-
index.js -> exporta { Context, Menu, logger, prompt, services }
|
|
87
|
+
shell.ts -> motor base (spawn), con retry/timeout/dryRun
|
|
88
|
+
docker.ts, git.ts, kubectl.ts, helm.ts, npm.ts, archive.ts
|
|
89
|
+
terraform.ts, ansible.ts, argocd.ts, tekton.ts, oc.ts, az.ts, azdo.ts
|
|
90
|
+
index.ts -> registra todos los servicios anteriores (ServicesRegistry)
|
|
91
|
+
index.ts -> entry point público: Context, Menu, Notifier, senders, classifiers, messages, services, tipos
|
|
92
|
+
bin/
|
|
93
|
+
devops-cli.ts -> CLI ejecutable (busca devops.pipeline.js en el proyecto consumidor)
|
|
88
94
|
examples/
|
|
89
|
-
pipeline-example.js
|
|
95
|
+
pipeline-example.js -> pipeline + menú + notificaciones de ejemplo, corre contra dist/
|
|
90
96
|
test/
|
|
91
|
-
context.test.js
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Qué se corrigió para que "convivan"
|
|
97
|
-
|
|
98
|
-
- `shell.js` usaba `logger.info(...)` / `logger.error(...)` sin importarlo. Se agregó `const logger = require("../core/logger")`.
|
|
99
|
-
- `Context.js` hacía `this.logger = logger` sin importar `logger` tampoco. Ahora importa `./logger`.
|
|
100
|
-
- Se agregó `this.services = services` dentro del constructor de `Context`, cableando exactamente el "Registry" que proponías al final de tu mensaje:
|
|
101
|
-
|
|
102
|
-
```javascript
|
|
103
|
-
ctx.services.git.clone(...)
|
|
104
|
-
ctx.services.docker.build(...)
|
|
97
|
+
context.test.js, shell.test.js, services.test.js, menu-selector.test.js,
|
|
98
|
+
notifier.test.js, senders.test.js, hooks-integration.test.js,
|
|
99
|
+
kubectl.test.js, oc.test.js, deployment-group.test.js
|
|
105
100
|
```
|
|
106
101
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
## Uso dentro de un proyecto que lo instaló
|
|
102
|
+
## Uso rápido: menú con `devops.pipeline.js` + el bin
|
|
110
103
|
|
|
111
104
|
Crea un `devops.pipeline.js` (o `devops.config.js` / `.devops-cli.js`) en la raíz de tu proyecto:
|
|
112
105
|
|
|
@@ -116,34 +109,53 @@ module.exports = (ctx) => ({
|
|
|
116
109
|
title: "Pipeline",
|
|
117
110
|
options: {
|
|
118
111
|
Build: async () => {
|
|
119
|
-
await ctx.services.docker.build({
|
|
120
|
-
image: "registry/app:v1",
|
|
121
|
-
dockerfile: "Dockerfile"
|
|
122
|
-
});
|
|
112
|
+
await ctx.services.docker.build({ image: "registry/app:v1", dockerfile: "Dockerfile" });
|
|
123
113
|
},
|
|
124
114
|
Deploy: async () => {
|
|
125
|
-
await ctx.services.kubectl.apply("deployment.yaml");
|
|
115
|
+
await ctx.services.kubectl.apply("deployment.yaml", { namespace: "prod" });
|
|
126
116
|
}
|
|
127
117
|
}
|
|
128
118
|
});
|
|
129
119
|
```
|
|
130
120
|
|
|
131
|
-
Y ejecuta:
|
|
132
|
-
|
|
133
121
|
```bash
|
|
134
122
|
npx devops-cli --debug --env=prod
|
|
135
123
|
```
|
|
136
124
|
|
|
137
125
|
`devops-cli` detecta el archivo, arma el `Context` a partir de los flags/params de `argv`, y renderiza el menú.
|
|
138
126
|
|
|
139
|
-
## Uso
|
|
127
|
+
## Uso directo en tu propio script (p. ej. con `tsx`)
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// src/index.ts
|
|
131
|
+
import { Context, Menu, type MenuDefinition } from "devops-cli";
|
|
132
|
+
|
|
133
|
+
const ctx = Context.parseArgv();
|
|
134
|
+
|
|
135
|
+
const mainMenu: MenuDefinition = {
|
|
136
|
+
title: "Pipeline",
|
|
137
|
+
"flag-selector": "--menu-selector",
|
|
138
|
+
options: {
|
|
139
|
+
Build: { selector: "build", action: () => ctx.services.docker.build({ image: "app:v1" }) }
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
Menu.render(mainMenu, ctx);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{ "scripts": { "dev": "tsx src/index.ts" } }
|
|
148
|
+
```
|
|
140
149
|
|
|
141
150
|
```bash
|
|
142
|
-
|
|
151
|
+
npx tsx src/index.ts --menu-selector=build
|
|
152
|
+
npm run dev -- --menu-selector=build # con npm hace falta el "--" para reenviar flags
|
|
143
153
|
```
|
|
144
154
|
|
|
155
|
+
## Uso como librería sin menú (pipeline lineal)
|
|
156
|
+
|
|
145
157
|
```javascript
|
|
146
|
-
const { Context
|
|
158
|
+
const { Context } = require("devops-cli"); // o require("./dist") dentro de este repo
|
|
147
159
|
|
|
148
160
|
const ctx = Context.parseArgv(); // llena flags/params desde argv
|
|
149
161
|
|
|
@@ -153,83 +165,74 @@ await ctx.services.git.checkout("develop");
|
|
|
153
165
|
await ctx.services.npm.ci();
|
|
154
166
|
await ctx.services.docker.build({ image: ctx.get("image"), dockerfile: "Dockerfile" });
|
|
155
167
|
await ctx.services.docker.push(ctx.get("image"));
|
|
156
|
-
await ctx.services.kubectl.apply("deployment.yaml");
|
|
168
|
+
await ctx.services.kubectl.apply("deployment.yaml", { namespace: "prod" });
|
|
157
169
|
```
|
|
158
170
|
|
|
159
|
-
|
|
171
|
+
## Menús: definición, anidamiento y selectores automáticos por flag
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
await Menu.render({
|
|
163
|
-
title: "Deploy",
|
|
164
|
-
options: {
|
|
165
|
-
Build: buildTask,
|
|
166
|
-
Docker: dockerMenu, // submenú anidado
|
|
167
|
-
Publish: publishTask
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
```
|
|
173
|
+
Un `MenuDefinition` es `{ title, options }`, donde cada entrada de `options` puede ser:
|
|
171
174
|
|
|
172
|
-
|
|
175
|
+
- una **función** — task directa: `Build: () => {...}`
|
|
176
|
+
- otro **`MenuDefinition`** — submenú directo: `Docker: dockerMenu`
|
|
177
|
+
- un **objeto largo** — para poder darle `selector`, `onSuccess`/`onError`, o envolver un submenú:
|
|
178
|
+
```javascript
|
|
179
|
+
Build: { selector: "build", action: () => {...}, onSuccess: (r, ctx) => {...}, onError: (e, ctx) => {...} }
|
|
180
|
+
Docker: { selector: "docker", menu: dockerMenu }
|
|
181
|
+
// también podés inlinear el submenú directo con su propio selector al lado:
|
|
182
|
+
Docker: { selector: "docker", title: "Docker", "flag-selector": "--docker-action", options: {...} }
|
|
183
|
+
```
|
|
173
184
|
|
|
174
|
-
|
|
185
|
+
### Selección automática por flag
|
|
175
186
|
|
|
176
|
-
|
|
177
|
-
await ctx.services.docker.push(image); // igual que siempre
|
|
187
|
+
Cualquier `MenuDefinition` puede declarar `"flag-selector": "--algun-flag"`. Si el `Context` trae un param que matchea el `selector` de alguno de sus items, esa opción se ejecuta **automáticamente, sin ningún prompt**:
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
189
|
+
```javascript
|
|
190
|
+
const dockerMenu = {
|
|
191
|
+
title: "Docker",
|
|
192
|
+
"flag-selector": "--docker-action",
|
|
193
|
+
options: {
|
|
194
|
+
Build: { selector: "build", action: buildTask },
|
|
195
|
+
Push: { selector: "push", action: pushTask }
|
|
196
|
+
}
|
|
197
|
+
};
|
|
186
198
|
|
|
187
|
-
|
|
199
|
+
const mainMenu = {
|
|
200
|
+
title: "Pipeline",
|
|
201
|
+
"flag-selector": "--menu-selector",
|
|
202
|
+
options: {
|
|
203
|
+
Docker: { selector: "docker", menu: dockerMenu },
|
|
204
|
+
Deploy: { selector: "deploy", action: deployTask }
|
|
205
|
+
}
|
|
206
|
+
};
|
|
188
207
|
|
|
189
|
-
|
|
190
|
-
ctx.services.shell.configure({ retry: 3, timeout: 30000 });
|
|
208
|
+
Menu.render(mainMenu, ctx);
|
|
191
209
|
```
|
|
192
210
|
|
|
193
|
-
Y `Context.parseArgv()` ya conecta flags de línea de comandos automáticamente:
|
|
194
|
-
|
|
195
211
|
```bash
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```
|
|
212
|
+
# encadena ambos niveles en un solo comando, sin ningún prompt interactivo:
|
|
213
|
+
devops-cli --menu-selector=docker --docker-action=build
|
|
199
214
|
|
|
200
|
-
|
|
215
|
+
# un solo nivel:
|
|
216
|
+
devops-cli --menu-selector=deploy
|
|
201
217
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
ctx.services.azdo.logWarning("El caché de npm no se encontró, se reconstruye desde cero.");
|
|
205
|
-
ctx.services.azdo.group("Build");
|
|
206
|
-
// ... pasos ...
|
|
207
|
-
ctx.services.azdo.endGroup();
|
|
218
|
+
# sin flags -> menú interactivo normal
|
|
219
|
+
devops-cli
|
|
208
220
|
```
|
|
209
221
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```javascript
|
|
213
|
-
await ctx.services.terraform.plan({ varFile: "prod.tfvars" });
|
|
214
|
-
await ctx.services.terraform.apply();
|
|
215
|
-
|
|
216
|
-
await ctx.services.ansible.playbook("site.yml", { inventory: "hosts.ini" });
|
|
217
|
-
|
|
218
|
-
await ctx.services.argocd.appSync("mi-app", { prune: true });
|
|
219
|
-
|
|
220
|
-
await ctx.services.tekton.pipelineStart("build-pipeline", { params: { image: "app:v1" } });
|
|
222
|
+
Si el valor del flag no matchea ningún `selector` del nivel actual, cae de vuelta al menú interactivo (con un warning), en vez de fallar en seco. Cada submenú revisa su **propio** `flag-selector` de forma independiente, así que podés automatizar tantos niveles como quieras encadenando flags.
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
await ctx.services.oc.rollout("mi-app");
|
|
224
|
+
### Callbacks de éxito/error por item
|
|
224
225
|
|
|
225
|
-
|
|
226
|
+
```javascript
|
|
227
|
+
Deploy: {
|
|
228
|
+
selector: "deploy",
|
|
229
|
+
action: () => ctx.services.kubectl.apply("deployment.yaml"),
|
|
230
|
+
onSuccess: (result, ctx) => ctx.logger.success("Deploy OK"),
|
|
231
|
+
onError: (error, ctx) => ctx.logger.error(`Deploy falló: ${error.message}`)
|
|
232
|
+
}
|
|
226
233
|
```
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
Cada tarea (`ctx.run()` o un item de menú) puede llevar sus propios callbacks, y además reporta automáticamente al `notifier` global del `Context`.
|
|
231
|
-
|
|
232
|
-
### Callbacks por tarea
|
|
235
|
+
Mismo patrón con `ctx.run()` fuera de un menú:
|
|
233
236
|
|
|
234
237
|
```javascript
|
|
235
238
|
await ctx.run(
|
|
@@ -242,72 +245,54 @@ await ctx.run(
|
|
|
242
245
|
);
|
|
243
246
|
```
|
|
244
247
|
|
|
245
|
-
En
|
|
248
|
+
En ambos casos, además de tus callbacks, el resultado se reporta automáticamente al `ctx.notifier` (ver más abajo) — no hay que llamarlo a mano.
|
|
249
|
+
|
|
250
|
+
## retry / timeout / dryRun en shell.exec
|
|
251
|
+
|
|
252
|
+
`shell.exec(command, ...args)` sigue aceptando exactamente los mismos argumentos de siempre. Si el último argumento es un objeto plano, se interpreta como opciones **solo para esa llamada**:
|
|
246
253
|
|
|
247
254
|
```javascript
|
|
248
|
-
|
|
249
|
-
Deploy: {
|
|
250
|
-
selector: "deploy",
|
|
251
|
-
action: () => ctx.services.kubectl.apply("deployment.yaml"),
|
|
252
|
-
onSuccess: (result, ctx) => {...},
|
|
253
|
-
onError: (error, ctx) => {...}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
```
|
|
255
|
+
await ctx.services.docker.push(image); // igual que siempre
|
|
257
256
|
|
|
258
|
-
|
|
257
|
+
await ctx.services.shell.exec("curl", "https://flaky-api.internal", {
|
|
258
|
+
retry: 3, // reintentos totales (default: 1 = sin retry)
|
|
259
|
+
retryDelay: 1000, // ms entre reintentos
|
|
260
|
+
timeout: 5000, // ms antes de matar el proceso con SIGTERM
|
|
261
|
+
dryRun: true // solo loguea el comando, no lo ejecuta
|
|
262
|
+
});
|
|
263
|
+
```
|
|
259
264
|
|
|
260
|
-
|
|
265
|
+
Defaults globales para todo el proceso:
|
|
261
266
|
|
|
262
267
|
```javascript
|
|
263
|
-
|
|
268
|
+
ctx.services.shell.configure({ retry: 3, timeout: 30000 });
|
|
269
|
+
```
|
|
264
270
|
|
|
265
|
-
|
|
266
|
-
ctx.notifier.classify(classifiers.byCommand({
|
|
267
|
-
docker: "containers",
|
|
268
|
-
kubectl: "kubernetes",
|
|
269
|
-
oc: "kubernetes",
|
|
270
|
-
terraform: "infra",
|
|
271
|
-
ansible: "infra",
|
|
272
|
-
git: "scm",
|
|
273
|
-
argocd: "cd-pipeline",
|
|
274
|
-
tkn: "cd-pipeline",
|
|
275
|
-
az: "cloud-azure"
|
|
276
|
-
}));
|
|
271
|
+
`Context.parseArgv()` ya conecta flags de línea de comandos automáticamente:
|
|
277
272
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
[/no space left|ENOSPC/i, "infra"]
|
|
283
|
-
]));
|
|
273
|
+
```bash
|
|
274
|
+
npx devops-cli --dry-run # activa dryRun global
|
|
275
|
+
npx devops-cli --retry=3 --timeout=15000
|
|
276
|
+
```
|
|
284
277
|
|
|
285
|
-
|
|
286
|
-
ctx.notifier.channel("kubernetes", senders.webhook({ url: process.env.SLACK_K8S_WEBHOOK }));
|
|
287
|
-
ctx.notifier.channel("security", senders.http({ url: "https://security.miempresa.com/incidents" }));
|
|
288
|
-
ctx.notifier.channel("*", senders.file({ path: "./devops-cli-errors.log" })); // TODO error, sin importar el área
|
|
278
|
+
## Servicios de infraestructura incluidos
|
|
289
279
|
|
|
290
|
-
|
|
291
|
-
ctx.
|
|
292
|
-
|
|
280
|
+
```javascript
|
|
281
|
+
await ctx.services.terraform.plan({ varFile: "prod.tfvars" });
|
|
282
|
+
await ctx.services.terraform.apply();
|
|
293
283
|
|
|
294
|
-
|
|
284
|
+
await ctx.services.ansible.playbook("site.yml", { inventory: "hosts.ini" });
|
|
295
285
|
|
|
296
|
-
|
|
286
|
+
await ctx.services.argocd.appSync("mi-app", { prune: true });
|
|
297
287
|
|
|
298
|
-
|
|
299
|
-
|---|---|
|
|
300
|
-
| `senders.log()` | Usa el logger interno (consola) |
|
|
301
|
-
| `senders.file({ path })` | Agrega el evento como una línea JSON al archivo |
|
|
302
|
-
| `senders.http({ url, method?, headers?, formatBody? })` | `POST` genérico del evento como JSON |
|
|
303
|
-
| `senders.webhook({ url, format? })` | Como `http`, pero formatea `{ text: "❌ ..." }` por defecto (Slack/Teams/Discord-friendly) |
|
|
304
|
-
| `senders.websocket({ url, timeout? })` | Abre una conexión WS, manda el evento como JSON y cierra. Requiere Node ≥21 (usa el `WebSocket` global) |
|
|
288
|
+
await ctx.services.tekton.pipelineStart("build-pipeline", { params: { image: "app:v1" } });
|
|
305
289
|
|
|
306
|
-
|
|
290
|
+
await ctx.services.az.acrBuild({ registry: "miregistro", image: "app:v1" });
|
|
291
|
+
```
|
|
307
292
|
|
|
308
293
|
## kubectl / oc: kubeconfig, namespace y espera cíclica del rollout
|
|
309
294
|
|
|
310
|
-
`kubectl` y `oc`
|
|
295
|
+
`kubectl` y `oc` aceptan `{ kubeconfig, namespace }` como último argumento en **todos** sus comandos (retrocompatible, sigue funcionando sin ese argumento):
|
|
311
296
|
|
|
312
297
|
```javascript
|
|
313
298
|
await ctx.services.kubectl.apply("deploy.yaml", { kubeconfig: "/etc/kube/prod.yaml", namespace: "prod" });
|
|
@@ -319,21 +304,16 @@ await ctx.services.oc.apply("deploy.yaml", { namespace: "prod" });
|
|
|
319
304
|
|
|
320
305
|
### `waitForDeployment` — validación cíclica del rollout
|
|
321
306
|
|
|
322
|
-
Sondea el Deployment (o `DeploymentConfig` con `oc`) hasta que:
|
|
307
|
+
Sondea el Deployment (o `DeploymentConfig` con `oc` + `resourceType: "dc"`) hasta que:
|
|
323
308
|
|
|
324
309
|
- llega a **estado exitoso** (réplicas listas/actualizadas == deseadas) → resuelve con `{ status: "success", ... }`,
|
|
325
310
|
- se queda en **estado Failed** más de `failedGracePeriod` sin recuperarse → lanza `DeploymentRolloutError`,
|
|
326
311
|
- supera **`maxRestarts`** reinicios acumulados entre todos sus pods → lanza `DeploymentRolloutError` de inmediato, sin esperar el grace period,
|
|
327
312
|
- o se cumple el **`timeout`** global sin éxito → lanza `DeploymentRolloutError`.
|
|
328
313
|
|
|
329
|
-
En los tres casos de fallo, el polling se detiene
|
|
314
|
+
En los tres casos de fallo, el polling se detiene y el error se re-lanza — listo para que `ctx.run(...)` lo capture y lo reporte automáticamente vía `ctx.notifier` (el error ya trae `command: "kubectl"` / `command: "oc"`, así que `classifiers.byCommand({ kubectl: "kubernetes" })` lo clasifica sin configuración extra).
|
|
330
315
|
|
|
331
316
|
```javascript
|
|
332
|
-
const { classifiers } = require("devops-cli");
|
|
333
|
-
|
|
334
|
-
ctx.notifier.classify(classifiers.byCommand({ kubectl: "kubernetes", oc: "kubernetes" }));
|
|
335
|
-
ctx.notifier.channel("kubernetes", senders.webhook({ url: process.env.SLACK_K8S_WEBHOOK }));
|
|
336
|
-
|
|
337
317
|
await ctx.run("deploy-api", async () => {
|
|
338
318
|
await ctx.services.kubectl.apply("deployment.yaml", { namespace: "prod" });
|
|
339
319
|
|
|
@@ -348,11 +328,9 @@ await ctx.run("deploy-api", async () => {
|
|
|
348
328
|
});
|
|
349
329
|
```
|
|
350
330
|
|
|
351
|
-
Con `oc`, usa `resourceType: "dc"` para apuntar a un `DeploymentConfig` clásico de OpenShift en vez de un `Deployment` nativo (default: `"deployment"`).
|
|
352
|
-
|
|
353
331
|
### `waitForDeploymentGroup` — validar todas las instancias de un mismo despliegue GitOps
|
|
354
332
|
|
|
355
|
-
Pensada para el caso de GitOps donde un mismo repo termina desplegado como **varios Deployments** (una instancia por región/config/cliente, etc.), todos marcados con un label común
|
|
333
|
+
Pensada para el caso de GitOps donde un mismo repo termina desplegado como **varios Deployments** (una instancia por región/config/cliente, etc.), todos marcados con un label común:
|
|
356
334
|
|
|
357
335
|
```yaml
|
|
358
336
|
metadata:
|
|
@@ -360,7 +338,7 @@ metadata:
|
|
|
360
338
|
deployment-group: repository-14
|
|
361
339
|
```
|
|
362
340
|
|
|
363
|
-
|
|
341
|
+
Descubre todas las instancias que compartan ese label y corre `waitForDeployment` sobre **cada una en paralelo**, con el mismo `timeout`/`pollInterval`/`failedGracePeriod`/`maxRestarts` para todas:
|
|
364
342
|
|
|
365
343
|
```javascript
|
|
366
344
|
await ctx.run("deploy-repo-14", () =>
|
|
@@ -376,10 +354,107 @@ await ctx.run("deploy-repo-14", () =>
|
|
|
376
354
|
```
|
|
377
355
|
|
|
378
356
|
- Si **todas** llegan a estado exitoso → resuelve con `{ status: "success", deployments: [...] }` (el detalle de cada una).
|
|
379
|
-
- Si **alguna falla**
|
|
357
|
+
- Si **alguna falla** → espera a que las demás terminen, y lanza `DeploymentGroupRolloutError` con `succeeded` (nombres que sí llegaron) y `failed` (nombre + status + mensaje de cada una que no).
|
|
380
358
|
- Si el label **no matchea ningún deployment**, también lanza `DeploymentGroupRolloutError` (grupo vacío = error, no éxito silencioso).
|
|
381
359
|
|
|
382
|
-
|
|
360
|
+
Con `oc`, ambas funciones aceptan `resourceType: "dc"` para apuntar a `DeploymentConfig` clásico en vez de `Deployment` nativo (default: `"deployment"`).
|
|
361
|
+
|
|
362
|
+
## Logging commands de Azure Pipelines (`ctx.services.azdo`)
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
ctx.services.azdo.setVariable("BUILD_TAG", "v1.2.3");
|
|
366
|
+
ctx.services.azdo.logWarning("El caché de npm no se encontró, se reconstruye desde cero.");
|
|
367
|
+
ctx.services.azdo.group("Build");
|
|
368
|
+
// ... pasos ...
|
|
369
|
+
ctx.services.azdo.endGroup();
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Notificaciones: clasificar errores por área de TI, personalizar el mensaje, y enviarlos
|
|
373
|
+
|
|
374
|
+
`ctx.notifier` tiene tres responsabilidades independientes:
|
|
375
|
+
|
|
376
|
+
1. **`classify()`** — decide a qué **área de TI** pertenece un error (para elegir a qué canal mandarlo).
|
|
377
|
+
2. **`describeError()`** — decide el **mensaje** a reportar (reemplaza el stderr crudo por algo humano).
|
|
378
|
+
3. **`channel()`** / **`onSuccess()`** — a qué **senders** se manda cada área.
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
const { classifiers, messages, senders } = require("devops-cli");
|
|
382
|
+
|
|
383
|
+
// 1. ¿A qué área de TI pertenece este error?
|
|
384
|
+
ctx.notifier.classify(classifiers.byCommand({
|
|
385
|
+
docker: "containers",
|
|
386
|
+
kubectl: "kubernetes",
|
|
387
|
+
oc: "kubernetes",
|
|
388
|
+
terraform: "infra",
|
|
389
|
+
ansible: "infra",
|
|
390
|
+
git: "scm",
|
|
391
|
+
argocd: "cd-pipeline",
|
|
392
|
+
tkn: "cd-pipeline",
|
|
393
|
+
az: "cloud-azure"
|
|
394
|
+
}));
|
|
395
|
+
|
|
396
|
+
// también podés clasificar por el texto del error:
|
|
397
|
+
ctx.notifier.classify(classifiers.byPattern([
|
|
398
|
+
[/permission denied|unauthorized/i, "security"],
|
|
399
|
+
[/timeout|ECONNREFUSED/i, "networking"],
|
|
400
|
+
[/no space left|ENOSPC/i, "infra"]
|
|
401
|
+
]));
|
|
402
|
+
|
|
403
|
+
// 2. ¿qué mensaje se reporta? (opcional — sin esto, se usa el stderr crudo)
|
|
404
|
+
ctx.notifier.describeError(messages.byRule([
|
|
405
|
+
{
|
|
406
|
+
command: "docker", args: "push", pattern: /500 Internal Server Error/,
|
|
407
|
+
message: "Se ha reportado a infraestructura: falta de espacio en el registry"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
command: "kubectl", pattern: /500/,
|
|
411
|
+
message: "El API server de Kubernetes devolvió 500, reintenta en unos minutos"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
command: "terraform", pattern: /500/,
|
|
415
|
+
message: (error, ctx) => `Backend remoto de Terraform no respondió (env: ${ctx.params.env ?? "?"})`
|
|
416
|
+
}
|
|
417
|
+
]));
|
|
418
|
+
|
|
419
|
+
// 3. ¿a dónde se manda cada área?
|
|
420
|
+
ctx.notifier.channel("kubernetes", senders.webhook({ url: process.env.TEAMS_WEBHOOK }));
|
|
421
|
+
ctx.notifier.channel("security", senders.http({ url: "https://security.miempresa.com/incidents" }));
|
|
422
|
+
ctx.notifier.channel("*", senders.file({ path: "./devops-cli-errors.log" })); // TODO error, sin importar el área
|
|
423
|
+
|
|
424
|
+
// (opcional) éxito, sin clasificación de área
|
|
425
|
+
ctx.notifier.onSuccess(senders.log());
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
A partir de aquí, cualquier `ctx.run(...)` o item de menú con `action` reporta automáticamente al notifier — no hay que llamarlo a mano en cada task.
|
|
429
|
+
|
|
430
|
+
### Clasificadores de área (`classifiers`)
|
|
431
|
+
|
|
432
|
+
| Fábrica | Uso |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `classifiers.byCommand({ docker: "containers", ... })` | Mapea el comando que falló (adjunto automáticamente por `shell.exec`) a un área |
|
|
435
|
+
| `classifiers.byPattern([[regex, area], ...])` | Matchea contra el `stderr`/mensaje del error |
|
|
436
|
+
|
|
437
|
+
### Formateadores de mensaje (`messages`)
|
|
438
|
+
|
|
439
|
+
| Fábrica | Uso |
|
|
440
|
+
|---|---|
|
|
441
|
+
| `messages.byPattern([[regex, mensaje], ...])` | Mismo mensaje sin importar el comando — solo mira el texto del error |
|
|
442
|
+
| `messages.byCommand({ docker: "mensaje fijo" })` | Mensaje fijo por comando, sin importar el detalle del error |
|
|
443
|
+
| `messages.byRule([{ command?, args?, pattern?, message }, ...])` | **La opción avanzada**: combina comando + sub-comando (`args`, distingue `docker push` de `docker build`) + patrón de texto, todo en modo AND. `message` puede ser un string fijo o una función `(error, ctx) => string`. Resuelve el caso de "el mismo 500 puede venir de docker, kubectl o terraform, y cada uno necesita su propio mensaje". |
|
|
444
|
+
|
|
445
|
+
Si ningún classifier/formatter matchea, se usa el área `"unclassified"` y el mensaje crudo del error, respectivamente — nada se rompe si no configurás nada de esto.
|
|
446
|
+
|
|
447
|
+
### Senders incluidos
|
|
448
|
+
|
|
449
|
+
| Sender | Uso |
|
|
450
|
+
|---|---|
|
|
451
|
+
| `senders.log()` | Usa el logger interno (consola) |
|
|
452
|
+
| `senders.file({ path })` | Agrega el evento como una línea JSON al archivo |
|
|
453
|
+
| `senders.http({ url, method?, headers?, formatBody? })` | `POST` genérico del evento como JSON |
|
|
454
|
+
| `senders.webhook({ url, format? })` | Como `http`, pero formatea `{ text: "❌ ..." }` por defecto — compatible con Slack/Discord y con **Microsoft Teams** vía Workflows (Power Automate), pasando un `format` que arme el payload de Adaptive Card que Teams espera |
|
|
455
|
+
| `senders.websocket({ url, timeout? })` | Abre una conexión WS, manda el evento como JSON y cierra. Requiere Node ≥21 (usa el `WebSocket` global) |
|
|
456
|
+
|
|
457
|
+
Podés escribir tu propio sender: es cualquier función `(event) => void | Promise<void>` — recibe `{ type, taskId, area?, error?, result?, message, timestamp }`.
|
|
383
458
|
|
|
384
459
|
## Tests
|
|
385
460
|
|
|
@@ -387,11 +462,21 @@ Igual que con `waitForDeployment`, el error lleva `command: "kubectl"` / `comman
|
|
|
387
462
|
npm test
|
|
388
463
|
```
|
|
389
464
|
|
|
390
|
-
|
|
465
|
+
`pretest` corre el build automáticamente, así los tests validan el `dist/` real que se publica (no el código fuente). Usa `node:test`, sin dependencias externas — interceptando `shell.exec` en vez de ejecutar binarios reales:
|
|
466
|
+
|
|
467
|
+
- `context.test.js` — Context, flags, vars, parseArgv, dryRun global
|
|
468
|
+
- `shell.test.js` — retry, timeout, dryRun del motor shell.exec
|
|
469
|
+
- `services.test.js` — que docker/terraform/argocd arman bien sus argumentos
|
|
470
|
+
- `menu-selector.test.js` — selección automática por flag, en cascada de varios niveles
|
|
471
|
+
- `hooks-integration.test.js` — ctx.run() y items de menú con onSuccess/onError
|
|
472
|
+
- `notifier.test.js` — classify/channel/onSuccess/describeError/byRule
|
|
473
|
+
- `senders.test.js` — file/http/webhook/log contra servidores reales en localhost
|
|
474
|
+
- `kubectl.test.js`, `oc.test.js` — kubeconfig/namespace, waitForDeployment (éxito, timeout, Failed, maxRestarts)
|
|
475
|
+
- `deployment-group.test.js` — waitForDeploymentGroup (éxito total, fallo parcial, label sin matches, oc con `dc`)
|
|
391
476
|
|
|
392
477
|
## Siguientes pasos posibles
|
|
393
478
|
|
|
394
479
|
- Publicar en un registro privado (Verdaccio/Artifactory/GitHub Packages) para instalarlo con scope, p.ej. `@miorg/devops-cli`.
|
|
395
|
-
- Agregar
|
|
396
|
-
- Agregar más plugins (`ansible-lint`, `trivy`, `sonar-scanner`) con el mismo patrón.
|
|
480
|
+
- Agregar más plugins (`ansible-lint`, `trivy`, `sonar-scanner`) con el mismo patrón que `terraform.ts`/`docker.ts`.
|
|
397
481
|
- CI propio (GitHub Actions/Azure Pipelines) que corra `npm test` en cada PR antes de `npm publish`.
|
|
482
|
+
- `--catch=throw` (o similar) para que un item de menú fallido mate el proceso completo en vez de solo loguear y seguir — útil corriendo vía `--menu-selector` dentro de un step de Azure Pipelines.
|
package/dist/core/Notifier.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Context } from "./Context";
|
|
2
|
-
import type { ErrorClassifier, Sender } from "./types";
|
|
2
|
+
import type { ErrorClassifier, ErrorMessageFormatter, Sender } from "./types";
|
|
3
3
|
export declare class Notifier {
|
|
4
4
|
private classifiers;
|
|
5
|
+
private messageFormatters;
|
|
5
6
|
private channels;
|
|
6
7
|
private successSenders;
|
|
7
8
|
private defaultArea;
|
|
@@ -12,6 +13,19 @@ export declare class Notifier {
|
|
|
12
13
|
* (configurable con `setDefaultArea`).
|
|
13
14
|
*/
|
|
14
15
|
classify(classifier: ErrorClassifier): this;
|
|
16
|
+
/**
|
|
17
|
+
* Registra una función que, dado el error, devuelve el mensaje
|
|
18
|
+
* personalizado a reportar (reemplaza el stderr/mensaje crudo). Se
|
|
19
|
+
* prueban en orden; gana la primera que devuelva un string. Si ninguna
|
|
20
|
+
* matchea, se usa el mensaje crudo del error (stderr/Error.message).
|
|
21
|
+
*
|
|
22
|
+
* Ejemplo:
|
|
23
|
+
* notifier.describeError(messages.byPattern([
|
|
24
|
+
* [/500 Internal Server Error/, "Se reportó a infraestructura: falta de espacio en el registry"],
|
|
25
|
+
* [/unauthorized|403/, "Credenciales inválidas contra el registry, revisa el secret"]
|
|
26
|
+
* ]));
|
|
27
|
+
*/
|
|
28
|
+
describeError(formatter: ErrorMessageFormatter): this;
|
|
15
29
|
setDefaultArea(area: string): this;
|
|
16
30
|
/**
|
|
17
31
|
* Asocia uno o más senders a un área. Puedes registrar el mismo sender
|
|
@@ -22,6 +36,7 @@ export declare class Notifier {
|
|
|
22
36
|
/** Senders que se disparan en cada tarea exitosa, sin clasificación. */
|
|
23
37
|
onSuccess(...senders: Sender[]): this;
|
|
24
38
|
private resolveArea;
|
|
39
|
+
private resolveMessage;
|
|
25
40
|
reportSuccess(taskId: string, result: unknown, ctx: Context): Promise<void>;
|
|
26
41
|
reportError(taskId: string, error: unknown, ctx: Context): Promise<string>;
|
|
27
42
|
}
|
package/dist/core/Notifier.js
CHANGED
|
@@ -18,6 +18,7 @@ function toMessage(error) {
|
|
|
18
18
|
class Notifier {
|
|
19
19
|
constructor() {
|
|
20
20
|
this.classifiers = [];
|
|
21
|
+
this.messageFormatters = [];
|
|
21
22
|
this.channels = new Map();
|
|
22
23
|
this.successSenders = [];
|
|
23
24
|
this.defaultArea = "unclassified";
|
|
@@ -32,6 +33,22 @@ class Notifier {
|
|
|
32
33
|
this.classifiers.push(classifier);
|
|
33
34
|
return this;
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Registra una función que, dado el error, devuelve el mensaje
|
|
38
|
+
* personalizado a reportar (reemplaza el stderr/mensaje crudo). Se
|
|
39
|
+
* prueban en orden; gana la primera que devuelva un string. Si ninguna
|
|
40
|
+
* matchea, se usa el mensaje crudo del error (stderr/Error.message).
|
|
41
|
+
*
|
|
42
|
+
* Ejemplo:
|
|
43
|
+
* notifier.describeError(messages.byPattern([
|
|
44
|
+
* [/500 Internal Server Error/, "Se reportó a infraestructura: falta de espacio en el registry"],
|
|
45
|
+
* [/unauthorized|403/, "Credenciales inválidas contra el registry, revisa el secret"]
|
|
46
|
+
* ]));
|
|
47
|
+
*/
|
|
48
|
+
describeError(formatter) {
|
|
49
|
+
this.messageFormatters.push(formatter);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
35
52
|
setDefaultArea(area) {
|
|
36
53
|
this.defaultArea = area;
|
|
37
54
|
return this;
|
|
@@ -59,6 +76,14 @@ class Notifier {
|
|
|
59
76
|
}
|
|
60
77
|
return this.defaultArea;
|
|
61
78
|
}
|
|
79
|
+
resolveMessage(error, ctx) {
|
|
80
|
+
for (const formatter of this.messageFormatters) {
|
|
81
|
+
const message = formatter(error, ctx);
|
|
82
|
+
if (message)
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
return toMessage(error);
|
|
86
|
+
}
|
|
62
87
|
async reportSuccess(taskId, result, ctx) {
|
|
63
88
|
if (!this.successSenders.length)
|
|
64
89
|
return;
|
|
@@ -82,7 +107,7 @@ class Notifier {
|
|
|
82
107
|
taskId,
|
|
83
108
|
area,
|
|
84
109
|
error,
|
|
85
|
-
message:
|
|
110
|
+
message: this.resolveMessage(error, ctx),
|
|
86
111
|
timestamp: new Date().toISOString()
|
|
87
112
|
};
|
|
88
113
|
await Promise.allSettled(senders.map(sender => sender(event)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Notifier.js","sourceRoot":"","sources":["../../src/core/Notifier.ts"],"names":[],"mappings":";;;AAGA,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErB,SAAS,SAAS,CAAC,KAAc;IAE7B,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,OAAO,CAAC;IACzB,CAAC;IAED,MAAM,cAAc,GAAG,KAA0D,CAAC;IAElF,IAAI,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,OAAO,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,cAAc,CAAC,MAAM,CAAC;IACnF,CAAC;IAED,IAAI,cAAc,EAAE,OAAO,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,OAAO,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzB,CAAC;AAED,MAAa,QAAQ;IAArB;QAEY,gBAAW,GAAsB,EAAE,CAAC;QACpC,aAAQ,GAA0B,IAAI,GAAG,EAAE,CAAC;QAC5C,mBAAc,GAAa,EAAE,CAAC;QAC9B,gBAAW,GAAG,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Notifier.js","sourceRoot":"","sources":["../../src/core/Notifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Context } from "./Context";
|
|
2
|
+
import type { ErrorMessageFormatter } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Formateador de fábrica: prueba una lista de [regex, mensaje] contra el
|
|
5
|
+
* stderr/mensaje del error y devuelve el mensaje personalizado del primer
|
|
6
|
+
* patrón que matchee, en vez del stderr crudo.
|
|
7
|
+
*
|
|
8
|
+
* Ojo: esto NO distingue por comando — un mismo patrón (ej. "500 Internal
|
|
9
|
+
* Server Error") aplica igual venga de `docker`, `kubectl` o `terraform`.
|
|
10
|
+
* Si necesitas distinguir por comando (y opcionalmente por sub-comando),
|
|
11
|
+
* usa `byRule` más abajo.
|
|
12
|
+
*
|
|
13
|
+
* Ejemplo:
|
|
14
|
+
* notifier.describeError(messages.byPattern([
|
|
15
|
+
* [/500 Internal Server Error/, "Se reportó a infraestructura: falta de espacio en el registry"],
|
|
16
|
+
* [/unauthorized|403/i, "Credenciales inválidas contra el registry, revisa el secret"],
|
|
17
|
+
* [/no space left|ENOSPC/i, "El nodo se quedó sin espacio en disco"]
|
|
18
|
+
* ]));
|
|
19
|
+
*/
|
|
20
|
+
export declare function byPattern(rules: Array<[RegExp, string]>): ErrorMessageFormatter;
|
|
21
|
+
/**
|
|
22
|
+
* Formateador de fábrica: mapea el comando que falló (docker, kubectl,
|
|
23
|
+
* terraform, etc.) a un mensaje genérico fijo, sin importar el detalle del
|
|
24
|
+
* error. Útil como mensaje "catch-all" por comando cuando no necesitas
|
|
25
|
+
* distinguir por patrón.
|
|
26
|
+
*
|
|
27
|
+
* Ejemplo:
|
|
28
|
+
* notifier.describeError(messages.byCommand({
|
|
29
|
+
* docker: "Falló un paso de Docker, revisa el build/push del registry"
|
|
30
|
+
* }));
|
|
31
|
+
*/
|
|
32
|
+
export declare function byCommand(map: Record<string, string>): ErrorMessageFormatter;
|
|
33
|
+
/**
|
|
34
|
+
* Una regla de `byRule`: TODAS las condiciones que definas deben cumplirse
|
|
35
|
+
* (AND) para que aplique. Omitir una condición equivale a "no filtrar por
|
|
36
|
+
* eso" (matchea cualquier valor).
|
|
37
|
+
*/
|
|
38
|
+
export interface MessageRule {
|
|
39
|
+
/**
|
|
40
|
+
* Comando exacto (o lista de comandos) que debe haber fallado, tal como
|
|
41
|
+
* lo ve `shell.exec` (el primer argumento: "docker", "kubectl", "oc",
|
|
42
|
+
* "terraform", "git", etc.). Sin esto, la regla no filtra por comando.
|
|
43
|
+
*/
|
|
44
|
+
command?: string | string[];
|
|
45
|
+
/**
|
|
46
|
+
* Sub-comando / argumento que debe estar presente, por ejemplo "push"
|
|
47
|
+
* para distinguir `docker push` de `docker build`. Puede ser un string
|
|
48
|
+
* exacto (se busca entre los args, o como substring de los args unidos)
|
|
49
|
+
* o un RegExp contra los args unidos con espacios.
|
|
50
|
+
*/
|
|
51
|
+
args?: string | RegExp;
|
|
52
|
+
/** Patrón contra el stderr/mensaje del error. Sin esto, no filtra por texto. */
|
|
53
|
+
pattern?: RegExp;
|
|
54
|
+
/** Mensaje final. Puede ser un string fijo o una función que lo arma dinámicamente. */
|
|
55
|
+
message: string | ((error: unknown, ctx: Context) => string);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Formateador de fábrica avanzado: combina comando + sub-comando/args +
|
|
59
|
+
* patrón de texto (todo opcional, en modo AND) para distinguir el MISMO
|
|
60
|
+
* error de red/HTTP según de dónde vino. Resuelve justo el caso de "un 500
|
|
61
|
+
* puede venir de docker push, de kubectl, o de terraform, y cada uno
|
|
62
|
+
* necesita su propio mensaje".
|
|
63
|
+
*
|
|
64
|
+
* Se prueban las reglas en orden; gana la primera que matchee todas sus
|
|
65
|
+
* condiciones. Una regla sin `command` ni `args` ni `pattern` matchea
|
|
66
|
+
* cualquier error (útil como catch-all al final de la lista).
|
|
67
|
+
*
|
|
68
|
+
* Ejemplo — el caso concreto de varios comandos devolviendo el mismo 500:
|
|
69
|
+
*
|
|
70
|
+
* notifier.describeError(messages.byRule([
|
|
71
|
+
* {
|
|
72
|
+
* command: "docker", args: "push", pattern: /500 Internal Server Error/,
|
|
73
|
+
* message: "Se ha reportado a infraestructura: falta de espacio en el registry"
|
|
74
|
+
* },
|
|
75
|
+
* {
|
|
76
|
+
* command: "kubectl", pattern: /500/,
|
|
77
|
+
* message: "El API server de Kubernetes devolvió 500, reintenta en unos minutos"
|
|
78
|
+
* },
|
|
79
|
+
* {
|
|
80
|
+
* command: "terraform", pattern: /500/,
|
|
81
|
+
* message: (error, ctx) => `El backend remoto de Terraform State no respondió (env: ${ctx.params.env ?? "?"})`
|
|
82
|
+
* },
|
|
83
|
+
* {
|
|
84
|
+
* pattern: /500 Internal Server Error/,
|
|
85
|
+
* message: "Error 500 no clasificado por comando, revisar logs crudos"
|
|
86
|
+
* }
|
|
87
|
+
* ]));
|
|
88
|
+
*/
|
|
89
|
+
export declare function byRule(rules: MessageRule[]): ErrorMessageFormatter;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.byPattern = byPattern;
|
|
4
|
+
exports.byCommand = byCommand;
|
|
5
|
+
exports.byRule = byRule;
|
|
6
|
+
function textOf(error) {
|
|
7
|
+
const execError = error;
|
|
8
|
+
return execError?.stderr || execError?.message || String(error);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Formateador de fábrica: prueba una lista de [regex, mensaje] contra el
|
|
12
|
+
* stderr/mensaje del error y devuelve el mensaje personalizado del primer
|
|
13
|
+
* patrón que matchee, en vez del stderr crudo.
|
|
14
|
+
*
|
|
15
|
+
* Ojo: esto NO distingue por comando — un mismo patrón (ej. "500 Internal
|
|
16
|
+
* Server Error") aplica igual venga de `docker`, `kubectl` o `terraform`.
|
|
17
|
+
* Si necesitas distinguir por comando (y opcionalmente por sub-comando),
|
|
18
|
+
* usa `byRule` más abajo.
|
|
19
|
+
*
|
|
20
|
+
* Ejemplo:
|
|
21
|
+
* notifier.describeError(messages.byPattern([
|
|
22
|
+
* [/500 Internal Server Error/, "Se reportó a infraestructura: falta de espacio en el registry"],
|
|
23
|
+
* [/unauthorized|403/i, "Credenciales inválidas contra el registry, revisa el secret"],
|
|
24
|
+
* [/no space left|ENOSPC/i, "El nodo se quedó sin espacio en disco"]
|
|
25
|
+
* ]));
|
|
26
|
+
*/
|
|
27
|
+
function byPattern(rules) {
|
|
28
|
+
return (error) => {
|
|
29
|
+
const text = textOf(error);
|
|
30
|
+
for (const [pattern, message] of rules) {
|
|
31
|
+
if (pattern.test(text)) {
|
|
32
|
+
return message;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formateador de fábrica: mapea el comando que falló (docker, kubectl,
|
|
40
|
+
* terraform, etc.) a un mensaje genérico fijo, sin importar el detalle del
|
|
41
|
+
* error. Útil como mensaje "catch-all" por comando cuando no necesitas
|
|
42
|
+
* distinguir por patrón.
|
|
43
|
+
*
|
|
44
|
+
* Ejemplo:
|
|
45
|
+
* notifier.describeError(messages.byCommand({
|
|
46
|
+
* docker: "Falló un paso de Docker, revisa el build/push del registry"
|
|
47
|
+
* }));
|
|
48
|
+
*/
|
|
49
|
+
function byCommand(map) {
|
|
50
|
+
return (error) => {
|
|
51
|
+
const command = error?.command;
|
|
52
|
+
if (!command)
|
|
53
|
+
return undefined;
|
|
54
|
+
return map[command];
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function commandMatches(rule, command) {
|
|
58
|
+
if (!rule.command)
|
|
59
|
+
return true;
|
|
60
|
+
if (!command)
|
|
61
|
+
return false;
|
|
62
|
+
const candidates = Array.isArray(rule.command) ? rule.command : [rule.command];
|
|
63
|
+
return candidates.includes(command);
|
|
64
|
+
}
|
|
65
|
+
function argsMatch(rule, args) {
|
|
66
|
+
if (!rule.args)
|
|
67
|
+
return true;
|
|
68
|
+
if (!args || !args.length)
|
|
69
|
+
return false;
|
|
70
|
+
const joined = args.join(" ");
|
|
71
|
+
if (rule.args instanceof RegExp) {
|
|
72
|
+
return rule.args.test(joined);
|
|
73
|
+
}
|
|
74
|
+
return args.includes(rule.args) || joined.includes(rule.args);
|
|
75
|
+
}
|
|
76
|
+
function patternMatches(rule, text) {
|
|
77
|
+
if (!rule.pattern)
|
|
78
|
+
return true;
|
|
79
|
+
return rule.pattern.test(text);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Formateador de fábrica avanzado: combina comando + sub-comando/args +
|
|
83
|
+
* patrón de texto (todo opcional, en modo AND) para distinguir el MISMO
|
|
84
|
+
* error de red/HTTP según de dónde vino. Resuelve justo el caso de "un 500
|
|
85
|
+
* puede venir de docker push, de kubectl, o de terraform, y cada uno
|
|
86
|
+
* necesita su propio mensaje".
|
|
87
|
+
*
|
|
88
|
+
* Se prueban las reglas en orden; gana la primera que matchee todas sus
|
|
89
|
+
* condiciones. Una regla sin `command` ni `args` ni `pattern` matchea
|
|
90
|
+
* cualquier error (útil como catch-all al final de la lista).
|
|
91
|
+
*
|
|
92
|
+
* Ejemplo — el caso concreto de varios comandos devolviendo el mismo 500:
|
|
93
|
+
*
|
|
94
|
+
* notifier.describeError(messages.byRule([
|
|
95
|
+
* {
|
|
96
|
+
* command: "docker", args: "push", pattern: /500 Internal Server Error/,
|
|
97
|
+
* message: "Se ha reportado a infraestructura: falta de espacio en el registry"
|
|
98
|
+
* },
|
|
99
|
+
* {
|
|
100
|
+
* command: "kubectl", pattern: /500/,
|
|
101
|
+
* message: "El API server de Kubernetes devolvió 500, reintenta en unos minutos"
|
|
102
|
+
* },
|
|
103
|
+
* {
|
|
104
|
+
* command: "terraform", pattern: /500/,
|
|
105
|
+
* message: (error, ctx) => `El backend remoto de Terraform State no respondió (env: ${ctx.params.env ?? "?"})`
|
|
106
|
+
* },
|
|
107
|
+
* {
|
|
108
|
+
* pattern: /500 Internal Server Error/,
|
|
109
|
+
* message: "Error 500 no clasificado por comando, revisar logs crudos"
|
|
110
|
+
* }
|
|
111
|
+
* ]));
|
|
112
|
+
*/
|
|
113
|
+
function byRule(rules) {
|
|
114
|
+
return (error, ctx) => {
|
|
115
|
+
const execError = error;
|
|
116
|
+
const text = textOf(error);
|
|
117
|
+
for (const rule of rules) {
|
|
118
|
+
const matches = commandMatches(rule, execError?.command)
|
|
119
|
+
&& argsMatch(rule, execError?.args)
|
|
120
|
+
&& patternMatches(rule, text);
|
|
121
|
+
if (matches) {
|
|
122
|
+
return typeof rule.message === "function"
|
|
123
|
+
? rule.message(error, ctx)
|
|
124
|
+
: rule.message;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/core/messages.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -103,3 +103,11 @@ export interface ResolvedMenuEntry {
|
|
|
103
103
|
onSuccess?: SuccessCallback;
|
|
104
104
|
onError?: ErrorCallback;
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Igual que ErrorClassifier, pero en vez de decidir el área, devuelve el
|
|
108
|
+
* mensaje humano que se va a reportar (reemplazando el stderr/mensaje crudo
|
|
109
|
+
* del error). Devuelve `undefined` si no sabe describir ese error (se
|
|
110
|
+
* prueba el siguiente formatter registrado, y si ninguno matchea se usa el
|
|
111
|
+
* mensaje crudo del error como antes).
|
|
112
|
+
*/
|
|
113
|
+
export type ErrorMessageFormatter = (error: unknown, ctx: Context) => string | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ export type { ServicesRegistry } from "./services";
|
|
|
9
9
|
export { Notifier } from "./core/Notifier";
|
|
10
10
|
export * as senders from "./core/senders";
|
|
11
11
|
export * as classifiers from "./core/classifiers";
|
|
12
|
-
export
|
|
12
|
+
export * as messages from "./core/messages";
|
|
13
|
+
export type { ExecOptions, ExecResult, MenuTask, MenuOptionWithSelector, MenuOptionValue, MenuDefinition, ResolvedMenuEntry, TaskHooks, SuccessCallback, ErrorCallback, NotificationEvent, ErrorClassifier, ErrorMessageFormatter, Sender } from "./core/types";
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.classifiers = exports.senders = exports.Notifier = exports.services = exports.prompt = exports.logger = exports.Menu = exports.Context = void 0;
|
|
36
|
+
exports.messages = exports.classifiers = exports.senders = exports.Notifier = exports.services = exports.prompt = exports.logger = exports.Menu = exports.Context = void 0;
|
|
37
37
|
var Context_1 = require("./core/Context");
|
|
38
38
|
Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return Context_1.Context; } });
|
|
39
39
|
var Menu_1 = require("./core/Menu");
|
|
@@ -47,4 +47,5 @@ var Notifier_1 = require("./core/Notifier");
|
|
|
47
47
|
Object.defineProperty(exports, "Notifier", { enumerable: true, get: function () { return Notifier_1.Notifier; } });
|
|
48
48
|
exports.senders = __importStar(require("./core/senders"));
|
|
49
49
|
exports.classifiers = __importStar(require("./core/classifiers"));
|
|
50
|
+
exports.messages = __importStar(require("./core/messages"));
|
|
50
51
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAGhB,oCAAmC;AAA1B,4FAAA,IAAI,OAAA;AAEb,wCAAuC;AAA9B,gGAAA,MAAM,OAAA;AAGf,wDAAwC;AAExC,uCAAsC;AAA7B,oGAAA,QAAQ,OAAA;AAGjB,4CAA2C;AAAlC,oGAAA,QAAQ,OAAA;AACjB,0DAA0C;AAC1C,kEAAkD"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAGhB,oCAAmC;AAA1B,4FAAA,IAAI,OAAA;AAEb,wCAAuC;AAA9B,gGAAA,MAAM,OAAA;AAGf,wDAAwC;AAExC,uCAAsC;AAA7B,oGAAA,QAAQ,OAAA;AAGjB,4CAA2C;AAAlC,oGAAA,QAAQ,OAAA;AACjB,0DAA0C;AAC1C,kEAAkD;AAClD,4DAA4C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-indev/catops-cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.7",
|
|
4
4
|
"description": "Framework CLI para pipelines DevOps (shell, docker, git, kubectl, helm, npm, terraform, ansible, argocd, tekton, oc, az) sobre un ExecutionContext compartido, con menus interactivos y selectores automaticos por flag. Escrito en TypeScript, 100% usable desde JavaScript.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|