@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,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinds a project generated for itself with `chant generate` from `k8s.crds`
|
|
3
|
+
* (./crd/project-codegen.ts), as opposed to the kinds the lexicon ships in
|
|
4
|
+
* `generated/lexicon-k8s.json`.
|
|
5
|
+
*
|
|
6
|
+
* The serializer's GVK lookup and the CRD spec checks (WK8501/WK8502) consult
|
|
7
|
+
* this registry before the packaged JSON. Entries arrive two ways:
|
|
8
|
+
*
|
|
9
|
+
* - The generated module registers its kinds (type and GVK) when it is
|
|
10
|
+
* imported, so a project's own tests that construct and serialize a
|
|
11
|
+
* resource need nothing else.
|
|
12
|
+
* - A build registers every kind, spec schema included, from the committed
|
|
13
|
+
* `kinds.json` beside the module before it serializes (the plugin's
|
|
14
|
+
* `projectCodegen().load`). That covers a build whose discovery imported the
|
|
15
|
+
* module somewhere else, such as the sandboxed child process.
|
|
16
|
+
*
|
|
17
|
+
* The store hangs off `globalThis` under a registered symbol, so two copies
|
|
18
|
+
* of this module (the plugin the CLI loaded and the one a project's import
|
|
19
|
+
* resolved) share it. No Node imports: the post-synth barrel reaches this
|
|
20
|
+
* through ./lint/post-synth/crd-schema-helpers.ts and must stay edge-safe.
|
|
21
|
+
*/
|
|
22
|
+
const STORE = Symbol.for("@intentius/chant-lexicon-k8s/project-kinds");
|
|
23
|
+
function store() {
|
|
24
|
+
const g = globalThis;
|
|
25
|
+
g[STORE] ?? (g[STORE] = new Map());
|
|
26
|
+
return g[STORE];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Register project-generated kinds. A later registration of the same type
|
|
30
|
+
* replaces the earlier one, keeping its spec schema when the new entry has
|
|
31
|
+
* none (the module's import-time registration carries no schema).
|
|
32
|
+
*/
|
|
33
|
+
export function registerProjectKinds(kinds) {
|
|
34
|
+
const s = store();
|
|
35
|
+
for (const k of kinds) {
|
|
36
|
+
const previous = s.get(k.resourceType);
|
|
37
|
+
const specSchema = k.specSchema ?? (previous?.apiVersion === k.apiVersion && previous.kind === k.kind ? previous.specSchema : undefined);
|
|
38
|
+
s.set(k.resourceType, { ...k, ...(specSchema ? { specSchema } : {}) });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** The project kind an entity type constructs, if a project registered one. */
|
|
42
|
+
export function projectKindForType(resourceType) {
|
|
43
|
+
return store().get(resourceType);
|
|
44
|
+
}
|
|
45
|
+
/** The spec schema a project registered for `apiVersion`/`kind`, if any. */
|
|
46
|
+
export function projectSpecSchema(apiVersion, kind) {
|
|
47
|
+
for (const k of store().values()) {
|
|
48
|
+
if (k.apiVersion === apiVersion && k.kind === kind && k.specSchema)
|
|
49
|
+
return k.specSchema;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/** Test seam: forget every registered kind. */
|
|
54
|
+
export function clearProjectKinds() {
|
|
55
|
+
store().clear();
|
|
56
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The k8s `ReceiptStore` (#2074, epic #1703): core's injectable receipt seam
|
|
3
|
+
* (#1834, `@intentius/chant/op/receipt-store`) implemented over a core
|
|
4
|
+
* `ConfigMap`, at the address ./effect-receipt-row.ts derives from the
|
|
5
|
+
* ownership marker fields, plus the plan-side live read of the same rows.
|
|
6
|
+
*
|
|
7
|
+
* The transport is the lexicon's own typed client (./api/connect.ts), so a
|
|
8
|
+
* receipt read and write take the same environment-to-cluster binding
|
|
9
|
+
* (#1100/#1155), the same credential policy and the same field manager as
|
|
10
|
+
* every other k8s mutation. No `kubectl` binary is involved.
|
|
11
|
+
*
|
|
12
|
+
* Write discipline (epic decision 3): `write` exists for the `effect()` step
|
|
13
|
+
* alone, because the step's read-compare-run-write is the only path that reaches it,
|
|
14
|
+
* on success, last. It is a server-side apply as `chant:<stack>`, stamping the
|
|
15
|
+
* ownership marker labels and {@link RECEIPT_LABEL_KEY}, so a later write
|
|
16
|
+
* updates the value in place and the owned-only prune retains rather than
|
|
17
|
+
* deletes it (./op/activities/kubectl.ts).
|
|
18
|
+
*
|
|
19
|
+
* Identity: the ConfigMap name needs `<stack>` and `<env>`, which the activity
|
|
20
|
+
* args deliberately do not carry (the `EffectReceiptRef` is
|
|
21
|
+
* identity-of-the-effect, not identity-of-the-deployment). The store resolves
|
|
22
|
+
* them once, lazily, at first use: an explicit option, else `CHANT_ENV` (what
|
|
23
|
+
* `chant run --env` sets) and the project's `ownership` block, the same fields
|
|
24
|
+
* that stamp markers (epic decision 4). Nothing resolving is an error, never a
|
|
25
|
+
* guessed segment.
|
|
26
|
+
*
|
|
27
|
+
* Not exported from the package entry point. Like ./secret-store.ts and
|
|
28
|
+
* ./teardown.ts, this module names the API client, which must stay off the
|
|
29
|
+
* build path (chant #1074, examples/k8s-client-boundary.test.ts). Consumers
|
|
30
|
+
* reach it by subpath: `@intentius/chant-lexicon-k8s/receipt-store`.
|
|
31
|
+
*/
|
|
32
|
+
import { loadChantConfigUpward, resolveOwnershipStack } from "@intentius/chant/config";
|
|
33
|
+
import { LABEL_OWNERSHIP_KEYS, ownershipEntries, classifyOwnership, readOwnership, } from "@intentius/chant/ownership";
|
|
34
|
+
import { defaultK8sConnector } from "./api/connect.js";
|
|
35
|
+
import { classifyApiFailure } from "./api/classify.js";
|
|
36
|
+
import { K8S_EFFECT_RECEIPT_ENTITY_TYPE, RECEIPT_CONFIGMAP_REF, RECEIPT_DATA_KEY, RECEIPT_LABEL_KEY, parseReceiptComment, receiptConfigMapRef, receiptNamespaceFrom, } from "./effect-receipt-row.js";
|
|
37
|
+
async function resolveIdentity(options) {
|
|
38
|
+
const processEnv = options.env ?? process.env;
|
|
39
|
+
let stack = options.stack;
|
|
40
|
+
let env = options.environment ?? processEnv.CHANT_ENV;
|
|
41
|
+
let namespace = options.namespace;
|
|
42
|
+
if (!stack || !env || !namespace) {
|
|
43
|
+
let config;
|
|
44
|
+
try {
|
|
45
|
+
config = (await loadChantConfigUpward(options.cwd ?? process.cwd())).config;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
config = undefined;
|
|
49
|
+
}
|
|
50
|
+
if (config) {
|
|
51
|
+
stack = stack ?? resolveOwnershipStack(config);
|
|
52
|
+
// Only a literal env can answer here: a `{ param }` reference resolves
|
|
53
|
+
// per build, and an op run has no build parameters, and `--env` does.
|
|
54
|
+
const configEnv = config.ownership?.env;
|
|
55
|
+
env = env ?? (typeof configEnv === "string" ? configEnv : undefined);
|
|
56
|
+
namespace = namespace ?? receiptNamespaceFrom(config);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!stack) {
|
|
60
|
+
throw new Error("k8s receipt store: no stack identity. The receipt ConfigMap is named " +
|
|
61
|
+
"chant-receipt.<stack>.<env>.<effect>, derived from the same ownership fields that stamp " +
|
|
62
|
+
"markers (chant #1703, decision 4). Set ownership: { stack } in chant.config.ts.");
|
|
63
|
+
}
|
|
64
|
+
if (!env) {
|
|
65
|
+
throw new Error("k8s receipt store: no environment resolved. The receipt name's <env> segment is explicit " +
|
|
66
|
+
"(chant #1703, decision 4). Run with --env <name>, set CHANT_ENV, or set a literal " +
|
|
67
|
+
"ownership.env in chant.config.ts.");
|
|
68
|
+
}
|
|
69
|
+
return { stack, env, namespace: namespace ?? receiptNamespaceFrom(undefined) };
|
|
70
|
+
}
|
|
71
|
+
/** The stored expectation on a live receipt ConfigMap, or undefined when the
|
|
72
|
+
* object holds none. */
|
|
73
|
+
export function receiptValueOf(object) {
|
|
74
|
+
const data = object?.data;
|
|
75
|
+
const value = data?.[RECEIPT_DATA_KEY];
|
|
76
|
+
return typeof value === "string" ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The `ReceiptStore` over ConfigMaps. Bind it once in the op activities barrel
|
|
80
|
+
* as `receiptActivities(k8sReceiptStore())`, and the registry resolves
|
|
81
|
+
* `receiptRead`/`receiptWrite`/`receiptStaleness` by name, exactly like
|
|
82
|
+
* `ensureSecret` (#1830). Identity and cluster resolve lazily at first use, so
|
|
83
|
+
* module load never reads the project or connects to anything.
|
|
84
|
+
*/
|
|
85
|
+
export function k8sReceiptStore(options = {}) {
|
|
86
|
+
let identity;
|
|
87
|
+
const identityOf = () => (identity ?? (identity = resolveIdentity(options)));
|
|
88
|
+
let pending;
|
|
89
|
+
const clientOf = () => (pending ?? (pending = (options.connect ?? defaultK8sConnector)({
|
|
90
|
+
...(options.environment !== undefined ? { environment: options.environment } : {}),
|
|
91
|
+
...(options.context !== undefined ? { context: options.context } : {}),
|
|
92
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
93
|
+
}).then((connected) => connected.client)));
|
|
94
|
+
return {
|
|
95
|
+
async read(receipt) {
|
|
96
|
+
const { stack, env, namespace } = await identityOf();
|
|
97
|
+
const ref = receiptConfigMapRef(stack, env, receipt.effect, namespace);
|
|
98
|
+
const client = await clientOf();
|
|
99
|
+
const live = await client.readIfPresent({ ...RECEIPT_CONFIGMAP_REF, ...ref });
|
|
100
|
+
return receiptValueOf(live);
|
|
101
|
+
},
|
|
102
|
+
async write(receipt, expectation) {
|
|
103
|
+
const { stack, env, namespace } = await identityOf();
|
|
104
|
+
const ref = receiptConfigMapRef(stack, env, receipt.effect, namespace);
|
|
105
|
+
const client = await clientOf();
|
|
106
|
+
const configMap = {
|
|
107
|
+
...RECEIPT_CONFIGMAP_REF,
|
|
108
|
+
metadata: {
|
|
109
|
+
...ref,
|
|
110
|
+
labels: {
|
|
111
|
+
...ownershipEntries(LABEL_OWNERSHIP_KEYS, { stack, env }),
|
|
112
|
+
[RECEIPT_LABEL_KEY]: receipt.effect,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
data: { [RECEIPT_DATA_KEY]: expectation },
|
|
116
|
+
};
|
|
117
|
+
await client.apply(configMap, { fieldManager: `chant:${stack}` });
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** The receipt rows a build output carries, keyed by entity name. Only the
|
|
122
|
+
* entities this observation was asked about. */
|
|
123
|
+
export function receiptRowsFor(entityNames, buildOutput) {
|
|
124
|
+
const rows = parseReceiptComment(buildOutput ?? "");
|
|
125
|
+
const wanted = new Set(entityNames);
|
|
126
|
+
const out = new Map();
|
|
127
|
+
for (const [name, row] of Object.entries(rows)) {
|
|
128
|
+
if (!wanted.has(name))
|
|
129
|
+
continue;
|
|
130
|
+
if (typeof row?.name !== "string" || typeof row?.namespace !== "string")
|
|
131
|
+
continue;
|
|
132
|
+
out.set(name, row);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The plan-side live read of the receipt rows (#2074's observation leg).
|
|
138
|
+
*
|
|
139
|
+
* A receipt is not a document the applier ever wrote (#1832), and it carries
|
|
140
|
+
* no `props` on the declared side, so the generic declared-entity sweep in
|
|
141
|
+
* ./describe-resources.ts has neither a `metadata.name` to query by nor an
|
|
142
|
+
* honest verdict to give. The serializer rendered each receipt's derived
|
|
143
|
+
* ConfigMap address into the build output's receipt comment, so this leg reads
|
|
144
|
+
* the addresses back from there, one derivation, decision 4, and asks the
|
|
145
|
+
* cluster for each.
|
|
146
|
+
*
|
|
147
|
+
* Present maps the stored value onto `attributes.value` (core's
|
|
148
|
+
* `RECEIPT_VALUE_ATTRIBUTE`); a genuine 404 is a real absence and stays one; a
|
|
149
|
+
* failed read is an `unobserved` hole, never a wrong answer: a receipt nobody
|
|
150
|
+
* could read must not arrive downstream as "the effect never ran".
|
|
151
|
+
*/
|
|
152
|
+
export async function observeReceiptRows(client, rows) {
|
|
153
|
+
const out = { resources: {}, unobserved: {} };
|
|
154
|
+
await client.concurrently([...rows], async ([entityName, row]) => {
|
|
155
|
+
try {
|
|
156
|
+
const live = await client.read({
|
|
157
|
+
...RECEIPT_CONFIGMAP_REF,
|
|
158
|
+
name: row.name,
|
|
159
|
+
namespace: row.namespace,
|
|
160
|
+
});
|
|
161
|
+
out.resources[entityName] = {
|
|
162
|
+
type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
|
|
163
|
+
physicalId: live.metadata?.uid,
|
|
164
|
+
// Live outside anything the applier wrote, by design. The same word
|
|
165
|
+
// the aws row's observation uses for a receipt parameter (#1835).
|
|
166
|
+
status: "EXTERNAL",
|
|
167
|
+
ownership: classifyOwnership(live.metadata?.labels, LABEL_OWNERSHIP_KEYS),
|
|
168
|
+
marker: readOwnership(live.metadata?.labels, LABEL_OWNERSHIP_KEYS),
|
|
169
|
+
attributes: {
|
|
170
|
+
namespace: row.namespace,
|
|
171
|
+
// Core's RECEIPT_VALUE_ATTRIBUTE, which is what `readReceiptValue` reads.
|
|
172
|
+
value: receiptValueOf(live) ?? "",
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const outcome = classifyApiFailure(err);
|
|
178
|
+
if (outcome.kind === "unobserved") {
|
|
179
|
+
out.unobserved[entityName] = {
|
|
180
|
+
type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
|
|
181
|
+
reason: outcome.reason,
|
|
182
|
+
detail: `reading receipt ConfigMap ${row.namespace}/${row.name}: ${outcome.detail}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// `absent` records nothing: in neither map is how the contract spells
|
|
186
|
+
// "asked, and it is not there", which is what the plan reads as "the
|
|
187
|
+
// effect has not fired for these inputs".
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The deep read's answer for the receipt rows.
|
|
194
|
+
*
|
|
195
|
+
* A receipt is read back here for the same reason the thin path reads it: a
|
|
196
|
+
* declared entity nobody looked at is a hole, and a hole in the deep read is
|
|
197
|
+
* noise on every `lifecycle diff --live --deep` a project with receipts runs.
|
|
198
|
+
* What it deliberately contributes is an EMPTY property tree: the declaration
|
|
199
|
+
* has no `props`, so every live path would land outside the claimed-field set
|
|
200
|
+
* (`@intentius/chant/claimed-fields`) and be reported unclaimed, and the
|
|
201
|
+
* receipt's stored value is not drift on any reading, because a stale receipt is an
|
|
202
|
+
* `effect` row from `planReceipts` (#1832), never an update. Presence and the
|
|
203
|
+
* uid are the whole of what the deep read has to say about a receipt.
|
|
204
|
+
*/
|
|
205
|
+
export async function observeReceiptRowsDeep(client, rows) {
|
|
206
|
+
const thin = await observeReceiptRows(client, rows);
|
|
207
|
+
const resources = {};
|
|
208
|
+
for (const [name, meta] of Object.entries(thin.resources)) {
|
|
209
|
+
resources[name] = {
|
|
210
|
+
type: K8S_EFFECT_RECEIPT_ENTITY_TYPE,
|
|
211
|
+
...(meta.physicalId ? { physicalId: meta.physicalId } : {}),
|
|
212
|
+
properties: {},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return { resources, unobserved: thin.unobserved };
|
|
216
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { createRequire } from "module";
|
|
15
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
15
16
|
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
16
17
|
import type { CrdFieldSchema } from "../../spec/parse";
|
|
17
18
|
import { docsToManifests, type K8sManifest } from "./k8s-helpers";
|
|
@@ -44,7 +45,7 @@ export function getCrdSchemaRegistry(): Map<string, CrdFieldSchema> {
|
|
|
44
45
|
// Built lazily so importing this module stays edge-safe: `createRequire`
|
|
45
46
|
// throws where import.meta.url is undefined (bundled Workers), and there
|
|
46
47
|
// the registry is simply empty.
|
|
47
|
-
const require = createRequire(import.meta.url);
|
|
48
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
48
49
|
const lexicon = require("../../generated/lexicon-k8s.json") as Record<string, LexiconEntry>;
|
|
49
50
|
for (const entry of Object.values(lexicon)) {
|
|
50
51
|
if (entry.kind === "resource" && entry.apiVersion && entry.gvkKind && entry.specSchema) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `generated-once` marker label (chant #1830, epic #1365 decision 5).
|
|
3
|
+
*
|
|
4
|
+
* `k8sSecretStore` (./secret-store.ts) stamps this label onto every Secret it
|
|
5
|
+
* mints, next to the ownership marker. Both k8s prune paths key on it:
|
|
6
|
+
*
|
|
7
|
+
* - the apply-path prune (`applyManifest`'s owned-only sweep,
|
|
8
|
+
* op/activities/kubectl.ts) excludes a labeled Secret from the prunable set
|
|
9
|
+
* and reports it `retained` instead of deleting it;
|
|
10
|
+
* - env teardown (./teardown.ts `executeTeardown`) reports the same `retained`
|
|
11
|
+
* outcome for it, loudly, and never deletes it.
|
|
12
|
+
*
|
|
13
|
+
* The rationale is the materialization contract: the stored bytes are the ONLY
|
|
14
|
+
* copy of material chant never held, so no sweep may destroy them. Deletion is
|
|
15
|
+
* an explicit human act — `kubectl delete secret <name>`, or a future gated
|
|
16
|
+
* op — never a side effect of "no longer declared".
|
|
17
|
+
*
|
|
18
|
+
* Dependency-light on purpose: no client import, so the constants are usable
|
|
19
|
+
* from the build path, the prune paths, and the docs/tests alike.
|
|
20
|
+
*/
|
|
21
|
+
/** The label key a generated-once Secret carries. */
|
|
22
|
+
export const GENERATED_ONCE_LABEL_KEY = "chant.intentius.io/generated-once";
|
|
23
|
+
/** The label value `k8sSecretStore` stamps. */
|
|
24
|
+
export const GENERATED_ONCE_LABEL_VALUE = "true";
|
|
25
|
+
/**
|
|
26
|
+
* True when a live object's labels mark it generated-once. Any non-empty
|
|
27
|
+
* value counts: the label's presence is the claim, and a sweep must err on
|
|
28
|
+
* the side of keeping.
|
|
29
|
+
*/
|
|
30
|
+
export function isGeneratedOnce(labels) {
|
|
31
|
+
const value = labels?.[GENERATED_ONCE_LABEL_KEY];
|
|
32
|
+
return typeof value === "string" && value.length > 0;
|
|
33
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Kubernetes row of the secret-store seam (chant #1830, epic #1365) — a
|
|
3
|
+
* `SecretStoreAdapter` over the typed client, for the `ensure-secret`
|
|
4
|
+
* capability verb and the `ensureSecret(...)` op step (both #1829, both
|
|
5
|
+
* surfaces over core's one materialization engine).
|
|
6
|
+
*
|
|
7
|
+
* The constitutional line, held structurally:
|
|
8
|
+
*
|
|
9
|
+
* - `exists` and `describe` read the live Secret and report key NAMES and
|
|
10
|
+
* labels/annotations only. The `data` values are projected to their key
|
|
11
|
+
* names inside one function frame and the object is dropped — nothing
|
|
12
|
+
* value-shaped is returned, logged, or retained. The mismatch check in core
|
|
13
|
+
* therefore has nothing to compare but names, by construction.
|
|
14
|
+
* - `create` is the only writer. It redeems each key's material through
|
|
15
|
+
* `consumeSecretMaterial` AT the write — the engine hands the generator
|
|
16
|
+
* through this seam and never calls it — and returns nothing.
|
|
17
|
+
*
|
|
18
|
+
* Every minted Secret carries chant's ownership marker plus the
|
|
19
|
+
* `chant.intentius.io/generated-once` label (./secret-labels.ts), which is
|
|
20
|
+
* what both prune paths key their `retained` outcome on: a generated-once
|
|
21
|
+
* Secret is never in any prunable set, because the stored bytes are the only
|
|
22
|
+
* copy of material chant never held.
|
|
23
|
+
*
|
|
24
|
+
* Not exported from the package entry point — like ./teardown.ts, this module
|
|
25
|
+
* names the API client, which must stay off the build path (chant #1074,
|
|
26
|
+
* examples/k8s-client-boundary.test.ts). Consumers reach it by subpath:
|
|
27
|
+
* `@intentius/chant-lexicon-k8s/secret-store`.
|
|
28
|
+
*/
|
|
29
|
+
import { consumeSecretMaterial } from "@intentius/chant/secret-materialization";
|
|
30
|
+
import { createEnsureSecretCapability, } from "@intentius/chant/components/verbs/ensure-secret";
|
|
31
|
+
import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE, ownershipEntries, } from "@intentius/chant/ownership";
|
|
32
|
+
import { defaultK8sConnector } from "./api/connect.js";
|
|
33
|
+
import { GENERATED_ONCE_LABEL_KEY, GENERATED_ONCE_LABEL_VALUE } from "./secret-labels.js";
|
|
34
|
+
const SECRET_REF = { apiVersion: "v1", kind: "Secret" };
|
|
35
|
+
/**
|
|
36
|
+
* Build the k8s `SecretStoreAdapter` for one namespace.
|
|
37
|
+
*
|
|
38
|
+
* Pass a `client` where the caller already holds one (an activity that
|
|
39
|
+
* connected for the rest of its work); omit it and the adapter connects
|
|
40
|
+
* lazily through the same environment→cluster binding every other k8s path
|
|
41
|
+
* uses (./api/connect.ts).
|
|
42
|
+
*/
|
|
43
|
+
export function k8sSecretStore(namespace, client, options = {}) {
|
|
44
|
+
let pending = client ? Promise.resolve(client) : undefined;
|
|
45
|
+
const clientOf = () => (pending ?? (pending = (options.connect ?? defaultK8sConnector)({
|
|
46
|
+
...(options.environment !== undefined ? { environment: options.environment } : {}),
|
|
47
|
+
...(options.context !== undefined ? { context: options.context } : {}),
|
|
48
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
49
|
+
}).then((connected) => connected.client)));
|
|
50
|
+
return {
|
|
51
|
+
async exists(name) {
|
|
52
|
+
const c = await clientOf();
|
|
53
|
+
return (await c.readIfPresent({ ...SECRET_REF, name, namespace })) !== undefined;
|
|
54
|
+
},
|
|
55
|
+
async describe(name) {
|
|
56
|
+
const c = await clientOf();
|
|
57
|
+
const live = await c.read({ ...SECRET_REF, name, namespace });
|
|
58
|
+
// Project key names and drop the object in the same frame. The values
|
|
59
|
+
// exist only in `live`, which nothing below touches again — the return
|
|
60
|
+
// carries names and metadata, never a byte of `data`.
|
|
61
|
+
const keys = Object.keys(live.data ?? {}).sort();
|
|
62
|
+
const metadata = {
|
|
63
|
+
...(live.metadata?.annotations ?? {}),
|
|
64
|
+
...(live.metadata?.labels ?? {}),
|
|
65
|
+
};
|
|
66
|
+
return { keys, metadata };
|
|
67
|
+
},
|
|
68
|
+
async create(name, keys, generate) {
|
|
69
|
+
const c = await clientOf();
|
|
70
|
+
// Mint and consume per key, at the write. `consumeSecretMaterial` burns
|
|
71
|
+
// each handle, so the material cannot be redeemed a second time by
|
|
72
|
+
// anything, this adapter included.
|
|
73
|
+
const data = {};
|
|
74
|
+
for (const key of keys) {
|
|
75
|
+
data[key] = Buffer.from(consumeSecretMaterial(await generate(key)), "utf-8").toString("base64");
|
|
76
|
+
}
|
|
77
|
+
const secret = {
|
|
78
|
+
apiVersion: SECRET_REF.apiVersion,
|
|
79
|
+
kind: SECRET_REF.kind,
|
|
80
|
+
metadata: {
|
|
81
|
+
name,
|
|
82
|
+
namespace,
|
|
83
|
+
labels: {
|
|
84
|
+
...(options.marker
|
|
85
|
+
? ownershipEntries(LABEL_OWNERSHIP_KEYS, options.marker)
|
|
86
|
+
: { [LABEL_OWNERSHIP_KEYS.managedBy]: OWNERSHIP_MANAGED_BY_VALUE }),
|
|
87
|
+
[GENERATED_ONCE_LABEL_KEY]: GENERATED_ONCE_LABEL_VALUE,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
type: "Opaque",
|
|
91
|
+
data,
|
|
92
|
+
};
|
|
93
|
+
// Server-side apply as chant's field manager — the same write path every
|
|
94
|
+
// other chant mutation takes. The result (which echoes `data`) is
|
|
95
|
+
// deliberately discarded.
|
|
96
|
+
await c.apply(secret, {
|
|
97
|
+
...(options.marker ? { fieldManager: `chant:${options.marker.stack}` } : {}),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The real `ensure-secret` capability for a Kubernetes namespace — core's
|
|
104
|
+
* factory (#1829) over this module's adapter. Replaces the starter set's
|
|
105
|
+
* typed stub for a project whose secret store is the cluster:
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* registry.register(k8sEnsureSecretCapability("prod", undefined, {
|
|
109
|
+
* marker: { stack: "shop", env: "prod" },
|
|
110
|
+
* environment: "prod",
|
|
111
|
+
* }));
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function k8sEnsureSecretCapability(namespace, client, options = {}) {
|
|
115
|
+
return createEnsureSecretCapability({ store: k8sSecretStore(namespace, client, options) });
|
|
116
|
+
}
|
package/dist/serializer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAsC,MAAM,6BAA6B,CAAC;AAoXlG,eAAO,MAAM,aAAa,EAAE,UAyO3B,CAAC"}
|