@oisincoveney/pipeline 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argo-workflow.js +0 -19
- package/dist/cli/program.d.ts +1 -0
- package/dist/cli/program.js +7 -4
- package/dist/cluster-doctor.js +69 -48
- package/dist/config/schemas.d.ts +2 -2
- package/dist/install-commands/opencode.js +422 -0
- package/dist/install-commands/shared.js +23 -0
- package/dist/install-commands.js +7 -440
- package/dist/schedule/backlog-context.js +114 -0
- package/dist/schedule/baseline.js +267 -0
- package/dist/schedule/planner.js +3 -373
- package/package.json +1 -1
package/dist/argo-workflow.js
CHANGED
|
@@ -303,25 +303,6 @@ function runnerWorkflowStorage(options, tasks) {
|
|
|
303
303
|
name: "runner-git-credentials",
|
|
304
304
|
secret: {
|
|
305
305
|
defaultMode: 256,
|
|
306
|
-
items: [
|
|
307
|
-
{
|
|
308
|
-
key: "username",
|
|
309
|
-
path: "username"
|
|
310
|
-
},
|
|
311
|
-
{
|
|
312
|
-
key: "password",
|
|
313
|
-
path: "password"
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
key: "identity",
|
|
317
|
-
path: "identity"
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
key: "known_hosts",
|
|
321
|
-
path: "known_hosts"
|
|
322
|
-
}
|
|
323
|
-
],
|
|
324
|
-
optional: true,
|
|
325
306
|
secretName: options.gitCredentialsSecretName
|
|
326
307
|
}
|
|
327
308
|
});
|
package/dist/cli/program.d.ts
CHANGED
package/dist/cli/program.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "../runner.
|
|
|
5
5
|
import { compileWorkflowPlan } from "../workflow-planner.js";
|
|
6
6
|
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "../schedule/planner.js";
|
|
7
7
|
import "../schedule-planner.js";
|
|
8
|
+
import { loadMokaGlobalConfig } from "../moka-global-config.js";
|
|
8
9
|
import { defaultClusterDoctorNamespace, runClusterDoctor } from "../cluster-doctor.js";
|
|
9
10
|
import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "../codex-auth-sync.js";
|
|
10
11
|
import { registerConfiguredEntrypointCommands } from "../commands/pipeline-command.js";
|
|
@@ -133,7 +134,7 @@ function createCliProgram() {
|
|
|
133
134
|
const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
134
135
|
console.log(formatSelectedWorkflowPlan(config, cwd, flags));
|
|
135
136
|
});
|
|
136
|
-
program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--kube-context <context>", "kubectl context for cluster checks").action(async (flags) => {
|
|
137
|
+
program.command("doctor").description("Check local prerequisites for pipeline init and execution").option("--cluster [namespace]", "also check runner-job Kubernetes prerequisites").option("--kube-context <context>", "kubectl context for cluster checks").option("--kubeconfig <path>", "kubeconfig path for cluster checks").action(async (flags) => {
|
|
137
138
|
const result = await runDoctor(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), flags);
|
|
138
139
|
console.log(formatDoctorResult(result));
|
|
139
140
|
if (!result.passed) throw new Error("Doctor checks failed.");
|
|
@@ -241,9 +242,11 @@ async function runDoctor(cwd, options = {}) {
|
|
|
241
242
|
checkCommand("fallow", ["--version"], cwd)
|
|
242
243
|
]);
|
|
243
244
|
const configCheck = checkPipelineConfig(cwd);
|
|
245
|
+
const globalConfig = loadMokaGlobalConfig();
|
|
244
246
|
const clusterResult = options.cluster ? await runClusterDoctor({
|
|
245
247
|
kubeContext: options.kubeContext,
|
|
246
|
-
|
|
248
|
+
kubeconfigPath: options.kubeconfig ?? globalConfig?.momokaya.kubernetes.kubeconfig,
|
|
249
|
+
namespace: clusterNamespace(options.cluster, globalConfig?.momokaya.kubernetes.namespace)
|
|
247
250
|
}) : { checks: [] };
|
|
248
251
|
const checks = [
|
|
249
252
|
...commandChecks,
|
|
@@ -255,8 +258,8 @@ async function runDoctor(cwd, options = {}) {
|
|
|
255
258
|
passed: checks.every((check) => check.passed)
|
|
256
259
|
};
|
|
257
260
|
}
|
|
258
|
-
function clusterNamespace(value) {
|
|
259
|
-
return typeof value === "string" && value.length > 0 ? value : defaultClusterDoctorNamespace();
|
|
261
|
+
function clusterNamespace(value, configuredNamespace) {
|
|
262
|
+
return typeof value === "string" && value.length > 0 ? value : configuredNamespace ?? defaultClusterDoctorNamespace();
|
|
260
263
|
}
|
|
261
264
|
function checkCommand(name, args, cwd) {
|
|
262
265
|
return checkCommandWithRunner(name, name, args, cwd);
|
package/dist/cluster-doctor.js
CHANGED
|
@@ -13,33 +13,38 @@ const DEFAULT_RESOURCES = {
|
|
|
13
13
|
queueName: "pipeline-runner",
|
|
14
14
|
serviceAccountName: "pipeline-runner"
|
|
15
15
|
};
|
|
16
|
+
const FORBIDDEN_RE = /forbidden/i;
|
|
16
17
|
async function runClusterDoctor(options = {}) {
|
|
17
18
|
const resources = clusterResources();
|
|
18
19
|
const namespace = options.namespace ?? DEFAULT_NAMESPACE;
|
|
20
|
+
const kubectlOptions = {
|
|
21
|
+
kubeContext: options.kubeContext,
|
|
22
|
+
kubeconfigPath: options.kubeconfigPath
|
|
23
|
+
};
|
|
19
24
|
const checks = await Promise.all([
|
|
20
|
-
checkKubectlNamespace(namespace,
|
|
21
|
-
...secretChecks(namespace,
|
|
22
|
-
checkExternalSecret(namespace, resources.eventAuthExternalSecretName, resources.externalSecretRemoteRef,
|
|
23
|
-
checkClusterSecretStore("openbao",
|
|
24
|
-
checkServiceAccount(namespace, resources.serviceAccountName,
|
|
25
|
-
|
|
26
|
-
kubeContext: options.kubeContext,
|
|
25
|
+
checkKubectlNamespace(namespace, kubectlOptions),
|
|
26
|
+
...secretChecks(namespace, kubectlOptions, resources),
|
|
27
|
+
checkExternalSecret(namespace, resources.eventAuthExternalSecretName, resources.externalSecretRemoteRef, kubectlOptions),
|
|
28
|
+
checkClusterSecretStore("openbao", kubectlOptions),
|
|
29
|
+
checkServiceAccount(namespace, resources.serviceAccountName, kubectlOptions),
|
|
30
|
+
checkWorkflowSubmitPermission(namespace, {
|
|
27
31
|
resource: "workflows.argoproj.io",
|
|
28
|
-
verb: "create"
|
|
32
|
+
verb: "create",
|
|
33
|
+
...kubectlOptions
|
|
29
34
|
}),
|
|
30
|
-
checkLocalQueue(namespace, resources.queueName,
|
|
35
|
+
checkLocalQueue(namespace, resources.queueName, kubectlOptions),
|
|
31
36
|
checkClusterResource("argo-workflow-crd", [
|
|
32
37
|
"get",
|
|
33
38
|
"crd",
|
|
34
39
|
"workflows.argoproj.io"
|
|
35
|
-
],
|
|
40
|
+
], kubectlOptions),
|
|
36
41
|
checkClusterResource("argo-workflow-controller", [
|
|
37
42
|
"get",
|
|
38
43
|
"pods",
|
|
39
44
|
"-A",
|
|
40
45
|
"-l",
|
|
41
46
|
"app=workflow-controller"
|
|
42
|
-
],
|
|
47
|
+
], kubectlOptions)
|
|
43
48
|
]);
|
|
44
49
|
return {
|
|
45
50
|
checks,
|
|
@@ -62,7 +67,7 @@ function clusterResources() {
|
|
|
62
67
|
serviceAccountName: configured.serviceAccountName
|
|
63
68
|
} : DEFAULT_RESOURCES;
|
|
64
69
|
}
|
|
65
|
-
function secretChecks(namespace,
|
|
70
|
+
function secretChecks(namespace, kubectlOptions, resources) {
|
|
66
71
|
return [
|
|
67
72
|
[resources.eventAuthSecretName, eventAuthMissingDetail(namespace)],
|
|
68
73
|
[resources.imagePullSecretName, `Secret ${resources.imagePullSecretName} missing in ${namespace}; expected imagePullSecret for ghcr.io/oisin-ee/pipeline-runner.`],
|
|
@@ -75,30 +80,31 @@ function secretChecks(namespace, kubeContext, resources) {
|
|
|
75
80
|
name,
|
|
76
81
|
"-n",
|
|
77
82
|
namespace
|
|
78
|
-
], missingDetail,
|
|
83
|
+
], missingDetail, kubectlOptions));
|
|
79
84
|
}
|
|
80
85
|
function eventAuthMissingDetail(namespace) {
|
|
81
86
|
return `Secret pipeline-runner-event-auth missing in ${namespace}; expected ExternalSecret pipeline-runner-event-auth to sync it from agent-runtime/pipeline-runner/event-auth.`;
|
|
82
87
|
}
|
|
83
|
-
function checkKubectlNamespace(namespace,
|
|
88
|
+
function checkKubectlNamespace(namespace, kubectlOptions) {
|
|
84
89
|
return checkNamespacedResource(`namespace/${namespace}`, [
|
|
85
90
|
"get",
|
|
86
91
|
"namespace",
|
|
87
92
|
namespace
|
|
88
|
-
], `Namespace ${namespace} missing or inaccessible.`,
|
|
93
|
+
], `Namespace ${namespace} missing or inaccessible.`, kubectlOptions);
|
|
89
94
|
}
|
|
90
|
-
async function checkNamespacedResource(name, args, missingDetail,
|
|
91
|
-
|
|
95
|
+
async function checkNamespacedResource(name, args, missingDetail, kubectlOptions) {
|
|
96
|
+
const result = await kubectl(args, kubectlOptions);
|
|
97
|
+
return result.ok ? {
|
|
92
98
|
detail: "present",
|
|
93
99
|
name,
|
|
94
100
|
passed: true
|
|
95
101
|
} : {
|
|
96
|
-
detail: missingDetail,
|
|
102
|
+
detail: inaccessibleOrMissingDetail(name, missingDetail, result),
|
|
97
103
|
name,
|
|
98
104
|
passed: false
|
|
99
105
|
};
|
|
100
106
|
}
|
|
101
|
-
async function checkExternalSecret(namespace, name, remoteRef,
|
|
107
|
+
async function checkExternalSecret(namespace, name, remoteRef, kubectlOptions) {
|
|
102
108
|
const result = await kubectl([
|
|
103
109
|
"get",
|
|
104
110
|
"externalsecret",
|
|
@@ -107,76 +113,79 @@ async function checkExternalSecret(namespace, name, remoteRef, kubeContext) {
|
|
|
107
113
|
namespace,
|
|
108
114
|
"-o",
|
|
109
115
|
"json"
|
|
110
|
-
],
|
|
111
|
-
if (!result.ok)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
], kubectlOptions);
|
|
117
|
+
if (!result.ok) {
|
|
118
|
+
const missingDetail = `ExternalSecret ${name} missing in ${namespace}; expected it to sync ${remoteRef}.`;
|
|
119
|
+
return {
|
|
120
|
+
detail: inaccessibleOrMissingDetail(`externalsecret/${name}`, missingDetail, result),
|
|
121
|
+
name: `externalsecret/${name}`,
|
|
122
|
+
passed: false
|
|
123
|
+
};
|
|
124
|
+
}
|
|
116
125
|
return readyConditionCheck(`externalsecret/${name}`, result.stdout);
|
|
117
126
|
}
|
|
118
|
-
async function checkClusterSecretStore(name,
|
|
127
|
+
async function checkClusterSecretStore(name, kubectlOptions) {
|
|
119
128
|
const result = await kubectl([
|
|
120
129
|
"get",
|
|
121
130
|
"clustersecretstore",
|
|
122
131
|
name,
|
|
123
132
|
"-o",
|
|
124
133
|
"json"
|
|
125
|
-
],
|
|
126
|
-
if (!result.ok)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
], kubectlOptions);
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
const missingDetail = `ClusterSecretStore/${name} missing or inaccessible; OpenBao/ESO readiness is an external prerequisite.`;
|
|
137
|
+
return {
|
|
138
|
+
detail: inaccessibleOrMissingDetail(`clustersecretstore/${name}`, missingDetail, result),
|
|
139
|
+
name: `clustersecretstore/${name}`,
|
|
140
|
+
passed: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
131
143
|
return readyConditionCheck(`clustersecretstore/${name}`, result.stdout);
|
|
132
144
|
}
|
|
133
|
-
function checkServiceAccount(namespace, name,
|
|
145
|
+
function checkServiceAccount(namespace, name, kubectlOptions) {
|
|
134
146
|
return checkNamespacedResource(`serviceaccount/${name}`, [
|
|
135
147
|
"get",
|
|
136
148
|
"serviceaccount",
|
|
137
149
|
name,
|
|
138
150
|
"-n",
|
|
139
151
|
namespace
|
|
140
|
-
], `ServiceAccount ${name} missing in ${namespace}; runner pods must use this account for workflow execution.`,
|
|
152
|
+
], `ServiceAccount ${name} missing in ${namespace}; runner pods must use this account for workflow execution.`, kubectlOptions);
|
|
141
153
|
}
|
|
142
|
-
async function
|
|
143
|
-
const subject = `system:serviceaccount:${namespace}:${serviceAccountName}`;
|
|
154
|
+
async function checkWorkflowSubmitPermission(namespace, options) {
|
|
144
155
|
return (await kubectl([
|
|
145
156
|
"auth",
|
|
146
157
|
"can-i",
|
|
147
158
|
options.verb,
|
|
148
159
|
options.resource,
|
|
149
|
-
"--as",
|
|
150
|
-
subject,
|
|
151
160
|
"-n",
|
|
152
161
|
namespace
|
|
153
|
-
], options
|
|
154
|
-
detail:
|
|
162
|
+
], options)).stdout.trim() === "yes" ? {
|
|
163
|
+
detail: `current kube identity can ${options.verb} ${options.resource}`,
|
|
155
164
|
name: "rbac/workflow-create",
|
|
156
165
|
passed: true
|
|
157
166
|
} : {
|
|
158
|
-
detail:
|
|
167
|
+
detail: `current kube identity cannot ${options.verb} ${options.resource}; check submitter RBAC for Workflow creation.`,
|
|
159
168
|
name: "rbac/workflow-create",
|
|
160
169
|
passed: false
|
|
161
170
|
};
|
|
162
171
|
}
|
|
163
|
-
function checkLocalQueue(namespace, queueName,
|
|
172
|
+
function checkLocalQueue(namespace, queueName, kubectlOptions) {
|
|
164
173
|
return checkNamespacedResource(`localqueue/${queueName}`, [
|
|
165
174
|
"get",
|
|
166
175
|
"localqueue",
|
|
167
176
|
queueName,
|
|
168
177
|
"-n",
|
|
169
178
|
namespace
|
|
170
|
-
], `Kueue LocalQueue ${queueName} missing in ${namespace}; runner Workflow pods cannot be admitted to the expected queue.`,
|
|
179
|
+
], `Kueue LocalQueue ${queueName} missing in ${namespace}; runner Workflow pods cannot be admitted to the expected queue.`, kubectlOptions);
|
|
171
180
|
}
|
|
172
|
-
async function checkClusterResource(name, args,
|
|
173
|
-
const result = await kubectl(args,
|
|
181
|
+
async function checkClusterResource(name, args, kubectlOptions) {
|
|
182
|
+
const result = await kubectl(args, kubectlOptions);
|
|
174
183
|
return result.ok ? {
|
|
175
184
|
detail: "present",
|
|
176
185
|
name,
|
|
177
186
|
passed: true
|
|
178
187
|
} : {
|
|
179
|
-
detail: result.stderr || "missing or inaccessible",
|
|
188
|
+
detail: isForbidden(result) ? inaccessibleDetail(name, result) : result.stderr || "missing or inaccessible",
|
|
180
189
|
name,
|
|
181
190
|
passed: false
|
|
182
191
|
};
|
|
@@ -193,9 +202,12 @@ function readyConditionCheck(name, source) {
|
|
|
193
202
|
passed: false
|
|
194
203
|
};
|
|
195
204
|
}
|
|
196
|
-
async function kubectl(args,
|
|
205
|
+
async function kubectl(args, options) {
|
|
197
206
|
try {
|
|
198
|
-
const result = await execa("kubectl", kubectlArgs(args, kubeContext), {
|
|
207
|
+
const result = await execa("kubectl", kubectlArgs(args, options.kubeContext), {
|
|
208
|
+
env: options.kubeconfigPath ? { KUBECONFIG: options.kubeconfigPath } : void 0,
|
|
209
|
+
stdin: "ignore"
|
|
210
|
+
});
|
|
199
211
|
return {
|
|
200
212
|
ok: true,
|
|
201
213
|
stderr: result.stderr,
|
|
@@ -217,6 +229,15 @@ function kubectlArgs(args, kubeContext) {
|
|
|
217
229
|
...args
|
|
218
230
|
] : args;
|
|
219
231
|
}
|
|
232
|
+
function inaccessibleOrMissingDetail(name, missingDetail, result) {
|
|
233
|
+
return isForbidden(result) ? inaccessibleDetail(name, result) : missingDetail;
|
|
234
|
+
}
|
|
235
|
+
function inaccessibleDetail(name, result) {
|
|
236
|
+
return `${name} inaccessible with the current kube identity: ${result.stderr}`;
|
|
237
|
+
}
|
|
238
|
+
function isForbidden(result) {
|
|
239
|
+
return FORBIDDEN_RE.test(result.stderr);
|
|
240
|
+
}
|
|
220
241
|
function parseJson(source) {
|
|
221
242
|
try {
|
|
222
243
|
return JSON.parse(source);
|
package/dist/config/schemas.d.ts
CHANGED
|
@@ -310,6 +310,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
310
310
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
311
311
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
312
312
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
313
|
+
task: "task";
|
|
313
314
|
read: "read";
|
|
314
315
|
list: "list";
|
|
315
316
|
grep: "grep";
|
|
@@ -317,7 +318,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
317
318
|
bash: "bash";
|
|
318
319
|
edit: "edit";
|
|
319
320
|
write: "write";
|
|
320
|
-
task: "task";
|
|
321
321
|
}>>>;
|
|
322
322
|
}, z.core.$strict>>>;
|
|
323
323
|
runner_command: z.ZodDefault<z.ZodObject<{
|
|
@@ -369,6 +369,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
369
369
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
370
370
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
371
371
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
372
|
+
task: "task";
|
|
372
373
|
read: "read";
|
|
373
374
|
list: "list";
|
|
374
375
|
grep: "grep";
|
|
@@ -376,7 +377,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
376
377
|
bash: "bash";
|
|
377
378
|
edit: "edit";
|
|
378
379
|
write: "write";
|
|
379
|
-
task: "task";
|
|
380
380
|
}>>>;
|
|
381
381
|
}, z.core.$strict>;
|
|
382
382
|
command: z.ZodOptional<z.ZodString>;
|