@intentius/chant 0.124.51 → 0.124.54
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/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +17 -3
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +224 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/covered-round.d.ts +173 -0
- package/dist/components/covered-round.d.ts.map +1 -0
- package/dist/components/covered-round.js +200 -0
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +42 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +74 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +62 -7
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +354 -53
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/components/wave-plan.d.ts +2 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +4 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +92 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +62 -3
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +76 -3
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +16 -3
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +238 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/covered-round.test.ts +166 -0
- package/src/components/covered-round.ts +293 -0
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +101 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +59 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +728 -4
- package/src/components/pr-run.ts +395 -55
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/components/wave-plan.ts +5 -2
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +57 -1
- package/src/pr-loop.ts +154 -6
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +76 -3
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behold#1029: Kubernetes, through API discovery and then one LIST per
|
|
3
|
+
* served kind, paged with `continue`.
|
|
4
|
+
*
|
|
5
|
+
* The lister does not go through `@intentius/chant-k8s-client`. That client
|
|
6
|
+
* builds a full read and write client, and its only network seam
|
|
7
|
+
* (`requestLayer`) replaces the whole HTTP send, so the read-only guard could
|
|
8
|
+
* not sit in front of its real transport. Discovery and LIST are plain GETs,
|
|
9
|
+
* so they go through the lister's own guarded transport like every other
|
|
10
|
+
* substrate, with the cluster's server, token and certificates passed
|
|
11
|
+
* explicitly. {@link kubeTargetFromKubeconfig} reads those from a kubeconfig
|
|
12
|
+
* the caller names, and refuses an exec plugin or an auth provider: those
|
|
13
|
+
* are credential chains, and the lister takes static credentials only.
|
|
14
|
+
*
|
|
15
|
+
* Locations are namespaces. A cluster-scoped object's location is `cluster`;
|
|
16
|
+
* it is listed when the scope's locations are `*` or include `cluster`. With
|
|
17
|
+
* types `*`, events and leases are left out (they change on every read, so
|
|
18
|
+
* every version's digest would differ) and a note says so.
|
|
19
|
+
*
|
|
20
|
+
* kind (the pinned `kindest/node` in choudoufu's live/kind-node-image) runs a
|
|
21
|
+
* real API server, so discovery and LIST are served as on any cluster.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { parseYAML } from "../yaml";
|
|
26
|
+
import { buildResource, describeFailure, listedVerdict, noteTagFilter, withThrottleRetry, type ListerContext } from "./common";
|
|
27
|
+
import { ListerConfigError } from "./credentials";
|
|
28
|
+
import { maskKubernetesObject } from "./mask";
|
|
29
|
+
import { jsonBody, type TlsOptions, type WireResponse } from "./transport";
|
|
30
|
+
|
|
31
|
+
export interface KubernetesTarget {
|
|
32
|
+
/** The API server, e.g. `https://127.0.0.1:6443`. */
|
|
33
|
+
server: string;
|
|
34
|
+
/** A bearer token; absent when the client certificate in `tls` authenticates. */
|
|
35
|
+
token?: string;
|
|
36
|
+
tls?: TlsOptions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A served, listable kind from discovery. */
|
|
40
|
+
export interface ServedKind {
|
|
41
|
+
group: string;
|
|
42
|
+
version: string;
|
|
43
|
+
kind: string;
|
|
44
|
+
plural: string;
|
|
45
|
+
namespaced: boolean;
|
|
46
|
+
/** `v1/Pod`, `apps/v1/Deployment`: the lister's `listedType`. */
|
|
47
|
+
listedType: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PAGE_SIZE = 500;
|
|
51
|
+
const MAX_PAGES = 10_000;
|
|
52
|
+
const LABEL_PART = /^[A-Za-z0-9_.\-\/]+$/;
|
|
53
|
+
const CLUSTER_LOCATION = "cluster";
|
|
54
|
+
/** Left out of `types: "*"`: they change on every read. */
|
|
55
|
+
const CHURNING_TYPES = new Set(["v1/Event", "events.k8s.io/v1/Event", "coordination.k8s.io/v1/Lease"]);
|
|
56
|
+
|
|
57
|
+
export function listedTypeOf(group: string, version: string, kind: string): string {
|
|
58
|
+
return group === "" ? `${version}/${kind}` : `${group}/${version}/${kind}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function apiUrl(target: KubernetesTarget, path: string, query?: Record<string, string>): string {
|
|
62
|
+
const base = target.server.replace(/\/+$/, "");
|
|
63
|
+
const url = new URL(base + path);
|
|
64
|
+
for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
|
|
65
|
+
return url.toString();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function headersFor(target: KubernetesTarget): Record<string, string> {
|
|
69
|
+
return { accept: "application/json", ...(target.token ? { authorization: `Bearer ${target.token}` } : {}) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function get(ctx: ListerContext, target: KubernetesTarget, path: string, query: Record<string, string> | undefined, retryDelays?: readonly number[]): Promise<WireResponse> {
|
|
73
|
+
const url = apiUrl(target, path, query);
|
|
74
|
+
return withThrottleRetry(() => ctx.transport.send({ substrate: "kubernetes", method: "GET", url, headers: headersFor(target) }), (r) => r.status === 429, retryDelays);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function k8sMessage(res: WireResponse): string {
|
|
78
|
+
const body = jsonBody(res) as { message?: string; reason?: string } | undefined;
|
|
79
|
+
return describeFailure(res, body?.reason, body?.message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resourcesOf(body: unknown, group: string, version: string): ServedKind[] {
|
|
83
|
+
const list = (body as { resources?: Array<{ name?: string; kind?: string; namespaced?: boolean; verbs?: string[] }> } | undefined)?.resources ?? [];
|
|
84
|
+
const out: ServedKind[] = [];
|
|
85
|
+
for (const r of list) {
|
|
86
|
+
if (!r.name || !r.kind || r.name.includes("/")) continue;
|
|
87
|
+
if (!(r.verbs ?? []).includes("list")) continue;
|
|
88
|
+
out.push({ group, version, kind: r.kind, plural: r.name, namespaced: r.namespaced === true, listedType: listedTypeOf(group, version, r.kind) });
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Every listable kind the cluster serves: the core group's `v1`, and for each
|
|
95
|
+
* other group its preferred version plus any version a requested type names.
|
|
96
|
+
*/
|
|
97
|
+
export async function discover(
|
|
98
|
+
ctx: ListerContext,
|
|
99
|
+
target: KubernetesTarget,
|
|
100
|
+
requested: ReadonlySet<string> | undefined,
|
|
101
|
+
retryDelays?: readonly number[],
|
|
102
|
+
): Promise<ServedKind[]> {
|
|
103
|
+
const kinds: ServedKind[] = [];
|
|
104
|
+
const core = await get(ctx, target, "/api/v1", undefined, retryDelays);
|
|
105
|
+
if (core.status >= 400) {
|
|
106
|
+
ctx.notes.push({ code: core.status === 401 || core.status === 403 ? "no-credentials" : "read-failed", message: `discovery of the core group failed: ${k8sMessage(core)}` });
|
|
107
|
+
} else {
|
|
108
|
+
kinds.push(...resourcesOf(jsonBody(core), "", "v1"));
|
|
109
|
+
}
|
|
110
|
+
const groups = await get(ctx, target, "/apis", undefined, retryDelays);
|
|
111
|
+
if (groups.status >= 400) {
|
|
112
|
+
ctx.notes.push({ code: groups.status === 401 || groups.status === 403 ? "no-credentials" : "read-failed", message: `discovery of API groups failed: ${k8sMessage(groups)}` });
|
|
113
|
+
return kinds;
|
|
114
|
+
}
|
|
115
|
+
const list = (jsonBody(groups) as { groups?: Array<{ name?: string; preferredVersion?: { version?: string }; versions?: Array<{ version?: string }> }> } | undefined)?.groups ?? [];
|
|
116
|
+
for (const g of list) {
|
|
117
|
+
if (!g.name) continue;
|
|
118
|
+
const versions = new Set<string>();
|
|
119
|
+
if (g.preferredVersion?.version) versions.add(g.preferredVersion.version);
|
|
120
|
+
if (requested) {
|
|
121
|
+
for (const v of g.versions ?? []) {
|
|
122
|
+
if (v.version && [...requested].some((t) => t.startsWith(`${g.name}/${v.version}/`))) versions.add(v.version);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const version of versions) {
|
|
126
|
+
const res = await get(ctx, target, `/apis/${g.name}/${version}`, undefined, retryDelays);
|
|
127
|
+
if (res.status >= 400) {
|
|
128
|
+
ctx.notes.push({ code: "read-failed", message: `discovery of ${g.name}/${version} failed: ${k8sMessage(res)}` });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
kinds.push(...resourcesOf(jsonBody(res), g.name, version));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return kinds;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** `labelSelector` for a tag filter, or undefined. Exported for tests. */
|
|
138
|
+
export function labelSelector(filter: Readonly<Record<string, string>> | undefined): string | undefined {
|
|
139
|
+
const entries = Object.entries(filter ?? {});
|
|
140
|
+
if (entries.length === 0) return undefined;
|
|
141
|
+
return entries
|
|
142
|
+
.map(([k, v]) => {
|
|
143
|
+
if (!LABEL_PART.test(k) || !LABEL_PART.test(v)) throw new ListerConfigError(`kubernetes label filter ${k}=${v} is not a plain label key and value`);
|
|
144
|
+
return `${k}=${v}`;
|
|
145
|
+
})
|
|
146
|
+
.join(",");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function listKubernetes(ctx: ListerContext, target: KubernetesTarget, retryDelays?: readonly number[]): Promise<void> {
|
|
150
|
+
const { scope } = ctx;
|
|
151
|
+
const requested = scope.types === "*" ? undefined : new Set(scope.types);
|
|
152
|
+
const selector = labelSelector(scope.tagFilter);
|
|
153
|
+
noteTagFilter(ctx, "as a labelSelector on every LIST");
|
|
154
|
+
const served = await discover(ctx, target, requested, retryDelays);
|
|
155
|
+
|
|
156
|
+
let toList: ServedKind[];
|
|
157
|
+
if (requested) {
|
|
158
|
+
const byType = new Map(served.map((k) => [k.listedType, k]));
|
|
159
|
+
toList = [];
|
|
160
|
+
for (const t of requested) {
|
|
161
|
+
const k = byType.get(t);
|
|
162
|
+
if (k) {
|
|
163
|
+
toList.push(k);
|
|
164
|
+
} else {
|
|
165
|
+
ctx.coverage.push({ listedType: t, location: "*", verdict: "unsupported-kind", detail: "the cluster's discovery serves no listable resource of this type" });
|
|
166
|
+
ctx.notes.push({ code: "no-lister", listedType: t, message: `${t} is not a listable resource on this cluster (not served by discovery, or no list verb)` });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
toList = served.filter((k) => !CHURNING_TYPES.has(k.listedType));
|
|
171
|
+
const skipped = served.filter((k) => CHURNING_TYPES.has(k.listedType)).map((k) => k.listedType);
|
|
172
|
+
if (skipped.length > 0) {
|
|
173
|
+
ctx.notes.push({ code: "default-types", message: `types "*" left out ${skipped.join(", ")}, which change on every read; name them to list them` });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const namespaces = scope.locations === "*" ? undefined : new Set(scope.locations);
|
|
178
|
+
const wantCluster = scope.locations === "*" || scope.locations.includes(CLUSTER_LOCATION);
|
|
179
|
+
const nsCoverage = scope.locations === "*" ? ["*"] : scope.locations.filter((l) => l !== CLUSTER_LOCATION);
|
|
180
|
+
|
|
181
|
+
for (const k of toList) {
|
|
182
|
+
if (!k.namespaced && !wantCluster) continue;
|
|
183
|
+
if (k.namespaced && nsCoverage.length === 0) continue;
|
|
184
|
+
const coverageLocations = k.namespaced ? nsCoverage : [CLUSTER_LOCATION];
|
|
185
|
+
const path = k.group === "" ? `/api/v1/${k.plural}` : `/apis/${k.group}/${k.version}/${k.plural}`;
|
|
186
|
+
const found: Parameters<typeof buildResource>[1][] = [];
|
|
187
|
+
let cont: string | undefined;
|
|
188
|
+
let failed: WireResponse | undefined;
|
|
189
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
190
|
+
const query: Record<string, string> = { limit: String(PAGE_SIZE) };
|
|
191
|
+
if (cont) query.continue = cont;
|
|
192
|
+
if (selector) query.labelSelector = selector;
|
|
193
|
+
const res = await get(ctx, target, path, query, retryDelays);
|
|
194
|
+
const observedAt = ctx.now().toISOString();
|
|
195
|
+
if (res.status >= 400) {
|
|
196
|
+
failed = res;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const body = jsonBody(res) as { items?: Array<Record<string, unknown>>; metadata?: { continue?: string } } | undefined;
|
|
200
|
+
for (const item of body?.items ?? []) {
|
|
201
|
+
const meta = (item.metadata ?? {}) as { name?: string; namespace?: string; uid?: string; labels?: Record<string, string>; managedFields?: unknown };
|
|
202
|
+
if (!meta.name) continue;
|
|
203
|
+
const ns = meta.namespace;
|
|
204
|
+
if (k.namespaced && namespaces && (!ns || !namespaces.has(ns))) continue;
|
|
205
|
+
const location = k.namespaced ? ns ?? "" : CLUSTER_LOCATION;
|
|
206
|
+
const obj: Record<string, unknown> = { ...item, apiVersion: k.group === "" ? k.version : `${k.group}/${k.version}`, kind: k.kind };
|
|
207
|
+
const { managedFields: _mf, ...restMeta } = meta;
|
|
208
|
+
obj.metadata = restMeta;
|
|
209
|
+
const tags: Record<string, string> = {};
|
|
210
|
+
for (const [lk, lv] of Object.entries(meta.labels ?? {})) tags[lk] = String(lv);
|
|
211
|
+
found.push({
|
|
212
|
+
listedType: k.listedType,
|
|
213
|
+
location,
|
|
214
|
+
nativeId: k.namespaced ? `${ns}/${meta.name}` : meta.name,
|
|
215
|
+
cloudIds: meta.uid ? [{ kind: "k8s-uid", value: meta.uid }] : [],
|
|
216
|
+
tags,
|
|
217
|
+
properties: maskKubernetesObject(obj),
|
|
218
|
+
observedAt,
|
|
219
|
+
labels: true,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
cont = body?.metadata?.continue || undefined;
|
|
223
|
+
if (!cont) break;
|
|
224
|
+
}
|
|
225
|
+
if (failed) {
|
|
226
|
+
const detail = k8sMessage(failed);
|
|
227
|
+
const verdict = failed.status === 401 || failed.status === 403 ? "no-credentials" : failed.status === 404 ? "unsupported-kind" : "read-failed";
|
|
228
|
+
for (const l of coverageLocations) ctx.coverage.push({ listedType: k.listedType, location: l, verdict, detail });
|
|
229
|
+
ctx.notes.push({
|
|
230
|
+
code: verdict === "no-credentials" ? "no-credentials" : verdict === "unsupported-kind" ? "no-lister" : "read-failed",
|
|
231
|
+
listedType: k.listedType,
|
|
232
|
+
message: `LIST ${k.listedType} failed: ${detail}`,
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
for (const r of found) ctx.resources.push(buildResource(ctx, r));
|
|
237
|
+
for (const l of coverageLocations) ctx.coverage.push({ listedType: k.listedType, location: l, ...listedVerdict(scope) });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A {@link KubernetesTarget} from a kubeconfig the caller names (its text,
|
|
243
|
+
* and the directory relative file paths resolve against). Static credentials
|
|
244
|
+
* only: a token, a token file, or a client certificate and key. An exec
|
|
245
|
+
* plugin or an auth provider is refused.
|
|
246
|
+
*/
|
|
247
|
+
export function kubeTargetFromKubeconfig(text: string, opts: { context?: string; baseDir?: string } = {}): KubernetesTarget & { contextName: string; clusterName: string } {
|
|
248
|
+
const doc = parseYAML(text) as Record<string, unknown>;
|
|
249
|
+
const named = <T>(list: unknown, name: string, field: string): T | undefined => {
|
|
250
|
+
for (const e of Array.isArray(list) ? list : []) {
|
|
251
|
+
const o = e as Record<string, unknown>;
|
|
252
|
+
if (o?.name === name) return o[field] as T;
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
};
|
|
256
|
+
const contextName = opts.context ?? (typeof doc["current-context"] === "string" ? (doc["current-context"] as string) : undefined);
|
|
257
|
+
if (!contextName) throw new ListerConfigError("the kubeconfig names no context and none was given");
|
|
258
|
+
const context = named<Record<string, unknown>>(doc.contexts, contextName, "context");
|
|
259
|
+
if (!context) throw new ListerConfigError(`the kubeconfig has no context ${contextName}`);
|
|
260
|
+
const clusterName = String(context.cluster ?? "");
|
|
261
|
+
const userName = String(context.user ?? "");
|
|
262
|
+
const cluster = named<Record<string, unknown>>(doc.clusters, clusterName, "cluster");
|
|
263
|
+
if (!cluster || typeof cluster.server !== "string") throw new ListerConfigError(`the kubeconfig's cluster ${clusterName} has no server`);
|
|
264
|
+
const user = named<Record<string, unknown>>(doc.users, userName, "user") ?? {};
|
|
265
|
+
if (user.exec || user["auth-provider"]) {
|
|
266
|
+
throw new ListerConfigError(`the kubeconfig user ${userName} authenticates through an exec plugin or auth provider; the lister takes static credentials only (a token, or a client certificate and key)`);
|
|
267
|
+
}
|
|
268
|
+
const file = (p: unknown): string | undefined => {
|
|
269
|
+
if (typeof p !== "string" || !p) return undefined;
|
|
270
|
+
const path = p.startsWith("/") || !opts.baseDir ? p : `${opts.baseDir}/${p}`;
|
|
271
|
+
return readFileSync(path, "utf8");
|
|
272
|
+
};
|
|
273
|
+
const b64 = (d: unknown): string | undefined => (typeof d === "string" && d ? Buffer.from(d, "base64").toString("utf8") : undefined);
|
|
274
|
+
const tls: TlsOptions = {};
|
|
275
|
+
const ca = b64(cluster["certificate-authority-data"]) ?? file(cluster["certificate-authority"]);
|
|
276
|
+
if (ca) tls.ca = ca;
|
|
277
|
+
const cert = b64(user["client-certificate-data"]) ?? file(user["client-certificate"]);
|
|
278
|
+
if (cert) tls.cert = cert;
|
|
279
|
+
const key = b64(user["client-key-data"]) ?? file(user["client-key"]);
|
|
280
|
+
if (key) tls.key = key;
|
|
281
|
+
if (cluster["insecure-skip-tls-verify"] === true) tls.insecureSkipVerify = true;
|
|
282
|
+
const token = typeof user.token === "string" && user.token ? (user.token as string) : file(user.tokenFile)?.trim();
|
|
283
|
+
return { server: cluster.server, ...(token ? { token } : {}), tls, contextName, clusterName };
|
|
284
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behold#1029: ownership markers read off a listed object's tags or labels,
|
|
3
|
+
* in chant#4520's `EngineFacts.markers` shape with `source: "live"`.
|
|
4
|
+
*
|
|
5
|
+
* The key sets the lister knows (#1025 "What a cloud source can and cannot
|
|
6
|
+
* say about engine facts"):
|
|
7
|
+
* - choudoufu: `tofu-estate` and `tofu-address`, the address escaped
|
|
8
|
+
* (choudoufu internal/live/markers/markers.go:40,43);
|
|
9
|
+
* - plunge: `plunge-stack` and `plunge-urn` (continued in `plunge-urn-2`
|
|
10
|
+
* to `-4`), plain on tags and digests on labels (chant#4520's
|
|
11
|
+
* `markerScheme` for plunge);
|
|
12
|
+
* - chant: the lexicons' `ChannelKeys` with managed-by `chant`
|
|
13
|
+
* (packages/core/src/ownership.ts; aws `chant:*`, azure `chant-*`, gcp
|
|
14
|
+
* `managed-by` and `chant-stack`, the shared label convention for
|
|
15
|
+
* Kubernetes).
|
|
16
|
+
*
|
|
17
|
+
* `markers` is one object, so an object carrying several engines' markers
|
|
18
|
+
* reports the first found in that order. Every tag stays in `tags`, so
|
|
19
|
+
* nothing is lost. A marker key is matched case-insensitively, because Azure
|
|
20
|
+
* tag names are case-insensitive. The lister never derives ownership from a
|
|
21
|
+
* marker: that is relative to an estate or stack it does not read.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { MarkerFact } from "./types";
|
|
25
|
+
|
|
26
|
+
function lookup(tags: Record<string, string>, key: string): string | undefined {
|
|
27
|
+
if (key in tags) return tags[key];
|
|
28
|
+
const lower = key.toLowerCase();
|
|
29
|
+
for (const [k, v] of Object.entries(tags)) if (k.toLowerCase() === lower) return v;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const CHANT_KEY_SETS: ReadonlyArray<{ managedBy: string; stack: string }> = [
|
|
34
|
+
{ managedBy: "chant:managed-by", stack: "chant:stack" },
|
|
35
|
+
{ managedBy: "chant-managed-by", stack: "chant-stack" },
|
|
36
|
+
{ managedBy: "managed-by", stack: "chant-stack" },
|
|
37
|
+
{ managedBy: "app.kubernetes.io/managed-by", stack: "chant.intentius.io/stack" },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The marker fact for one object. `labels` says the values came from GCP or
|
|
42
|
+
* Kubernetes labels, where plunge writes digests rather than plain values.
|
|
43
|
+
*/
|
|
44
|
+
export function readMarkers(tags: Record<string, string>, opts: { labels: boolean }): MarkerFact | undefined {
|
|
45
|
+
const estate = lookup(tags, "tofu-estate");
|
|
46
|
+
if (estate !== undefined) {
|
|
47
|
+
const address = lookup(tags, "tofu-address");
|
|
48
|
+
return { owner: estate, ...(address !== undefined ? { address } : {}), encoding: "escaped", source: "live" };
|
|
49
|
+
}
|
|
50
|
+
const stack = lookup(tags, "plunge-stack");
|
|
51
|
+
if (stack !== undefined) {
|
|
52
|
+
let urn = lookup(tags, "plunge-urn");
|
|
53
|
+
if (urn !== undefined && !opts.labels) {
|
|
54
|
+
for (let i = 2; i <= 4; i++) {
|
|
55
|
+
const more = lookup(tags, `plunge-urn-${i}`);
|
|
56
|
+
if (more === undefined) break;
|
|
57
|
+
urn += more;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
owner: stack,
|
|
62
|
+
...(urn !== undefined ? { address: urn } : {}),
|
|
63
|
+
encoding: opts.labels ? "digest" : "plain",
|
|
64
|
+
source: "live",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
for (const keys of CHANT_KEY_SETS) {
|
|
68
|
+
if (lookup(tags, keys.managedBy) === "chant") {
|
|
69
|
+
const owner = lookup(tags, keys.stack);
|
|
70
|
+
return { ...(owner !== undefined ? { owner } : {}), encoding: "plain", source: "live" };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behold#1029: secret-bearing values are masked in the lister, before a
|
|
3
|
+
* `ListingResult` exists, so no later stage (chant#4521's typing, the store,
|
|
4
|
+
* LadybugDB) ever holds one (#1025 "Source interface").
|
|
5
|
+
*
|
|
6
|
+
* The key list is chant's `SENSITIVE_KEY_PATTERNS` (../sensitive-key.ts), the
|
|
7
|
+
* one list chant's thin and deep observation paths share. behold carried a
|
|
8
|
+
* copy while the lister lived there; in chant it is the same list.
|
|
9
|
+
*
|
|
10
|
+
* Beyond key names, two things are masked whole because their values are
|
|
11
|
+
* secrets whatever their keys are called:
|
|
12
|
+
* - a Kubernetes Secret's `data` and `stringData`, and its
|
|
13
|
+
* `kubectl.kubernetes.io/last-applied-configuration` annotation, which
|
|
14
|
+
* repeats them;
|
|
15
|
+
* - any tag or label whose key matches the patterns.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { isSensitiveKey, SENSITIVE_KEY_PATTERNS } from "../sensitive-key";
|
|
19
|
+
|
|
20
|
+
export { isSensitiveKey, SENSITIVE_KEY_PATTERNS };
|
|
21
|
+
|
|
22
|
+
/** What a masked value becomes. A fixed string, so a masked value never diffs. */
|
|
23
|
+
export const MASKED = "<masked>";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A deep copy of `value` with every value under a sensitive key replaced by
|
|
27
|
+
* {@link MASKED}. Arrays of `{Key, Value}` / `{key, value}` pairs (AWS tags,
|
|
28
|
+
* some ARM shapes) are masked by the pair's key as well.
|
|
29
|
+
*/
|
|
30
|
+
export function maskValue(value: unknown): unknown {
|
|
31
|
+
if (Array.isArray(value)) return value.map((v) => maskValue(v));
|
|
32
|
+
if (value === null || typeof value !== "object") return value;
|
|
33
|
+
const obj = value as Record<string, unknown>;
|
|
34
|
+
const pairKey = typeof obj.Key === "string" ? obj.Key : typeof obj.key === "string" ? obj.key : undefined;
|
|
35
|
+
const out: Record<string, unknown> = {};
|
|
36
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
37
|
+
if (isSensitiveKey(k)) {
|
|
38
|
+
out[k] = MASKED;
|
|
39
|
+
} else if (pairKey !== undefined && isSensitiveKey(pairKey) && (k === "Value" || k === "value")) {
|
|
40
|
+
out[k] = MASKED;
|
|
41
|
+
} else {
|
|
42
|
+
out[k] = maskValue(v);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function maskProperties(props: Record<string, unknown>): Record<string, unknown> {
|
|
49
|
+
return maskValue(props) as Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function maskTags(tags: Record<string, string>): Record<string, string> {
|
|
53
|
+
const out: Record<string, string> = {};
|
|
54
|
+
for (const [k, v] of Object.entries(tags)) out[k] = isSensitiveKey(k) ? MASKED : v;
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const LAST_APPLIED = "kubectl.kubernetes.io/last-applied-configuration";
|
|
59
|
+
|
|
60
|
+
/** A Kubernetes object, masked: key names everywhere, and a Secret's payload whole. */
|
|
61
|
+
export function maskKubernetesObject(obj: Record<string, unknown>): Record<string, unknown> {
|
|
62
|
+
const masked = maskProperties(obj);
|
|
63
|
+
if (obj.kind === "Secret") {
|
|
64
|
+
for (const field of ["data", "stringData"]) {
|
|
65
|
+
const payload = masked[field];
|
|
66
|
+
if (payload && typeof payload === "object") {
|
|
67
|
+
masked[field] = Object.fromEntries(Object.keys(payload as object).map((k) => [k, MASKED]));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const metadata = masked.metadata as Record<string, unknown> | undefined;
|
|
72
|
+
const annotations = metadata?.annotations as Record<string, unknown> | undefined;
|
|
73
|
+
if (annotations && LAST_APPLIED in annotations && obj.kind === "Secret") {
|
|
74
|
+
annotations[LAST_APPLIED] = MASKED;
|
|
75
|
+
}
|
|
76
|
+
return masked;
|
|
77
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behold#1029: the pieces of the listing-result shape: node ids (ruling Q2),
|
|
3
|
+
* masking (chant's one key list), and markers (chant#4520's shape).
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { listedNodeId, parseListedNodeId } from "./ids";
|
|
7
|
+
import { isSensitiveKey, MASKED, maskProperties, maskTags, SENSITIVE_KEY_PATTERNS } from "./mask";
|
|
8
|
+
import { readMarkers } from "./markers";
|
|
9
|
+
import { isSensitiveKey as chantIsSensitiveKey, SENSITIVE_KEY_PATTERNS as chantPatterns } from "../sensitive-key";
|
|
10
|
+
|
|
11
|
+
describe("listed node ids", () => {
|
|
12
|
+
it("are path-style and parse back, with slashes in the type and the native id", () => {
|
|
13
|
+
const parts = {
|
|
14
|
+
substrate: "azure" as const,
|
|
15
|
+
scopeId: "sub-1",
|
|
16
|
+
location: "eastus",
|
|
17
|
+
listedType: "microsoft.storage/storageaccounts",
|
|
18
|
+
nativeId: "/subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sa",
|
|
19
|
+
};
|
|
20
|
+
const id = listedNodeId(parts);
|
|
21
|
+
expect(id).toBe("azure/sub-1/eastus/microsoft.storage%2Fstorageaccounts//subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sa");
|
|
22
|
+
expect(parseListedNodeId(id)).toEqual(parts);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("keep an aws identifier verbatim as the last part", () => {
|
|
26
|
+
const id = listedNodeId({ substrate: "aws", scopeId: "000000000000", location: "us-east-1", listedType: "AWS::EC2::SecurityGroupIngress", nativeId: "sg-1|tcp|80" });
|
|
27
|
+
expect(id).toBe("aws/000000000000/us-east-1/AWS::EC2::SecurityGroupIngress/sg-1|tcp|80");
|
|
28
|
+
expect(parseListedNodeId(id)?.nativeId).toBe("sg-1|tcp|80");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("escape a percent sign so it parses back", () => {
|
|
32
|
+
const parts = { substrate: "kubernetes" as const, scopeId: "c%1", location: "ns", listedType: "v1/Pod", nativeId: "ns/p" };
|
|
33
|
+
expect(parseListedNodeId(listedNodeId(parts))).toEqual(parts);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("masking", () => {
|
|
38
|
+
it("masks by key at any depth and in key/value pairs", () => {
|
|
39
|
+
const masked = maskProperties({
|
|
40
|
+
Name: "db",
|
|
41
|
+
MasterUserPassword: "hunter2",
|
|
42
|
+
Nested: { list: [{ clientSecret: "s" }, { ok: 1 }] },
|
|
43
|
+
Tags: [{ Key: "github-token", Value: "ghp_x" }, { Key: "env", Value: "prod" }],
|
|
44
|
+
KeyName: "deploy-key",
|
|
45
|
+
});
|
|
46
|
+
expect(masked).toEqual({
|
|
47
|
+
Name: "db",
|
|
48
|
+
MasterUserPassword: MASKED,
|
|
49
|
+
Nested: { list: [{ clientSecret: MASKED }, { ok: 1 }] },
|
|
50
|
+
Tags: [{ Key: "github-token", Value: MASKED }, { Key: "env", Value: "prod" }],
|
|
51
|
+
KeyName: "deploy-key",
|
|
52
|
+
});
|
|
53
|
+
expect(maskTags({ "db-password": "x", env: "prod" })).toEqual({ "db-password": MASKED, env: "prod" });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("is chant's one key list (../sensitive-key.ts), not a copy", () => {
|
|
57
|
+
expect(SENSITIVE_KEY_PATTERNS).toBe(chantPatterns);
|
|
58
|
+
for (const k of ["password", "Secret", "apiToken", "private_key", "credentials", "ConnectionString", "Key", "KeyName"]) {
|
|
59
|
+
expect(isSensitiveKey(k)).toBe(chantIsSensitiveKey(k));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("markers", () => {
|
|
65
|
+
it("reads choudoufu's, plunge's and chant's keys, in that order", () => {
|
|
66
|
+
expect(readMarkers({ "tofu-estate": "web", "tofu-address": "module.app.aws_eip.pool[1]", "chant:managed-by": "chant" }, { labels: false })).toEqual({
|
|
67
|
+
owner: "web",
|
|
68
|
+
address: "module.app.aws_eip.pool[1]",
|
|
69
|
+
encoding: "escaped",
|
|
70
|
+
source: "live",
|
|
71
|
+
});
|
|
72
|
+
expect(readMarkers({ "plunge-stack": "app/dev", "plunge-urn": "urn:pulumi:", "plunge-urn-2": "dev::app" }, { labels: false })).toEqual({
|
|
73
|
+
owner: "app/dev",
|
|
74
|
+
address: "urn:pulumi:dev::app",
|
|
75
|
+
encoding: "plain",
|
|
76
|
+
source: "live",
|
|
77
|
+
});
|
|
78
|
+
expect(readMarkers({ "Chant-Managed-By": "chant", "chant-stack": "web" }, { labels: false })).toEqual({ owner: "web", encoding: "plain", source: "live" });
|
|
79
|
+
expect(readMarkers({ "managed-by": "terraform" }, { labels: true })).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* behold#1029: AWS Signature Version 4 for the lister's one AWS call.
|
|
3
|
+
*
|
|
4
|
+
* Signed by hand with node:crypto rather than through the AWS SDK, so the
|
|
5
|
+
* lister builds no SDK client (no client to hold a mutating command, no
|
|
6
|
+
* default provider chain) and adds no dependency. Only what Cloud Control's
|
|
7
|
+
* JSON protocol needs: a POST to `/` with no query string.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash, createHmac } from "node:crypto";
|
|
11
|
+
import type { AwsStaticCredentials } from "./credentials";
|
|
12
|
+
|
|
13
|
+
function sha256Hex(data: string): string {
|
|
14
|
+
return createHash("sha256").update(data, "utf8").digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hmac(key: Buffer | string, data: string): Buffer {
|
|
18
|
+
return createHmac("sha256", key).update(data, "utf8").digest();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `20261026T120000Z` and `20261026` for a date. */
|
|
22
|
+
export function amzDates(now: Date): { amzDate: string; dateStamp: string } {
|
|
23
|
+
const iso = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
24
|
+
return { amzDate: iso, dateStamp: iso.slice(0, 8) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The headers to send, `Authorization` included. `headers` must not already
|
|
29
|
+
* hold `host` or `x-amz-date`; they are added here.
|
|
30
|
+
*/
|
|
31
|
+
export function signV4(input: {
|
|
32
|
+
method: string;
|
|
33
|
+
url: string;
|
|
34
|
+
headers: Record<string, string>;
|
|
35
|
+
body: string;
|
|
36
|
+
region: string;
|
|
37
|
+
service: string;
|
|
38
|
+
credentials: AwsStaticCredentials;
|
|
39
|
+
now: Date;
|
|
40
|
+
}): Record<string, string> {
|
|
41
|
+
const url = new URL(input.url);
|
|
42
|
+
const { amzDate, dateStamp } = amzDates(input.now);
|
|
43
|
+
const headers: Record<string, string> = {};
|
|
44
|
+
for (const [k, v] of Object.entries(input.headers)) headers[k.toLowerCase()] = v.trim();
|
|
45
|
+
headers.host = url.host;
|
|
46
|
+
headers["x-amz-date"] = amzDate;
|
|
47
|
+
if (input.credentials.sessionToken) headers["x-amz-security-token"] = input.credentials.sessionToken;
|
|
48
|
+
const payloadHash = sha256Hex(input.body);
|
|
49
|
+
|
|
50
|
+
const names = Object.keys(headers).sort();
|
|
51
|
+
const canonicalHeaders = names.map((n) => `${n}:${headers[n]}\n`).join("");
|
|
52
|
+
const signedHeaders = names.join(";");
|
|
53
|
+
const canonicalQuery = [...url.searchParams.entries()]
|
|
54
|
+
.map(([k, v]) => [encodeURIComponent(k), encodeURIComponent(v)])
|
|
55
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
56
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
57
|
+
.join("&");
|
|
58
|
+
const canonicalRequest = [input.method, url.pathname || "/", canonicalQuery, canonicalHeaders, signedHeaders, payloadHash].join("\n");
|
|
59
|
+
|
|
60
|
+
const scope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
61
|
+
const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256Hex(canonicalRequest)].join("\n");
|
|
62
|
+
const kDate = hmac(`AWS4${input.credentials.secretAccessKey}`, dateStamp);
|
|
63
|
+
const kRegion = hmac(kDate, input.region);
|
|
64
|
+
const kService = hmac(kRegion, input.service);
|
|
65
|
+
const kSigning = hmac(kService, "aws4_request");
|
|
66
|
+
const signature = createHmac("sha256", kSigning).update(stringToSign, "utf8").digest("hex");
|
|
67
|
+
|
|
68
|
+
headers.authorization = `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
69
|
+
return headers;
|
|
70
|
+
}
|