@enspirit/emb 0.14.1 → 0.17.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/README.md +162 -43
- package/dist/src/cli/abstract/BaseCommand.d.ts +1 -0
- package/dist/src/cli/abstract/BaseCommand.js +23 -4
- package/dist/src/cli/abstract/FlavouredCommand.d.ts +1 -0
- package/dist/src/cli/abstract/KubernetesCommand.d.ts +1 -0
- package/dist/src/cli/commands/components/logs.d.ts +2 -1
- package/dist/src/cli/commands/components/logs.js +21 -24
- package/dist/src/cli/commands/secrets/index.d.ts +14 -0
- package/dist/src/cli/commands/secrets/index.js +71 -0
- package/dist/src/cli/commands/secrets/providers.d.ts +12 -0
- package/dist/src/cli/commands/secrets/providers.js +50 -0
- package/dist/src/cli/commands/secrets/validate.d.ts +18 -0
- package/dist/src/cli/commands/secrets/validate.js +145 -0
- package/dist/src/cli/hooks/init.js +7 -1
- package/dist/src/cli/hooks/postrun.d.ts +7 -0
- package/dist/src/cli/hooks/postrun.js +128 -0
- package/dist/src/config/index.d.ts +10 -1
- package/dist/src/config/index.js +28 -3
- package/dist/src/config/schema.d.ts +7 -4
- package/dist/src/config/schema.json +173 -9
- package/dist/src/context.d.ts +9 -0
- package/dist/src/context.js +19 -0
- package/dist/src/docker/compose/operations/ComposeLogsOperation.d.ts +21 -0
- package/dist/src/docker/compose/operations/ComposeLogsOperation.js +85 -0
- package/dist/src/docker/compose/operations/index.d.ts +1 -0
- package/dist/src/docker/compose/operations/index.js +1 -0
- package/dist/src/docker/resources/DockerImageResource.js +16 -6
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/monorepo/monorepo.js +13 -5
- package/dist/src/monorepo/operations/resources/BuildResourcesOperation.js +1 -2
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.d.ts +1 -1
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +1 -1
- package/dist/src/monorepo/plugins/VaultPlugin.d.ts +46 -0
- package/dist/src/monorepo/plugins/VaultPlugin.js +91 -0
- package/dist/src/monorepo/plugins/index.d.ts +1 -0
- package/dist/src/monorepo/plugins/index.js +3 -0
- package/dist/src/monorepo/resources/index.d.ts +1 -0
- package/dist/src/monorepo/resources/index.js +1 -0
- package/dist/src/secrets/SecretDiscovery.d.ts +46 -0
- package/dist/src/secrets/SecretDiscovery.js +82 -0
- package/dist/src/secrets/SecretManager.d.ts +52 -0
- package/dist/src/secrets/SecretManager.js +75 -0
- package/dist/src/secrets/SecretProvider.d.ts +45 -0
- package/dist/src/secrets/SecretProvider.js +38 -0
- package/dist/src/secrets/index.d.ts +3 -0
- package/dist/src/secrets/index.js +3 -0
- package/dist/src/secrets/providers/VaultOidcHelper.d.ts +39 -0
- package/dist/src/secrets/providers/VaultOidcHelper.js +226 -0
- package/dist/src/secrets/providers/VaultProvider.d.ts +74 -0
- package/dist/src/secrets/providers/VaultProvider.js +266 -0
- package/dist/src/secrets/providers/VaultTokenCache.d.ts +60 -0
- package/dist/src/secrets/providers/VaultTokenCache.js +188 -0
- package/dist/src/secrets/providers/index.d.ts +2 -0
- package/dist/src/secrets/providers/index.js +2 -0
- package/dist/src/types.d.ts +2 -0
- package/dist/src/utils/TemplateExpander.d.ts +13 -1
- package/dist/src/utils/TemplateExpander.js +68 -15
- package/oclif.manifest.json +473 -68
- package/package.json +36 -30
|
@@ -24,15 +24,7 @@
|
|
|
24
24
|
"plugins": {
|
|
25
25
|
"type": "array",
|
|
26
26
|
"items": {
|
|
27
|
-
"
|
|
28
|
-
"required": ["name"],
|
|
29
|
-
"properties": {
|
|
30
|
-
"name": {
|
|
31
|
-
"$ref": "#/definitions/Identifier"
|
|
32
|
-
},
|
|
33
|
-
"config": {}
|
|
34
|
-
},
|
|
35
|
-
"additionalProperties": false
|
|
27
|
+
"$ref": "#/definitions/PluginConfigItem"
|
|
36
28
|
}
|
|
37
29
|
},
|
|
38
30
|
"env": {
|
|
@@ -376,6 +368,178 @@
|
|
|
376
368
|
{ "$ref": "#/definitions/JsonPatchMoveOperation" },
|
|
377
369
|
{ "$ref": "#/definitions/JsonPatchCopyOperation" }
|
|
378
370
|
]
|
|
371
|
+
},
|
|
372
|
+
"VaultTokenAuth": {
|
|
373
|
+
"type": "object",
|
|
374
|
+
"required": ["method", "token"],
|
|
375
|
+
"properties": {
|
|
376
|
+
"method": { "const": "token" },
|
|
377
|
+
"token": { "type": "string", "description": "Vault token for authentication" }
|
|
378
|
+
},
|
|
379
|
+
"additionalProperties": false
|
|
380
|
+
},
|
|
381
|
+
"VaultAppRoleAuth": {
|
|
382
|
+
"type": "object",
|
|
383
|
+
"required": ["method", "roleId", "secretId"],
|
|
384
|
+
"properties": {
|
|
385
|
+
"method": { "const": "approle" },
|
|
386
|
+
"roleId": { "type": "string", "description": "AppRole role ID" },
|
|
387
|
+
"secretId": { "type": "string", "description": "AppRole secret ID" }
|
|
388
|
+
},
|
|
389
|
+
"additionalProperties": false
|
|
390
|
+
},
|
|
391
|
+
"VaultKubernetesAuth": {
|
|
392
|
+
"type": "object",
|
|
393
|
+
"required": ["method", "role"],
|
|
394
|
+
"properties": {
|
|
395
|
+
"method": { "const": "kubernetes" },
|
|
396
|
+
"role": { "type": "string", "description": "Kubernetes auth role name" }
|
|
397
|
+
},
|
|
398
|
+
"additionalProperties": false
|
|
399
|
+
},
|
|
400
|
+
"VaultJwtAuth": {
|
|
401
|
+
"type": "object",
|
|
402
|
+
"required": ["method", "role", "jwt"],
|
|
403
|
+
"properties": {
|
|
404
|
+
"method": { "const": "jwt" },
|
|
405
|
+
"role": { "type": "string", "description": "JWT auth role name" },
|
|
406
|
+
"jwt": { "type": "string", "description": "JWT token for authentication" }
|
|
407
|
+
},
|
|
408
|
+
"additionalProperties": false
|
|
409
|
+
},
|
|
410
|
+
"VaultOidcAuth": {
|
|
411
|
+
"type": "object",
|
|
412
|
+
"required": ["method"],
|
|
413
|
+
"properties": {
|
|
414
|
+
"method": { "const": "oidc" },
|
|
415
|
+
"role": { "type": "string", "description": "OIDC auth role name" },
|
|
416
|
+
"port": { "type": "integer", "description": "Local port for OIDC callback" }
|
|
417
|
+
},
|
|
418
|
+
"additionalProperties": false
|
|
419
|
+
},
|
|
420
|
+
"VaultAuthConfig": {
|
|
421
|
+
"description": "Authentication configuration for HashiCorp Vault",
|
|
422
|
+
"oneOf": [
|
|
423
|
+
{ "$ref": "#/definitions/VaultTokenAuth" },
|
|
424
|
+
{ "$ref": "#/definitions/VaultAppRoleAuth" },
|
|
425
|
+
{ "$ref": "#/definitions/VaultKubernetesAuth" },
|
|
426
|
+
{ "$ref": "#/definitions/VaultJwtAuth" },
|
|
427
|
+
{ "$ref": "#/definitions/VaultOidcAuth" }
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
"VaultPluginConfig": {
|
|
431
|
+
"type": "object",
|
|
432
|
+
"description": "Configuration for the HashiCorp Vault plugin",
|
|
433
|
+
"properties": {
|
|
434
|
+
"address": {
|
|
435
|
+
"type": "string",
|
|
436
|
+
"description": "Vault server address. Defaults to VAULT_ADDR env var."
|
|
437
|
+
},
|
|
438
|
+
"namespace": {
|
|
439
|
+
"type": "string",
|
|
440
|
+
"description": "Vault namespace. Defaults to VAULT_NAMESPACE env var."
|
|
441
|
+
},
|
|
442
|
+
"auth": {
|
|
443
|
+
"$ref": "#/definitions/VaultAuthConfig"
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
"additionalProperties": false
|
|
447
|
+
},
|
|
448
|
+
"AutoDockerPluginConfig": {
|
|
449
|
+
"type": "object",
|
|
450
|
+
"description": "Configuration for the AutoDocker plugin",
|
|
451
|
+
"properties": {
|
|
452
|
+
"glob": {
|
|
453
|
+
"type": "string",
|
|
454
|
+
"description": "Glob pattern to find Dockerfiles. Defaults to '*/Dockerfile'."
|
|
455
|
+
},
|
|
456
|
+
"ignore": {
|
|
457
|
+
"oneOf": [
|
|
458
|
+
{ "type": "string" },
|
|
459
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
460
|
+
],
|
|
461
|
+
"description": "Patterns to ignore when searching for Dockerfiles."
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
"additionalProperties": false
|
|
465
|
+
},
|
|
466
|
+
"DotEnvPluginConfig": {
|
|
467
|
+
"type": "array",
|
|
468
|
+
"description": "Array of .env file paths to load",
|
|
469
|
+
"items": {
|
|
470
|
+
"type": "string"
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
"EmbfilesPluginConfig": {
|
|
474
|
+
"type": "object",
|
|
475
|
+
"description": "Configuration for the Embfiles loader plugin",
|
|
476
|
+
"properties": {
|
|
477
|
+
"glob": {
|
|
478
|
+
"oneOf": [
|
|
479
|
+
{ "type": "string" },
|
|
480
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
481
|
+
],
|
|
482
|
+
"description": "Glob pattern(s) to find Embfiles. Defaults to '*/Embfile.{yaml,yml}'."
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"additionalProperties": false
|
|
486
|
+
},
|
|
487
|
+
"PluginConfigItem": {
|
|
488
|
+
"type": "object",
|
|
489
|
+
"required": ["name"],
|
|
490
|
+
"properties": {
|
|
491
|
+
"name": {
|
|
492
|
+
"$ref": "#/definitions/Identifier"
|
|
493
|
+
},
|
|
494
|
+
"config": {}
|
|
495
|
+
},
|
|
496
|
+
"additionalProperties": false,
|
|
497
|
+
"allOf": [
|
|
498
|
+
{
|
|
499
|
+
"if": {
|
|
500
|
+
"properties": { "name": { "const": "vault" } },
|
|
501
|
+
"required": ["name"]
|
|
502
|
+
},
|
|
503
|
+
"then": {
|
|
504
|
+
"properties": {
|
|
505
|
+
"config": { "$ref": "#/definitions/VaultPluginConfig" }
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"if": {
|
|
511
|
+
"properties": { "name": { "const": "autodocker" } },
|
|
512
|
+
"required": ["name"]
|
|
513
|
+
},
|
|
514
|
+
"then": {
|
|
515
|
+
"properties": {
|
|
516
|
+
"config": { "$ref": "#/definitions/AutoDockerPluginConfig" }
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"if": {
|
|
522
|
+
"properties": { "name": { "const": "dotenv" } },
|
|
523
|
+
"required": ["name"]
|
|
524
|
+
},
|
|
525
|
+
"then": {
|
|
526
|
+
"properties": {
|
|
527
|
+
"config": { "$ref": "#/definitions/DotEnvPluginConfig" }
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
"if": {
|
|
533
|
+
"properties": { "name": { "const": "embfiles" } },
|
|
534
|
+
"required": ["name"]
|
|
535
|
+
},
|
|
536
|
+
"then": {
|
|
537
|
+
"properties": {
|
|
538
|
+
"config": { "$ref": "#/definitions/EmbfilesPluginConfig" }
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
]
|
|
379
543
|
}
|
|
380
544
|
}
|
|
381
545
|
}
|
package/dist/src/context.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { EmbContext } from './types.js';
|
|
2
2
|
export declare const getContext: () => EmbContext;
|
|
3
3
|
export declare const setContext: (ctx: EmbContext) => EmbContext;
|
|
4
|
+
/**
|
|
5
|
+
* Check if the context's EMB_ROOT matches the current environment.
|
|
6
|
+
* Used to detect when tests switch between different example monorepos.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isContextStale: () => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Reset the context. Used in testing to ensure a fresh context for each test suite.
|
|
11
|
+
*/
|
|
12
|
+
export declare const resetContext: () => void;
|
package/dist/src/context.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
let context;
|
|
2
|
+
let contextEmbRoot;
|
|
2
3
|
export const getContext = () => {
|
|
3
4
|
return context;
|
|
4
5
|
};
|
|
5
6
|
export const setContext = (ctx) => {
|
|
6
7
|
context = ctx;
|
|
8
|
+
contextEmbRoot = process.env.EMB_ROOT;
|
|
7
9
|
return ctx;
|
|
8
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Check if the context's EMB_ROOT matches the current environment.
|
|
13
|
+
* Used to detect when tests switch between different example monorepos.
|
|
14
|
+
*/
|
|
15
|
+
export const isContextStale = () => {
|
|
16
|
+
if (!context) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return contextEmbRoot !== process.env.EMB_ROOT;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Reset the context. Used in testing to ensure a fresh context for each test suite.
|
|
23
|
+
*/
|
|
24
|
+
export const resetContext = () => {
|
|
25
|
+
context = undefined;
|
|
26
|
+
contextEmbRoot = undefined;
|
|
27
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* https://docs.docker.com/reference/cli/docker/compose/logs/
|
|
6
|
+
*/
|
|
7
|
+
export declare const ComposeLogsOperationInputSchema: z.ZodOptional<z.ZodObject<{
|
|
8
|
+
services: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
9
|
+
follow: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
timestamps: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
tail: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
export declare class ComposeLogsOperation extends AbstractOperation<typeof ComposeLogsOperationInputSchema, void> {
|
|
14
|
+
protected out?: Writable | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* @param out Optional writable stream to capture output. If not provided,
|
|
17
|
+
* output is streamed directly to the terminal (stdio: 'inherit').
|
|
18
|
+
*/
|
|
19
|
+
constructor(out?: Writable | undefined);
|
|
20
|
+
protected _run(input: z.input<typeof ComposeLogsOperationInputSchema>): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { AbstractOperation } from '../../../operations/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* https://docs.docker.com/reference/cli/docker/compose/logs/
|
|
6
|
+
*/
|
|
7
|
+
export const ComposeLogsOperationInputSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
services: z
|
|
10
|
+
.array(z.string())
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('The list of services to show logs for (all if omitted)'),
|
|
13
|
+
follow: z.boolean().optional().describe('Follow log output'),
|
|
14
|
+
timestamps: z.boolean().optional().describe('Show timestamps'),
|
|
15
|
+
tail: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Number of lines to show from the end'),
|
|
19
|
+
})
|
|
20
|
+
.optional();
|
|
21
|
+
export class ComposeLogsOperation extends AbstractOperation {
|
|
22
|
+
out;
|
|
23
|
+
/**
|
|
24
|
+
* @param out Optional writable stream to capture output. If not provided,
|
|
25
|
+
* output is streamed directly to the terminal (stdio: 'inherit').
|
|
26
|
+
*/
|
|
27
|
+
constructor(out) {
|
|
28
|
+
super(ComposeLogsOperationInputSchema);
|
|
29
|
+
this.out = out;
|
|
30
|
+
}
|
|
31
|
+
async _run(input) {
|
|
32
|
+
const { monorepo } = this.context;
|
|
33
|
+
const cmd = 'docker';
|
|
34
|
+
const args = ['compose', 'logs'];
|
|
35
|
+
const follow = input?.follow ?? true;
|
|
36
|
+
if (follow) {
|
|
37
|
+
args.push('-f');
|
|
38
|
+
}
|
|
39
|
+
if (input?.timestamps) {
|
|
40
|
+
args.push('-t');
|
|
41
|
+
}
|
|
42
|
+
if (input?.tail !== undefined) {
|
|
43
|
+
args.push('--tail', String(input.tail));
|
|
44
|
+
}
|
|
45
|
+
if (input?.services && input.services.length > 0) {
|
|
46
|
+
args.push(...input.services);
|
|
47
|
+
}
|
|
48
|
+
const child = spawn(cmd, args, {
|
|
49
|
+
stdio: this.out ? 'pipe' : 'inherit',
|
|
50
|
+
cwd: monorepo.rootDir,
|
|
51
|
+
});
|
|
52
|
+
if (this.out && child.stdout && child.stderr) {
|
|
53
|
+
child.stdout.pipe(this.out, { end: false });
|
|
54
|
+
child.stderr.pipe(this.out, { end: false });
|
|
55
|
+
}
|
|
56
|
+
const forward = (sig) => {
|
|
57
|
+
try {
|
|
58
|
+
child.kill(sig);
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
};
|
|
62
|
+
const signals = [
|
|
63
|
+
'SIGINT',
|
|
64
|
+
'SIGTERM',
|
|
65
|
+
'SIGHUP',
|
|
66
|
+
'SIGQUIT',
|
|
67
|
+
];
|
|
68
|
+
signals.forEach((sig) => {
|
|
69
|
+
process.on(sig, () => forward(sig));
|
|
70
|
+
});
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
child.on('error', (err) => {
|
|
73
|
+
reject(new Error(`Failed to execute docker compose logs: ${err.message}`));
|
|
74
|
+
});
|
|
75
|
+
child.on('exit', (code) => {
|
|
76
|
+
if (code !== null && code !== 0) {
|
|
77
|
+
reject(new Error(`docker compose logs exited with code ${code}`));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComposeDownOperation.js';
|
|
2
2
|
export * from './ComposeExecOperation.js';
|
|
3
3
|
export * from './ComposeExecShellOperation.js';
|
|
4
|
+
export * from './ComposeLogsOperation.js';
|
|
4
5
|
export * from './ComposePsOperation.js';
|
|
5
6
|
export * from './ComposeRestartOperation.js';
|
|
6
7
|
export * from './ComposeStartOperation.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './ComposeDownOperation.js';
|
|
2
2
|
export * from './ComposeExecOperation.js';
|
|
3
3
|
export * from './ComposeExecShellOperation.js';
|
|
4
|
+
export * from './ComposeLogsOperation.js';
|
|
4
5
|
export * from './ComposePsOperation.js';
|
|
5
6
|
export * from './ComposeRestartOperation.js';
|
|
6
7
|
export * from './ComposeStartOperation.js';
|
|
@@ -19,12 +19,22 @@ class DockerImageResourceBuilder extends SentinelFileBasedBuilder {
|
|
|
19
19
|
: buildContext.monorepo.join(buildContext.component.rootDir);
|
|
20
20
|
}
|
|
21
21
|
async getReference() {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const configTag = this.config?.tag;
|
|
23
|
+
let imageNamePart;
|
|
24
|
+
let tagPart;
|
|
25
|
+
if (configTag && configTag.includes(':')) {
|
|
26
|
+
// config.tag contains both image name and tag (e.g., "myimage:v1.0.0")
|
|
27
|
+
const colonIndex = configTag.lastIndexOf(':');
|
|
28
|
+
imageNamePart = configTag.slice(0, colonIndex);
|
|
29
|
+
tagPart = configTag.slice(colonIndex + 1);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// config.tag is just an image name or undefined
|
|
33
|
+
imageNamePart = configTag || this.component.name;
|
|
34
|
+
tagPart = this.monorepo.defaults.docker?.tag || 'latest';
|
|
35
|
+
}
|
|
36
|
+
const imageName = [this.monorepo.name, imageNamePart].join('/');
|
|
37
|
+
return this.monorepo.expand(`${imageName}:${tagPart}`);
|
|
28
38
|
}
|
|
29
39
|
get monorepo() {
|
|
30
40
|
return this.buildContext.monorepo;
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TaskManagerFactory } from '../index.js';
|
|
1
|
+
import { getContext, TaskManagerFactory } from '../index.js';
|
|
2
2
|
import jsonpatch from 'fast-json-patch';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { TemplateExpander } from '../utils/index.js';
|
|
@@ -106,12 +106,20 @@ export class Monorepo {
|
|
|
106
106
|
}
|
|
107
107
|
// Helper to expand a record of strings
|
|
108
108
|
async expand(toExpand, vars, expander = new TemplateExpander()) {
|
|
109
|
+
const secrets = getContext()?.secrets;
|
|
110
|
+
const sources = {
|
|
111
|
+
env: process.env,
|
|
112
|
+
vars: vars || this.vars,
|
|
113
|
+
};
|
|
114
|
+
// Add all registered secret providers as sources
|
|
115
|
+
if (secrets) {
|
|
116
|
+
for (const providerName of secrets.getProviderNames()) {
|
|
117
|
+
sources[providerName] = secrets.createSource(providerName);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
109
120
|
const options = {
|
|
110
121
|
default: 'vars',
|
|
111
|
-
sources
|
|
112
|
-
env: process.env,
|
|
113
|
-
vars: vars || this.vars,
|
|
114
|
-
},
|
|
122
|
+
sources,
|
|
115
123
|
};
|
|
116
124
|
return expander.expandRecord(toExpand, options);
|
|
117
125
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PRESET_TIMER, } from 'listr2';
|
|
2
2
|
import * as z from 'zod';
|
|
3
|
-
import { EMBCollection, findRunOrder, } from '../../index.js';
|
|
4
|
-
import { ResourceFactory } from '../../resources/ResourceFactory.js';
|
|
3
|
+
import { EMBCollection, findRunOrder, ResourceFactory, } from '../../index.js';
|
|
5
4
|
import { AbstractOperation } from '../../../operations/index.js';
|
|
6
5
|
const schema = z.object({
|
|
7
6
|
resources: z
|
|
@@ -21,7 +21,7 @@ export type TaskWithScriptAndComponent = TaskInfo & {
|
|
|
21
21
|
export declare class RunTasksOperation implements IOperation<RunTasksOperationParams, Array<TaskInfo>> {
|
|
22
22
|
run(params: RunTasksOperationParams): Promise<Array<TaskInfo>>;
|
|
23
23
|
protected runDocker(task: TaskWithScriptAndComponent, out?: Writable): Promise<void>;
|
|
24
|
-
protected runLocal(task: TaskWithScript,
|
|
24
|
+
protected runLocal(task: TaskWithScript, _out: Writable): Promise<import("stream").Readable>;
|
|
25
25
|
private defaultExecutorFor;
|
|
26
26
|
private ensureExecutorValid;
|
|
27
27
|
private availableExecutorsFor;
|
|
@@ -82,7 +82,7 @@ export class RunTasksOperation {
|
|
|
82
82
|
env: await monorepo.expand(task.vars || {}),
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
-
async runLocal(task,
|
|
85
|
+
async runLocal(task, _out) {
|
|
86
86
|
const { monorepo } = getContext();
|
|
87
87
|
const cwd = task.component
|
|
88
88
|
? monorepo.join(monorepo.component(task.component).rootDir)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { VaultAuthConfig } from '../../secrets/providers/VaultProvider.js';
|
|
2
|
+
import { AbstractPlugin } from './plugin.js';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the Vault plugin in .emb.yml
|
|
5
|
+
*/
|
|
6
|
+
export interface VaultPluginConfig {
|
|
7
|
+
/** Vault server address. Defaults to VAULT_ADDR env var. */
|
|
8
|
+
address?: string;
|
|
9
|
+
/** Authentication configuration */
|
|
10
|
+
auth?: VaultAuthConfig;
|
|
11
|
+
/** Vault namespace. Defaults to VAULT_NAMESPACE env var. */
|
|
12
|
+
namespace?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Plugin that integrates HashiCorp Vault with EMB.
|
|
16
|
+
*
|
|
17
|
+
* Usage in .emb.yml:
|
|
18
|
+
* ```yaml
|
|
19
|
+
* plugins:
|
|
20
|
+
* - name: vault
|
|
21
|
+
* config:
|
|
22
|
+
* address: ${env:VAULT_ADDR:-http://localhost:8200}
|
|
23
|
+
* auth:
|
|
24
|
+
* method: token
|
|
25
|
+
* token: ${env:VAULT_TOKEN}
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Then use secrets in templates:
|
|
29
|
+
* ```yaml
|
|
30
|
+
* env:
|
|
31
|
+
* DB_PASSWORD: ${vault:secret/myapp/db#password}
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class VaultPlugin extends AbstractPlugin<VaultPluginConfig> {
|
|
35
|
+
static name: string;
|
|
36
|
+
private provider;
|
|
37
|
+
init(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the plugin configuration, filling in defaults from env vars.
|
|
40
|
+
*/
|
|
41
|
+
private resolveConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve authentication configuration.
|
|
44
|
+
*/
|
|
45
|
+
private resolveAuth;
|
|
46
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getContext } from '../../context.js';
|
|
2
|
+
import { VaultProvider, } from '../../secrets/providers/VaultProvider.js';
|
|
3
|
+
import { AbstractPlugin } from './plugin.js';
|
|
4
|
+
/**
|
|
5
|
+
* Plugin that integrates HashiCorp Vault with EMB.
|
|
6
|
+
*
|
|
7
|
+
* Usage in .emb.yml:
|
|
8
|
+
* ```yaml
|
|
9
|
+
* plugins:
|
|
10
|
+
* - name: vault
|
|
11
|
+
* config:
|
|
12
|
+
* address: ${env:VAULT_ADDR:-http://localhost:8200}
|
|
13
|
+
* auth:
|
|
14
|
+
* method: token
|
|
15
|
+
* token: ${env:VAULT_TOKEN}
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Then use secrets in templates:
|
|
19
|
+
* ```yaml
|
|
20
|
+
* env:
|
|
21
|
+
* DB_PASSWORD: ${vault:secret/myapp/db#password}
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class VaultPlugin extends AbstractPlugin {
|
|
25
|
+
static name = 'vault';
|
|
26
|
+
provider = null;
|
|
27
|
+
async init() {
|
|
28
|
+
const resolvedConfig = this.resolveConfig();
|
|
29
|
+
this.provider = new VaultProvider(resolvedConfig);
|
|
30
|
+
await this.provider.connect();
|
|
31
|
+
// Register the provider with the global SecretManager
|
|
32
|
+
const context = getContext();
|
|
33
|
+
if (context?.secrets) {
|
|
34
|
+
context.secrets.register('vault', this.provider);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the plugin configuration, filling in defaults from env vars.
|
|
39
|
+
*/
|
|
40
|
+
resolveConfig() {
|
|
41
|
+
const address = this.config.address || process.env.VAULT_ADDR;
|
|
42
|
+
if (!address) {
|
|
43
|
+
throw new Error('Vault address not configured. Set VAULT_ADDR environment variable or configure address in plugin config.');
|
|
44
|
+
}
|
|
45
|
+
const auth = this.resolveAuth();
|
|
46
|
+
return {
|
|
47
|
+
address,
|
|
48
|
+
namespace: this.config.namespace || process.env.VAULT_NAMESPACE,
|
|
49
|
+
auth,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve authentication configuration.
|
|
54
|
+
*/
|
|
55
|
+
resolveAuth() {
|
|
56
|
+
// If explicit auth config is provided, use it
|
|
57
|
+
if (this.config.auth) {
|
|
58
|
+
return this.config.auth;
|
|
59
|
+
}
|
|
60
|
+
// Try to infer from environment
|
|
61
|
+
const token = process.env.VAULT_TOKEN;
|
|
62
|
+
if (token) {
|
|
63
|
+
return { method: 'token', token };
|
|
64
|
+
}
|
|
65
|
+
const roleId = process.env.VAULT_ROLE_ID;
|
|
66
|
+
const secretId = process.env.VAULT_SECRET_ID;
|
|
67
|
+
if (roleId && secretId) {
|
|
68
|
+
return { method: 'approle', roleId, secretId };
|
|
69
|
+
}
|
|
70
|
+
const k8sRole = process.env.VAULT_K8S_ROLE;
|
|
71
|
+
if (k8sRole) {
|
|
72
|
+
return { method: 'kubernetes', role: k8sRole };
|
|
73
|
+
}
|
|
74
|
+
// JWT auth (non-interactive, for CI/CD)
|
|
75
|
+
const jwt = process.env.VAULT_JWT;
|
|
76
|
+
const jwtRole = process.env.VAULT_JWT_ROLE;
|
|
77
|
+
if (jwt && jwtRole) {
|
|
78
|
+
return { method: 'jwt', role: jwtRole, jwt };
|
|
79
|
+
}
|
|
80
|
+
// OIDC auth (interactive browser flow)
|
|
81
|
+
const oidcRole = process.env.VAULT_OIDC_ROLE;
|
|
82
|
+
if (oidcRole !== undefined) {
|
|
83
|
+
return { method: 'oidc', role: oidcRole || undefined };
|
|
84
|
+
}
|
|
85
|
+
throw new Error('Vault authentication not configured. ' +
|
|
86
|
+
'Set VAULT_TOKEN, or VAULT_ROLE_ID + VAULT_SECRET_ID, ' +
|
|
87
|
+
'or VAULT_K8S_ROLE, or VAULT_JWT + VAULT_JWT_ROLE, ' +
|
|
88
|
+
'or VAULT_OIDC_ROLE environment variable, ' +
|
|
89
|
+
'or configure auth in plugin config.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -2,6 +2,7 @@ import { AbstractPlugin } from './plugin.js';
|
|
|
2
2
|
export * from './AutoDockerPlugin.js';
|
|
3
3
|
export * from './DotEnvPlugin.js';
|
|
4
4
|
export * from './EmbfileLoaderPlugin.js';
|
|
5
|
+
export * from './VaultPlugin.js';
|
|
5
6
|
import { Monorepo } from '../index.js';
|
|
6
7
|
export type AbstractPluginConstructor = new <C, P extends AbstractPlugin<C>>(config: C, monorepo: Monorepo) => P;
|
|
7
8
|
export declare const registerPlugin: (plugin: AbstractPluginConstructor) => void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from './AutoDockerPlugin.js';
|
|
2
2
|
export * from './DotEnvPlugin.js';
|
|
3
3
|
export * from './EmbfileLoaderPlugin.js';
|
|
4
|
+
export * from './VaultPlugin.js';
|
|
4
5
|
import { AutoDockerPlugin } from './AutoDockerPlugin.js';
|
|
5
6
|
import { DotEnvPlugin } from './DotEnvPlugin.js';
|
|
6
7
|
import { EmbfileLoaderPlugin } from './EmbfileLoaderPlugin.js';
|
|
8
|
+
import { VaultPlugin } from './VaultPlugin.js';
|
|
7
9
|
const PluginRegistry = new Map();
|
|
8
10
|
export const registerPlugin = (plugin) => {
|
|
9
11
|
if (PluginRegistry.has(plugin.name)) {
|
|
@@ -21,3 +23,4 @@ export const getPlugin = (name) => {
|
|
|
21
23
|
registerPlugin(AutoDockerPlugin);
|
|
22
24
|
registerPlugin(DotEnvPlugin);
|
|
23
25
|
registerPlugin(EmbfileLoaderPlugin);
|
|
26
|
+
registerPlugin(VaultPlugin);
|