@oisincoveney/pipeline 3.22.0 → 3.23.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/README.md +17 -17
- package/dist/argo-submit.d.ts +56 -0
- package/dist/argo-submit.js +205 -66
- package/dist/argo-workflow.d.ts +22 -0
- package/dist/commands/runner-command-command.js +1 -1
- package/dist/factory/create-experiment.js +2 -1
- package/dist/factory/exec.js +1 -0
- package/dist/factory/git-credentials.js +37 -0
- package/dist/factory/template-update.js +5 -1
- package/dist/loop/controller-deps.js +8 -3
- package/dist/loop/controller.js +1 -0
- package/dist/moka-submit.d.ts +140 -0
- package/dist/moka-submit.js +5 -2
- package/dist/planning/generate.d.ts +1 -0
- package/dist/planning/generate.js +5 -2
- package/dist/remote/argo/model.d.ts +30 -1
- package/dist/remote/argo/model.js +36 -14
- package/dist/remote/argo/templates.js +6 -2
- package/dist/remote/submit/argo-submission.d.ts +4 -0
- package/dist/remote/submit/argo-submission.js +4 -1
- package/dist/remote/submit/compilation.js +14 -0
- package/dist/run-control/postgres/schema.js +7 -7
- package/dist/run-control/runtime-event-projection.js +1 -0
- package/dist/runner-command/finalize.js +65 -4
- package/dist/runner-command-contract.d.ts +9 -1
- package/dist/runner-command-contract.js +11 -1
- package/dist/runner-event-schema.d.ts +26 -0
- package/dist/runner-event-schema.js +10 -0
- package/dist/runner-event-sink.js +7 -5
- package/dist/runtime/contracts/contracts.d.ts +6 -0
- package/dist/runtime/durable-store/postgres/migrate-substrate.js +112 -5
- package/dist/runtime/durable-store/postgres/postgres-store.js +1 -1
- package/dist/runtime/durable-store/postgres/schema.js +8 -5
- package/dist/runtime/open-pull-request/open-pull-request.js +30 -4
- package/dist/runtime/services/kubernetes-argo-service.d.ts +5 -1
- package/dist/runtime/services/kubernetes-argo-service.js +22 -1
- package/dist/runtime/services/runner-command-io-service.js +11 -2
- package/dist/runtime/services/runner-event-sink-http-service.js +7 -1
- package/dist/schedule/passes/open-pull-request.js +4 -1
- package/dist/tickets/backlog-task-store.d.ts +41 -1
- package/dist/tickets/ticket-graph-dto.d.ts +60 -0
- package/dist/tickets/ticket-graph.d.ts +20 -1
- package/docs/adr-moka-postgres-schema-isolation.md +58 -0
- package/docs/operator-guide.md +26 -28
- package/docs/pipeline-console-runner-contract.md +19 -10
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -44,12 +44,12 @@ Initialize package-owned pipeline support:
|
|
|
44
44
|
moka init
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
`moka init` installs or refreshes
|
|
48
|
-
|
|
49
|
-
`pipeline-gateway` MCP
|
|
50
|
-
|
|
51
|
-
OpenCode is the package default runtime. The
|
|
52
|
-
`.pipeline` config files.
|
|
47
|
+
`moka init` installs or refreshes Moka-owned host adapters: `/moka-quick`,
|
|
48
|
+
`/moka-execute`, and `/moka-inspect` command surfaces, native-agent projections,
|
|
49
|
+
and the singleton `pipeline-gateway` MCP host config. It does not install the
|
|
50
|
+
shared agent harness; skills, hooks, rules, and Claude settings come from
|
|
51
|
+
`oisin-ee/agent` via chezmoi. OpenCode is the package default runtime. The
|
|
52
|
+
command does not create repo-local `.pipeline` config files.
|
|
53
53
|
|
|
54
54
|
The default MCP gateway can run locally or point at the hosted Momokaya gateway.
|
|
55
55
|
Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
|
|
@@ -59,14 +59,14 @@ value before starting OpenCode when using a protected gateway:
|
|
|
59
59
|
export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' | base64)"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Verify
|
|
63
|
-
|
|
62
|
+
Verify generated Moka host adapters are current after package upgrades, without
|
|
63
|
+
writing anything:
|
|
64
64
|
|
|
65
65
|
```shell
|
|
66
66
|
moka init --check
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Refresh
|
|
69
|
+
Refresh generated Moka host adapters, overwriting locally edited adapter files:
|
|
70
70
|
|
|
71
71
|
```shell
|
|
72
72
|
moka init --force
|
|
@@ -107,10 +107,10 @@ Canonical commands:
|
|
|
107
107
|
- `moka stop <run-id> [node-id]`: abort a run or one active node.
|
|
108
108
|
- `moka export <run-id> --sanitize`: print a portable evidence bundle.
|
|
109
109
|
- `moka doctor`: check local prerequisites and config health.
|
|
110
|
-
- `moka init`: install or refresh
|
|
111
|
-
|
|
112
|
-
`--dry-run` previews, `--force` overwrites locally edited
|
|
113
|
-
|
|
110
|
+
- `moka init`: install or refresh Moka-owned host adapters (command surfaces,
|
|
111
|
+
native-agent projections, and gateway config). `--check` verifies without
|
|
112
|
+
writing, `--dry-run` previews, `--force` overwrites locally edited adapter
|
|
113
|
+
files. The shared agent harness is installed separately by chezmoi.
|
|
114
114
|
|
|
115
115
|
```shell
|
|
116
116
|
moka run "Implement PIPE-123 user-facing behavior"
|
|
@@ -197,10 +197,10 @@ For a compact command reference, see
|
|
|
197
197
|
## Momokaya Runner Image
|
|
198
198
|
|
|
199
199
|
The package is also the runner code used by the Momokaya runner image. The
|
|
200
|
-
control plane owns Argo Workflow submission, run listing,
|
|
201
|
-
|
|
202
|
-
`moka runner-command` and `moka runner-finalize` commands used by
|
|
203
|
-
DAG tasks.
|
|
200
|
+
control plane owns Argo Workflow submission, status observation, run listing,
|
|
201
|
+
cancellation, event storage, and UI rendering. This package owns the
|
|
202
|
+
in-container `moka runner-command` and `moka runner-finalize` commands used by
|
|
203
|
+
Argo Workflow DAG tasks.
|
|
204
204
|
|
|
205
205
|
Argo starts the image with payload, schedule, and per-task descriptor files
|
|
206
206
|
mounted from ConfigMaps, plus the event auth token mounted from a Secret. The
|
package/dist/argo-submit.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
|
|
6
6
|
//#region src/argo-submit.d.ts
|
|
7
7
|
declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
8
|
+
activeDeadlineSeconds: z.ZodOptional<z.ZodNumber>;
|
|
8
9
|
brokerAuth: z.ZodObject<{
|
|
9
10
|
secretKey: z.ZodDefault<z.ZodString>;
|
|
10
11
|
secretName: z.ZodString;
|
|
@@ -36,10 +37,38 @@ declare const submitRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
36
37
|
namespace: z.ZodString;
|
|
37
38
|
npmRegistryAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
38
39
|
payloadJson: z.ZodString;
|
|
40
|
+
podGC: z.ZodOptional<z.ZodObject<{
|
|
41
|
+
deleteDelayDuration: z.ZodOptional<z.ZodString>;
|
|
42
|
+
labelSelector: z.ZodOptional<z.ZodObject<{
|
|
43
|
+
matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
44
|
+
key: z.ZodString;
|
|
45
|
+
operator: z.ZodEnum<{
|
|
46
|
+
DoesNotExist: "DoesNotExist";
|
|
47
|
+
Exists: "Exists";
|
|
48
|
+
In: "In";
|
|
49
|
+
NotIn: "NotIn";
|
|
50
|
+
}>;
|
|
51
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
52
|
+
}, z.core.$strict>>>;
|
|
53
|
+
matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
54
|
+
}, z.core.$strict>>;
|
|
55
|
+
strategy: z.ZodOptional<z.ZodEnum<{
|
|
56
|
+
OnPodCompletion: "OnPodCompletion";
|
|
57
|
+
OnPodSuccess: "OnPodSuccess";
|
|
58
|
+
OnWorkflowCompletion: "OnWorkflowCompletion";
|
|
59
|
+
OnWorkflowSuccess: "OnWorkflowSuccess";
|
|
60
|
+
}>>;
|
|
61
|
+
}, z.core.$strict>>;
|
|
39
62
|
serviceAccountName: z.ZodOptional<z.ZodString>;
|
|
63
|
+
ttlStrategy: z.ZodOptional<z.ZodObject<{
|
|
64
|
+
secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
secondsAfterSuccess: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
}, z.core.$strict>>;
|
|
40
68
|
scheduleYaml: z.ZodString;
|
|
41
69
|
}, z.core.$strict>;
|
|
42
70
|
declare const submitDynamicRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
71
|
+
activeDeadlineSeconds: z.ZodOptional<z.ZodNumber>;
|
|
43
72
|
brokerAuth: z.ZodObject<{
|
|
44
73
|
secretKey: z.ZodDefault<z.ZodString>;
|
|
45
74
|
secretName: z.ZodString;
|
|
@@ -71,7 +100,34 @@ declare const submitDynamicRunnerArgoWorkflowOptionsSchema: z.ZodObject<{
|
|
|
71
100
|
namespace: z.ZodString;
|
|
72
101
|
npmRegistryAuthSecretName: z.ZodOptional<z.ZodString>;
|
|
73
102
|
payloadJson: z.ZodString;
|
|
103
|
+
podGC: z.ZodOptional<z.ZodObject<{
|
|
104
|
+
deleteDelayDuration: z.ZodOptional<z.ZodString>;
|
|
105
|
+
labelSelector: z.ZodOptional<z.ZodObject<{
|
|
106
|
+
matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
107
|
+
key: z.ZodString;
|
|
108
|
+
operator: z.ZodEnum<{
|
|
109
|
+
DoesNotExist: "DoesNotExist";
|
|
110
|
+
Exists: "Exists";
|
|
111
|
+
In: "In";
|
|
112
|
+
NotIn: "NotIn";
|
|
113
|
+
}>;
|
|
114
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
|
+
}, z.core.$strict>>>;
|
|
116
|
+
matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
117
|
+
}, z.core.$strict>>;
|
|
118
|
+
strategy: z.ZodOptional<z.ZodEnum<{
|
|
119
|
+
OnPodCompletion: "OnPodCompletion";
|
|
120
|
+
OnPodSuccess: "OnPodSuccess";
|
|
121
|
+
OnWorkflowCompletion: "OnWorkflowCompletion";
|
|
122
|
+
OnWorkflowSuccess: "OnWorkflowSuccess";
|
|
123
|
+
}>>;
|
|
124
|
+
}, z.core.$strict>>;
|
|
74
125
|
serviceAccountName: z.ZodOptional<z.ZodString>;
|
|
126
|
+
ttlStrategy: z.ZodOptional<z.ZodObject<{
|
|
127
|
+
secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
|
|
128
|
+
secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
|
|
129
|
+
secondsAfterSuccess: z.ZodOptional<z.ZodNumber>;
|
|
130
|
+
}, z.core.$strict>>;
|
|
75
131
|
workflowId: z.ZodString;
|
|
76
132
|
}, z.core.$strict>;
|
|
77
133
|
declare const commandScheduleOptionsSchema: z.ZodObject<{
|
package/dist/argo-submit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ArgoGraphCompilerError, compileArgoExecutionGraph } from "./argo-graph.js";
|
|
2
|
-
import { dbAuthOptionSchema, mcpGatewayAuthOptionSchema } from "./remote/argo/model.js";
|
|
2
|
+
import { argoWorkflowActiveDeadlineSecondsSchema, argoWorkflowPodGcSchema, argoWorkflowTtlStrategySchema, dbAuthOptionSchema, mcpGatewayAuthOptionSchema } from "./remote/argo/model.js";
|
|
3
3
|
import { brokerAuthOptionSchema } from "./credentials/broker.js";
|
|
4
4
|
import { appendPullRequestDelivery } from "./schedule/passes/open-pull-request.js";
|
|
5
5
|
import { compileScheduleArtifact, parseScheduleArtifact } from "./planning/generate.js";
|
|
@@ -25,11 +25,19 @@ const configMapSchema = z.object({
|
|
|
25
25
|
namespace: z.string().min(1)
|
|
26
26
|
}).strict()
|
|
27
27
|
}).strict();
|
|
28
|
+
const workflowOwnerReferenceSchema = z.object({
|
|
29
|
+
apiVersion: z.literal("argoproj.io/v1alpha1"),
|
|
30
|
+
kind: z.literal("Workflow"),
|
|
31
|
+
name: z.string().min(1),
|
|
32
|
+
uid: z.string().min(1)
|
|
33
|
+
}).strict();
|
|
34
|
+
const configMapOwnerReferencesPatchSchema = z.object({ metadata: z.object({ ownerReferences: z.tuple([workflowOwnerReferenceSchema]) }).strict() }).strict();
|
|
28
35
|
const createdWorkflowSchema = z.object({ metadata: z.object({
|
|
29
36
|
name: z.string().min(1).optional(),
|
|
30
37
|
uid: z.string().min(1).optional()
|
|
31
38
|
}).passthrough() }).passthrough();
|
|
32
39
|
const submitRunnerArgoWorkflowBaseOptionShape = {
|
|
40
|
+
activeDeadlineSeconds: argoWorkflowActiveDeadlineSecondsSchema.optional(),
|
|
33
41
|
brokerAuth: brokerAuthOptionSchema,
|
|
34
42
|
dbAuth: dbAuthOptionSchema.optional(),
|
|
35
43
|
eventAuthSecretKey: z.string().min(1).optional(),
|
|
@@ -51,7 +59,9 @@ const submitRunnerArgoWorkflowBaseOptionShape = {
|
|
|
51
59
|
namespace: z.string().min(1),
|
|
52
60
|
npmRegistryAuthSecretName: z.string().min(1).optional(),
|
|
53
61
|
payloadJson: z.string().min(1),
|
|
54
|
-
|
|
62
|
+
podGC: argoWorkflowPodGcSchema.optional(),
|
|
63
|
+
serviceAccountName: z.string().min(1).optional(),
|
|
64
|
+
ttlStrategy: argoWorkflowTtlStrategySchema.optional()
|
|
55
65
|
};
|
|
56
66
|
const submitRunnerArgoWorkflowOptionsSchema = z.object({
|
|
57
67
|
...submitRunnerArgoWorkflowBaseOptionShape,
|
|
@@ -102,6 +112,7 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
102
112
|
"pipeline.oisin.dev/ticket-project": payload.run.project,
|
|
103
113
|
"pipeline.oisin.dev/ticket-title": payload.task.title
|
|
104
114
|
} : {},
|
|
115
|
+
activeDeadlineSeconds: options.activeDeadlineSeconds,
|
|
105
116
|
brokerAuth: options.brokerAuth,
|
|
106
117
|
dbAuth: options.dbAuth,
|
|
107
118
|
mcpGatewayAuth: options.mcpGatewayAuth,
|
|
@@ -121,51 +132,21 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
121
132
|
plan: compiled.plan,
|
|
122
133
|
scheduleConfigMapName: scheduleArtifactConfigMapName,
|
|
123
134
|
serviceAccountName: options.serviceAccountName,
|
|
124
|
-
taskDescriptorConfigMapName
|
|
135
|
+
taskDescriptorConfigMapName,
|
|
136
|
+
ttlStrategy: options.ttlStrategy
|
|
125
137
|
});
|
|
126
138
|
return Effect.gen(function* () {
|
|
127
|
-
const service = yield* KubernetesArgoService;
|
|
128
139
|
const graph = yield* graphEffect;
|
|
129
|
-
yield*
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}),
|
|
140
|
-
dependencies,
|
|
141
|
-
namespace: options.namespace,
|
|
142
|
-
options
|
|
143
|
-
});
|
|
144
|
-
yield* service.createConfigMap({
|
|
145
|
-
body: configMapSchema.parse({
|
|
146
|
-
apiVersion: "v1",
|
|
147
|
-
data: Object.fromEntries(graph.tasks.map((task) => [`${task.taskName}.json`, `${JSON.stringify(buildRunnerTaskDescriptor(task.nodeId))}\n`])),
|
|
148
|
-
kind: "ConfigMap",
|
|
149
|
-
metadata: {
|
|
150
|
-
labels,
|
|
151
|
-
name: taskDescriptorConfigMapName,
|
|
152
|
-
namespace: options.namespace
|
|
153
|
-
}
|
|
154
|
-
}),
|
|
155
|
-
dependencies,
|
|
156
|
-
namespace: options.namespace,
|
|
157
|
-
options
|
|
158
|
-
});
|
|
159
|
-
yield* service.createConfigMap({
|
|
160
|
-
body: configMapSchema.parse({
|
|
161
|
-
apiVersion: "v1",
|
|
162
|
-
data: { "schedule.yaml": options.scheduleYaml },
|
|
163
|
-
kind: "ConfigMap",
|
|
164
|
-
metadata: {
|
|
165
|
-
labels,
|
|
166
|
-
name: scheduleArtifactConfigMapName,
|
|
167
|
-
namespace: options.namespace
|
|
168
|
-
}
|
|
140
|
+
const createdConfigMapNames = yield* createRunConfigMaps({
|
|
141
|
+
configMaps: staticRunConfigMaps({
|
|
142
|
+
labels,
|
|
143
|
+
namespace: options.namespace,
|
|
144
|
+
payloadConfigMapName,
|
|
145
|
+
payloadJson,
|
|
146
|
+
scheduleConfigMapName: scheduleArtifactConfigMapName,
|
|
147
|
+
scheduleYaml: options.scheduleYaml,
|
|
148
|
+
taskDescriptorConfigMapName,
|
|
149
|
+
tasks: graph.tasks
|
|
169
150
|
}),
|
|
170
151
|
dependencies,
|
|
171
152
|
namespace: options.namespace,
|
|
@@ -180,8 +161,21 @@ function submitRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
180
161
|
scheduleConfigMapName: scheduleArtifactConfigMapName,
|
|
181
162
|
taskDescriptorConfigMapName
|
|
182
163
|
},
|
|
183
|
-
workflow
|
|
184
|
-
|
|
164
|
+
workflow,
|
|
165
|
+
workflowFieldOverrides: { podGC: options.podGC }
|
|
166
|
+
}).pipe(Effect.flatMap((result) => ownRunConfigMaps({
|
|
167
|
+
configMapNames: createdConfigMapNames,
|
|
168
|
+
dependencies,
|
|
169
|
+
namespace: options.namespace,
|
|
170
|
+
options,
|
|
171
|
+
result
|
|
172
|
+
})), Effect.catch((error) => cleanupRunConfigMapsOnFailure({
|
|
173
|
+
configMapNames: createdConfigMapNames,
|
|
174
|
+
dependencies,
|
|
175
|
+
error,
|
|
176
|
+
namespace: options.namespace,
|
|
177
|
+
options
|
|
178
|
+
})));
|
|
185
179
|
});
|
|
186
180
|
}
|
|
187
181
|
function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
@@ -199,12 +193,14 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
199
193
|
"pipeline.oisin.dev/source": "argo-workflow",
|
|
200
194
|
"pipeline.oisin.dev/workflow": options.workflowId
|
|
201
195
|
};
|
|
196
|
+
const annotations = payload.task.kind === "ticket" ? {
|
|
197
|
+
"pipeline.oisin.dev/ticket-id": payload.task.id,
|
|
198
|
+
"pipeline.oisin.dev/ticket-project": payload.run.project,
|
|
199
|
+
"pipeline.oisin.dev/ticket-title": payload.task.title
|
|
200
|
+
} : {};
|
|
202
201
|
const workflow = buildDynamicRunnerArgoWorkflowManifest({
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"pipeline.oisin.dev/ticket-project": payload.run.project,
|
|
206
|
-
"pipeline.oisin.dev/ticket-title": payload.task.title
|
|
207
|
-
} : {},
|
|
202
|
+
activeDeadlineSeconds: options.activeDeadlineSeconds,
|
|
203
|
+
annotations,
|
|
208
204
|
brokerAuth: options.brokerAuth,
|
|
209
205
|
dbAuth: options.dbAuth,
|
|
210
206
|
eventAuthSecretKey: options.eventAuthSecretKey,
|
|
@@ -222,19 +218,16 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
222
218
|
npmRegistryAuthSecretName: options.npmRegistryAuthSecretName,
|
|
223
219
|
payloadConfigMapName,
|
|
224
220
|
serviceAccountName: options.serviceAccountName,
|
|
221
|
+
ttlStrategy: options.ttlStrategy,
|
|
225
222
|
workflowId: options.workflowId
|
|
226
223
|
});
|
|
227
224
|
return Effect.gen(function* () {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
labels,
|
|
235
|
-
name: payloadConfigMapName,
|
|
236
|
-
namespace: options.namespace
|
|
237
|
-
}
|
|
225
|
+
const createdConfigMapNames = yield* createRunConfigMaps({
|
|
226
|
+
configMaps: dynamicRunConfigMaps({
|
|
227
|
+
labels,
|
|
228
|
+
namespace: options.namespace,
|
|
229
|
+
payloadConfigMapName,
|
|
230
|
+
payloadJson
|
|
238
231
|
}),
|
|
239
232
|
dependencies,
|
|
240
233
|
namespace: options.namespace,
|
|
@@ -245,23 +238,169 @@ function submitDynamicRunnerArgoWorkflowEffect(rawOptions, dependencies) {
|
|
|
245
238
|
namespace: options.namespace,
|
|
246
239
|
options,
|
|
247
240
|
resultExtras: { payloadConfigMapName },
|
|
248
|
-
workflow
|
|
249
|
-
|
|
241
|
+
workflow,
|
|
242
|
+
workflowFieldOverrides: { podGC: options.podGC }
|
|
243
|
+
}).pipe(Effect.flatMap((result) => ownRunConfigMaps({
|
|
244
|
+
configMapNames: createdConfigMapNames,
|
|
245
|
+
dependencies,
|
|
246
|
+
namespace: options.namespace,
|
|
247
|
+
options,
|
|
248
|
+
result
|
|
249
|
+
})), Effect.catch((error) => cleanupRunConfigMapsOnFailure({
|
|
250
|
+
configMapNames: createdConfigMapNames,
|
|
251
|
+
dependencies,
|
|
252
|
+
error,
|
|
253
|
+
namespace: options.namespace,
|
|
254
|
+
options
|
|
255
|
+
})));
|
|
250
256
|
});
|
|
251
257
|
}
|
|
258
|
+
function staticRunConfigMaps(input) {
|
|
259
|
+
return [
|
|
260
|
+
runConfigMap({
|
|
261
|
+
data: { "payload.json": input.payloadJson },
|
|
262
|
+
labels: input.labels,
|
|
263
|
+
name: input.payloadConfigMapName,
|
|
264
|
+
namespace: input.namespace
|
|
265
|
+
}),
|
|
266
|
+
runConfigMap({
|
|
267
|
+
data: Object.fromEntries(input.tasks.map((task) => [`${task.taskName}.json`, `${JSON.stringify(buildRunnerTaskDescriptor(task.nodeId))}\n`])),
|
|
268
|
+
labels: input.labels,
|
|
269
|
+
name: input.taskDescriptorConfigMapName,
|
|
270
|
+
namespace: input.namespace
|
|
271
|
+
}),
|
|
272
|
+
runConfigMap({
|
|
273
|
+
data: { "schedule.yaml": input.scheduleYaml },
|
|
274
|
+
labels: input.labels,
|
|
275
|
+
name: input.scheduleConfigMapName,
|
|
276
|
+
namespace: input.namespace
|
|
277
|
+
})
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
function dynamicRunConfigMaps(input) {
|
|
281
|
+
return [runConfigMap({
|
|
282
|
+
data: { "payload.json": input.payloadJson },
|
|
283
|
+
labels: input.labels,
|
|
284
|
+
name: input.payloadConfigMapName,
|
|
285
|
+
namespace: input.namespace
|
|
286
|
+
})];
|
|
287
|
+
}
|
|
288
|
+
function runConfigMap(input) {
|
|
289
|
+
return {
|
|
290
|
+
body: configMapSchema.parse({
|
|
291
|
+
apiVersion: "v1",
|
|
292
|
+
data: input.data,
|
|
293
|
+
kind: "ConfigMap",
|
|
294
|
+
metadata: {
|
|
295
|
+
labels: input.labels,
|
|
296
|
+
name: input.name,
|
|
297
|
+
namespace: input.namespace
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
300
|
+
name: input.name
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function createRunConfigMaps(input) {
|
|
304
|
+
const createdConfigMapNames = [];
|
|
305
|
+
return Effect.gen(function* () {
|
|
306
|
+
const service = yield* KubernetesArgoService;
|
|
307
|
+
yield* Effect.forEach(input.configMaps, (configMap) => service.createConfigMap({
|
|
308
|
+
body: configMap.body,
|
|
309
|
+
dependencies: input.dependencies,
|
|
310
|
+
namespace: input.namespace,
|
|
311
|
+
options: input.options
|
|
312
|
+
}).pipe(Effect.tap(() => Effect.sync(() => {
|
|
313
|
+
createdConfigMapNames.push(configMap.name);
|
|
314
|
+
}))));
|
|
315
|
+
return createdConfigMapNames;
|
|
316
|
+
}).pipe(Effect.catch((error) => cleanupRunConfigMapsOnFailure({
|
|
317
|
+
configMapNames: createdConfigMapNames,
|
|
318
|
+
dependencies: input.dependencies,
|
|
319
|
+
error,
|
|
320
|
+
namespace: input.namespace,
|
|
321
|
+
options: input.options
|
|
322
|
+
})));
|
|
323
|
+
}
|
|
324
|
+
function ownRunConfigMaps(input) {
|
|
325
|
+
return patchRunConfigMapOwnerReferences({
|
|
326
|
+
body: configMapOwnerReferencesPatch(workflowOwnerReference(input.result)),
|
|
327
|
+
configMapNames: input.configMapNames,
|
|
328
|
+
dependencies: input.dependencies,
|
|
329
|
+
namespace: input.namespace,
|
|
330
|
+
options: input.options
|
|
331
|
+
}).pipe(Effect.as(input.result));
|
|
332
|
+
}
|
|
333
|
+
function cleanupRunConfigMapsOnFailure(input) {
|
|
334
|
+
return deleteRunConfigMaps({
|
|
335
|
+
configMapNames: input.configMapNames,
|
|
336
|
+
dependencies: input.dependencies,
|
|
337
|
+
namespace: input.namespace,
|
|
338
|
+
options: input.options
|
|
339
|
+
}).pipe(Effect.catch((cleanupError) => Effect.fail(/* @__PURE__ */ new Error(`Failed to clean up ConfigMaps after submit failure: ${errorMessage(input.error)}; cleanup failed: ${errorMessage(cleanupError)}`))), Effect.flatMap(() => Effect.fail(input.error)));
|
|
340
|
+
}
|
|
341
|
+
function patchRunConfigMapOwnerReferences(input) {
|
|
342
|
+
return Effect.gen(function* () {
|
|
343
|
+
const service = yield* KubernetesArgoService;
|
|
344
|
+
return yield* Effect.all(input.configMapNames.map((name) => service.patchConfigMapOwnerReferences({
|
|
345
|
+
body: input.body,
|
|
346
|
+
dependencies: input.dependencies,
|
|
347
|
+
name,
|
|
348
|
+
namespace: input.namespace,
|
|
349
|
+
options: input.options
|
|
350
|
+
})), { concurrency: "unbounded" });
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function deleteRunConfigMaps(input) {
|
|
354
|
+
return Effect.gen(function* () {
|
|
355
|
+
const service = yield* KubernetesArgoService;
|
|
356
|
+
return yield* Effect.all(input.configMapNames.map((name) => service.deleteConfigMap({
|
|
357
|
+
dependencies: input.dependencies,
|
|
358
|
+
name,
|
|
359
|
+
namespace: input.namespace,
|
|
360
|
+
options: input.options
|
|
361
|
+
})), { concurrency: "unbounded" });
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
function workflowOwnerReference(result) {
|
|
365
|
+
if (result.workflowUid === void 0) throw new Error(`Created Argo Workflow '${result.workflowName}' did not include metadata.uid; cannot own ConfigMaps`);
|
|
366
|
+
return workflowOwnerReferenceSchema.parse({
|
|
367
|
+
apiVersion: "argoproj.io/v1alpha1",
|
|
368
|
+
kind: "Workflow",
|
|
369
|
+
name: result.workflowName,
|
|
370
|
+
uid: result.workflowUid
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
function configMapOwnerReferencesPatch(ownerReference) {
|
|
374
|
+
return configMapOwnerReferencesPatchSchema.parse({ metadata: { ownerReferences: [ownerReference] } });
|
|
375
|
+
}
|
|
376
|
+
function errorMessage(error) {
|
|
377
|
+
return error instanceof Error ? error.message : String(error);
|
|
378
|
+
}
|
|
252
379
|
function submitWorkflowManifest(input) {
|
|
253
380
|
return Effect.gen(function* () {
|
|
254
|
-
|
|
255
|
-
|
|
381
|
+
const service = yield* KubernetesArgoService;
|
|
382
|
+
const workflow = applyWorkflowFieldOverrides(input.workflow, input.workflowFieldOverrides);
|
|
383
|
+
return workflowSubmitResult(yield* service.createWorkflow({
|
|
384
|
+
body: runnerArgoWorkflowManifestSchema.parse(workflow),
|
|
256
385
|
dependencies: input.dependencies,
|
|
257
386
|
namespace: input.namespace,
|
|
258
387
|
options: input.options
|
|
259
|
-
}),
|
|
388
|
+
}), workflow, {
|
|
260
389
|
namespace: input.namespace,
|
|
261
390
|
...input.resultExtras
|
|
262
391
|
});
|
|
263
392
|
});
|
|
264
393
|
}
|
|
394
|
+
function applyWorkflowFieldOverrides(workflow, overrides) {
|
|
395
|
+
if (overrides?.podGC === void 0) return workflow;
|
|
396
|
+
return runnerArgoWorkflowManifestSchema.parse({
|
|
397
|
+
...workflow,
|
|
398
|
+
spec: {
|
|
399
|
+
...workflow.spec,
|
|
400
|
+
podGC: overrides.podGC
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
265
404
|
function workflowSubmitResult(response, workflow, base) {
|
|
266
405
|
const created = createdWorkflowSchema.parse(response);
|
|
267
406
|
return workflowSubmitResultSchema.parse({
|
package/dist/argo-workflow.d.ts
CHANGED
|
@@ -126,6 +126,28 @@ declare const runnerArgoWorkflowManifestSchema: z.ZodObject<{
|
|
|
126
126
|
}>;
|
|
127
127
|
}, z.core.$strict>>;
|
|
128
128
|
}, z.core.$strict>>;
|
|
129
|
+
podGC: z.ZodOptional<z.ZodObject<{
|
|
130
|
+
deleteDelayDuration: z.ZodOptional<z.ZodString>;
|
|
131
|
+
labelSelector: z.ZodOptional<z.ZodObject<{
|
|
132
|
+
matchExpressions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
133
|
+
key: z.ZodString;
|
|
134
|
+
operator: z.ZodEnum<{
|
|
135
|
+
DoesNotExist: "DoesNotExist";
|
|
136
|
+
Exists: "Exists";
|
|
137
|
+
In: "In";
|
|
138
|
+
NotIn: "NotIn";
|
|
139
|
+
}>;
|
|
140
|
+
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
141
|
+
}, z.core.$strict>>>;
|
|
142
|
+
matchLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
143
|
+
}, z.core.$strict>>;
|
|
144
|
+
strategy: z.ZodOptional<z.ZodEnum<{
|
|
145
|
+
OnPodCompletion: "OnPodCompletion";
|
|
146
|
+
OnPodSuccess: "OnPodSuccess";
|
|
147
|
+
OnWorkflowCompletion: "OnWorkflowCompletion";
|
|
148
|
+
OnWorkflowSuccess: "OnWorkflowSuccess";
|
|
149
|
+
}>>;
|
|
150
|
+
}, z.core.$strict>>;
|
|
129
151
|
ttlStrategy: z.ZodOptional<z.ZodObject<{
|
|
130
152
|
secondsAfterCompletion: z.ZodOptional<z.ZodNumber>;
|
|
131
153
|
secondsAfterFailure: z.ZodOptional<z.ZodNumber>;
|
|
@@ -51,7 +51,7 @@ function registerRunnerCommandCommand(program) {
|
|
|
51
51
|
program.command("runner-command").description("Run one scheduled Argo Workflow task").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--node-id <id>", "Node id to execute without a task descriptor").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").action((options) => runRunnerProgram(runRunnerCommandEffect(options)));
|
|
52
52
|
program.command("runner-lifecycle").description("Run one Argo Workflow lifecycle phase").requiredOption("--phase <phase>", "Lifecycle phase to run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--schedule-file <path>", "Path to the schedule artifact YAML").action((options) => runRunnerProgram(runRunnerLifecycleEffect(options)));
|
|
53
53
|
program.command("runner-pre-schedule").description("Run one dynamic pre-schedule phase").requiredOption("--phase <phase>", "pre-research, pre-planning, or generate-schedule").requiredOption("--payload-file <path>", "Path to the runner payload JSON").action((options) => runRunnerProgram(runPreScheduleEffect(options)));
|
|
54
|
-
program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").requiredOption("--argo-status <status>", "Argo Workflow status").action((options) => runRunnerProgram(runRunnerFinalizeEffect(options)));
|
|
54
|
+
program.command("runner-finalize").description("Finalize one Argo Workflow run").requiredOption("--payload-file <path>", "Path to the runner payload JSON").option("--schedule-file <path>", "Path to the schedule artifact YAML").option("--schedule-source <source>", "Schedule source: file or db").requiredOption("--argo-status <status>", "Argo Workflow status").option("--argo-failures <json>", "Argo Workflow failure details JSON").action((options) => runRunnerProgram(runRunnerFinalizeEffect(options)));
|
|
55
55
|
program.command("runner-select-ready-wave").description("Select DB-ready nodes for the next dynamic Argo wave").requiredOption("--payload-file <path>", "Path to the runner payload JSON").requiredOption("--output-file <path>", "Path where the ready node id JSON array is written").action((options) => runRunnerProgram(runSelectReadyWaveEffect(options)));
|
|
56
56
|
}
|
|
57
57
|
//#endregion
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_RUNNER_COMMAND_GIT_COMMITTER } from "../config/schema/catalog.js";
|
|
2
2
|
import { resolveFactorySeams } from "./exec.js";
|
|
3
|
+
import { githubGitCredentialEnv } from "./git-credentials.js";
|
|
3
4
|
import { existsSync, mkdtempSync } from "node:fs";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join, resolve } from "node:path";
|
|
@@ -80,7 +81,7 @@ async function runCreateExperiment(options) {
|
|
|
80
81
|
previews,
|
|
81
82
|
...options.templateRef ? { templateRef: options.templateRef } : {},
|
|
82
83
|
templateSource
|
|
83
|
-
}));
|
|
84
|
+
}), { env: githubGitCredentialEnv() });
|
|
84
85
|
const stampedRegistryEntry = join(stampDir, STAMPED_REGISTRY_DIR, `${name}.yaml`);
|
|
85
86
|
if (!existsSync(stampedRegistryEntry)) throw new Error(`create-experiment: stamp is missing the registry entry ${STAMPED_REGISTRY_DIR}/${name}.yaml — template contract changed?`);
|
|
86
87
|
log("create-experiment: committing the stamped tree");
|
package/dist/factory/exec.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execa } from "execa";
|
|
|
3
3
|
//#region src/factory/exec.ts
|
|
4
4
|
const defaultFactoryExec = (command, args, options) => execa(command, [...args], {
|
|
5
5
|
...options?.cwd ? { cwd: options.cwd } : {},
|
|
6
|
+
...options?.env ? { env: options.env } : {},
|
|
6
7
|
stdin: "ignore"
|
|
7
8
|
});
|
|
8
9
|
const defaultFactoryGit = (cwd, args) => runAuthenticatedGit(cwd, args);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
//#region src/factory/git-credentials.ts
|
|
5
|
+
const DEFAULT_GIT_CREDENTIALS_DIR = "/etc/pipeline/git-credentials";
|
|
6
|
+
/**
|
|
7
|
+
* copier fetches the (private) momokaya-template by spawning its OWN git
|
|
8
|
+
* subprocess (`git ls-remote` / `clone`, via plumbum). `runAuthenticatedGit`
|
|
9
|
+
* only configures credentials per-invocation with `-c` flags on the git calls
|
|
10
|
+
* the lane makes itself, so copier's git sees no github.com credential and
|
|
11
|
+
* dies `fatal: could not read Username for 'https://github.com'`.
|
|
12
|
+
*
|
|
13
|
+
* This materializes a process-wide github.com credential from the runner's
|
|
14
|
+
* mounted git-credentials (username + PAT under /etc/pipeline/git-credentials)
|
|
15
|
+
* as `GIT_CONFIG_*` env any child (and grandchild) git process inherits, so
|
|
16
|
+
* copier's template fetch authenticates. The token is written to a 0600 store
|
|
17
|
+
* file and referenced by a `credential.helper=store` entry — it never lands in
|
|
18
|
+
* a URL or argv. No mounted credentials (local dev) -> empty env, so git falls
|
|
19
|
+
* back to ambient auth.
|
|
20
|
+
*/
|
|
21
|
+
function githubGitCredentialEnv(credentialsDir = process.env.PIPELINE_GIT_CREDENTIALS_DIR ?? DEFAULT_GIT_CREDENTIALS_DIR) {
|
|
22
|
+
const usernamePath = resolve(credentialsDir, "username");
|
|
23
|
+
const passwordPath = resolve(credentialsDir, "password");
|
|
24
|
+
if (!(existsSync(usernamePath) && existsSync(passwordPath))) return {};
|
|
25
|
+
const username = readFileSync(usernamePath, "utf8").trim();
|
|
26
|
+
const password = readFileSync(passwordPath, "utf8").trim();
|
|
27
|
+
const storePath = join(mkdtempSync(join(tmpdir(), "factory-git-cred-")), ".git-credentials");
|
|
28
|
+
writeFileSync(storePath, `https://${encodeURIComponent(username)}:${encodeURIComponent(password)}@github.com\n`, { mode: 384 });
|
|
29
|
+
return {
|
|
30
|
+
GIT_CONFIG_COUNT: "1",
|
|
31
|
+
GIT_CONFIG_KEY_0: "credential.helper",
|
|
32
|
+
GIT_CONFIG_VALUE_0: `store --file=${storePath}`,
|
|
33
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { githubGitCredentialEnv };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveFactorySeams } from "./exec.js";
|
|
2
|
+
import { githubGitCredentialEnv } from "./git-credentials.js";
|
|
2
3
|
import { committerConfigArgs } from "./create-experiment.js";
|
|
3
4
|
import { isStampOf, parseCopierAnswers } from "./stamp-answers.js";
|
|
4
5
|
import { parse } from "yaml";
|
|
@@ -101,7 +102,10 @@ async function updateRepo(input) {
|
|
|
101
102
|
"--trust",
|
|
102
103
|
"--defaults",
|
|
103
104
|
...input.templateRef ? ["--vcs-ref", input.templateRef] : []
|
|
104
|
-
], {
|
|
105
|
+
], {
|
|
106
|
+
cwd: cloneDir,
|
|
107
|
+
env: githubGitCredentialEnv()
|
|
108
|
+
});
|
|
105
109
|
const status = await git(cloneDir, ["status", "--porcelain"]);
|
|
106
110
|
if (status.trim().length === 0) return {
|
|
107
111
|
repo,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runAuthenticatedGit } from "../run-state/git-refs.js";
|
|
2
2
|
import { RunnerEventSinkHttpService, RunnerEventSinkHttpServiceLive } from "../runtime/services/runner-event-sink-http-service.js";
|
|
3
|
+
import { RUNNER_EVENT_SINK_RETRY_POLICY } from "../runner-event-sink.js";
|
|
3
4
|
import { pollWorkflowPhaseUntilTerminal } from "./argo-poll.js";
|
|
4
5
|
import { ticketGraphDtoSchema } from "../tickets/ticket-graph-dto.js";
|
|
5
6
|
import { submitMoka } from "../moka-submit.js";
|
|
@@ -39,6 +40,7 @@ function buildControllerDeps(context, seams = {}) {
|
|
|
39
40
|
runId: input.runId,
|
|
40
41
|
workflowName: input.workflowName
|
|
41
42
|
}),
|
|
43
|
+
projectId: context.project,
|
|
42
44
|
refreshBacklog: () => Effect.tryPromise({
|
|
43
45
|
catch: refreshError,
|
|
44
46
|
try: () => gitRefresh(context.worktreePath)
|
|
@@ -130,7 +132,10 @@ function submitRepository(context, request) {
|
|
|
130
132
|
const RECORD_BUILDER = {
|
|
131
133
|
"loop.start": (event, envelope) => event.type === "loop.start" ? {
|
|
132
134
|
...envelope,
|
|
133
|
-
loopStart: {
|
|
135
|
+
loopStart: {
|
|
136
|
+
projectId: event.projectId,
|
|
137
|
+
strategy: event.strategy
|
|
138
|
+
},
|
|
134
139
|
type: "loop.start"
|
|
135
140
|
} : unreachable(event),
|
|
136
141
|
"loop.graph.snapshot": (event, envelope) => event.type === "loop.graph.snapshot" ? {
|
|
@@ -183,8 +188,8 @@ function defaultPostEvent(context) {
|
|
|
183
188
|
authToken: context.eventAuthToken,
|
|
184
189
|
events: [record],
|
|
185
190
|
fetch: fetchImpl,
|
|
186
|
-
maxRetries:
|
|
187
|
-
retryDelayMs:
|
|
191
|
+
maxRetries: RUNNER_EVENT_SINK_RETRY_POLICY.maxRetries,
|
|
192
|
+
retryDelayMs: RUNNER_EVENT_SINK_RETRY_POLICY.retryDelayMs,
|
|
188
193
|
url: context.eventUrl
|
|
189
194
|
})), RunnerEventSinkHttpServiceLive));
|
|
190
195
|
}
|
package/dist/loop/controller.js
CHANGED
|
@@ -17,6 +17,7 @@ function runLoopController(deps) {
|
|
|
17
17
|
const graph = yield* buildTicketGraphEffect([...initialTasks]).pipe(Effect.mapError((error) => new Error(error.message)));
|
|
18
18
|
yield* deps.emit({
|
|
19
19
|
type: "loop.start",
|
|
20
|
+
projectId: deps.projectId,
|
|
20
21
|
strategy: deps.strategy
|
|
21
22
|
});
|
|
22
23
|
const snapshot = yield* serializeTicketGraph(graph);
|