@olivervea/pipelines 2.0.0-beta.17 → 2.0.0-beta.18
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 +2 -0
- package/manifest.schema.json +22 -0
- package/package.json +1 -1
- package/src/builder/pipeline.ts +14 -0
- package/src/describe/describe.ts +10 -0
- package/src/describe/manifest-schema.ts +8 -0
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -253,6 +253,7 @@ export default pipeline({ name: "Olve.Pipelines", description: "…", input: M.D
|
|
|
253
253
|
- **Describe checks the mapper twice.** It records the mapper with proxies, and it statically analyses the mapper's source. The source must be an object literal of member chains, literals, constant arrays and nested object literals. Ternaries, `&&`, `||`, `??`, `!`, comparisons, arithmetic, template strings, calls, spread and optional chaining are all rejected. Free identifiers such as module constants are allowed; they are recorded as `{const}`.
|
|
254
254
|
- **Triggers.** A pipeline may call `onPush` at most once. Each `onPoll` and `onWebhook` needs a distinct `name` (`onPoll` defaults to `"poll"`). `onWebhook({ name, verify }, ({ headers, body }) => input | null)` maps a delivery to `/api/hooks/<pipeline id>/<name>`; `verify` is `hmacSha256({ secret, header, prefix? })`, `githubSignature(secret)` or `bearer(secret)`, and `null` ignores the delivery.
|
|
255
255
|
- **`select` (branch on config).** `.select(TARGET.prod, { ssh: (p, prod) => p.stage(…), inCluster: (p, prod) => p.stage(…) })`: the first argument references a `z.discriminatedUnion` in a config entry (`DiscriminatorOf<U>` is its one literal field; a union with no or several literal fields is a type error). Cases are exhaustive and checked at compile time; each gets the reference narrowed to its variant (`ConfigNode<VariantOf<U, D, V>>`), recorded as `{"$config": "target.prod", "$case": "target.prod.kind=ssh"}` and usable only inside that case. The manifest records `{select, discriminator, cases}`; the service resolves it per run from the pinned config snapshot (`when` is per run input). The same step name may appear in several cases (same output model; not two different custom steps, since handlers pair by name); a step every case has is definitely available afterwards, one only some have is maybe-available. The harness picks the variant of a same-named step from `OLVE_CASES` (a JSON array of the case conditions the step sits in, set by the service).
|
|
256
|
+
- **Retention.** `pipeline({ …, retention: { keepLatestRuns?, minRunAgeDays?, buildGraceDays? } })` overrides the instance's retention thresholds for this pipeline's runs and binaries, field by field (non-negative integers; unset = the instance's value). Recorded on the manifest root as `retention`; the service applies the active version's.
|
|
256
257
|
- **Failure handlers.** `.onFailure(handler, { steps? })` takes a step whose inputs are all `FailureContext`. When a run fails, the service runs each handler whose scope (default: any step) contains a failed step, once per failure: `run <handler>` in the handler's image with its declared secrets, from the run's pinned binary. It is untracked and best-effort — not an attempt of the run, never retried, and its own failure triggers nothing; its log is kept at `runs/<p>/<run>/_failure/<handler>/<id>/log`. `FailureContext` carries `pipeline {id, name}`, `run {id, number, address, versionStamp, trigger, url}`, `version {number, commit}`, the run's `input`, a one-line `reason`, and `failedSteps[]` (`step, attempt, address, reason, logUrl`) — only the steps in the handler's scope, never empty.
|
|
257
258
|
|
|
258
259
|
### Testing handlers
|
|
@@ -397,6 +398,7 @@ Exact format: [`manifest.schema.json`](manifest.schema.json) (JSON Schema 2020-1
|
|
|
397
398
|
- `{kind: "poll", name, url, headers, secrets, valuePath, intervalSeconds}`, where header values may contain `$SECRET:NAME`;
|
|
398
399
|
- `{kind: "webhook", name, verify: {scheme: "hmac-sha256", secret, header, prefix} | {scheme: "bearer", secret}}`. The `map-trigger` event is `{headers, body}`; a `null` mapping yields a `skipped` envelope (the delivery is ignored).
|
|
399
400
|
- **`failureHandlers`** is a list of `{step: Step, steps: [names] | null}`. Each handler's inputs are all FailureContext (`{"ref": "failure"}`).
|
|
401
|
+
- **`retention`** (when present) is `{keepLatestRuns?, minRunAgeDays?, buildGraceDays?}`, non-negative integers that replace the instance's thresholds for this pipeline. Omitted when the definition sets none.
|
|
400
402
|
- **`secrets`** lists every secret used by steps, polls and webhooks, sorted, as `{name, description}`.
|
|
401
403
|
- **`config`** lists every config entry steps declare, sorted by name, as `{name, description, required, schema}`. `schema` is the `z.input` view (defaults listed); it validates the entry's value in the config document. `required` is false when the schema accepts a missing value. A `secretRef` field is marked `"x-olve-kind": "secretRef"`, with the secret's name as `properties.secret.const`. Omitted when there are no entries.
|
|
402
404
|
- **Step `configEntries`** names the entries the step receives (sorted; omitted when none). It is part of the step's content, like `secrets`. The step key `config` is the step's `settings`.
|
package/manifest.schema.json
CHANGED
|
@@ -165,6 +165,28 @@
|
|
|
165
165
|
"minLength": 1,
|
|
166
166
|
"type": "string"
|
|
167
167
|
},
|
|
168
|
+
"retention": {
|
|
169
|
+
"additionalProperties": false,
|
|
170
|
+
"description": "Retention overrides for this pipeline (omitted when none). Each field replaces the instance setting of the same meaning.",
|
|
171
|
+
"properties": {
|
|
172
|
+
"buildGraceDays": {
|
|
173
|
+
"maximum": 9007199254740991,
|
|
174
|
+
"minimum": 0,
|
|
175
|
+
"type": "integer"
|
|
176
|
+
},
|
|
177
|
+
"keepLatestRuns": {
|
|
178
|
+
"maximum": 9007199254740991,
|
|
179
|
+
"minimum": 0,
|
|
180
|
+
"type": "integer"
|
|
181
|
+
},
|
|
182
|
+
"minRunAgeDays": {
|
|
183
|
+
"maximum": 9007199254740991,
|
|
184
|
+
"minimum": 0,
|
|
185
|
+
"type": "integer"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"type": "object"
|
|
189
|
+
},
|
|
168
190
|
"secrets": {
|
|
169
191
|
"description": "All declared secrets (steps + poll triggers), sorted by name.",
|
|
170
192
|
"items": {
|
package/package.json
CHANGED
package/src/builder/pipeline.ts
CHANGED
|
@@ -183,10 +183,21 @@ export interface FailureHandlerNode {
|
|
|
183
183
|
readonly steps: readonly AnyStep[] | null;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/** Per-pipeline retention; each field overrides the instance's `Retention:*` setting of the same meaning. */
|
|
187
|
+
export interface RetentionPolicy {
|
|
188
|
+
/** A run is never deleted while among its pipeline's newest N runs. */
|
|
189
|
+
keepLatestRuns?: number;
|
|
190
|
+
/** A run is never deleted while younger than this. */
|
|
191
|
+
minRunAgeDays?: number;
|
|
192
|
+
/** An unused compiled binary is deleted this long after it was last modified. */
|
|
193
|
+
buildGraceDays?: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
186
196
|
export interface PipelineData {
|
|
187
197
|
readonly name: string;
|
|
188
198
|
readonly description: string | undefined;
|
|
189
199
|
readonly input: AnyModel;
|
|
200
|
+
readonly retention: RetentionPolicy | undefined;
|
|
190
201
|
readonly nodes: StageNode[];
|
|
191
202
|
readonly triggers: TriggerNode[];
|
|
192
203
|
readonly failureHandlers: FailureHandlerNode[];
|
|
@@ -216,6 +227,8 @@ export interface PipelineDef<In extends AnyModel> {
|
|
|
216
227
|
description?: string;
|
|
217
228
|
/** The pipeline input model (what triggers produce, what `when` matches on). */
|
|
218
229
|
input: In;
|
|
230
|
+
/** Retention overrides for this pipeline's runs and binaries (unset fields keep the instance's values). */
|
|
231
|
+
retention?: RetentionPolicy;
|
|
219
232
|
}
|
|
220
233
|
|
|
221
234
|
export class Pipeline<In extends AnyModel, A extends AvailMap> {
|
|
@@ -228,6 +241,7 @@ export class Pipeline<In extends AnyModel, A extends AvailMap> {
|
|
|
228
241
|
name: def.name,
|
|
229
242
|
description: def.description,
|
|
230
243
|
input: def.input,
|
|
244
|
+
retention: def.retention,
|
|
231
245
|
nodes: [],
|
|
232
246
|
triggers: shared?.triggers ?? [],
|
|
233
247
|
failureHandlers: shared?.failureHandlers ?? [],
|
package/src/describe/describe.ts
CHANGED
|
@@ -488,6 +488,15 @@ export function describePipeline(pipeline: unknown, opts: DescribeOptions): Mani
|
|
|
488
488
|
return { kind: "poll", name: t.name, url: t.url, headers: { ...t.headers }, secrets: [...declared].sort(), valuePath: t.valuePath, intervalSeconds: t.intervalSeconds };
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
+
// ── retention ──
|
|
492
|
+
const retention: Record<string, number> = {};
|
|
493
|
+
for (const key of ["keepLatestRuns", "minRunAgeDays", "buildGraceDays"] as const) {
|
|
494
|
+
const v = data.retention?.[key];
|
|
495
|
+
if (v === undefined) continue;
|
|
496
|
+
if (!(Number.isInteger(v) && v >= 0)) problems.push(`retention.${key} must be a non-negative integer`);
|
|
497
|
+
else retention[key] = v;
|
|
498
|
+
}
|
|
499
|
+
|
|
491
500
|
if (problems.length) throw new DescribeError(problems);
|
|
492
501
|
|
|
493
502
|
const manifest = canonicalize({
|
|
@@ -495,6 +504,7 @@ export function describePipeline(pipeline: unknown, opts: DescribeOptions): Mani
|
|
|
495
504
|
name: data.name,
|
|
496
505
|
description: data.description,
|
|
497
506
|
input: inputModel.name,
|
|
507
|
+
...(Object.keys(retention).length ? { retention } : {}),
|
|
498
508
|
harness: opts.harness,
|
|
499
509
|
models: Object.fromEntries([...models].map(([k, v]) => [k, v.schemas])),
|
|
500
510
|
secrets: [...secrets].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([name, description]) => ({ name, description })),
|
|
@@ -182,6 +182,14 @@ export const Manifest = z
|
|
|
182
182
|
)
|
|
183
183
|
.optional()
|
|
184
184
|
.describe("Config entries used by steps, sorted by name (omitted when none). Fields of kind secretRef ({secret: NAME}) name a secret."),
|
|
185
|
+
retention: z
|
|
186
|
+
.strictObject({
|
|
187
|
+
keepLatestRuns: z.int().min(0).optional(),
|
|
188
|
+
minRunAgeDays: z.int().min(0).optional(),
|
|
189
|
+
buildGraceDays: z.int().min(0).optional(),
|
|
190
|
+
})
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("Retention overrides for this pipeline (omitted when none). Each field replaces the instance setting of the same meaning."),
|
|
185
193
|
triggers: z.array(Trigger),
|
|
186
194
|
stages: z.array(Stage),
|
|
187
195
|
failureHandlers: z.array(
|
package/src/index.ts
CHANGED
|
@@ -12,5 +12,5 @@ export type { Simplify } from "./core/types.ts";
|
|
|
12
12
|
export { implement, type Implementation, type Handler, type HandlerResult, type Skipped } from "./core/handlers.ts";
|
|
13
13
|
export type { Ctx, RunInfo, Sh, ShOptions, ExecOptions, Http, HttpInit, RetryOptions, OutputFileOptions } from "./ctx/types.ts";
|
|
14
14
|
export type { Ref, Mappable, MapperResult } from "./builder/refs.ts";
|
|
15
|
-
export { pipeline, Pipeline, type PipelineDef, type InRefs, type InitialAvail, type WhenCond, type PushEvent, type StageEntry, type CheckStage, type MapperFn } from "./builder/pipeline.ts";
|
|
15
|
+
export { pipeline, Pipeline, type PipelineDef, type RetentionPolicy, type InRefs, type InitialAvail, type WhenCond, type PushEvent, type StageEntry, type CheckStage, type MapperFn } from "./builder/pipeline.ts";
|
|
16
16
|
export { steps, Source, BuiltImage, PushedImage, HelmRelease, FailureContext, type GitHubSourceDef, type ImageBuildDef, type ImageBuildTarballDef, type ImageBuildPushDef, type RegistryAuth, type RegistryAuthConfig, type HelmDeployOverSshDef, helmTarget, deployTarget, type DeployTargetOptions, type ComposeDeployDef, type ComposeTargetValue, type HelmDeployInClusterDef, type HelmDeploySshTargetDef, type HelmTargetDeployDef, type HelmTargetValue, type InClusterTargetValue, type SshTargetValue, httpHealthCheck, type HttpHealthCheckOptions, sshSession, type SshSession, serviceUrl, checkSpa } from "./library/index.ts";
|