typekro 0.28.1 → 0.30.0
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/.tsbuildinfo +1 -1
- package/dist/advanced/index.d.ts +5 -1
- package/dist/advanced/index.d.ts.map +1 -1
- package/dist/advanced/index.js +2 -3
- package/dist/advanced/index.js.map +1 -1
- package/dist/alchemy/deployers.d.ts +6 -4
- package/dist/alchemy/deployers.d.ts.map +1 -1
- package/dist/alchemy/deployers.js +46 -9
- package/dist/alchemy/deployers.js.map +1 -1
- package/dist/alchemy/index.d.ts +2 -3
- package/dist/alchemy/index.d.ts.map +1 -1
- package/dist/alchemy/index.js +1 -1
- package/dist/alchemy/index.js.map +1 -1
- package/dist/alchemy/kro-delete.d.ts +6 -5
- package/dist/alchemy/kro-delete.d.ts.map +1 -1
- package/dist/alchemy/kro-delete.js +169 -22
- package/dist/alchemy/kro-delete.js.map +1 -1
- package/dist/alchemy/resource-registration.d.ts +24 -31
- package/dist/alchemy/resource-registration.d.ts.map +1 -1
- package/dist/alchemy/resource-registration.js +439 -75
- package/dist/alchemy/resource-registration.js.map +1 -1
- package/dist/alchemy/types.d.ts +56 -14
- package/dist/alchemy/types.d.ts.map +1 -1
- package/dist/alchemy/utilities.d.ts +2 -2
- package/dist/alchemy/utilities.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.js +31 -28
- package/dist/compositions/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/compositions/typekro-runtime/types.d.ts +12 -0
- package/dist/compositions/typekro-runtime/types.d.ts.map +1 -1
- package/dist/core/composition/analysis-metadata.d.ts +15 -0
- package/dist/core/composition/analysis-metadata.d.ts.map +1 -0
- package/dist/core/composition/analysis-metadata.js +20 -0
- package/dist/core/composition/analysis-metadata.js.map +1 -0
- package/dist/core/composition/context.d.ts +4 -0
- package/dist/core/composition/context.d.ts.map +1 -1
- package/dist/core/composition/context.js +1 -0
- package/dist/core/composition/context.js.map +1 -1
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +39 -9
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/nested-status-cel.js +3 -3
- package/dist/core/composition/nested-status-cel.js.map +1 -1
- package/dist/core/constants/brands.d.ts +1 -1
- package/dist/core/constants/brands.d.ts.map +1 -1
- package/dist/core/constants/brands.js +1 -1
- package/dist/core/constants/brands.js.map +1 -1
- package/dist/core/dependencies/resolver.d.ts +3 -1
- package/dist/core/dependencies/resolver.d.ts.map +1 -1
- package/dist/core/dependencies/resolver.js +29 -13
- package/dist/core/dependencies/resolver.js.map +1 -1
- package/dist/core/deployment/crd-manager.d.ts +4 -1
- package/dist/core/deployment/crd-manager.d.ts.map +1 -1
- package/dist/core/deployment/crd-manager.js +9 -4
- package/dist/core/deployment/crd-manager.js.map +1 -1
- package/dist/core/deployment/deletion-result.d.ts +22 -0
- package/dist/core/deployment/deletion-result.d.ts.map +1 -0
- package/dist/core/deployment/deletion-result.js +108 -0
- package/dist/core/deployment/deletion-result.js.map +1 -0
- package/dist/core/deployment/deployment-state-discovery.d.ts +1 -0
- package/dist/core/deployment/deployment-state-discovery.d.ts.map +1 -1
- package/dist/core/deployment/deployment-state-discovery.js +89 -9
- package/dist/core/deployment/deployment-state-discovery.js.map +1 -1
- package/dist/core/deployment/direct-factory.d.ts +46 -25
- package/dist/core/deployment/direct-factory.d.ts.map +1 -1
- package/dist/core/deployment/direct-factory.js +536 -126
- package/dist/core/deployment/direct-factory.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +4 -1
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +105 -34
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/deployment/errors.d.ts +20 -1
- package/dist/core/deployment/errors.d.ts.map +1 -1
- package/dist/core/deployment/errors.js +55 -0
- package/dist/core/deployment/errors.js.map +1 -1
- package/dist/core/deployment/event-monitor.d.ts +4 -3
- package/dist/core/deployment/event-monitor.d.ts.map +1 -1
- package/dist/core/deployment/event-monitor.js +17 -5
- package/dist/core/deployment/event-monitor.js.map +1 -1
- package/dist/core/deployment/index.d.ts +1 -1
- package/dist/core/deployment/index.d.ts.map +1 -1
- package/dist/core/deployment/index.js +1 -1
- package/dist/core/deployment/index.js.map +1 -1
- package/dist/core/deployment/k8s-helpers.d.ts +1 -1
- package/dist/core/deployment/k8s-helpers.d.ts.map +1 -1
- package/dist/core/deployment/k8s-helpers.js +11 -5
- package/dist/core/deployment/k8s-helpers.js.map +1 -1
- package/dist/core/deployment/kro-factory.d.ts +54 -30
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +2002 -259
- package/dist/core/deployment/kro-factory.js.map +1 -1
- package/dist/core/deployment/kro-instance-admission.d.ts +17 -0
- package/dist/core/deployment/kro-instance-admission.d.ts.map +1 -0
- package/dist/core/deployment/kro-instance-admission.js +73 -0
- package/dist/core/deployment/kro-instance-admission.js.map +1 -0
- package/dist/core/deployment/kro-instance-safety.d.ts +0 -3
- package/dist/core/deployment/kro-instance-safety.d.ts.map +1 -1
- package/dist/core/deployment/kro-instance-safety.js +0 -11
- package/dist/core/deployment/kro-instance-safety.js.map +1 -1
- package/dist/core/deployment/kro-namespace-teardown.d.ts +63 -5
- package/dist/core/deployment/kro-namespace-teardown.d.ts.map +1 -1
- package/dist/core/deployment/kro-namespace-teardown.js +124 -12
- package/dist/core/deployment/kro-namespace-teardown.js.map +1 -1
- package/dist/core/deployment/kro-readiness.d.ts +2 -0
- package/dist/core/deployment/kro-readiness.d.ts.map +1 -1
- package/dist/core/deployment/kro-readiness.js +23 -6
- package/dist/core/deployment/kro-readiness.js.map +1 -1
- package/dist/core/deployment/poll-timeout.d.ts +1 -1
- package/dist/core/deployment/poll-timeout.d.ts.map +1 -1
- package/dist/core/deployment/poll-timeout.js +12 -1
- package/dist/core/deployment/poll-timeout.js.map +1 -1
- package/dist/core/deployment/resource-applier.d.ts +19 -3
- package/dist/core/deployment/resource-applier.d.ts.map +1 -1
- package/dist/core/deployment/resource-applier.js +263 -44
- package/dist/core/deployment/resource-applier.js.map +1 -1
- package/dist/core/deployment/rollback-manager.d.ts +6 -1
- package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
- package/dist/core/deployment/rollback-manager.js +38 -12
- package/dist/core/deployment/rollback-manager.js.map +1 -1
- package/dist/core/deployment/schema-cel-evaluator.d.ts.map +1 -1
- package/dist/core/deployment/schema-cel-evaluator.js +63 -0
- package/dist/core/deployment/schema-cel-evaluator.js.map +1 -1
- package/dist/core/deployment/shared-utilities.d.ts +11 -6
- package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
- package/dist/core/deployment/shared-utilities.js +169 -14
- package/dist/core/deployment/shared-utilities.js.map +1 -1
- package/dist/core/deployment/singleton-gitops.d.ts.map +1 -1
- package/dist/core/deployment/singleton-gitops.js +0 -2
- package/dist/core/deployment/singleton-gitops.js.map +1 -1
- package/dist/core/deployment/singleton-owner-drift.d.ts +11 -2
- package/dist/core/deployment/singleton-owner-drift.d.ts.map +1 -1
- package/dist/core/deployment/singleton-owner-drift.js +30 -13
- package/dist/core/deployment/singleton-owner-drift.js.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.d.ts +2 -0
- package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.js +19 -2
- package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.d.ts +3 -0
- package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.js +31 -8
- package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/index.d.ts +1 -1
- package/dist/core/deployment/strategies/index.d.ts.map +1 -1
- package/dist/core/deployment/strategies/index.js +1 -1
- package/dist/core/deployment/strategies/index.js.map +1 -1
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/expressions/analysis/cel-emitter.d.ts.map +1 -1
- package/dist/core/expressions/analysis/cel-emitter.js +17 -13
- package/dist/core/expressions/analysis/cel-emitter.js.map +1 -1
- package/dist/core/expressions/analysis/expression-metadata.d.ts +15 -0
- package/dist/core/expressions/analysis/expression-metadata.d.ts.map +1 -0
- package/dist/core/expressions/analysis/expression-metadata.js +10 -0
- package/dist/core/expressions/analysis/expression-metadata.js.map +1 -0
- package/dist/core/expressions/analysis/ready-when.d.ts +3 -0
- package/dist/core/expressions/analysis/ready-when.d.ts.map +1 -0
- package/dist/core/expressions/analysis/ready-when.js +127 -0
- package/dist/core/expressions/analysis/ready-when.js.map +1 -0
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts +3 -2
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.js +6 -4
- package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts +3 -2
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.js +12 -12
- package/dist/core/expressions/composition/composition-analyzer-ternary.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.js +68 -4
- package/dist/core/expressions/composition/composition-analyzer-traversal.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts +2 -0
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.js +2 -0
- package/dist/core/expressions/composition/composition-analyzer.js.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.js +14 -1
- package/dist/core/expressions/factory/status-builder-analyzer.js.map +1 -1
- package/dist/core/kubernetes/api.d.ts.map +1 -1
- package/dist/core/kubernetes/api.js +2 -3
- package/dist/core/kubernetes/api.js.map +1 -1
- package/dist/core/kubernetes/bun-api-client.d.ts +1 -1
- package/dist/core/kubernetes/bun-api-client.d.ts.map +1 -1
- package/dist/core/kubernetes/bun-api-client.js +17 -17
- package/dist/core/kubernetes/bun-api-client.js.map +1 -1
- package/dist/core/kubernetes/bun-http-library.d.ts +16 -0
- package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -1
- package/dist/core/kubernetes/bun-http-library.js +7 -1
- package/dist/core/kubernetes/bun-http-library.js.map +1 -1
- package/dist/core/kubernetes/bun-watch.d.ts +30 -0
- package/dist/core/kubernetes/bun-watch.d.ts.map +1 -0
- package/dist/core/kubernetes/bun-watch.js +144 -0
- package/dist/core/kubernetes/bun-watch.js.map +1 -0
- package/dist/core/kubernetes/client-node-runtime.d.ts +10 -0
- package/dist/core/kubernetes/client-node-runtime.d.ts.map +1 -0
- package/dist/core/kubernetes/client-node-runtime.js +15 -0
- package/dist/core/kubernetes/client-node-runtime.js.map +1 -0
- package/dist/core/kubernetes/client-provider.d.ts +21 -1
- package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
- package/dist/core/kubernetes/client-provider.js +10 -10
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/kubernetes/index.d.ts +1 -1
- package/dist/core/kubernetes/index.d.ts.map +1 -1
- package/dist/core/kubernetes/index.js.map +1 -1
- package/dist/core/metadata/resource-metadata.d.ts +24 -1
- package/dist/core/metadata/resource-metadata.d.ts.map +1 -1
- package/dist/core/metadata/resource-metadata.js +8 -4
- package/dist/core/metadata/resource-metadata.js.map +1 -1
- package/dist/core/planning/artifact-codec.d.ts +14 -0
- package/dist/core/planning/artifact-codec.d.ts.map +1 -0
- package/dist/core/planning/artifact-codec.js +329 -0
- package/dist/core/planning/artifact-codec.js.map +1 -0
- package/dist/core/planning/artifacts.d.ts +212 -0
- package/dist/core/planning/artifacts.d.ts.map +1 -0
- package/dist/core/planning/artifacts.js +5 -0
- package/dist/core/planning/artifacts.js.map +1 -0
- package/dist/core/planning/aspects.d.ts +11 -0
- package/dist/core/planning/aspects.d.ts.map +1 -0
- package/dist/core/planning/aspects.js +77 -0
- package/dist/core/planning/aspects.js.map +1 -0
- package/dist/core/planning/canonical.d.ts +10 -0
- package/dist/core/planning/canonical.d.ts.map +1 -0
- package/dist/core/planning/canonical.js +54 -0
- package/dist/core/planning/canonical.js.map +1 -0
- package/dist/core/planning/capabilities.d.ts +16 -0
- package/dist/core/planning/capabilities.d.ts.map +1 -0
- package/dist/core/planning/capabilities.js +90 -0
- package/dist/core/planning/capabilities.js.map +1 -0
- package/dist/core/planning/capture.d.ts +29 -0
- package/dist/core/planning/capture.d.ts.map +1 -0
- package/dist/core/planning/capture.js +18 -0
- package/dist/core/planning/capture.js.map +1 -0
- package/dist/core/planning/compiler.d.ts +12 -0
- package/dist/core/planning/compiler.d.ts.map +1 -0
- package/dist/core/planning/compiler.js +631 -0
- package/dist/core/planning/compiler.js.map +1 -0
- package/dist/core/planning/direct-runtime-adapter.d.ts +39 -0
- package/dist/core/planning/direct-runtime-adapter.d.ts.map +1 -0
- package/dist/core/planning/direct-runtime-adapter.js +238 -0
- package/dist/core/planning/direct-runtime-adapter.js.map +1 -0
- package/dist/core/planning/execution-record.d.ts +21 -0
- package/dist/core/planning/execution-record.d.ts.map +1 -0
- package/dist/core/planning/execution-record.js +304 -0
- package/dist/core/planning/execution-record.js.map +1 -0
- package/dist/core/planning/index.d.ts +20 -0
- package/dist/core/planning/index.d.ts.map +1 -0
- package/dist/core/planning/index.js +18 -0
- package/dist/core/planning/index.js.map +1 -0
- package/dist/core/planning/kro-bundle.d.ts +24 -0
- package/dist/core/planning/kro-bundle.d.ts.map +1 -0
- package/dist/core/planning/kro-bundle.js +314 -0
- package/dist/core/planning/kro-bundle.js.map +1 -0
- package/dist/core/planning/kro-runtime-adapter.d.ts +28 -0
- package/dist/core/planning/kro-runtime-adapter.d.ts.map +1 -0
- package/dist/core/planning/kro-runtime-adapter.js +211 -0
- package/dist/core/planning/kro-runtime-adapter.js.map +1 -0
- package/dist/core/planning/materialization.d.ts +54 -0
- package/dist/core/planning/materialization.d.ts.map +1 -0
- package/dist/core/planning/materialization.js +489 -0
- package/dist/core/planning/materialization.js.map +1 -0
- package/dist/core/planning/plan-codec.d.ts +11 -0
- package/dist/core/planning/plan-codec.d.ts.map +1 -0
- package/dist/core/planning/plan-codec.js +555 -0
- package/dist/core/planning/plan-codec.js.map +1 -0
- package/dist/core/planning/planner.d.ts +21 -0
- package/dist/core/planning/planner.d.ts.map +1 -0
- package/dist/core/planning/planner.js +1518 -0
- package/dist/core/planning/planner.js.map +1 -0
- package/dist/core/planning/schema.d.ts +10 -0
- package/dist/core/planning/schema.d.ts.map +1 -0
- package/dist/core/planning/schema.js +159 -0
- package/dist/core/planning/schema.js.map +1 -0
- package/dist/core/planning/types.d.ts +441 -0
- package/dist/core/planning/types.d.ts.map +1 -0
- package/dist/core/planning/types.js +3 -0
- package/dist/core/planning/types.js.map +1 -0
- package/dist/core/planning/values.d.ts +76 -0
- package/dist/core/planning/values.d.ts.map +1 -0
- package/dist/core/planning/values.js +852 -0
- package/dist/core/planning/values.js.map +1 -0
- package/dist/core/proxy/create-resource.d.ts +12 -0
- package/dist/core/proxy/create-resource.d.ts.map +1 -1
- package/dist/core/proxy/create-resource.js +41 -9
- package/dist/core/proxy/create-resource.js.map +1 -1
- package/dist/core/readiness/evaluator-factories.d.ts +3 -1
- package/dist/core/readiness/evaluator-factories.d.ts.map +1 -1
- package/dist/core/readiness/evaluator-factories.js +130 -5
- package/dist/core/readiness/evaluator-factories.js.map +1 -1
- package/dist/core/readiness/index.d.ts +2 -1
- package/dist/core/readiness/index.d.ts.map +1 -1
- package/dist/core/readiness/index.js +2 -1
- package/dist/core/readiness/index.js.map +1 -1
- package/dist/core/readiness/portable-strategies.d.ts +28 -0
- package/dist/core/readiness/portable-strategies.d.ts.map +1 -0
- package/dist/core/readiness/portable-strategies.js +55 -0
- package/dist/core/readiness/portable-strategies.js.map +1 -0
- package/dist/core/readiness/strategy-configuration.d.ts +12 -0
- package/dist/core/readiness/strategy-configuration.d.ts.map +1 -0
- package/dist/core/readiness/strategy-configuration.js +51 -0
- package/dist/core/readiness/strategy-configuration.js.map +1 -0
- package/dist/core/references/cel-evaluator.d.ts +5 -0
- package/dist/core/references/cel-evaluator.d.ts.map +1 -1
- package/dist/core/references/cel-evaluator.js +33 -2
- package/dist/core/references/cel-evaluator.js.map +1 -1
- package/dist/core/references/cel.d.ts +10 -7
- package/dist/core/references/cel.d.ts.map +1 -1
- package/dist/core/references/cel.js +13 -12
- package/dist/core/references/cel.js.map +1 -1
- package/dist/core/references/external-refs.d.ts +2 -0
- package/dist/core/references/external-refs.d.ts.map +1 -1
- package/dist/core/references/external-refs.js +3 -1
- package/dist/core/references/external-refs.js.map +1 -1
- package/dist/core/references/resolver.d.ts +7 -1
- package/dist/core/references/resolver.d.ts.map +1 -1
- package/dist/core/references/resolver.js +91 -9
- package/dist/core/references/resolver.js.map +1 -1
- package/dist/core/references/schema-proxy.d.ts.map +1 -1
- package/dist/core/references/schema-proxy.js +35 -21
- package/dist/core/references/schema-proxy.js.map +1 -1
- package/dist/core/resources/factory-registry.d.ts +64 -2
- package/dist/core/resources/factory-registry.d.ts.map +1 -1
- package/dist/core/resources/factory-registry.js +74 -3
- package/dist/core/resources/factory-registry.js.map +1 -1
- package/dist/core/resources/index.d.ts +3 -2
- package/dist/core/resources/index.d.ts.map +1 -1
- package/dist/core/resources/index.js +2 -1
- package/dist/core/resources/index.js.map +1 -1
- package/dist/core/resources/live-comparison.d.ts +58 -0
- package/dist/core/resources/live-comparison.d.ts.map +1 -0
- package/dist/core/resources/live-comparison.js +293 -0
- package/dist/core/resources/live-comparison.js.map +1 -0
- package/dist/core/runtime/standalone-operation.d.ts +12 -0
- package/dist/core/runtime/standalone-operation.d.ts.map +1 -0
- package/dist/core/runtime/standalone-operation.js +62 -0
- package/dist/core/runtime/standalone-operation.js.map +1 -0
- package/dist/core/serialization/cel-references.d.ts +1 -0
- package/dist/core/serialization/cel-references.d.ts.map +1 -1
- package/dist/core/serialization/cel-references.js +12 -2
- package/dist/core/serialization/cel-references.js.map +1 -1
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +277 -182
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/serialization/schema.d.ts.map +1 -1
- package/dist/core/serialization/schema.js +350 -26
- package/dist/core/serialization/schema.js.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.js +14 -11
- package/dist/core/serialization/status-analysis-pipeline.js.map +1 -1
- package/dist/core/serialization/yaml.d.ts +5 -1
- package/dist/core/serialization/yaml.d.ts.map +1 -1
- package/dist/core/serialization/yaml.js +35 -182
- package/dist/core/serialization/yaml.js.map +1 -1
- package/dist/core/types/deployment.d.ts +135 -14
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/kubernetes.d.ts +3 -0
- package/dist/core/types/kubernetes.d.ts.map +1 -1
- package/dist/core/types/resource-graph.d.ts +7 -1
- package/dist/core/types/resource-graph.d.ts.map +1 -1
- package/dist/core/types/serialization.d.ts +8 -1
- package/dist/core/types/serialization.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.js +25 -16
- package/dist/core/validation/cel-validator.js.map +1 -1
- package/dist/experimental-planning.d.ts +13 -0
- package/dist/experimental-planning.d.ts.map +1 -0
- package/dist/experimental-planning.js +12 -0
- package/dist/experimental-planning.js.map +1 -0
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.js +29 -23
- package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -1
- package/dist/factories/apisix/compositions/apisix-helm-repository.d.ts +15 -0
- package/dist/factories/apisix/compositions/apisix-helm-repository.d.ts.map +1 -0
- package/dist/factories/apisix/compositions/apisix-helm-repository.js +28 -0
- package/dist/factories/apisix/compositions/apisix-helm-repository.js.map +1 -0
- package/dist/factories/apisix/compositions/index.d.ts +1 -0
- package/dist/factories/apisix/compositions/index.d.ts.map +1 -1
- package/dist/factories/apisix/compositions/index.js +1 -0
- package/dist/factories/apisix/compositions/index.js.map +1 -1
- package/dist/factories/apisix/types.d.ts +22 -0
- package/dist/factories/apisix/types.d.ts.map +1 -1
- package/dist/factories/apisix/types.js +16 -0
- package/dist/factories/apisix/types.js.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.d.ts +1 -0
- package/dist/factories/apisix/utils/admin-credentials.d.ts.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.js +5 -4
- package/dist/factories/apisix/utils/admin-credentials.js.map +1 -1
- package/dist/factories/caddy/compositions/caddy-ingress.d.ts.map +1 -1
- package/dist/factories/caddy/compositions/caddy-ingress.js +9 -2
- package/dist/factories/caddy/compositions/caddy-ingress.js.map +1 -1
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.d.ts.map +1 -1
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js +11 -25
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js.map +1 -1
- package/dist/factories/cert-manager/resources/certificates.d.ts.map +1 -1
- package/dist/factories/cert-manager/resources/certificates.js +2 -1
- package/dist/factories/cert-manager/resources/certificates.js.map +1 -1
- package/dist/factories/cert-manager/resources/challenges.d.ts.map +1 -1
- package/dist/factories/cert-manager/resources/challenges.js +3 -0
- package/dist/factories/cert-manager/resources/challenges.js.map +1 -1
- package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +4 -4
- package/dist/factories/cilium/resources/gateway.d.ts.map +1 -1
- package/dist/factories/cilium/resources/gateway.js +13 -12
- package/dist/factories/cilium/resources/gateway.js.map +1 -1
- package/dist/factories/cilium/resources/networking.d.ts.map +1 -1
- package/dist/factories/cilium/resources/networking.js +9 -0
- package/dist/factories/cilium/resources/networking.js.map +1 -1
- package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.d.ts +2 -1
- package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.d.ts.map +1 -1
- package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.js +2 -6
- package/dist/factories/clickhouse/compositions/clickhouse-operator-bootstrap.js.map +1 -1
- package/dist/factories/clickhouse/resources/installation.d.ts.map +1 -1
- package/dist/factories/clickhouse/resources/installation.js +2 -0
- package/dist/factories/clickhouse/resources/installation.js.map +1 -1
- package/dist/factories/clickhouse/resources/keeper.d.ts.map +1 -1
- package/dist/factories/clickhouse/resources/keeper.js +2 -0
- package/dist/factories/clickhouse/resources/keeper.js.map +1 -1
- package/dist/factories/clickhouse/types.d.ts +5 -2
- package/dist/factories/clickhouse/types.d.ts.map +1 -1
- package/dist/factories/clickhouse/types.js +2 -1
- package/dist/factories/clickhouse/types.js.map +1 -1
- package/dist/factories/clickstack/compositions/k8s-telemetry.d.ts +4 -2
- package/dist/factories/clickstack/compositions/k8s-telemetry.d.ts.map +1 -1
- package/dist/factories/clickstack/compositions/k8s-telemetry.js +2 -5
- package/dist/factories/clickstack/compositions/k8s-telemetry.js.map +1 -1
- package/dist/factories/clickstack/types.d.ts +2 -1
- package/dist/factories/clickstack/types.d.ts.map +1 -1
- package/dist/factories/clickstack/types.js +3 -1
- package/dist/factories/clickstack/types.js.map +1 -1
- package/dist/factories/clickstack/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts +2 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js +3 -8
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js.map +1 -1
- package/dist/factories/cnpg/resources/backup.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/backup.js +2 -0
- package/dist/factories/cnpg/resources/backup.js.map +1 -1
- package/dist/factories/cnpg/resources/cluster.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/cluster.js +2 -1
- package/dist/factories/cnpg/resources/cluster.js.map +1 -1
- package/dist/factories/cnpg/resources/pooler.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/pooler.js +2 -0
- package/dist/factories/cnpg/resources/pooler.js.map +1 -1
- package/dist/factories/cnpg/resources/scheduled-backup.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/scheduled-backup.js +2 -1
- package/dist/factories/cnpg/resources/scheduled-backup.js.map +1 -1
- package/dist/factories/cnpg/types.d.ts +6 -3
- package/dist/factories/cnpg/types.d.ts.map +1 -1
- package/dist/factories/cnpg/types.js +2 -1
- package/dist/factories/cnpg/types.js.map +1 -1
- package/dist/factories/dagster/compositions/dagster-bootstrap.d.ts +2 -0
- package/dist/factories/dagster/compositions/dagster-bootstrap.d.ts.map +1 -1
- package/dist/factories/dagster/types.d.ts +5 -0
- package/dist/factories/dagster/types.d.ts.map +1 -1
- package/dist/factories/dagster/types.js +1 -0
- package/dist/factories/dagster/types.js.map +1 -1
- package/dist/factories/dagster/utils/helm-values-mapper.js +3 -0
- package/dist/factories/dagster/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +7 -4
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.d.ts.map +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.js +20 -18
- package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -1
- package/dist/factories/flux/git-repository.d.ts.map +1 -1
- package/dist/factories/flux/git-repository.js +41 -34
- package/dist/factories/flux/git-repository.js.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts +0 -6
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.js +12 -2
- package/dist/factories/flux/kustomize/readiness-evaluators.js.map +1 -1
- package/dist/factories/harbor/compositions/harbor-installation.d.ts +46 -38
- package/dist/factories/harbor/compositions/harbor-installation.d.ts.map +1 -1
- package/dist/factories/harbor/compositions/harbor-installation.js +4 -2
- package/dist/factories/harbor/compositions/harbor-installation.js.map +1 -1
- package/dist/factories/harbor/types.d.ts +13 -11
- package/dist/factories/harbor/types.d.ts.map +1 -1
- package/dist/factories/harbor/types.js +11 -9
- package/dist/factories/harbor/types.js.map +1 -1
- package/dist/factories/helm/helm-repository.d.ts.map +1 -1
- package/dist/factories/helm/helm-repository.js +14 -1
- package/dist/factories/helm/helm-repository.js.map +1 -1
- package/dist/factories/helm/index.d.ts +2 -0
- package/dist/factories/helm/index.d.ts.map +1 -1
- package/dist/factories/helm/index.js +1 -0
- package/dist/factories/helm/index.js.map +1 -1
- package/dist/factories/helm/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/helm/readiness-evaluators.js +46 -7
- package/dist/factories/helm/readiness-evaluators.js.map +1 -1
- package/dist/factories/helm/status.d.ts +18 -0
- package/dist/factories/helm/status.d.ts.map +1 -0
- package/dist/factories/helm/status.js +38 -0
- package/dist/factories/helm/status.js.map +1 -0
- package/dist/factories/index.d.ts +2 -2
- package/dist/factories/index.d.ts.map +1 -1
- package/dist/factories/index.js +4 -4
- package/dist/factories/index.js.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.js +2 -4
- package/dist/factories/inngest/compositions/inngest-bootstrap.js.map +1 -1
- package/dist/factories/inngest/resources/helm.d.ts.map +1 -1
- package/dist/factories/inngest/resources/helm.js +12 -3
- package/dist/factories/inngest/resources/helm.js.map +1 -1
- package/dist/factories/inngest/types.d.ts +3 -7
- package/dist/factories/inngest/types.d.ts.map +1 -1
- package/dist/factories/inngest/types.js +1 -1
- package/dist/factories/inngest/types.js.map +1 -1
- package/dist/factories/kro/kro-crd.d.ts.map +1 -1
- package/dist/factories/kro/kro-crd.js +32 -33
- package/dist/factories/kro/kro-crd.js.map +1 -1
- package/dist/factories/kro/kro-custom-resource.d.ts.map +1 -1
- package/dist/factories/kro/kro-custom-resource.js +91 -85
- package/dist/factories/kro/kro-custom-resource.js.map +1 -1
- package/dist/factories/kro/resource-graph-definition.d.ts.map +1 -1
- package/dist/factories/kro/resource-graph-definition.js +127 -117
- package/dist/factories/kro/resource-graph-definition.js.map +1 -1
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.d.ts.map +1 -1
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.js +27 -26
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler-v1.js.map +1 -1
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.d.ts.map +1 -1
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.js +22 -22
- package/dist/factories/kubernetes/autoscaling/horizontal-pod-autoscaler.js.map +1 -1
- package/dist/factories/kubernetes/certificates/certificate-signing-request.d.ts.map +1 -1
- package/dist/factories/kubernetes/certificates/certificate-signing-request.js +41 -39
- package/dist/factories/kubernetes/certificates/certificate-signing-request.js.map +1 -1
- package/dist/factories/kubernetes/core/component-status.d.ts.map +1 -1
- package/dist/factories/kubernetes/core/component-status.js +14 -16
- package/dist/factories/kubernetes/core/component-status.js.map +1 -1
- package/dist/factories/kubernetes/core/namespace.d.ts.map +1 -1
- package/dist/factories/kubernetes/core/namespace.js +31 -38
- package/dist/factories/kubernetes/core/namespace.js.map +1 -1
- package/dist/factories/kubernetes/core/node.d.ts.map +1 -1
- package/dist/factories/kubernetes/core/node.js +17 -25
- package/dist/factories/kubernetes/core/node.js.map +1 -1
- package/dist/factories/kubernetes/core/pod.d.ts.map +1 -1
- package/dist/factories/kubernetes/core/pod.js +31 -35
- package/dist/factories/kubernetes/core/pod.js.map +1 -1
- package/dist/factories/kubernetes/extensions/custom-resource-definition.d.ts.map +1 -1
- package/dist/factories/kubernetes/extensions/custom-resource-definition.js +34 -36
- package/dist/factories/kubernetes/extensions/custom-resource-definition.js.map +1 -1
- package/dist/factories/kubernetes/extensions/custom-resource.d.ts.map +1 -1
- package/dist/factories/kubernetes/extensions/custom-resource.js +2 -4
- package/dist/factories/kubernetes/extensions/custom-resource.js.map +1 -1
- package/dist/factories/kubernetes/networking/endpoints.d.ts.map +1 -1
- package/dist/factories/kubernetes/networking/endpoints.js +29 -30
- package/dist/factories/kubernetes/networking/endpoints.js.map +1 -1
- package/dist/factories/kubernetes/networking/ingress.d.ts.map +1 -1
- package/dist/factories/kubernetes/networking/ingress.js +46 -57
- package/dist/factories/kubernetes/networking/ingress.js.map +1 -1
- package/dist/factories/kubernetes/networking/network-policy.d.ts +9 -0
- package/dist/factories/kubernetes/networking/network-policy.d.ts.map +1 -1
- package/dist/factories/kubernetes/networking/network-policy.js +51 -1
- package/dist/factories/kubernetes/networking/network-policy.js.map +1 -1
- package/dist/factories/kubernetes/networking/service.d.ts.map +1 -1
- package/dist/factories/kubernetes/networking/service.js +48 -40
- package/dist/factories/kubernetes/networking/service.js.map +1 -1
- package/dist/factories/kubernetes/policy/pod-disruption-budget.d.ts.map +1 -1
- package/dist/factories/kubernetes/policy/pod-disruption-budget.js +40 -46
- package/dist/factories/kubernetes/policy/pod-disruption-budget.js.map +1 -1
- package/dist/factories/kubernetes/policy/resource-quota.d.ts.map +1 -1
- package/dist/factories/kubernetes/policy/resource-quota.js +30 -34
- package/dist/factories/kubernetes/policy/resource-quota.js.map +1 -1
- package/dist/factories/kubernetes/storage/persistent-volume-claim.d.ts.map +1 -1
- package/dist/factories/kubernetes/storage/persistent-volume-claim.js +22 -21
- package/dist/factories/kubernetes/storage/persistent-volume-claim.js.map +1 -1
- package/dist/factories/kubernetes/storage/persistent-volume.d.ts.map +1 -1
- package/dist/factories/kubernetes/storage/persistent-volume.js +32 -38
- package/dist/factories/kubernetes/storage/persistent-volume.js.map +1 -1
- package/dist/factories/kubernetes/storage/volume-attachment.d.ts.map +1 -1
- package/dist/factories/kubernetes/storage/volume-attachment.js +12 -11
- package/dist/factories/kubernetes/storage/volume-attachment.js.map +1 -1
- package/dist/factories/kubernetes/workloads/cron-job.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/cron-job.js +39 -34
- package/dist/factories/kubernetes/workloads/cron-job.js.map +1 -1
- package/dist/factories/kubernetes/workloads/daemon-set.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/daemon-set.js +24 -23
- package/dist/factories/kubernetes/workloads/daemon-set.js.map +1 -1
- package/dist/factories/kubernetes/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/deployment.js +45 -35
- package/dist/factories/kubernetes/workloads/deployment.js.map +1 -1
- package/dist/factories/kubernetes/workloads/job.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/job.js +87 -77
- package/dist/factories/kubernetes/workloads/job.js.map +1 -1
- package/dist/factories/kubernetes/workloads/replica-set.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/replica-set.js +24 -24
- package/dist/factories/kubernetes/workloads/replica-set.js.map +1 -1
- package/dist/factories/kubernetes/workloads/replication-controller.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/replication-controller.js +37 -26
- package/dist/factories/kubernetes/workloads/replication-controller.js.map +1 -1
- package/dist/factories/kubernetes/workloads/stateful-set.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/stateful-set.js +69 -55
- package/dist/factories/kubernetes/workloads/stateful-set.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-artifact-applier.d.ts +15 -0
- package/dist/factories/kubernetes/yaml/yaml-artifact-applier.d.ts.map +1 -0
- package/dist/factories/kubernetes/yaml/yaml-artifact-applier.js +69 -0
- package/dist/factories/kubernetes/yaml/yaml-artifact-applier.js.map +1 -0
- package/dist/factories/kubernetes/yaml/yaml-directory.d.ts +2 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.js +3 -34
- package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts +13 -91
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.js +21 -311
- package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
- package/dist/factories/nats/compositions/nats-bootstrap.d.ts +2 -2
- package/dist/factories/nats/compositions/nats-bootstrap.d.ts.map +1 -1
- package/dist/factories/nats/compositions/nats-bootstrap.js +2 -3
- package/dist/factories/nats/compositions/nats-bootstrap.js.map +1 -1
- package/dist/factories/nats/resources/jetstream.d.ts.map +1 -1
- package/dist/factories/nats/resources/jetstream.js +2 -0
- package/dist/factories/nats/resources/jetstream.js.map +1 -1
- package/dist/factories/nats/types.d.ts +3 -3
- package/dist/factories/nats/types.js +1 -1
- package/dist/factories/nats/types.js.map +1 -1
- package/dist/factories/ory/compositions/ory-platform-stack.d.ts.map +1 -1
- package/dist/factories/ory/compositions/ory-platform-stack.js +35 -9
- package/dist/factories/ory/compositions/ory-platform-stack.js.map +1 -1
- package/dist/factories/ory/resources/oathkeeper-rule.d.ts.map +1 -1
- package/dist/factories/ory/resources/oathkeeper-rule.js +11 -8
- package/dist/factories/ory/resources/oathkeeper-rule.js.map +1 -1
- package/dist/factories/ory/resources/oauth2-client.d.ts.map +1 -1
- package/dist/factories/ory/resources/oauth2-client.js +12 -9
- package/dist/factories/ory/resources/oauth2-client.js.map +1 -1
- package/dist/factories/ory/types.d.ts +3 -0
- package/dist/factories/ory/types.d.ts.map +1 -1
- package/dist/factories/ory/types.js +1 -0
- package/dist/factories/ory/types.js.map +1 -1
- package/dist/factories/ory/utils/helm-values-mapper.d.ts.map +1 -1
- package/dist/factories/ory/utils/helm-values-mapper.js +6 -3
- package/dist/factories/ory/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/rook/compositions/rook-ceph-operator-installation.d.ts +2 -2
- package/dist/factories/rook/compositions/rook-ceph-operator-installation.d.ts.map +1 -1
- package/dist/factories/rook/compositions/rook-ceph-operator-installation.js +2 -3
- package/dist/factories/rook/compositions/rook-ceph-operator-installation.js.map +1 -1
- package/dist/factories/rook/compositions/rook-ceph-platform.d.ts +104 -0
- package/dist/factories/rook/compositions/rook-ceph-platform.d.ts.map +1 -1
- package/dist/factories/rook/resources/ceph-object-store-user.d.ts.map +1 -1
- package/dist/factories/rook/resources/ceph-object-store-user.js +2 -0
- package/dist/factories/rook/resources/ceph-object-store-user.js.map +1 -1
- package/dist/factories/rook/resources/ceph-object-store.d.ts.map +1 -1
- package/dist/factories/rook/resources/ceph-object-store.js +2 -0
- package/dist/factories/rook/resources/ceph-object-store.js.map +1 -1
- package/dist/factories/rook/resources/object-bucket-claim.d.ts.map +1 -1
- package/dist/factories/rook/resources/object-bucket-claim.js +2 -0
- package/dist/factories/rook/resources/object-bucket-claim.js.map +1 -1
- package/dist/factories/rook/types.d.ts +105 -1
- package/dist/factories/rook/types.d.ts.map +1 -1
- package/dist/factories/rook/types.js +10 -1
- package/dist/factories/rook/types.js.map +1 -1
- package/dist/factories/rook/utils/helm-values-mapper.d.ts +1 -1
- package/dist/factories/rook/utils/helm-values-mapper.d.ts.map +1 -1
- package/dist/factories/rook/utils/helm-values-mapper.js +19 -7
- package/dist/factories/rook/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts.map +1 -1
- package/dist/factories/searxng/compositions/searxng-bootstrap.js +2 -2
- package/dist/factories/searxng/compositions/searxng-bootstrap.js.map +1 -1
- package/dist/factories/searxng/resources/searxng.d.ts.map +1 -1
- package/dist/factories/searxng/resources/searxng.js +2 -0
- package/dist/factories/searxng/resources/searxng.js.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts +2 -2
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.js +4 -9
- package/dist/factories/valkey/compositions/valkey-bootstrap.js.map +1 -1
- package/dist/factories/valkey/resources/valkey.d.ts.map +1 -1
- package/dist/factories/valkey/resources/valkey.js +2 -1
- package/dist/factories/valkey/resources/valkey.js.map +1 -1
- package/dist/factories/valkey/types.d.ts +3 -7
- package/dist/factories/valkey/types.d.ts.map +1 -1
- package/dist/factories/valkey/types.js +1 -1
- package/dist/factories/valkey/types.js.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts +3 -2
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.js +7 -6
- package/dist/factories/webapp/compositions/web-app-with-processing.js.map +1 -1
- package/dist/factories/webapp/types.d.ts +1 -0
- package/dist/factories/webapp/types.d.ts.map +1 -1
- package/dist/factories/webapp/types.js +2 -0
- package/dist/factories/webapp/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/brands.d.ts +7 -0
- package/dist/shared/brands.d.ts.map +1 -1
- package/dist/shared/brands.js +7 -0
- package/dist/shared/brands.js.map +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +20 -5
- package/dist/factories/kubernetes/yaml/conflict-handler.d.ts +0 -23
- package/dist/factories/kubernetes/yaml/conflict-handler.d.ts.map +0 -1
- package/dist/factories/kubernetes/yaml/conflict-handler.js +0 -83
- package/dist/factories/kubernetes/yaml/conflict-handler.js.map +0 -1
|
@@ -8,8 +8,10 @@ import { compile as compileExpression } from 'angular-expressions';
|
|
|
8
8
|
import * as yaml from 'js-yaml';
|
|
9
9
|
import { createAlchemyResourceId } from '../../alchemy/utilities.js';
|
|
10
10
|
import { preserveNonEnumerableProperties } from '../../utils/helpers.js';
|
|
11
|
+
import { shortStableHash, toCamelCase } from '../../utils/string.js';
|
|
11
12
|
import { isKubernetesRef } from '../../utils/type-guards.js';
|
|
12
13
|
import { applyAspects } from '../aspects/apply.js';
|
|
14
|
+
import { copyCompositionAnalysisMetadata } from '../composition/analysis-metadata.js';
|
|
13
15
|
import { createCompositionContext, runWithCompositionContext } from '../composition/context.js';
|
|
14
16
|
import { buildNestedCompositionAliasTargets } from '../composition/nested-status-cel.js';
|
|
15
17
|
import { DEFAULT_DEPLOYMENT_TIMEOUT, DEFAULT_KRO_INSTANCE_TIMEOUT, DEFAULT_RGD_TIMEOUT, } from '../config/defaults.js';
|
|
@@ -19,31 +21,33 @@ import { isStrictCelDiagnosticsEnabled } from '../expressions/analysis/strict-ce
|
|
|
19
21
|
import { applyAnalysisToResources } from '../expressions/composition/composition-analyzer.js';
|
|
20
22
|
import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
|
|
21
23
|
import { getComponentLogger } from '../logging/index.js';
|
|
22
|
-
import { namespace as namespaceResource } from '../../factories/kubernetes/core/namespace.js';
|
|
23
|
-
import { shortStableHash, toCamelCase } from '../../utils/string.js';
|
|
24
24
|
// Dependency inversion: kroCustomResource, resourceGraphDefinition, and
|
|
25
25
|
// alchemy bridge are injected via FactoryOptions providers (Phase 3.5)
|
|
26
26
|
// instead of dynamic import() from higher layers.
|
|
27
|
-
import { copyResourceMetadata, getMetadataField, getReadinessEvaluator, getResourceScope, setMetadataField, setReadinessEvaluator, } from '../metadata/index.js';
|
|
28
|
-
import {
|
|
27
|
+
import { copyResourceMetadata, getMetadataField, getReadinessEvaluator, getResourceScope, setMetadataField, setReadinessEvaluator, setResourceId, } from '../metadata/index.js';
|
|
28
|
+
import { assertAdapterCapabilitiesSupported, canonicalStringify, collectArtifactOutputUses, collectPlanValueSensitiveBindings, compileKroArtifactPlan, createKroArtifactBundle, decodeKroArtifactBundle, encodeKroArtifactBundle, KRO_ARTIFACT_BINDINGS_SPEC_FIELD, kroArtifactOutputField, kroArtifactPlanToGraphResources, kroArtifactPlanToInstanceResource, kroArtifactPlanToSupportingResources, kroArtifactRequirementField, lowerPlanValue, mapPlanValueSensitiveBindings, materializeKroArtifactBundleOperation, materializePlanValue, mergeKroArtifactBundleOperations, orderKroArtifactBundleOperations, planValueSensitiveBindingNames, resolveStaticYamlSensitiveBindings, schemaToIR, } from '../planning/index.js';
|
|
29
|
+
import { createAlwaysReadyEvaluator, createCustomResourceDefinitionReadinessEvaluator, ensureReadinessEvaluator, getPortableReadinessStrategy, resolvePortableReadinessStrategy, } from '../readiness/index.js';
|
|
29
30
|
import { createSchemaProxy, DeploymentMode } from '../references/index.js';
|
|
31
|
+
import { runStandaloneOperation } from '../runtime/standalone-operation.js';
|
|
30
32
|
import { isStaticExpression } from '../serialization/cel-references.js';
|
|
31
33
|
import { applyTernaryConditionalsToResources } from '../serialization/kro-post-processing.js';
|
|
32
34
|
import { generateKroSchemaFromArktype } from '../serialization/schema.js';
|
|
33
|
-
import {
|
|
35
|
+
import { serializeResourceGraphDefinitionToYaml, serializeResourceGraphToManifest, } from '../serialization/yaml.js';
|
|
34
36
|
import { getSingletonResourceId } from '../singleton/singleton.js';
|
|
35
37
|
import { validateStatusCelExpressions } from '../validation/cel-validator.js';
|
|
36
38
|
import { KubernetesClientManager } from './client-provider-manager.js';
|
|
39
|
+
import { blockerForRemainingResource, createDeletionResultState, deletionTarget, finishDeletionResult, readDeletionResourceIdentity, } from './deletion-result.js';
|
|
37
40
|
import { DirectDeploymentEngine } from './engine.js';
|
|
38
41
|
import { logHandleSnapshot } from './handle-tracing.js';
|
|
39
42
|
import { isNotFoundError } from './k8s-helpers.js';
|
|
40
|
-
import {
|
|
43
|
+
import { assertKroInstanceSpecPreserved } from './kro-instance-admission.js';
|
|
44
|
+
import { assertKroInstanceNamespaceOwnershipSafe, assertNoHoistWeakenedStatusFields, concreteActiveOwnedResources, concreteOwnedNamespaceResources, findDanglingHoistedReference, resolveConcreteMetadataValue, resolveNamespaceName, rewriteHoistedNamespaceReferences, rewriteHoistedNamespaceRefsInValue, selectHoistedNamespaces, } from './kro-instance-safety.js';
|
|
41
45
|
import { decideNamespaceOwnershipCreateFirst, deleteNamespaceIfEmpty, HOISTED_NAMESPACES_ANNOTATION, listNamespacesOwnedByRgd, NAMESPACE_OWNER_ANNOTATION, parseHoistedNamespacesAnnotation, readHoistedNamespacesRecord, } from './kro-namespace-teardown.js';
|
|
42
|
-
import { createRollbackManager } from './rollback-manager.js';
|
|
43
46
|
import { waitForKroInstanceReady as waitForKroInstanceReadyShared } from './kro-readiness.js';
|
|
47
|
+
import { createRollbackManager } from './rollback-manager.js';
|
|
44
48
|
import { evaluateSchemaCelExpression } from './schema-cel-evaluator.js';
|
|
45
49
|
import { convertToKubernetesName, extractSerializableKubeConfigOptions, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateSpec, } from './shared-utilities.js';
|
|
46
|
-
import { joinYamlDocuments,
|
|
50
|
+
import { joinYamlDocuments, singletonOwnerInstanceManifests, singletonRgdYamls, } from './singleton-gitops.js';
|
|
47
51
|
import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationValue, } from './singleton-owner-drift.js';
|
|
48
52
|
/**
|
|
49
53
|
* Label stamped on every KRO instance CR this factory creates, keyed to the
|
|
@@ -51,6 +55,48 @@ import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationV
|
|
|
51
55
|
* to decide whether other instances still share the RGD.
|
|
52
56
|
*/
|
|
53
57
|
const INSTANCE_RGD_LABEL = 'typekro.io/rgd';
|
|
58
|
+
function uniqueArtifactRequirements(requirements) {
|
|
59
|
+
const byId = new Map();
|
|
60
|
+
for (const requirement of requirements) {
|
|
61
|
+
const prior = byId.get(requirement.id);
|
|
62
|
+
if (prior && canonicalStringify(prior) !== canonicalStringify(requirement)) {
|
|
63
|
+
throw new TypeKroError(`Artifact requirement ${requirement.id} has conflicting definitions across KRO bundle members.`, 'KRO_ARTIFACT_REQUIREMENT_CONFLICT', { requirementId: requirement.id });
|
|
64
|
+
}
|
|
65
|
+
byId.set(requirement.id, requirement);
|
|
66
|
+
}
|
|
67
|
+
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
68
|
+
}
|
|
69
|
+
function uniqueCapabilityRequirements(requirements) {
|
|
70
|
+
return [
|
|
71
|
+
...new Map(requirements.map((requirement) => [canonicalStringify(requirement), requirement])).values(),
|
|
72
|
+
].sort((left, right) => canonicalStringify(left).localeCompare(canonicalStringify(right)));
|
|
73
|
+
}
|
|
74
|
+
function kroBundleOperationId(resource) {
|
|
75
|
+
const name = resource.metadata?.name;
|
|
76
|
+
if (typeof resource.apiVersion !== 'string' || typeof resource.kind !== 'string' || !name) {
|
|
77
|
+
throw new TypeKroError('Cannot identify KRO bundle operation without a complete Kubernetes identity.', 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { apiVersion: resource.apiVersion, kind: resource.kind, name });
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
'k8s',
|
|
81
|
+
resource.apiVersion,
|
|
82
|
+
resource.kind,
|
|
83
|
+
resource.metadata?.namespace ?? '_cluster',
|
|
84
|
+
name,
|
|
85
|
+
]
|
|
86
|
+
.map((part) => encodeURIComponent(part))
|
|
87
|
+
.join(':');
|
|
88
|
+
}
|
|
89
|
+
function kroMemberSensitiveBinding(memberId, binding) {
|
|
90
|
+
return binding.startsWith('spec/') ? `kro/${encodeURIComponent(memberId)}/${binding}` : binding;
|
|
91
|
+
}
|
|
92
|
+
function mergeSensitiveBindingEnvelopes(target, incoming) {
|
|
93
|
+
for (const [binding, value] of Object.entries(incoming)) {
|
|
94
|
+
if (Object.hasOwn(target, binding) && !Object.is(target[binding], value)) {
|
|
95
|
+
throw new TypeKroError(`Sensitive binding ${binding} resolved to conflicting values across KRO bundle members.`, 'KRO_SENSITIVE_BINDING_CONFLICT', { binding });
|
|
96
|
+
}
|
|
97
|
+
target[binding] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
54
100
|
/**
|
|
55
101
|
* Decide whether the RGD/CRD should be preserved after a `deleteInstance`
|
|
56
102
|
* call, i.e., whether other instances still depend on it.
|
|
@@ -92,6 +138,100 @@ instances, targetName, instanceDeleted, targetNamespace) {
|
|
|
92
138
|
: instances;
|
|
93
139
|
return others.length > 0;
|
|
94
140
|
}
|
|
141
|
+
function recordNamespaceDeletionOutcome(state, namespace, outcome) {
|
|
142
|
+
const resource = deletionTarget('v1', 'Namespace', namespace);
|
|
143
|
+
if (outcome.status === 'deleted' || outcome.status === 'absent') {
|
|
144
|
+
// A bounded descendant-drain retry may first observe the Namespace as occupied and then
|
|
145
|
+
// confirm it gone. Keep the final DTO truthful by removing interim retention/blocker evidence.
|
|
146
|
+
const replaceWithFilteredEntries = (entries, keep) => {
|
|
147
|
+
entries.splice(0, entries.length, ...entries.filter(keep));
|
|
148
|
+
};
|
|
149
|
+
replaceWithFilteredEntries(state.retained, (entry) => !(entry.resource.kind === 'Namespace' && entry.resource.name === namespace));
|
|
150
|
+
replaceWithFilteredEntries(state.remaining, (entry) => !(entry.kind === 'Namespace' && entry.name === namespace));
|
|
151
|
+
replaceWithFilteredEntries(state.blockers, (entry) => !(entry.resource?.kind === 'Namespace' && entry.resource.name === namespace));
|
|
152
|
+
state.deleted.push(resource);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (outcome.cause === 'not-owned') {
|
|
156
|
+
state.retained.push({ resource, policy: 'adopted-resource', reason: outcome.reason });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (outcome.cause === 'occupied') {
|
|
160
|
+
state.retained.push({ resource, policy: 'occupied-namespace', reason: outcome.reason });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
state.retained.push({
|
|
164
|
+
resource,
|
|
165
|
+
policy: 'safety-proof-unavailable',
|
|
166
|
+
reason: outcome.reason,
|
|
167
|
+
});
|
|
168
|
+
state.remaining.push(resource);
|
|
169
|
+
state.blockers.push({
|
|
170
|
+
code: outcome.cause === 'read-failed' ? 'OWNERSHIP_UNPROVEN' : 'DISCOVERY_FAILED',
|
|
171
|
+
message: outcome.reason,
|
|
172
|
+
resource,
|
|
173
|
+
retryable: true,
|
|
174
|
+
retryGuidance: 'Retry after Kubernetes discovery and namespace reads are available; TypeKro retained the namespace to avoid destructive cleanup.',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const DESCENDANT_PRODUCING_KINDS = new Set([
|
|
178
|
+
'CronJob',
|
|
179
|
+
'DaemonSet',
|
|
180
|
+
'Deployment',
|
|
181
|
+
'HelmRelease',
|
|
182
|
+
'Job',
|
|
183
|
+
'Kustomization',
|
|
184
|
+
'ReplicaSet',
|
|
185
|
+
'ReplicationController',
|
|
186
|
+
'StatefulSet',
|
|
187
|
+
]);
|
|
188
|
+
/**
|
|
189
|
+
* Capture dependents that Kubernetes may orphan when KRO removes their direct
|
|
190
|
+
* controller before garbage collection completes. The UID match is the
|
|
191
|
+
* ownership proof: a same-named Pod from a replacement Job is never adopted.
|
|
192
|
+
*/
|
|
193
|
+
async function captureKnownControllerDescendants(k8sApi, childTargets, defaultNamespace) {
|
|
194
|
+
const descendants = [];
|
|
195
|
+
for (const target of childTargets) {
|
|
196
|
+
if (target.kind !== 'Job')
|
|
197
|
+
continue;
|
|
198
|
+
const namespace = target.namespace ?? defaultNamespace;
|
|
199
|
+
let live;
|
|
200
|
+
try {
|
|
201
|
+
live = await k8sApi.read({
|
|
202
|
+
apiVersion: target.apiVersion,
|
|
203
|
+
kind: target.kind,
|
|
204
|
+
metadata: { name: target.name, namespace },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const statusCode = error.statusCode ??
|
|
209
|
+
error.body?.code;
|
|
210
|
+
if (statusCode === 404)
|
|
211
|
+
continue;
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
const uid = live.metadata?.uid;
|
|
215
|
+
if (!uid) {
|
|
216
|
+
throw new Error(`Cannot prove descendant ownership for Job/${target.name}: UID is missing`);
|
|
217
|
+
}
|
|
218
|
+
const pods = await k8sApi.list('v1', 'Pod', namespace, undefined, undefined, undefined, undefined, `batch.kubernetes.io/controller-uid=${uid}`);
|
|
219
|
+
for (const pod of pods.items) {
|
|
220
|
+
const podName = pod.metadata?.name;
|
|
221
|
+
const controlledByJob = pod.metadata?.ownerReferences?.some((owner) => owner.uid === uid && owner.kind === 'Job' && owner.controller !== false);
|
|
222
|
+
if (!podName || !controlledByJob)
|
|
223
|
+
continue;
|
|
224
|
+
descendants.push({
|
|
225
|
+
apiVersion: 'v1',
|
|
226
|
+
kind: 'Pod',
|
|
227
|
+
name: podName,
|
|
228
|
+
namespace,
|
|
229
|
+
owner: { ...target, namespace, uid },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return descendants;
|
|
234
|
+
}
|
|
95
235
|
function deepMergeStatusPlaceholders(staticFields, dynamicFields) {
|
|
96
236
|
const merged = { ...staticFields };
|
|
97
237
|
for (const [key, dynamicValue] of Object.entries(dynamicFields)) {
|
|
@@ -167,6 +307,16 @@ export class KroResourceFactoryImpl {
|
|
|
167
307
|
this.singletonDefinitions = options.singletonDefinitions ?? [];
|
|
168
308
|
this.factoryOptions = options;
|
|
169
309
|
this.clientManager = new KubernetesClientManager(options);
|
|
310
|
+
const specSchema = schemaToIR(schemaDefinition.spec);
|
|
311
|
+
if (specSchema.root.kind === 'object' &&
|
|
312
|
+
specSchema.root.properties.some((property) => property.name === KRO_ARTIFACT_BINDINGS_SPEC_FIELD)) {
|
|
313
|
+
throw new TypeKroError(`Spec field ${KRO_ARTIFACT_BINDINGS_SPEC_FIELD} is reserved for TypeKro KRO provider bindings.`, 'KRO_RESERVED_SPEC_FIELD', {
|
|
314
|
+
field: KRO_ARTIFACT_BINDINGS_SPEC_FIELD,
|
|
315
|
+
surface: 'schema',
|
|
316
|
+
factory: name,
|
|
317
|
+
policyVersion: 1,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
170
320
|
// Pass the Arktype JSON so the proxy is shape-aware: spread
|
|
171
321
|
// (`{ ...schema.spec.X }`) enumerates declared fields and
|
|
172
322
|
// `Object.keys(schema.spec.X)` returns them. See the docstring on
|
|
@@ -180,7 +330,9 @@ export class KroResourceFactoryImpl {
|
|
|
180
330
|
}
|
|
181
331
|
/** Extract nested composition status CEL mappings from the raw status object. */
|
|
182
332
|
getNestedStatusCel() {
|
|
183
|
-
|
|
333
|
+
if (!this.statusMappings)
|
|
334
|
+
return undefined;
|
|
335
|
+
return Object.getOwnPropertyDescriptor(this.statusMappings, '__nestedStatusCel')?.value;
|
|
184
336
|
}
|
|
185
337
|
getSchemaVersion() {
|
|
186
338
|
return this.schemaDefinition.apiVersion.includes('/')
|
|
@@ -197,6 +349,19 @@ export class KroResourceFactoryImpl {
|
|
|
197
349
|
getInstanceApiVersion() {
|
|
198
350
|
return `${this.getSchemaGroup()}/${this.getSchemaVersion()}`;
|
|
199
351
|
}
|
|
352
|
+
assertNoReservedKroSpecField(spec) {
|
|
353
|
+
if (!spec ||
|
|
354
|
+
typeof spec !== 'object' ||
|
|
355
|
+
!Object.hasOwn(spec, KRO_ARTIFACT_BINDINGS_SPEC_FIELD)) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
throw new TypeKroError(`Spec field ${KRO_ARTIFACT_BINDINGS_SPEC_FIELD} is reserved for TypeKro KRO provider bindings.`, 'KRO_RESERVED_SPEC_FIELD', {
|
|
359
|
+
field: KRO_ARTIFACT_BINDINGS_SPEC_FIELD,
|
|
360
|
+
surface: 'instance-spec',
|
|
361
|
+
factory: this.name,
|
|
362
|
+
policyVersion: 1,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
200
365
|
/**
|
|
201
366
|
* Resolve the namespace the KRO instance CR lives in for this call.
|
|
202
367
|
*
|
|
@@ -310,6 +475,302 @@ export class KroResourceFactoryImpl {
|
|
|
310
475
|
const remaining = Object.fromEntries(Object.entries(resources).filter(([id]) => !hoistIds.has(id)));
|
|
311
476
|
return rewriteHoistedNamespaceReferences(remaining, hoistIds);
|
|
312
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Compile captured symbolic intent back into the established serializer input.
|
|
480
|
+
* Legacy resources remain a fallback for factories created outside the
|
|
481
|
+
* composition capture frontend.
|
|
482
|
+
*/
|
|
483
|
+
plannedResources() {
|
|
484
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
485
|
+
if (!capture) {
|
|
486
|
+
return this.resources;
|
|
487
|
+
}
|
|
488
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
489
|
+
const plan = capture.planTemplate({
|
|
490
|
+
...configuredPlan,
|
|
491
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
492
|
+
});
|
|
493
|
+
const outerInputs = this.outerArtifactCompilationInputs();
|
|
494
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
495
|
+
strict: true,
|
|
496
|
+
rgdName: this.rgdName,
|
|
497
|
+
...outerInputs,
|
|
498
|
+
...(this.factoryOptions.applyPolicy
|
|
499
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
500
|
+
: {}),
|
|
501
|
+
});
|
|
502
|
+
return kroArtifactPlanToGraphResources(artifacts);
|
|
503
|
+
}
|
|
504
|
+
/** Compile this factory's local graph and directly managed outer artifacts once. */
|
|
505
|
+
compiledKroArtifactPlan(spec, options = {}) {
|
|
506
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
507
|
+
if (!capture)
|
|
508
|
+
return undefined;
|
|
509
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
510
|
+
const planOptions = {
|
|
511
|
+
...configuredPlan,
|
|
512
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
513
|
+
};
|
|
514
|
+
const plan = capture.planTemplate(planOptions);
|
|
515
|
+
const concretePlan = spec === undefined ? undefined : capture.planSymbolic(spec, planOptions);
|
|
516
|
+
const memberId = options.memberId ?? 'root';
|
|
517
|
+
const instanceSpec = concretePlan
|
|
518
|
+
? mapPlanValueSensitiveBindings(concretePlan.spec, (binding) => kroMemberSensitiveBinding(memberId, binding))
|
|
519
|
+
: undefined;
|
|
520
|
+
const sensitiveBindings = spec !== undefined && instanceSpec
|
|
521
|
+
? collectPlanValueSensitiveBindings(instanceSpec, spec, `$.members.${memberId}.instance.spec`)
|
|
522
|
+
: {};
|
|
523
|
+
const instanceName = spec === undefined
|
|
524
|
+
? undefined
|
|
525
|
+
: (options.instanceNameOverride ?? generateInstanceName(spec, this.name));
|
|
526
|
+
const metadata = spec === undefined
|
|
527
|
+
? undefined
|
|
528
|
+
: this.instanceManifestMetadata(spec, options.singletonSpecFingerprint);
|
|
529
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
530
|
+
strict: true,
|
|
531
|
+
rgdName: this.rgdName,
|
|
532
|
+
...this.outerArtifactCompilationInputs(spec),
|
|
533
|
+
...(this.factoryOptions.applyPolicy
|
|
534
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
535
|
+
: {}),
|
|
536
|
+
...(spec !== undefined && instanceName && metadata && instanceSpec
|
|
537
|
+
? {
|
|
538
|
+
instance: {
|
|
539
|
+
name: lowerPlanValue(instanceName).value,
|
|
540
|
+
namespace: lowerPlanValue(this.resolveInstanceNamespace(spec)).value,
|
|
541
|
+
apiVersion: this.getInstanceApiVersion(),
|
|
542
|
+
kind: this.schemaDefinition.kind,
|
|
543
|
+
spec: instanceSpec,
|
|
544
|
+
labels: lowerPlanValue(metadata.labels).value,
|
|
545
|
+
annotations: lowerPlanValue(metadata.annotations).value,
|
|
546
|
+
},
|
|
547
|
+
}
|
|
548
|
+
: {}),
|
|
549
|
+
});
|
|
550
|
+
return { artifacts, sensitiveBindings };
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Recursively compile local plans into physical outer operations. Cross-member
|
|
554
|
+
* singleton edges resolve to the owner's concrete instance operation.
|
|
555
|
+
*/
|
|
556
|
+
compileKroBundleAssembly(spec, options) {
|
|
557
|
+
const compilation = this.compiledKroArtifactPlan(spec, options);
|
|
558
|
+
if (!compilation)
|
|
559
|
+
return undefined;
|
|
560
|
+
const { artifacts } = compilation;
|
|
561
|
+
const sensitiveBindings = {
|
|
562
|
+
...compilation.sensitiveBindings,
|
|
563
|
+
};
|
|
564
|
+
const childOperations = [];
|
|
565
|
+
const requiredCapabilities = [...artifacts.requiredCapabilities];
|
|
566
|
+
const artifactRequirements = [...artifacts.artifactRequirements];
|
|
567
|
+
const singletonOperationByPhysicalId = new Map();
|
|
568
|
+
const definitions = spec === undefined ? this.singletonDefinitions : this.discoverSingletonDefinitions(spec);
|
|
569
|
+
for (const definition of definitions) {
|
|
570
|
+
if (options.singletonStack.includes(definition.key)) {
|
|
571
|
+
throw new TypeKroError(`Singleton dependency cycle detected: ${[...options.singletonStack, definition.key].join(' -> ')}`, 'KRO_SINGLETON_DEPENDENCY_CYCLE', { singletonKey: definition.key });
|
|
572
|
+
}
|
|
573
|
+
const singletonFactory = this.singletonFactoryFor(definition);
|
|
574
|
+
const child = singletonFactory.compileKroBundleAssembly(definition.spec, {
|
|
575
|
+
memberId: `singleton:${definition.key}`,
|
|
576
|
+
root: false,
|
|
577
|
+
instanceNameOverride: getSingletonInstanceName(definition.id),
|
|
578
|
+
singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
|
|
579
|
+
singletonStack: [...options.singletonStack, definition.key],
|
|
580
|
+
});
|
|
581
|
+
if (!child?.instanceOperationId) {
|
|
582
|
+
throw new TypeKroError(`Singleton owner ${definition.key} did not compile a concrete instance operation.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { singletonKey: definition.key });
|
|
583
|
+
}
|
|
584
|
+
mergeSensitiveBindingEnvelopes(sensitiveBindings, child.sensitiveBindings);
|
|
585
|
+
childOperations.push(...child.operations);
|
|
586
|
+
requiredCapabilities.push(...child.requiredCapabilities);
|
|
587
|
+
artifactRequirements.push(...child.artifactRequirements);
|
|
588
|
+
const ownerOperation = child.operations.find((operation) => operation.id === child.instanceOperationId);
|
|
589
|
+
if (!ownerOperation) {
|
|
590
|
+
throw new TypeKroError(`Singleton owner ${definition.key} instance operation is absent from its bundle.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { singletonKey: definition.key, operationId: child.instanceOperationId });
|
|
591
|
+
}
|
|
592
|
+
singletonOperationByPhysicalId.set(ownerOperation.id, ownerOperation.id);
|
|
593
|
+
}
|
|
594
|
+
const source = {
|
|
595
|
+
memberId: options.memberId,
|
|
596
|
+
planIdentityDigest: artifacts.planIdentityDigest,
|
|
597
|
+
compiledArtifactDigest: artifacts.compiledArtifactDigest,
|
|
598
|
+
};
|
|
599
|
+
const operations = [...childOperations];
|
|
600
|
+
const operationIdByArtifactId = new Map();
|
|
601
|
+
for (const artifact of artifacts.resources.filter((candidate) => candidate.role === 'singleton-owner')) {
|
|
602
|
+
if (!artifact.identity) {
|
|
603
|
+
throw new TypeKroError(`Singleton artifact ${artifact.id} has no physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: artifact.id });
|
|
604
|
+
}
|
|
605
|
+
const name = materializePlanValue(artifact.identity.name, {
|
|
606
|
+
sensitive: sensitiveBindings,
|
|
607
|
+
});
|
|
608
|
+
const namespace = artifact.identity.namespace
|
|
609
|
+
? materializePlanValue(artifact.identity.namespace, {
|
|
610
|
+
sensitive: sensitiveBindings,
|
|
611
|
+
})
|
|
612
|
+
: undefined;
|
|
613
|
+
if (typeof name !== 'string' || (namespace !== undefined && typeof namespace !== 'string')) {
|
|
614
|
+
throw new TypeKroError(`Singleton artifact ${artifact.id} has a non-concrete physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: artifact.id, name, namespace });
|
|
615
|
+
}
|
|
616
|
+
const physicalId = kroBundleOperationId({
|
|
617
|
+
apiVersion: artifact.identity.apiVersion,
|
|
618
|
+
kind: artifact.identity.kind,
|
|
619
|
+
metadata: { name, ...(namespace ? { namespace } : {}) },
|
|
620
|
+
});
|
|
621
|
+
const ownerOperationId = singletonOperationByPhysicalId.get(physicalId);
|
|
622
|
+
if (!ownerOperationId) {
|
|
623
|
+
throw new TypeKroError(`Singleton artifact ${artifact.id} has no compiled owner member.`, 'KRO_SINGLETON_OWNER_ARTIFACT_MISSING', { artifactId: artifact.id, physicalId });
|
|
624
|
+
}
|
|
625
|
+
operationIdByArtifactId.set(artifact.id, ownerOperationId);
|
|
626
|
+
}
|
|
627
|
+
const nonSingletonArtifacts = {
|
|
628
|
+
...artifacts,
|
|
629
|
+
resources: artifacts.resources.filter((artifact) => artifact.role !== 'singleton-owner'),
|
|
630
|
+
};
|
|
631
|
+
for (const { artifact, resource } of kroArtifactPlanToSupportingResources(nonSingletonArtifacts, {
|
|
632
|
+
readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
|
|
633
|
+
resolveReadinessStrategy: resolvePortableReadinessStrategy,
|
|
634
|
+
})) {
|
|
635
|
+
if (artifact.role === 'singleton-owner') {
|
|
636
|
+
throw new TypeKroError(`Singleton artifact ${artifact.id} bypassed identity-only bundle lowering.`, 'KRO_SINGLETON_OWNER_ARTIFACT_INVALID', { artifactId: artifact.id });
|
|
637
|
+
}
|
|
638
|
+
const physicalId = kroBundleOperationId(resource);
|
|
639
|
+
operationIdByArtifactId.set(artifact.id, physicalId);
|
|
640
|
+
operations.push({
|
|
641
|
+
id: physicalId,
|
|
642
|
+
role: artifact.role,
|
|
643
|
+
sources: [source],
|
|
644
|
+
artifact,
|
|
645
|
+
manifest: lowerPlanValue(resource).value,
|
|
646
|
+
dependencies: [],
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const rgdArtifact = artifacts.resources.find((artifact) => artifact.role === 'resource-graph-definition');
|
|
650
|
+
if (!rgdArtifact || rgdArtifact.role !== 'resource-graph-definition') {
|
|
651
|
+
throw new TypeKroError(`KRO plan ${artifacts.compiledArtifactDigest} has no RGD artifact.`, 'KRO_RGD_ARTIFACT_MISSING');
|
|
652
|
+
}
|
|
653
|
+
const rgdManifest = this.buildRgdManifest();
|
|
654
|
+
const rgdOperationId = kroBundleOperationId(rgdManifest);
|
|
655
|
+
operationIdByArtifactId.set(rgdArtifact.id, rgdOperationId);
|
|
656
|
+
operations.push({
|
|
657
|
+
id: rgdOperationId,
|
|
658
|
+
role: options.root ? 'resource-graph-definition' : 'singleton-owner-rgd',
|
|
659
|
+
sources: [source],
|
|
660
|
+
artifact: rgdArtifact,
|
|
661
|
+
manifest: lowerPlanValue(rgdManifest).value,
|
|
662
|
+
dependencies: [],
|
|
663
|
+
});
|
|
664
|
+
let instanceOperationId;
|
|
665
|
+
if (spec !== undefined) {
|
|
666
|
+
const instanceArtifact = artifacts.resources.find((artifact) => artifact.role === 'instance');
|
|
667
|
+
if (!instanceArtifact || instanceArtifact.role !== 'instance') {
|
|
668
|
+
throw new TypeKroError(`KRO plan ${artifacts.compiledArtifactDigest} has no instance artifact.`, 'KRO_INSTANCE_ARTIFACT_MISSING');
|
|
669
|
+
}
|
|
670
|
+
if (!instanceArtifact.identity) {
|
|
671
|
+
throw new TypeKroError(`KRO instance artifact ${instanceArtifact.id} has no physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: instanceArtifact.id });
|
|
672
|
+
}
|
|
673
|
+
const instanceName = materializePlanValue(instanceArtifact.identity.name, {
|
|
674
|
+
sensitive: sensitiveBindings,
|
|
675
|
+
});
|
|
676
|
+
const instanceNamespace = instanceArtifact.identity.namespace
|
|
677
|
+
? materializePlanValue(instanceArtifact.identity.namespace, {
|
|
678
|
+
sensitive: sensitiveBindings,
|
|
679
|
+
})
|
|
680
|
+
: undefined;
|
|
681
|
+
if (typeof instanceName !== 'string' ||
|
|
682
|
+
(instanceNamespace !== undefined && typeof instanceNamespace !== 'string')) {
|
|
683
|
+
throw new TypeKroError(`KRO instance artifact ${instanceArtifact.id} has a non-concrete physical identity.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { artifactId: instanceArtifact.id, instanceName, instanceNamespace });
|
|
684
|
+
}
|
|
685
|
+
instanceOperationId = kroBundleOperationId({
|
|
686
|
+
apiVersion: instanceArtifact.identity.apiVersion,
|
|
687
|
+
kind: instanceArtifact.identity.kind,
|
|
688
|
+
metadata: {
|
|
689
|
+
name: instanceName,
|
|
690
|
+
...(instanceNamespace ? { namespace: instanceNamespace } : {}),
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
operationIdByArtifactId.set(instanceArtifact.id, instanceOperationId);
|
|
694
|
+
operations.push({
|
|
695
|
+
id: instanceOperationId,
|
|
696
|
+
role: options.root ? 'instance' : 'singleton-owner-instance',
|
|
697
|
+
sources: [source],
|
|
698
|
+
artifact: instanceArtifact,
|
|
699
|
+
manifest: instanceArtifact.desired,
|
|
700
|
+
dependencies: [],
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
const dependencies = new Map();
|
|
704
|
+
for (const edge of artifacts.edges) {
|
|
705
|
+
let prerequisite;
|
|
706
|
+
let dependent;
|
|
707
|
+
switch (edge.kind) {
|
|
708
|
+
case 'output':
|
|
709
|
+
prerequisite = operationIdByArtifactId.get(edge.producer);
|
|
710
|
+
dependent = operationIdByArtifactId.get(edge.consumer);
|
|
711
|
+
break;
|
|
712
|
+
case 'existence':
|
|
713
|
+
case 'ready':
|
|
714
|
+
prerequisite = operationIdByArtifactId.get(edge.prerequisite);
|
|
715
|
+
dependent = operationIdByArtifactId.get(edge.dependent);
|
|
716
|
+
break;
|
|
717
|
+
case 'ownership':
|
|
718
|
+
prerequisite = operationIdByArtifactId.get(edge.owner);
|
|
719
|
+
dependent = operationIdByArtifactId.get(edge.child);
|
|
720
|
+
break;
|
|
721
|
+
case 'delete-after':
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
if (!prerequisite || !dependent || prerequisite === dependent)
|
|
725
|
+
continue;
|
|
726
|
+
const incoming = dependencies.get(dependent) ?? new Set();
|
|
727
|
+
incoming.add(prerequisite);
|
|
728
|
+
dependencies.set(dependent, incoming);
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
operations: mergeKroArtifactBundleOperations(operations.map((operation) => ({
|
|
732
|
+
...operation,
|
|
733
|
+
dependencies: [
|
|
734
|
+
...new Set([
|
|
735
|
+
...operation.dependencies,
|
|
736
|
+
...(dependencies.get(operation.id) ?? new Set()),
|
|
737
|
+
]),
|
|
738
|
+
],
|
|
739
|
+
}))),
|
|
740
|
+
requiredCapabilities: uniqueCapabilityRequirements(requiredCapabilities),
|
|
741
|
+
artifactRequirements: uniqueArtifactRequirements(artifactRequirements),
|
|
742
|
+
rgdOperationId,
|
|
743
|
+
...(instanceOperationId ? { instanceOperationId } : {}),
|
|
744
|
+
sensitiveBindings,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/** Compile, canonicalize, and decode the complete KRO outer bundle. */
|
|
748
|
+
compiledKroArtifactBundle(spec, options = {}) {
|
|
749
|
+
const assembly = this.compileKroBundleAssembly(spec, {
|
|
750
|
+
memberId: 'root',
|
|
751
|
+
root: true,
|
|
752
|
+
...options,
|
|
753
|
+
singletonStack: [],
|
|
754
|
+
});
|
|
755
|
+
if (!assembly)
|
|
756
|
+
return undefined;
|
|
757
|
+
const bundle = createKroArtifactBundle({
|
|
758
|
+
root: {
|
|
759
|
+
memberId: 'root',
|
|
760
|
+
rgdOperationId: assembly.rgdOperationId,
|
|
761
|
+
...(assembly.instanceOperationId
|
|
762
|
+
? { instanceOperationId: assembly.instanceOperationId }
|
|
763
|
+
: {}),
|
|
764
|
+
},
|
|
765
|
+
operations: assembly.operations,
|
|
766
|
+
requiredCapabilities: assembly.requiredCapabilities,
|
|
767
|
+
artifactRequirements: assembly.artifactRequirements,
|
|
768
|
+
});
|
|
769
|
+
return {
|
|
770
|
+
bundle: decodeKroArtifactBundle(encodeKroArtifactBundle(bundle)),
|
|
771
|
+
sensitiveBindings: assembly.sensitiveBindings,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
313
774
|
/**
|
|
314
775
|
* Idempotently create the factory's target namespace if it doesn't
|
|
315
776
|
* exist. KRO does not auto-create the CR's containing namespace, and
|
|
@@ -320,13 +781,15 @@ export class KroResourceFactoryImpl {
|
|
|
320
781
|
* tolerance — ignored if the namespace already exists so concurrent
|
|
321
782
|
* callers don't collide.
|
|
322
783
|
*/
|
|
323
|
-
async ensureTargetNamespace(namespace = this.namespace) {
|
|
784
|
+
async ensureTargetNamespace(namespace = this.namespace, abortSignal) {
|
|
324
785
|
try {
|
|
786
|
+
abortSignal?.throwIfAborted();
|
|
325
787
|
const { createBunCompatibleKubernetesObjectApi } = await import('../kubernetes/bun-api-client.js');
|
|
326
788
|
const k8sApi = createBunCompatibleKubernetesObjectApi(this.getKubeConfig());
|
|
327
789
|
const waitForNamespaceDeletion = async () => {
|
|
328
790
|
const start = Date.now();
|
|
329
791
|
while (Date.now() - start < 120000) {
|
|
792
|
+
abortSignal?.throwIfAborted();
|
|
330
793
|
try {
|
|
331
794
|
const existing = (await k8sApi.read({
|
|
332
795
|
apiVersion: 'v1',
|
|
@@ -345,7 +808,25 @@ export class KroResourceFactoryImpl {
|
|
|
345
808
|
}
|
|
346
809
|
throw pollError;
|
|
347
810
|
}
|
|
348
|
-
await new Promise((resolve) =>
|
|
811
|
+
await new Promise((resolve, reject) => {
|
|
812
|
+
if (!abortSignal) {
|
|
813
|
+
setTimeout(resolve, 1000);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const onAbort = () => {
|
|
817
|
+
clearTimeout(timeout);
|
|
818
|
+
reject(abortSignal.reason ?? new DOMException('The operation was aborted', 'AbortError'));
|
|
819
|
+
};
|
|
820
|
+
const timeout = setTimeout(() => {
|
|
821
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
822
|
+
resolve();
|
|
823
|
+
}, 1000);
|
|
824
|
+
if (abortSignal.aborted) {
|
|
825
|
+
onAbort();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
829
|
+
});
|
|
349
830
|
}
|
|
350
831
|
throw new Error(`Namespace ${namespace} is still terminating after 120000ms`);
|
|
351
832
|
};
|
|
@@ -559,20 +1040,40 @@ export class KroResourceFactoryImpl {
|
|
|
559
1040
|
* Deploy a new instance by creating a custom resource
|
|
560
1041
|
*/
|
|
561
1042
|
async deploy(spec, opts) {
|
|
1043
|
+
if (opts?.operationSignal) {
|
|
1044
|
+
return this.deployWithinOperation(spec, opts, opts.operationSignal);
|
|
1045
|
+
}
|
|
1046
|
+
return runStandaloneOperation((abortSignal) => this.deployWithinOperation(spec, { ...opts, operationSignal: abortSignal }, abortSignal), { abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal] });
|
|
1047
|
+
}
|
|
1048
|
+
async deployWithinOperation(spec, opts, abortSignal) {
|
|
562
1049
|
if (opts?.targetScopes !== undefined) {
|
|
563
1050
|
throw new TypeKroError('Scope-targeted deployment is not supported in KRO mode. KRO manages resource lifecycle via its own controller. Use direct mode for scope-targeted deploys.', 'UNSUPPORTED_OPTION', { targetScopes: opts.targetScopes, mode: 'kro' });
|
|
564
1051
|
}
|
|
1052
|
+
this.assertNoReservedKroSpecField(spec);
|
|
565
1053
|
// Validate spec against ArkType schema
|
|
566
1054
|
validateSpec(spec, this.schemaDefinition, {
|
|
567
1055
|
kind: this.schemaDefinition.kind,
|
|
568
1056
|
name: this.name,
|
|
569
1057
|
});
|
|
570
1058
|
this.assertInstanceNamespaceOwnershipSafe(spec);
|
|
571
|
-
this.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
1059
|
+
const compiledBundle = this.compiledKroArtifactBundle(spec, {
|
|
1060
|
+
...(opts?.instanceNameOverride ? { instanceNameOverride: opts.instanceNameOverride } : {}),
|
|
1061
|
+
...(opts?.singletonSpecFingerprint
|
|
1062
|
+
? { singletonSpecFingerprint: opts.singletonSpecFingerprint }
|
|
1063
|
+
: {}),
|
|
1064
|
+
});
|
|
1065
|
+
this.assertBundleCapabilities(compiledBundle?.bundle, {
|
|
1066
|
+
host: 'standalone',
|
|
1067
|
+
output: 'live',
|
|
1068
|
+
});
|
|
1069
|
+
// Execute compatibility closures only after the complete semantic bundle
|
|
1070
|
+
// has passed host/output capability negotiation, but before RGD creation.
|
|
1071
|
+
await this.executeClosuresBeforeRGD(spec, abortSignal);
|
|
1072
|
+
if (compiledBundle) {
|
|
1073
|
+
return this.deployKroArtifactBundle(compiledBundle.bundle, spec, compiledBundle.sensitiveBindings, abortSignal);
|
|
1074
|
+
}
|
|
1075
|
+
await this.ensureSingletonOwners(spec, abortSignal);
|
|
1076
|
+
return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint, abortSignal);
|
|
576
1077
|
}
|
|
577
1078
|
/**
|
|
578
1079
|
* Discover the singleton-owner definitions this composition depends on (deduped by key): from
|
|
@@ -596,6 +1097,58 @@ export class KroResourceFactoryImpl {
|
|
|
596
1097
|
}
|
|
597
1098
|
return Array.from(discovered.values());
|
|
598
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Recreate only the runtime member contexts needed to execute a decoded KRO
|
|
1102
|
+
* bundle. Desired state and ordering remain authoritative in the bundle;
|
|
1103
|
+
* these contexts provide existing operational hooks, clients, and status
|
|
1104
|
+
* hydration for each compiled member.
|
|
1105
|
+
*/
|
|
1106
|
+
collectKroBundleRuntimeMembers(spec) {
|
|
1107
|
+
const rootFactory = this;
|
|
1108
|
+
const members = new Map([
|
|
1109
|
+
[
|
|
1110
|
+
'root',
|
|
1111
|
+
{
|
|
1112
|
+
memberId: 'root',
|
|
1113
|
+
factory: rootFactory,
|
|
1114
|
+
spec,
|
|
1115
|
+
consumers: [],
|
|
1116
|
+
dispose: false,
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
]);
|
|
1120
|
+
const visit = (member, stack) => {
|
|
1121
|
+
for (const definition of member.factory.discoverSingletonDefinitions(member.spec)) {
|
|
1122
|
+
if (stack.includes(definition.key)) {
|
|
1123
|
+
throw new TypeKroError(`Singleton dependency cycle detected: ${[...stack, definition.key].join(' -> ')}`, 'KRO_SINGLETON_DEPENDENCY_CYCLE', { singletonKey: definition.key });
|
|
1124
|
+
}
|
|
1125
|
+
const memberId = `singleton:${definition.key}`;
|
|
1126
|
+
const consumer = { factory: member.factory, definition };
|
|
1127
|
+
const existing = members.get(memberId);
|
|
1128
|
+
if (existing) {
|
|
1129
|
+
existing.consumers.push(consumer);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
const childFactory = member.factory.singletonFactoryFor(definition);
|
|
1133
|
+
const child = {
|
|
1134
|
+
memberId,
|
|
1135
|
+
factory: childFactory,
|
|
1136
|
+
spec: definition.spec,
|
|
1137
|
+
definition,
|
|
1138
|
+
consumers: [consumer],
|
|
1139
|
+
dispose: true,
|
|
1140
|
+
};
|
|
1141
|
+
members.set(memberId, child);
|
|
1142
|
+
visit(child, [...stack, definition.key]);
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
const root = members.get('root');
|
|
1146
|
+
if (!root) {
|
|
1147
|
+
throw new TypeKroError('KRO bundle runtime member collection lost the root member.', 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING');
|
|
1148
|
+
}
|
|
1149
|
+
visit(root, []);
|
|
1150
|
+
return members;
|
|
1151
|
+
}
|
|
599
1152
|
/** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
|
|
600
1153
|
singletonFactoryFor(definition) {
|
|
601
1154
|
return definition.composition.factory('kro', {
|
|
@@ -617,10 +1170,9 @@ export class KroResourceFactoryImpl {
|
|
|
617
1170
|
: {}),
|
|
618
1171
|
});
|
|
619
1172
|
}
|
|
620
|
-
async ensureSingletonOwners(spec) {
|
|
1173
|
+
async ensureSingletonOwners(spec, operationSignal) {
|
|
621
1174
|
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
622
|
-
|
|
623
|
-
await this.ensureTargetNamespace(definition.registryNamespace);
|
|
1175
|
+
await this.ensureTargetNamespace(definition.registryNamespace, operationSignal);
|
|
624
1176
|
const singletonInstanceName = getSingletonInstanceName(definition.id);
|
|
625
1177
|
const singletonFactory = this.singletonFactoryFor(definition);
|
|
626
1178
|
try {
|
|
@@ -629,11 +1181,12 @@ export class KroResourceFactoryImpl {
|
|
|
629
1181
|
singletonKey: definition.key,
|
|
630
1182
|
registryNamespace: definition.registryNamespace,
|
|
631
1183
|
});
|
|
632
|
-
const existingInstances = await this.getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition);
|
|
1184
|
+
const existingInstances = await this.getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition, operationSignal);
|
|
633
1185
|
assertNoDeployedSingletonSpecDrift(definition, singletonInstanceName, existingInstances);
|
|
634
1186
|
const singletonDeployOptions = {
|
|
635
1187
|
instanceNameOverride: singletonInstanceName,
|
|
636
1188
|
singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
|
|
1189
|
+
...(operationSignal ? { operationSignal } : {}),
|
|
637
1190
|
};
|
|
638
1191
|
const deployedSingleton = await singletonFactory.deploy(definition.spec, singletonDeployOptions);
|
|
639
1192
|
const singletonStatus = deployedSingleton.status;
|
|
@@ -648,9 +1201,9 @@ export class KroResourceFactoryImpl {
|
|
|
648
1201
|
}
|
|
649
1202
|
}
|
|
650
1203
|
}
|
|
651
|
-
async getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition) {
|
|
1204
|
+
async getSingletonOwnerInstancesForDriftCheck(singletonFactory, definition, operationSignal) {
|
|
652
1205
|
try {
|
|
653
|
-
return await singletonFactory.getInstances();
|
|
1206
|
+
return await singletonFactory.getInstances(operationSignal ? { operationSignal } : undefined);
|
|
654
1207
|
}
|
|
655
1208
|
catch (error) {
|
|
656
1209
|
if (this.isMissingSingletonOwnerCrdError(error)) {
|
|
@@ -680,7 +1233,7 @@ export class KroResourceFactoryImpl {
|
|
|
680
1233
|
* Execute closures before RGD creation (Kro mode requirement)
|
|
681
1234
|
* Closures must execute before ResourceGraphDefinition is created
|
|
682
1235
|
*/
|
|
683
|
-
async executeClosuresBeforeRGD(_spec) {
|
|
1236
|
+
async executeClosuresBeforeRGD(_spec, abortSignal) {
|
|
684
1237
|
if (Object.keys(this.closures).length === 0) {
|
|
685
1238
|
return []; // No closures to execute
|
|
686
1239
|
}
|
|
@@ -693,6 +1246,7 @@ export class KroResourceFactoryImpl {
|
|
|
693
1246
|
const mockDeploymentContext = {
|
|
694
1247
|
// kubernetesApi intentionally omitted - not needed for validation
|
|
695
1248
|
validationOnly: true,
|
|
1249
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
696
1250
|
namespace: this.namespace,
|
|
697
1251
|
deployedResources: new Map(),
|
|
698
1252
|
resolveReference: async (ref) => {
|
|
@@ -701,6 +1255,7 @@ export class KroResourceFactoryImpl {
|
|
|
701
1255
|
};
|
|
702
1256
|
for (const [closureName, closure] of Object.entries(this.closures)) {
|
|
703
1257
|
try {
|
|
1258
|
+
abortSignal?.throwIfAborted();
|
|
704
1259
|
// Execute the deployment closure with mock context to trigger validation
|
|
705
1260
|
await closure(mockDeploymentContext);
|
|
706
1261
|
}
|
|
@@ -721,6 +1276,7 @@ export class KroResourceFactoryImpl {
|
|
|
721
1276
|
const deploymentContext = {
|
|
722
1277
|
kubernetesApi: createBunCompatibleKubernetesObjectApi(kubeConfig),
|
|
723
1278
|
kubeConfig: kubeConfig,
|
|
1279
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
724
1280
|
namespace: this.namespace,
|
|
725
1281
|
deployedResources: new Map(), // Empty for pre-RGD execution
|
|
726
1282
|
resolveReference: async (ref) => {
|
|
@@ -730,6 +1286,7 @@ export class KroResourceFactoryImpl {
|
|
|
730
1286
|
// Execute closures sequentially to maintain order
|
|
731
1287
|
for (const [closureName, closure] of Object.entries(this.closures)) {
|
|
732
1288
|
try {
|
|
1289
|
+
abortSignal?.throwIfAborted();
|
|
733
1290
|
this.logger.debug('Executing closure', { name: closureName });
|
|
734
1291
|
// Note: We can't validate closure arguments here because we don't have access to them
|
|
735
1292
|
// The validation happens inside the closure when it processes its config
|
|
@@ -762,13 +1319,222 @@ export class KroResourceFactoryImpl {
|
|
|
762
1319
|
});
|
|
763
1320
|
return allResults;
|
|
764
1321
|
}
|
|
1322
|
+
assertBundleCapabilities(bundle, context) {
|
|
1323
|
+
const compatibilityRequirements = bundle?.requiredCapabilities ??
|
|
1324
|
+
(Object.keys(this.closures).length > 0
|
|
1325
|
+
? [
|
|
1326
|
+
{
|
|
1327
|
+
id: 'typekro.runtime-closure',
|
|
1328
|
+
version: 1,
|
|
1329
|
+
host: 'standalone',
|
|
1330
|
+
output: 'live',
|
|
1331
|
+
},
|
|
1332
|
+
]
|
|
1333
|
+
: []);
|
|
1334
|
+
assertAdapterCapabilitiesSupported(compatibilityRequirements, {
|
|
1335
|
+
target: 'kro',
|
|
1336
|
+
...context,
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
materializeKroBundleRuntimeResource(operation, member, sensitiveBindings) {
|
|
1340
|
+
const resource = materializeKroArtifactBundleOperation(operation, {
|
|
1341
|
+
sensitive: sensitiveBindings,
|
|
1342
|
+
});
|
|
1343
|
+
Object.defineProperty(resource, 'id', {
|
|
1344
|
+
value: operation.id,
|
|
1345
|
+
configurable: true,
|
|
1346
|
+
enumerable: false,
|
|
1347
|
+
});
|
|
1348
|
+
setResourceId(resource, operation.artifact.id);
|
|
1349
|
+
if (operation.artifact.identity?.scope) {
|
|
1350
|
+
setMetadataField(resource, 'scope', operation.artifact.identity.scope);
|
|
1351
|
+
}
|
|
1352
|
+
setMetadataField(resource, 'applyPolicy', operation.artifact.apply);
|
|
1353
|
+
const strategy = operation.artifact.readiness.strategy;
|
|
1354
|
+
if (strategy?.kind === 'runtime-binding') {
|
|
1355
|
+
const evaluator = member.factory.prerequisiteRuntimeReadinessEvaluators()[strategy.binding];
|
|
1356
|
+
if (!evaluator) {
|
|
1357
|
+
throw new TypeKroError(`KRO readiness binding ${strategy.binding} is unavailable for operation ${operation.id}.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, binding: strategy.binding });
|
|
1358
|
+
}
|
|
1359
|
+
setReadinessEvaluator(resource, evaluator);
|
|
1360
|
+
}
|
|
1361
|
+
else if (strategy?.kind === 'registered') {
|
|
1362
|
+
const evaluator = resolvePortableReadinessStrategy(strategy);
|
|
1363
|
+
if (!evaluator) {
|
|
1364
|
+
throw new TypeKroError(`Registered readiness strategy ${strategy.id}@${strategy.revision} is unavailable.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, strategy: strategy.id });
|
|
1365
|
+
}
|
|
1366
|
+
setReadinessEvaluator(resource, evaluator);
|
|
1367
|
+
}
|
|
1368
|
+
return resource;
|
|
1369
|
+
}
|
|
1370
|
+
/** Execute every directly managed KRO operation from one decoded artifact bundle. */
|
|
1371
|
+
async deployKroArtifactBundle(bundle, spec, sensitiveBindings, abortSignal) {
|
|
1372
|
+
const members = this.collectKroBundleRuntimeMembers(spec);
|
|
1373
|
+
const childMembers = [...members.values()].filter((member) => member.dispose);
|
|
1374
|
+
const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
|
|
1375
|
+
let rootResult;
|
|
1376
|
+
const memberFor = (operation) => {
|
|
1377
|
+
for (const source of operation.sources) {
|
|
1378
|
+
const member = members.get(source.memberId);
|
|
1379
|
+
if (member)
|
|
1380
|
+
return member;
|
|
1381
|
+
}
|
|
1382
|
+
throw new TypeKroError(`KRO bundle operation ${operation.id} has no runtime member context.`, 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING', { operationId: operation.id });
|
|
1383
|
+
};
|
|
1384
|
+
try {
|
|
1385
|
+
abortSignal?.throwIfAborted();
|
|
1386
|
+
// Preserve singleton operational checks and compatibility closures while
|
|
1387
|
+
// taking desired state and ordering exclusively from the decoded bundle.
|
|
1388
|
+
for (const member of childMembers) {
|
|
1389
|
+
const definition = member.definition;
|
|
1390
|
+
if (!definition) {
|
|
1391
|
+
throw new TypeKroError(`KRO bundle member ${member.memberId} has no singleton definition.`, 'KRO_ARTIFACT_BUNDLE_MEMBER_MISSING', { memberId: member.memberId });
|
|
1392
|
+
}
|
|
1393
|
+
await member.factory.ensureTargetNamespace(definition.registryNamespace, abortSignal);
|
|
1394
|
+
const existing = await member.factory.getSingletonOwnerInstancesForDriftCheck(member.factory, definition, abortSignal);
|
|
1395
|
+
assertNoDeployedSingletonSpecDrift(definition, getSingletonInstanceName(definition.id), existing);
|
|
1396
|
+
await member.factory.executeClosuresBeforeRGD(member.spec, abortSignal);
|
|
1397
|
+
}
|
|
1398
|
+
// Fail closed on pre-hoist upgrades before mutating any RGD, then ensure
|
|
1399
|
+
// every CR placement namespace not represented by a hoisted artifact.
|
|
1400
|
+
for (const member of members.values()) {
|
|
1401
|
+
abortSignal?.throwIfAborted();
|
|
1402
|
+
const hoistedNames = bundle.operations
|
|
1403
|
+
.filter((operation) => operation.role === 'hoisted-namespace' &&
|
|
1404
|
+
operation.sources.some((source) => source.memberId === member.memberId))
|
|
1405
|
+
.map((operation) => materializeKroArtifactBundleOperation(operation, {
|
|
1406
|
+
sensitive: sensitiveBindings,
|
|
1407
|
+
}).metadata.name)
|
|
1408
|
+
.filter((name) => typeof name === 'string');
|
|
1409
|
+
await member.factory.assertNoPreHoistNamespaceConflict(hoistedNames);
|
|
1410
|
+
const instanceOperation = bundle.operations.find((operation) => (operation.role === 'instance' || operation.role === 'singleton-owner-instance') &&
|
|
1411
|
+
operation.sources.some((source) => source.memberId === member.memberId));
|
|
1412
|
+
if (instanceOperation) {
|
|
1413
|
+
const instance = materializeKroArtifactBundleOperation(instanceOperation, {
|
|
1414
|
+
sensitive: sensitiveBindings,
|
|
1415
|
+
});
|
|
1416
|
+
const namespace = instance.metadata.namespace ?? member.factory.namespace;
|
|
1417
|
+
if (!hoistedNames.includes(namespace)) {
|
|
1418
|
+
await member.factory.ensureTargetNamespace(namespace, abortSignal);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
for (const operation of orderKroArtifactBundleOperations(bundle)) {
|
|
1423
|
+
abortSignal?.throwIfAborted();
|
|
1424
|
+
const member = memberFor(operation);
|
|
1425
|
+
const resource = this.materializeKroBundleRuntimeResource(operation, member, sensitiveBindings);
|
|
1426
|
+
const namespace = resource.metadata.namespace ?? member.factory.namespace;
|
|
1427
|
+
if (operation.role === 'kro-prerequisite') {
|
|
1428
|
+
const waitForReady = getReadinessEvaluator(resource) !== undefined;
|
|
1429
|
+
const deployable = waitForReady
|
|
1430
|
+
? ensureReadinessEvaluator(resource)
|
|
1431
|
+
: resource;
|
|
1432
|
+
await deploymentEngine.deployResource(deployable, {
|
|
1433
|
+
mode: 'direct',
|
|
1434
|
+
namespace,
|
|
1435
|
+
waitForReady,
|
|
1436
|
+
timeout: member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
|
|
1437
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
1438
|
+
});
|
|
1439
|
+
const crdName = member.factory.prerequisiteCRDName(resource);
|
|
1440
|
+
if (crdName) {
|
|
1441
|
+
await deploymentEngine.waitForCRDReady(crdName, member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT, abortSignal);
|
|
1442
|
+
}
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (operation.role === 'hoisted-namespace') {
|
|
1446
|
+
await member.factory.applyRetainedHoistedNamespace(resource, operation.artifact, abortSignal);
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (operation.role === 'resource-graph-definition' ||
|
|
1450
|
+
operation.role === 'singleton-owner-rgd') {
|
|
1451
|
+
await member.factory.runKroPrerequisiteHook(deploymentEngine, undefined, abortSignal);
|
|
1452
|
+
await member.factory.addRgdSchemaStatusPruneMarkers(resource);
|
|
1453
|
+
const rgdFactory = member.factory.rgdProvider ??
|
|
1454
|
+
(await import('../../factories/kro/resource-graph-definition.js'))
|
|
1455
|
+
.resourceGraphDefinition;
|
|
1456
|
+
const enhanced = rgdFactory(resource);
|
|
1457
|
+
copyResourceMetadata(resource, enhanced);
|
|
1458
|
+
const deployable = {
|
|
1459
|
+
...enhanced,
|
|
1460
|
+
id: operation.id,
|
|
1461
|
+
};
|
|
1462
|
+
copyResourceMetadata(enhanced, deployable);
|
|
1463
|
+
await deploymentEngine.deployResource(deployable, {
|
|
1464
|
+
mode: 'direct',
|
|
1465
|
+
namespace: member.factory.namespace,
|
|
1466
|
+
waitForReady: true,
|
|
1467
|
+
timeout: member.factory.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
|
|
1468
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
1469
|
+
});
|
|
1470
|
+
await member.factory.waitForCRDReadyWithEngine(deploymentEngine, abortSignal);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
const kroCustomResource = member.factory.kroCustomResourceProvider ??
|
|
1474
|
+
(await import('../../factories/kro/kro-custom-resource.js')).kroCustomResource;
|
|
1475
|
+
const resourceName = resource.metadata.name;
|
|
1476
|
+
if (typeof resourceName !== 'string' || resourceName.length === 0) {
|
|
1477
|
+
throw new TypeKroError(`KRO instance operation ${operation.id} has no concrete metadata.name.`, 'KRO_ARTIFACT_BUNDLE_IDENTITY_MISSING', { operationId: operation.id });
|
|
1478
|
+
}
|
|
1479
|
+
const enhanced = kroCustomResource({
|
|
1480
|
+
apiVersion: resource.apiVersion,
|
|
1481
|
+
kind: resource.kind,
|
|
1482
|
+
metadata: { ...resource.metadata, name: resourceName },
|
|
1483
|
+
spec: resource.spec,
|
|
1484
|
+
});
|
|
1485
|
+
copyResourceMetadata(resource, enhanced);
|
|
1486
|
+
const deployable = {
|
|
1487
|
+
...enhanced,
|
|
1488
|
+
id: operation.id,
|
|
1489
|
+
metadata: { ...enhanced.metadata, name: resourceName, namespace },
|
|
1490
|
+
spec: resource.spec,
|
|
1491
|
+
};
|
|
1492
|
+
copyResourceMetadata(enhanced, deployable);
|
|
1493
|
+
const deployed = await deploymentEngine.deployResource(deployable, {
|
|
1494
|
+
mode: 'kro',
|
|
1495
|
+
namespace,
|
|
1496
|
+
waitForReady: false,
|
|
1497
|
+
timeout: member.factory.factoryOptions.timeout || DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
1498
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
1499
|
+
});
|
|
1500
|
+
assertKroInstanceSpecPreserved(deployable, deployed.liveManifest ?? deployed.manifest);
|
|
1501
|
+
const shouldWait = operation.role === 'singleton-owner-instance' ||
|
|
1502
|
+
(member.factory.factoryOptions.waitForReady ?? true);
|
|
1503
|
+
if (shouldWait) {
|
|
1504
|
+
await member.factory.waitForKroInstanceReady(resourceName, member.factory.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, namespace, abortSignal);
|
|
1505
|
+
}
|
|
1506
|
+
const result = await member.factory.createEnhancedProxy(member.spec, resourceName);
|
|
1507
|
+
if (operation.role === 'instance') {
|
|
1508
|
+
rootResult = result;
|
|
1509
|
+
}
|
|
1510
|
+
else {
|
|
1511
|
+
const status = result.status;
|
|
1512
|
+
if (status && typeof status === 'object' && !Array.isArray(status)) {
|
|
1513
|
+
for (const consumer of member.consumers) {
|
|
1514
|
+
consumer.factory.singletonOwnerStatuses.set(getSingletonResourceId(consumer.definition.key), status);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (!rootResult) {
|
|
1520
|
+
throw new TypeKroError('KRO artifact bundle completed without producing the root instance.', 'KRO_ARTIFACT_BUNDLE_ROOT_RESULT_MISSING', { bundleDigest: bundle.bundleDigest });
|
|
1521
|
+
}
|
|
1522
|
+
return rootResult;
|
|
1523
|
+
}
|
|
1524
|
+
finally {
|
|
1525
|
+
await deploymentEngine.dispose();
|
|
1526
|
+
for (const member of childMembers.reverse()) {
|
|
1527
|
+
await member.factory.dispose();
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
765
1531
|
/**
|
|
766
1532
|
* Deploy directly to Kubernetes using DirectDeploymentEngine
|
|
767
1533
|
*/
|
|
768
|
-
async deployDirect(spec, instanceNameOverride, singletonSpecFingerprint) {
|
|
1534
|
+
async deployDirect(spec, instanceNameOverride, singletonSpecFingerprint, abortSignal) {
|
|
769
1535
|
const instanceNamespace = this.resolveInstanceNamespace(spec);
|
|
770
1536
|
const instanceName = instanceNameOverride ?? generateInstanceName(spec, this.name);
|
|
771
|
-
const hoistedNamespaces = this.
|
|
1537
|
+
const hoistedNamespaces = this.materializedHoistedNamespaces(spec);
|
|
772
1538
|
// FINDING #2: fail closed on a PRE-HOIST upgrade BEFORE touching the RGD. If a
|
|
773
1539
|
// namespace this factory now hoists is currently a KRO ApplySet member (a namespace
|
|
774
1540
|
// a pre-hoist RGD owned as a graph child), rolling the new hoisted RGD over the old
|
|
@@ -781,7 +1547,7 @@ export class KroResourceFactoryImpl {
|
|
|
781
1547
|
// owns a namespace and deleting the instance can never garbage-collect the
|
|
782
1548
|
// namespace holding its own finalizer. The hoist is STRUCTURAL, so the shared RGD
|
|
783
1549
|
// shape is the same for every instance (finding #4).
|
|
784
|
-
await this.ensureRGDDeployed(spec);
|
|
1550
|
+
await this.ensureRGDDeployed(spec, abortSignal);
|
|
785
1551
|
// Ensure the namespace that will hold the CR exists before posting it. KRO
|
|
786
1552
|
// reconciles resources from the RGD into their own namespaces, but the CR
|
|
787
1553
|
// instance itself must live in a namespace the user can write to. If the
|
|
@@ -794,10 +1560,10 @@ export class KroResourceFactoryImpl {
|
|
|
794
1560
|
// (all labels incl. Pod Security + spec) plus retention markers; a non-hoisted
|
|
795
1561
|
// instance namespace just needs to exist.
|
|
796
1562
|
if (!hoistedNamespaces.has(instanceNamespace)) {
|
|
797
|
-
await this.ensureTargetNamespace(instanceNamespace);
|
|
1563
|
+
await this.ensureTargetNamespace(instanceNamespace, abortSignal);
|
|
798
1564
|
}
|
|
799
|
-
for (const
|
|
800
|
-
await this.applyRetainedHoistedNamespace(
|
|
1565
|
+
for (const { artifact, resource } of hoistedNamespaces.values()) {
|
|
1566
|
+
await this.applyRetainedHoistedNamespace(resource, artifact, abortSignal);
|
|
801
1567
|
}
|
|
802
1568
|
// Create DirectDeploymentEngine with KRO mode for CEL string conversion
|
|
803
1569
|
const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
|
|
@@ -830,19 +1596,21 @@ export class KroResourceFactoryImpl {
|
|
|
830
1596
|
// Deploy without waiting for readiness - we'll handle that ourselves
|
|
831
1597
|
this.logger.info('Deploying Kro instance', { instanceName, rgdName: this.rgdName });
|
|
832
1598
|
try {
|
|
833
|
-
await deploymentEngine.deployResource(deployableResource, {
|
|
1599
|
+
const deployed = await deploymentEngine.deployResource(deployableResource, {
|
|
834
1600
|
mode: 'kro',
|
|
835
1601
|
namespace: instanceNamespace,
|
|
836
1602
|
waitForReady: false, // We'll handle Kro-specific readiness ourselves
|
|
837
1603
|
timeout: this.factoryOptions.timeout || DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
1604
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
838
1605
|
});
|
|
1606
|
+
assertKroInstanceSpecPreserved(deployableResource, deployed.liveManifest ?? deployed.manifest);
|
|
839
1607
|
this.logger.info('Instance deployed, checking readiness', {
|
|
840
1608
|
instanceName,
|
|
841
1609
|
rgdName: this.rgdName,
|
|
842
1610
|
});
|
|
843
1611
|
// Handle Kro-specific readiness checking if requested
|
|
844
1612
|
if (this.factoryOptions.waitForReady ?? true) {
|
|
845
|
-
await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, instanceNamespace); // 10 minutes
|
|
1613
|
+
await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT, instanceNamespace, abortSignal); // 10 minutes
|
|
846
1614
|
}
|
|
847
1615
|
this.logger.info('Instance ready, creating enhanced proxy', {
|
|
848
1616
|
instanceName,
|
|
@@ -867,23 +1635,41 @@ export class KroResourceFactoryImpl {
|
|
|
867
1635
|
* silently degrading to a bare `ensureTargetNamespace` that would drop Pod
|
|
868
1636
|
* Security + other declared configuration.
|
|
869
1637
|
*/
|
|
870
|
-
async applyRetainedHoistedNamespace(
|
|
871
|
-
|
|
1638
|
+
async applyRetainedHoistedNamespace(resource, artifact, abortSignal) {
|
|
1639
|
+
abortSignal?.throwIfAborted();
|
|
1640
|
+
const name = String(resource.metadata.name);
|
|
1641
|
+
const annotations = Object.fromEntries(Object.entries(resource.metadata.annotations ?? {}).map(([key, value]) => [
|
|
1642
|
+
key,
|
|
1643
|
+
String(value),
|
|
1644
|
+
]));
|
|
1645
|
+
const applyPolicy = artifact?.apply ?? {
|
|
1646
|
+
strategy: 'server-side-apply',
|
|
1647
|
+
fieldManager: 'typekro',
|
|
1648
|
+
fieldConflictPolicy: 'force-owned-fields',
|
|
1649
|
+
immutableFieldPolicy: 'fail',
|
|
1650
|
+
};
|
|
1651
|
+
if (applyPolicy.strategy !== 'server-side-apply') {
|
|
1652
|
+
throw new TypeKroError(`Hoisted Namespace artifact ${artifact?.id ?? name} requires server-side apply.`, 'INVALID_ARTIFACT_APPLY_POLICY', { artifactId: artifact?.id ?? name, strategy: applyPolicy.strategy });
|
|
1653
|
+
}
|
|
872
1654
|
const k8sApi = createBunCompatibleKubernetesObjectApi(this.getKubeConfig());
|
|
873
1655
|
const baseMetadata = {
|
|
874
|
-
name
|
|
875
|
-
labels:
|
|
876
|
-
...(
|
|
877
|
-
|
|
1656
|
+
name,
|
|
1657
|
+
...(resource.metadata.labels !== undefined ? { labels: resource.metadata.labels } : {}),
|
|
1658
|
+
...(resource.metadata.finalizers !== undefined
|
|
1659
|
+
? { finalizers: resource.metadata.finalizers }
|
|
1660
|
+
: {}),
|
|
1661
|
+
...(resource.metadata.ownerReferences !== undefined
|
|
1662
|
+
? { ownerReferences: resource.metadata.ownerReferences }
|
|
1663
|
+
: {}),
|
|
878
1664
|
};
|
|
879
|
-
const buildManifest = (
|
|
1665
|
+
const buildManifest = (manifestAnnotations) => ({
|
|
880
1666
|
apiVersion: 'v1',
|
|
881
1667
|
kind: 'Namespace',
|
|
882
|
-
metadata: { ...baseMetadata, annotations },
|
|
883
|
-
...(
|
|
1668
|
+
metadata: { ...baseMetadata, annotations: manifestAnnotations },
|
|
1669
|
+
...(resource.spec !== undefined ? { spec: resource.spec } : {}),
|
|
884
1670
|
});
|
|
885
1671
|
const annotationsWithStamp = {
|
|
886
|
-
...
|
|
1672
|
+
...annotations,
|
|
887
1673
|
[NAMESPACE_OWNER_ANNOTATION]: this.rgdName,
|
|
888
1674
|
};
|
|
889
1675
|
// CREATE-FIRST ownership (finding #3): attempt to CREATE the namespace WITH the
|
|
@@ -895,6 +1681,7 @@ export class KroResourceFactoryImpl {
|
|
|
895
1681
|
let decision;
|
|
896
1682
|
try {
|
|
897
1683
|
decision = await decideNamespaceOwnershipCreateFirst(k8sApi, buildManifest(annotationsWithStamp), this.rgdName);
|
|
1684
|
+
abortSignal?.throwIfAborted();
|
|
898
1685
|
}
|
|
899
1686
|
catch (error) {
|
|
900
1687
|
// A non-conflict CREATE failure (or a failed conflict-read) — do NOT fall back to a
|
|
@@ -907,11 +1694,9 @@ export class KroResourceFactoryImpl {
|
|
|
907
1694
|
return;
|
|
908
1695
|
// 409: the namespace already existed. Apply the retained config via SSA, keeping the
|
|
909
1696
|
// ownership stamp ONLY if we already own it (adoption must NOT be stamped).
|
|
910
|
-
const
|
|
1697
|
+
const appliedAnnotations = decision.owned ? annotationsWithStamp : annotations;
|
|
911
1698
|
try {
|
|
912
|
-
await k8sApi.patch(buildManifest(
|
|
913
|
-
true, // force (own the retention fields)
|
|
914
|
-
'application/apply-patch+yaml' // server-side apply
|
|
1699
|
+
await k8sApi.patch(buildManifest(appliedAnnotations), undefined, undefined, applyPolicy.fieldManager, applyPolicy.fieldConflictPolicy === 'force-owned-fields', 'application/apply-patch+yaml' // server-side apply
|
|
915
1700
|
);
|
|
916
1701
|
}
|
|
917
1702
|
catch (error) {
|
|
@@ -1114,7 +1899,16 @@ export class KroResourceFactoryImpl {
|
|
|
1114
1899
|
/**
|
|
1115
1900
|
* Get all deployed instances
|
|
1116
1901
|
*/
|
|
1117
|
-
async getInstances() {
|
|
1902
|
+
async getInstances(opts) {
|
|
1903
|
+
if (opts?.operationSignal) {
|
|
1904
|
+
return this.getInstancesWithinOperation(opts.operationSignal);
|
|
1905
|
+
}
|
|
1906
|
+
return runStandaloneOperation((abortSignal) => this.getInstancesWithinOperation(abortSignal), {
|
|
1907
|
+
abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
async getInstancesWithinOperation(abortSignal) {
|
|
1911
|
+
abortSignal.throwIfAborted();
|
|
1118
1912
|
const customApi = await this.createCustomObjectsApi();
|
|
1119
1913
|
try {
|
|
1120
1914
|
const version = this.getSchemaVersion();
|
|
@@ -1128,6 +1922,7 @@ export class KroResourceFactoryImpl {
|
|
|
1128
1922
|
if (!this.discoveredPlural) {
|
|
1129
1923
|
this.discoveredPlural = await this.lookupCRDPlural();
|
|
1130
1924
|
}
|
|
1925
|
+
abortSignal.throwIfAborted();
|
|
1131
1926
|
const plural = this.discoveredPlural ?? pluralizeKind(this.schemaDefinition.kind);
|
|
1132
1927
|
// The instance CR lives in the namespace {@link resolveInstanceNamespace}
|
|
1133
1928
|
// resolves to — the SAME shared resolver `deploy()`/`toYaml()` use, so a
|
|
@@ -1140,9 +1935,11 @@ export class KroResourceFactoryImpl {
|
|
|
1140
1935
|
namespace: this.resolveInstanceNamespace(),
|
|
1141
1936
|
plural,
|
|
1142
1937
|
});
|
|
1938
|
+
abortSignal.throwIfAborted();
|
|
1143
1939
|
const listResult = listResponse;
|
|
1144
1940
|
const instances = listResult.items || [];
|
|
1145
1941
|
return await Promise.all(instances.map(async (instance) => {
|
|
1942
|
+
abortSignal.throwIfAborted();
|
|
1146
1943
|
const enhanced = await this.createEnhancedProxy(instance.spec, instance.metadata?.name || 'unknown');
|
|
1147
1944
|
if (instance.metadata?.annotations) {
|
|
1148
1945
|
const mutableEnhanced = enhanced;
|
|
@@ -1162,6 +1959,7 @@ export class KroResourceFactoryImpl {
|
|
|
1162
1959
|
}));
|
|
1163
1960
|
}
|
|
1164
1961
|
catch (error) {
|
|
1962
|
+
abortSignal.throwIfAborted();
|
|
1165
1963
|
const k8sError = error;
|
|
1166
1964
|
// If the CRD doesn't exist yet or there are no instances, return empty array
|
|
1167
1965
|
const bodyString = typeof k8sError.body === 'string' ? k8sError.body : JSON.stringify(k8sError.body || '');
|
|
@@ -1206,6 +2004,11 @@ export class KroResourceFactoryImpl {
|
|
|
1206
2004
|
* Delete a specific instance by name
|
|
1207
2005
|
*/
|
|
1208
2006
|
async deleteInstance(name, opts) {
|
|
2007
|
+
return runStandaloneOperation((abortSignal) => this.deleteInstanceWithinOperation(name, opts, abortSignal), { abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal] });
|
|
2008
|
+
}
|
|
2009
|
+
async deleteInstanceWithinOperation(name, opts, abortSignal) {
|
|
2010
|
+
const deletion = createDeletionResultState('kro', this.name, name);
|
|
2011
|
+
abortSignal.throwIfAborted();
|
|
1209
2012
|
if (opts?.scopes?.length) {
|
|
1210
2013
|
throw new TypeKroError('Scope-filtered deletion is not supported in KRO mode. KRO manages resource lifecycle via its own controller. Use direct mode for scope-filtered deletes.', 'UNSUPPORTED_OPTION', { scopes: opts.scopes, instanceName: name, mode: 'kro' });
|
|
1211
2014
|
}
|
|
@@ -1215,7 +2018,7 @@ export class KroResourceFactoryImpl {
|
|
|
1215
2018
|
// and THROWS DeploymentTimeoutError on timeout — never a silent return that lets a
|
|
1216
2019
|
// later step run behind a still-Terminating resource.
|
|
1217
2020
|
const rollback = createRollbackManager(k8sApi);
|
|
1218
|
-
const timeout = this.factoryOptions.timeout ?? 300000;
|
|
2021
|
+
const timeout = opts?.timeout ?? this.factoryOptions.timeout ?? 300000;
|
|
1219
2022
|
const apiVersion = this.getInstanceApiVersion();
|
|
1220
2023
|
let definitionsAlreadyAbsent = false;
|
|
1221
2024
|
try {
|
|
@@ -1231,6 +2034,7 @@ export class KroResourceFactoryImpl {
|
|
|
1231
2034
|
// SAME shared resolver `deploy()` created it with (finding #2), so we never look in
|
|
1232
2035
|
// the wrong namespace and mistake a 404 there for "already gone".
|
|
1233
2036
|
const instanceNamespace = this.resolveInstanceNamespace();
|
|
2037
|
+
const instanceTarget = deletionTarget(apiVersion, this.schemaDefinition.kind, name, instanceNamespace);
|
|
1234
2038
|
// Capture the CR's durable RECORD of which namespaces THIS instance hoisted BEFORE
|
|
1235
2039
|
// deleting it (finding #4). deleteInstance has no caller spec, so the live CR is the
|
|
1236
2040
|
// only source of the declared set. PREFER the recorded `typekro.io/hoisted-namespaces`
|
|
@@ -1265,31 +2069,53 @@ export class KroResourceFactoryImpl {
|
|
|
1265
2069
|
: crSpec
|
|
1266
2070
|
? [...this.concreteHoistedNamespaces(crSpec).keys()]
|
|
1267
2071
|
: [];
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
2072
|
+
const permanentlyRetainedOwnedNamespaces = new Set();
|
|
2073
|
+
let graphMayHaveDrainingDescendants = false;
|
|
2074
|
+
const deleteOwnedNamespace = async (namespace) => {
|
|
2075
|
+
const target = deletionTarget('v1', 'Namespace', namespace);
|
|
2076
|
+
try {
|
|
2077
|
+
const outcome = await deleteNamespaceIfEmpty(this.getKubeConfig(), namespace, {
|
|
2078
|
+
logger: this.logger,
|
|
2079
|
+
k8sApi,
|
|
2080
|
+
ownedByRgd: this.rgdName,
|
|
2081
|
+
timeoutMs: timeout,
|
|
2082
|
+
abortSignal,
|
|
2083
|
+
context: { rgdName: this.rgdName },
|
|
2084
|
+
});
|
|
2085
|
+
recordNamespaceDeletionOutcome(deletion, namespace, outcome);
|
|
2086
|
+
if (outcome.status === 'retained' &&
|
|
2087
|
+
outcome.cause === 'occupied' &&
|
|
2088
|
+
!graphMayHaveDrainingDescendants) {
|
|
2089
|
+
permanentlyRetainedOwnedNamespaces.add(namespace);
|
|
2090
|
+
}
|
|
2091
|
+
return undefined;
|
|
1276
2092
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
2093
|
+
catch (error) {
|
|
2094
|
+
if (abortSignal.aborted)
|
|
2095
|
+
throw abortSignal.reason ?? error;
|
|
2096
|
+
let live = target;
|
|
2097
|
+
try {
|
|
2098
|
+
live = (await readDeletionResourceIdentity(k8sApi, target)) ?? target;
|
|
2099
|
+
}
|
|
2100
|
+
catch (readError) {
|
|
2101
|
+
deletion.blockers.push({
|
|
2102
|
+
code: 'CLEANUP_ERROR',
|
|
2103
|
+
message: `Namespace cleanup failed and blocker inspection also failed: ${ensureError(readError).message}`,
|
|
2104
|
+
resource: target,
|
|
2105
|
+
retryable: true,
|
|
2106
|
+
retryGuidance: 'Restore Kubernetes API access, then retry the deletion operation.',
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
deletion.remaining.push(live);
|
|
2110
|
+
deletion.blockers.push(blockerForRemainingResource(live, `Namespace ${namespace} did not complete deletion: ${ensureError(error).message}`));
|
|
2111
|
+
return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
|
|
2112
|
+
safe: true,
|
|
2113
|
+
afterMs: 2_000,
|
|
2114
|
+
guidance: 'Retry after the listed namespace finalizers or occupants have drained; the RGD and generated CRD were retained.',
|
|
2115
|
+
});
|
|
1284
2116
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
// KRO's owner CR reaching 404 proves that its finalizer issued child
|
|
1288
|
-
// deletions; it does not prove that controller-owned child finalizers have
|
|
1289
|
-
// completed. Gate on every concrete owned child before touching the RGD or
|
|
1290
|
-
// reporting successful cleanup. External refs and hoisted Namespaces are
|
|
1291
|
-
// deliberately excluded: this instance never owns the former and TypeKro
|
|
1292
|
-
// tears down the latter in the namespace phase below.
|
|
2117
|
+
};
|
|
2118
|
+
const childTargets = [];
|
|
1293
2119
|
if (crSpec) {
|
|
1294
2120
|
const compositionFn = this.factoryOptions.compositionFn;
|
|
1295
2121
|
const ownedChildren = concreteActiveOwnedResources({
|
|
@@ -1298,14 +2124,22 @@ export class KroResourceFactoryImpl {
|
|
|
1298
2124
|
resources: this.resources,
|
|
1299
2125
|
...(compositionFn ? { compositionFn } : {}),
|
|
1300
2126
|
});
|
|
1301
|
-
const childTargets = [];
|
|
1302
2127
|
for (const [resourceId, resource] of ownedChildren) {
|
|
1303
2128
|
if (resource.kind === 'Namespace')
|
|
1304
2129
|
continue;
|
|
1305
2130
|
const childName = resolveNamespaceName(resource.metadata?.name, crSpec);
|
|
1306
2131
|
const childNamespace = resolveNamespaceName(resource.metadata?.namespace, crSpec);
|
|
1307
2132
|
if (!childName) {
|
|
1308
|
-
|
|
2133
|
+
deletion.blockers.push({
|
|
2134
|
+
code: 'OWNERSHIP_UNPROVEN',
|
|
2135
|
+
message: `Cannot prove KRO child cleanup: resource ${resourceId} (${resource.kind}) has an unresolved concrete name.`,
|
|
2136
|
+
retryable: false,
|
|
2137
|
+
retryGuidance: 'Give every KRO-owned child a concretely resolvable metadata.name so TypeKro can prove teardown completion.',
|
|
2138
|
+
});
|
|
2139
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2140
|
+
safe: false,
|
|
2141
|
+
guidance: 'Correct the unresolved child identity before retrying; the instance was retained.',
|
|
2142
|
+
});
|
|
1309
2143
|
}
|
|
1310
2144
|
childTargets.push({
|
|
1311
2145
|
apiVersion: resource.apiVersion,
|
|
@@ -1314,11 +2148,154 @@ export class KroResourceFactoryImpl {
|
|
|
1314
2148
|
...(childNamespace ? { namespace: childNamespace } : {}),
|
|
1315
2149
|
});
|
|
1316
2150
|
}
|
|
2151
|
+
graphMayHaveDrainingDescendants = childTargets.some((target) => DESCENDANT_PRODUCING_KINDS.has(target.kind));
|
|
2152
|
+
}
|
|
2153
|
+
let capturedDescendants = [];
|
|
2154
|
+
try {
|
|
2155
|
+
capturedDescendants = await captureKnownControllerDescendants(k8sApi, childTargets, instanceNamespace);
|
|
2156
|
+
}
|
|
2157
|
+
catch (error) {
|
|
2158
|
+
deletion.blockers.push({
|
|
2159
|
+
code: 'DISCOVERY_FAILED',
|
|
2160
|
+
message: `Cannot capture KRO-owned controller descendants before deletion: ${ensureError(error).message}`,
|
|
2161
|
+
retryable: true,
|
|
2162
|
+
retryGuidance: 'Restore read/list access to KRO-owned children and their dependents, then retry deletion.',
|
|
2163
|
+
});
|
|
2164
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2165
|
+
safe: true,
|
|
2166
|
+
guidance: 'TypeKro retained the instance because descendant ownership could not be proven safely.',
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
// 1. Delete the CR and GATE on its 404 — KRO cleared `kro.run/finalizer` after
|
|
2170
|
+
// graph-deleting every child (findings #1 + #2). On timeout the gate THROWS, so we
|
|
2171
|
+
// NEVER proceed to delete the RGD while KRO is mid-finalizer (which would orphan
|
|
2172
|
+
// cleanup). A pre-existing 404 on delete is treated as already-deleted.
|
|
2173
|
+
let instanceDeleted = false;
|
|
2174
|
+
try {
|
|
2175
|
+
if (!definitionsAlreadyAbsent) {
|
|
2176
|
+
await rollback.deleteResourceAndWait({ apiVersion, kind: this.schemaDefinition.kind, name, namespace: instanceNamespace }, { timeout, abortSignal });
|
|
2177
|
+
}
|
|
2178
|
+
instanceDeleted = true;
|
|
2179
|
+
deletion.deleted.push(instanceTarget);
|
|
2180
|
+
}
|
|
2181
|
+
catch (error) {
|
|
2182
|
+
if (abortSignal.aborted)
|
|
2183
|
+
throw abortSignal.reason ?? error;
|
|
2184
|
+
if (error instanceof DeploymentTimeoutError) {
|
|
2185
|
+
// KRO is still processing the finalizer. The RGD/CRD/namespace are LEFT ALONE
|
|
2186
|
+
// so KRO can finish cleanup in the background, and the caller receives the exact
|
|
2187
|
+
// live finalizer/owner state instead of a success-shaped void return.
|
|
2188
|
+
let live = instanceTarget;
|
|
2189
|
+
try {
|
|
2190
|
+
live = (await readDeletionResourceIdentity(k8sApi, instanceTarget)) ?? instanceTarget;
|
|
2191
|
+
}
|
|
2192
|
+
catch (readError) {
|
|
2193
|
+
deletion.blockers.push({
|
|
2194
|
+
code: 'CLEANUP_ERROR',
|
|
2195
|
+
message: `KRO instance deletion timed out and its live blocker state could not be read: ${ensureError(readError).message}`,
|
|
2196
|
+
resource: instanceTarget,
|
|
2197
|
+
retryable: true,
|
|
2198
|
+
retryGuidance: 'Retry when the Kubernetes API is readable; keep the RGD installed so KRO can continue finalizer processing.',
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
deletion.remaining.push(live);
|
|
2202
|
+
deletion.blockers.push(blockerForRemainingResource(live, `KRO instance ${name} deletion did not complete within ${timeout}ms.`));
|
|
2203
|
+
return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
|
|
2204
|
+
safe: true,
|
|
2205
|
+
afterMs: 2_000,
|
|
2206
|
+
guidance: 'Retry deletion after KRO and the listed owners have had time to clear the remaining finalizers.',
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
deletion.remaining.push(instanceTarget);
|
|
2210
|
+
deletion.blockers.push({
|
|
2211
|
+
code: 'CLEANUP_ERROR',
|
|
2212
|
+
message: `Failed to delete instance ${name}: ${ensureError(error).message}`,
|
|
2213
|
+
resource: instanceTarget,
|
|
2214
|
+
retryable: true,
|
|
2215
|
+
retryGuidance: 'Resolve the reported Kubernetes API error, then retry. TypeKro has preserved the RGD and generated CRD.',
|
|
2216
|
+
});
|
|
2217
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2218
|
+
safe: true,
|
|
2219
|
+
guidance: 'Retry after resolving the reported Kubernetes API error.',
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
// KRO's owner CR reaching 404 proves that its finalizer issued child
|
|
2223
|
+
// deletions; it does not prove that controller-owned child finalizers have
|
|
2224
|
+
// completed. Gate on every concrete owned child before touching the RGD or
|
|
2225
|
+
// reporting successful cleanup. External refs and hoisted Namespaces are
|
|
2226
|
+
// deliberately excluded: this instance never owns the former and TypeKro
|
|
2227
|
+
// tears down the latter in the namespace phase below.
|
|
2228
|
+
if (childTargets.length > 0) {
|
|
1317
2229
|
try {
|
|
1318
|
-
await Promise.all(childTargets.map((target) => rollback.waitForResourceGone(target, { timeout })));
|
|
2230
|
+
await Promise.all(childTargets.map((target) => rollback.waitForResourceGone(target, { timeout, abortSignal })));
|
|
2231
|
+
deletion.deleted.push(...childTargets.map((target) => deletionTarget(target.apiVersion, target.kind, target.name, target.namespace)));
|
|
1319
2232
|
}
|
|
1320
2233
|
catch (error) {
|
|
1321
|
-
|
|
2234
|
+
if (abortSignal.aborted)
|
|
2235
|
+
throw abortSignal.reason ?? error;
|
|
2236
|
+
for (const target of childTargets) {
|
|
2237
|
+
const identity = deletionTarget(target.apiVersion, target.kind, target.name, target.namespace);
|
|
2238
|
+
try {
|
|
2239
|
+
const live = await readDeletionResourceIdentity(k8sApi, identity);
|
|
2240
|
+
if (!live) {
|
|
2241
|
+
deletion.deleted.push(identity);
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
deletion.remaining.push(live);
|
|
2245
|
+
deletion.blockers.push(blockerForRemainingResource(live));
|
|
2246
|
+
}
|
|
2247
|
+
catch (readError) {
|
|
2248
|
+
deletion.remaining.push(identity);
|
|
2249
|
+
deletion.blockers.push({
|
|
2250
|
+
code: 'CLEANUP_ERROR',
|
|
2251
|
+
message: `Could not inspect remaining child ${target.kind}/${target.name}: ${ensureError(readError).message}`,
|
|
2252
|
+
resource: identity,
|
|
2253
|
+
retryable: true,
|
|
2254
|
+
retryGuidance: 'Retry after the Kubernetes API is readable; TypeKro retained the RGD and generated CRD.',
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
const progressing = deletion.remaining.length > 0 &&
|
|
2259
|
+
deletion.remaining.every((resource) => resource.deletionTimestamp !== undefined);
|
|
2260
|
+
return finishDeletionResult(deletion, progressing ? 'progressing' : 'blocked', {
|
|
2261
|
+
safe: true,
|
|
2262
|
+
afterMs: 2_000,
|
|
2263
|
+
guidance: 'Retry after the listed child resources and finalizers have drained; TypeKro retained the RGD and generated CRD.',
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
// KRO 0.9 can remove a completed Job before Kubernetes garbage collection
|
|
2268
|
+
// removes its Pod. Delete only the dependents captured before instance
|
|
2269
|
+
// deletion whose controller UID matched the KRO-owned Job exactly.
|
|
2270
|
+
for (const descendant of capturedDescendants) {
|
|
2271
|
+
const identity = deletionTarget(descendant.apiVersion, descendant.kind, descendant.name, descendant.namespace);
|
|
2272
|
+
try {
|
|
2273
|
+
await rollback.deleteResourceAndWait(identity, { timeout, abortSignal });
|
|
2274
|
+
deletion.deleted.push(identity);
|
|
2275
|
+
}
|
|
2276
|
+
catch (error) {
|
|
2277
|
+
if (abortSignal.aborted)
|
|
2278
|
+
throw abortSignal.reason ?? error;
|
|
2279
|
+
let live = identity;
|
|
2280
|
+
try {
|
|
2281
|
+
live = (await readDeletionResourceIdentity(k8sApi, identity)) ?? identity;
|
|
2282
|
+
}
|
|
2283
|
+
catch (readError) {
|
|
2284
|
+
deletion.blockers.push({
|
|
2285
|
+
code: 'CLEANUP_ERROR',
|
|
2286
|
+
message: `Could not inspect captured descendant ${descendant.kind}/${descendant.name}: ${ensureError(readError).message}`,
|
|
2287
|
+
resource: identity,
|
|
2288
|
+
retryable: true,
|
|
2289
|
+
retryGuidance: 'Restore Kubernetes API access, then retry deletion.',
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
deletion.remaining.push(live);
|
|
2293
|
+
deletion.blockers.push(blockerForRemainingResource(live, `${descendant.kind}/${descendant.name}, owned by ${descendant.owner.kind}/${descendant.owner.name} (${descendant.owner.uid}), did not complete deletion: ${ensureError(error).message}`));
|
|
2294
|
+
return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
|
|
2295
|
+
safe: true,
|
|
2296
|
+
afterMs: 2_000,
|
|
2297
|
+
guidance: 'Retry after the listed controller-owned descendants and finalizers have drained.',
|
|
2298
|
+
});
|
|
1322
2299
|
}
|
|
1323
2300
|
}
|
|
1324
2301
|
// 2. LIST REMAINING INSTANCES FIRST — BEFORE deleting any namespace (finding #2, v7).
|
|
@@ -1334,10 +2311,37 @@ export class KroResourceFactoryImpl {
|
|
|
1334
2311
|
let remainingItems;
|
|
1335
2312
|
try {
|
|
1336
2313
|
remainingItems = definitionsAlreadyAbsent ? [] : await this.listInstancesForCleanup();
|
|
2314
|
+
abortSignal.throwIfAborted();
|
|
1337
2315
|
}
|
|
1338
2316
|
catch (listError) {
|
|
2317
|
+
const reason = `Cannot list KRO instances to prove shared ownership: ${ensureError(listError).message}`;
|
|
1339
2318
|
this.logger.warn('Cannot list instances — preserving ALL namespaces + the RGD/CRD (fail closed; retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
|
|
1340
|
-
|
|
2319
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2320
|
+
deletion.retained.push({
|
|
2321
|
+
resource: rgd,
|
|
2322
|
+
policy: 'safety-proof-unavailable',
|
|
2323
|
+
reason,
|
|
2324
|
+
});
|
|
2325
|
+
for (const namespace of declaredHoistedNamespaceNames) {
|
|
2326
|
+
const resource = deletionTarget('v1', 'Namespace', namespace);
|
|
2327
|
+
deletion.retained.push({
|
|
2328
|
+
resource,
|
|
2329
|
+
policy: 'safety-proof-unavailable',
|
|
2330
|
+
reason,
|
|
2331
|
+
});
|
|
2332
|
+
deletion.remaining.push(resource);
|
|
2333
|
+
}
|
|
2334
|
+
deletion.blockers.push({
|
|
2335
|
+
code: 'DISCOVERY_FAILED',
|
|
2336
|
+
message: reason,
|
|
2337
|
+
resource: rgd,
|
|
2338
|
+
retryable: true,
|
|
2339
|
+
retryGuidance: 'Restore cluster-wide list access for the generated custom resource, then retry deletion.',
|
|
2340
|
+
});
|
|
2341
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2342
|
+
safe: true,
|
|
2343
|
+
guidance: 'Retry after cluster-wide instance discovery is available; TypeKro retained shared resources fail-closed.',
|
|
2344
|
+
});
|
|
1341
2345
|
}
|
|
1342
2346
|
// The decision to keep the RGD is a pure function of (listed instances, target name,
|
|
1343
2347
|
// instanceDeleted flag) — see {@link shouldPreserveRgd} for the rules.
|
|
@@ -1388,36 +2392,72 @@ export class KroResourceFactoryImpl {
|
|
|
1388
2392
|
preserved.add(ns);
|
|
1389
2393
|
}
|
|
1390
2394
|
if (!allRemainingResolvable) {
|
|
2395
|
+
const reason = 'A remaining KRO instance has no valid hoisted-namespace ownership record, so shared namespace safety cannot be proven.';
|
|
1391
2396
|
this.logger.warn("A remaining instance has no resolvable namespace record — preserving ALL of this instance's " +
|
|
1392
2397
|
'namespaces (fail closed) to avoid deleting one a remaining instance shares', { rgdName: this.rgdName, instance: name });
|
|
2398
|
+
for (const ns of declaredHoistedNamespaceNames) {
|
|
2399
|
+
const resource = deletionTarget('v1', 'Namespace', ns);
|
|
2400
|
+
deletion.retained.push({
|
|
2401
|
+
resource,
|
|
2402
|
+
policy: 'safety-proof-unavailable',
|
|
2403
|
+
reason,
|
|
2404
|
+
});
|
|
2405
|
+
deletion.remaining.push(resource);
|
|
2406
|
+
}
|
|
2407
|
+
deletion.blockers.push({
|
|
2408
|
+
code: 'OWNERSHIP_UNPROVEN',
|
|
2409
|
+
message: reason,
|
|
2410
|
+
retryable: true,
|
|
2411
|
+
retryGuidance: 'Repair or migrate the remaining instance hoisted-namespace annotation, then retry deletion.',
|
|
2412
|
+
});
|
|
1393
2413
|
}
|
|
1394
2414
|
else {
|
|
1395
2415
|
for (const ns of declaredHoistedNamespaceNames) {
|
|
2416
|
+
abortSignal.throwIfAborted();
|
|
1396
2417
|
if (preserved.has(ns)) {
|
|
1397
2418
|
this.logger.debug('Namespace recorded by a remaining instance — preserving', {
|
|
1398
2419
|
namespace: ns,
|
|
1399
2420
|
rgdName: this.rgdName,
|
|
1400
2421
|
});
|
|
2422
|
+
deletion.retained.push({
|
|
2423
|
+
resource: deletionTarget('v1', 'Namespace', ns),
|
|
2424
|
+
policy: 'shared-instance',
|
|
2425
|
+
reason: 'A remaining KRO instance records this namespace as shared state.',
|
|
2426
|
+
});
|
|
1401
2427
|
continue;
|
|
1402
2428
|
}
|
|
1403
|
-
await
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
k8sApi,
|
|
1407
|
-
ownedByRgd: this.rgdName,
|
|
1408
|
-
timeoutMs: timeout,
|
|
1409
|
-
context: { rgdName: this.rgdName },
|
|
1410
|
-
});
|
|
2429
|
+
const incomplete = await deleteOwnedNamespace(ns);
|
|
2430
|
+
if (incomplete)
|
|
2431
|
+
return incomplete;
|
|
1411
2432
|
}
|
|
1412
2433
|
}
|
|
1413
2434
|
}
|
|
1414
2435
|
// Only delete the RGD and CRD if no other instances remain. Multiple instances can share
|
|
1415
2436
|
// one RGD — deleting it would break the others.
|
|
1416
2437
|
if (hasRemainingInstances) {
|
|
2438
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2439
|
+
deletion.retained.push({
|
|
2440
|
+
resource: rgd,
|
|
2441
|
+
policy: 'shared-instance',
|
|
2442
|
+
reason: 'Other KRO instances still depend on this ResourceGraphDefinition.',
|
|
2443
|
+
});
|
|
2444
|
+
if (this.discoveredPlural) {
|
|
2445
|
+
deletion.retained.push({
|
|
2446
|
+
resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
|
|
2447
|
+
policy: 'generated-crd',
|
|
2448
|
+
reason: 'The generated CRD is retained while shared instances exist.',
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
1417
2451
|
this.logger.debug('Skipping RGD/CRD deletion — other instances still exist', {
|
|
1418
2452
|
rgdName: this.rgdName,
|
|
1419
2453
|
});
|
|
1420
|
-
|
|
2454
|
+
const blocked = deletion.blockers.length > 0;
|
|
2455
|
+
return finishDeletionResult(deletion, blocked ? 'blocked' : 'complete', {
|
|
2456
|
+
safe: true,
|
|
2457
|
+
guidance: blocked
|
|
2458
|
+
? 'Retry after repairing the missing namespace ownership evidence.'
|
|
2459
|
+
: 'The requested instance is gone. Shared definitions and namespaces were retained intentionally.',
|
|
2460
|
+
});
|
|
1421
2461
|
}
|
|
1422
2462
|
// 2b. RETRY-SAFE owned-namespace sweep (finding #2). The per-instance cleanup above
|
|
1423
2463
|
// reads the names off the CR's `typekro.io/hoisted-namespaces` annotation — but that
|
|
@@ -1434,20 +2474,35 @@ export class KroResourceFactoryImpl {
|
|
|
1434
2474
|
ownedNamespaces = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
|
|
1435
2475
|
k8sApi,
|
|
1436
2476
|
logger: this.logger,
|
|
2477
|
+
abortSignal,
|
|
1437
2478
|
});
|
|
1438
2479
|
}
|
|
1439
2480
|
catch (listError) {
|
|
2481
|
+
const reason = `Cannot list namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
|
|
1440
2482
|
this.logger.warn('Cannot list owned namespaces to confirm cleanup — preserving RGD/CRD (retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
|
|
1441
|
-
|
|
2483
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2484
|
+
deletion.retained.push({
|
|
2485
|
+
resource: rgd,
|
|
2486
|
+
policy: 'safety-proof-unavailable',
|
|
2487
|
+
reason,
|
|
2488
|
+
});
|
|
2489
|
+
deletion.blockers.push({
|
|
2490
|
+
code: 'DISCOVERY_FAILED',
|
|
2491
|
+
message: reason,
|
|
2492
|
+
resource: rgd,
|
|
2493
|
+
retryable: true,
|
|
2494
|
+
retryGuidance: 'Restore Namespace list access and retry; TypeKro retained the RGD and generated CRD.',
|
|
2495
|
+
});
|
|
2496
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2497
|
+
safe: true,
|
|
2498
|
+
guidance: 'Retry after Namespace ownership discovery is available.',
|
|
2499
|
+
});
|
|
1442
2500
|
}
|
|
1443
2501
|
for (const ns of ownedNamespaces) {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
timeoutMs: timeout,
|
|
1449
|
-
context: { rgdName: this.rgdName },
|
|
1450
|
-
});
|
|
2502
|
+
abortSignal.throwIfAborted();
|
|
2503
|
+
const incomplete = await deleteOwnedNamespace(ns);
|
|
2504
|
+
if (incomplete)
|
|
2505
|
+
return incomplete;
|
|
1451
2506
|
}
|
|
1452
2507
|
// 2c. CONFIRM owned-namespace cleanup BEFORE removing the definitions (finding #2): do
|
|
1453
2508
|
// NOT delete the RGD/CRD while any owned namespace still exists or cannot be confirmed
|
|
@@ -1460,11 +2515,29 @@ export class KroResourceFactoryImpl {
|
|
|
1460
2515
|
remainingOwned = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
|
|
1461
2516
|
k8sApi,
|
|
1462
2517
|
logger: this.logger,
|
|
2518
|
+
abortSignal,
|
|
1463
2519
|
});
|
|
1464
2520
|
}
|
|
1465
2521
|
catch (listError) {
|
|
2522
|
+
const reason = `Cannot confirm cleanup of namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
|
|
1466
2523
|
this.logger.warn('Cannot confirm owned-namespace cleanup — preserving RGD/CRD (retry to complete)', { rgdName: this.rgdName, error: ensureError(listError).message });
|
|
1467
|
-
|
|
2524
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2525
|
+
deletion.retained.push({
|
|
2526
|
+
resource: rgd,
|
|
2527
|
+
policy: 'safety-proof-unavailable',
|
|
2528
|
+
reason,
|
|
2529
|
+
});
|
|
2530
|
+
deletion.blockers.push({
|
|
2531
|
+
code: 'DISCOVERY_FAILED',
|
|
2532
|
+
message: reason,
|
|
2533
|
+
resource: rgd,
|
|
2534
|
+
retryable: true,
|
|
2535
|
+
retryGuidance: 'Restore Namespace list access and retry; TypeKro retained the RGD and generated CRD.',
|
|
2536
|
+
});
|
|
2537
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2538
|
+
safe: true,
|
|
2539
|
+
guidance: 'Retry after Namespace cleanup can be confirmed.',
|
|
2540
|
+
});
|
|
1468
2541
|
}
|
|
1469
2542
|
// A graph child reaching 404 only proves its controller accepted deletion;
|
|
1470
2543
|
// grandchildren such as Helm-managed Pods or Rook RGW Deployments may need
|
|
@@ -1473,40 +2546,126 @@ export class KroResourceFactoryImpl {
|
|
|
1473
2546
|
// before returning the durable "preserved for retry" state. This prevents a
|
|
1474
2547
|
// successful deleteInstance() from racing immediately into teardown of a
|
|
1475
2548
|
// storage prerequisite while owned descendants are still draining.
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
2549
|
+
const namespacesStillDraining = () => remainingOwned.filter((namespace) => !permanentlyRetainedOwnedNamespaces.has(namespace));
|
|
2550
|
+
if (crSpec && namespacesStillDraining().length > 0) {
|
|
2551
|
+
const drainDeadline = Date.now() + Math.min(timeout, 60_000);
|
|
2552
|
+
while (namespacesStillDraining().length > 0 && Date.now() < drainDeadline) {
|
|
2553
|
+
await new Promise((resolve, reject) => {
|
|
2554
|
+
const onAbort = () => {
|
|
2555
|
+
clearTimeout(delay);
|
|
2556
|
+
reject(abortSignal.reason ?? new DOMException('The operation was aborted', 'AbortError'));
|
|
2557
|
+
};
|
|
2558
|
+
const delay = setTimeout(() => {
|
|
2559
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
2560
|
+
resolve();
|
|
2561
|
+
}, 2_000);
|
|
2562
|
+
if (abortSignal.aborted) {
|
|
2563
|
+
onAbort();
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
2567
|
+
});
|
|
2568
|
+
for (const ns of namespacesStillDraining()) {
|
|
2569
|
+
abortSignal.throwIfAborted();
|
|
2570
|
+
const incomplete = await deleteOwnedNamespace(ns);
|
|
2571
|
+
if (incomplete)
|
|
2572
|
+
return incomplete;
|
|
1488
2573
|
}
|
|
1489
2574
|
try {
|
|
1490
2575
|
remainingOwned = await listNamespacesOwnedByRgd(this.getKubeConfig(), this.rgdName, {
|
|
1491
2576
|
k8sApi,
|
|
1492
2577
|
logger: this.logger,
|
|
2578
|
+
abortSignal,
|
|
1493
2579
|
});
|
|
1494
2580
|
}
|
|
1495
2581
|
catch (listError) {
|
|
2582
|
+
const reason = `Cannot confirm descendant drain for namespaces owned by RGD ${this.rgdName}: ${ensureError(listError).message}`;
|
|
1496
2583
|
this.logger.warn('Cannot confirm owned-namespace descendant drain — preserving RGD/CRD for retry', { rgdName: this.rgdName, error: ensureError(listError).message });
|
|
1497
|
-
|
|
2584
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2585
|
+
deletion.retained.push({
|
|
2586
|
+
resource: rgd,
|
|
2587
|
+
policy: 'safety-proof-unavailable',
|
|
2588
|
+
reason,
|
|
2589
|
+
});
|
|
2590
|
+
deletion.blockers.push({
|
|
2591
|
+
code: 'DISCOVERY_FAILED',
|
|
2592
|
+
message: reason,
|
|
2593
|
+
resource: rgd,
|
|
2594
|
+
retryable: true,
|
|
2595
|
+
retryGuidance: 'Restore Namespace list access, then retry deletion.',
|
|
2596
|
+
});
|
|
2597
|
+
return finishDeletionResult(deletion, 'blocked', {
|
|
2598
|
+
safe: true,
|
|
2599
|
+
guidance: 'Retry after Namespace descendant cleanup can be confirmed.',
|
|
2600
|
+
});
|
|
1498
2601
|
}
|
|
1499
2602
|
}
|
|
1500
2603
|
}
|
|
1501
2604
|
if (remainingOwned.length > 0) {
|
|
1502
2605
|
this.logger.info('Owned namespace(s) still present after cleanup — preserving RGD/CRD until they are gone', { rgdName: this.rgdName, remaining: remainingOwned });
|
|
1503
|
-
|
|
2606
|
+
for (const namespace of remainingOwned) {
|
|
2607
|
+
const resource = deletionTarget('v1', 'Namespace', namespace);
|
|
2608
|
+
if (!deletion.retained.some((entry) => entry.resource.kind === 'Namespace' && entry.resource.name === namespace)) {
|
|
2609
|
+
deletion.retained.push({
|
|
2610
|
+
resource,
|
|
2611
|
+
policy: 'occupied-namespace',
|
|
2612
|
+
reason: 'The namespace remains owned by this RGD but contains resources that TypeKro will not delete implicitly.',
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
const rgd = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2617
|
+
deletion.retained.push({
|
|
2618
|
+
resource: rgd,
|
|
2619
|
+
policy: deletion.blockers.length > 0 ? 'safety-proof-unavailable' : 'occupied-namespace',
|
|
2620
|
+
reason: 'The RGD is retained while an owned namespace remains, preventing orphaned lifecycle state.',
|
|
2621
|
+
});
|
|
2622
|
+
if (this.discoveredPlural) {
|
|
2623
|
+
deletion.retained.push({
|
|
2624
|
+
resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
|
|
2625
|
+
policy: 'generated-crd',
|
|
2626
|
+
reason: 'The generated CRD is retained Active while owned namespaces remain.',
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
return finishDeletionResult(deletion, deletion.blockers.length > 0 ? 'blocked' : 'complete', {
|
|
2630
|
+
safe: true,
|
|
2631
|
+
guidance: deletion.blockers.length > 0
|
|
2632
|
+
? 'Resolve the listed discovery or ownership blockers, then retry.'
|
|
2633
|
+
: 'The instance graph is gone. Occupied or adopted namespaces and their definitions were retained explicitly.',
|
|
2634
|
+
});
|
|
1504
2635
|
}
|
|
1505
2636
|
// 3. Delete the RGD and GATE on its 404 (findings #1 + #2). The RGD carries a KRO
|
|
1506
2637
|
// finalizer; while KRO processes it, KRO's per-RGD dynamic controller is still
|
|
1507
2638
|
// WATCHING the generated CRD's resources. Waiting for the RGD 404 first lets KRO
|
|
1508
2639
|
// tear its controller down cleanly. The gate THROWS on timeout (no silent proceed).
|
|
1509
|
-
|
|
2640
|
+
const rgdTarget = deletionTarget('kro.run/v1alpha1', 'ResourceGraphDefinition', this.rgdName);
|
|
2641
|
+
try {
|
|
2642
|
+
await rollback.deleteResourceAndWait(rgdTarget, { timeout, abortSignal });
|
|
2643
|
+
deletion.deleted.push(rgdTarget);
|
|
2644
|
+
}
|
|
2645
|
+
catch (error) {
|
|
2646
|
+
if (abortSignal.aborted)
|
|
2647
|
+
throw abortSignal.reason ?? error;
|
|
2648
|
+
let live = rgdTarget;
|
|
2649
|
+
try {
|
|
2650
|
+
live = (await readDeletionResourceIdentity(k8sApi, rgdTarget)) ?? rgdTarget;
|
|
2651
|
+
}
|
|
2652
|
+
catch (readError) {
|
|
2653
|
+
deletion.blockers.push({
|
|
2654
|
+
code: 'CLEANUP_ERROR',
|
|
2655
|
+
message: `RGD deletion failed and blocker inspection also failed: ${ensureError(readError).message}`,
|
|
2656
|
+
resource: rgdTarget,
|
|
2657
|
+
retryable: true,
|
|
2658
|
+
retryGuidance: 'Restore Kubernetes API access, then retry deletion.',
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
deletion.remaining.push(live);
|
|
2662
|
+
deletion.blockers.push(blockerForRemainingResource(live, `ResourceGraphDefinition ${this.rgdName} did not complete deletion: ${ensureError(error).message}`));
|
|
2663
|
+
return finishDeletionResult(deletion, live.deletionTimestamp ? 'progressing' : 'blocked', {
|
|
2664
|
+
safe: true,
|
|
2665
|
+
afterMs: 2_000,
|
|
2666
|
+
guidance: 'Retry after KRO clears the listed RGD finalizers.',
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
1510
2669
|
this.logger.debug('RGD deleted and fully gone', { rgdName: this.rgdName });
|
|
1511
2670
|
// 4. RETAIN the generated CRD. This matches the Alchemy path and KRO's own
|
|
1512
2671
|
// `allowCRDDeletion=false` default. Initiating a best-effort delete is not harmless:
|
|
@@ -1522,6 +2681,17 @@ export class KroResourceFactoryImpl {
|
|
|
1522
2681
|
? { crdName: `${this.discoveredPlural}.${this.getSchemaGroup()}` }
|
|
1523
2682
|
: {}),
|
|
1524
2683
|
});
|
|
2684
|
+
if (this.discoveredPlural) {
|
|
2685
|
+
deletion.retained.push({
|
|
2686
|
+
resource: deletionTarget('apiextensions.k8s.io/v1', 'CustomResourceDefinition', `${this.discoveredPlural}.${this.getSchemaGroup()}`),
|
|
2687
|
+
policy: 'generated-crd',
|
|
2688
|
+
reason: 'Generated CRDs are retained Active by policy after the final instance is removed; administrative GC may delete them after proving they are unused.',
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
return finishDeletionResult(deletion, 'complete', {
|
|
2692
|
+
safe: true,
|
|
2693
|
+
guidance: 'The instance graph and RGD are gone. The generated CRD was retained Active under the documented zero-instance policy.',
|
|
2694
|
+
});
|
|
1525
2695
|
}
|
|
1526
2696
|
/**
|
|
1527
2697
|
* Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
|
|
@@ -1545,6 +2715,7 @@ export class KroResourceFactoryImpl {
|
|
|
1545
2715
|
*/
|
|
1546
2716
|
async toAlchemyResources(spec, opts) {
|
|
1547
2717
|
this.assertNoKroPrerequisiteHookForDeclarative('toAlchemyResources()');
|
|
2718
|
+
this.assertNoReservedKroSpecField(spec);
|
|
1548
2719
|
validateSpec(spec, this.schemaDefinition, {
|
|
1549
2720
|
kind: this.schemaDefinition.kind,
|
|
1550
2721
|
name: this.name,
|
|
@@ -1552,6 +2723,32 @@ export class KroResourceFactoryImpl {
|
|
|
1552
2723
|
this.assertInstanceNamespaceOwnershipSafe(spec);
|
|
1553
2724
|
const instanceNamespace = this.resolveInstanceNamespace(spec);
|
|
1554
2725
|
const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
|
|
2726
|
+
const compiledBundle = this.compiledKroArtifactBundle(spec, opts);
|
|
2727
|
+
if (compiledBundle) {
|
|
2728
|
+
this.assertBundleCapabilities(compiledBundle.bundle, {
|
|
2729
|
+
host: 'alchemy',
|
|
2730
|
+
output: 'live',
|
|
2731
|
+
});
|
|
2732
|
+
const runtimeBindings = compiledBundle.bundle.operations.flatMap((operation) => {
|
|
2733
|
+
const strategy = operation.artifact.readiness.strategy;
|
|
2734
|
+
return strategy?.kind === 'runtime-binding'
|
|
2735
|
+
? [
|
|
2736
|
+
{
|
|
2737
|
+
operationId: operation.id,
|
|
2738
|
+
binding: strategy.binding,
|
|
2739
|
+
classification: strategy.classification ?? {
|
|
2740
|
+
reason: 'unclassified-evaluator',
|
|
2741
|
+
},
|
|
2742
|
+
},
|
|
2743
|
+
]
|
|
2744
|
+
: [];
|
|
2745
|
+
});
|
|
2746
|
+
if (runtimeBindings.length > 0) {
|
|
2747
|
+
throw new TypeKroError('KRO Alchemy declarations cannot persist runtime-only readiness functions. Register a portable readiness strategy or use standalone deployment.', 'KRO_ALCHEMY_RUNTIME_BINDING_UNSUPPORTED', { runtimeBindings });
|
|
2748
|
+
}
|
|
2749
|
+
return this.alchemyDeclarationsFromKroArtifactBundle(compiledBundle.bundle, kubeConfigOptions, compiledBundle.sensitiveBindings);
|
|
2750
|
+
}
|
|
2751
|
+
this.assertBundleCapabilities(undefined, { host: 'alchemy', output: 'live' });
|
|
1555
2752
|
const kroDeletion = this.createAlchemyKroDeletionOptions(instanceNamespace);
|
|
1556
2753
|
const prerequisiteDeclarations = this.prerequisiteAlchemyDeclarations(kubeConfigOptions);
|
|
1557
2754
|
const prerequisiteIds = prerequisiteDeclarations.map((decl) => decl.id);
|
|
@@ -1570,8 +2767,12 @@ export class KroResourceFactoryImpl {
|
|
|
1570
2767
|
// occupies it (no cross-stack refcount needed).
|
|
1571
2768
|
const instanceNamespaceDeclarations = [];
|
|
1572
2769
|
const instanceNamespaceIds = [];
|
|
1573
|
-
for (const [hoistedNs,
|
|
1574
|
-
|
|
2770
|
+
for (const [hoistedNs, { artifact, resource }] of this.materializedHoistedNamespaces(spec)) {
|
|
2771
|
+
resource.metadata.annotations = {
|
|
2772
|
+
...resource.metadata.annotations,
|
|
2773
|
+
[NAMESPACE_OWNER_ANNOTATION]: this.rgdName,
|
|
2774
|
+
};
|
|
2775
|
+
const decl = this.hoistedNamespaceAlchemyDeclaration(hoistedNs, resource, artifact, kubeConfigOptions, prerequisiteIds);
|
|
1575
2776
|
instanceNamespaceDeclarations.push(decl);
|
|
1576
2777
|
instanceNamespaceIds.push(decl.id);
|
|
1577
2778
|
}
|
|
@@ -1585,7 +2786,6 @@ export class KroResourceFactoryImpl {
|
|
|
1585
2786
|
const singletonDeclarations = [];
|
|
1586
2787
|
const singletonInstanceIds = [];
|
|
1587
2788
|
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
1588
|
-
assertSingletonOwnerNamespaceOwnershipSafe(definition);
|
|
1589
2789
|
const singletonFactory = this.singletonFactoryFor(definition);
|
|
1590
2790
|
try {
|
|
1591
2791
|
const decls = await singletonFactory.toAlchemyResources(definition.spec, {
|
|
@@ -1604,18 +2804,10 @@ export class KroResourceFactoryImpl {
|
|
|
1604
2804
|
await singletonFactory.dispose?.();
|
|
1605
2805
|
}
|
|
1606
2806
|
}
|
|
1607
|
-
// 1. RGD declaration (deployed once per factory; shared by all instances).
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1610
|
-
|
|
1611
|
-
// Load with JSON_SCHEMA to MATCH `serializeResourceGraphToYaml`'s dump schema. The dump uses
|
|
1612
|
-
// JSON_SCHEMA (no YAML timestamp/`!!timestamp` type), so a string value that merely LOOKS like a
|
|
1613
|
-
// date — e.g. an env var `"2026-06-01"` — is emitted UNQUOTED. Loading it back with js-yaml's
|
|
1614
|
-
// DEFAULT schema (timestamp-aware) would coerce that scalar to a `Date` OBJECT, and the applied
|
|
1615
|
-
// RGD would then carry an object where a string belongs — KRO rejects the whole graph
|
|
1616
|
-
// (`GraphAccepted=False: expected string type ..., got object`) and it never reconciles. Matching
|
|
1617
|
-
// the load schema to the dump makes the round-trip lossless (the scalar stays the string it was).
|
|
1618
|
-
const rgdManifest = yaml.load(this.buildRgdYaml(spec), { schema: yaml.JSON_SCHEMA });
|
|
2807
|
+
// 1. RGD declaration (deployed once per factory; shared by all instances). The serializer
|
|
2808
|
+
// exposes the manifest directly, so Alchemy consumes the same representation without a
|
|
2809
|
+
// YAML dump/load round trip or timestamp-like scalar coercion.
|
|
2810
|
+
const rgdManifest = this.buildRgdManifest(spec);
|
|
1619
2811
|
const rgdFactory = this.rgdProvider ??
|
|
1620
2812
|
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
1621
2813
|
const rgdEnhanced = rgdFactory(rgdManifest);
|
|
@@ -1695,7 +2887,15 @@ export class KroResourceFactoryImpl {
|
|
|
1695
2887
|
}
|
|
1696
2888
|
/** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
|
|
1697
2889
|
extractKubeConfigOptionsForAlchemy() {
|
|
1698
|
-
|
|
2890
|
+
const persistence = this.factoryOptions.alchemyKubeConfig ??
|
|
2891
|
+
(this.factoryOptions.kubeConfig === undefined
|
|
2892
|
+
? { source: { kind: 'default' } }
|
|
2893
|
+
: undefined);
|
|
2894
|
+
const kubeConfig = this.factoryOptions.kubeConfig ?? (persistence?.source ? undefined : this.getKubeConfig());
|
|
2895
|
+
return extractSerializableKubeConfigOptions(kubeConfig, {
|
|
2896
|
+
...(this.factoryOptions.skipTLSVerify === true ? { skipTLSVerifyOverride: true } : {}),
|
|
2897
|
+
...(persistence ? { persistence } : {}),
|
|
2898
|
+
});
|
|
1699
2899
|
}
|
|
1700
2900
|
/** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
|
|
1701
2901
|
createAlchemyKroDeletionOptions(instanceNamespace = this.namespace) {
|
|
@@ -1709,12 +2909,201 @@ export class KroResourceFactoryImpl {
|
|
|
1709
2909
|
timeout: this.factoryOptions.timeout ?? 300000,
|
|
1710
2910
|
};
|
|
1711
2911
|
}
|
|
2912
|
+
kroDeletionForBundleOperation(bundle, operation) {
|
|
2913
|
+
if (operation.role !== 'resource-graph-definition' &&
|
|
2914
|
+
operation.role !== 'singleton-owner-rgd' &&
|
|
2915
|
+
operation.role !== 'instance' &&
|
|
2916
|
+
operation.role !== 'singleton-owner-instance') {
|
|
2917
|
+
return undefined;
|
|
2918
|
+
}
|
|
2919
|
+
const memberId = operation.sources[0]?.memberId;
|
|
2920
|
+
if (!memberId)
|
|
2921
|
+
return undefined;
|
|
2922
|
+
const rgdOperation = bundle.operations.find((candidate) => (candidate.role === 'resource-graph-definition' ||
|
|
2923
|
+
candidate.role === 'singleton-owner-rgd') &&
|
|
2924
|
+
candidate.sources.some((source) => source.memberId === memberId));
|
|
2925
|
+
const instanceOperation = bundle.operations.find((candidate) => (candidate.role === 'instance' || candidate.role === 'singleton-owner-instance') &&
|
|
2926
|
+
candidate.sources.some((source) => source.memberId === memberId));
|
|
2927
|
+
if (!rgdOperation || !instanceOperation)
|
|
2928
|
+
return undefined;
|
|
2929
|
+
const rgdIdentity = rgdOperation.artifact.identity;
|
|
2930
|
+
const instanceIdentity = instanceOperation.artifact.identity;
|
|
2931
|
+
if (!rgdIdentity || !instanceIdentity)
|
|
2932
|
+
return undefined;
|
|
2933
|
+
const apiVersion = instanceIdentity.apiVersion;
|
|
2934
|
+
const group = apiVersion.includes('/') ? apiVersion.split('/')[0] : undefined;
|
|
2935
|
+
return {
|
|
2936
|
+
apiVersion,
|
|
2937
|
+
kind: instanceIdentity.kind,
|
|
2938
|
+
namespace: instanceIdentity.namespace
|
|
2939
|
+
? String(materializePlanValue(instanceIdentity.namespace))
|
|
2940
|
+
: this.namespace,
|
|
2941
|
+
rgdName: String(materializePlanValue(rgdIdentity.name)),
|
|
2942
|
+
...(group ? { group } : {}),
|
|
2943
|
+
timeout: this.factoryOptions.timeout ?? 300000,
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
async alchemyDeclarationsFromKroArtifactBundle(bundle, kubeConfigOptions, sensitiveBindings) {
|
|
2947
|
+
const encodedBundle = encodeKroArtifactBundle(bundle);
|
|
2948
|
+
const ordered = orderKroArtifactBundleOperations(bundle);
|
|
2949
|
+
const Redacted = Object.keys(sensitiveBindings).length > 0 ? await import('effect/Redacted') : undefined;
|
|
2950
|
+
const rgdFactory = this.rgdProvider ??
|
|
2951
|
+
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
2952
|
+
const resources = new Map();
|
|
2953
|
+
const operationSensitiveBindings = new Map();
|
|
2954
|
+
const operationArtifactUses = new Map();
|
|
2955
|
+
const declarationIds = new Map();
|
|
2956
|
+
for (const operation of ordered) {
|
|
2957
|
+
const bindingNames = planValueSensitiveBindingNames(operation.manifest);
|
|
2958
|
+
const missingBindings = bindingNames.filter((binding) => !Object.hasOwn(sensitiveBindings, binding));
|
|
2959
|
+
if (missingBindings.length > 0) {
|
|
2960
|
+
throw new TypeKroError(`KRO operation ${operation.id} has unresolved sensitive bindings: ${missingBindings.join(', ')}.`, 'KRO_SENSITIVE_BINDING_UNRESOLVED', { operationId: operation.id, bindings: missingBindings });
|
|
2961
|
+
}
|
|
2962
|
+
const redactedBindings = Redacted && bindingNames.length > 0
|
|
2963
|
+
? Object.fromEntries(bindingNames.map((binding) => [binding, Redacted.make(sensitiveBindings[binding])]))
|
|
2964
|
+
: {};
|
|
2965
|
+
operationSensitiveBindings.set(operation.id, redactedBindings);
|
|
2966
|
+
const artifactOutputUses = collectArtifactOutputUses(operation.manifest);
|
|
2967
|
+
operationArtifactUses.set(operation.id, artifactOutputUses);
|
|
2968
|
+
const declarationArtifactOutputs = Object.fromEntries(bundle.artifactRequirements.map((requirement) => [
|
|
2969
|
+
requirement.id,
|
|
2970
|
+
Object.fromEntries(requirement.outputs.map((output) => [
|
|
2971
|
+
output,
|
|
2972
|
+
`__typekro_artifact_${encodeURIComponent(requirement.id)}_${encodeURIComponent(output)}__`,
|
|
2973
|
+
])),
|
|
2974
|
+
]));
|
|
2975
|
+
const manifest = materializeKroArtifactBundleOperation(operation, {
|
|
2976
|
+
sensitive: redactedBindings,
|
|
2977
|
+
...(artifactOutputUses.length > 0 ? { artifactOutputs: declarationArtifactOutputs } : {}),
|
|
2978
|
+
});
|
|
2979
|
+
const artifact = operation.artifact;
|
|
2980
|
+
if (artifact.identity?.scope)
|
|
2981
|
+
setMetadataField(manifest, 'scope', artifact.identity.scope);
|
|
2982
|
+
setMetadataField(manifest, 'applyPolicy', artifact.apply);
|
|
2983
|
+
setResourceId(manifest, artifact.id);
|
|
2984
|
+
const strategy = artifact.readiness.strategy;
|
|
2985
|
+
if (strategy?.kind === 'registered') {
|
|
2986
|
+
const evaluator = resolvePortableReadinessStrategy(strategy);
|
|
2987
|
+
if (!evaluator) {
|
|
2988
|
+
throw new TypeKroError(`Registered readiness strategy ${strategy.id}@${strategy.revision} is unavailable.`, 'KRO_ARTIFACT_READINESS_UNAVAILABLE', { operationId: operation.id, artifactId: artifact.id });
|
|
2989
|
+
}
|
|
2990
|
+
setReadinessEvaluator(manifest, evaluator);
|
|
2991
|
+
}
|
|
2992
|
+
if (operation.role === 'hoisted-namespace') {
|
|
2993
|
+
const evidence = artifact.lifecycle.unusedEvidence;
|
|
2994
|
+
const evidenceInputs = evidence
|
|
2995
|
+
? materializePlanValue(evidence.inputs)
|
|
2996
|
+
: undefined;
|
|
2997
|
+
manifest.metadata.annotations = {
|
|
2998
|
+
...manifest.metadata.annotations,
|
|
2999
|
+
[NAMESPACE_OWNER_ANNOTATION]: String(evidenceInputs?.rgdName ?? this.rgdName),
|
|
3000
|
+
};
|
|
3001
|
+
}
|
|
3002
|
+
const enhanced = operation.role === 'resource-graph-definition' || operation.role === 'singleton-owner-rgd'
|
|
3003
|
+
? rgdFactory(manifest)
|
|
3004
|
+
: manifest;
|
|
3005
|
+
setMetadataField(enhanced, 'applyPolicy', artifact.apply);
|
|
3006
|
+
resources.set(operation.id, enhanced);
|
|
3007
|
+
const declarationId = operation.role === 'hoisted-namespace'
|
|
3008
|
+
? KroResourceFactoryImpl.hoistedNamespaceId(String(manifest.metadata.name))
|
|
3009
|
+
: operation.role === 'instance' || operation.role === 'singleton-owner-instance'
|
|
3010
|
+
? this.instanceAlchemyId(enhanced)
|
|
3011
|
+
: createAlchemyResourceId(enhanced, artifact.identity?.scope === 'cluster'
|
|
3012
|
+
? undefined
|
|
3013
|
+
: (manifest.metadata.namespace ?? this.namespace));
|
|
3014
|
+
if ([...declarationIds.values()].includes(declarationId)) {
|
|
3015
|
+
throw new TypeKroError(`KRO bundle operations collapse to duplicate Alchemy id ${declarationId}.`, 'KRO_ALCHEMY_DECLARATION_ID_COLLISION', { operationId: operation.id, declarationId });
|
|
3016
|
+
}
|
|
3017
|
+
declarationIds.set(operation.id, declarationId);
|
|
3018
|
+
}
|
|
3019
|
+
return ordered.map((operation) => {
|
|
3020
|
+
const resource = resources.get(operation.id);
|
|
3021
|
+
const declarationId = declarationIds.get(operation.id);
|
|
3022
|
+
if (!resource || !declarationId) {
|
|
3023
|
+
throw new TypeKroError(`KRO operation ${operation.id} was not prepared for Alchemy declaration emission.`, 'KRO_ALCHEMY_DECLARATION_MISSING', { operationId: operation.id });
|
|
3024
|
+
}
|
|
3025
|
+
const artifact = operation.artifact;
|
|
3026
|
+
const operationBindings = operationSensitiveBindings.get(operation.id);
|
|
3027
|
+
const artifactOutputUses = operationArtifactUses.get(operation.id) ?? [];
|
|
3028
|
+
const artifactRequirementIds = new Set(artifactOutputUses.map((use) => use.requirementId));
|
|
3029
|
+
const artifactRequirements = bundle.artifactRequirements.filter((requirement) => artifactRequirementIds.has(requirement.id));
|
|
3030
|
+
const manifest = resource;
|
|
3031
|
+
const direct = operation.role === 'kro-prerequisite' || operation.role === 'hoisted-namespace';
|
|
3032
|
+
const kroDeletion = this.kroDeletionForBundleOperation(bundle, operation);
|
|
3033
|
+
const evidence = artifact.lifecycle.unusedEvidence;
|
|
3034
|
+
const evidenceInputs = evidence
|
|
3035
|
+
? materializePlanValue(evidence.inputs)
|
|
3036
|
+
: undefined;
|
|
3037
|
+
const namespace = operation.role === 'hoisted-namespace'
|
|
3038
|
+
? String(manifest.metadata.name)
|
|
3039
|
+
: (manifest.metadata.namespace ?? this.namespace);
|
|
3040
|
+
return {
|
|
3041
|
+
id: declarationId,
|
|
3042
|
+
dependsOn: operation.dependencies.map((dependency) => {
|
|
3043
|
+
const declarationId = declarationIds.get(dependency);
|
|
3044
|
+
if (!declarationId) {
|
|
3045
|
+
throw new TypeKroError(`KRO operation ${operation.id} references an unemitted dependency ${dependency}.`, 'KRO_ALCHEMY_DEPENDENCY_MISSING', { operationId: operation.id, dependency });
|
|
3046
|
+
}
|
|
3047
|
+
return declarationId;
|
|
3048
|
+
}),
|
|
3049
|
+
...(artifactRequirements.length > 0 ? { artifactRequirements } : {}),
|
|
3050
|
+
...(artifactOutputUses.length > 0 ? { artifactOutputUses } : {}),
|
|
3051
|
+
props: {
|
|
3052
|
+
resource,
|
|
3053
|
+
resourceId: artifact.id,
|
|
3054
|
+
namespace,
|
|
3055
|
+
deploymentStrategy: direct ? 'direct' : 'kro',
|
|
3056
|
+
kubeConfigOptions,
|
|
3057
|
+
kroArtifactBundle: encodedBundle,
|
|
3058
|
+
kroArtifactOperationId: operation.id,
|
|
3059
|
+
...(artifactRequirements.length > 0 ? { artifactRequirements } : {}),
|
|
3060
|
+
...(artifactOutputUses.length > 0 ? { artifactOutputUses } : {}),
|
|
3061
|
+
...(operationBindings && Object.keys(operationBindings).length > 0
|
|
3062
|
+
? { sensitiveBindings: operationBindings }
|
|
3063
|
+
: {}),
|
|
3064
|
+
...(kroDeletion ? { kroDeletion } : {}),
|
|
3065
|
+
...(operation.role === 'hoisted-namespace'
|
|
3066
|
+
? {
|
|
3067
|
+
namespaceEmptyGate: true,
|
|
3068
|
+
namespaceOwnerRgd: String(evidenceInputs?.rgdName ?? this.rgdName),
|
|
3069
|
+
namespacePreHoistQuery: {
|
|
3070
|
+
group: String(evidenceInputs?.group ?? this.getSchemaGroup()),
|
|
3071
|
+
version: String(evidenceInputs?.version ?? this.getSchemaVersion()),
|
|
3072
|
+
kind: String(evidenceInputs?.kind ?? this.schemaDefinition.kind),
|
|
3073
|
+
},
|
|
3074
|
+
}
|
|
3075
|
+
: {}),
|
|
3076
|
+
options: {
|
|
3077
|
+
waitForReady: operation.role === 'resource-graph-definition' ||
|
|
3078
|
+
operation.role === 'singleton-owner-rgd' ||
|
|
3079
|
+
operation.role === 'singleton-owner-instance' ||
|
|
3080
|
+
(operation.role === 'instance'
|
|
3081
|
+
? (this.factoryOptions.waitForReady ?? true)
|
|
3082
|
+
: artifact.readiness.strategy !== undefined),
|
|
3083
|
+
timeout: this.factoryOptions.timeout ??
|
|
3084
|
+
(operation.role === 'resource-graph-definition' ||
|
|
3085
|
+
operation.role === 'singleton-owner-rgd'
|
|
3086
|
+
? DEFAULT_RGD_TIMEOUT
|
|
3087
|
+
: DEFAULT_DEPLOYMENT_TIMEOUT),
|
|
3088
|
+
},
|
|
3089
|
+
},
|
|
3090
|
+
};
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
1712
3093
|
/**
|
|
1713
3094
|
* Get factory status
|
|
1714
3095
|
*/
|
|
1715
|
-
async getStatus() {
|
|
1716
|
-
|
|
1717
|
-
|
|
3096
|
+
async getStatus(opts) {
|
|
3097
|
+
if (opts?.operationSignal) {
|
|
3098
|
+
return this.getStatusWithinOperation(opts.operationSignal);
|
|
3099
|
+
}
|
|
3100
|
+
return runStandaloneOperation((abortSignal) => this.getStatusWithinOperation(abortSignal), {
|
|
3101
|
+
abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
async getStatusWithinOperation(abortSignal) {
|
|
3105
|
+
const instances = await this.getInstancesWithinOperation(abortSignal);
|
|
3106
|
+
const rgdStatus = await this.getRGDStatusWithinOperation(abortSignal);
|
|
1718
3107
|
return {
|
|
1719
3108
|
name: this.name,
|
|
1720
3109
|
mode: this.mode,
|
|
@@ -1726,7 +3115,16 @@ export class KroResourceFactoryImpl {
|
|
|
1726
3115
|
/**
|
|
1727
3116
|
* Get ResourceGraphDefinition status
|
|
1728
3117
|
*/
|
|
1729
|
-
async getRGDStatus() {
|
|
3118
|
+
async getRGDStatus(opts) {
|
|
3119
|
+
if (opts?.operationSignal) {
|
|
3120
|
+
return this.getRGDStatusWithinOperation(opts.operationSignal);
|
|
3121
|
+
}
|
|
3122
|
+
return runStandaloneOperation((abortSignal) => this.getRGDStatusWithinOperation(abortSignal), {
|
|
3123
|
+
abortSignals: [this.factoryOptions.abortSignal, opts?.abortSignal],
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
async getRGDStatusWithinOperation(abortSignal) {
|
|
3127
|
+
abortSignal.throwIfAborted();
|
|
1730
3128
|
const kubeConfig = this.getKubeConfig();
|
|
1731
3129
|
const k8sApi = createBunCompatibleKubernetesObjectApi(kubeConfig);
|
|
1732
3130
|
try {
|
|
@@ -1738,6 +3136,7 @@ export class KroResourceFactoryImpl {
|
|
|
1738
3136
|
name: this.rgdName,
|
|
1739
3137
|
},
|
|
1740
3138
|
});
|
|
3139
|
+
abortSignal.throwIfAborted();
|
|
1741
3140
|
const rgd = response;
|
|
1742
3141
|
// Map Kro's state to our phase enum
|
|
1743
3142
|
let phase = 'pending';
|
|
@@ -1757,6 +3156,7 @@ export class KroResourceFactoryImpl {
|
|
|
1757
3156
|
};
|
|
1758
3157
|
}
|
|
1759
3158
|
catch (error) {
|
|
3159
|
+
abortSignal.throwIfAborted();
|
|
1760
3160
|
const k8sError = error;
|
|
1761
3161
|
// Check for 404 in multiple ways since different API clients report it differently
|
|
1762
3162
|
const bodyString = typeof k8sError.body === 'string' ? k8sError.body : JSON.stringify(k8sError.body || '');
|
|
@@ -1781,14 +3181,35 @@ export class KroResourceFactoryImpl {
|
|
|
1781
3181
|
/**
|
|
1782
3182
|
* Implementation of overloaded toYaml method
|
|
1783
3183
|
*/
|
|
1784
|
-
toYaml(spec) {
|
|
3184
|
+
toYaml(spec, options) {
|
|
1785
3185
|
this.assertNoKroPrerequisiteHookForDeclarative('toYaml()');
|
|
1786
3186
|
if (spec) {
|
|
3187
|
+
this.assertNoReservedKroSpecField(spec);
|
|
1787
3188
|
validateSpec(spec, this.schemaDefinition, {
|
|
1788
3189
|
kind: this.schemaDefinition.kind,
|
|
1789
3190
|
name: this.name,
|
|
1790
3191
|
});
|
|
1791
3192
|
this.assertInstanceNamespaceOwnershipSafe(spec);
|
|
3193
|
+
const compiledBundle = this.compiledKroArtifactBundle(spec);
|
|
3194
|
+
if (compiledBundle) {
|
|
3195
|
+
this.assertBundleCapabilities(compiledBundle.bundle, { host: null, output: 'static' });
|
|
3196
|
+
const operations = orderKroArtifactBundleOperations(compiledBundle.bundle).filter((operation) => operation.role === 'hoisted-namespace' ||
|
|
3197
|
+
operation.role === 'singleton-owner-instance' ||
|
|
3198
|
+
operation.role === 'instance');
|
|
3199
|
+
const requiredBindings = operations.flatMap((operation) => planValueSensitiveBindingNames(operation.manifest));
|
|
3200
|
+
const sensitiveBindings = resolveStaticYamlSensitiveBindings(requiredBindings, compiledBundle.sensitiveBindings, options, `KRO factory ${this.name} YAML`);
|
|
3201
|
+
const documents = operations.map((operation) => yaml
|
|
3202
|
+
.dump(materializeKroArtifactBundleOperation(operation, {
|
|
3203
|
+
sensitive: sensitiveBindings,
|
|
3204
|
+
}), {
|
|
3205
|
+
lineWidth: -1,
|
|
3206
|
+
noRefs: true,
|
|
3207
|
+
sortKeys: false,
|
|
3208
|
+
})
|
|
3209
|
+
.trimEnd());
|
|
3210
|
+
return joinYamlDocuments(documents.slice(0, -1), documents.at(-1) ?? '');
|
|
3211
|
+
}
|
|
3212
|
+
this.assertBundleCapabilities(undefined, { host: null, output: 'static' });
|
|
1792
3213
|
// Generate CRD instance YAML
|
|
1793
3214
|
const instanceName = generateInstanceName(spec, this.name);
|
|
1794
3215
|
const customResource = this.createCustomResourceInstance(instanceName, spec);
|
|
@@ -1800,15 +3221,46 @@ export class KroResourceFactoryImpl {
|
|
|
1800
3221
|
// so the consuming RGD's externalRef resolves. No-op without singletons.
|
|
1801
3222
|
// Any owned workload Namespace hoisted out of the RGD graph leads, so the CR
|
|
1802
3223
|
// always has a namespace to apply into and KRO never garbage-collects it.
|
|
1803
|
-
const ownerYamls =
|
|
3224
|
+
const ownerYamls = this.materializedSingletonOwnerInstances(spec).map((resource) => yaml
|
|
3225
|
+
.dump(JSON.parse(JSON.stringify(resource)), {
|
|
3226
|
+
lineWidth: -1,
|
|
3227
|
+
noRefs: true,
|
|
3228
|
+
sortKeys: false,
|
|
3229
|
+
})
|
|
3230
|
+
.trimEnd());
|
|
1804
3231
|
const leadingInstanceYamls = [
|
|
1805
|
-
...[...this.
|
|
3232
|
+
...[...this.materializedHoistedNamespaces(spec).values()].map(({ resource }) => yaml
|
|
3233
|
+
.dump(JSON.parse(JSON.stringify(resource)), {
|
|
3234
|
+
lineWidth: -1,
|
|
3235
|
+
noRefs: true,
|
|
3236
|
+
sortKeys: false,
|
|
3237
|
+
})
|
|
3238
|
+
.trimEnd()),
|
|
1806
3239
|
...ownerYamls,
|
|
1807
3240
|
];
|
|
1808
3241
|
return leadingInstanceYamls.length === 0
|
|
1809
3242
|
? instanceYaml
|
|
1810
3243
|
: joinYamlDocuments(leadingInstanceYamls, instanceYaml);
|
|
1811
3244
|
}
|
|
3245
|
+
const compiledBundle = this.compiledKroArtifactBundle();
|
|
3246
|
+
if (compiledBundle) {
|
|
3247
|
+
this.assertBundleCapabilities(compiledBundle.bundle, { host: null, output: 'static' });
|
|
3248
|
+
const documents = orderKroArtifactBundleOperations(compiledBundle.bundle)
|
|
3249
|
+
.filter((operation) => operation.role === 'kro-prerequisite' ||
|
|
3250
|
+
operation.role === 'singleton-owner-rgd' ||
|
|
3251
|
+
operation.role === 'resource-graph-definition')
|
|
3252
|
+
.map((operation) => yaml
|
|
3253
|
+
.dump(materializeKroArtifactBundleOperation(operation, {
|
|
3254
|
+
sensitive: compiledBundle.sensitiveBindings,
|
|
3255
|
+
}), {
|
|
3256
|
+
lineWidth: -1,
|
|
3257
|
+
noRefs: true,
|
|
3258
|
+
sortKeys: false,
|
|
3259
|
+
})
|
|
3260
|
+
.trimEnd());
|
|
3261
|
+
return joinYamlDocuments(documents.slice(0, -1), documents.at(-1) ?? '');
|
|
3262
|
+
}
|
|
3263
|
+
this.assertBundleCapabilities(undefined, { host: null, output: 'static' });
|
|
1812
3264
|
const rgdYaml = this.buildRgdYaml();
|
|
1813
3265
|
const prerequisiteYamls = this.prerequisiteResourceYamls();
|
|
1814
3266
|
const leadingYamls = [...prerequisiteYamls, ...singletonRgdYamls(this.singletonDefinitions)];
|
|
@@ -1832,7 +3284,7 @@ export class KroResourceFactoryImpl {
|
|
|
1832
3284
|
* symmetry with the callers and is deliberately NOT used to decide hoisting — see
|
|
1833
3285
|
* {@link hoistedNamespaceRefs} / {@link resourcesForRgd}.
|
|
1834
3286
|
*/
|
|
1835
|
-
|
|
3287
|
+
buildRgdManifest(_spec) {
|
|
1836
3288
|
if (this.factoryOptions.compositionAnalysis && !this.compositionAnalysisApplied) {
|
|
1837
3289
|
this.compositionAnalysisApplied = true;
|
|
1838
3290
|
applyAnalysisToResources(this.resources, this.factoryOptions.compositionAnalysis);
|
|
@@ -1842,13 +3294,15 @@ export class KroResourceFactoryImpl {
|
|
|
1842
3294
|
// to it. typekro NEVER emits a Namespace into RGD YAML; each is applied as a
|
|
1843
3295
|
// sibling (toYaml/toAlchemyResources/imperative deploy), so deleting the instance
|
|
1844
3296
|
// can never garbage-collect the namespace holding its own finalizer.
|
|
1845
|
-
const
|
|
1846
|
-
const
|
|
3297
|
+
const plannedResources = this.plannedResources();
|
|
3298
|
+
const hoistIds = this.hoistedNamespaceRefs(plannedResources);
|
|
3299
|
+
const graphResources = this.resourcesForRgd(plannedResources);
|
|
1847
3300
|
// Rewrite references to a hoisted Namespace in the STATUS mappings too
|
|
1848
3301
|
// (finding #6): a status CEL like `ns-${string(ownedNamespace.metadata.name)}`
|
|
1849
3302
|
// would otherwise reference a resource removed from the graph and KRO would
|
|
1850
3303
|
// reject the RGD. Each becomes the referenced Namespace's own name expression.
|
|
1851
3304
|
const statusMappings = rewriteHoistedNamespaceRefsInValue(this.statusMappings, hoistIds);
|
|
3305
|
+
copyCompositionAnalysisMetadata(this.statusMappings, statusMappings);
|
|
1852
3306
|
const nestedCel = rewriteHoistedNamespaceRefsInValue(this.getNestedStatusCel(), hoistIds);
|
|
1853
3307
|
// #6 — REJECT (throw) HONESTLY, never silently drop: a status field whose only
|
|
1854
3308
|
// reference was a hoisted Namespace becomes a schema-only expression
|
|
@@ -1857,10 +3311,12 @@ export class KroResourceFactoryImpl {
|
|
|
1857
3311
|
// the declared one (the old warn-and-drop), fail loudly naming the field(s).
|
|
1858
3312
|
// Resource-derived sibling fields are unaffected.
|
|
1859
3313
|
assertNoHoistWeakenedStatusFields(this.statusMappings, hoistIds, this.name);
|
|
1860
|
-
const aspectResources =
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
3314
|
+
const aspectResources = this.factoryOptions.semanticCapture
|
|
3315
|
+
? graphResources
|
|
3316
|
+
: applyAspects(graphResources, {
|
|
3317
|
+
mode: 'kro',
|
|
3318
|
+
aspects: this.factoryOptions.aspects ?? [],
|
|
3319
|
+
});
|
|
1864
3320
|
// Strict CEL diagnostics gate: fail fast if the status CEL that is about
|
|
1865
3321
|
// to be emitted references resources that are not part of this graph,
|
|
1866
3322
|
// instead of shipping an RGD that KRO will mark Inactive on the cluster.
|
|
@@ -1869,6 +3325,19 @@ export class KroResourceFactoryImpl {
|
|
|
1869
3325
|
}
|
|
1870
3326
|
const kroSchema = generateKroSchemaFromArktype(this.name, this.schemaDefinition, aspectResources, statusMappings, nestedCel, this.factoryOptions.compositionOptions
|
|
1871
3327
|
?.schemaFieldValidations);
|
|
3328
|
+
const artifactOutputUses = this.kroArtifactOutputUses();
|
|
3329
|
+
if (artifactOutputUses.length > 0) {
|
|
3330
|
+
const byRequirement = new Map();
|
|
3331
|
+
for (const use of artifactOutputUses) {
|
|
3332
|
+
const current = byRequirement.get(use.requirementId) ?? [];
|
|
3333
|
+
current.push(use);
|
|
3334
|
+
byRequirement.set(use.requirementId, current);
|
|
3335
|
+
}
|
|
3336
|
+
kroSchema.spec[KRO_ARTIFACT_BINDINGS_SPEC_FIELD] = Object.fromEntries([...byRequirement.entries()].map(([requirementId, uses]) => [
|
|
3337
|
+
kroArtifactRequirementField(requirementId),
|
|
3338
|
+
Object.fromEntries(uses.map((use) => [kroArtifactOutputField(use.output), 'string'])),
|
|
3339
|
+
]));
|
|
3340
|
+
}
|
|
1872
3341
|
// Attach nested status CEL mappings as non-enumerable property so
|
|
1873
3342
|
// serializeResourceGraphToYaml can inline virtual composition IDs.
|
|
1874
3343
|
if (nestedCel && Object.keys(nestedCel).length > 0) {
|
|
@@ -1907,13 +3376,34 @@ export class KroResourceFactoryImpl {
|
|
|
1907
3376
|
applyTernaryConditionalsToResources(aspectResources, kroSchema.__ternaryConditionals, kroSchema.__nestedStatusCel);
|
|
1908
3377
|
}
|
|
1909
3378
|
}
|
|
1910
|
-
const
|
|
3379
|
+
const rgdManifest = serializeResourceGraphToManifest(this.rgdName, aspectResources, {
|
|
3380
|
+
...this.factoryOptions.compositionOptions,
|
|
3381
|
+
namespace: this.namespace,
|
|
3382
|
+
}, kroSchema);
|
|
1911
3383
|
// VALIDATE (finding #6): after hoisting, NO reference to any removed resource id
|
|
1912
3384
|
// may remain anywhere in the emitted RGD. If one slipped through (a form the
|
|
1913
3385
|
// rewrite didn't structurally cover), fail LOUDLY here rather than shipping an
|
|
1914
3386
|
// RGD KRO will reject at runtime with a dangling `${...}` reference.
|
|
1915
|
-
this.assertNoDanglingHoistedReferences(
|
|
1916
|
-
return
|
|
3387
|
+
this.assertNoDanglingHoistedReferences(JSON.stringify(rgdManifest), new Set(hoistIds.keys()));
|
|
3388
|
+
return rgdManifest;
|
|
3389
|
+
}
|
|
3390
|
+
kroArtifactOutputUses() {
|
|
3391
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
3392
|
+
if (!capture)
|
|
3393
|
+
return [];
|
|
3394
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
3395
|
+
const plan = capture.planTemplate({
|
|
3396
|
+
...configuredPlan,
|
|
3397
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
3398
|
+
});
|
|
3399
|
+
return collectArtifactOutputUses({
|
|
3400
|
+
nodes: plan.nodes,
|
|
3401
|
+
outputs: plan.outputs,
|
|
3402
|
+
representationRequirements: plan.representationRequirements,
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
buildRgdYaml(spec) {
|
|
3406
|
+
return serializeResourceGraphDefinitionToYaml(this.buildRgdManifest(spec), this.factoryOptions.compositionOptions);
|
|
1917
3407
|
}
|
|
1918
3408
|
/**
|
|
1919
3409
|
* Assert the emitted RGD carries NO leftover reference to a hoisted (removed)
|
|
@@ -2019,7 +3509,8 @@ export class KroResourceFactoryImpl {
|
|
|
2019
3509
|
* The `spec` (when the caller has one) lets the RGD hoist the composition's own
|
|
2020
3510
|
* workload Namespace out of the graph with its schema-driven name resolved.
|
|
2021
3511
|
*/
|
|
2022
|
-
async ensureRGDDeployed(spec) {
|
|
3512
|
+
async ensureRGDDeployed(spec, abortSignal) {
|
|
3513
|
+
abortSignal?.throwIfAborted();
|
|
2023
3514
|
// Create DirectDeploymentEngine instance with KRO mode for CEL string generation
|
|
2024
3515
|
const deploymentEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
|
|
2025
3516
|
// Build the RGD YAML — shared with toYaml() so both call sites emit
|
|
@@ -2061,7 +3552,7 @@ export class KroResourceFactoryImpl {
|
|
|
2061
3552
|
rgdManifest: JSON.stringify(rgdWithMetadata, null, 2),
|
|
2062
3553
|
});
|
|
2063
3554
|
try {
|
|
2064
|
-
await this.applyKroPrerequisites(deploymentEngine);
|
|
3555
|
+
await this.applyKroPrerequisites(deploymentEngine, abortSignal);
|
|
2065
3556
|
// Deploy RGD using DirectDeploymentEngine with readiness checking
|
|
2066
3557
|
this.logger.info('Deploying RGD via engine', { rgdName: this.rgdName });
|
|
2067
3558
|
await deploymentEngine.deployResource(deployableRGD, {
|
|
@@ -2069,10 +3560,11 @@ export class KroResourceFactoryImpl {
|
|
|
2069
3560
|
namespace: this.namespace,
|
|
2070
3561
|
waitForReady: true,
|
|
2071
3562
|
timeout: this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT,
|
|
3563
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
2072
3564
|
});
|
|
2073
3565
|
this.logger.info('RGD accepted, waiting for CRD', { rgdName: this.rgdName });
|
|
2074
3566
|
// Wait for the CRD to be created by Kro using DirectDeploymentEngine
|
|
2075
|
-
await this.waitForCRDReadyWithEngine(deploymentEngine);
|
|
3567
|
+
await this.waitForCRDReadyWithEngine(deploymentEngine, abortSignal);
|
|
2076
3568
|
this.logger.info('CRD ready', { rgdName: this.rgdName });
|
|
2077
3569
|
}
|
|
2078
3570
|
catch (error) {
|
|
@@ -2102,13 +3594,22 @@ export class KroResourceFactoryImpl {
|
|
|
2102
3594
|
await deploymentEngine.dispose();
|
|
2103
3595
|
}
|
|
2104
3596
|
}
|
|
2105
|
-
async applyKroPrerequisites(deploymentEngine) {
|
|
3597
|
+
async applyKroPrerequisites(deploymentEngine, abortSignal) {
|
|
2106
3598
|
const prerequisites = this.factoryOptions.kroPrerequisites;
|
|
2107
3599
|
if (!prerequisites?.resources?.length && !prerequisites?.beforeResourceGraphDefinition) {
|
|
2108
3600
|
return;
|
|
2109
3601
|
}
|
|
3602
|
+
const deployResource = this.kroPrerequisiteDeployResource(deploymentEngine, abortSignal);
|
|
3603
|
+
for (const resource of prerequisites.resources ?? []) {
|
|
3604
|
+
abortSignal?.throwIfAborted();
|
|
3605
|
+
await deployResource(resource);
|
|
3606
|
+
}
|
|
3607
|
+
await this.runKroPrerequisiteHook(deploymentEngine, deployResource, abortSignal);
|
|
3608
|
+
}
|
|
3609
|
+
kroPrerequisiteDeployResource(deploymentEngine, abortSignal) {
|
|
2110
3610
|
const timeout = this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT;
|
|
2111
|
-
|
|
3611
|
+
return async (resource, options = {}) => {
|
|
3612
|
+
abortSignal?.throwIfAborted();
|
|
2112
3613
|
const deployable = this.normalizePrerequisiteResource(resource, {
|
|
2113
3614
|
ensureReadiness: options.waitForReady ?? false,
|
|
2114
3615
|
});
|
|
@@ -2120,27 +3621,32 @@ export class KroResourceFactoryImpl {
|
|
|
2120
3621
|
namespace: this.namespace,
|
|
2121
3622
|
waitForReady: options.waitForReady ?? false,
|
|
2122
3623
|
timeout,
|
|
3624
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
2123
3625
|
});
|
|
2124
3626
|
const crdName = this.prerequisiteCRDName(resource);
|
|
2125
3627
|
if (crdName) {
|
|
2126
|
-
await deploymentEngine.waitForCRDReady(crdName, timeout);
|
|
3628
|
+
await deploymentEngine.waitForCRDReady(crdName, timeout, abortSignal);
|
|
2127
3629
|
}
|
|
2128
3630
|
return deployed;
|
|
2129
3631
|
};
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
if (
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
3632
|
+
}
|
|
3633
|
+
async runKroPrerequisiteHook(deploymentEngine, deployResource, abortSignal) {
|
|
3634
|
+
const hook = this.factoryOptions.kroPrerequisites?.beforeResourceGraphDefinition;
|
|
3635
|
+
if (!hook)
|
|
3636
|
+
return;
|
|
3637
|
+
abortSignal?.throwIfAborted();
|
|
3638
|
+
const timeout = this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT;
|
|
3639
|
+
const deploy = deployResource ?? this.kroPrerequisiteDeployResource(deploymentEngine, abortSignal);
|
|
3640
|
+
const context = {
|
|
3641
|
+
kubernetesApi: deploymentEngine.getKubernetesApi(),
|
|
3642
|
+
kubeConfig: this.getKubeConfig(),
|
|
3643
|
+
namespace: this.namespace,
|
|
3644
|
+
timeout,
|
|
3645
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
3646
|
+
deployResource: deploy,
|
|
3647
|
+
waitForCRDReady: (crdName, waitTimeout = timeout) => deploymentEngine.waitForCRDReady(crdName, waitTimeout, abortSignal),
|
|
3648
|
+
};
|
|
3649
|
+
await hook(context);
|
|
2144
3650
|
}
|
|
2145
3651
|
normalizePrerequisiteResource(resource, options = {}) {
|
|
2146
3652
|
const name = resource.metadata?.name ?? 'unnamed';
|
|
@@ -2171,24 +3677,7 @@ export class KroResourceFactoryImpl {
|
|
|
2171
3677
|
deployable.metadata.namespace = this.namespace;
|
|
2172
3678
|
}
|
|
2173
3679
|
if (this.prerequisiteCRDName(resource) && !getReadinessEvaluator(deployable)) {
|
|
2174
|
-
setReadinessEvaluator(deployable, (
|
|
2175
|
-
const conditions = liveResource
|
|
2176
|
-
.status?.conditions ?? [];
|
|
2177
|
-
const established = conditions.find((condition) => condition.type === 'Established');
|
|
2178
|
-
const namesAccepted = conditions.find((condition) => condition.type === 'NamesAccepted');
|
|
2179
|
-
const ready = established?.status === 'True' && namesAccepted?.status === 'True';
|
|
2180
|
-
return ready
|
|
2181
|
-
? {
|
|
2182
|
-
ready: true,
|
|
2183
|
-
message: 'CustomResourceDefinition is established and names are accepted',
|
|
2184
|
-
}
|
|
2185
|
-
: {
|
|
2186
|
-
ready: false,
|
|
2187
|
-
reason: 'ConditionsNotMet',
|
|
2188
|
-
message: 'CustomResourceDefinition is not established and names accepted yet',
|
|
2189
|
-
details: { conditions },
|
|
2190
|
-
};
|
|
2191
|
-
});
|
|
3680
|
+
setReadinessEvaluator(deployable, createCustomResourceDefinitionReadinessEvaluator());
|
|
2192
3681
|
}
|
|
2193
3682
|
else if (options.attachFallbackReadiness && !getReadinessEvaluator(deployable)) {
|
|
2194
3683
|
setReadinessEvaluator(deployable, createAlwaysReadyEvaluator(resource.kind));
|
|
@@ -2199,6 +3688,34 @@ export class KroResourceFactoryImpl {
|
|
|
2199
3688
|
return deployable;
|
|
2200
3689
|
}
|
|
2201
3690
|
prerequisiteResourceYamls() {
|
|
3691
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
3692
|
+
if (capture) {
|
|
3693
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
3694
|
+
const plan = capture.planTemplate({
|
|
3695
|
+
...configuredPlan,
|
|
3696
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
3697
|
+
});
|
|
3698
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
3699
|
+
strict: true,
|
|
3700
|
+
rgdName: this.rgdName,
|
|
3701
|
+
...this.prerequisiteArtifactCompilationInputs(),
|
|
3702
|
+
...(this.factoryOptions.applyPolicy
|
|
3703
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
3704
|
+
: {}),
|
|
3705
|
+
});
|
|
3706
|
+
return kroArtifactPlanToSupportingResources(artifacts, {
|
|
3707
|
+
readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
|
|
3708
|
+
resolveReadinessStrategy: resolvePortableReadinessStrategy,
|
|
3709
|
+
})
|
|
3710
|
+
.filter(({ artifact }) => artifact.role === 'kro-prerequisite')
|
|
3711
|
+
.map(({ resource }) => yaml
|
|
3712
|
+
.dump(JSON.parse(JSON.stringify(resource)), {
|
|
3713
|
+
lineWidth: -1,
|
|
3714
|
+
noRefs: true,
|
|
3715
|
+
sortKeys: false,
|
|
3716
|
+
})
|
|
3717
|
+
.trimEnd());
|
|
3718
|
+
}
|
|
2202
3719
|
return (this.factoryOptions.kroPrerequisites?.resources ?? []).map((resource) => yaml
|
|
2203
3720
|
.dump(this.toSerializablePrerequisiteResource(resource), {
|
|
2204
3721
|
lineWidth: -1,
|
|
@@ -2207,6 +3724,196 @@ export class KroResourceFactoryImpl {
|
|
|
2207
3724
|
})
|
|
2208
3725
|
.trimEnd());
|
|
2209
3726
|
}
|
|
3727
|
+
prerequisiteArtifactCompilationInputs() {
|
|
3728
|
+
const supportingArtifacts = [];
|
|
3729
|
+
const outerEdges = [];
|
|
3730
|
+
let previousId;
|
|
3731
|
+
for (const prerequisite of this.factoryOptions.kroPrerequisites?.resources ?? []) {
|
|
3732
|
+
const normalized = this.normalizePrerequisiteResource(prerequisite, {
|
|
3733
|
+
attachFallbackReadiness: true,
|
|
3734
|
+
});
|
|
3735
|
+
const id = normalized.id;
|
|
3736
|
+
const serialized = this.toSerializablePrerequisiteResource(prerequisite);
|
|
3737
|
+
const scope = getMetadataField(normalized, 'scope') === 'cluster' ? 'cluster' : 'namespaced';
|
|
3738
|
+
const namespace = scope === 'namespaced' && typeof normalized.metadata.namespace === 'string'
|
|
3739
|
+
? lowerPlanValue(normalized.metadata.namespace).value
|
|
3740
|
+
: undefined;
|
|
3741
|
+
const evaluator = getReadinessEvaluator(normalized);
|
|
3742
|
+
const portableStrategy = evaluator ? getPortableReadinessStrategy(evaluator) : undefined;
|
|
3743
|
+
const runtimeBinding = `readiness:kro-prerequisite:${id}`;
|
|
3744
|
+
supportingArtifacts.push({
|
|
3745
|
+
id,
|
|
3746
|
+
role: 'kro-prerequisite',
|
|
3747
|
+
desired: lowerPlanValue(serialized).value,
|
|
3748
|
+
identity: {
|
|
3749
|
+
apiVersion: normalized.apiVersion,
|
|
3750
|
+
kind: normalized.kind,
|
|
3751
|
+
name: lowerPlanValue(normalized.metadata.name).value,
|
|
3752
|
+
...(namespace ? { namespace } : {}),
|
|
3753
|
+
scope,
|
|
3754
|
+
},
|
|
3755
|
+
lifecycle: {
|
|
3756
|
+
creation: 'adopt',
|
|
3757
|
+
management: 'authoritative',
|
|
3758
|
+
deletion: 'delete',
|
|
3759
|
+
instancing: { kind: 'per-cluster' },
|
|
3760
|
+
sharing: 'exclusive',
|
|
3761
|
+
},
|
|
3762
|
+
readiness: {
|
|
3763
|
+
activation: [],
|
|
3764
|
+
readyWhen: [],
|
|
3765
|
+
...(portableStrategy
|
|
3766
|
+
? { strategy: portableStrategy }
|
|
3767
|
+
: evaluator
|
|
3768
|
+
? {
|
|
3769
|
+
strategy: {
|
|
3770
|
+
kind: 'runtime-binding',
|
|
3771
|
+
binding: runtimeBinding,
|
|
3772
|
+
version: 1,
|
|
3773
|
+
},
|
|
3774
|
+
}
|
|
3775
|
+
: {}),
|
|
3776
|
+
},
|
|
3777
|
+
});
|
|
3778
|
+
if (previousId) {
|
|
3779
|
+
outerEdges.push({ kind: 'existence', prerequisite: previousId, dependent: id });
|
|
3780
|
+
}
|
|
3781
|
+
previousId = id;
|
|
3782
|
+
}
|
|
3783
|
+
if (previousId) {
|
|
3784
|
+
outerEdges.push({
|
|
3785
|
+
kind: 'existence',
|
|
3786
|
+
prerequisite: previousId,
|
|
3787
|
+
dependent: '__typekro_rgd__',
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
return { supportingArtifacts, outerEdges };
|
|
3791
|
+
}
|
|
3792
|
+
outerArtifactCompilationInputs(spec) {
|
|
3793
|
+
const prerequisiteInputs = this.prerequisiteArtifactCompilationInputs();
|
|
3794
|
+
if (spec === undefined)
|
|
3795
|
+
return prerequisiteInputs;
|
|
3796
|
+
const supportingArtifacts = [...prerequisiteInputs.supportingArtifacts];
|
|
3797
|
+
const outerEdges = [...prerequisiteInputs.outerEdges];
|
|
3798
|
+
const lastPrerequisite = prerequisiteInputs.supportingArtifacts.at(-1)?.id;
|
|
3799
|
+
for (const [name, original] of this.concreteHoistedNamespaces(spec)) {
|
|
3800
|
+
const id = KroResourceFactoryImpl.hoistedNamespaceId(name);
|
|
3801
|
+
const manifest = this.hoistedNamespaceManifest(name, original, spec);
|
|
3802
|
+
supportingArtifacts.push({
|
|
3803
|
+
id,
|
|
3804
|
+
role: 'hoisted-namespace',
|
|
3805
|
+
desired: lowerPlanValue(manifest).value,
|
|
3806
|
+
identity: {
|
|
3807
|
+
apiVersion: 'v1',
|
|
3808
|
+
kind: 'Namespace',
|
|
3809
|
+
name: lowerPlanValue(name).value,
|
|
3810
|
+
scope: 'cluster',
|
|
3811
|
+
},
|
|
3812
|
+
lifecycle: {
|
|
3813
|
+
creation: 'adopt',
|
|
3814
|
+
management: 'authoritative',
|
|
3815
|
+
deletion: 'delete-when-unused',
|
|
3816
|
+
instancing: { kind: 'per-scope', key: lowerPlanValue(name).value },
|
|
3817
|
+
sharing: 'shareable',
|
|
3818
|
+
unusedEvidence: {
|
|
3819
|
+
provider: 'typekro.hoisted-namespace-empty-owned',
|
|
3820
|
+
version: 1,
|
|
3821
|
+
inputs: lowerPlanValue({
|
|
3822
|
+
rgdName: this.rgdName,
|
|
3823
|
+
group: this.getSchemaGroup(),
|
|
3824
|
+
version: this.getSchemaVersion(),
|
|
3825
|
+
kind: this.schemaDefinition.kind,
|
|
3826
|
+
}).value,
|
|
3827
|
+
},
|
|
3828
|
+
},
|
|
3829
|
+
apply: {
|
|
3830
|
+
strategy: 'server-side-apply',
|
|
3831
|
+
fieldManager: 'typekro',
|
|
3832
|
+
fieldConflictPolicy: 'force-owned-fields',
|
|
3833
|
+
immutableFieldPolicy: 'fail',
|
|
3834
|
+
},
|
|
3835
|
+
});
|
|
3836
|
+
if (lastPrerequisite) {
|
|
3837
|
+
outerEdges.push({
|
|
3838
|
+
kind: 'existence',
|
|
3839
|
+
prerequisite: lastPrerequisite,
|
|
3840
|
+
dependent: id,
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3843
|
+
outerEdges.push({
|
|
3844
|
+
kind: 'existence',
|
|
3845
|
+
prerequisite: id,
|
|
3846
|
+
dependent: '__typekro_rgd__',
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
return { supportingArtifacts, outerEdges };
|
|
3850
|
+
}
|
|
3851
|
+
materializedHoistedNamespaces(spec) {
|
|
3852
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
3853
|
+
if (!capture) {
|
|
3854
|
+
return new Map([...this.concreteHoistedNamespaces(spec)].map(([name, original]) => [
|
|
3855
|
+
name,
|
|
3856
|
+
{ resource: this.hoistedNamespaceManifest(name, original, spec) },
|
|
3857
|
+
]));
|
|
3858
|
+
}
|
|
3859
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
3860
|
+
const plan = capture.planTemplate({
|
|
3861
|
+
...configuredPlan,
|
|
3862
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
3863
|
+
});
|
|
3864
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
3865
|
+
strict: true,
|
|
3866
|
+
rgdName: this.rgdName,
|
|
3867
|
+
...this.outerArtifactCompilationInputs(spec),
|
|
3868
|
+
...(this.factoryOptions.applyPolicy
|
|
3869
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
3870
|
+
: {}),
|
|
3871
|
+
});
|
|
3872
|
+
return new Map(kroArtifactPlanToSupportingResources(artifacts, {
|
|
3873
|
+
readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
|
|
3874
|
+
resolveReadinessStrategy: resolvePortableReadinessStrategy,
|
|
3875
|
+
})
|
|
3876
|
+
.filter(({ artifact }) => artifact.role === 'hoisted-namespace')
|
|
3877
|
+
.map(({ artifact, resource }) => [String(resource.metadata.name), { artifact, resource }]));
|
|
3878
|
+
}
|
|
3879
|
+
materializedSingletonOwnerInstances(spec) {
|
|
3880
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
3881
|
+
if (!capture) {
|
|
3882
|
+
return singletonOwnerInstanceManifests(this.discoverSingletonDefinitions(spec));
|
|
3883
|
+
}
|
|
3884
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
3885
|
+
const plan = capture.planTemplate({
|
|
3886
|
+
...configuredPlan,
|
|
3887
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
3888
|
+
});
|
|
3889
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
3890
|
+
strict: true,
|
|
3891
|
+
rgdName: this.rgdName,
|
|
3892
|
+
...this.outerArtifactCompilationInputs(spec),
|
|
3893
|
+
...(this.factoryOptions.applyPolicy
|
|
3894
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
3895
|
+
: {}),
|
|
3896
|
+
});
|
|
3897
|
+
return kroArtifactPlanToSupportingResources(artifacts, {
|
|
3898
|
+
readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
|
|
3899
|
+
resolveReadinessStrategy: resolvePortableReadinessStrategy,
|
|
3900
|
+
})
|
|
3901
|
+
.filter(({ artifact }) => artifact.role === 'singleton-owner')
|
|
3902
|
+
.map(({ resource }) => resource);
|
|
3903
|
+
}
|
|
3904
|
+
prerequisiteRuntimeReadinessEvaluators() {
|
|
3905
|
+
const bindings = {};
|
|
3906
|
+
for (const prerequisite of this.factoryOptions.kroPrerequisites?.resources ?? []) {
|
|
3907
|
+
const normalized = this.normalizePrerequisiteResource(prerequisite, {
|
|
3908
|
+
attachFallbackReadiness: true,
|
|
3909
|
+
});
|
|
3910
|
+
const evaluator = getReadinessEvaluator(normalized);
|
|
3911
|
+
if (evaluator && !getPortableReadinessStrategy(evaluator)) {
|
|
3912
|
+
bindings[`readiness:kro-prerequisite:${normalized.id}`] = evaluator;
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
return bindings;
|
|
3916
|
+
}
|
|
2210
3917
|
toSerializablePrerequisiteResource(resource) {
|
|
2211
3918
|
const normalized = this.normalizePrerequisiteResource(resource);
|
|
2212
3919
|
const toJSON = normalized.toJSON;
|
|
@@ -2218,6 +3925,60 @@ export class KroResourceFactoryImpl {
|
|
|
2218
3925
|
}
|
|
2219
3926
|
prerequisiteAlchemyDeclarations(kubeConfigOptions) {
|
|
2220
3927
|
const timeout = this.factoryOptions.timeout;
|
|
3928
|
+
const capture = this.factoryOptions.semanticCapture;
|
|
3929
|
+
if (capture) {
|
|
3930
|
+
const configuredPlan = this.factoryOptions.plan ?? {};
|
|
3931
|
+
const plan = capture.planTemplate({
|
|
3932
|
+
...configuredPlan,
|
|
3933
|
+
aspects: configuredPlan.aspects ?? this.factoryOptions.aspects ?? [],
|
|
3934
|
+
});
|
|
3935
|
+
const artifacts = compileKroArtifactPlan(plan, {
|
|
3936
|
+
strict: true,
|
|
3937
|
+
rgdName: this.rgdName,
|
|
3938
|
+
...this.prerequisiteArtifactCompilationInputs(),
|
|
3939
|
+
...(this.factoryOptions.applyPolicy
|
|
3940
|
+
? { outerApplyPolicy: this.factoryOptions.applyPolicy }
|
|
3941
|
+
: {}),
|
|
3942
|
+
});
|
|
3943
|
+
const materialized = kroArtifactPlanToSupportingResources(artifacts, {
|
|
3944
|
+
readinessEvaluators: this.prerequisiteRuntimeReadinessEvaluators(),
|
|
3945
|
+
resolveReadinessStrategy: resolvePortableReadinessStrategy,
|
|
3946
|
+
}).filter(({ artifact }) => artifact.role === 'kro-prerequisite');
|
|
3947
|
+
const declarationIdByArtifactId = new Map(materialized.map(({ artifact, resource }) => {
|
|
3948
|
+
const namespaceForId = artifact.identity?.scope === 'cluster' ? undefined : this.namespace;
|
|
3949
|
+
return [artifact.id, createAlchemyResourceId(resource, namespaceForId)];
|
|
3950
|
+
}));
|
|
3951
|
+
return materialized.map(({ artifact, resource }) => {
|
|
3952
|
+
const declarationId = declarationIdByArtifactId.get(artifact.id);
|
|
3953
|
+
if (!declarationId) {
|
|
3954
|
+
throw new TypeKroError(`KRO prerequisite ${artifact.id} has no Alchemy declaration identity.`, 'KRO_ALCHEMY_DECLARATION_MISSING', { artifactId: artifact.id });
|
|
3955
|
+
}
|
|
3956
|
+
const dependsOn = artifacts.edges.flatMap((edge) => {
|
|
3957
|
+
if ((edge.kind !== 'existence' && edge.kind !== 'ready') ||
|
|
3958
|
+
edge.dependent !== artifact.id) {
|
|
3959
|
+
return [];
|
|
3960
|
+
}
|
|
3961
|
+
const dependencyId = declarationIdByArtifactId.get(edge.prerequisite);
|
|
3962
|
+
return dependencyId ? [dependencyId] : [];
|
|
3963
|
+
});
|
|
3964
|
+
return {
|
|
3965
|
+
id: declarationId,
|
|
3966
|
+
dependsOn,
|
|
3967
|
+
props: {
|
|
3968
|
+
resource: resource,
|
|
3969
|
+
resourceId: artifact.id,
|
|
3970
|
+
namespace: this.namespace,
|
|
3971
|
+
deploymentStrategy: 'direct',
|
|
3972
|
+
kubeConfigOptions,
|
|
3973
|
+
options: {
|
|
3974
|
+
waitForReady: resource.apiVersion === 'apiextensions.k8s.io/v1' &&
|
|
3975
|
+
resource.kind === 'CustomResourceDefinition',
|
|
3976
|
+
...(timeout !== undefined && { timeout }),
|
|
3977
|
+
},
|
|
3978
|
+
},
|
|
3979
|
+
};
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
2221
3982
|
const declarations = [];
|
|
2222
3983
|
for (const resource of this.factoryOptions.kroPrerequisites?.resources ?? []) {
|
|
2223
3984
|
const normalized = this.normalizePrerequisiteResource(resource, {
|
|
@@ -2262,7 +4023,10 @@ export class KroResourceFactoryImpl {
|
|
|
2262
4023
|
return getResourceScope(resource);
|
|
2263
4024
|
}
|
|
2264
4025
|
canDefaultPrerequisiteNamespace(resource) {
|
|
2265
|
-
|
|
4026
|
+
if (Reflect.get(resource, 'scope') === 'namespaced')
|
|
4027
|
+
return true;
|
|
4028
|
+
return (this.prerequisiteScope(resource) === 'namespaced' &&
|
|
4029
|
+
getMetadataField(resource, 'scopeProvenance') === 'explicit');
|
|
2266
4030
|
}
|
|
2267
4031
|
/**
|
|
2268
4032
|
* Wait for the CRD to be created by Kro using DirectDeploymentEngine.
|
|
@@ -2272,11 +4036,11 @@ export class KroResourceFactoryImpl {
|
|
|
2272
4036
|
* is authoritative, and client-side heuristics cannot handle all cases
|
|
2273
4037
|
* (e.g., already-plural kind names that shouldn't get an extra "s").
|
|
2274
4038
|
*/
|
|
2275
|
-
async waitForCRDReadyWithEngine(deploymentEngine) {
|
|
4039
|
+
async waitForCRDReadyWithEngine(deploymentEngine, abortSignal) {
|
|
2276
4040
|
if (typeof deploymentEngine.waitForCRDByKindAndGroup !== 'function') {
|
|
2277
4041
|
throw new ResourceGraphFactoryError(`deploymentEngine.waitForCRDByKindAndGroup is not a function. Available methods: ${Object.getOwnPropertyNames(Object.getPrototypeOf(deploymentEngine)).join(', ')}`, this.name, 'deployment');
|
|
2278
4042
|
}
|
|
2279
|
-
const { plural } = await deploymentEngine.waitForCRDByKindAndGroup(this.schemaDefinition.kind, this.getSchemaGroup(), this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT);
|
|
4043
|
+
const { plural } = await deploymentEngine.waitForCRDByKindAndGroup(this.schemaDefinition.kind, this.getSchemaGroup(), this.factoryOptions.timeout || DEFAULT_RGD_TIMEOUT, abortSignal);
|
|
2280
4044
|
this.discoveredPlural = plural;
|
|
2281
4045
|
}
|
|
2282
4046
|
/**
|
|
@@ -2512,6 +4276,22 @@ export class KroResourceFactoryImpl {
|
|
|
2512
4276
|
/**
|
|
2513
4277
|
* Create custom resource instance
|
|
2514
4278
|
*/
|
|
4279
|
+
instanceManifestMetadata(spec, singletonSpecFingerprint) {
|
|
4280
|
+
const hoistedNamespaceNames = [...this.concreteHoistedNamespaces(spec).keys()];
|
|
4281
|
+
return {
|
|
4282
|
+
labels: {
|
|
4283
|
+
'typekro.io/factory': this.name,
|
|
4284
|
+
'typekro.io/mode': this.mode,
|
|
4285
|
+
[INSTANCE_RGD_LABEL]: this.rgdName,
|
|
4286
|
+
},
|
|
4287
|
+
annotations: {
|
|
4288
|
+
...(singletonSpecFingerprint
|
|
4289
|
+
? { 'typekro.io/singleton-spec-fingerprint': singletonSpecFingerprint }
|
|
4290
|
+
: {}),
|
|
4291
|
+
[HOISTED_NAMESPACES_ANNOTATION]: JSON.stringify(hoistedNamespaceNames),
|
|
4292
|
+
},
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
2515
4295
|
createCustomResourceInstance(instanceName, spec, singletonSpecFingerprint) {
|
|
2516
4296
|
const apiVersion = this.getInstanceApiVersion();
|
|
2517
4297
|
// DURABLE RECORD (finding #4): stamp the CONCRETE hoisted-namespace names on the CR so
|
|
@@ -2521,28 +4301,27 @@ export class KroResourceFactoryImpl {
|
|
|
2521
4301
|
// empty [] — so the pre-hoist guard can distinguish "this instance hoists zero namespaces"
|
|
2522
4302
|
// (a valid, safe record) from a genuinely-legacy CR that predates the annotation (missing →
|
|
2523
4303
|
// fail closed). Omitting [] made an ordinary namespace-less composition's 2nd deploy throw.
|
|
2524
|
-
const
|
|
2525
|
-
const
|
|
2526
|
-
...(singletonSpecFingerprint
|
|
2527
|
-
? { 'typekro.io/singleton-spec-fingerprint': singletonSpecFingerprint }
|
|
2528
|
-
: {}),
|
|
2529
|
-
[HOISTED_NAMESPACES_ANNOTATION]: JSON.stringify(hoistedNamespaceNames),
|
|
2530
|
-
};
|
|
2531
|
-
return {
|
|
4304
|
+
const { labels, annotations } = this.instanceManifestMetadata(spec, singletonSpecFingerprint);
|
|
4305
|
+
const legacyManifest = {
|
|
2532
4306
|
apiVersion,
|
|
2533
4307
|
kind: this.schemaDefinition.kind,
|
|
2534
4308
|
metadata: {
|
|
2535
4309
|
name: instanceName,
|
|
2536
4310
|
namespace: this.resolveInstanceNamespace(spec),
|
|
2537
|
-
labels
|
|
2538
|
-
'typekro.io/factory': this.name,
|
|
2539
|
-
'typekro.io/mode': this.mode,
|
|
2540
|
-
[INSTANCE_RGD_LABEL]: this.rgdName,
|
|
2541
|
-
},
|
|
4311
|
+
labels,
|
|
2542
4312
|
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
|
|
2543
4313
|
},
|
|
2544
4314
|
spec,
|
|
2545
4315
|
};
|
|
4316
|
+
const compilation = this.compiledKroArtifactPlan(spec, {
|
|
4317
|
+
instanceNameOverride: instanceName,
|
|
4318
|
+
...(singletonSpecFingerprint ? { singletonSpecFingerprint } : {}),
|
|
4319
|
+
});
|
|
4320
|
+
if (!compilation)
|
|
4321
|
+
return legacyManifest;
|
|
4322
|
+
return kroArtifactPlanToInstanceResource(compilation.artifacts, {
|
|
4323
|
+
sensitive: compilation.sensitiveBindings,
|
|
4324
|
+
});
|
|
2546
4325
|
}
|
|
2547
4326
|
/**
|
|
2548
4327
|
* Labels/annotations marking a hoisted workload Namespace as SHARED, RETAINED
|
|
@@ -2670,34 +4449,16 @@ export class KroResourceFactoryImpl {
|
|
|
2670
4449
|
...(originalSpec !== undefined && originalSpec !== null ? { spec: originalSpec } : {}),
|
|
2671
4450
|
};
|
|
2672
4451
|
}
|
|
2673
|
-
/**
|
|
2674
|
-
|
|
2675
|
-
* emitted as a retained resource instead. It is created OUTSIDE the KRO graph —
|
|
2676
|
-
* deps-first in GitOps/alchemy, and applied directly in the imperative path — so
|
|
2677
|
-
* KRO never garbage-collects it and the instance's finalizer can never be
|
|
2678
|
-
* stranded by its own namespace terminating.
|
|
2679
|
-
*
|
|
2680
|
-
* The COMPLETE original Namespace configuration is preserved (finding #5) — see
|
|
2681
|
-
* {@link mergedHoistedNamespaceMetadata}. It is a SHARED SINGLETON deduped by the
|
|
2682
|
-
* namespace NAME (collision-free id, see {@link hoistedNamespaceId}), so every
|
|
2683
|
-
* consumer/stack targeting the SAME workload namespace emits the SAME declaration
|
|
2684
|
-
* — alchemy dedupes them to one retained owner rather than N fighting copies.
|
|
2685
|
-
*/
|
|
2686
|
-
buildHoistedNamespaceResource(workloadNamespace, original, spec) {
|
|
4452
|
+
/** Host-neutral desired state for one hoisted Namespace. */
|
|
4453
|
+
hoistedNamespaceManifest(workloadNamespace, original, spec) {
|
|
2687
4454
|
const merged = KroResourceFactoryImpl.mergedHoistedNamespaceMetadata(workloadNamespace, original, spec);
|
|
2688
|
-
return
|
|
4455
|
+
return {
|
|
4456
|
+
apiVersion: 'v1',
|
|
4457
|
+
kind: 'Namespace',
|
|
2689
4458
|
metadata: {
|
|
2690
4459
|
name: merged.name,
|
|
2691
4460
|
labels: merged.labels,
|
|
2692
|
-
|
|
2693
|
-
// namespace as owned by THIS composition's RGD (the annotation the empty-gated
|
|
2694
|
-
// teardown checks). This build step is cluster-free, so it cannot yet distinguish
|
|
2695
|
-
// create-vs-adopt — that is resolved at DEPLOY time by
|
|
2696
|
-
// `_preserveHoistedNamespaceAdoption` (resource-registration.ts), which reads the
|
|
2697
|
-
// live namespace and STRIPS this stamp when adopting a pre-existing namespace, so
|
|
2698
|
-
// teardown never deletes a namespace typekro merely adopted. Ownership is the
|
|
2699
|
-
// PRIMARY teardown guard; the empty-gate is the secondary one.
|
|
2700
|
-
annotations: { ...merged.annotations, [NAMESPACE_OWNER_ANNOTATION]: this.rgdName },
|
|
4461
|
+
annotations: merged.annotations,
|
|
2701
4462
|
...(merged.finalizers !== undefined ? { finalizers: merged.finalizers } : {}),
|
|
2702
4463
|
...(merged.ownerReferences !== undefined
|
|
2703
4464
|
? {
|
|
@@ -2705,24 +4466,29 @@ export class KroResourceFactoryImpl {
|
|
|
2705
4466
|
}
|
|
2706
4467
|
: {}),
|
|
2707
4468
|
},
|
|
2708
|
-
...(merged.spec !== undefined
|
|
2709
|
-
|
|
2710
|
-
spec: merged.spec,
|
|
2711
|
-
}
|
|
2712
|
-
: {}),
|
|
2713
|
-
id: KroResourceFactoryImpl.hoistedNamespaceId(workloadNamespace),
|
|
2714
|
-
});
|
|
4469
|
+
...(merged.spec !== undefined ? { spec: merged.spec } : {}),
|
|
4470
|
+
};
|
|
2715
4471
|
}
|
|
2716
|
-
hoistedNamespaceAlchemyDeclaration(workloadNamespace,
|
|
4472
|
+
hoistedNamespaceAlchemyDeclaration(workloadNamespace, nsResource, artifact, kubeConfigOptions, dependsOn) {
|
|
2717
4473
|
const timeout = this.factoryOptions.timeout;
|
|
2718
|
-
const resourceId =
|
|
4474
|
+
const resourceId = artifact?.id ?? nsResource.id;
|
|
4475
|
+
const evidence = artifact?.lifecycle.unusedEvidence;
|
|
4476
|
+
if (artifact &&
|
|
4477
|
+
(artifact.lifecycle.creation !== 'adopt' ||
|
|
4478
|
+
artifact.lifecycle.deletion !== 'delete-when-unused' ||
|
|
4479
|
+
evidence?.provider !== 'typekro.hoisted-namespace-empty-owned')) {
|
|
4480
|
+
throw new TypeKroError(`Hoisted Namespace artifact ${artifact.id} has an unsupported lifecycle contract.`, 'INVALID_ARTIFACT_LIFECYCLE', { artifactId: artifact.id });
|
|
4481
|
+
}
|
|
4482
|
+
const evidenceInputs = evidence
|
|
4483
|
+
? materializePlanValue(evidence.inputs)
|
|
4484
|
+
: undefined;
|
|
2719
4485
|
return {
|
|
2720
4486
|
// Collision-free (finding #7) and cluster-scoped (no namespace segment):
|
|
2721
4487
|
// distinct workload namespaces → distinct ids; same name → same id (dedup).
|
|
2722
4488
|
id: KroResourceFactoryImpl.hoistedNamespaceId(workloadNamespace),
|
|
2723
4489
|
dependsOn,
|
|
2724
4490
|
props: {
|
|
2725
|
-
resource:
|
|
4491
|
+
resource: nsResource,
|
|
2726
4492
|
...(resourceId !== undefined ? { resourceId } : {}),
|
|
2727
4493
|
namespace: workloadNamespace,
|
|
2728
4494
|
deploymentStrategy: 'direct',
|
|
@@ -2733,16 +4499,16 @@ export class KroResourceFactoryImpl {
|
|
|
2733
4499
|
// declaration) are gone — the load-bearing delete-after-RGD ordering (a
|
|
2734
4500
|
// namespace deleted while the CR is still inside it re-creates the finalizer
|
|
2735
4501
|
// deadlock).
|
|
2736
|
-
namespaceEmptyGate:
|
|
4502
|
+
namespaceEmptyGate: artifact?.lifecycle.deletion === 'delete-when-unused' || artifact === undefined,
|
|
2737
4503
|
// Ownership record for the empty-gated delete (finding #4): only a namespace
|
|
2738
4504
|
// carrying this RGD's `created-by-rgd` annotation is a candidate.
|
|
2739
|
-
namespaceOwnerRgd: this.rgdName,
|
|
4505
|
+
namespaceOwnerRgd: String(evidenceInputs?.rgdName ?? this.rgdName),
|
|
2740
4506
|
// CRD coordinates so the alchemy pre-hoist check enumerates EVERY existing
|
|
2741
4507
|
// instance of this shared RGD (finding #7), not just the incoming namespace.
|
|
2742
4508
|
namespacePreHoistQuery: {
|
|
2743
|
-
group: this.getSchemaGroup(),
|
|
2744
|
-
version: this.getSchemaVersion(),
|
|
2745
|
-
kind: this.schemaDefinition.kind,
|
|
4509
|
+
group: String(evidenceInputs?.group ?? this.getSchemaGroup()),
|
|
4510
|
+
version: String(evidenceInputs?.version ?? this.getSchemaVersion()),
|
|
4511
|
+
kind: String(evidenceInputs?.kind ?? this.schemaDefinition.kind),
|
|
2746
4512
|
},
|
|
2747
4513
|
kubeConfigOptions,
|
|
2748
4514
|
options: {
|
|
@@ -2752,25 +4518,6 @@ export class KroResourceFactoryImpl {
|
|
|
2752
4518
|
},
|
|
2753
4519
|
};
|
|
2754
4520
|
}
|
|
2755
|
-
hoistedNamespaceYaml(workloadNamespace, original, spec) {
|
|
2756
|
-
const merged = KroResourceFactoryImpl.mergedHoistedNamespaceMetadata(workloadNamespace, original, spec);
|
|
2757
|
-
return yaml
|
|
2758
|
-
.dump({
|
|
2759
|
-
apiVersion: 'v1',
|
|
2760
|
-
kind: 'Namespace',
|
|
2761
|
-
metadata: {
|
|
2762
|
-
name: merged.name,
|
|
2763
|
-
labels: merged.labels,
|
|
2764
|
-
annotations: merged.annotations,
|
|
2765
|
-
...(merged.finalizers !== undefined ? { finalizers: merged.finalizers } : {}),
|
|
2766
|
-
...(merged.ownerReferences !== undefined
|
|
2767
|
-
? { ownerReferences: merged.ownerReferences }
|
|
2768
|
-
: {}),
|
|
2769
|
-
},
|
|
2770
|
-
...(merged.spec !== undefined ? { spec: merged.spec } : {}),
|
|
2771
|
-
}, { lineWidth: -1, noRefs: true, sortKeys: false })
|
|
2772
|
-
.trimEnd();
|
|
2773
|
-
}
|
|
2774
4521
|
assertInstanceNamespaceOwnershipSafe(spec) {
|
|
2775
4522
|
const compositionFn = this.factoryOptions.compositionFn;
|
|
2776
4523
|
assertKroInstanceNamespaceOwnershipSafe({
|
|
@@ -2786,11 +4533,6 @@ export class KroResourceFactoryImpl {
|
|
|
2786
4533
|
...(compositionFn ? { compositionFn } : {}),
|
|
2787
4534
|
});
|
|
2788
4535
|
}
|
|
2789
|
-
assertSingletonOwnersNamespaceOwnershipSafe(spec) {
|
|
2790
|
-
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
2791
|
-
assertSingletonOwnerNamespaceOwnershipSafe(definition);
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
4536
|
/**
|
|
2795
4537
|
* Create an Enhanced proxy for the instance with mixed static/dynamic hydration
|
|
2796
4538
|
*/
|
|
@@ -3013,7 +4755,7 @@ export class KroResourceFactoryImpl {
|
|
|
3013
4755
|
* Wait for Kro instance to be ready with Kro-specific logic.
|
|
3014
4756
|
* Delegates to the shared `waitForKroInstanceReady` in `kro-readiness.ts`.
|
|
3015
4757
|
*/
|
|
3016
|
-
async waitForKroInstanceReady(instanceName, timeout, instanceNamespace = this.resolveInstanceNamespace()) {
|
|
4758
|
+
async waitForKroInstanceReady(instanceName, timeout, instanceNamespace = this.resolveInstanceNamespace(), abortSignal) {
|
|
3017
4759
|
const apiVersion = this.getInstanceApiVersion();
|
|
3018
4760
|
const kubeConfig = this.getKubeConfig();
|
|
3019
4761
|
const k8sApi = createBunCompatibleKubernetesObjectApi(kubeConfig);
|
|
@@ -3027,6 +4769,7 @@ export class KroResourceFactoryImpl {
|
|
|
3027
4769
|
kind: this.schemaDefinition.kind,
|
|
3028
4770
|
rgdName: this.rgdName,
|
|
3029
4771
|
factoryContext: this.name,
|
|
4772
|
+
...(abortSignal ? { abortSignal } : {}),
|
|
3030
4773
|
});
|
|
3031
4774
|
}
|
|
3032
4775
|
/**
|