typekro 0.3.0 → 0.4.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/README.md +142 -1054
- package/dist/.tsbuildinfo +1 -1
- package/dist/alchemy/deployers.d.ts +0 -5
- package/dist/alchemy/deployers.d.ts.map +1 -1
- package/dist/alchemy/deployers.js +25 -72
- package/dist/alchemy/deployers.js.map +1 -1
- package/dist/core/composition/imperative.d.ts +2 -2
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +394 -10
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.d.ts +1 -9
- package/dist/core/composition/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.js +76 -10
- package/dist/core/composition/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/core/composition/typekro-runtime/types.d.ts +8 -5
- package/dist/core/composition/typekro-runtime/types.d.ts.map +1 -1
- package/dist/core/composition/typekro-runtime/types.js.map +1 -1
- package/dist/core/constants/brands.d.ts +20 -0
- package/dist/core/constants/brands.d.ts.map +1 -1
- package/dist/core/constants/brands.js +20 -0
- package/dist/core/constants/brands.js.map +1 -1
- package/dist/core/deployment/direct-factory.d.ts +36 -1
- package/dist/core/deployment/direct-factory.d.ts.map +1 -1
- package/dist/core/deployment/direct-factory.js +289 -4
- package/dist/core/deployment/direct-factory.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +57 -2
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +677 -49
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/deployment/event-filter.d.ts.map +1 -1
- package/dist/core/deployment/event-filter.js +6 -7
- package/dist/core/deployment/event-filter.js.map +1 -1
- package/dist/core/deployment/event-monitor.d.ts +36 -2
- package/dist/core/deployment/event-monitor.d.ts.map +1 -1
- package/dist/core/deployment/event-monitor.js +183 -33
- package/dist/core/deployment/event-monitor.js.map +1 -1
- package/dist/core/deployment/kro-factory.d.ts +22 -0
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +291 -27
- package/dist/core/deployment/kro-factory.js.map +1 -1
- package/dist/core/deployment/readiness.d.ts.map +1 -1
- package/dist/core/deployment/readiness.js +4 -2
- package/dist/core/deployment/readiness.js.map +1 -1
- package/dist/core/deployment/rollback-manager.d.ts +2 -1
- package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
- package/dist/core/deployment/rollback-manager.js +5 -2
- package/dist/core/deployment/rollback-manager.js.map +1 -1
- package/dist/core/deployment/shared-utilities.js.map +1 -1
- package/dist/core/deployment/status-hydrator.d.ts +1 -1
- package/dist/core/deployment/status-hydrator.d.ts.map +1 -1
- package/dist/core/deployment/status-hydrator.js +8 -5
- package/dist/core/deployment/status-hydrator.js.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.js +60 -21
- package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.d.ts +7 -0
- package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.js +50 -0
- package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/kro-strategy.d.ts +5 -0
- package/dist/core/deployment/strategies/kro-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/kro-strategy.js +53 -4
- package/dist/core/deployment/strategies/kro-strategy.js.map +1 -1
- package/dist/core/evaluation/cel-optimizer.d.ts.map +1 -1
- package/dist/core/evaluation/cel-optimizer.js +7 -13
- package/dist/core/evaluation/cel-optimizer.js.map +1 -1
- package/dist/core/expressions/analyzer.d.ts +6 -6
- package/dist/core/expressions/analyzer.d.ts.map +1 -1
- package/dist/core/expressions/analyzer.js +40 -75
- package/dist/core/expressions/analyzer.js.map +1 -1
- package/dist/core/expressions/cel-conversion-engine.d.ts.map +1 -1
- package/dist/core/expressions/cel-conversion-engine.js +10 -11
- package/dist/core/expressions/cel-conversion-engine.js.map +1 -1
- package/dist/core/expressions/composition-integration.d.ts +2 -2
- package/dist/core/expressions/composition-integration.d.ts.map +1 -1
- package/dist/core/expressions/composition-integration.js +20 -12
- package/dist/core/expressions/composition-integration.js.map +1 -1
- package/dist/core/expressions/field-hydration-processor.d.ts +4 -1
- package/dist/core/expressions/field-hydration-processor.d.ts.map +1 -1
- package/dist/core/expressions/field-hydration-processor.js +12 -13
- package/dist/core/expressions/field-hydration-processor.js.map +1 -1
- package/dist/core/expressions/imperative-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/imperative-analyzer.js +193 -49
- package/dist/core/expressions/imperative-analyzer.js.map +1 -1
- package/dist/core/expressions/index.d.ts +2 -0
- package/dist/core/expressions/index.d.ts.map +1 -1
- package/dist/core/expressions/index.js +2 -0
- package/dist/core/expressions/index.js.map +1 -1
- package/dist/core/expressions/magic-proxy-analyzer.d.ts +1 -1
- package/dist/core/expressions/magic-proxy-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy-analyzer.js +8 -7
- package/dist/core/expressions/magic-proxy-analyzer.js.map +1 -1
- package/dist/core/expressions/parser.d.ts +163 -0
- package/dist/core/expressions/parser.d.ts.map +1 -0
- package/dist/core/expressions/parser.js +272 -0
- package/dist/core/expressions/parser.js.map +1 -0
- package/dist/core/expressions/status-builder-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/status-builder-analyzer.js +15 -5
- package/dist/core/expressions/status-builder-analyzer.js.map +1 -1
- package/dist/core/kubernetes/api.d.ts.map +1 -1
- package/dist/core/kubernetes/api.js +5 -6
- package/dist/core/kubernetes/api.js.map +1 -1
- package/dist/core/kubernetes/bun-api-client.d.ts +95 -0
- package/dist/core/kubernetes/bun-api-client.d.ts.map +1 -0
- package/dist/core/kubernetes/bun-api-client.js +160 -0
- package/dist/core/kubernetes/bun-api-client.js.map +1 -0
- package/dist/core/kubernetes/bun-http-library.d.ts +38 -0
- package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -0
- package/dist/core/kubernetes/bun-http-library.js +133 -0
- package/dist/core/kubernetes/bun-http-library.js.map +1 -0
- package/dist/core/kubernetes/client-provider.d.ts +3 -0
- package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
- package/dist/core/kubernetes/client-provider.js +32 -23
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/kubernetes/errors.d.ts +189 -0
- package/dist/core/kubernetes/errors.d.ts.map +1 -0
- package/dist/core/kubernetes/errors.js +298 -0
- package/dist/core/kubernetes/errors.js.map +1 -0
- package/dist/core/kubernetes/index.d.ts +19 -0
- package/dist/core/kubernetes/index.d.ts.map +1 -0
- package/dist/core/kubernetes/index.js +23 -0
- package/dist/core/kubernetes/index.js.map +1 -0
- package/dist/core/kubernetes/type-guards.d.ts +142 -0
- package/dist/core/kubernetes/type-guards.d.ts.map +1 -0
- package/dist/core/kubernetes/type-guards.js +151 -0
- package/dist/core/kubernetes/type-guards.js.map +1 -0
- package/dist/core/logging/logger.d.ts.map +1 -1
- package/dist/core/logging/logger.js +8 -0
- package/dist/core/logging/logger.js.map +1 -1
- package/dist/core/readiness/cluster-state.d.ts.map +1 -1
- package/dist/core/readiness/cluster-state.js +6 -4
- package/dist/core/readiness/cluster-state.js.map +1 -1
- package/dist/core/references/cel-evaluator.d.ts.map +1 -1
- package/dist/core/references/cel-evaluator.js +12 -2
- package/dist/core/references/cel-evaluator.js.map +1 -1
- package/dist/core/references/cel.d.ts +13 -1
- package/dist/core/references/cel.d.ts.map +1 -1
- package/dist/core/references/cel.js +50 -0
- package/dist/core/references/cel.js.map +1 -1
- package/dist/core/references/resolver.d.ts +28 -1
- package/dist/core/references/resolver.d.ts.map +1 -1
- package/dist/core/references/resolver.js +325 -11
- 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 +27 -13
- package/dist/core/references/schema-proxy.js.map +1 -1
- package/dist/core/scope/resolver.d.ts +16 -0
- package/dist/core/scope/resolver.d.ts.map +1 -0
- package/dist/core/scope/resolver.js +45 -0
- package/dist/core/scope/resolver.js.map +1 -0
- package/dist/core/serialization/core.d.ts +1 -1
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +78 -54
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/types/common.d.ts +23 -1
- package/dist/core/types/common.d.ts.map +1 -1
- package/dist/core/types/deployment.d.ts +71 -1
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js +24 -0
- package/dist/core/types/deployment.js.map +1 -1
- package/dist/core/types/factory-scope.d.ts +42 -0
- package/dist/core/types/factory-scope.d.ts.map +1 -0
- package/dist/core/types/factory-scope.js +14 -0
- package/dist/core/types/factory-scope.js.map +1 -0
- package/dist/core/types/kubernetes.d.ts +146 -4
- package/dist/core/types/kubernetes.d.ts.map +1 -1
- package/dist/core/types/references.d.ts +47 -3
- package/dist/core/types/references.d.ts.map +1 -1
- package/dist/core/types/references.js.map +1 -1
- package/dist/core/types/serialization.d.ts +17 -6
- package/dist/core/types/serialization.d.ts.map +1 -1
- package/dist/core/utils/crd-patcher.d.ts +30 -0
- package/dist/core/utils/crd-patcher.d.ts.map +1 -0
- package/dist/core/utils/crd-patcher.js +198 -0
- package/dist/core/utils/crd-patcher.js.map +1 -0
- package/dist/core/utils/crd-schema-fix.d.ts +108 -0
- package/dist/core/utils/crd-schema-fix.d.ts.map +1 -0
- package/dist/core/utils/crd-schema-fix.js +245 -0
- package/dist/core/utils/crd-schema-fix.js.map +1 -0
- package/dist/core/utils/index.d.ts +12 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/index.js +12 -0
- package/dist/core/utils/index.js.map +1 -0
- package/dist/core/utils/minimal-connection-reset-suppression.d.ts +16 -0
- package/dist/core/utils/minimal-connection-reset-suppression.d.ts.map +1 -0
- package/dist/core/utils/minimal-connection-reset-suppression.js +72 -0
- package/dist/core/utils/minimal-connection-reset-suppression.js.map +1 -0
- package/dist/core/utils/output-filter.d.ts +16 -0
- package/dist/core/utils/output-filter.d.ts.map +1 -0
- package/dist/core/utils/output-filter.js +90 -0
- package/dist/core/utils/output-filter.js.map +1 -0
- package/dist/core/utils/scoped-error-suppression.d.ts +25 -0
- package/dist/core/utils/scoped-error-suppression.d.ts.map +1 -0
- package/dist/core/utils/scoped-error-suppression.js +226 -0
- package/dist/core/utils/scoped-error-suppression.js.map +1 -0
- package/dist/core/validation/cel-validator.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.js +51 -15
- package/dist/core/validation/cel-validator.js.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts +38 -0
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -0
- package/dist/factories/apisix/compositions/apisix-bootstrap.js +280 -0
- package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -0
- package/dist/factories/apisix/compositions/index.d.ts +5 -0
- package/dist/factories/apisix/compositions/index.d.ts.map +1 -0
- package/dist/factories/apisix/compositions/index.js +5 -0
- package/dist/factories/apisix/compositions/index.js.map +1 -0
- package/dist/factories/apisix/index.d.ts +11 -0
- package/dist/factories/apisix/index.d.ts.map +1 -0
- package/dist/factories/apisix/index.js +11 -0
- package/dist/factories/apisix/index.js.map +1 -0
- package/dist/factories/apisix/resources/helm.d.ts +59 -0
- package/dist/factories/apisix/resources/helm.d.ts.map +1 -0
- package/dist/factories/apisix/resources/helm.js +175 -0
- package/dist/factories/apisix/resources/helm.js.map +1 -0
- package/dist/factories/apisix/resources/index.d.ts +5 -0
- package/dist/factories/apisix/resources/index.d.ts.map +1 -0
- package/dist/factories/apisix/resources/index.js +5 -0
- package/dist/factories/apisix/resources/index.js.map +1 -0
- package/dist/factories/apisix/types.d.ts +339 -0
- package/dist/factories/apisix/types.d.ts.map +1 -0
- package/dist/factories/apisix/types.js +88 -0
- package/dist/factories/apisix/types.js.map +1 -0
- package/dist/factories/apisix/utils/helm-values-mapper.d.ts +19 -0
- package/dist/factories/apisix/utils/helm-values-mapper.d.ts.map +1 -0
- package/dist/factories/apisix/utils/helm-values-mapper.js +108 -0
- package/dist/factories/apisix/utils/helm-values-mapper.js.map +1 -0
- package/dist/factories/apisix/utils/index.d.ts +5 -0
- package/dist/factories/apisix/utils/index.d.ts.map +1 -0
- package/dist/factories/apisix/utils/index.js +5 -0
- package/dist/factories/apisix/utils/index.js.map +1 -0
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.d.ts +45 -0
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.d.ts.map +1 -0
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js +323 -0
- package/dist/factories/cert-manager/compositions/cert-manager-bootstrap.js.map +1 -0
- package/dist/factories/cert-manager/compositions/index.d.ts +2 -0
- package/dist/factories/cert-manager/compositions/index.d.ts.map +1 -0
- package/dist/factories/cert-manager/compositions/index.js +2 -0
- package/dist/factories/cert-manager/compositions/index.js.map +1 -0
- package/dist/factories/cert-manager/index.d.ts +10 -0
- package/dist/factories/cert-manager/index.d.ts.map +1 -0
- package/dist/factories/cert-manager/index.js +19 -0
- package/dist/factories/cert-manager/index.js.map +1 -0
- package/dist/factories/cert-manager/resources/certificates.d.ts +39 -0
- package/dist/factories/cert-manager/resources/certificates.d.ts.map +1 -0
- package/dist/factories/cert-manager/resources/certificates.js +132 -0
- package/dist/factories/cert-manager/resources/certificates.js.map +1 -0
- package/dist/factories/cert-manager/resources/challenges.d.ts +113 -0
- package/dist/factories/cert-manager/resources/challenges.d.ts.map +1 -0
- package/dist/factories/cert-manager/resources/challenges.js +281 -0
- package/dist/factories/cert-manager/resources/challenges.js.map +1 -0
- package/dist/factories/cert-manager/resources/helm.d.ts +57 -0
- package/dist/factories/cert-manager/resources/helm.d.ts.map +1 -0
- package/dist/factories/cert-manager/resources/helm.js +390 -0
- package/dist/factories/cert-manager/resources/helm.js.map +1 -0
- package/dist/factories/cert-manager/resources/index.d.ts +5 -0
- package/dist/factories/cert-manager/resources/index.d.ts.map +1 -0
- package/dist/factories/cert-manager/resources/index.js +6 -0
- package/dist/factories/cert-manager/resources/index.js.map +1 -0
- package/dist/factories/cert-manager/resources/issuers.d.ts +79 -0
- package/dist/factories/cert-manager/resources/issuers.d.ts.map +1 -0
- package/dist/factories/cert-manager/resources/issuers.js +204 -0
- package/dist/factories/cert-manager/resources/issuers.js.map +1 -0
- package/dist/factories/cert-manager/types.d.ts +1015 -0
- package/dist/factories/cert-manager/types.d.ts.map +1 -0
- package/dist/factories/cert-manager/types.js +198 -0
- package/dist/factories/cert-manager/types.js.map +1 -0
- package/dist/factories/cert-manager/utils/helm-values-mapper.d.ts +19 -0
- package/dist/factories/cert-manager/utils/helm-values-mapper.d.ts.map +1 -0
- package/dist/factories/cert-manager/utils/helm-values-mapper.js +117 -0
- package/dist/factories/cert-manager/utils/helm-values-mapper.js.map +1 -0
- package/dist/factories/cert-manager/utils/index.d.ts +2 -0
- package/dist/factories/cert-manager/utils/index.d.ts.map +1 -0
- package/dist/factories/cert-manager/utils/index.js +2 -0
- package/dist/factories/cert-manager/utils/index.js.map +1 -0
- package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +220 -0
- package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts.map +1 -0
- package/dist/factories/cilium/compositions/cilium-bootstrap.js +293 -0
- package/dist/factories/cilium/compositions/cilium-bootstrap.js.map +1 -0
- package/dist/factories/cilium/compositions/index.d.ts +8 -0
- package/dist/factories/cilium/compositions/index.d.ts.map +1 -0
- package/dist/factories/cilium/compositions/index.js +11 -0
- package/dist/factories/cilium/compositions/index.js.map +1 -0
- package/dist/factories/cilium/index.d.ts +10 -0
- package/dist/factories/cilium/index.d.ts.map +1 -0
- package/dist/factories/cilium/index.js +19 -0
- package/dist/factories/cilium/index.js.map +1 -0
- package/dist/factories/cilium/resources/bgp.d.ts +10 -0
- package/dist/factories/cilium/resources/bgp.d.ts.map +1 -0
- package/dist/factories/cilium/resources/bgp.js +14 -0
- package/dist/factories/cilium/resources/bgp.js.map +1 -0
- package/dist/factories/cilium/resources/gateway.d.ts +72 -0
- package/dist/factories/cilium/resources/gateway.d.ts.map +1 -0
- package/dist/factories/cilium/resources/gateway.js +118 -0
- package/dist/factories/cilium/resources/gateway.js.map +1 -0
- package/dist/factories/cilium/resources/helm.d.ts +93 -0
- package/dist/factories/cilium/resources/helm.d.ts.map +1 -0
- package/dist/factories/cilium/resources/helm.js +377 -0
- package/dist/factories/cilium/resources/helm.js.map +1 -0
- package/dist/factories/cilium/resources/index.d.ts +14 -0
- package/dist/factories/cilium/resources/index.d.ts.map +1 -0
- package/dist/factories/cilium/resources/index.js +35 -0
- package/dist/factories/cilium/resources/index.js.map +1 -0
- package/dist/factories/cilium/resources/load-balancer.d.ts +9 -0
- package/dist/factories/cilium/resources/load-balancer.d.ts.map +1 -0
- package/dist/factories/cilium/resources/load-balancer.js +12 -0
- package/dist/factories/cilium/resources/load-balancer.js.map +1 -0
- package/dist/factories/cilium/resources/networking.d.ts +202 -0
- package/dist/factories/cilium/resources/networking.d.ts.map +1 -0
- package/dist/factories/cilium/resources/networking.js +581 -0
- package/dist/factories/cilium/resources/networking.js.map +1 -0
- package/dist/factories/cilium/resources/observability.d.ts +9 -0
- package/dist/factories/cilium/resources/observability.d.ts.map +1 -0
- package/dist/factories/cilium/resources/observability.js +11 -0
- package/dist/factories/cilium/resources/observability.js.map +1 -0
- package/dist/factories/cilium/resources/security.d.ts +10 -0
- package/dist/factories/cilium/resources/security.d.ts.map +1 -0
- package/dist/factories/cilium/resources/security.js +14 -0
- package/dist/factories/cilium/resources/security.js.map +1 -0
- package/dist/factories/cilium/types.d.ts +1001 -0
- package/dist/factories/cilium/types.d.ts.map +1 -0
- package/dist/factories/cilium/types.js +79 -0
- package/dist/factories/cilium/types.js.map +1 -0
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts +32 -0
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -0
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +152 -0
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -0
- package/dist/factories/external-dns/compositions/index.d.ts +2 -0
- package/dist/factories/external-dns/compositions/index.d.ts.map +1 -0
- package/dist/factories/external-dns/compositions/index.js +3 -0
- package/dist/factories/external-dns/compositions/index.js.map +1 -0
- package/dist/factories/external-dns/index.d.ts +10 -0
- package/dist/factories/external-dns/index.d.ts.map +1 -0
- package/dist/factories/external-dns/index.js +19 -0
- package/dist/factories/external-dns/index.js.map +1 -0
- package/dist/factories/external-dns/resources/dns-endpoint.d.ts +54 -0
- package/dist/factories/external-dns/resources/dns-endpoint.d.ts.map +1 -0
- package/dist/factories/external-dns/resources/dns-endpoint.js +53 -0
- package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -0
- package/dist/factories/external-dns/resources/helm.d.ts +57 -0
- package/dist/factories/external-dns/resources/helm.d.ts.map +1 -0
- package/dist/factories/external-dns/resources/helm.js +435 -0
- package/dist/factories/external-dns/resources/helm.js.map +1 -0
- package/dist/factories/external-dns/resources/index.d.ts +3 -0
- package/dist/factories/external-dns/resources/index.d.ts.map +1 -0
- package/dist/factories/external-dns/resources/index.js +4 -0
- package/dist/factories/external-dns/resources/index.js.map +1 -0
- package/dist/factories/external-dns/types.d.ts +180 -0
- package/dist/factories/external-dns/types.d.ts.map +1 -0
- package/dist/factories/external-dns/types.js +39 -0
- package/dist/factories/external-dns/types.js.map +1 -0
- package/dist/factories/flux/git-repository.d.ts +2 -2
- package/dist/factories/flux/git-repository.d.ts.map +1 -1
- package/dist/factories/helm/types.d.ts +24 -3
- package/dist/factories/helm/types.d.ts.map +1 -1
- package/dist/factories/index.d.ts +7 -1
- package/dist/factories/index.d.ts.map +1 -1
- package/dist/factories/index.js +21 -1
- package/dist/factories/index.js.map +1 -1
- package/dist/factories/kubernetes/config/config-map.d.ts +4 -1
- package/dist/factories/kubernetes/config/config-map.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/config-map.js +4 -0
- package/dist/factories/kubernetes/config/config-map.js.map +1 -1
- package/dist/factories/kubernetes/config/secret.d.ts +6 -1
- package/dist/factories/kubernetes/config/secret.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/secret.js +4 -0
- package/dist/factories/kubernetes/config/secret.js.map +1 -1
- package/dist/factories/kubernetes/networking/service.d.ts.map +1 -1
- package/dist/factories/kubernetes/networking/service.js +6 -1
- package/dist/factories/kubernetes/networking/service.js.map +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role-binding.d.ts.map +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role-binding.js +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role-binding.js.map +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role.d.ts.map +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role.js +1 -1
- package/dist/factories/kubernetes/rbac/cluster-role.js.map +1 -1
- package/dist/factories/kubernetes/scheduling/priority-class.d.ts.map +1 -1
- package/dist/factories/kubernetes/scheduling/priority-class.js +1 -1
- package/dist/factories/kubernetes/scheduling/priority-class.js.map +1 -1
- package/dist/factories/kubernetes/storage/storage-class.d.ts.map +1 -1
- package/dist/factories/kubernetes/storage/storage-class.js +1 -1
- package/dist/factories/kubernetes/storage/storage-class.js.map +1 -1
- package/dist/factories/kubernetes/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/kubernetes/workloads/deployment.js +9 -4
- package/dist/factories/kubernetes/workloads/deployment.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.js +2 -2
- package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts +31 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.js +80 -7
- package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
- package/dist/factories/pebble/compositions/index.d.ts +2 -0
- package/dist/factories/pebble/compositions/index.d.ts.map +1 -0
- package/dist/factories/pebble/compositions/index.js +3 -0
- package/dist/factories/pebble/compositions/index.js.map +1 -0
- package/dist/factories/pebble/compositions/pebble-bootstrap.d.ts +56 -0
- package/dist/factories/pebble/compositions/pebble-bootstrap.d.ts.map +1 -0
- package/dist/factories/pebble/compositions/pebble-bootstrap.js +97 -0
- package/dist/factories/pebble/compositions/pebble-bootstrap.js.map +1 -0
- package/dist/factories/pebble/index.d.ts +10 -0
- package/dist/factories/pebble/index.d.ts.map +1 -0
- package/dist/factories/pebble/index.js +19 -0
- package/dist/factories/pebble/index.js.map +1 -0
- package/dist/factories/pebble/resources/helm.d.ts +99 -0
- package/dist/factories/pebble/resources/helm.d.ts.map +1 -0
- package/dist/factories/pebble/resources/helm.js +167 -0
- package/dist/factories/pebble/resources/helm.js.map +1 -0
- package/dist/factories/pebble/resources/index.d.ts +2 -0
- package/dist/factories/pebble/resources/index.d.ts.map +1 -0
- package/dist/factories/pebble/resources/index.js +3 -0
- package/dist/factories/pebble/resources/index.js.map +1 -0
- package/dist/factories/pebble/types.d.ts +184 -0
- package/dist/factories/pebble/types.d.ts.map +1 -0
- package/dist/factories/pebble/types.js +23 -0
- package/dist/factories/pebble/types.js.map +1 -0
- package/dist/factories/pebble/utils/helm-values-mapper.d.ts +21 -0
- package/dist/factories/pebble/utils/helm-values-mapper.d.ts.map +1 -0
- package/dist/factories/pebble/utils/helm-values-mapper.js +96 -0
- package/dist/factories/pebble/utils/helm-values-mapper.js.map +1 -0
- package/dist/factories/shared.d.ts +18 -1
- package/dist/factories/shared.d.ts.map +1 -1
- package/dist/factories/shared.js +89 -8
- package/dist/factories/shared.js.map +1 -1
- package/dist/factories/simple/config/config-map.d.ts +7 -2
- package/dist/factories/simple/config/config-map.d.ts.map +1 -1
- package/dist/factories/simple/config/config-map.js +3 -0
- package/dist/factories/simple/config/config-map.js.map +1 -1
- package/dist/factories/simple/config/secret.d.ts +11 -2
- package/dist/factories/simple/config/secret.d.ts.map +1 -1
- package/dist/factories/simple/config/secret.js +23 -1
- package/dist/factories/simple/config/secret.js.map +1 -1
- package/dist/factories/simple/storage/persistent-volume-claim.d.ts.map +1 -1
- package/dist/factories/simple/storage/persistent-volume-claim.js +1 -0
- package/dist/factories/simple/storage/persistent-volume-claim.js.map +1 -1
- package/dist/factories/simple/types.d.ts +5 -1
- package/dist/factories/simple/types.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +67 -0
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/type-guards.d.ts +5 -0
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/dist/utils/type-guards.js +9 -3
- package/dist/utils/type-guards.js.map +1 -1
- package/package.json +49 -5
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Orchestrates the deployment of Kubernetes resources directly to a cluster
|
|
5
5
|
* without requiring the Kro controller, using in-process dependency resolution.
|
|
6
6
|
*/
|
|
7
|
-
import * as k8s from '@kubernetes/client-node';
|
|
8
7
|
import { ensureReadinessEvaluator } from '../../utils/helpers.js';
|
|
9
8
|
import { DependencyResolver } from '../dependencies/index.js';
|
|
10
9
|
import { CircularDependencyError } from '../errors.js';
|
|
10
|
+
import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
|
|
11
11
|
import { getComponentLogger } from '../logging/index.js';
|
|
12
12
|
import { DeploymentMode, ReferenceResolver } from '../references/index.js';
|
|
13
|
-
import { ResourceDeploymentError } from '../types/deployment.js';
|
|
13
|
+
import { ResourceDeploymentError, ResourceConflictError, UnsupportedMediaTypeError } from '../types/deployment.js';
|
|
14
14
|
import { createDebugLoggerFromDeploymentOptions } from './debug-logger.js';
|
|
15
15
|
import { ResourceReadinessChecker } from './readiness.js';
|
|
16
16
|
import { StatusHydrator } from './status-hydrator.js';
|
|
@@ -27,6 +27,7 @@ export class DirectDeploymentEngine {
|
|
|
27
27
|
eventMonitor;
|
|
28
28
|
deploymentState = new Map();
|
|
29
29
|
readyResources = new Set(); // Track resources that are already ready
|
|
30
|
+
activeAbortControllers = new Set(); // Track active abort controllers for cleanup
|
|
30
31
|
logger = getComponentLogger('deployment-engine');
|
|
31
32
|
constructor(kubeClient, k8sApi, referenceResolver, deploymentMode = DeploymentMode.DIRECT) {
|
|
32
33
|
this.kubeClient = kubeClient;
|
|
@@ -34,7 +35,9 @@ export class DirectDeploymentEngine {
|
|
|
34
35
|
this.dependencyResolver = new DependencyResolver();
|
|
35
36
|
this.referenceResolver =
|
|
36
37
|
referenceResolver || new ReferenceResolver(kubeClient, this.deploymentMode, k8sApi);
|
|
37
|
-
|
|
38
|
+
// Use createBunCompatibleKubernetesObjectApi which handles both Bun and Node.js
|
|
39
|
+
// This works around Bun's fetch TLS issues (https://github.com/oven-sh/bun/issues/10642)
|
|
40
|
+
this.k8sApi = k8sApi || createBunCompatibleKubernetesObjectApi(kubeClient);
|
|
38
41
|
this.readinessChecker = new ResourceReadinessChecker(this.k8sApi);
|
|
39
42
|
this.statusHydrator = new StatusHydrator(this.k8sApi);
|
|
40
43
|
// this.eventFilter = createEventFilter();
|
|
@@ -48,6 +51,94 @@ export class DirectDeploymentEngine {
|
|
|
48
51
|
});
|
|
49
52
|
});
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Create an abortable delay that can be cancelled via AbortSignal
|
|
56
|
+
* @param ms - Delay in milliseconds
|
|
57
|
+
* @param signal - Optional AbortSignal to cancel the delay
|
|
58
|
+
* @returns Promise that resolves after the delay or rejects if aborted
|
|
59
|
+
*/
|
|
60
|
+
abortableDelay(ms, signal) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
if (signal?.aborted) {
|
|
63
|
+
reject(new DOMException('Delay aborted', 'AbortError'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const timeoutId = setTimeout(() => {
|
|
67
|
+
resolve();
|
|
68
|
+
}, ms);
|
|
69
|
+
signal?.addEventListener('abort', () => {
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
reject(new DOMException('Delay aborted', 'AbortError'));
|
|
72
|
+
}, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Wrap an async operation with abort signal handling
|
|
77
|
+
* If the signal is aborted, the promise will reject with AbortError
|
|
78
|
+
* Note: This doesn't actually cancel the underlying operation, but it allows
|
|
79
|
+
* the caller to stop waiting for it and handle the abort gracefully
|
|
80
|
+
* @param operation - The async operation to wrap
|
|
81
|
+
* @param signal - Optional AbortSignal to cancel the wait
|
|
82
|
+
* @returns Promise that resolves with the operation result or rejects if aborted
|
|
83
|
+
*/
|
|
84
|
+
async withAbortSignal(operation, signal) {
|
|
85
|
+
if (!signal) {
|
|
86
|
+
return operation;
|
|
87
|
+
}
|
|
88
|
+
if (signal.aborted) {
|
|
89
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
90
|
+
}
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const abortHandler = () => {
|
|
93
|
+
reject(new DOMException('Operation aborted', 'AbortError'));
|
|
94
|
+
};
|
|
95
|
+
signal.addEventListener('abort', abortHandler, { once: true });
|
|
96
|
+
operation
|
|
97
|
+
.then((result) => {
|
|
98
|
+
signal.removeEventListener('abort', abortHandler);
|
|
99
|
+
resolve(result);
|
|
100
|
+
})
|
|
101
|
+
.catch((error) => {
|
|
102
|
+
signal.removeEventListener('abort', abortHandler);
|
|
103
|
+
// If the signal was aborted, throw AbortError instead of the original error
|
|
104
|
+
if (signal.aborted) {
|
|
105
|
+
reject(new DOMException('Operation aborted', 'AbortError'));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
reject(error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Create and track an AbortController for a deployment operation
|
|
115
|
+
* @returns AbortController that is tracked for cleanup
|
|
116
|
+
*/
|
|
117
|
+
createTrackedAbortController() {
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
this.activeAbortControllers.add(controller);
|
|
120
|
+
return controller;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Remove an AbortController from tracking
|
|
124
|
+
* @param controller - The AbortController to remove
|
|
125
|
+
*/
|
|
126
|
+
removeTrackedAbortController(controller) {
|
|
127
|
+
this.activeAbortControllers.delete(controller);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Abort all active operations and clean up
|
|
131
|
+
* This is called when a deployment times out or is cancelled
|
|
132
|
+
*/
|
|
133
|
+
abortAllOperations() {
|
|
134
|
+
this.logger.debug('Aborting all active operations', {
|
|
135
|
+
activeControllers: this.activeAbortControllers.size,
|
|
136
|
+
});
|
|
137
|
+
for (const controller of this.activeAbortControllers) {
|
|
138
|
+
controller.abort();
|
|
139
|
+
}
|
|
140
|
+
this.activeAbortControllers.clear();
|
|
141
|
+
}
|
|
51
142
|
/**
|
|
52
143
|
* Get the Kubernetes API client for external integrations
|
|
53
144
|
* @returns The configured KubernetesObjectApi instance
|
|
@@ -55,6 +146,41 @@ export class DirectDeploymentEngine {
|
|
|
55
146
|
getKubernetesApi() {
|
|
56
147
|
return this.k8sApi;
|
|
57
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Enhance a resource for evaluation by applying kind-specific logic
|
|
151
|
+
* This allows generic evaluators to work correctly without needing special cases
|
|
152
|
+
*/
|
|
153
|
+
enhanceResourceForEvaluation(resource, kind) {
|
|
154
|
+
// For HelmRepository resources, handle OCI special case
|
|
155
|
+
if (kind === 'HelmRepository') {
|
|
156
|
+
const spec = resource.spec;
|
|
157
|
+
const isOciRepository = spec?.type === 'oci';
|
|
158
|
+
const hasBeenProcessed = resource.metadata?.generation && resource.metadata?.resourceVersion;
|
|
159
|
+
// If it's an OCI repo without Ready condition, synthesize one
|
|
160
|
+
// OCI repositories don't get status conditions from Flux, but they are functional
|
|
161
|
+
// once they've been processed (have generation and resourceVersion)
|
|
162
|
+
if (isOciRepository &&
|
|
163
|
+
hasBeenProcessed &&
|
|
164
|
+
!resource.status?.conditions?.some((c) => c.type === 'Ready')) {
|
|
165
|
+
return {
|
|
166
|
+
...resource,
|
|
167
|
+
status: {
|
|
168
|
+
...resource.status,
|
|
169
|
+
conditions: [
|
|
170
|
+
...(resource.status?.conditions || []),
|
|
171
|
+
{
|
|
172
|
+
type: 'Ready',
|
|
173
|
+
status: 'True',
|
|
174
|
+
message: 'OCI repository is functional',
|
|
175
|
+
reason: 'OciRepositoryProcessed',
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return resource;
|
|
183
|
+
}
|
|
58
184
|
/**
|
|
59
185
|
* Check if a deployed resource is ready using the factory-provided readiness evaluator
|
|
60
186
|
*/
|
|
@@ -75,9 +201,12 @@ export class DirectDeploymentEngine {
|
|
|
75
201
|
},
|
|
76
202
|
};
|
|
77
203
|
// Get the live resource from the cluster
|
|
204
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
78
205
|
const liveResource = await this.k8sApi.read(resourceRef);
|
|
206
|
+
// Apply kind-specific enhancements before calling custom evaluator
|
|
207
|
+
const enhancedResource = this.enhanceResourceForEvaluation(liveResource, deployedResource.kind);
|
|
79
208
|
// Use the factory-provided readiness evaluator
|
|
80
|
-
const result = readinessEvaluator(
|
|
209
|
+
const result = readinessEvaluator(enhancedResource);
|
|
81
210
|
let readinessResult;
|
|
82
211
|
if (typeof result === 'boolean') {
|
|
83
212
|
readinessResult = { ready: result };
|
|
@@ -108,8 +237,9 @@ export class DirectDeploymentEngine {
|
|
|
108
237
|
namespace: deployedResource.namespace,
|
|
109
238
|
},
|
|
110
239
|
};
|
|
240
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
111
241
|
const liveResource = await this.k8sApi.read(resourceRef);
|
|
112
|
-
return this.readinessChecker.isResourceReady(liveResource
|
|
242
|
+
return this.readinessChecker.isResourceReady(liveResource);
|
|
113
243
|
}
|
|
114
244
|
}
|
|
115
245
|
catch (error) {
|
|
@@ -136,6 +266,18 @@ export class DirectDeploymentEngine {
|
|
|
136
266
|
const deploymentId = this.generateDeploymentId();
|
|
137
267
|
const startTime = Date.now();
|
|
138
268
|
const deployedResources = [];
|
|
269
|
+
// Create an AbortController for this deployment to enable proper cancellation
|
|
270
|
+
const deploymentAbortController = this.createTrackedAbortController();
|
|
271
|
+
const abortSignal = deploymentAbortController.signal;
|
|
272
|
+
// Set up timeout-based abort if timeout is specified
|
|
273
|
+
const timeout = options.timeout || 300000; // 5 minutes default
|
|
274
|
+
const timeoutId = setTimeout(() => {
|
|
275
|
+
this.logger.debug('Deployment timeout reached, aborting operations', {
|
|
276
|
+
deploymentId,
|
|
277
|
+
timeout,
|
|
278
|
+
});
|
|
279
|
+
deploymentAbortController.abort();
|
|
280
|
+
}, timeout);
|
|
139
281
|
const errors = [];
|
|
140
282
|
const deploymentLogger = this.logger.child({
|
|
141
283
|
deploymentId,
|
|
@@ -185,10 +327,31 @@ export class DirectDeploymentEngine {
|
|
|
185
327
|
this.readinessChecker.setDebugLogger(this.debugLogger);
|
|
186
328
|
deploymentLogger.debug('Debug logging initialized');
|
|
187
329
|
}
|
|
188
|
-
// 4. Create resolution context
|
|
330
|
+
// 4. Create resolution context with resourceKeyMapping for cross-resource references
|
|
331
|
+
// The resourceKeyMapping maps original resource IDs (like 'webappDeployment') to their manifests
|
|
332
|
+
// This allows the reference resolver to find resources by their original IDs during deployment
|
|
333
|
+
const resourceKeyMapping = new Map();
|
|
334
|
+
for (const resource of graph.resources) {
|
|
335
|
+
const manifest = resource.manifest;
|
|
336
|
+
const originalResourceId = manifest.__resourceId;
|
|
337
|
+
if (originalResourceId) {
|
|
338
|
+
// Convert the Enhanced proxy to a plain object for reliable field extraction
|
|
339
|
+
// The proxy's toJSON method returns a clean object without proxy behavior
|
|
340
|
+
const plainManifest = typeof manifest.toJSON === 'function'
|
|
341
|
+
? manifest.toJSON()
|
|
342
|
+
: JSON.parse(JSON.stringify(manifest));
|
|
343
|
+
resourceKeyMapping.set(originalResourceId, plainManifest);
|
|
344
|
+
deploymentLogger.debug('Added resource to resourceKeyMapping', {
|
|
345
|
+
originalResourceId,
|
|
346
|
+
kind: manifest.kind,
|
|
347
|
+
name: manifest.metadata?.name,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
189
351
|
const context = {
|
|
190
352
|
deployedResources,
|
|
191
353
|
kubeClient: this.kubeClient,
|
|
354
|
+
resourceKeyMapping,
|
|
192
355
|
...(options.namespace && { namespace: options.namespace }),
|
|
193
356
|
timeout: options.timeout || 30000,
|
|
194
357
|
};
|
|
@@ -232,7 +395,7 @@ export class DirectDeploymentEngine {
|
|
|
232
395
|
try {
|
|
233
396
|
resourceLogger.debug('Calling deploySingleResource');
|
|
234
397
|
// Wait for CRD establishment if this is a custom resource
|
|
235
|
-
await this.waitForCRDIfCustomResource(resource.manifest, options, resourceLogger);
|
|
398
|
+
await this.waitForCRDIfCustomResource(resource.manifest, options, resourceLogger, abortSignal);
|
|
236
399
|
// FIX: Unconditionally ensure the readiness evaluator is attached just before deployment.
|
|
237
400
|
const resourceWithEvaluator = ensureReadinessEvaluator(resource.manifest);
|
|
238
401
|
// Add resource to event monitoring before deployment to capture creation events
|
|
@@ -254,7 +417,7 @@ export class DirectDeploymentEngine {
|
|
|
254
417
|
resourceLogger.warn('Failed to add resource to event monitoring, continuing deployment', error);
|
|
255
418
|
}
|
|
256
419
|
}
|
|
257
|
-
const deployedResource = await this.deploySingleResource(resourceWithEvaluator, context, options);
|
|
420
|
+
const deployedResource = await this.deploySingleResource(resourceWithEvaluator, context, options, abortSignal);
|
|
258
421
|
resourceLogger.debug('Resource deployed successfully');
|
|
259
422
|
return {
|
|
260
423
|
success: true,
|
|
@@ -296,6 +459,37 @@ export class DirectDeploymentEngine {
|
|
|
296
459
|
const deploymentResult = result.value;
|
|
297
460
|
if (deploymentResult.success && deploymentResult.deployedResource) {
|
|
298
461
|
deployedResources.push(deploymentResult.deployedResource);
|
|
462
|
+
// Update resourceKeyMapping with the live resource from the cluster (including status)
|
|
463
|
+
// This is critical for CEL expression evaluation which needs access to resource status
|
|
464
|
+
const deployedRes = deploymentResult.deployedResource;
|
|
465
|
+
const manifestWithId = deployedRes.manifest;
|
|
466
|
+
const originalResourceId = manifestWithId.__resourceId;
|
|
467
|
+
if (originalResourceId && resourceKeyMapping.has(originalResourceId)) {
|
|
468
|
+
try {
|
|
469
|
+
// Query the live resource from the cluster to get its current status
|
|
470
|
+
const liveResource = await this.k8sApi.read({
|
|
471
|
+
apiVersion: deployedRes.manifest.apiVersion || '',
|
|
472
|
+
kind: deployedRes.kind,
|
|
473
|
+
metadata: {
|
|
474
|
+
name: deployedRes.name,
|
|
475
|
+
namespace: deployedRes.namespace,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
resourceKeyMapping.set(originalResourceId, liveResource);
|
|
479
|
+
deploymentLogger.debug('Updated resourceKeyMapping with live resource status', {
|
|
480
|
+
originalResourceId,
|
|
481
|
+
kind: deployedRes.kind,
|
|
482
|
+
name: deployedRes.name,
|
|
483
|
+
hasStatus: !!liveResource.status,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
deploymentLogger.warn('Failed to update resourceKeyMapping with live resource', {
|
|
488
|
+
originalResourceId,
|
|
489
|
+
error: error instanceof Error ? error.message : String(error),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
299
493
|
}
|
|
300
494
|
else {
|
|
301
495
|
levelHasFailures = true;
|
|
@@ -378,6 +572,9 @@ export class DirectDeploymentEngine {
|
|
|
378
572
|
deploymentLogger.warn('Failed to stop event monitoring cleanly', error);
|
|
379
573
|
}
|
|
380
574
|
}
|
|
575
|
+
// Clean up abort controller and timeout
|
|
576
|
+
clearTimeout(timeoutId);
|
|
577
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
381
578
|
// Store deployment state for rollback
|
|
382
579
|
this.deploymentState.set(deploymentId, {
|
|
383
580
|
deploymentId,
|
|
@@ -400,8 +597,14 @@ export class DirectDeploymentEngine {
|
|
|
400
597
|
catch (error) {
|
|
401
598
|
// Re-throw circular dependency errors immediately - these are configuration errors
|
|
402
599
|
if (error instanceof CircularDependencyError) {
|
|
600
|
+
// Clean up abort controller and timeout before re-throwing
|
|
601
|
+
clearTimeout(timeoutId);
|
|
602
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
403
603
|
throw error;
|
|
404
604
|
}
|
|
605
|
+
// Clean up abort controller and timeout
|
|
606
|
+
clearTimeout(timeoutId);
|
|
607
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
405
608
|
const duration = Date.now() - startTime;
|
|
406
609
|
this.emitEvent(options, {
|
|
407
610
|
type: 'failed',
|
|
@@ -556,6 +759,18 @@ export class DirectDeploymentEngine {
|
|
|
556
759
|
resourceCount: graph.resources.length,
|
|
557
760
|
closureCount: Object.keys(closures).length,
|
|
558
761
|
});
|
|
762
|
+
// Create an AbortController for this deployment to enable proper cancellation
|
|
763
|
+
const deploymentAbortController = this.createTrackedAbortController();
|
|
764
|
+
const abortSignal = deploymentAbortController.signal;
|
|
765
|
+
// Set up timeout-based abort if timeout is specified
|
|
766
|
+
const timeout = options.timeout || 300000; // 5 minutes default
|
|
767
|
+
const timeoutId = setTimeout(() => {
|
|
768
|
+
deploymentLogger.debug('Deployment timeout reached, aborting operations', {
|
|
769
|
+
deploymentId,
|
|
770
|
+
timeout,
|
|
771
|
+
});
|
|
772
|
+
deploymentAbortController.abort();
|
|
773
|
+
}, timeout);
|
|
559
774
|
deploymentLogger.info('Starting deployment with closures', {
|
|
560
775
|
options,
|
|
561
776
|
closures: Object.keys(closures),
|
|
@@ -588,10 +803,30 @@ export class DirectDeploymentEngine {
|
|
|
588
803
|
totalClosures: enhancedPlan.totalClosures,
|
|
589
804
|
maxParallelism: enhancedPlan.maxParallelism,
|
|
590
805
|
});
|
|
591
|
-
// 4. Create resolution context
|
|
806
|
+
// 4. Create resolution context with resourceKeyMapping for cross-resource references
|
|
807
|
+
// The resourceKeyMapping maps original resource IDs (like 'webappDeployment') to their manifests
|
|
808
|
+
const resourceKeyMapping = new Map();
|
|
809
|
+
for (const resource of graph.resources) {
|
|
810
|
+
const manifest = resource.manifest;
|
|
811
|
+
const originalResourceId = manifest.__resourceId;
|
|
812
|
+
if (originalResourceId) {
|
|
813
|
+
// Convert the Enhanced proxy to a plain object for reliable field extraction
|
|
814
|
+
// The proxy's toJSON method returns a clean object without proxy behavior
|
|
815
|
+
const plainManifest = typeof manifest.toJSON === 'function'
|
|
816
|
+
? manifest.toJSON()
|
|
817
|
+
: JSON.parse(JSON.stringify(manifest));
|
|
818
|
+
resourceKeyMapping.set(originalResourceId, plainManifest);
|
|
819
|
+
deploymentLogger.debug('Added resource to resourceKeyMapping', {
|
|
820
|
+
originalResourceId,
|
|
821
|
+
kind: manifest.kind,
|
|
822
|
+
name: manifest.metadata?.name,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
592
826
|
const context = {
|
|
593
827
|
deployedResources,
|
|
594
828
|
kubeClient: this.kubeClient,
|
|
829
|
+
resourceKeyMapping,
|
|
595
830
|
...(options.namespace && { namespace: options.namespace }),
|
|
596
831
|
timeout: options.timeout || 30000,
|
|
597
832
|
};
|
|
@@ -652,8 +887,10 @@ export class DirectDeploymentEngine {
|
|
|
652
887
|
});
|
|
653
888
|
try {
|
|
654
889
|
resourceLogger.debug('Calling deploySingleResource');
|
|
890
|
+
// Wait for CRD establishment if this is a custom resource
|
|
891
|
+
await this.waitForCRDIfCustomResource(resource.manifest, options, resourceLogger, abortSignal);
|
|
655
892
|
const resourceWithEvaluator = ensureReadinessEvaluator(resource.manifest);
|
|
656
|
-
const deployedResource = await this.deploySingleResource(resourceWithEvaluator, context, options);
|
|
893
|
+
const deployedResource = await this.deploySingleResource(resourceWithEvaluator, context, options, abortSignal);
|
|
657
894
|
resourceLogger.debug('Resource deployed successfully');
|
|
658
895
|
return {
|
|
659
896
|
success: true,
|
|
@@ -748,6 +985,37 @@ export class DirectDeploymentEngine {
|
|
|
748
985
|
if (deploymentResult.success && deploymentResult.deployedResource) {
|
|
749
986
|
deployedResources.push(deploymentResult.deployedResource);
|
|
750
987
|
successfulResources++;
|
|
988
|
+
// Update resourceKeyMapping with the live resource from the cluster (including status)
|
|
989
|
+
// This is critical for CEL expression evaluation which needs access to resource status
|
|
990
|
+
const deployedRes = deploymentResult.deployedResource;
|
|
991
|
+
const manifestWithId = deployedRes.manifest;
|
|
992
|
+
const originalResourceId = manifestWithId.__resourceId;
|
|
993
|
+
if (originalResourceId && resourceKeyMapping.has(originalResourceId)) {
|
|
994
|
+
try {
|
|
995
|
+
// Query the live resource from the cluster to get its current status
|
|
996
|
+
const liveResource = await this.k8sApi.read({
|
|
997
|
+
apiVersion: deployedRes.manifest.apiVersion || '',
|
|
998
|
+
kind: deployedRes.kind,
|
|
999
|
+
metadata: {
|
|
1000
|
+
name: deployedRes.name,
|
|
1001
|
+
namespace: deployedRes.namespace,
|
|
1002
|
+
},
|
|
1003
|
+
});
|
|
1004
|
+
resourceKeyMapping.set(originalResourceId, liveResource);
|
|
1005
|
+
deploymentLogger.debug('Updated resourceKeyMapping with live resource status', {
|
|
1006
|
+
originalResourceId,
|
|
1007
|
+
kind: deployedRes.kind,
|
|
1008
|
+
name: deployedRes.name,
|
|
1009
|
+
hasStatus: !!liveResource.status,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
catch (error) {
|
|
1013
|
+
deploymentLogger.warn('Failed to update resourceKeyMapping with live resource', {
|
|
1014
|
+
originalResourceId,
|
|
1015
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
751
1019
|
}
|
|
752
1020
|
else {
|
|
753
1021
|
levelHasFailures = true;
|
|
@@ -816,6 +1084,9 @@ export class DirectDeploymentEngine {
|
|
|
816
1084
|
message: `Deployment with closures ${status} in ${duration}ms (${enhancedPlan.totalClosures} closures + ${enhancedPlan.totalResources} resources across ${enhancedPlan.levels.length} levels)`,
|
|
817
1085
|
timestamp: new Date(),
|
|
818
1086
|
});
|
|
1087
|
+
// Clean up abort controller and timeout
|
|
1088
|
+
clearTimeout(timeoutId);
|
|
1089
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
819
1090
|
// Store deployment state for rollback
|
|
820
1091
|
this.deploymentState.set(deploymentId, {
|
|
821
1092
|
deploymentId,
|
|
@@ -838,8 +1109,14 @@ export class DirectDeploymentEngine {
|
|
|
838
1109
|
catch (error) {
|
|
839
1110
|
// Re-throw circular dependency errors immediately - these are configuration errors
|
|
840
1111
|
if (error instanceof CircularDependencyError) {
|
|
1112
|
+
// Clean up abort controller and timeout before re-throwing
|
|
1113
|
+
clearTimeout(timeoutId);
|
|
1114
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
841
1115
|
throw error;
|
|
842
1116
|
}
|
|
1117
|
+
// Clean up abort controller and timeout
|
|
1118
|
+
clearTimeout(timeoutId);
|
|
1119
|
+
this.removeTrackedAbortController(deploymentAbortController);
|
|
843
1120
|
const duration = Date.now() - startTime;
|
|
844
1121
|
this.emitEvent(options, {
|
|
845
1122
|
type: 'failed',
|
|
@@ -877,8 +1154,14 @@ export class DirectDeploymentEngine {
|
|
|
877
1154
|
|
|
878
1155
|
* Deploy a single resource
|
|
879
1156
|
*/
|
|
880
|
-
async deploySingleResource(resource, context, options) {
|
|
881
|
-
|
|
1157
|
+
async deploySingleResource(resource, context, options, abortSignal) {
|
|
1158
|
+
// Check if already aborted
|
|
1159
|
+
if (abortSignal?.aborted) {
|
|
1160
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
1161
|
+
}
|
|
1162
|
+
// __resourceId is an internal field that may be set during resource processing
|
|
1163
|
+
const resourceWithInternalId = resource;
|
|
1164
|
+
const resourceId = resource.id || resourceWithInternalId.__resourceId || resource.metadata?.name || 'unknown';
|
|
882
1165
|
const resourceLogger = this.logger.child({
|
|
883
1166
|
resourceId,
|
|
884
1167
|
kind: resource.kind,
|
|
@@ -902,13 +1185,25 @@ export class DirectDeploymentEngine {
|
|
|
902
1185
|
this.referenceResolver.resolveReferences(resource, context),
|
|
903
1186
|
new Promise((_, reject) => setTimeout(() => reject(new Error('Reference resolution timeout')), resolveTimeout)),
|
|
904
1187
|
]));
|
|
1188
|
+
// Check for readinessEvaluator which may be on Enhanced resources
|
|
1189
|
+
const enhancedResource = resolvedResource;
|
|
905
1190
|
resourceLogger.debug('References resolved successfully', {
|
|
906
1191
|
resolvedMetadata: resolvedResource.metadata,
|
|
907
|
-
hasReadinessEvaluator: !!
|
|
1192
|
+
hasReadinessEvaluator: !!enhancedResource.readinessEvaluator,
|
|
908
1193
|
});
|
|
909
1194
|
}
|
|
910
1195
|
catch (error) {
|
|
911
|
-
|
|
1196
|
+
// In Alchemy deployments, resourceKeyMapping is often empty because resources are deployed
|
|
1197
|
+
// one at a time. This is expected behavior, so we log at debug level instead of warn.
|
|
1198
|
+
const hasResourceKeyMapping = context.resourceKeyMapping && context.resourceKeyMapping.size > 0;
|
|
1199
|
+
if (hasResourceKeyMapping) {
|
|
1200
|
+
resourceLogger.warn('Reference resolution failed, using original resource', error);
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
resourceLogger.debug('Reference resolution skipped (no resourceKeyMapping), using original resource', {
|
|
1204
|
+
error: error.message,
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
912
1207
|
resolvedResource = resource;
|
|
913
1208
|
}
|
|
914
1209
|
// 2. Apply namespace if specified, but only if resource doesn't already have one
|
|
@@ -931,7 +1226,8 @@ export class DirectDeploymentEngine {
|
|
|
931
1226
|
metadata: newMetadata,
|
|
932
1227
|
};
|
|
933
1228
|
// Copy the non-enumerable readiness evaluator if it exists
|
|
934
|
-
const
|
|
1229
|
+
const resourceWithEvaluator = resolvedResource;
|
|
1230
|
+
const readinessEvaluator = resourceWithEvaluator.readinessEvaluator;
|
|
935
1231
|
if (readinessEvaluator) {
|
|
936
1232
|
Object.defineProperty(newResolvedResource, 'readinessEvaluator', {
|
|
937
1233
|
value: readinessEvaluator,
|
|
@@ -940,6 +1236,16 @@ export class DirectDeploymentEngine {
|
|
|
940
1236
|
writable: false,
|
|
941
1237
|
});
|
|
942
1238
|
}
|
|
1239
|
+
// Copy the non-enumerable __resourceId if it exists (used for cross-resource references)
|
|
1240
|
+
const originalResourceId = resourceWithEvaluator.__resourceId;
|
|
1241
|
+
if (originalResourceId) {
|
|
1242
|
+
Object.defineProperty(newResolvedResource, '__resourceId', {
|
|
1243
|
+
value: originalResourceId,
|
|
1244
|
+
enumerable: false,
|
|
1245
|
+
configurable: true,
|
|
1246
|
+
writable: false,
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
943
1249
|
resolvedResource = newResolvedResource;
|
|
944
1250
|
}
|
|
945
1251
|
// 3. Apply the resource to the cluster (or simulate for dry run)
|
|
@@ -970,7 +1276,8 @@ export class DirectDeploymentEngine {
|
|
|
970
1276
|
// Check if resource already exists
|
|
971
1277
|
let existing;
|
|
972
1278
|
try {
|
|
973
|
-
|
|
1279
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
1280
|
+
existing = await this.k8sApi.read({
|
|
974
1281
|
apiVersion: resolvedResource.apiVersion,
|
|
975
1282
|
kind: resolvedResource.kind,
|
|
976
1283
|
metadata: {
|
|
@@ -978,26 +1285,109 @@ export class DirectDeploymentEngine {
|
|
|
978
1285
|
namespace: resolvedResource.metadata?.namespace || 'default',
|
|
979
1286
|
},
|
|
980
1287
|
});
|
|
981
|
-
existing = readResult.body;
|
|
982
1288
|
}
|
|
983
1289
|
catch (error) {
|
|
984
1290
|
// If it's a 404, the resource doesn't exist, which is expected for creation
|
|
985
|
-
|
|
986
|
-
|
|
1291
|
+
const apiError = error;
|
|
1292
|
+
// Check for 404 in various error formats:
|
|
1293
|
+
// - apiError.statusCode (direct property)
|
|
1294
|
+
// - apiError.response?.statusCode (nested response)
|
|
1295
|
+
// - apiError.body?.code (body code)
|
|
1296
|
+
// - error message containing "HTTP-Code: 404"
|
|
1297
|
+
const is404 = apiError.statusCode === 404 ||
|
|
1298
|
+
apiError.response?.statusCode === 404 ||
|
|
1299
|
+
apiError.body?.code === 404 ||
|
|
1300
|
+
(typeof apiError.message === 'string' && apiError.message.includes('HTTP-Code: 404'));
|
|
1301
|
+
if (!is404) {
|
|
1302
|
+
// Also check for "Unrecognized API version and kind" errors - these indicate
|
|
1303
|
+
// the CRD is not installed yet, which should trigger CRD waiting logic
|
|
1304
|
+
const isUnrecognizedApiError = typeof apiError.message === 'string' &&
|
|
1305
|
+
apiError.message.includes('Unrecognized API version and kind');
|
|
1306
|
+
if (isUnrecognizedApiError) {
|
|
1307
|
+
resourceLogger.debug('CRD not yet registered, will retry after CRD establishment', error);
|
|
1308
|
+
}
|
|
1309
|
+
else {
|
|
1310
|
+
resourceLogger.error('Error checking resource existence', error);
|
|
1311
|
+
}
|
|
987
1312
|
throw error;
|
|
988
1313
|
}
|
|
1314
|
+
// 404 means resource doesn't exist - this is expected, we'll create it below
|
|
989
1315
|
}
|
|
990
1316
|
if (existing) {
|
|
991
1317
|
// Resource exists, use patch for safer updates
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
|
|
1318
|
+
// Log the full resource being patched, including non-standard fields like 'data' for Secrets
|
|
1319
|
+
const patchPayload = {
|
|
1320
|
+
apiVersion: resolvedResource.apiVersion,
|
|
1321
|
+
kind: resolvedResource.kind,
|
|
1322
|
+
metadata: resolvedResource.metadata,
|
|
1323
|
+
};
|
|
1324
|
+
// Include spec if present (most resources)
|
|
1325
|
+
if (resolvedResource.spec !== undefined) {
|
|
1326
|
+
patchPayload.spec = resolvedResource.spec;
|
|
1327
|
+
}
|
|
1328
|
+
// Include data if present (Secrets)
|
|
1329
|
+
if (resolvedResource.data !== undefined) {
|
|
1330
|
+
patchPayload.data = resolvedResource.data;
|
|
1331
|
+
}
|
|
1332
|
+
// Include stringData if present (Secrets)
|
|
1333
|
+
if (resolvedResource.stringData !== undefined) {
|
|
1334
|
+
patchPayload.stringData = resolvedResource.stringData;
|
|
1335
|
+
}
|
|
1336
|
+
// Include rules if present (RBAC resources)
|
|
1337
|
+
if (resolvedResource.rules !== undefined) {
|
|
1338
|
+
// Ensure arrays are preserved (not converted to objects with numeric keys)
|
|
1339
|
+
const rules = resolvedResource.rules;
|
|
1340
|
+
patchPayload.rules = Array.isArray(rules) ? [...rules] : rules;
|
|
1341
|
+
}
|
|
1342
|
+
// Include subjects if present (ClusterRoleBinding, RoleBinding)
|
|
1343
|
+
if (resolvedResource.subjects !== undefined) {
|
|
1344
|
+
// Ensure arrays are preserved (not converted to objects with numeric keys)
|
|
1345
|
+
const subjects = resolvedResource.subjects;
|
|
1346
|
+
patchPayload.subjects = Array.isArray(subjects) ? [...subjects] : subjects;
|
|
1347
|
+
}
|
|
1348
|
+
// Include roleRef if present (ClusterRoleBinding, RoleBinding)
|
|
1349
|
+
if (resolvedResource.roleRef !== undefined) {
|
|
1350
|
+
patchPayload.roleRef = resolvedResource.roleRef;
|
|
1351
|
+
}
|
|
1352
|
+
// Explicitly call toJSON to ensure arrays are preserved via our custom toJSON implementation
|
|
1353
|
+
// Then use JSON.parse(JSON.stringify()) to ensure we have a plain object without proxies
|
|
1354
|
+
const jsonPayload = typeof resolvedResource.toJSON === 'function'
|
|
1355
|
+
? resolvedResource.toJSON()
|
|
1356
|
+
: patchPayload;
|
|
1357
|
+
// Deep clone to remove any proxy wrappers that might cause serialization issues
|
|
1358
|
+
const cleanPayload = JSON.parse(JSON.stringify(jsonPayload));
|
|
1359
|
+
// Strip internal TypeKro fields that should not be sent to Kubernetes
|
|
1360
|
+
// The 'id' field is used internally for resource mapping but is not a valid K8s field
|
|
1361
|
+
delete cleanPayload.id;
|
|
1362
|
+
resourceLogger.debug('Resource exists, patching', { patchPayload: cleanPayload });
|
|
1363
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
1364
|
+
appliedResource = await this.patchResourceWithCorrectContentType(cleanPayload);
|
|
995
1365
|
}
|
|
996
1366
|
else {
|
|
997
1367
|
// Resource does not exist, create it
|
|
998
1368
|
resourceLogger.debug('Resource does not exist, creating');
|
|
999
|
-
|
|
1000
|
-
|
|
1369
|
+
// DEBUG: Log the resource being created for Secrets
|
|
1370
|
+
if (resolvedResource.kind === 'Secret') {
|
|
1371
|
+
resourceLogger.debug('Creating Secret resource', {
|
|
1372
|
+
name: resolvedResource.metadata?.name,
|
|
1373
|
+
hasData: 'data' in resolvedResource,
|
|
1374
|
+
hasSpec: 'spec' in resolvedResource,
|
|
1375
|
+
dataKeys: resolvedResource.data ? Object.keys(resolvedResource.data) : [],
|
|
1376
|
+
specValue: resolvedResource.spec,
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
// Explicitly call toJSON to ensure arrays are preserved via our custom toJSON implementation
|
|
1380
|
+
// Then use JSON.parse(JSON.stringify()) to ensure we have a plain object without proxies
|
|
1381
|
+
const jsonResource = typeof resolvedResource.toJSON === 'function'
|
|
1382
|
+
? resolvedResource.toJSON()
|
|
1383
|
+
: resolvedResource;
|
|
1384
|
+
// Deep clone to remove any proxy wrappers that might cause serialization issues
|
|
1385
|
+
const cleanResource = JSON.parse(JSON.stringify(jsonResource));
|
|
1386
|
+
// Strip internal TypeKro fields that should not be sent to Kubernetes
|
|
1387
|
+
// The 'id' field is used internally for resource mapping but is not a valid K8s field
|
|
1388
|
+
delete cleanResource.id;
|
|
1389
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
1390
|
+
appliedResource = await this.k8sApi.create(cleanResource);
|
|
1001
1391
|
}
|
|
1002
1392
|
resourceLogger.debug('Resource applied successfully', {
|
|
1003
1393
|
appliedName: appliedResource.metadata?.name,
|
|
@@ -1010,7 +1400,121 @@ export class DirectDeploymentEngine {
|
|
|
1010
1400
|
}
|
|
1011
1401
|
catch (error) {
|
|
1012
1402
|
lastError = error;
|
|
1403
|
+
// Check for 409 Conflict errors - resource already exists
|
|
1404
|
+
const apiError = error;
|
|
1405
|
+
const is409 = apiError.statusCode === 409 ||
|
|
1406
|
+
apiError.response?.statusCode === 409 ||
|
|
1407
|
+
apiError.body?.code === 409 ||
|
|
1408
|
+
(typeof apiError.message === 'string' && apiError.message.includes('HTTP-Code: 409'));
|
|
1409
|
+
if (is409) {
|
|
1410
|
+
const conflictStrategy = options.conflictStrategy || 'warn';
|
|
1411
|
+
const resourceName = resolvedResource.metadata?.name || 'unknown';
|
|
1412
|
+
const resourceKind = resolvedResource.kind || 'Unknown';
|
|
1413
|
+
const resourceNamespace = resolvedResource.metadata?.namespace;
|
|
1414
|
+
let conflictHandled = false;
|
|
1415
|
+
resourceLogger.debug('Resource already exists (409)', {
|
|
1416
|
+
name: resourceName,
|
|
1417
|
+
kind: resourceKind,
|
|
1418
|
+
conflictStrategy,
|
|
1419
|
+
});
|
|
1420
|
+
// Handle based on conflict strategy
|
|
1421
|
+
switch (conflictStrategy) {
|
|
1422
|
+
case 'fail':
|
|
1423
|
+
// Throw error immediately - don't retry
|
|
1424
|
+
throw new ResourceConflictError(resourceName, resourceKind, resourceNamespace);
|
|
1425
|
+
case 'warn':
|
|
1426
|
+
// Log warning and treat as success - fetch existing resource
|
|
1427
|
+
resourceLogger.warn('Resource already exists, treating as success', {
|
|
1428
|
+
name: resourceName,
|
|
1429
|
+
kind: resourceKind,
|
|
1430
|
+
namespace: resourceNamespace,
|
|
1431
|
+
});
|
|
1432
|
+
try {
|
|
1433
|
+
// Fetch the existing resource to return it
|
|
1434
|
+
appliedResource = await this.k8sApi.read({
|
|
1435
|
+
apiVersion: resolvedResource.apiVersion,
|
|
1436
|
+
kind: resolvedResource.kind,
|
|
1437
|
+
metadata: {
|
|
1438
|
+
name: resourceName,
|
|
1439
|
+
namespace: resourceNamespace || 'default',
|
|
1440
|
+
},
|
|
1441
|
+
});
|
|
1442
|
+
conflictHandled = true;
|
|
1443
|
+
}
|
|
1444
|
+
catch (readError) {
|
|
1445
|
+
resourceLogger.warn('Failed to read existing resource after 409, falling back to patch', readError);
|
|
1446
|
+
// Fall back to patch strategy
|
|
1447
|
+
try {
|
|
1448
|
+
const jsonResource = typeof resolvedResource.toJSON === 'function'
|
|
1449
|
+
? resolvedResource.toJSON()
|
|
1450
|
+
: resolvedResource;
|
|
1451
|
+
const cleanResource = JSON.parse(JSON.stringify(jsonResource));
|
|
1452
|
+
delete cleanResource.id;
|
|
1453
|
+
appliedResource = await this.patchResourceWithCorrectContentType(cleanResource);
|
|
1454
|
+
resourceLogger.debug('Resource patched successfully after 409 conflict (warn fallback)');
|
|
1455
|
+
conflictHandled = true;
|
|
1456
|
+
}
|
|
1457
|
+
catch (patchError) {
|
|
1458
|
+
resourceLogger.warn('Failed to patch resource after 409 conflict', patchError);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
break;
|
|
1462
|
+
case 'patch':
|
|
1463
|
+
// Attempt to patch the existing resource
|
|
1464
|
+
try {
|
|
1465
|
+
const jsonResource = typeof resolvedResource.toJSON === 'function'
|
|
1466
|
+
? resolvedResource.toJSON()
|
|
1467
|
+
: resolvedResource;
|
|
1468
|
+
const cleanResource = JSON.parse(JSON.stringify(jsonResource));
|
|
1469
|
+
delete cleanResource.id;
|
|
1470
|
+
appliedResource = await this.patchResourceWithCorrectContentType(cleanResource);
|
|
1471
|
+
resourceLogger.debug('Resource patched successfully after 409 conflict');
|
|
1472
|
+
conflictHandled = true;
|
|
1473
|
+
}
|
|
1474
|
+
catch (patchError) {
|
|
1475
|
+
resourceLogger.warn('Failed to patch resource after 409 conflict', patchError);
|
|
1476
|
+
}
|
|
1477
|
+
break;
|
|
1478
|
+
case 'replace':
|
|
1479
|
+
// Delete and recreate the resource
|
|
1480
|
+
try {
|
|
1481
|
+
resourceLogger.debug('Deleting existing resource for replace strategy');
|
|
1482
|
+
await this.k8sApi.delete({
|
|
1483
|
+
apiVersion: resolvedResource.apiVersion,
|
|
1484
|
+
kind: resolvedResource.kind,
|
|
1485
|
+
metadata: {
|
|
1486
|
+
name: resourceName,
|
|
1487
|
+
namespace: resourceNamespace || 'default',
|
|
1488
|
+
},
|
|
1489
|
+
});
|
|
1490
|
+
// Wait a moment for deletion to propagate
|
|
1491
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1492
|
+
// Create the new resource
|
|
1493
|
+
const jsonResource = typeof resolvedResource.toJSON === 'function'
|
|
1494
|
+
? resolvedResource.toJSON()
|
|
1495
|
+
: resolvedResource;
|
|
1496
|
+
const cleanResource = JSON.parse(JSON.stringify(jsonResource));
|
|
1497
|
+
delete cleanResource.id;
|
|
1498
|
+
appliedResource = await this.k8sApi.create(cleanResource);
|
|
1499
|
+
resourceLogger.debug('Resource replaced successfully after 409 conflict');
|
|
1500
|
+
conflictHandled = true;
|
|
1501
|
+
}
|
|
1502
|
+
catch (replaceError) {
|
|
1503
|
+
resourceLogger.warn('Failed to replace resource after 409 conflict', replaceError);
|
|
1504
|
+
}
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
// If we successfully handled the conflict, break out of retry loop
|
|
1508
|
+
if (conflictHandled) {
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1013
1512
|
resourceLogger.error('Failed to apply resource to cluster', lastError, { attempt });
|
|
1513
|
+
// Check for HTTP 415 Unsupported Media Type errors
|
|
1514
|
+
if (this.isUnsupportedMediaTypeError(error)) {
|
|
1515
|
+
const acceptedTypes = this.extractAcceptedMediaTypes(error);
|
|
1516
|
+
throw new UnsupportedMediaTypeError(resolvedResource.metadata?.name || 'unknown', resolvedResource.kind || 'Unknown', acceptedTypes, lastError);
|
|
1517
|
+
}
|
|
1014
1518
|
// If this was the last attempt, throw the error
|
|
1015
1519
|
if (attempt >= retryPolicy.maxRetries) {
|
|
1016
1520
|
throw new ResourceDeploymentError(resolvedResource.metadata?.name || 'unknown', resolvedResource.kind || 'Unknown', lastError);
|
|
@@ -1040,7 +1544,7 @@ export class DirectDeploymentEngine {
|
|
|
1040
1544
|
// 5. Wait for resource to be ready if requested
|
|
1041
1545
|
if (options.waitForReady !== false) {
|
|
1042
1546
|
resourceLogger.debug('Waiting for resource to be ready');
|
|
1043
|
-
await this.waitForResourceReady(deployedResource, options);
|
|
1547
|
+
await this.waitForResourceReady(deployedResource, options, abortSignal);
|
|
1044
1548
|
deployedResource.status = 'ready';
|
|
1045
1549
|
}
|
|
1046
1550
|
resourceLogger.debug('Single resource deployment completed');
|
|
@@ -1048,16 +1552,23 @@ export class DirectDeploymentEngine {
|
|
|
1048
1552
|
}
|
|
1049
1553
|
/**
|
|
1050
1554
|
* Wait for a resource to be ready
|
|
1555
|
+
* @param deployedResource - The deployed resource to wait for
|
|
1556
|
+
* @param options - Deployment options
|
|
1557
|
+
* @param abortSignal - Optional AbortSignal to cancel the wait
|
|
1051
1558
|
*/
|
|
1052
|
-
async waitForResourceReady(deployedResource, options) {
|
|
1559
|
+
async waitForResourceReady(deployedResource, options, abortSignal) {
|
|
1053
1560
|
const resourceKey = `${deployedResource.kind}/${deployedResource.name}/${deployedResource.namespace}`;
|
|
1054
1561
|
// Check if already marked as ready
|
|
1055
1562
|
if (deployedResource.status === 'ready' || this.readyResources.has(resourceKey)) {
|
|
1056
1563
|
this.logger.debug('Resource already marked as ready', { resourceKey });
|
|
1057
1564
|
return;
|
|
1058
1565
|
}
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1566
|
+
// Check if already aborted
|
|
1567
|
+
if (abortSignal?.aborted) {
|
|
1568
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
1569
|
+
}
|
|
1570
|
+
const enhancedManifest = deployedResource.manifest;
|
|
1571
|
+
const readinessEvaluator = enhancedManifest.readinessEvaluator;
|
|
1061
1572
|
// Debug logging removed
|
|
1062
1573
|
if (!readinessEvaluator) {
|
|
1063
1574
|
const errorMessage = `Resource ${deployedResource.kind}/${deployedResource.name} does not have a factory-provided readiness evaluator`;
|
|
@@ -1068,17 +1579,25 @@ export class DirectDeploymentEngine {
|
|
|
1068
1579
|
const timeout = options.timeout || 300000; // 5 minutes default
|
|
1069
1580
|
let lastStatus = null;
|
|
1070
1581
|
while (Date.now() - startTime < timeout) {
|
|
1582
|
+
// Check if aborted before each iteration
|
|
1583
|
+
if (abortSignal?.aborted) {
|
|
1584
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
1585
|
+
}
|
|
1071
1586
|
try {
|
|
1072
1587
|
// Use custom readiness evaluator
|
|
1073
|
-
|
|
1588
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
1589
|
+
// Wrap with abort signal handling to stop waiting if aborted
|
|
1590
|
+
const liveResource = await this.withAbortSignal(this.k8sApi.read({
|
|
1074
1591
|
apiVersion: deployedResource.manifest.apiVersion || '',
|
|
1075
1592
|
kind: deployedResource.kind,
|
|
1076
1593
|
metadata: {
|
|
1077
1594
|
name: deployedResource.name,
|
|
1078
1595
|
namespace: deployedResource.namespace,
|
|
1079
1596
|
},
|
|
1080
|
-
});
|
|
1081
|
-
|
|
1597
|
+
}), abortSignal);
|
|
1598
|
+
// Apply kind-specific enhancements before calling custom evaluator
|
|
1599
|
+
const enhancedResource = this.enhanceResourceForEvaluation(liveResource, deployedResource.kind);
|
|
1600
|
+
const result = readinessEvaluator(enhancedResource);
|
|
1082
1601
|
if (typeof result === 'boolean') {
|
|
1083
1602
|
if (result) {
|
|
1084
1603
|
this.readyResources.add(resourceKey);
|
|
@@ -1114,10 +1633,22 @@ export class DirectDeploymentEngine {
|
|
|
1114
1633
|
timestamp: new Date(),
|
|
1115
1634
|
});
|
|
1116
1635
|
}
|
|
1117
|
-
// Wait before next check
|
|
1118
|
-
|
|
1636
|
+
// Wait before next check - use abortable delay
|
|
1637
|
+
try {
|
|
1638
|
+
await this.abortableDelay(2000, abortSignal);
|
|
1639
|
+
}
|
|
1640
|
+
catch (error) {
|
|
1641
|
+
if (error instanceof DOMException && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
|
|
1642
|
+
throw error; // Re-throw abort/timeout errors
|
|
1643
|
+
}
|
|
1644
|
+
// Ignore other errors from delay
|
|
1645
|
+
}
|
|
1119
1646
|
}
|
|
1120
1647
|
catch (error) {
|
|
1648
|
+
// Re-throw abort/timeout errors immediately
|
|
1649
|
+
if (error instanceof DOMException && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
|
|
1650
|
+
throw error;
|
|
1651
|
+
}
|
|
1121
1652
|
// Emit error status event
|
|
1122
1653
|
this.emitEvent(options, {
|
|
1123
1654
|
type: 'resource-status',
|
|
@@ -1125,8 +1656,16 @@ export class DirectDeploymentEngine {
|
|
|
1125
1656
|
message: `Unable to read resource status: ${error instanceof Error ? error.message : String(error)}`,
|
|
1126
1657
|
timestamp: new Date(),
|
|
1127
1658
|
});
|
|
1128
|
-
// If we can't read the resource, it's not ready yet
|
|
1129
|
-
|
|
1659
|
+
// If we can't read the resource, it's not ready yet - use abortable delay
|
|
1660
|
+
try {
|
|
1661
|
+
await this.abortableDelay(2000, abortSignal);
|
|
1662
|
+
}
|
|
1663
|
+
catch (delayError) {
|
|
1664
|
+
if (delayError instanceof DOMException && (delayError.name === 'AbortError' || delayError.name === 'TimeoutError')) {
|
|
1665
|
+
throw delayError; // Re-throw abort/timeout errors
|
|
1666
|
+
}
|
|
1667
|
+
// Ignore other errors from delay
|
|
1668
|
+
}
|
|
1130
1669
|
}
|
|
1131
1670
|
}
|
|
1132
1671
|
// Timeout reached
|
|
@@ -1206,7 +1745,8 @@ export class DirectDeploymentEngine {
|
|
|
1206
1745
|
...(options.namespace && { namespace: options.namespace }),
|
|
1207
1746
|
timeout: options.timeout || 30000,
|
|
1208
1747
|
};
|
|
1209
|
-
|
|
1748
|
+
// Legacy method - no abort signal support
|
|
1749
|
+
return this.deploySingleResource(resource, context, options, undefined);
|
|
1210
1750
|
}
|
|
1211
1751
|
/**
|
|
1212
1752
|
* Delete a resource from the cluster
|
|
@@ -1262,7 +1802,8 @@ export class DirectDeploymentEngine {
|
|
|
1262
1802
|
* Wait for resource readiness (legacy method for compatibility)
|
|
1263
1803
|
*/
|
|
1264
1804
|
async waitForResourceReadiness(resource, options) {
|
|
1265
|
-
|
|
1805
|
+
// Legacy method - no abort signal support
|
|
1806
|
+
return this.waitForResourceReady(resource, options, undefined);
|
|
1266
1807
|
}
|
|
1267
1808
|
/**
|
|
1268
1809
|
* Rollback a deployment by ID
|
|
@@ -1331,6 +1872,33 @@ export class DirectDeploymentEngine {
|
|
|
1331
1872
|
}
|
|
1332
1873
|
return result;
|
|
1333
1874
|
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Patch a resource with the correct Content-Type header for merge patch operations
|
|
1877
|
+
* This fixes HTTP 415 "Unsupported Media Type" errors that occur when using the generic patch method
|
|
1878
|
+
* In the new API, methods return objects directly (no .body wrapper)
|
|
1879
|
+
*/
|
|
1880
|
+
async patchResourceWithCorrectContentType(resource) {
|
|
1881
|
+
// DEBUG: Log the resource being sent to K8s API for Secrets
|
|
1882
|
+
if (resource.kind === 'Secret') {
|
|
1883
|
+
const secretResource = resource;
|
|
1884
|
+
this.logger.debug('Patching Secret resource', {
|
|
1885
|
+
name: resource.metadata?.name,
|
|
1886
|
+
hasData: 'data' in resource,
|
|
1887
|
+
hasSpec: 'spec' in resource,
|
|
1888
|
+
dataKeys: secretResource.data ? Object.keys(secretResource.data) : [],
|
|
1889
|
+
specValue: secretResource.spec,
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
// The k8sApi.patch method requires the full content-type string for the patchStrategy parameter
|
|
1893
|
+
// Use 'application/merge-patch+json' for merge patch operations
|
|
1894
|
+
// See: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/
|
|
1895
|
+
return await this.k8sApi.patch(resource, undefined, // pretty
|
|
1896
|
+
undefined, // dryRun
|
|
1897
|
+
undefined, // fieldManager
|
|
1898
|
+
undefined, // force
|
|
1899
|
+
'application/merge-patch+json' // patchStrategy - must be the full content-type string
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1334
1902
|
/**
|
|
1335
1903
|
* Check if an error is a "not found" error
|
|
1336
1904
|
*/
|
|
@@ -1344,7 +1912,11 @@ export class DirectDeploymentEngine {
|
|
|
1344
1912
|
/**
|
|
1345
1913
|
* Wait for CRD establishment if the resource is a custom resource
|
|
1346
1914
|
*/
|
|
1347
|
-
async waitForCRDIfCustomResource(resource, options, logger) {
|
|
1915
|
+
async waitForCRDIfCustomResource(resource, options, logger, abortSignal) {
|
|
1916
|
+
// Check if already aborted
|
|
1917
|
+
if (abortSignal?.aborted) {
|
|
1918
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
1919
|
+
}
|
|
1348
1920
|
// Skip if this is not a custom resource
|
|
1349
1921
|
if (!this.isCustomResource(resource)) {
|
|
1350
1922
|
return;
|
|
@@ -1361,7 +1933,7 @@ export class DirectDeploymentEngine {
|
|
|
1361
1933
|
resourceKind: resource.kind,
|
|
1362
1934
|
crdName,
|
|
1363
1935
|
});
|
|
1364
|
-
await this.waitForCRDEstablishment({ metadata: { name: crdName } }, options, logger);
|
|
1936
|
+
await this.waitForCRDEstablishment({ metadata: { name: crdName } }, options, logger, abortSignal);
|
|
1365
1937
|
logger.debug('CRD established, proceeding with custom resource deployment', {
|
|
1366
1938
|
resourceKind: resource.kind,
|
|
1367
1939
|
crdName,
|
|
@@ -1422,12 +1994,14 @@ export class DirectDeploymentEngine {
|
|
|
1422
1994
|
// Try to find the CRD by querying the API
|
|
1423
1995
|
const crds = await this.k8sApi.list('apiextensions.k8s.io/v1', 'CustomResourceDefinition');
|
|
1424
1996
|
// Look for a CRD that matches our group and kind
|
|
1425
|
-
|
|
1997
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
1998
|
+
const crdList = crds;
|
|
1999
|
+
const matchingCrd = crdList?.items?.find((crd) => {
|
|
1426
2000
|
const crdSpec = crd.spec;
|
|
1427
2001
|
return crdSpec?.group === group && crdSpec?.names?.kind === resource.kind;
|
|
1428
2002
|
});
|
|
1429
2003
|
if (matchingCrd) {
|
|
1430
|
-
return matchingCrd.metadata?.name;
|
|
2004
|
+
return matchingCrd.metadata?.name ?? null;
|
|
1431
2005
|
}
|
|
1432
2006
|
}
|
|
1433
2007
|
catch (error) {
|
|
@@ -1442,32 +2016,39 @@ export class DirectDeploymentEngine {
|
|
|
1442
2016
|
/**
|
|
1443
2017
|
* Public method to wait for CRD readiness by name
|
|
1444
2018
|
*/
|
|
1445
|
-
async waitForCRDReady(crdName, timeout = 300000) {
|
|
2019
|
+
async waitForCRDReady(crdName, timeout = 300000, abortSignal) {
|
|
1446
2020
|
const logger = this.logger.child({ crdName, timeout });
|
|
1447
2021
|
const options = {
|
|
1448
2022
|
mode: this.deploymentMode,
|
|
1449
2023
|
timeout,
|
|
1450
2024
|
};
|
|
1451
|
-
await this.waitForCRDEstablishment({ metadata: { name: crdName } }, options, logger);
|
|
2025
|
+
await this.waitForCRDEstablishment({ metadata: { name: crdName } }, options, logger, abortSignal);
|
|
1452
2026
|
}
|
|
1453
2027
|
/**
|
|
1454
2028
|
* Wait for a CRD to be established in the cluster
|
|
1455
2029
|
*/
|
|
1456
|
-
async waitForCRDEstablishment(crd, options, logger) {
|
|
2030
|
+
async waitForCRDEstablishment(crd, options, logger, abortSignal) {
|
|
1457
2031
|
const crdName = crd.metadata?.name;
|
|
1458
2032
|
const timeout = options.timeout || 300000; // 5 minutes default
|
|
1459
2033
|
const startTime = Date.now();
|
|
1460
2034
|
const pollInterval = 2000; // 2 seconds
|
|
1461
2035
|
logger.debug('Waiting for CRD to exist and be established', { crdName, timeout });
|
|
1462
2036
|
while (Date.now() - startTime < timeout) {
|
|
2037
|
+
// Check if aborted before each iteration
|
|
2038
|
+
if (abortSignal?.aborted) {
|
|
2039
|
+
throw new DOMException('Operation aborted', 'AbortError');
|
|
2040
|
+
}
|
|
1463
2041
|
try {
|
|
1464
2042
|
// Check if CRD is established by reading its status
|
|
1465
|
-
|
|
2043
|
+
// Wrap with abort signal handling
|
|
2044
|
+
const crdStatus = await this.withAbortSignal(this.k8sApi.read({
|
|
1466
2045
|
apiVersion: 'apiextensions.k8s.io/v1',
|
|
1467
2046
|
kind: 'CustomResourceDefinition',
|
|
1468
2047
|
metadata: { name: crdName }, // CRDs are cluster-scoped, no namespace needed
|
|
1469
|
-
});
|
|
1470
|
-
|
|
2048
|
+
}), abortSignal);
|
|
2049
|
+
// In the new API, methods return objects directly (no .body wrapper)
|
|
2050
|
+
const crdItem = crdStatus;
|
|
2051
|
+
const conditions = crdItem?.status?.conditions || [];
|
|
1471
2052
|
const establishedCondition = conditions.find((c) => c.type === 'Established');
|
|
1472
2053
|
if (establishedCondition?.status === 'True') {
|
|
1473
2054
|
logger.debug('CRD exists and is established', { crdName });
|
|
@@ -1479,6 +2060,10 @@ export class DirectDeploymentEngine {
|
|
|
1479
2060
|
});
|
|
1480
2061
|
}
|
|
1481
2062
|
catch (error) {
|
|
2063
|
+
// Re-throw abort/timeout errors immediately
|
|
2064
|
+
if (error instanceof DOMException && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
|
|
2065
|
+
throw error;
|
|
2066
|
+
}
|
|
1482
2067
|
// CRD might not exist yet (e.g., being installed by a closure)
|
|
1483
2068
|
// This is expected in scenarios where closures install CRDs
|
|
1484
2069
|
logger.debug('CRD not found yet, waiting for it to be created...', {
|
|
@@ -1486,11 +2071,54 @@ export class DirectDeploymentEngine {
|
|
|
1486
2071
|
error: error.message,
|
|
1487
2072
|
});
|
|
1488
2073
|
}
|
|
1489
|
-
// Wait before next poll
|
|
1490
|
-
|
|
2074
|
+
// Wait before next poll - use abortable delay
|
|
2075
|
+
try {
|
|
2076
|
+
await this.abortableDelay(pollInterval, abortSignal);
|
|
2077
|
+
}
|
|
2078
|
+
catch (error) {
|
|
2079
|
+
if (error instanceof DOMException && (error.name === 'AbortError' || error.name === 'TimeoutError')) {
|
|
2080
|
+
throw error; // Re-throw abort/timeout errors
|
|
2081
|
+
}
|
|
2082
|
+
// Ignore other errors from delay
|
|
2083
|
+
}
|
|
1491
2084
|
}
|
|
1492
2085
|
// Timeout reached
|
|
1493
2086
|
throw new Error(`Timeout waiting for CRD ${crdName} to be established after ${timeout}ms`);
|
|
1494
2087
|
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Check if an error is an HTTP 415 Unsupported Media Type error
|
|
2090
|
+
*/
|
|
2091
|
+
isUnsupportedMediaTypeError(error) {
|
|
2092
|
+
if (!error || typeof error !== 'object') {
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
const apiError = error;
|
|
2096
|
+
return (apiError.statusCode === 415 ||
|
|
2097
|
+
apiError.response?.statusCode === 415 ||
|
|
2098
|
+
apiError.body?.code === 415);
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* Extract accepted media types from HTTP 415 error message
|
|
2102
|
+
*/
|
|
2103
|
+
extractAcceptedMediaTypes(error) {
|
|
2104
|
+
const defaultTypes = [
|
|
2105
|
+
'application/json-patch+json',
|
|
2106
|
+
'application/merge-patch+json',
|
|
2107
|
+
'application/apply-patch+yaml',
|
|
2108
|
+
];
|
|
2109
|
+
try {
|
|
2110
|
+
// Try to extract from error message
|
|
2111
|
+
const apiError = error;
|
|
2112
|
+
const message = apiError.message || apiError.body?.message || '';
|
|
2113
|
+
const match = message.match(/accepted media types include: ([^"]+)/);
|
|
2114
|
+
if (match && match[1]) {
|
|
2115
|
+
return match[1].split(', ').map((type) => type.trim());
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
catch (_e) {
|
|
2119
|
+
// Fallback to default types
|
|
2120
|
+
}
|
|
2121
|
+
return defaultTypes;
|
|
2122
|
+
}
|
|
1495
2123
|
}
|
|
1496
2124
|
//# sourceMappingURL=engine.js.map
|