@intentius/chant-lexicon-k8s 0.124.30 → 0.124.32
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/actions/apps.js +18 -0
- package/dist/actions/batch.js +6 -0
- package/dist/actions/core.js +48 -0
- package/dist/actions/index.js +13 -0
- package/dist/actions/networking.js +11 -0
- package/dist/actions/rbac.js +8 -0
- package/dist/api/classify.js +123 -0
- package/dist/api/connect.js +62 -0
- package/dist/api/fake-cluster.js +175 -0
- package/dist/api/operation-surface.d.ts.map +1 -1
- package/dist/api/operation-surface.js +109 -0
- package/dist/api/owner-chain.js +100 -0
- package/dist/api/sweep-types.js +35 -0
- package/dist/behaviour-kinds.js +82 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +146 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +74 -0
- package/dist/codegen/generate-list-map-keys.js +88 -0
- package/dist/codegen/generate-operations.js +53 -0
- package/dist/codegen/generate-typescript.js +124 -0
- package/dist/codegen/generate.js +173 -0
- package/dist/codegen/naming.js +174 -0
- package/dist/codegen/package.js +36 -0
- package/dist/codegen/patches.js +88 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/components/argo-app.js +51 -0
- package/dist/components/builders.js +22 -0
- package/dist/components/capability-plugin.js +31 -0
- package/dist/components/flux-reconcile.js +61 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +18 -0
- package/dist/components/kubectl-apply.d.ts +83 -1
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +274 -0
- package/dist/components/kustomize-apply.js +25 -0
- package/dist/composites/adot-collector.js +184 -0
- package/dist/composites/agic-ingress.js +85 -0
- package/dist/composites/aks-external-dns-agent.js +146 -0
- package/dist/composites/alb-ingress.js +86 -0
- package/dist/composites/argo-app.js +199 -0
- package/dist/composites/autoscaled-service.js +174 -0
- package/dist/composites/azure-disk-storage-class.js +47 -0
- package/dist/composites/azure-file-storage-class.js +48 -0
- package/dist/composites/azure-monitor-collector.js +176 -0
- package/dist/composites/batch-job.js +139 -0
- package/dist/composites/catalog.js +4110 -0
- package/dist/composites/cockroachdb-cluster.js +371 -0
- package/dist/composites/cockroachdb-region-stack.js +369 -0
- package/dist/composites/config-connector-context.js +40 -0
- package/dist/composites/configured-app.js +135 -0
- package/dist/composites/cron-schedule.js +38 -0
- package/dist/composites/cron-workload.js +109 -0
- package/dist/composites/ebs-storage-class.js +54 -0
- package/dist/composites/efs-storage-class.js +46 -0
- package/dist/composites/external-dns-agent.js +122 -0
- package/dist/composites/filestore-storage-class.js +49 -0
- package/dist/composites/fluent-bit-agent.js +157 -0
- package/dist/composites/flux-app.js +156 -0
- package/dist/composites/gce-ingress.js +83 -0
- package/dist/composites/gce-pd-storage-class.js +49 -0
- package/dist/composites/gke-external-dns-agent.js +129 -0
- package/dist/composites/gke-fluent-bit-agent.js +165 -0
- package/dist/composites/gke-gateway.js +91 -0
- package/dist/composites/gke-otel-collector.js +94 -0
- package/dist/composites/index.js +51 -0
- package/dist/composites/inference-service.js +76 -0
- package/dist/composites/irsa-service-account.js +77 -0
- package/dist/composites/member-defaults.js +14 -0
- package/dist/composites/metrics-server.js +181 -0
- package/dist/composites/model.js +92 -0
- package/dist/composites/monitored-service.js +152 -0
- package/dist/composites/namespace-env.js +114 -0
- package/dist/composites/network-isolated-app.js +124 -0
- package/dist/composites/node-agent.js +149 -0
- package/dist/composites/operator-stack.js +268 -0
- package/dist/composites/otel-collector-agent.js +142 -0
- package/dist/composites/otel-collector-gateway.js +216 -0
- package/dist/composites/otel-collector-node.js +176 -0
- package/dist/composites/otel-collector-rbac.js +115 -0
- package/dist/composites/otel-collector-shape.js +128 -0
- package/dist/composites/otel-collector.js +115 -0
- package/dist/composites/otel-operator-collector.js +123 -0
- package/dist/composites/ray-cluster.js +378 -0
- package/dist/composites/ray-job.js +136 -0
- package/dist/composites/ray-service.js +178 -0
- package/dist/composites/secure-ingress.js +100 -0
- package/dist/composites/security-context.js +1 -0
- package/dist/composites/sidecar-app.js +105 -0
- package/dist/composites/stateful-app.js +111 -0
- package/dist/composites/vllm-serving-runtime.js +114 -0
- package/dist/composites/web-app.js +157 -0
- package/dist/composites/worker-pool.js +167 -0
- package/dist/composites/workload-identity-sa.js +81 -0
- package/dist/composites/workload-identity-service-account.js +77 -0
- package/dist/config-schema.js +72 -0
- package/dist/config.js +59 -0
- package/dist/coverage.js +27 -0
- package/dist/crd/crd-sources.js +615 -0
- package/dist/crd/loader.js +181 -0
- package/dist/crd/oci-chart.js +144 -0
- package/dist/crd/parser.js +382 -0
- package/dist/crd/project-codegen.js +273 -0
- package/dist/crd/types.js +7 -0
- package/dist/deep-observe-hooks.js +306 -0
- package/dist/deep-observe.js +234 -0
- package/dist/default-labels.js +87 -0
- package/dist/describe-resources.js +699 -0
- package/dist/describe-stack-status.js +99 -0
- package/dist/detect.js +11 -0
- package/dist/effect-receipt-row.js +205 -0
- package/dist/export-resources.js +39 -0
- package/dist/generated/index.js +1092 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/group-namespace.js +96 -0
- package/dist/import/embedded.js +197 -0
- package/dist/import/generator.js +288 -0
- package/dist/import/live-export.js +85 -0
- package/dist/import/parser.js +204 -0
- package/dist/import/testdata/embedded/fixtures.js +60 -0
- package/dist/include/entity.js +78 -0
- package/dist/include/resolve.js +170 -0
- package/dist/index.js +59 -0
- package/dist/integrity.json +3 -3
- package/dist/kube/apply.js +90 -0
- package/dist/kube/connect.js +46 -0
- package/dist/kube/delete.js +138 -0
- package/dist/kube/describe.js +169 -0
- package/dist/kube/events.js +132 -0
- package/dist/kube/flags.js +91 -0
- package/dist/kube/get.js +159 -0
- package/dist/kube/group.js +123 -0
- package/dist/kube/logs.js +82 -0
- package/dist/kube/project.js +94 -0
- package/dist/kube/render.js +227 -0
- package/dist/kube/source.js +122 -0
- package/dist/kube/target.js +63 -0
- package/dist/kube/testing.js +28 -0
- package/dist/kube/top.js +99 -0
- package/dist/kube/verdict.js +72 -0
- package/dist/kube/wait.js +129 -0
- package/dist/kustomize/render.js +73 -0
- package/dist/kustomize/rendered-entity.js +67 -0
- package/dist/kustomize/root.js +68 -0
- package/dist/lint/audit-catalog.js +61 -0
- package/dist/lint/audit-lineage.js +118 -0
- package/dist/lint/post-synth/argo-helpers.js +36 -0
- package/dist/lint/post-synth/argo002.js +31 -0
- package/dist/lint/post-synth/argo003.js +53 -0
- package/dist/lint/post-synth/argo005.js +54 -0
- package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
- package/dist/lint/post-synth/flux002.js +56 -0
- package/dist/lint/post-synth/flux003.js +50 -0
- package/dist/lint/post-synth/index.js +104 -0
- package/dist/lint/post-synth/k8s-helpers.js +186 -0
- package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
- package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
- package/dist/lint/post-synth/sops-helpers.js +43 -0
- package/dist/lint/post-synth/wk8005.js +44 -0
- package/dist/lint/post-synth/wk8006.js +56 -0
- package/dist/lint/post-synth/wk8041.js +60 -0
- package/dist/lint/post-synth/wk8042.js +36 -0
- package/dist/lint/post-synth/wk8101.js +52 -0
- package/dist/lint/post-synth/wk8102.js +30 -0
- package/dist/lint/post-synth/wk8103.js +33 -0
- package/dist/lint/post-synth/wk8104.js +58 -0
- package/dist/lint/post-synth/wk8105.js +33 -0
- package/dist/lint/post-synth/wk8201.js +44 -0
- package/dist/lint/post-synth/wk8202.js +34 -0
- package/dist/lint/post-synth/wk8203.js +34 -0
- package/dist/lint/post-synth/wk8204.js +71 -0
- package/dist/lint/post-synth/wk8205.js +38 -0
- package/dist/lint/post-synth/wk8207.js +33 -0
- package/dist/lint/post-synth/wk8208.js +33 -0
- package/dist/lint/post-synth/wk8209.js +33 -0
- package/dist/lint/post-synth/wk8301.js +49 -0
- package/dist/lint/post-synth/wk8302.js +34 -0
- package/dist/lint/post-synth/wk8303.js +68 -0
- package/dist/lint/post-synth/wk8304.js +58 -0
- package/dist/lint/post-synth/wk8305.js +96 -0
- package/dist/lint/post-synth/wk8306.js +38 -0
- package/dist/lint/post-synth/wk8401.js +86 -0
- package/dist/lint/post-synth/wk8402.js +31 -0
- package/dist/lint/post-synth/wk8403.js +49 -0
- package/dist/lint/post-synth/wk8404.js +36 -0
- package/dist/lint/post-synth/wk8405.js +65 -0
- package/dist/lint/post-synth/wk8406.js +50 -0
- package/dist/lint/post-synth/wk8407.js +58 -0
- package/dist/lint/post-synth/wk8501.js +36 -0
- package/dist/lint/post-synth/wk8502.js +35 -0
- package/dist/lint/post-synth/wk8503.js +164 -0
- package/dist/lint/post-synth/wk8504.js +20 -0
- package/dist/lint/post-synth/wk8505.js +31 -0
- package/dist/lint/post-synth/wk8601.js +40 -0
- package/dist/lint/post-synth/wk8602.js +120 -0
- package/dist/lint/post-synth/wk8603.js +63 -0
- package/dist/lint/post-synth/wk8604.js +31 -0
- package/dist/lint/post-synth/wk8605.js +124 -0
- package/dist/lint/post-synth/wk8606.js +22 -0
- package/dist/lint/post-synth/wk8701.js +31 -0
- package/dist/lint/post-synth/wk8702.js +37 -0
- package/dist/lint/post-synth/wk8703.js +31 -0
- package/dist/lint/post-synth/wk8704.js +135 -0
- package/dist/lint/post-synth/wk8705.js +38 -0
- package/dist/lint/rules/argo-appset-single-project.js +56 -0
- package/dist/lint/rules/argo-ast.js +94 -0
- package/dist/lint/rules/argo-automated-prune.js +59 -0
- package/dist/lint/rules/flux-source-ref-pin.js +44 -0
- package/dist/lint/rules/hardcoded-namespace.js +57 -0
- package/dist/lint/rules/latest-image-tag.js +103 -0
- package/dist/lint/rules/missing-resource-limits.js +85 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +57 -0
- package/dist/manifest-entity.js +71 -0
- package/dist/manifest.json +1 -1
- package/dist/micro-time.js +49 -0
- package/dist/op/activities/apply-diff.d.ts +31 -0
- package/dist/op/activities/apply-diff.d.ts.map +1 -0
- package/dist/op/activities/apply-diff.js +79 -0
- package/dist/op/activities/argo.js +98 -0
- package/dist/op/activities/ensure-secret.js +38 -0
- package/dist/op/activities/index.js +51 -0
- package/dist/op/activities/kubectl.d.ts +64 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/kubectl.js +517 -0
- package/dist/op/activities/wait-for-ready.js +229 -0
- package/dist/op/builders.js +68 -0
- package/dist/package-cli.js +21 -0
- package/dist/patch/json-patch.js +187 -0
- package/dist/patch/rendered-patch.js +94 -0
- package/dist/plugin.js +741 -0
- package/dist/project-kinds.js +56 -0
- package/dist/receipt-store.js +216 -0
- package/dist/rules/crd-schema-helpers.ts +2 -1
- package/dist/secret-labels.js +33 -0
- package/dist/secret-store.js +116 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +578 -0
- package/dist/sops/encrypted-secret-file.js +194 -0
- package/dist/sops/entity.js +32 -0
- package/dist/spec/fetch.js +58 -0
- package/dist/spec/parse.js +622 -0
- package/dist/subscribe-changes.js +168 -0
- package/dist/teardown.js +197 -0
- package/dist/telemetry-release.js +86 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +47 -0
- package/dist/variables.js +34 -0
- package/dist/whoami.js +109 -0
- package/package.json +12 -12
- package/src/api/operation-surface.ts +2 -1
- package/src/codegen/package.ts +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
- package/src/components/kubectl-apply.test.ts +262 -0
- package/src/components/kubectl-apply.ts +341 -12
- package/src/deep-observe-hooks.ts +2 -2
- package/src/import/roundtrip-build.test.ts +46 -0
- package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/op/activities/apply-diff.ts +87 -0
- package/src/op/activities/kubectl-plan.test.ts +224 -0
- package/src/op/activities/kubectl.ts +204 -16
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
|
|
2
|
+
import { defaultK8sConnector } from "./api/connect.js";
|
|
3
|
+
/** The kinds a chant-applied stack's objects land as — the sweep's bound. */
|
|
4
|
+
const SWEEP_KINDS = [
|
|
5
|
+
{ apiVersion: "apps/v1", kind: "Deployment" },
|
|
6
|
+
{ apiVersion: "apps/v1", kind: "StatefulSet" },
|
|
7
|
+
{ apiVersion: "apps/v1", kind: "DaemonSet" },
|
|
8
|
+
{ apiVersion: "v1", kind: "Service" },
|
|
9
|
+
{ apiVersion: "v1", kind: "ConfigMap" },
|
|
10
|
+
{ apiVersion: "v1", kind: "Secret" },
|
|
11
|
+
{ apiVersion: "v1", kind: "ServiceAccount" },
|
|
12
|
+
{ apiVersion: "v1", kind: "Namespace" },
|
|
13
|
+
{ apiVersion: "networking.k8s.io/v1", kind: "Ingress" },
|
|
14
|
+
{ apiVersion: "policy/v1", kind: "PodDisruptionBudget" },
|
|
15
|
+
{ apiVersion: "autoscaling/v2", kind: "HorizontalPodAutoscaler" },
|
|
16
|
+
{ apiVersion: "batch/v1", kind: "CronJob" },
|
|
17
|
+
// The definitions themselves: a crds unit deploys these and nothing else.
|
|
18
|
+
{ apiVersion: "apiextensions.k8s.io/v1", kind: "CustomResourceDefinition" },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Every kind the cluster's CRDs declare, one served version each (storage
|
|
22
|
+
* preferred). Read unfiltered — see the module doc. Failure to read the CRD
|
|
23
|
+
* list narrows the sweep back to the fixed bound rather than failing it.
|
|
24
|
+
*/
|
|
25
|
+
async function crdBackedKinds(client) {
|
|
26
|
+
let crds;
|
|
27
|
+
try {
|
|
28
|
+
crds = await client.list({ apiVersion: "apiextensions.k8s.io/v1", kind: "CustomResourceDefinition" });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const kinds = [];
|
|
34
|
+
for (const crd of crds) {
|
|
35
|
+
const spec = crd.spec;
|
|
36
|
+
const group = spec?.group;
|
|
37
|
+
const kind = spec?.names?.kind;
|
|
38
|
+
const versions = spec?.versions ?? [];
|
|
39
|
+
const version = versions.find((v) => v.storage && v.served) ?? versions.find((v) => v.served);
|
|
40
|
+
if (!group || !kind || !version)
|
|
41
|
+
continue;
|
|
42
|
+
kinds.push({ apiVersion: `${group}/${version.name}`, kind });
|
|
43
|
+
}
|
|
44
|
+
return kinds;
|
|
45
|
+
}
|
|
46
|
+
/** A workload is healthy when its controller reports every replica ready; a
|
|
47
|
+
* kind with no readiness story (a ConfigMap, a Service) asserts nothing. */
|
|
48
|
+
function objectHealthy(obj) {
|
|
49
|
+
const status = obj.status;
|
|
50
|
+
if (!status)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (typeof status.replicas === "number") {
|
|
53
|
+
return (typeof status.readyReplicas === "number" ? status.readyReplicas : 0) >= status.replicas;
|
|
54
|
+
}
|
|
55
|
+
if (typeof status.desiredNumberScheduled === "number") {
|
|
56
|
+
return (typeof status.numberReady === "number" ? status.numberReady : 0) >= status.desiredNumberScheduled;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
export async function describeStackStatus(options, connect = defaultK8sConnector) {
|
|
61
|
+
const selector = `${LABEL_OWNERSHIP_KEYS.managedBy}=${OWNERSHIP_MANAGED_BY_VALUE},` +
|
|
62
|
+
`${LABEL_OWNERSHIP_KEYS.stack}=${options.stack}`;
|
|
63
|
+
let client;
|
|
64
|
+
try {
|
|
65
|
+
({ client } = await connect({ environment: options.environment }));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// No cluster binding / unreadable kubeconfig — indeterminate, never a
|
|
69
|
+
// confident "not there".
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const matched = [];
|
|
73
|
+
let anyRead = false;
|
|
74
|
+
const targets = [...SWEEP_KINDS, ...(await crdBackedKinds(client))];
|
|
75
|
+
for (const target of targets) {
|
|
76
|
+
try {
|
|
77
|
+
const items = await client.list(target, { labelSelector: selector });
|
|
78
|
+
anyRead = true;
|
|
79
|
+
matched.push(...items);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// One kind's list failing (RBAC, an aggregated API down) proves nothing
|
|
83
|
+
// about the others; keep sweeping.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!anyRead)
|
|
87
|
+
return null; // every read failed — indeterminate
|
|
88
|
+
if (matched.length === 0)
|
|
89
|
+
return { stack: options.stack, present: false };
|
|
90
|
+
const verdicts = matched.map(objectHealthy).filter((v) => v !== undefined);
|
|
91
|
+
const healthy = verdicts.length > 0 ? verdicts.every(Boolean) : true;
|
|
92
|
+
const ready = verdicts.filter(Boolean).length;
|
|
93
|
+
return {
|
|
94
|
+
stack: options.stack,
|
|
95
|
+
present: true,
|
|
96
|
+
status: verdicts.length > 0 ? `${ready}/${verdicts.length} workloads ready` : `${matched.length} objects present`,
|
|
97
|
+
healthy,
|
|
98
|
+
};
|
|
99
|
+
}
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Edge-safe template detection for k8s — extracted from plugin.ts so it imports
|
|
2
|
+
* without the lint rules (and the TypeScript compiler they pull in). See #426. */
|
|
3
|
+
export function detectTemplate(data) {
|
|
4
|
+
if (typeof data !== "object" || data === null)
|
|
5
|
+
return false;
|
|
6
|
+
const obj = data;
|
|
7
|
+
// K8s manifests have apiVersion + kind
|
|
8
|
+
if (typeof obj.apiVersion === "string" && typeof obj.kind === "string")
|
|
9
|
+
return true;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The k8s effect-receipt materialization row (#2074, epic #1703): an effect
|
|
3
|
+
* receipt stored as a core `ConfigMap`, named
|
|
4
|
+
* `chant-receipt.<stack>.<env>.<effect>`, holding the expectation under
|
|
5
|
+
* `data.expectation`.
|
|
6
|
+
*
|
|
7
|
+
* Core's `EffectReceipt` factory (#1831) declares a receipt under the `chant`
|
|
8
|
+
* pseudo-lexicon, which no serializer claims. This module is the k8s
|
|
9
|
+
* materialization, built to the same shape as the aws SSM row (#1835,
|
|
10
|
+
* `lexicons/aws/src/effect-receipt-row.ts`): the {@link EffectReceipt} factory
|
|
11
|
+
* here produces core's declaration under `lexicon: "k8s"`, so the build
|
|
12
|
+
* partitions it to the k8s serializer, #1832's write-exclusion seam withholds
|
|
13
|
+
* it from the apply-bound entity set, and the serializer renders it for
|
|
14
|
+
* visibility through `SerializeContext.receipts` (see ./serializer.ts).
|
|
15
|
+
*
|
|
16
|
+
* Where the aws row parks its rendered rows in the CloudFormation template's
|
|
17
|
+
* `Metadata` (deliberately outside `Resources`, the section an applier writes
|
|
18
|
+
* from), this row parks them in a YAML COMMENT at the end of the manifest
|
|
19
|
+
* stream. Kubernetes YAML has no metadata channel outside the documents
|
|
20
|
+
* themselves, and a document is exactly what an applier applies: `loadAll`
|
|
21
|
+
* yields no object for a comment, so the receipt block is structurally
|
|
22
|
+
* unreachable from `applyManifest` while still riding the one build output the
|
|
23
|
+
* observation leg is handed. See {@link EFFECT_RECEIPTS_COMMENT_MARKER}.
|
|
24
|
+
*
|
|
25
|
+
* ## Name and namespace
|
|
26
|
+
*
|
|
27
|
+
* Path identity (epic decision 4): the ConfigMap name derives from the SAME
|
|
28
|
+
* ownership-block fields that stamp markers, `ownership.stack` and an explicit
|
|
29
|
+
* `ownership.env`, plus the receipt's `effect`. The separator is `.`, which no
|
|
30
|
+
* segment may contain, so `stack=a-b env=c` and `stack=a env=b-c` cannot
|
|
31
|
+
* produce the same name. The result is a valid RFC 1123 DNS subdomain, which
|
|
32
|
+
* is what a ConfigMap name has to be.
|
|
33
|
+
*
|
|
34
|
+
* The namespace is the project's `k8s.receipts.namespace`, and `default` when
|
|
35
|
+
* the project sets none, which is where every other namespace-less k8s read
|
|
36
|
+
* and write in this lexicon already lands. It is never invented from the stack
|
|
37
|
+
* or the environment: a namespace chant guessed would be a namespace chant has
|
|
38
|
+
* to create, and the receipt row creates nothing but the receipt.
|
|
39
|
+
*
|
|
40
|
+
* ## Ownership, and why the prune leaves it alone
|
|
41
|
+
*
|
|
42
|
+
* Every receipt ConfigMap carries chant's ownership marker labels plus
|
|
43
|
+
* {@link RECEIPT_LABEL_KEY}. The marker is what makes `chant kube get` and the
|
|
44
|
+
* observation classify it as chant's rather than foreign. The receipt label is
|
|
45
|
+
* what keeps `delete: "owned-only"` from pruning it: the receipt is owned and
|
|
46
|
+
* is never in any apply set (the effect step is its sole writer), which is the
|
|
47
|
+
* exact shape the owned-only sweep deletes. `pruneOrphans`
|
|
48
|
+
* (./op/activities/kubectl.ts) reports it `retained` instead, the same
|
|
49
|
+
* treatment a generated-once Secret gets (./secret-labels.ts) and for the same
|
|
50
|
+
* reason: destroying it silently converts at-least-once into a re-run nobody
|
|
51
|
+
* asked for, or worse, into never.
|
|
52
|
+
*
|
|
53
|
+
* Plain store (#1833's COR023): a ConfigMap is the plain half of the
|
|
54
|
+
* ConfigMap/Secret pair, and the entityType alone is what the guard reads. A
|
|
55
|
+
* receipt value is a witness (an existence marker or a `sha256:` digest),
|
|
56
|
+
* never a secret; materializing one into a `K8s::Core::Secret` fails COR023.
|
|
57
|
+
*/
|
|
58
|
+
import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
|
|
59
|
+
import { EffectReceipt as CoreEffectReceipt, EFFECT_RECEIPT_MARKER, } from "@intentius/chant/effect-receipt";
|
|
60
|
+
/** The entityType of the k8s materialization row, the real resource kind the
|
|
61
|
+
* receipt is stored as, which is what lint's plain-store guard checks. */
|
|
62
|
+
export const K8S_EFFECT_RECEIPT_ENTITY_TYPE = "K8s::Core::ConfigMap";
|
|
63
|
+
/** apiVersion/kind of the materialized row, for the client reads and writes. */
|
|
64
|
+
export const RECEIPT_CONFIGMAP_REF = { apiVersion: "v1", kind: "ConfigMap" };
|
|
65
|
+
/**
|
|
66
|
+
* The line prefix the serializer renders receipt rows behind, and the
|
|
67
|
+
* observation leg reads them back from. A YAML comment: an applier's `loadAll`
|
|
68
|
+
* produces no document for it, so a receipt can never enter an apply set, and
|
|
69
|
+
* `kubectl apply -f` ignores it exactly as it ignores every other comment.
|
|
70
|
+
* The remainder of the line is one JSON object keyed by entity name.
|
|
71
|
+
*/
|
|
72
|
+
export const EFFECT_RECEIPTS_COMMENT_MARKER = "# chant:effect-receipts ";
|
|
73
|
+
/** First segment of every receipt ConfigMap name. */
|
|
74
|
+
export const RECEIPT_NAME_PREFIX = "chant-receipt";
|
|
75
|
+
/** The `data` key the expectation is stored under. One key, the same single
|
|
76
|
+
* value the aws row puts in the SSM parameter's `Value`. */
|
|
77
|
+
export const RECEIPT_DATA_KEY = "expectation";
|
|
78
|
+
/** The label a live receipt ConfigMap carries, valued with the effect it
|
|
79
|
+
* witnesses. Recognition for the observation leg and, more importantly, the
|
|
80
|
+
* exclusion the owned-only prune keys on. */
|
|
81
|
+
export const RECEIPT_LABEL_KEY = "chant.intentius.io/effect-receipt";
|
|
82
|
+
/** Where receipts live when the project names no namespace. */
|
|
83
|
+
export const RECEIPT_DEFAULT_NAMESPACE = "default";
|
|
84
|
+
/**
|
|
85
|
+
* The rendered value of a hash-flavor receipt that still carries reference
|
|
86
|
+
* inputs at synthesis. References resolve at plan and at run, never at
|
|
87
|
+
* synthesis (epic decision 5), so the row carries this note instead of a
|
|
88
|
+
* digest hashed over placeholders.
|
|
89
|
+
*/
|
|
90
|
+
export const RECEIPT_UNRESOLVED_VALUE_NOTE = "unresolved at synthesis, reference inputs; the expectation resolves at plan and at run (chant #1703, decision 5)";
|
|
91
|
+
/** One segment of the receipt name. RFC 1123 DNS label, which is what each
|
|
92
|
+
* dot-separated piece of a ConfigMap name has to be, and the `.` separator is
|
|
93
|
+
* excluded from it so the identity stays unambiguous. */
|
|
94
|
+
const NAME_SEGMENT = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
|
|
95
|
+
/** Kubernetes' own ceiling on a DNS subdomain, which a ConfigMap name is. */
|
|
96
|
+
const MAX_NAME_LENGTH = 253;
|
|
97
|
+
function checkSegment(field, value) {
|
|
98
|
+
if (value.length > 63 || !NAME_SEGMENT.test(value)) {
|
|
99
|
+
throw new Error(`receipt name: ${field} "${value}" is not a valid DNS-1123 label, ` +
|
|
100
|
+
`use lowercase letters, digits and "-" (the segment becomes one dot-separated piece of ` +
|
|
101
|
+
`${RECEIPT_NAME_PREFIX}.<stack>.<env>.<effect>, which must be a valid ConfigMap name)`);
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The ConfigMap name of one effect's receipt:
|
|
107
|
+
* `chant-receipt.<stack>.<env>.<effect>`, from the resolved ownership marker
|
|
108
|
+
* fields (epic decision 4). The single source of the name identity: the
|
|
109
|
+
* serializer's rendered row, the receipt store's reads and writes, and the
|
|
110
|
+
* observation leg all call this.
|
|
111
|
+
*/
|
|
112
|
+
export function receiptConfigMapName(stack, env, effect) {
|
|
113
|
+
const name = `${RECEIPT_NAME_PREFIX}.${checkSegment("stack", stack)}.${checkSegment("env", env)}.${checkSegment("effect", effect)}`;
|
|
114
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
115
|
+
throw new Error(`receipt name: "${name}" is ${name.length} characters, over Kubernetes' ${MAX_NAME_LENGTH}-character ` +
|
|
116
|
+
`limit for a ConfigMap name. Shorten the stack, the environment, or the effect.`);
|
|
117
|
+
}
|
|
118
|
+
return name;
|
|
119
|
+
}
|
|
120
|
+
/** The project's receipt namespace: `k8s.receipts.namespace`, else `default`.
|
|
121
|
+
* The serializer reads it off `SerializeContext.config` and the store reads it
|
|
122
|
+
* off the project config, so both derive one namespace from one setting. */
|
|
123
|
+
export function receiptNamespaceFrom(config) {
|
|
124
|
+
const k8s = config?.k8s;
|
|
125
|
+
const declared = k8s?.receipts?.namespace;
|
|
126
|
+
return typeof declared === "string" && declared.length > 0 ? declared : RECEIPT_DEFAULT_NAMESPACE;
|
|
127
|
+
}
|
|
128
|
+
/** The full address of one effect's receipt ConfigMap. */
|
|
129
|
+
export function receiptConfigMapRef(stack, env, effect, namespace = RECEIPT_DEFAULT_NAMESPACE) {
|
|
130
|
+
return { name: receiptConfigMapName(stack, env, effect), namespace };
|
|
131
|
+
}
|
|
132
|
+
/** True when a live object's labels mark it a chant effect receipt. Any
|
|
133
|
+
* non-empty value counts: the label's presence is the claim, and a sweep must
|
|
134
|
+
* err on the side of keeping. */
|
|
135
|
+
export function isEffectReceiptObject(labels) {
|
|
136
|
+
const value = labels?.[RECEIPT_LABEL_KEY];
|
|
137
|
+
return typeof value === "string" && value.length > 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Render the receipt block the serializer appends to the manifest stream.
|
|
141
|
+
* Deterministic: one line, entity names sorted.
|
|
142
|
+
*/
|
|
143
|
+
export function renderReceiptComment(rows) {
|
|
144
|
+
const sorted = {};
|
|
145
|
+
for (const name of Object.keys(rows).sort())
|
|
146
|
+
sorted[name] = rows[name];
|
|
147
|
+
return `${EFFECT_RECEIPTS_COMMENT_MARKER}${JSON.stringify(sorted)}`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Read the receipt rows back out of a build output. Returns an empty record
|
|
151
|
+
* for an output that carries no block, which is every project that declares no
|
|
152
|
+
* receipt. Never throws: an unparseable block is no block, and the observation
|
|
153
|
+
* leg reports the receipts it could not address as absent rather than
|
|
154
|
+
* inventing one.
|
|
155
|
+
*/
|
|
156
|
+
export function parseReceiptComment(buildOutput) {
|
|
157
|
+
const start = buildOutput.lastIndexOf(EFFECT_RECEIPTS_COMMENT_MARKER);
|
|
158
|
+
if (start < 0)
|
|
159
|
+
return {};
|
|
160
|
+
const end = buildOutput.indexOf("\n", start);
|
|
161
|
+
const line = buildOutput.slice(start + EFFECT_RECEIPTS_COMMENT_MARKER.length, end < 0 ? undefined : end);
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(line);
|
|
164
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
165
|
+
return {};
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Declare a k8s-materialized effect receipt. Same signature and semantics as
|
|
174
|
+
* core's `EffectReceipt` (#1831), whose factory validates and freezes the
|
|
175
|
+
* options, but the declaration lands in the k8s partition, so the k8s
|
|
176
|
+
* serializer renders the ConfigMap row and the receipt store
|
|
177
|
+
* (./receipt-store.ts) is its writer. Pass the returned const straight to the
|
|
178
|
+
* `effect(...)` op step.
|
|
179
|
+
*/
|
|
180
|
+
export function EffectReceipt(name, options) {
|
|
181
|
+
// Fail at declaration, not at serialize: the effect is the name's final
|
|
182
|
+
// segment, and a name that cannot become a segment has no receipt address.
|
|
183
|
+
if (typeof options?.effect === "string" && options.effect.length > 0) {
|
|
184
|
+
checkSegment("effect", options.effect);
|
|
185
|
+
}
|
|
186
|
+
const core = CoreEffectReceipt(name, options);
|
|
187
|
+
const decl = {
|
|
188
|
+
[DECLARABLE_MARKER]: true,
|
|
189
|
+
[EFFECT_RECEIPT_MARKER]: true,
|
|
190
|
+
lexicon: "k8s",
|
|
191
|
+
entityType: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
|
|
192
|
+
name: core.name,
|
|
193
|
+
effect: core.effect,
|
|
194
|
+
flavor: core.flavor,
|
|
195
|
+
// The same frozen structure the core factory built: intrinsic inputs stay
|
|
196
|
+
// live so discovery can stamp logical names onto attr-refs.
|
|
197
|
+
inputs: core.inputs,
|
|
198
|
+
};
|
|
199
|
+
// Declared fields immutable, object extensible for discovery's own
|
|
200
|
+
// symbol-keyed metadata, the same lock the core factory applies.
|
|
201
|
+
for (const key of Object.keys(decl)) {
|
|
202
|
+
Object.defineProperty(decl, key, { writable: false, configurable: false });
|
|
203
|
+
}
|
|
204
|
+
return decl;
|
|
205
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defaultK8sConnector } from "./api/connect.js";
|
|
2
|
+
import { operationFor } from "./api/operation-surface.js";
|
|
3
|
+
import { buildExportFromObjects } from "./import/live-export.js";
|
|
4
|
+
import { DEFAULT_IMPORT_TYPES } from "./api/sweep-types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Entity types a bare `chant import --from <cluster>` sweeps: the workload,
|
|
7
|
+
* config, networking and RBAC kinds people actually author. Everything else is
|
|
8
|
+
* reachable by naming it with `--selector type=...`. Defined in
|
|
9
|
+
* `./api/sweep-types.ts` and re-exported here, its original home — chant
|
|
10
|
+
* #1075's ownership-scoped prune needs the same list without pulling the
|
|
11
|
+
* import parser in behind the apply activity.
|
|
12
|
+
*/
|
|
13
|
+
export { DEFAULT_IMPORT_TYPES };
|
|
14
|
+
export async function exportResources(options, connect = defaultK8sConnector) {
|
|
15
|
+
const types = options.selector?.type ? [options.selector.type] : DEFAULT_IMPORT_TYPES;
|
|
16
|
+
const operations = types.map((t) => operationFor(t)).filter((o) => o !== undefined);
|
|
17
|
+
if (operations.length === 0) {
|
|
18
|
+
return { resources: [], parameters: [] };
|
|
19
|
+
}
|
|
20
|
+
const { client } = await connect({ environment: options.environment, cwd: options.cwd });
|
|
21
|
+
// One List per kind keeps parsing simple and isolates a missing-kind error
|
|
22
|
+
// to that kind rather than failing the whole export. They now run
|
|
23
|
+
// concurrently instead of serially.
|
|
24
|
+
const perKind = await client.concurrently(operations, async (operation) => {
|
|
25
|
+
try {
|
|
26
|
+
return await client.list({ apiVersion: operation.apiVersion, kind: operation.kind });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Kind not served by this cluster / RBAC denied — skip it, don't fail
|
|
30
|
+
// the whole export.
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return buildExportFromObjects(perKind.flat(), {
|
|
35
|
+
verbatim: options.verbatim,
|
|
36
|
+
selector: options.selector,
|
|
37
|
+
owned: options.owned,
|
|
38
|
+
});
|
|
39
|
+
}
|