@enspirit/emb 0.23.0 → 0.24.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 +42 -42
- package/dist/src/cli/abstract/KubernetesCommand.d.ts +5 -1
- package/dist/src/cli/abstract/KubernetesCommand.js +11 -2
- package/dist/src/cli/commands/kubernetes/logs.js +7 -12
- package/dist/src/cli/commands/kubernetes/ps.js +2 -1
- package/dist/src/cli/commands/kubernetes/restart.js +2 -1
- package/dist/src/cli/commands/kubernetes/shell.js +7 -10
- package/dist/src/config/schema.d.ts +33 -1
- package/dist/src/config/schema.json +42 -1
- package/dist/src/docker/compose/client.js +2 -2
- package/dist/src/docker/compose/operations/ComposeLogsArchiveOperation.d.ts +1 -1
- package/dist/src/kubernetes/index.d.ts +1 -0
- package/dist/src/kubernetes/index.js +1 -0
- package/dist/src/kubernetes/operations/GetComponentPodOperation.d.ts +17 -0
- package/dist/src/kubernetes/operations/GetComponentPodOperation.js +77 -0
- package/dist/src/kubernetes/operations/GetDeploymentPodsOperation.js +3 -2
- package/dist/src/kubernetes/operations/PodExecOperation.d.ts +20 -0
- package/dist/src/kubernetes/operations/PodExecOperation.js +158 -0
- package/dist/src/kubernetes/operations/index.d.ts +3 -0
- package/dist/src/kubernetes/operations/index.js +3 -0
- package/dist/src/kubernetes/utils/index.d.ts +1 -0
- package/dist/src/kubernetes/utils/index.js +1 -0
- package/dist/src/kubernetes/utils/resolveNamespace.d.ts +13 -0
- package/dist/src/kubernetes/utils/resolveNamespace.js +12 -0
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.d.ts +2 -0
- package/dist/src/monorepo/operations/tasks/RunTasksOperation.js +43 -3
- package/oclif.manifest.json +142 -145
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { V1Pod } from '@kubernetes/client-node';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { Component } from '../../monorepo/index.js';
|
|
4
|
+
import { AbstractOperation } from '../../operations/index.js';
|
|
5
|
+
declare const schema: z.ZodObject<{
|
|
6
|
+
component: z.ZodCustom<Component, Component>;
|
|
7
|
+
namespace: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export interface GetComponentPodOutput {
|
|
10
|
+
container: string;
|
|
11
|
+
pod: V1Pod;
|
|
12
|
+
}
|
|
13
|
+
export declare class GetComponentPodOperation extends AbstractOperation<typeof schema, GetComponentPodOutput> {
|
|
14
|
+
constructor();
|
|
15
|
+
protected _run(input: z.input<typeof schema>): Promise<GetComponentPodOutput>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { CliError } from '../../errors.js';
|
|
3
|
+
import { Component } from '../../monorepo/index.js';
|
|
4
|
+
import { AbstractOperation } from '../../operations/index.js';
|
|
5
|
+
const schema = z.object({
|
|
6
|
+
component: z
|
|
7
|
+
.instanceof(Component)
|
|
8
|
+
.describe('The component to get the pod for'),
|
|
9
|
+
namespace: z.string().describe('The Kubernetes namespace'),
|
|
10
|
+
});
|
|
11
|
+
export class GetComponentPodOperation extends AbstractOperation {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(schema);
|
|
14
|
+
}
|
|
15
|
+
async _run(input) {
|
|
16
|
+
const { kubernetes, monorepo } = this.context;
|
|
17
|
+
const { component, namespace } = input;
|
|
18
|
+
const k8sConfig = component.config.kubernetes;
|
|
19
|
+
const projectK8sConfig = monorepo.config.defaults?.kubernetes;
|
|
20
|
+
// Build label selector: use explicit config or default convention
|
|
21
|
+
// Priority: component.kubernetes.selector > project.kubernetes.selectorLabel > default
|
|
22
|
+
const selectorLabel = projectK8sConfig?.selectorLabel ?? 'app.kubernetes.io/component';
|
|
23
|
+
const labelSelector = k8sConfig?.selector ?? `${selectorLabel}=${component.name}`;
|
|
24
|
+
// List pods matching the selector
|
|
25
|
+
const res = await kubernetes.core.listNamespacedPod({
|
|
26
|
+
namespace,
|
|
27
|
+
labelSelector,
|
|
28
|
+
});
|
|
29
|
+
// Filter to ready pods
|
|
30
|
+
const readyPods = res.items.filter((pod) => {
|
|
31
|
+
const conditions = pod.status?.conditions ?? [];
|
|
32
|
+
return conditions.some((c) => c.type === 'Ready' && c.status === 'True');
|
|
33
|
+
});
|
|
34
|
+
if (readyPods.length === 0) {
|
|
35
|
+
throw new CliError('K8S_NO_READY_PODS', `No ready pods found for component "${component.name}" in namespace "${namespace}"`, [
|
|
36
|
+
`Label selector used: ${labelSelector}`,
|
|
37
|
+
`Check pod status: kubectl get pods -l ${labelSelector} -n ${namespace}`,
|
|
38
|
+
`To use a different selector, add kubernetes.selector to component config`,
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
// Get first ready pod
|
|
42
|
+
const pod = readyPods[0];
|
|
43
|
+
// Determine container name
|
|
44
|
+
const containers = pod.spec?.containers ?? [];
|
|
45
|
+
if (containers.length === 0) {
|
|
46
|
+
throw new CliError('K8S_NO_CONTAINERS', `Pod "${pod.metadata?.name}" has no containers`);
|
|
47
|
+
}
|
|
48
|
+
let containerName;
|
|
49
|
+
if (k8sConfig?.container) {
|
|
50
|
+
// Use explicit container config
|
|
51
|
+
containerName = k8sConfig.container;
|
|
52
|
+
const containerExists = containers.some((c) => c.name === containerName);
|
|
53
|
+
if (!containerExists) {
|
|
54
|
+
throw new CliError('K8S_CONTAINER_NOT_FOUND', `Container "${containerName}" not found in pod "${pod.metadata?.name}"`, [
|
|
55
|
+
`Available containers: ${containers.map((c) => c.name).join(', ')}`,
|
|
56
|
+
`Update kubernetes.container in component config if needed`,
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (containers.length === 1) {
|
|
61
|
+
// Single container pod: use it
|
|
62
|
+
containerName = containers[0].name;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Multi-container pod: require explicit config
|
|
66
|
+
throw new CliError('K8S_MULTI_CONTAINER', `Pod "${pod.metadata?.name}" has multiple containers, explicit container config required`, [
|
|
67
|
+
`Available containers: ${containers.map((c) => c.name).join(', ')}`,
|
|
68
|
+
`Add kubernetes.container to component "${component.name}" config:`,
|
|
69
|
+
` components:`,
|
|
70
|
+
` ${component.name}:`,
|
|
71
|
+
` kubernetes:`,
|
|
72
|
+
` container: <container-name>`,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
return { pod, container: containerName };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -10,10 +10,11 @@ export class GetDeploymentPodsOperation extends AbstractOperation {
|
|
|
10
10
|
super(schema);
|
|
11
11
|
}
|
|
12
12
|
async _run(input) {
|
|
13
|
-
const { kubernetes } = getContext();
|
|
13
|
+
const { kubernetes, monorepo } = getContext();
|
|
14
|
+
const selectorLabel = monorepo.config.defaults?.kubernetes?.selectorLabel ?? 'app.kubernetes.io/component';
|
|
14
15
|
const res = await kubernetes.core.listNamespacedPod({
|
|
15
16
|
namespace: input.namespace,
|
|
16
|
-
labelSelector:
|
|
17
|
+
labelSelector: `${selectorLabel}=${input.deployment}`,
|
|
17
18
|
});
|
|
18
19
|
return res.items;
|
|
19
20
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { AbstractOperation } from '../../operations/index.js';
|
|
4
|
+
declare const schema: z.ZodObject<{
|
|
5
|
+
namespace: z.ZodString;
|
|
6
|
+
podName: z.ZodString;
|
|
7
|
+
container: z.ZodOptional<z.ZodString>;
|
|
8
|
+
script: z.ZodString;
|
|
9
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
10
|
+
interactive: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
11
|
+
tty: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
12
|
+
workingDir: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare class PodExecOperation extends AbstractOperation<typeof schema, void> {
|
|
15
|
+
protected out?: Writable | undefined;
|
|
16
|
+
constructor(out?: Writable | undefined);
|
|
17
|
+
protected _run(input: z.input<typeof schema>): Promise<void>;
|
|
18
|
+
private wrapError;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Exec } from '@kubernetes/client-node';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { CliError } from '../../errors.js';
|
|
4
|
+
import { AbstractOperation } from '../../operations/index.js';
|
|
5
|
+
const schema = z.object({
|
|
6
|
+
namespace: z.string().describe('The namespace of the pod'),
|
|
7
|
+
podName: z.string().describe('The name of the pod'),
|
|
8
|
+
container: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe('The container name (required for multi-container pods)'),
|
|
12
|
+
script: z.string().describe('Command to run, as a string'),
|
|
13
|
+
env: z
|
|
14
|
+
.record(z.string(), z.string())
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Environment variables to pass to the command'),
|
|
17
|
+
interactive: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.default(false)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Whether the command is interactive'),
|
|
22
|
+
tty: z.boolean().default(false).optional().describe('Allocate a pseudo-TTY'),
|
|
23
|
+
workingDir: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('The working directory for the command'),
|
|
27
|
+
});
|
|
28
|
+
export class PodExecOperation extends AbstractOperation {
|
|
29
|
+
out;
|
|
30
|
+
constructor(out) {
|
|
31
|
+
super(schema);
|
|
32
|
+
this.out = out;
|
|
33
|
+
}
|
|
34
|
+
async _run(input) {
|
|
35
|
+
const { kubernetes } = this.context;
|
|
36
|
+
const exec = new Exec(kubernetes.config);
|
|
37
|
+
const isInteractive = input.interactive || input.tty;
|
|
38
|
+
// Build the command with optional workdir and env vars
|
|
39
|
+
let { script } = input;
|
|
40
|
+
// Handle working directory by wrapping the command
|
|
41
|
+
if (input.workingDir) {
|
|
42
|
+
script = `cd ${JSON.stringify(input.workingDir)} && ${script}`;
|
|
43
|
+
}
|
|
44
|
+
// Build environment variable exports
|
|
45
|
+
const envExports = Object.entries(input.env || {})
|
|
46
|
+
.map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
|
|
47
|
+
.join('; ');
|
|
48
|
+
if (envExports) {
|
|
49
|
+
script = `${envExports}; ${script}`;
|
|
50
|
+
}
|
|
51
|
+
const command = ['sh', '-c', script];
|
|
52
|
+
// Determine container name
|
|
53
|
+
const containerName = input.container;
|
|
54
|
+
if (!containerName) {
|
|
55
|
+
throw new CliError('K8S_NO_CONTAINER', 'Container name is required', [
|
|
56
|
+
'Specify kubernetes.container in component config:',
|
|
57
|
+
' components:',
|
|
58
|
+
' <component>:',
|
|
59
|
+
' kubernetes:',
|
|
60
|
+
' container: <container-name>',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
const stdout = isInteractive ? process.stdout : (this.out ?? null);
|
|
64
|
+
const stderr = isInteractive ? process.stderr : (this.out ?? null);
|
|
65
|
+
const stdin = isInteractive ? process.stdin : null;
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
let exitCode = 0;
|
|
68
|
+
let ws;
|
|
69
|
+
let sigintHandler;
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
// Restore stdin raw mode
|
|
72
|
+
if (isInteractive && process.stdin.isTTY) {
|
|
73
|
+
process.stdin.setRawMode?.(false);
|
|
74
|
+
}
|
|
75
|
+
// Remove SIGINT handler
|
|
76
|
+
if (sigintHandler) {
|
|
77
|
+
process.off('SIGINT', sigintHandler);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const statusCallback = (status) => {
|
|
81
|
+
if (status.status === 'Success') {
|
|
82
|
+
exitCode = 0;
|
|
83
|
+
}
|
|
84
|
+
else if (status.status === 'Failure') {
|
|
85
|
+
// Try to extract exit code from the status message
|
|
86
|
+
const match = status.message?.match(/command terminated with exit code (\d+)/);
|
|
87
|
+
exitCode = match ? Number.parseInt(match[1], 10) : 1;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
// Set raw mode for interactive sessions
|
|
91
|
+
if (isInteractive && process.stdin.isTTY) {
|
|
92
|
+
process.stdin.setRawMode?.(true);
|
|
93
|
+
// Handle Ctrl+C gracefully
|
|
94
|
+
sigintHandler = () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
if (ws) {
|
|
97
|
+
ws.close();
|
|
98
|
+
}
|
|
99
|
+
reject(new Error('Interrupted'));
|
|
100
|
+
};
|
|
101
|
+
process.on('SIGINT', sigintHandler);
|
|
102
|
+
}
|
|
103
|
+
exec
|
|
104
|
+
.exec(input.namespace, input.podName, containerName, command, stdout, stderr, stdin, isInteractive ?? false, statusCallback)
|
|
105
|
+
.then((websocket) => {
|
|
106
|
+
ws = websocket;
|
|
107
|
+
websocket.on('close', () => {
|
|
108
|
+
cleanup();
|
|
109
|
+
if (exitCode === 0) {
|
|
110
|
+
resolve();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
reject(new Error(`command failed (exit ${exitCode})`));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
websocket.on('error', (error) => {
|
|
117
|
+
cleanup();
|
|
118
|
+
reject(this.wrapError(error, input));
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
.catch((error) => {
|
|
122
|
+
cleanup();
|
|
123
|
+
reject(this.wrapError(error, input));
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
wrapError(error, input) {
|
|
128
|
+
const message = error.message || String(error);
|
|
129
|
+
// Handle common Kubernetes API errors
|
|
130
|
+
if (message.includes('not found') || message.includes('404')) {
|
|
131
|
+
return new CliError('K8S_POD_NOT_FOUND', `Pod "${input.podName}" not found in namespace "${input.namespace}"`, [
|
|
132
|
+
`Check pod exists: kubectl get pod ${input.podName} -n ${input.namespace}`,
|
|
133
|
+
`List pods: kubectl get pods -n ${input.namespace}`,
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
if (message.includes('Forbidden') || message.includes('403')) {
|
|
137
|
+
return new CliError('K8S_FORBIDDEN', `Access denied to pod "${input.podName}" in namespace "${input.namespace}"`, [
|
|
138
|
+
`Check permissions: kubectl auth can-i create pods/exec -n ${input.namespace}`,
|
|
139
|
+
'Ensure your kubeconfig has the correct context and credentials',
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
if (message.includes('Unauthorized') || message.includes('401')) {
|
|
143
|
+
return new CliError('K8S_UNAUTHORIZED', 'Kubernetes authentication failed', [
|
|
144
|
+
'Check your kubeconfig: kubectl config view',
|
|
145
|
+
'Try re-authenticating: kubectl auth login',
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
if (message.includes('connection refused') ||
|
|
149
|
+
message.includes('ECONNREFUSED')) {
|
|
150
|
+
return new CliError('K8S_CONNECTION_REFUSED', 'Cannot connect to Kubernetes cluster', [
|
|
151
|
+
'Check cluster is running: kubectl cluster-info',
|
|
152
|
+
'Verify kubeconfig context: kubectl config current-context',
|
|
153
|
+
]);
|
|
154
|
+
}
|
|
155
|
+
// Return original error if not a known type
|
|
156
|
+
return error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resolveNamespace.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resolveNamespace.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the Kubernetes namespace to use for operations.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (first non-empty value wins):
|
|
5
|
+
* 1. CLI flag (--namespace)
|
|
6
|
+
* 2. Environment variable (K8S_NAMESPACE)
|
|
7
|
+
* 3. Config file (kubernetes.namespace in .emb.yml)
|
|
8
|
+
* 4. Default: "default"
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveNamespace(options: {
|
|
11
|
+
cliFlag?: string;
|
|
12
|
+
config?: string;
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the Kubernetes namespace to use for operations.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order (first non-empty value wins):
|
|
5
|
+
* 1. CLI flag (--namespace)
|
|
6
|
+
* 2. Environment variable (K8S_NAMESPACE)
|
|
7
|
+
* 3. Config file (kubernetes.namespace in .emb.yml)
|
|
8
|
+
* 4. Default: "default"
|
|
9
|
+
*/
|
|
10
|
+
export function resolveNamespace(options) {
|
|
11
|
+
return (options.cliFlag || process.env.K8S_NAMESPACE || options.config || 'default');
|
|
12
|
+
}
|
|
@@ -3,6 +3,7 @@ import { TaskInfo } from '../../index.js';
|
|
|
3
3
|
import { IOperation } from '../../../operations/index.js';
|
|
4
4
|
export declare enum ExecutorType {
|
|
5
5
|
container = "container",
|
|
6
|
+
kubernetes = "kubernetes",
|
|
6
7
|
local = "local"
|
|
7
8
|
}
|
|
8
9
|
export type RunTasksOperationParams = {
|
|
@@ -22,6 +23,7 @@ export declare class RunTasksOperation implements IOperation<RunTasksOperationPa
|
|
|
22
23
|
run(params: RunTasksOperationParams): Promise<Array<TaskInfo>>;
|
|
23
24
|
protected runDocker(task: TaskWithScriptAndComponent, out?: Writable): Promise<void>;
|
|
24
25
|
protected runLocal(task: TaskWithScript, _out: Writable): Promise<import("stream").Readable>;
|
|
26
|
+
protected runKubernetes(task: TaskWithScriptAndComponent, out?: Writable): Promise<void>;
|
|
25
27
|
private defaultExecutorFor;
|
|
26
28
|
private ensureExecutorValid;
|
|
27
29
|
private availableExecutorsFor;
|
|
@@ -3,11 +3,14 @@ import { input } from '@inquirer/prompts';
|
|
|
3
3
|
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
|
|
4
4
|
import { PassThrough } from 'node:stream';
|
|
5
5
|
import { ContainerExecOperation } from '../../../docker/index.js';
|
|
6
|
+
import { GetComponentPodOperation, PodExecOperation, } from '../../../kubernetes/operations/index.js';
|
|
7
|
+
import { resolveNamespace } from '../../../kubernetes/utils/index.js';
|
|
6
8
|
import { EMBCollection, findRunOrder } from '../../index.js';
|
|
7
9
|
import { ExecuteLocalCommandOperation } from '../index.js';
|
|
8
10
|
export var ExecutorType;
|
|
9
11
|
(function (ExecutorType) {
|
|
10
12
|
ExecutorType["container"] = "container";
|
|
13
|
+
ExecutorType["kubernetes"] = "kubernetes";
|
|
11
14
|
ExecutorType["local"] = "local";
|
|
12
15
|
})(ExecutorType || (ExecutorType = {}));
|
|
13
16
|
export class RunTasksOperation {
|
|
@@ -59,6 +62,9 @@ export class RunTasksOperation {
|
|
|
59
62
|
case ExecutorType.container: {
|
|
60
63
|
return this.runDocker(task, tee);
|
|
61
64
|
}
|
|
65
|
+
case ExecutorType.kubernetes: {
|
|
66
|
+
return this.runKubernetes(task, tee);
|
|
67
|
+
}
|
|
62
68
|
case ExecutorType.local: {
|
|
63
69
|
return this.runLocal(task, tee);
|
|
64
70
|
}
|
|
@@ -94,6 +100,30 @@ export class RunTasksOperation {
|
|
|
94
100
|
env: await monorepo.expand(task.vars || {}),
|
|
95
101
|
});
|
|
96
102
|
}
|
|
103
|
+
async runKubernetes(task, out) {
|
|
104
|
+
const { monorepo } = getContext();
|
|
105
|
+
const component = monorepo.component(task.component);
|
|
106
|
+
const namespace = resolveNamespace({
|
|
107
|
+
config: monorepo.config.defaults?.kubernetes?.namespace,
|
|
108
|
+
});
|
|
109
|
+
// Resolve the pod and container for this component
|
|
110
|
+
const { pod, container } = await monorepo.run(new GetComponentPodOperation(), {
|
|
111
|
+
component,
|
|
112
|
+
namespace,
|
|
113
|
+
});
|
|
114
|
+
const podName = pod.metadata?.name;
|
|
115
|
+
if (!podName) {
|
|
116
|
+
throw new Error('Pod has no name');
|
|
117
|
+
}
|
|
118
|
+
return monorepo.run(new PodExecOperation(task.interactive ? undefined : out), {
|
|
119
|
+
namespace,
|
|
120
|
+
podName,
|
|
121
|
+
container,
|
|
122
|
+
script: task.script,
|
|
123
|
+
interactive: task.interactive || false,
|
|
124
|
+
env: await monorepo.expand(task.vars || {}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
97
127
|
async defaultExecutorFor(task) {
|
|
98
128
|
const available = await this.availableExecutorsFor(task);
|
|
99
129
|
if (available.length === 0) {
|
|
@@ -112,8 +142,18 @@ export class RunTasksOperation {
|
|
|
112
142
|
if (task.executors) {
|
|
113
143
|
return task.executors;
|
|
114
144
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
// For tasks with a component, check what executors are available
|
|
146
|
+
if (task.component) {
|
|
147
|
+
const available = [ExecutorType.local];
|
|
148
|
+
// Container executor available if component is a docker-compose service
|
|
149
|
+
if (await compose.isService(task.component)) {
|
|
150
|
+
available.unshift(ExecutorType.container);
|
|
151
|
+
}
|
|
152
|
+
// Kubernetes executor is always available for component tasks
|
|
153
|
+
// (actual availability checked at runtime when --executor kubernetes is used)
|
|
154
|
+
available.push(ExecutorType.kubernetes);
|
|
155
|
+
return available;
|
|
156
|
+
}
|
|
157
|
+
return [ExecutorType.local];
|
|
118
158
|
}
|
|
119
159
|
}
|