@intentius/chant-lexicon-k8s 0.124.41 → 0.124.43
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/components/kubectl-apply.d.ts +6 -0
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +76 -6
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +5 -5
- package/src/components/kubectl-apply.test.ts +49 -0
- package/src/components/kubectl-apply.ts +85 -6
|
@@ -159,6 +159,12 @@ export interface KubectlApplyOutputField {
|
|
|
159
159
|
/** The field's dot path in the object; a list index is a number, `status.loadBalancer.ingress.0.hostname`. */
|
|
160
160
|
field: string;
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* The documents `targets` name (#4300), each `Kind/namespace/name` or
|
|
164
|
+
* `Kind/name` as {@link addressOf} writes it, in manifest order. Throws for
|
|
165
|
+
* a target the manifest does not declare.
|
|
166
|
+
*/
|
|
167
|
+
export declare function targetedDocuments(member: string, manifest: string, documents: Record<string, unknown>[], targets: readonly string[]): Record<string, unknown>[];
|
|
162
168
|
/** Factory with an injectable applier, so tests assert the delegation without
|
|
163
169
|
* a cluster — the same seam `createGenerateSbomCapability` uses. The default
|
|
164
170
|
* applier is resolved by dynamic import on first run, keeping the API-client
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kubectl-apply.d.ts","sourceRoot":"","sources":["../../src/components/kubectl-apply.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"kubectl-apply.d.ts","sourceRoot":"","sources":["../../src/components/kubectl-apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAAE,UAAU,EAAiD,MAAM,wCAAwC,CAAC;AAOxH;;;;;;;;;;GAUG;AACH,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,4DAA4D;AAC5D,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,2GAA2G;IAC3G,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;CAC/C;AAED,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnE,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AAElE,wFAAwF;AACxF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,oDAAoD;AACpD,UAAU,kBAAkB;IAC1B,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAElE,0DAA0D;AAC1D,UAAU,cAAc;IACtB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gGAAgG;AAChG,KAAK,UAAU,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;AAEhG,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,oBAAoB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACtC;AAOD;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;aAIjC,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;aAChB,KAAK,EAAE,MAAM;IAL/B,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,QAAQ,KAAK;IACtB,YACkB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAO9B;CACF;AA2GD,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;CACnD;AAED,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,8GAA8G;IAC9G,KAAK,EAAE,MAAM,CAAC;CACf;AA4CD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAoB/J;AAUD;;;uCAGuC;AACvC,wBAAgB,4BAA4B,CAC1C,KAAK,CAAC,EAAE,OAAO,EACf,IAAI,CAAC,EAAE,cAAc,EACrB,MAAM,CAAC,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,UAAU,GACpB,UAAU,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAkJpD;AAED,eAAO,MAAM,sBAAsB,oDAAiC,CAAC"}
|
|
@@ -157,6 +157,36 @@ async function readOutputs(read, refs, input, where) {
|
|
|
157
157
|
}
|
|
158
158
|
return out;
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The documents `targets` name (#4300), each `Kind/namespace/name` or
|
|
162
|
+
* `Kind/name` as {@link addressOf} writes it, in manifest order. Throws for
|
|
163
|
+
* a target the manifest does not declare.
|
|
164
|
+
*/
|
|
165
|
+
export function targetedDocuments(member, manifest, documents, targets) {
|
|
166
|
+
const addressOfDoc = (doc) => {
|
|
167
|
+
const metadata = (doc.metadata ?? {});
|
|
168
|
+
return addressOf({
|
|
169
|
+
apiVersion: String(doc.apiVersion ?? ""),
|
|
170
|
+
kind: String(doc.kind ?? ""),
|
|
171
|
+
name: String(metadata.name ?? ""),
|
|
172
|
+
...(metadata.namespace !== undefined ? { namespace: String(metadata.namespace) } : {}),
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
const declared = new Set(documents.map(addressOfDoc));
|
|
176
|
+
const missing = targets.filter((t) => !declared.has(t));
|
|
177
|
+
if (missing.length > 0) {
|
|
178
|
+
throw new Error(`kubectl-apply "${member}": --target ${missing.join(", ")} names no object ${manifest} declares ` +
|
|
179
|
+
`(it declares ${[...declared].join(", ") || "nothing"})`);
|
|
180
|
+
}
|
|
181
|
+
const wanted = new Set(targets);
|
|
182
|
+
return documents.filter((d) => wanted.has(addressOfDoc(d)));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The input a targeted step plans and applies with (#4300): the same, with no
|
|
186
|
+
* prune. A prune over a narrowed set would delete every owned object the
|
|
187
|
+
* targets leave out.
|
|
188
|
+
*/
|
|
189
|
+
const narrowedInput = (ctx, input) => ctx.targets && ctx.targets.length > 0 ? { ...input, delete: "never" } : input;
|
|
160
190
|
/** Factory with an injectable applier, so tests assert the delegation without
|
|
161
191
|
* a cluster — the same seam `createGenerateSbomCapability` uses. The default
|
|
162
192
|
* applier is resolved by dynamic import on first run, keeping the API-client
|
|
@@ -205,28 +235,61 @@ export function createKubectlApplyCapability(apply, read, dryRun, readLive) {
|
|
|
205
235
|
}
|
|
206
236
|
return { known, unknown: unknown.sort() };
|
|
207
237
|
};
|
|
238
|
+
/**
|
|
239
|
+
* {@link plannedOutputs} for a targeted plan (#4300): an output of a
|
|
240
|
+
* targeted object as the plan says, and one of an object the targets leave
|
|
241
|
+
* out as the cluster holds it now, since the apply leaves that object alone.
|
|
242
|
+
*/
|
|
243
|
+
const targetedPlannedOutputs = async (ctx, input, member, documents, dry, targets, declared) => {
|
|
244
|
+
if (!targets)
|
|
245
|
+
return plannedOutputs(ctx, input, member, documents, dry);
|
|
246
|
+
const wanted = new Set(targets);
|
|
247
|
+
const all = Object.entries(input.outputs ?? {});
|
|
248
|
+
const inside = Object.fromEntries(all.filter(([, spec]) => wanted.has(spec.object)));
|
|
249
|
+
const outside = Object.fromEntries(all.filter(([, spec]) => !wanted.has(spec.object)));
|
|
250
|
+
const planned = Object.keys(inside).length > 0 ? await plannedOutputs(ctx, { ...input, outputs: inside }, member, documents, dry) : { known: {}, unknown: [] };
|
|
251
|
+
if (Object.keys(outside).length === 0)
|
|
252
|
+
return planned;
|
|
253
|
+
const rest = { ...input, outputs: outside };
|
|
254
|
+
const live = await readOutputs(await liveReader(), outputRefs(member, rest, declared), rest, where(ctx, input));
|
|
255
|
+
const known = { ...planned.known, ...live };
|
|
256
|
+
const unknown = [...planned.unknown, ...Object.keys(outside).filter((n) => !(n in live))].sort();
|
|
257
|
+
return { known, unknown };
|
|
258
|
+
};
|
|
208
259
|
return {
|
|
209
260
|
kind: "kubectl-apply",
|
|
210
261
|
// A server-side apply has no native undo (the previous object state is not
|
|
211
262
|
// kept by the API server), so COMP003 requires the component to acknowledge
|
|
212
263
|
// the compensation gap — the same posture as s3-sync/run-migration.
|
|
213
264
|
rollbackPolicy: "needs-opt-out",
|
|
265
|
+
// #4300: a target names one object, `Kind/namespace/name`; the plan and the apply send those alone.
|
|
266
|
+
targets: "accept",
|
|
214
267
|
/**
|
|
215
268
|
* Dry-run the manifest against the cluster (#3998): the documents `run`
|
|
216
269
|
* would apply, release attributes set, server-side applied with
|
|
217
270
|
* `dryRun=All` as the same field manager, each compared with its live
|
|
218
271
|
* object. The member is the step's `stack`, or the component.
|
|
219
272
|
*/
|
|
220
|
-
async plan(ctx,
|
|
273
|
+
async plan(ctx, stepInput) {
|
|
274
|
+
const input = narrowedInput(ctx, stepInput);
|
|
221
275
|
const member = input.stack ?? ctx.component;
|
|
222
|
-
const
|
|
276
|
+
const read_ = (read ?? (await activities()).readManifestDocuments)(input.manifest);
|
|
277
|
+
const targets = ctx.targets && ctx.targets.length > 0 ? ctx.targets : undefined;
|
|
278
|
+
const documents = targets ? targetedDocuments(member, input.manifest, read_, targets) : read_;
|
|
223
279
|
const suffix = releaseAttributesSuffix(ctx.release);
|
|
224
280
|
if (suffix !== "")
|
|
225
281
|
annotateRelease(documents, suffix);
|
|
226
282
|
const { result, planDigest } = await dryRunOf(ctx, input, member, documents);
|
|
227
283
|
const artifact = { kind: "kubectl-apply-plan", planDigest, documents };
|
|
228
|
-
const outputs = input.outputs ? await
|
|
229
|
-
return {
|
|
284
|
+
const outputs = input.outputs ? await targetedPlannedOutputs(ctx, input, member, documents, result, targets, read_) : undefined;
|
|
285
|
+
return {
|
|
286
|
+
member,
|
|
287
|
+
planDigest,
|
|
288
|
+
artifact,
|
|
289
|
+
changeSet: changeSetPartOf(member, planDigest, result),
|
|
290
|
+
...(outputs ? { outputs } : {}),
|
|
291
|
+
...(targets ? { targets } : {}),
|
|
292
|
+
};
|
|
230
293
|
},
|
|
231
294
|
/**
|
|
232
295
|
* The fields the step names as outputs, as the cluster holds them now
|
|
@@ -239,7 +302,8 @@ export function createKubectlApplyCapability(apply, read, dryRun, readLive) {
|
|
|
239
302
|
const documents = (read ?? (await activities()).readManifestDocuments)(input.manifest);
|
|
240
303
|
return liveOutputs(ctx, input, documents);
|
|
241
304
|
},
|
|
242
|
-
async run(ctx,
|
|
305
|
+
async run(ctx, stepInput) {
|
|
306
|
+
const input = narrowedInput(ctx, stepInput);
|
|
243
307
|
const member = input.stack ?? ctx.component;
|
|
244
308
|
const approved = ctx.plans?.[member];
|
|
245
309
|
if (isPlanArtifact(approved)) {
|
|
@@ -258,7 +322,13 @@ export function createKubectlApplyCapability(apply, read, dryRun, readLive) {
|
|
|
258
322
|
// workload the build stamped, in what it applies; the committed manifest is not touched.
|
|
259
323
|
const suffix = releaseAttributesSuffix(ctx.release);
|
|
260
324
|
let documents;
|
|
261
|
-
if (
|
|
325
|
+
if (ctx.targets && ctx.targets.length > 0) {
|
|
326
|
+
// #4300: only the targeted objects, and no prune (`narrowedInput`).
|
|
327
|
+
documents = targetedDocuments(member, input.manifest, (read ?? loaded.readManifestDocuments)(input.manifest), ctx.targets);
|
|
328
|
+
if (suffix !== "")
|
|
329
|
+
annotateRelease(documents, suffix);
|
|
330
|
+
}
|
|
331
|
+
else if (suffix !== "") {
|
|
262
332
|
documents = (read ?? loaded.readManifestDocuments)(input.manifest);
|
|
263
333
|
if (annotateRelease(documents, suffix) === 0)
|
|
264
334
|
documents = undefined;
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "0bb2e0d248aa5b1808d3d4ad070e78463f9c935177c09c7bb1fefaeb0d9b4b64",
|
|
5
5
|
"meta.json": "c1831ac15083f5e0e45b29698d7b77f1c01646240fe6de5d6320753188594f69",
|
|
6
6
|
"types/index.d.ts": "5c5340046d090ae9db5553039d923cf92a8b2eb57aa4dc941273ed5bcfb5bc9d",
|
|
7
7
|
"rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"skills/chant-k8s-argo.md": "24c9a85b78a2fc5c6657424baac9cc6d6caff6f47605b8146b2c0082133181cf",
|
|
79
79
|
"skills/chant-k8s-flux.md": "9670efc2e9854bd14686b5adeb707396b9030a4dcaee6529b30abf56efe2c50c"
|
|
80
80
|
},
|
|
81
|
-
"composite": "
|
|
81
|
+
"composite": "fe30957da3c3498c28db61f85f707151d9f8a5ef6297632f8e080bcbf6ff5f68"
|
|
82
82
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-k8s",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.43",
|
|
4
4
|
"description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -72,13 +72,13 @@
|
|
|
72
72
|
"build": "../../scripts/tsc7.sh -p tsconfig.build.json --sourceMap false && tsc-alias -p tsconfig.build.json && cp src/generated/index.d.ts dist/generated/index.d.d.ts"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@intentius/chant-lexicon-otel": "^0.124.
|
|
76
|
-
"@intentius/chant-lexicon-prometheus": "^0.124.
|
|
75
|
+
"@intentius/chant-lexicon-otel": "^0.124.43",
|
|
76
|
+
"@intentius/chant-lexicon-prometheus": "^0.124.43",
|
|
77
77
|
"js-yaml": "^4.3.1",
|
|
78
78
|
"@types/js-yaml": "^4.0.9"
|
|
79
79
|
},
|
|
80
80
|
"optionalDependencies": {
|
|
81
|
-
"@intentius/chant-k8s-client": "^0.124.
|
|
81
|
+
"@intentius/chant-k8s-client": "^0.124.43"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@intentius/chant": "*",
|
|
@@ -86,6 +86,6 @@
|
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"zod": "^4.3.6",
|
|
89
|
-
"@intentius/chant": "^0.124.
|
|
89
|
+
"@intentius/chant": "^0.124.43"
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -388,3 +388,52 @@ describe("kubectl-apply outputs: the fields a step names (#3999)", () => {
|
|
|
388
388
|
expect((await cap.plan!(ctx, { manifest: "k8s.yaml", stack: "web" })).outputs).toBeUndefined();
|
|
389
389
|
});
|
|
390
390
|
});
|
|
391
|
+
|
|
392
|
+
describe("kubectl-apply targets: one object at a time (#4300)", () => {
|
|
393
|
+
type DryRun = import("./kubectl-apply").KubectlApplyDryRun;
|
|
394
|
+
const doc = (kind: string, name: string, namespace?: string) => ({
|
|
395
|
+
apiVersion: kind === "Deployment" ? "apps/v1" : "v1",
|
|
396
|
+
kind,
|
|
397
|
+
metadata: { name, ...(namespace ? { namespace } : {}) },
|
|
398
|
+
});
|
|
399
|
+
const docs = () => [doc("Deployment", "web", "prod"), doc("Service", "web", "prod"), doc("Namespace", "prod")];
|
|
400
|
+
const none = (): DryRun => ({ fieldManager: "chant:web", stack: "web", objects: [], deletes: [], retained: [] });
|
|
401
|
+
const noApply = async (): Promise<never> => {
|
|
402
|
+
throw new Error("applied");
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
test("accepts targets", () => {
|
|
406
|
+
expect(createKubectlApplyCapability().targets).toBe("accept");
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("plans the targeted objects alone, with no prune, and gives the targets back", async () => {
|
|
410
|
+
let seen: Record<string, unknown> = {};
|
|
411
|
+
const cap = createKubectlApplyCapability(noApply, docs, async (args) => {
|
|
412
|
+
seen = args as never;
|
|
413
|
+
return none();
|
|
414
|
+
});
|
|
415
|
+
const ctx = { env: "prod", component: "api", targets: ["Namespace/prod", "Service/prod/web"] };
|
|
416
|
+
const planned = await cap.plan!(ctx, { manifest: "k8s.yaml", stack: "web", delete: "owned-only" });
|
|
417
|
+
expect(seen).toMatchObject({ documents: [doc("Service", "web", "prod"), doc("Namespace", "prod")], deleteMode: "never" });
|
|
418
|
+
expect(planned.targets).toEqual(["Namespace/prod", "Service/prod/web"]);
|
|
419
|
+
const whole = await cap.plan!({ env: "prod", component: "api" }, { manifest: "k8s.yaml", stack: "web", delete: "owned-only" });
|
|
420
|
+
expect(whole.planDigest).not.toBe(planned.planDigest);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("a target the manifest does not declare refuses and names what it declares", async () => {
|
|
424
|
+
const cap = createKubectlApplyCapability(noApply, docs, async () => none());
|
|
425
|
+
await expect(cap.plan!({ env: "prod", component: "api", targets: ["Deployment/prod/api"] }, { manifest: "k8s.yaml" })).rejects.toThrow(
|
|
426
|
+
/--target Deployment\/prod\/api names no object k8s\.yaml declares \(it declares Deployment\/prod\/web, Service\/prod\/web, Namespace\/prod\)/,
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("run without a plan applies the targeted objects alone, with no prune", async () => {
|
|
431
|
+
let seen: Record<string, unknown> = {};
|
|
432
|
+
const cap = createKubectlApplyCapability(async (args) => {
|
|
433
|
+
seen = args as never;
|
|
434
|
+
return { fieldManager: "chant:web", applied: [], pruned: [] };
|
|
435
|
+
}, docs);
|
|
436
|
+
await cap.run({ env: "prod", component: "api", targets: ["Deployment/prod/web"] }, { manifest: "k8s.yaml", stack: "web", delete: "owned-only" });
|
|
437
|
+
expect(seen).toMatchObject({ documents: [doc("Deployment", "web", "prod")], deleteMode: "never" });
|
|
438
|
+
});
|
|
439
|
+
});
|
|
@@ -338,6 +338,41 @@ async function readOutputs(read: LiveReader, refs: Map<string, AppliedRef>, inpu
|
|
|
338
338
|
return out;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/**
|
|
342
|
+
* The documents `targets` name (#4300), each `Kind/namespace/name` or
|
|
343
|
+
* `Kind/name` as {@link addressOf} writes it, in manifest order. Throws for
|
|
344
|
+
* a target the manifest does not declare.
|
|
345
|
+
*/
|
|
346
|
+
export function targetedDocuments(member: string, manifest: string, documents: Record<string, unknown>[], targets: readonly string[]): Record<string, unknown>[] {
|
|
347
|
+
const addressOfDoc = (doc: Record<string, unknown>): string => {
|
|
348
|
+
const metadata = (doc.metadata ?? {}) as { name?: unknown; namespace?: unknown };
|
|
349
|
+
return addressOf({
|
|
350
|
+
apiVersion: String(doc.apiVersion ?? ""),
|
|
351
|
+
kind: String(doc.kind ?? ""),
|
|
352
|
+
name: String(metadata.name ?? ""),
|
|
353
|
+
...(metadata.namespace !== undefined ? { namespace: String(metadata.namespace) } : {}),
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
const declared = new Set(documents.map(addressOfDoc));
|
|
357
|
+
const missing = targets.filter((t) => !declared.has(t));
|
|
358
|
+
if (missing.length > 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`kubectl-apply "${member}": --target ${missing.join(", ")} names no object ${manifest} declares ` +
|
|
361
|
+
`(it declares ${[...declared].join(", ") || "nothing"})`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const wanted = new Set(targets);
|
|
365
|
+
return documents.filter((d) => wanted.has(addressOfDoc(d)));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The input a targeted step plans and applies with (#4300): the same, with no
|
|
370
|
+
* prune. A prune over a narrowed set would delete every owned object the
|
|
371
|
+
* targets leave out.
|
|
372
|
+
*/
|
|
373
|
+
const narrowedInput = (ctx: DeployContext, input: KubectlApplyInput): KubectlApplyInput =>
|
|
374
|
+
ctx.targets && ctx.targets.length > 0 ? { ...input, delete: "never" } : input;
|
|
375
|
+
|
|
341
376
|
/** Factory with an injectable applier, so tests assert the delegation without
|
|
342
377
|
* a cluster — the same seam `createGenerateSbomCapability` uses. The default
|
|
343
378
|
* applier is resolved by dynamic import on first run, keeping the API-client
|
|
@@ -389,27 +424,66 @@ export function createKubectlApplyCapability(
|
|
|
389
424
|
}
|
|
390
425
|
return { known, unknown: unknown.sort() };
|
|
391
426
|
};
|
|
427
|
+
/**
|
|
428
|
+
* {@link plannedOutputs} for a targeted plan (#4300): an output of a
|
|
429
|
+
* targeted object as the plan says, and one of an object the targets leave
|
|
430
|
+
* out as the cluster holds it now, since the apply leaves that object alone.
|
|
431
|
+
*/
|
|
432
|
+
const targetedPlannedOutputs = async (
|
|
433
|
+
ctx: DeployContext,
|
|
434
|
+
input: KubectlApplyInput,
|
|
435
|
+
member: string,
|
|
436
|
+
documents: Record<string, unknown>[],
|
|
437
|
+
dry: KubectlApplyDryRun,
|
|
438
|
+
targets: readonly string[] | undefined,
|
|
439
|
+
declared: Record<string, unknown>[],
|
|
440
|
+
): Promise<PlannedOutputs> => {
|
|
441
|
+
if (!targets) return plannedOutputs(ctx, input, member, documents, dry);
|
|
442
|
+
const wanted = new Set(targets);
|
|
443
|
+
const all = Object.entries(input.outputs ?? {});
|
|
444
|
+
const inside = Object.fromEntries(all.filter(([, spec]) => wanted.has(spec.object)));
|
|
445
|
+
const outside = Object.fromEntries(all.filter(([, spec]) => !wanted.has(spec.object)));
|
|
446
|
+
const planned = Object.keys(inside).length > 0 ? await plannedOutputs(ctx, { ...input, outputs: inside }, member, documents, dry) : { known: {}, unknown: [] };
|
|
447
|
+
if (Object.keys(outside).length === 0) return planned;
|
|
448
|
+
const rest = { ...input, outputs: outside };
|
|
449
|
+
const live = await readOutputs(await liveReader(), outputRefs(member, rest, declared), rest, where(ctx, input));
|
|
450
|
+
const known = { ...planned.known, ...live };
|
|
451
|
+
const unknown = [...planned.unknown, ...Object.keys(outside).filter((n) => !(n in live))].sort();
|
|
452
|
+
return { known, unknown };
|
|
453
|
+
};
|
|
392
454
|
return {
|
|
393
455
|
kind: "kubectl-apply",
|
|
394
456
|
// A server-side apply has no native undo (the previous object state is not
|
|
395
457
|
// kept by the API server), so COMP003 requires the component to acknowledge
|
|
396
458
|
// the compensation gap — the same posture as s3-sync/run-migration.
|
|
397
459
|
rollbackPolicy: "needs-opt-out",
|
|
460
|
+
// #4300: a target names one object, `Kind/namespace/name`; the plan and the apply send those alone.
|
|
461
|
+
targets: "accept",
|
|
398
462
|
/**
|
|
399
463
|
* Dry-run the manifest against the cluster (#3998): the documents `run`
|
|
400
464
|
* would apply, release attributes set, server-side applied with
|
|
401
465
|
* `dryRun=All` as the same field manager, each compared with its live
|
|
402
466
|
* object. The member is the step's `stack`, or the component.
|
|
403
467
|
*/
|
|
404
|
-
async plan(ctx: DeployContext,
|
|
468
|
+
async plan(ctx: DeployContext, stepInput: KubectlApplyInput): Promise<CapabilityPlan> {
|
|
469
|
+
const input = narrowedInput(ctx, stepInput);
|
|
405
470
|
const member = input.stack ?? ctx.component;
|
|
406
|
-
const
|
|
471
|
+
const read_ = (read ?? (await activities()).readManifestDocuments)(input.manifest);
|
|
472
|
+
const targets = ctx.targets && ctx.targets.length > 0 ? ctx.targets : undefined;
|
|
473
|
+
const documents = targets ? targetedDocuments(member, input.manifest, read_, targets) : read_;
|
|
407
474
|
const suffix = releaseAttributesSuffix(ctx.release);
|
|
408
475
|
if (suffix !== "") annotateRelease(documents, suffix);
|
|
409
476
|
const { result, planDigest } = await dryRunOf(ctx, input, member, documents);
|
|
410
477
|
const artifact: KubectlApplyPlanArtifact = { kind: "kubectl-apply-plan", planDigest, documents };
|
|
411
|
-
const outputs = input.outputs ? await
|
|
412
|
-
return {
|
|
478
|
+
const outputs = input.outputs ? await targetedPlannedOutputs(ctx, input, member, documents, result, targets, read_) : undefined;
|
|
479
|
+
return {
|
|
480
|
+
member,
|
|
481
|
+
planDigest,
|
|
482
|
+
artifact,
|
|
483
|
+
changeSet: changeSetPartOf(member, planDigest, result),
|
|
484
|
+
...(outputs ? { outputs } : {}),
|
|
485
|
+
...(targets ? { targets } : {}),
|
|
486
|
+
};
|
|
413
487
|
},
|
|
414
488
|
/**
|
|
415
489
|
* The fields the step names as outputs, as the cluster holds them now
|
|
@@ -421,7 +495,8 @@ export function createKubectlApplyCapability(
|
|
|
421
495
|
const documents = (read ?? (await activities()).readManifestDocuments)(input.manifest);
|
|
422
496
|
return liveOutputs(ctx, input, documents);
|
|
423
497
|
},
|
|
424
|
-
async run(ctx: DeployContext,
|
|
498
|
+
async run(ctx: DeployContext, stepInput: KubectlApplyInput): Promise<KubectlApplyOutcome> {
|
|
499
|
+
const input = narrowedInput(ctx, stepInput);
|
|
425
500
|
const member = input.stack ?? ctx.component;
|
|
426
501
|
const approved = ctx.plans?.[member];
|
|
427
502
|
if (isPlanArtifact(approved)) {
|
|
@@ -439,7 +514,11 @@ export function createKubectlApplyCapability(
|
|
|
439
514
|
// workload the build stamped, in what it applies; the committed manifest is not touched.
|
|
440
515
|
const suffix = releaseAttributesSuffix(ctx.release);
|
|
441
516
|
let documents: Record<string, unknown>[] | undefined;
|
|
442
|
-
if (
|
|
517
|
+
if (ctx.targets && ctx.targets.length > 0) {
|
|
518
|
+
// #4300: only the targeted objects, and no prune (`narrowedInput`).
|
|
519
|
+
documents = targetedDocuments(member, input.manifest, (read ?? loaded!.readManifestDocuments)(input.manifest), ctx.targets);
|
|
520
|
+
if (suffix !== "") annotateRelease(documents, suffix);
|
|
521
|
+
} else if (suffix !== "") {
|
|
443
522
|
documents = (read ?? loaded!.readManifestDocuments)(input.manifest);
|
|
444
523
|
if (annotateRelease(documents, suffix) === 0) documents = undefined;
|
|
445
524
|
}
|