typekro 0.13.0 → 0.15.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/alchemy/deployers.d.ts +2 -2
- package/dist/alchemy/deployers.d.ts.map +1 -1
- package/dist/alchemy/deployers.js +12 -5
- package/dist/alchemy/deployers.js.map +1 -1
- package/dist/alchemy/index.d.ts +4 -2
- package/dist/alchemy/index.d.ts.map +1 -1
- package/dist/alchemy/index.js +4 -3
- package/dist/alchemy/index.js.map +1 -1
- package/dist/alchemy/resource-registration.d.ts +81 -19
- package/dist/alchemy/resource-registration.d.ts.map +1 -1
- package/dist/alchemy/resource-registration.js +296 -147
- package/dist/alchemy/resource-registration.js.map +1 -1
- package/dist/alchemy/types.d.ts +76 -8
- package/dist/alchemy/types.d.ts.map +1 -1
- package/dist/alchemy/wrapper.d.ts +0 -9
- package/dist/alchemy/wrapper.d.ts.map +1 -1
- package/dist/alchemy/wrapper.js +0 -15
- package/dist/alchemy/wrapper.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/containers/image.d.ts +40 -43
- package/dist/core/containers/image.d.ts.map +1 -1
- package/dist/core/containers/image.js +52 -34
- package/dist/core/containers/image.js.map +1 -1
- package/dist/core/containers/index.d.ts +8 -12
- package/dist/core/containers/index.d.ts.map +1 -1
- package/dist/core/containers/index.js +8 -12
- package/dist/core/containers/index.js.map +1 -1
- package/dist/core/deployment/direct-factory.d.ts +14 -2
- package/dist/core/deployment/direct-factory.d.ts.map +1 -1
- package/dist/core/deployment/direct-factory.js +117 -21
- package/dist/core/deployment/direct-factory.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +8 -5
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +36 -12
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/deployment/kro-factory.d.ts +33 -14
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +130 -164
- package/dist/core/deployment/kro-factory.js.map +1 -1
- package/dist/core/deployment/shared-utilities.d.ts +15 -4
- package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
- package/dist/core/deployment/shared-utilities.js +47 -14
- package/dist/core/deployment/shared-utilities.js.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.js +1 -2
- package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/index.d.ts +0 -1
- package/dist/core/deployment/strategies/index.d.ts.map +1 -1
- package/dist/core/deployment/strategies/index.js +0 -1
- package/dist/core/deployment/strategies/index.js.map +1 -1
- package/dist/core/types/deployment.d.ts +28 -42
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/resource-graph.d.ts +2 -6
- package/dist/core/types/resource-graph.d.ts.map +1 -1
- package/dist/core/types/schema.d.ts +0 -1
- package/dist/core/types/schema.d.ts.map +1 -1
- package/dist/core/types/serialization.d.ts +3 -7
- package/dist/core/types/serialization.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.js +2 -13
- package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.js +2 -18
- package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
- package/dist/shared/brands.d.ts +0 -9
- package/dist/shared/brands.d.ts.map +1 -1
- package/dist/shared/brands.js +0 -9
- package/dist/shared/brands.js.map +1 -1
- package/package.json +6 -3
- package/dist/alchemy/deployment.d.ts +0 -12
- package/dist/alchemy/deployment.d.ts.map +0 -1
- package/dist/alchemy/deployment.js +0 -12
- package/dist/alchemy/deployment.js.map +0 -1
- package/dist/core/containers/resolve.d.ts +0 -14
- package/dist/core/containers/resolve.d.ts.map +0 -1
- package/dist/core/containers/resolve.js +0 -88
- package/dist/core/containers/resolve.js.map +0 -1
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts +0 -109
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts.map +0 -1
- package/dist/core/deployment/strategies/alchemy-strategy.js +0 -556
- package/dist/core/deployment/strategies/alchemy-strategy.js.map +0 -1
|
@@ -11,7 +11,6 @@ import { preserveNonEnumerableProperties } from '../../utils/helpers.js';
|
|
|
11
11
|
import { isKubernetesRef } from '../../utils/type-guards.js';
|
|
12
12
|
import { applyAspects } from '../aspects/apply.js';
|
|
13
13
|
import { createCompositionContext, runWithCompositionContext } from '../composition/context.js';
|
|
14
|
-
import { hasContainerImageRefs, resolveContainerImages } from '../containers/index.js';
|
|
15
14
|
import { buildNestedCompositionAliasTargets } from '../composition/nested-status-cel.js';
|
|
16
15
|
import { DEFAULT_DEPLOYMENT_TIMEOUT, DEFAULT_KRO_INSTANCE_TIMEOUT, DEFAULT_RGD_TIMEOUT, } from '../config/defaults.js';
|
|
17
16
|
import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_SCHEMA_MARKER_SOURCE } from '../constants/brands.js';
|
|
@@ -19,6 +18,7 @@ import { CRDInstanceError, ensureError, ResourceGraphFactoryError, TypeKroError,
|
|
|
19
18
|
import { applyAnalysisToResources } from '../expressions/composition/composition-analyzer.js';
|
|
20
19
|
import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
|
|
21
20
|
import { getComponentLogger } from '../logging/index.js';
|
|
21
|
+
import { createAlchemyResourceId } from '../../alchemy/utilities.js';
|
|
22
22
|
// Dependency inversion: kroCustomResource, resourceGraphDefinition, and
|
|
23
23
|
// alchemy bridge are injected via FactoryOptions providers (Phase 3.5)
|
|
24
24
|
// instead of dynamic import() from higher layers.
|
|
@@ -33,7 +33,7 @@ import { DirectDeploymentEngine } from './engine.js';
|
|
|
33
33
|
import { logHandleSnapshot } from './handle-tracing.js';
|
|
34
34
|
import { isNotFoundError } from './k8s-helpers.js';
|
|
35
35
|
import { waitForKroInstanceReady as waitForKroInstanceReadyShared } from './kro-readiness.js';
|
|
36
|
-
import { convertToKubernetesName, generateInstanceName, getSingletonInstanceName, pluralizeKind,
|
|
36
|
+
import { convertToKubernetesName, extractSerializableKubeConfigOptions, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateSpec, } from './shared-utilities.js';
|
|
37
37
|
import { joinYamlDocuments, singletonOwnerInstanceYamls, singletonRgdYamls, } from './singleton-gitops.js';
|
|
38
38
|
import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationValue, } from './singleton-owner-drift.js';
|
|
39
39
|
/**
|
|
@@ -105,14 +105,12 @@ export class KroResourceFactoryImpl {
|
|
|
105
105
|
mode = 'kro';
|
|
106
106
|
name;
|
|
107
107
|
namespace;
|
|
108
|
-
isAlchemyManaged;
|
|
109
108
|
rgdName;
|
|
110
109
|
schema;
|
|
111
110
|
resources;
|
|
112
111
|
closures;
|
|
113
112
|
schemaDefinition;
|
|
114
113
|
statusMappings;
|
|
115
|
-
alchemyScope;
|
|
116
114
|
singletonDefinitions;
|
|
117
115
|
singletonOwnerStatuses = new Map();
|
|
118
116
|
logger = getComponentLogger('kro-factory');
|
|
@@ -143,12 +141,9 @@ export class KroResourceFactoryImpl {
|
|
|
143
141
|
// instead of dynamic import() from factories/ and alchemy/ layers.
|
|
144
142
|
kroCustomResourceProvider;
|
|
145
143
|
rgdProvider;
|
|
146
|
-
alchemyBridge;
|
|
147
144
|
constructor(name, resources, schemaDefinition, statusMappings, options = {}) {
|
|
148
145
|
this.name = name;
|
|
149
146
|
this.namespace = options.namespace || 'default';
|
|
150
|
-
this.alchemyScope = options.alchemyScope;
|
|
151
|
-
this.isAlchemyManaged = !!options.alchemyScope;
|
|
152
147
|
this.rgdName = convertToKubernetesName(name); // Convert to valid Kubernetes resource name
|
|
153
148
|
this.resources = resources;
|
|
154
149
|
this.closures = options.closures || {};
|
|
@@ -165,7 +160,6 @@ export class KroResourceFactoryImpl {
|
|
|
165
160
|
// Injected providers — fall back to dynamic import() for backward compatibility
|
|
166
161
|
this.kroCustomResourceProvider = options.kroCustomResourceProvider;
|
|
167
162
|
this.rgdProvider = options.rgdProvider;
|
|
168
|
-
this.alchemyBridge = options.alchemyBridge;
|
|
169
163
|
// Validate closures for Kro mode - detect KubernetesRef inputs and raise clear errors
|
|
170
164
|
this.validateClosuresForKroMode();
|
|
171
165
|
}
|
|
@@ -397,69 +391,50 @@ export class KroResourceFactoryImpl {
|
|
|
397
391
|
});
|
|
398
392
|
// Execute closures before RGD creation (Kro mode requirement)
|
|
399
393
|
await this.executeClosuresBeforeRGD(spec);
|
|
400
|
-
// Resolve any container() image refs to literal URIs (build + push via buildContainer) BEFORE the
|
|
401
|
-
// RGD is serialized — Kro reconciles in-cluster and can't build images, so it must see a literal.
|
|
402
|
-
await this.resolveContainerImagesBeforeRGD();
|
|
403
394
|
await this.ensureSingletonOwners(spec);
|
|
404
|
-
|
|
405
|
-
return this.deployWithAlchemy(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
409
|
-
}
|
|
395
|
+
return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
410
396
|
}
|
|
411
397
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* subsequent `buildRgdYaml` serializes literal images (Kro never sees a build).
|
|
398
|
+
* Discover the singleton-owner definitions this composition depends on (deduped by key): from
|
|
399
|
+
* re-executing the composition fn under a discovery context, plus any injected definitions.
|
|
400
|
+
* Shared by {@link ensureSingletonOwners} (imperative deploy) and {@link toAlchemyResources}.
|
|
416
401
|
*/
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
if (!hasContainerImageRefs(resources))
|
|
420
|
-
return;
|
|
421
|
-
await resolveContainerImages(resources);
|
|
422
|
-
}
|
|
423
|
-
async ensureSingletonOwners(spec) {
|
|
424
|
-
const discoveredSingletons = new Map();
|
|
402
|
+
discoverSingletonDefinitions(spec) {
|
|
403
|
+
const discovered = new Map();
|
|
425
404
|
if (this.factoryOptions.compositionFn) {
|
|
426
|
-
const
|
|
427
|
-
runWithCompositionContext(
|
|
405
|
+
const ctx = createCompositionContext('singleton-owner-discovery');
|
|
406
|
+
runWithCompositionContext(ctx, () => {
|
|
428
407
|
this.factoryOptions.compositionFn?.(spec);
|
|
429
408
|
});
|
|
430
|
-
for (const [key, definition] of
|
|
431
|
-
|
|
409
|
+
for (const [key, definition] of ctx.singletonDefinitions ?? []) {
|
|
410
|
+
discovered.set(key, definition);
|
|
432
411
|
}
|
|
433
412
|
}
|
|
434
413
|
for (const definition of this.singletonDefinitions) {
|
|
435
|
-
if (!
|
|
436
|
-
|
|
437
|
-
}
|
|
414
|
+
if (!discovered.has(definition.key))
|
|
415
|
+
discovered.set(definition.key, definition);
|
|
438
416
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
417
|
+
return Array.from(discovered.values());
|
|
418
|
+
}
|
|
419
|
+
/** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
|
|
420
|
+
singletonFactoryFor(definition) {
|
|
421
|
+
return definition.composition.factory('kro', {
|
|
422
|
+
namespace: definition.registryNamespace,
|
|
423
|
+
waitForReady: true,
|
|
424
|
+
...(this.factoryOptions.timeout !== undefined ? { timeout: this.factoryOptions.timeout } : {}),
|
|
425
|
+
...(this.factoryOptions.kubeConfig !== undefined
|
|
426
|
+
? { kubeConfig: this.factoryOptions.kubeConfig }
|
|
427
|
+
: {}),
|
|
428
|
+
...(this.factoryOptions.skipTLSVerify !== undefined
|
|
429
|
+
? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
|
|
430
|
+
: {}),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
async ensureSingletonOwners(spec) {
|
|
434
|
+
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
448
435
|
await this.ensureTargetNamespace(definition.registryNamespace);
|
|
449
436
|
const singletonInstanceName = getSingletonInstanceName(definition.id);
|
|
450
|
-
const singletonFactory =
|
|
451
|
-
namespace: definition.registryNamespace,
|
|
452
|
-
waitForReady: true,
|
|
453
|
-
...(this.factoryOptions.timeout !== undefined
|
|
454
|
-
? { timeout: this.factoryOptions.timeout }
|
|
455
|
-
: {}),
|
|
456
|
-
...(this.factoryOptions.kubeConfig !== undefined
|
|
457
|
-
? { kubeConfig: this.factoryOptions.kubeConfig }
|
|
458
|
-
: {}),
|
|
459
|
-
...(this.factoryOptions.skipTLSVerify !== undefined
|
|
460
|
-
? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
|
|
461
|
-
: {}),
|
|
462
|
-
});
|
|
437
|
+
const singletonFactory = this.singletonFactoryFor(definition);
|
|
463
438
|
try {
|
|
464
439
|
this.logger.info('Ensuring singleton owner boundary', {
|
|
465
440
|
singletonId: definition.id,
|
|
@@ -558,7 +533,6 @@ export class KroResourceFactoryImpl {
|
|
|
558
533
|
const deploymentContext = {
|
|
559
534
|
kubernetesApi: createBunCompatibleKubernetesObjectApi(kubeConfig),
|
|
560
535
|
kubeConfig: kubeConfig,
|
|
561
|
-
...(this.alchemyScope && { alchemyScope: this.alchemyScope }),
|
|
562
536
|
namespace: this.namespace,
|
|
563
537
|
deployedResources: new Map(), // Empty for pre-RGD execution
|
|
564
538
|
resolveReference: async (ref) => {
|
|
@@ -669,78 +643,6 @@ export class KroResourceFactoryImpl {
|
|
|
669
643
|
await deploymentEngine.dispose();
|
|
670
644
|
}
|
|
671
645
|
}
|
|
672
|
-
/**
|
|
673
|
-
* Deploy using type-safe alchemy resource wrapping
|
|
674
|
-
*
|
|
675
|
-
* In alchemy mode, the RGD gets one typed alchemy Resource and each instance gets another
|
|
676
|
-
*/
|
|
677
|
-
async deployWithAlchemy(spec, instanceNameOverride, singletonSpecFingerprint) {
|
|
678
|
-
validateAlchemyScope(this.alchemyScope, 'KRO Alchemy deployment');
|
|
679
|
-
const alchemyScope = this.alchemyScope;
|
|
680
|
-
// Use static registration functions
|
|
681
|
-
// Create deployer instance using DirectDeploymentEngine with KRO mode
|
|
682
|
-
const kroEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
|
|
683
|
-
const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
|
|
684
|
-
const kroDeletion = this.createAlchemyKroDeletionOptions();
|
|
685
|
-
try {
|
|
686
|
-
// 1. Ensure RGD is deployed via alchemy (once per factory). Reuse the
|
|
687
|
-
// normal serializer so externalRef/forEach/includeWhen/readyWhen and
|
|
688
|
-
// singleton owner boundaries match non-Alchemy KRO deploys.
|
|
689
|
-
const rgdManifest = yaml.load(this.buildRgdYaml());
|
|
690
|
-
// Register RGD type dynamically
|
|
691
|
-
const rgdFactory = this.rgdProvider ??
|
|
692
|
-
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
693
|
-
const rgdEnhanced = rgdFactory(rgdManifest);
|
|
694
|
-
const bridge = this.alchemyBridge ?? (await import('../../alchemy/deployment.js'));
|
|
695
|
-
const RGDProvider = bridge.ensureResourceTypeRegistered(rgdEnhanced);
|
|
696
|
-
const rgdId = bridge.createAlchemyResourceId(rgdEnhanced, this.namespace);
|
|
697
|
-
await alchemyScope.run(async () => {
|
|
698
|
-
await RGDProvider(rgdId, {
|
|
699
|
-
resource: rgdEnhanced,
|
|
700
|
-
namespace: this.namespace,
|
|
701
|
-
deploymentStrategy: 'kro',
|
|
702
|
-
kubeConfigOptions,
|
|
703
|
-
kroDeletion,
|
|
704
|
-
options: {
|
|
705
|
-
waitForReady: true,
|
|
706
|
-
timeout: DEFAULT_RGD_TIMEOUT, // RGD should be ready quickly
|
|
707
|
-
},
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
await this.waitForCRDReadyWithEngine(kroEngine);
|
|
711
|
-
// 2. Create instance via alchemy (once per deploy call)
|
|
712
|
-
await this.ensureTargetNamespace();
|
|
713
|
-
const instanceName = instanceNameOverride ?? generateInstanceName(spec, this.name);
|
|
714
|
-
const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, singletonSpecFingerprint);
|
|
715
|
-
// Register CRD instance type dynamically
|
|
716
|
-
// Cast required: crdInstanceManifest is a plain KubernetesResource, but alchemy functions
|
|
717
|
-
// expect Enhanced<unknown, unknown>. They only access kind/metadata.name for type inference.
|
|
718
|
-
const crdAsEnhanced = crdInstanceManifest;
|
|
719
|
-
const CRDInstanceProvider = bridge.ensureResourceTypeRegistered(crdAsEnhanced);
|
|
720
|
-
const instanceId = bridge.createAlchemyResourceId(crdAsEnhanced, this.namespace);
|
|
721
|
-
await alchemyScope.run(async () => {
|
|
722
|
-
await CRDInstanceProvider(instanceId, {
|
|
723
|
-
resource: crdAsEnhanced,
|
|
724
|
-
namespace: this.namespace,
|
|
725
|
-
deploymentStrategy: 'kro',
|
|
726
|
-
kubeConfigOptions,
|
|
727
|
-
kroDeletion,
|
|
728
|
-
options: {
|
|
729
|
-
waitForReady: false,
|
|
730
|
-
timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
731
|
-
},
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
if (this.factoryOptions.waitForReady ?? true) {
|
|
735
|
-
await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT);
|
|
736
|
-
}
|
|
737
|
-
// Create Enhanced proxy for the deployed instance
|
|
738
|
-
return await this.createEnhancedProxy(spec, instanceName);
|
|
739
|
-
}
|
|
740
|
-
finally {
|
|
741
|
-
await kroEngine.dispose();
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
646
|
/**
|
|
745
647
|
* Get all deployed instances
|
|
746
648
|
*/
|
|
@@ -985,41 +887,106 @@ export class KroResourceFactoryImpl {
|
|
|
985
887
|
// KRO's finalizer processing handles deleting all child resources
|
|
986
888
|
// (including Namespaces) via its applyset — no manual cleanup needed.
|
|
987
889
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
890
|
+
/**
|
|
891
|
+
* Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
|
|
892
|
+
* analog of the removed imperative `deployWithAlchemy(scope)`. Returns, in dependency order, a
|
|
893
|
+
* declaration for each discovered **singleton owner** (its own RGD + instance — mirroring
|
|
894
|
+
* `ensureSingletonOwners` in the imperative `deploy()` path), then the **RGD** (shared, deployed
|
|
895
|
+
* once), then the **CR instance** (one per `spec`, which `dependsOn` the RGD + the singletons),
|
|
896
|
+
* matching the old integration's per-resource state granularity. The caller instantiates
|
|
897
|
+
* each with `KroResource` inside their Stack (RGD first so reverse-topo removes the
|
|
898
|
+
* instance before the shared RGD):
|
|
899
|
+
*
|
|
900
|
+
* ```ts
|
|
901
|
+
* for (const { id, props } of await factory.toAlchemyResources(spec)) {
|
|
902
|
+
* yield* KroResource(id, props);
|
|
903
|
+
* }
|
|
904
|
+
* ```
|
|
905
|
+
*
|
|
906
|
+
* Each declaration carries serialized `kubeConfigOptions` (so reconcile can reconnect to the
|
|
907
|
+
* cluster after state rehydration) and `kroDeletion` (so delete is finalizer-safe / shared-RGD
|
|
908
|
+
* aware). The actual deploy/teardown runs in `kroProvider`'s reconcile/delete.
|
|
909
|
+
*/
|
|
910
|
+
async toAlchemyResources(spec, opts) {
|
|
911
|
+
const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
|
|
912
|
+
const kroDeletion = this.createAlchemyKroDeletionOptions();
|
|
913
|
+
// 0. Singleton owners. The imperative `deploy()` ensures shared singleton owners (each its own
|
|
914
|
+
// RGD + instance, in its registry namespace) via `ensureSingletonOwners` BEFORE the main
|
|
915
|
+
// instance; emit them as declarations too so the declarative path has the same boundaries.
|
|
916
|
+
// Their deterministic ids/names mean alchemy dedupes a singleton shared across compositions.
|
|
917
|
+
// (The runtime spec-drift check from `ensureSingletonOwners` is omitted here — `toAlchemyResources`
|
|
918
|
+
// stays cluster-free — but the spec-fingerprint annotation it relied on is still emitted.)
|
|
919
|
+
const singletonDeclarations = [];
|
|
920
|
+
const singletonInstanceIds = [];
|
|
921
|
+
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
922
|
+
const singletonFactory = this.singletonFactoryFor(definition);
|
|
923
|
+
try {
|
|
924
|
+
const decls = await singletonFactory.toAlchemyResources(definition.spec, {
|
|
925
|
+
instanceNameOverride: getSingletonInstanceName(definition.id),
|
|
926
|
+
singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
|
|
927
|
+
});
|
|
928
|
+
singletonDeclarations.push(...decls);
|
|
929
|
+
// `toAlchemyResources` returns the owner's OWN CR instance LAST (after any of ITS nested
|
|
930
|
+
// singletons + its RGD), so the consumer must wait on `decls[last]` — using "first decl
|
|
931
|
+
// with a dependency" would wrongly point at a nested singleton's instance.
|
|
932
|
+
const ownerInstance = decls[decls.length - 1];
|
|
933
|
+
if (ownerInstance)
|
|
934
|
+
singletonInstanceIds.push(ownerInstance.id);
|
|
935
|
+
}
|
|
936
|
+
finally {
|
|
937
|
+
await singletonFactory.dispose?.();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// 1. RGD declaration (deployed once per factory; shared by all instances). Reuse the normal
|
|
941
|
+
// serializer so externalRef/forEach/includeWhen/readyWhen + singleton boundaries match
|
|
942
|
+
// non-alchemy KRO deploys.
|
|
943
|
+
const rgdManifest = yaml.load(this.buildRgdYaml());
|
|
944
|
+
const rgdFactory = this.rgdProvider ??
|
|
945
|
+
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
946
|
+
const rgdEnhanced = rgdFactory(rgdManifest);
|
|
947
|
+
const rgdId = createAlchemyResourceId(rgdEnhanced, this.namespace);
|
|
948
|
+
const rgdDeclaration = {
|
|
949
|
+
id: rgdId,
|
|
950
|
+
dependsOn: [],
|
|
951
|
+
props: {
|
|
952
|
+
resource: rgdEnhanced,
|
|
953
|
+
namespace: this.namespace,
|
|
954
|
+
deploymentStrategy: 'kro',
|
|
955
|
+
kubeConfigOptions,
|
|
956
|
+
kroDeletion,
|
|
957
|
+
options: { waitForReady: true, timeout: DEFAULT_RGD_TIMEOUT },
|
|
958
|
+
},
|
|
959
|
+
};
|
|
960
|
+
// 2. CR instance declaration (one per deploy call). Depends on the RGD so alchemy applies the
|
|
961
|
+
// instance only after the RGD's CRD is established (else the CR apply races a missing kind).
|
|
962
|
+
const instanceName = opts?.instanceNameOverride ?? generateInstanceName(spec, this.name);
|
|
963
|
+
const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, opts?.singletonSpecFingerprint);
|
|
964
|
+
// Cast: a plain KubernetesResource is fine — the alchemy path only reads kind/metadata.
|
|
965
|
+
const crdAsEnhanced = crdInstanceManifest;
|
|
966
|
+
const instanceDeclaration = {
|
|
967
|
+
id: createAlchemyResourceId(crdAsEnhanced, this.namespace),
|
|
968
|
+
// Wait for the RGD's CRD AND for any singleton owners (mirrors `ensureSingletonOwners` running
|
|
969
|
+
// before the main deploy), so the instance applies only once its dependencies exist.
|
|
970
|
+
dependsOn: [rgdId, ...singletonInstanceIds],
|
|
971
|
+
props: {
|
|
972
|
+
resource: crdAsEnhanced,
|
|
973
|
+
namespace: this.namespace,
|
|
974
|
+
deploymentStrategy: 'kro',
|
|
975
|
+
kubeConfigOptions,
|
|
976
|
+
kroDeletion,
|
|
977
|
+
options: {
|
|
978
|
+
waitForReady: false,
|
|
979
|
+
timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
1019
980
|
},
|
|
1020
|
-
}
|
|
981
|
+
},
|
|
1021
982
|
};
|
|
983
|
+
return [...singletonDeclarations, rgdDeclaration, instanceDeclaration];
|
|
984
|
+
}
|
|
985
|
+
/** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
|
|
986
|
+
extractKubeConfigOptionsForAlchemy() {
|
|
987
|
+
return extractSerializableKubeConfigOptions(this.getKubeConfig(), this.factoryOptions.skipTLSVerify === true ? true : undefined);
|
|
1022
988
|
}
|
|
989
|
+
/** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
|
|
1023
990
|
createAlchemyKroDeletionOptions() {
|
|
1024
991
|
return {
|
|
1025
992
|
apiVersion: this.schemaDefinition.apiVersion,
|
|
@@ -1040,7 +1007,6 @@ export class KroResourceFactoryImpl {
|
|
|
1040
1007
|
return {
|
|
1041
1008
|
name: this.name,
|
|
1042
1009
|
mode: this.mode,
|
|
1043
|
-
isAlchemyManaged: this.isAlchemyManaged,
|
|
1044
1010
|
namespace: this.namespace,
|
|
1045
1011
|
instanceCount: instances.length,
|
|
1046
1012
|
health: rgdStatus.phase === 'ready' ? 'healthy' : 'degraded',
|