@oisincoveney/pipeline 1.22.5 → 1.23.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/.agents/skills/execute/SKILL.md +5 -3
- package/.agents/skills/quick/SKILL.md +5 -3
- package/defaults/opencode-ecosystem.yaml +9 -0
- package/dist/commands/pipeline-command.js +2 -2
- package/dist/config.d.ts +3 -3
- package/dist/gates.js +5 -1
- package/dist/index.js +13 -2
- package/dist/install-commands.js +13 -3
- package/dist/k8s-submit.js +267 -0
- package/docs/operator-guide.md +42 -0
- package/package.json +2 -1
|
@@ -20,9 +20,11 @@ hooks: generated-defaults-audit
|
|
|
20
20
|
Instructions:
|
|
21
21
|
Orchestrate package-owned pipeline config.
|
|
22
22
|
|
|
23
|
+
Submit Kubernetes runner Jobs by default through `oisin-pipeline quick` and `oisin-pipeline execute`.
|
|
24
|
+
|
|
23
25
|
Generate a schedule for entrypoint `execute` and the user task.
|
|
24
26
|
The schedule policy is `execute-schedule`.
|
|
25
|
-
Run `oisin-pipeline
|
|
26
|
-
The pipeline
|
|
27
|
-
|
|
27
|
+
Run `oisin-pipeline execute <task description>` to submit the pipeline as a k8s job.
|
|
28
|
+
The pipeline runtime executes inside a Kubernetes pod using the package-owned runner image.
|
|
29
|
+
Use `oisin-pipeline execute --local <task description>` for local execution instead.
|
|
28
30
|
Use `oisin-pipeline run --schedule <schedule.yaml>` only when rerunning an existing schedule artifact.
|
|
@@ -20,9 +20,11 @@ hooks: generated-defaults-audit
|
|
|
20
20
|
Instructions:
|
|
21
21
|
Orchestrate package-owned pipeline config.
|
|
22
22
|
|
|
23
|
+
Submit Kubernetes runner Jobs by default through `oisin-pipeline quick` and `oisin-pipeline execute`.
|
|
24
|
+
|
|
23
25
|
Generate a schedule for entrypoint `quick` and the user task.
|
|
24
26
|
The schedule policy is `quick-schedule`.
|
|
25
|
-
Run `oisin-pipeline
|
|
26
|
-
The pipeline
|
|
27
|
-
|
|
27
|
+
Run `oisin-pipeline quick <task description>` to submit the pipeline as a k8s job.
|
|
28
|
+
The pipeline runtime executes inside a Kubernetes pod using the package-owned runner image.
|
|
29
|
+
Use `oisin-pipeline quick --local <task description>` for local execution instead.
|
|
28
30
|
Use `oisin-pipeline run --schedule <schedule.yaml>` only when rerunning an existing schedule artifact.
|
|
@@ -59,6 +59,15 @@ ecosystem_code:
|
|
|
59
59
|
role: Codex-style long-running goal mode with /goal slash command, persistent state, evidence-gated completion, and TUI sidebar
|
|
60
60
|
default_stack: true
|
|
61
61
|
source: https://www.npmjs.com/package/@prevalentware/opencode-goal-plugin
|
|
62
|
+
- id: oc-codex-multi-auth
|
|
63
|
+
name: oc-codex-multi-auth
|
|
64
|
+
package: oc-codex-multi-auth
|
|
65
|
+
plugin:
|
|
66
|
+
kind: npm
|
|
67
|
+
package: oc-codex-multi-auth
|
|
68
|
+
role: ChatGPT Plus/Pro OAuth with multi-account rotation, health checks, and Codex/GPT-5 routing for OpenCode
|
|
69
|
+
default_stack: true
|
|
70
|
+
source: https://github.com/ndycode/oc-codex-multi-auth
|
|
62
71
|
- id: opencode-snip
|
|
63
72
|
name: opencode-snip
|
|
64
73
|
role: prompt snippet and command-discipline helper patterns
|
|
@@ -14,8 +14,8 @@ function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
|
|
|
14
14
|
const reservedCommands = new Set(program.commands.map((command) => command.name()));
|
|
15
15
|
for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
|
|
16
16
|
if (reservedCommands.has(id)) continue;
|
|
17
|
-
program.command(id).description(entrypoint.description ?? `Run the ${id} workflow`).argument("<description...>", "task description").action(async (descriptionParts) => {
|
|
18
|
-
await runEntrypoint(id, descriptionParts.join(" "));
|
|
17
|
+
program.command(id).description(entrypoint.description ?? `Run the ${id} workflow`).argument("<description...>", "task description").option("--local", "run locally instead of submitting as a k8s job").action(async (descriptionParts, flags) => {
|
|
18
|
+
await runEntrypoint(id, descriptionParts.join(" "), flags);
|
|
19
19
|
});
|
|
20
20
|
registered.add(id);
|
|
21
21
|
reservedCommands.add(id);
|
package/dist/config.d.ts
CHANGED
|
@@ -494,8 +494,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
494
494
|
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
495
495
|
path: z.ZodString;
|
|
496
496
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
497
|
-
package: "package";
|
|
498
497
|
project: "project";
|
|
498
|
+
package: "package";
|
|
499
499
|
}>>;
|
|
500
500
|
}, z.core.$strict>>>;
|
|
501
501
|
runners: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -534,8 +534,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
534
534
|
host_models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
535
535
|
model: z.ZodOptional<z.ZodString>;
|
|
536
536
|
type: z.ZodEnum<{
|
|
537
|
-
opencode: "opencode";
|
|
538
537
|
command: "command";
|
|
538
|
+
opencode: "opencode";
|
|
539
539
|
codex: "codex";
|
|
540
540
|
}>;
|
|
541
541
|
}, z.core.$strict>>>;
|
|
@@ -636,8 +636,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
636
636
|
skills: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
637
637
|
path: z.ZodString;
|
|
638
638
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
639
|
-
package: "package";
|
|
640
639
|
project: "project";
|
|
640
|
+
package: "package";
|
|
641
641
|
}>>;
|
|
642
642
|
}, z.core.$strict>>>;
|
|
643
643
|
task_context: z.ZodOptional<z.ZodObject<{
|
package/dist/gates.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { formatConfigError, runPipelineFromConfig } from "./pipeline-runtime.js"
|
|
|
10
10
|
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule-planner.js";
|
|
11
11
|
import { registerRunnerJobCommand } from "./commands/runner-job-command.js";
|
|
12
12
|
import { formatInstallCommandsResult, installCommands, parseCommandHost } from "./install-commands.js";
|
|
13
|
+
import { submitK8sRunnerJob } from "./k8s-submit.js";
|
|
13
14
|
import { formatPipelineInitResult, initPipelineProject } from "./pipeline-init.js";
|
|
14
15
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
15
16
|
import { resolve } from "node:path";
|
|
@@ -296,7 +297,16 @@ function createCliProgram() {
|
|
|
296
297
|
console.log(formatInstallCommandsResult(result));
|
|
297
298
|
});
|
|
298
299
|
registerRunnerJobCommand(program);
|
|
299
|
-
const configuredEntrypointCommands = registerConfiguredEntrypointCommands(program, configuredPipeline, (entrypoint, task) =>
|
|
300
|
+
const configuredEntrypointCommands = registerConfiguredEntrypointCommands(program, configuredPipeline, async (entrypoint, task, opts) => {
|
|
301
|
+
if (!opts.local && (entrypoint === "quick" || entrypoint === "execute")) {
|
|
302
|
+
const result = await submitK8sRunnerJob({
|
|
303
|
+
entrypoint,
|
|
304
|
+
task,
|
|
305
|
+
eventUrl: process.env.PIPELINE_EVENT_URL ?? ""
|
|
306
|
+
});
|
|
307
|
+
console.log(`Job submitted: ${result.jobName} in ${result.namespace}`);
|
|
308
|
+
} else await execute(task, { entrypoint });
|
|
309
|
+
});
|
|
300
310
|
if (configuredEntrypointCommands.size > 0) program.configureHelp({ subcommandTerm(command) {
|
|
301
311
|
if (configuredEntrypointCommands.has(command.name())) return command.name();
|
|
302
312
|
return Help.prototype.subcommandTerm.call(this, command);
|
|
@@ -385,7 +395,8 @@ async function runDoctor(cwd) {
|
|
|
385
395
|
const commandChecks = await Promise.all([
|
|
386
396
|
checkCommand("npx", ["--version"], cwd),
|
|
387
397
|
checkCommand("codex", ["--version"], cwd),
|
|
388
|
-
checkCommand("opencode", ["--version"], cwd)
|
|
398
|
+
checkCommand("opencode", ["--version"], cwd),
|
|
399
|
+
checkCommand("fallow", ["--version"], cwd)
|
|
389
400
|
]);
|
|
390
401
|
const configCheck = checkPipelineConfig(cwd);
|
|
391
402
|
const checks = [...commandChecks, configCheck];
|
package/dist/install-commands.js
CHANGED
|
@@ -175,12 +175,16 @@ function entrypointDispatchBlock(host, config, id, entrypoint) {
|
|
|
175
175
|
return [
|
|
176
176
|
`Generate a schedule for entrypoint \`${id}\` and the user task.`,
|
|
177
177
|
`The schedule policy is \`${entrypoint.schedule}\`.`,
|
|
178
|
-
`Run \`oisin-pipeline
|
|
179
|
-
"The pipeline
|
|
180
|
-
|
|
178
|
+
`Run \`oisin-pipeline ${id} <task description>\` to submit the pipeline as a k8s job.`,
|
|
179
|
+
"The pipeline runtime executes inside a Kubernetes pod using the package-owned runner image.",
|
|
180
|
+
`Use \`oisin-pipeline ${id} --local <task description>\` for local execution instead.`,
|
|
181
181
|
"Use `oisin-pipeline run --schedule <schedule.yaml>` only when rerunning an existing schedule artifact."
|
|
182
182
|
].join("\n");
|
|
183
183
|
}
|
|
184
|
+
function scheduledEntrypointK8sNote(entrypoint) {
|
|
185
|
+
if ("workflow" in entrypoint) return;
|
|
186
|
+
return "Submit Kubernetes runner Jobs by default through `oisin-pipeline quick` and `oisin-pipeline execute`.";
|
|
187
|
+
}
|
|
184
188
|
function orchestratorEntrypointDispatchBlock(host, config) {
|
|
185
189
|
const scheduledEntrypoints = entrypointEntries(config).filter(([, entrypoint]) => !("workflow" in entrypoint));
|
|
186
190
|
if (scheduledEntrypoints.length === 0) return dispatchBlock(host, config);
|
|
@@ -379,6 +383,8 @@ function opencodeDefinitions(config, cwd) {
|
|
|
379
383
|
"",
|
|
380
384
|
orchestratorBlock(config),
|
|
381
385
|
"",
|
|
386
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
387
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
382
388
|
entrypointDispatchBlock("opencode", config, id, entrypoint)
|
|
383
389
|
]).join("\n")),
|
|
384
390
|
host: "opencode",
|
|
@@ -447,6 +453,8 @@ function codexDefinitions(config, cwd) {
|
|
|
447
453
|
"",
|
|
448
454
|
orchestratorBlock(config),
|
|
449
455
|
"",
|
|
456
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
457
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
450
458
|
entrypointDispatchBlock("codex", config, id, entrypoint)
|
|
451
459
|
]).join("\n")),
|
|
452
460
|
host: "codex",
|
|
@@ -466,6 +474,8 @@ function codexDefinitions(config, cwd) {
|
|
|
466
474
|
"",
|
|
467
475
|
orchestratorBlock(config),
|
|
468
476
|
"",
|
|
477
|
+
scheduledEntrypointK8sNote(entrypoint),
|
|
478
|
+
scheduledEntrypointK8sNote(entrypoint) ? "" : void 0,
|
|
469
479
|
entrypointDispatchBlock("codex", config, id, entrypoint)
|
|
470
480
|
]).join("\n")),
|
|
471
481
|
host: "codex",
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { simpleGit } from "simple-git";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { BatchV1Api, CoreV1Api, KubeConfig } from "@kubernetes/client-node";
|
|
5
|
+
//#region src/k8s-submit.ts
|
|
6
|
+
const GIT_SUFFIX_RE = /\.git$/;
|
|
7
|
+
const k8sSecretRefSchema = z.object({
|
|
8
|
+
key: z.string().min(1),
|
|
9
|
+
name: z.string().min(1)
|
|
10
|
+
}).strict();
|
|
11
|
+
const k8sSubmitOptionsSchema = z.object({
|
|
12
|
+
codexAuth: k8sSecretRefSchema.default({
|
|
13
|
+
key: "auth.json",
|
|
14
|
+
name: "codex-auth-1"
|
|
15
|
+
}),
|
|
16
|
+
entrypoint: z.enum(["execute", "quick"]),
|
|
17
|
+
eventAuth: k8sSecretRefSchema.default({
|
|
18
|
+
key: "token",
|
|
19
|
+
name: "pipeline-runner-event-auth"
|
|
20
|
+
}),
|
|
21
|
+
eventUrl: z.string().url(),
|
|
22
|
+
githubAuth: k8sSecretRefSchema.default({
|
|
23
|
+
key: "hosts.yml",
|
|
24
|
+
name: "pipeline-runner-github-auth"
|
|
25
|
+
}),
|
|
26
|
+
jobName: z.string().min(1).optional(),
|
|
27
|
+
kubeconfigPath: z.string().min(1).optional(),
|
|
28
|
+
namespace: z.string().min(1).default("momokaya-pipeline"),
|
|
29
|
+
opencodeAuth: k8sSecretRefSchema.default({
|
|
30
|
+
key: "auth.json",
|
|
31
|
+
name: "opencode-auth-1"
|
|
32
|
+
}),
|
|
33
|
+
opencodeOpenaiAccounts: k8sSecretRefSchema.optional(),
|
|
34
|
+
orchestrator: z.enum(["codex", "opencode"]).default("opencode"),
|
|
35
|
+
serviceAccountName: z.string().min(1).default("pipeline-runner"),
|
|
36
|
+
task: z.string().min(1)
|
|
37
|
+
}).strict();
|
|
38
|
+
z.object({
|
|
39
|
+
jobName: z.string().min(1),
|
|
40
|
+
namespace: z.string().min(1)
|
|
41
|
+
}).strict();
|
|
42
|
+
async function resolveGitContext() {
|
|
43
|
+
const git = simpleGit();
|
|
44
|
+
const [branchResult, sha, remoteConfig] = await Promise.all([
|
|
45
|
+
git.branch(),
|
|
46
|
+
git.revparse(["HEAD"]),
|
|
47
|
+
git.getConfig("remote.origin.url")
|
|
48
|
+
]);
|
|
49
|
+
const url = remoteConfig.value;
|
|
50
|
+
if (!url) throw new Error("Could not resolve git remote origin URL. Ensure the repository has a remote configured.");
|
|
51
|
+
const project = projectNameFromUrl(url);
|
|
52
|
+
return {
|
|
53
|
+
baseBranch: branchResult.current,
|
|
54
|
+
project,
|
|
55
|
+
sha: sha.trim(),
|
|
56
|
+
url
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function projectNameFromUrl(url) {
|
|
60
|
+
return url.replace(GIT_SUFFIX_RE, "").split("/").at(-1) ?? "unknown";
|
|
61
|
+
}
|
|
62
|
+
function generateRunId() {
|
|
63
|
+
return `run-${randomBytes(8).toString("hex")}`;
|
|
64
|
+
}
|
|
65
|
+
function generatePayloadConfigMapName() {
|
|
66
|
+
return `pipeline-payload-${randomBytes(6).toString("hex")}`;
|
|
67
|
+
}
|
|
68
|
+
async function submitK8sRunnerJob(rawOptions) {
|
|
69
|
+
const options = k8sSubmitOptionsSchema.parse(rawOptions);
|
|
70
|
+
const git = await resolveGitContext();
|
|
71
|
+
const kc = new KubeConfig();
|
|
72
|
+
if (options.kubeconfigPath) kc.loadFromFile(options.kubeconfigPath);
|
|
73
|
+
else kc.loadFromDefault();
|
|
74
|
+
const coreApi = kc.makeApiClient(CoreV1Api);
|
|
75
|
+
const batchApi = kc.makeApiClient(BatchV1Api);
|
|
76
|
+
const payloadConfigMapName = generatePayloadConfigMapName();
|
|
77
|
+
const jobName = options.jobName ?? `pipeline-run-${options.entrypoint}-${randomBytes(4).toString("hex")}`;
|
|
78
|
+
const namespace = options.namespace;
|
|
79
|
+
const payload = {
|
|
80
|
+
command: options.entrypoint,
|
|
81
|
+
contractVersion: "1",
|
|
82
|
+
events: {
|
|
83
|
+
authTokenFile: `/etc/pipeline/event-auth/${options.eventAuth.key}`,
|
|
84
|
+
url: options.eventUrl
|
|
85
|
+
},
|
|
86
|
+
repository: {
|
|
87
|
+
baseBranch: git.baseBranch,
|
|
88
|
+
sha: git.sha,
|
|
89
|
+
url: git.url
|
|
90
|
+
},
|
|
91
|
+
run: {
|
|
92
|
+
id: generateRunId(),
|
|
93
|
+
project: git.project
|
|
94
|
+
},
|
|
95
|
+
task: {
|
|
96
|
+
kind: "prompt",
|
|
97
|
+
prompt: options.task
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const configMapBody = {
|
|
101
|
+
apiVersion: "v1",
|
|
102
|
+
kind: "ConfigMap",
|
|
103
|
+
metadata: {
|
|
104
|
+
name: payloadConfigMapName,
|
|
105
|
+
namespace
|
|
106
|
+
},
|
|
107
|
+
data: { "payload.json": JSON.stringify(payload) }
|
|
108
|
+
};
|
|
109
|
+
await coreApi.createNamespacedConfigMap(namespace, configMapBody);
|
|
110
|
+
const volumes = [
|
|
111
|
+
{
|
|
112
|
+
name: payloadConfigMapName,
|
|
113
|
+
configMap: {
|
|
114
|
+
name: payloadConfigMapName,
|
|
115
|
+
items: [{
|
|
116
|
+
key: "payload.json",
|
|
117
|
+
path: "payload.json"
|
|
118
|
+
}]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: options.eventAuth.name,
|
|
123
|
+
secret: {
|
|
124
|
+
secretName: options.eventAuth.name,
|
|
125
|
+
items: [{
|
|
126
|
+
key: options.eventAuth.key,
|
|
127
|
+
path: options.eventAuth.key
|
|
128
|
+
}]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: options.codexAuth.name,
|
|
133
|
+
secret: {
|
|
134
|
+
secretName: options.codexAuth.name,
|
|
135
|
+
defaultMode: 256
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: options.opencodeAuth.name,
|
|
140
|
+
secret: {
|
|
141
|
+
secretName: options.opencodeAuth.name,
|
|
142
|
+
defaultMode: 256
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
...options.opencodeOpenaiAccounts ? [{
|
|
146
|
+
name: options.opencodeOpenaiAccounts.name,
|
|
147
|
+
secret: {
|
|
148
|
+
secretName: options.opencodeOpenaiAccounts.name,
|
|
149
|
+
defaultMode: 256
|
|
150
|
+
}
|
|
151
|
+
}] : [],
|
|
152
|
+
{
|
|
153
|
+
name: options.githubAuth.name,
|
|
154
|
+
secret: {
|
|
155
|
+
secretName: options.githubAuth.name,
|
|
156
|
+
items: [
|
|
157
|
+
{
|
|
158
|
+
key: "gitconfig",
|
|
159
|
+
path: "gitconfig"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "git-credentials",
|
|
163
|
+
path: "git-credentials"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
key: "hosts.yml",
|
|
167
|
+
path: "hosts.yml"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
const volumeMounts = [
|
|
174
|
+
{
|
|
175
|
+
name: payloadConfigMapName,
|
|
176
|
+
mountPath: "/etc/pipeline/payload.json",
|
|
177
|
+
subPath: "payload.json",
|
|
178
|
+
readOnly: true
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: options.eventAuth.name,
|
|
182
|
+
mountPath: "/etc/pipeline/event-auth",
|
|
183
|
+
readOnly: true
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: options.codexAuth.name,
|
|
187
|
+
mountPath: `/root/.codex/${options.codexAuth.key}`,
|
|
188
|
+
subPath: options.codexAuth.key,
|
|
189
|
+
readOnly: true
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: options.opencodeAuth.name,
|
|
193
|
+
mountPath: `/root/.local/share/opencode/${options.opencodeAuth.key}`,
|
|
194
|
+
subPath: options.opencodeAuth.key,
|
|
195
|
+
readOnly: true
|
|
196
|
+
},
|
|
197
|
+
...options.opencodeOpenaiAccounts ? [{
|
|
198
|
+
name: options.opencodeOpenaiAccounts.name,
|
|
199
|
+
mountPath: "/root/.opencode/oc-codex-multi-auth-accounts.json",
|
|
200
|
+
subPath: options.opencodeOpenaiAccounts.key,
|
|
201
|
+
readOnly: true
|
|
202
|
+
}] : [],
|
|
203
|
+
{
|
|
204
|
+
name: options.githubAuth.name,
|
|
205
|
+
mountPath: "/root/.gitconfig",
|
|
206
|
+
subPath: "gitconfig",
|
|
207
|
+
readOnly: true
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: options.githubAuth.name,
|
|
211
|
+
mountPath: "/root/.git-credentials",
|
|
212
|
+
subPath: "git-credentials",
|
|
213
|
+
readOnly: true
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: options.githubAuth.name,
|
|
217
|
+
mountPath: "/root/.config/gh/hosts.yml",
|
|
218
|
+
subPath: "hosts.yml",
|
|
219
|
+
readOnly: true
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
const env = options.opencodeOpenaiAccounts ? [{
|
|
223
|
+
name: "CODEX_AUTH_PER_PROJECT_ACCOUNTS",
|
|
224
|
+
value: "false"
|
|
225
|
+
}] : [];
|
|
226
|
+
const jobBody = {
|
|
227
|
+
apiVersion: "batch/v1",
|
|
228
|
+
kind: "Job",
|
|
229
|
+
metadata: {
|
|
230
|
+
name: jobName,
|
|
231
|
+
namespace
|
|
232
|
+
},
|
|
233
|
+
spec: {
|
|
234
|
+
backoffLimit: 0,
|
|
235
|
+
template: { spec: {
|
|
236
|
+
serviceAccountName: options.serviceAccountName,
|
|
237
|
+
restartPolicy: "Never",
|
|
238
|
+
containers: [{
|
|
239
|
+
name: "runner",
|
|
240
|
+
image: "ghcr.io/oisin-ee/pipeline-runner:latest",
|
|
241
|
+
imagePullPolicy: "Always",
|
|
242
|
+
args: [
|
|
243
|
+
"runner-job",
|
|
244
|
+
"--payload-file",
|
|
245
|
+
"/etc/pipeline/payload.json",
|
|
246
|
+
options.orchestrator
|
|
247
|
+
],
|
|
248
|
+
...env.length > 0 ? { env } : {},
|
|
249
|
+
volumeMounts
|
|
250
|
+
}],
|
|
251
|
+
volumes
|
|
252
|
+
} }
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
await batchApi.createNamespacedJob(namespace, jobBody);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
259
|
+
throw new Error(`Kubernetes cluster unreachable or Job creation failed: ${message}`);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
jobName,
|
|
263
|
+
namespace
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
export { submitK8sRunnerJob };
|
package/docs/operator-guide.md
CHANGED
|
@@ -250,6 +250,48 @@ The runner does not own the console database, event store, Job builder, Kueue
|
|
|
250
250
|
watcher, or UI. Do not add a runner-side Kubernetes API kind, database, console
|
|
251
251
|
deployment per run, or separate language stack for this integration.
|
|
252
252
|
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
### K8s-native pipeline execution (quick and execute)
|
|
256
|
+
|
|
257
|
+
The `quick` and `execute` entrypoints submit Kubernetes runner Jobs by default. Each command builds a runner job payload from the task description
|
|
258
|
+
and current git context, creates a ConfigMap, and submits a `batch/v1` Job that
|
|
259
|
+
runs the pipeline inside a pod using the package-owned runner image.
|
|
260
|
+
|
|
261
|
+
Set `PIPELINE_EVENT_URL` to configure the runner event sink. Without it, the
|
|
262
|
+
command fails with a validation error.
|
|
263
|
+
|
|
264
|
+
#### Prerequisites
|
|
265
|
+
|
|
266
|
+
The following must exist in the target namespace:
|
|
267
|
+
|
|
268
|
+
- **ServiceAccount** `pipeline-runner` with RBAC to read pods/logs (for
|
|
269
|
+
debugging).
|
|
270
|
+
- **Secret** `codex-auth-1` with key `auth.json` (Codex authentication).
|
|
271
|
+
- **Secret** `opencode-auth-1` with key `auth.json` (OpenCode authentication).
|
|
272
|
+
- **Secret** `pipeline-runner-event-auth` with key `token` (event sink bearer
|
|
273
|
+
token, mounted at `/etc/pipeline/event-auth/token`).
|
|
274
|
+
- **Secret** `pipeline-runner-github-auth` with keys `gitconfig`,
|
|
275
|
+
`git-credentials`, `hosts.yml` (GitHub authentication for `git` and `gh`).
|
|
276
|
+
- A pipeline-console event sink endpoint reachable from the pod.
|
|
277
|
+
|
|
278
|
+
#### Usage
|
|
279
|
+
|
|
280
|
+
```shell
|
|
281
|
+
export PIPELINE_EVENT_URL="https://console.example.com/api/pipeline/runner-events"
|
|
282
|
+
oisin-pipeline quick "fix the login bug"
|
|
283
|
+
oisin-pipeline execute "Implement PIPE-53"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### Local execution fallback
|
|
287
|
+
|
|
288
|
+
Use the `--local` flag for workstation-local execution:
|
|
289
|
+
|
|
290
|
+
```shell
|
|
291
|
+
oisin-pipeline quick --local "fix the login bug"
|
|
292
|
+
oisin-pipeline execute --local "Implement PIPE-53"
|
|
293
|
+
```
|
|
294
|
+
|
|
253
295
|
Generated invocations include:
|
|
254
296
|
|
|
255
297
|
```text
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@dagrejs/graphlib": "^4.0.1",
|
|
4
|
+
"@kubernetes/client-node": "^1.4.0",
|
|
4
5
|
"ajv": "^8.20.0",
|
|
5
6
|
"ajv-formats": "^3.0.1",
|
|
6
7
|
"commander": "^14.0.3",
|
|
@@ -111,7 +112,7 @@
|
|
|
111
112
|
"prepack": "bun run build:cli"
|
|
112
113
|
},
|
|
113
114
|
"type": "module",
|
|
114
|
-
"version": "1.
|
|
115
|
+
"version": "1.23.0",
|
|
115
116
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
116
117
|
"main": "./dist/index.js",
|
|
117
118
|
"types": "./dist/index.d.ts",
|