@intentius/chant-lexicon-k8s 0.41.16 → 0.41.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/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "c2f3553d27f37ccedf78b608acf76ee427dcd03bdc832f0a9ffbb4da78ff9bd9",
|
|
5
5
|
"meta.json": "c0b141b882c51483c1d1aead748a0eb171aceca7432edd2a0404b062bf6d7831",
|
|
6
6
|
"types/index.d.ts": "3be2af7494aca94adfa16269cb9c4e7a155529872207a3d70b0c4d39914f9eec",
|
|
7
7
|
"rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9",
|
|
51
51
|
"skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5"
|
|
52
52
|
},
|
|
53
|
-
"composite": "
|
|
53
|
+
"composite": "719807c3da16afea57ea7a7709cc29007e28cd3c8d7ab094c273794fa95fbb9e"
|
|
54
54
|
}
|
package/dist/manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAaH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;
|
|
1
|
+
{"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAaH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-k8s",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.18",
|
|
4
4
|
"description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"zod": "^4.3.6",
|
|
87
|
-
"@intentius/chant": "^0.41.
|
|
87
|
+
"@intentius/chant": "^0.41.18",
|
|
88
88
|
"typescript": "^5.9.3"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -164,6 +164,109 @@ describe("kubectlApply", () => {
|
|
|
164
164
|
expect(bodies[1].metadata?.labels?.["chant.intentius.io/env"]).toBeUndefined();
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
test("a conflict owned entirely by chant's own managers is retaken with force — the stack-label migration path", async () => {
|
|
168
|
+
// An estate applied before its components named per-unit stacks has every
|
|
169
|
+
// object's labels owned by `chant:<ownership.stack>`. The re-stamp under
|
|
170
|
+
// the unit's manager conflicts — with chant itself. Refusing that forever
|
|
171
|
+
// would strand the estate with no non-force path (the capability exposes
|
|
172
|
+
// no force flag), so chant retakes its OWN fields, once, per document.
|
|
173
|
+
const file = join(dir, "k8s.yaml");
|
|
174
|
+
writeFileSync(file, deploymentYaml);
|
|
175
|
+
let patches = 0;
|
|
176
|
+
const cluster = fakeCluster({
|
|
177
|
+
respond: (req) => {
|
|
178
|
+
if (req.method !== "PATCH") return undefined;
|
|
179
|
+
patches++;
|
|
180
|
+
const forced = req.query.force === "true";
|
|
181
|
+
if (forced) return { body: JSON.parse(String(req.body)) };
|
|
182
|
+
return {
|
|
183
|
+
status: 409,
|
|
184
|
+
body: {
|
|
185
|
+
...statusBody(409, "Conflict", 'Apply failed with 1 conflict: conflict with "chant:kubemicrovm-ops"'),
|
|
186
|
+
details: {
|
|
187
|
+
causes: [
|
|
188
|
+
{
|
|
189
|
+
type: "FieldManagerConflict",
|
|
190
|
+
message: 'conflict with "chant:kubemicrovm-ops"',
|
|
191
|
+
field: ".metadata.labels.chant.intentius.io/stack",
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const result = await applyManifest({ manifest: file, stack: "kmv-workload" }, undefined, cluster.connector);
|
|
201
|
+
expect(result.applied).toHaveLength(2);
|
|
202
|
+
// Each of the two documents: one refused apply + one forced retake.
|
|
203
|
+
expect(patches).toBe(4);
|
|
204
|
+
const requests = cluster.layer.requests.filter((r) => r.method === "PATCH");
|
|
205
|
+
expect(requests.map((r) => r.query.force)).toEqual(["false", "true", "false", "true"]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("a controller echoing chant's marker label is retaken too — chant owns its reserved channel", async () => {
|
|
209
|
+
// An operator that reconciles whole objects becomes SSA owner of labels it
|
|
210
|
+
// merely echoed (observed live: kubemicrovm's microvmimagereconciler
|
|
211
|
+
// owning .metadata.labels.chant.intentius.io/stack). It never chose that
|
|
212
|
+
// value; the marker namespace is chant's.
|
|
213
|
+
const file = join(dir, "k8s.yaml");
|
|
214
|
+
writeFileSync(file, deploymentYaml);
|
|
215
|
+
const cluster = fakeCluster({
|
|
216
|
+
respond: (req) => {
|
|
217
|
+
if (req.method !== "PATCH") return undefined;
|
|
218
|
+
if (req.query.force === "true") return { body: JSON.parse(String(req.body)) };
|
|
219
|
+
return {
|
|
220
|
+
status: 409,
|
|
221
|
+
body: {
|
|
222
|
+
...statusBody(409, "Conflict", 'Apply failed with 1 conflict: conflict with "microvmimagereconciler"'),
|
|
223
|
+
details: {
|
|
224
|
+
causes: [
|
|
225
|
+
{
|
|
226
|
+
type: "FieldManagerConflict",
|
|
227
|
+
message: 'conflict with "microvmimagereconciler"',
|
|
228
|
+
field: ".metadata.labels.chant.intentius.io/stack",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const result = await applyManifest({ manifest: file, stack: "kmv-workload" }, undefined, cluster.connector);
|
|
238
|
+
expect(result.applied).toHaveLength(2);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("a conflict involving any FOREIGN manager still refuses — self-retake never widens", async () => {
|
|
242
|
+
const file = join(dir, "k8s.yaml");
|
|
243
|
+
writeFileSync(file, deploymentYaml);
|
|
244
|
+
const cluster = fakeCluster({
|
|
245
|
+
respond: (req) =>
|
|
246
|
+
req.method === "PATCH"
|
|
247
|
+
? {
|
|
248
|
+
status: 409,
|
|
249
|
+
body: {
|
|
250
|
+
...statusBody(409, "Conflict", "Apply failed with 2 conflicts"),
|
|
251
|
+
details: {
|
|
252
|
+
causes: [
|
|
253
|
+
{ type: "FieldManagerConflict", message: 'conflict with "chant:old"', field: ".metadata.labels.chant.intentius.io/stack" },
|
|
254
|
+
{ type: "FieldManagerConflict", message: 'conflict with "helm"', field: ".spec.replicas" },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
: undefined,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const err = (await applyManifest({ manifest: file, stack: "kmv-workload" }, undefined, cluster.connector).catch(
|
|
263
|
+
(e: unknown) => e,
|
|
264
|
+
)) as Error;
|
|
265
|
+
expect(err.name).toBe("FieldManagerConflictError");
|
|
266
|
+
// No forced retry was attempted.
|
|
267
|
+
expect(cluster.layer.requests.filter((r) => r.method === "PATCH" && r.query.force === "true")).toHaveLength(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
167
270
|
test("no resolvable stack applies the documents verbatim — no marker invented", async () => {
|
|
168
271
|
const file = join(dir, "k8s.yaml");
|
|
169
272
|
writeFileSync(file, deploymentYaml);
|
|
@@ -191,6 +191,49 @@ async function resolveApplyIdentity(
|
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Structural mirror of the client's `FieldManagerConflictError` surface —
|
|
196
|
+
* matched by `name` + shape, NOT `instanceof`, because a static value import
|
|
197
|
+
* of `@intentius/chant-k8s-client` here would put the API-client chain on the
|
|
198
|
+
* build path (the #1074 boundary; same reason the applier itself is reached
|
|
199
|
+
* by dynamic import).
|
|
200
|
+
*/
|
|
201
|
+
interface ConflictErrorLike {
|
|
202
|
+
name: string;
|
|
203
|
+
conflicts?: Array<{ manager?: unknown; field?: unknown }>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** chant's reserved label channel — the `chant.intentius.io/*` keys the
|
|
207
|
+
* ownership marker lives under, as a conflict-cause field-path prefix. */
|
|
208
|
+
const MARKER_FIELD_PREFIX = `.metadata.labels.${LABEL_OWNERSHIP_KEYS.stack.split("/")[0]}/`;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Is this conflict one chant may retake without a human? Two shapes qualify,
|
|
212
|
+
* per contested field:
|
|
213
|
+
*
|
|
214
|
+
* - the owning manager is chant itself (`chant` bare or `chant:<stack>`) —
|
|
215
|
+
* the ownership-stack → unit-stack migration, or a renamed deploy unit; or
|
|
216
|
+
* - the field lives in chant's OWN reserved label channel
|
|
217
|
+
* (`chant.intentius.io/*`). A controller that reconciles whole objects
|
|
218
|
+
* echoes existing labels and becomes their SSA owner as a side effect
|
|
219
|
+
* (observed live: kubemicrovm's `microvmimagereconciler` owning
|
|
220
|
+
* `.metadata.labels.chant.intentius.io/stack`) — it never chose that value,
|
|
221
|
+
* and chant is the authority for its marker namespace no matter who last
|
|
222
|
+
* echoed it.
|
|
223
|
+
*
|
|
224
|
+
* Any contested field outside both shapes — a spec field, an app label —
|
|
225
|
+
* keeps the presented refusal: that IS a dispute with another tool.
|
|
226
|
+
*/
|
|
227
|
+
function isChantSelfConflict(err: unknown): boolean {
|
|
228
|
+
const e = err as ConflictErrorLike;
|
|
229
|
+
if (!e || e.name !== "FieldManagerConflictError" || !Array.isArray(e.conflicts) || e.conflicts.length === 0) return false;
|
|
230
|
+
return e.conflicts.every((c) => {
|
|
231
|
+
const chantManager = typeof c.manager === "string" && (c.manager === "chant" || c.manager.startsWith("chant:"));
|
|
232
|
+
const markerField = typeof c.field === "string" && c.field.startsWith(MARKER_FIELD_PREFIX);
|
|
233
|
+
return chantManager || markerField;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
194
237
|
/**
|
|
195
238
|
* Stamp the apply's own stack identity onto a document's labels.
|
|
196
239
|
*
|
|
@@ -257,12 +300,26 @@ export async function applyManifest(
|
|
|
257
300
|
|
|
258
301
|
const applied: AppliedRef[] = [];
|
|
259
302
|
for (const document of documents) {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
303
|
+
const stamped = stampOwnership(document as K8sObject, stack);
|
|
304
|
+
let result: K8sObject;
|
|
305
|
+
try {
|
|
306
|
+
result = await client.apply(stamped, {
|
|
307
|
+
fieldManager,
|
|
308
|
+
force: args.force ?? false,
|
|
309
|
+
dryRun: args.dryRun,
|
|
310
|
+
signal,
|
|
311
|
+
});
|
|
312
|
+
} catch (err) {
|
|
313
|
+
// "chant never forces a conflict on its own" is about taking fields
|
|
314
|
+
// from ANOTHER tool. A conflict where every contested field is owned
|
|
315
|
+
// by another `chant:*` manager is chant contesting itself — the
|
|
316
|
+
// ownership-stack → unit-stack label migration, or a renamed deploy
|
|
317
|
+
// unit — and refusing that forever would strand every estate applied
|
|
318
|
+
// before the rename with no non-force path back. Retake those fields
|
|
319
|
+
// deliberately, once, and only when no foreign manager is involved.
|
|
320
|
+
if (!isChantSelfConflict(err)) throw err;
|
|
321
|
+
result = await client.apply(stamped, { fieldManager, force: true, dryRun: args.dryRun, signal });
|
|
322
|
+
}
|
|
266
323
|
const ref: AppliedRef = {
|
|
267
324
|
apiVersion: String(result.apiVersion ?? document.apiVersion ?? ""),
|
|
268
325
|
kind: String(result.kind ?? document.kind ?? ""),
|