typekro 0.14.0 → 0.15.1
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/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 -14
- 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 +35 -9
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +143 -151
- 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/expressions/composition/composition-analyzer-helpers.d.ts +0 -13
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.js +78 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.js +34 -1
- package/dist/core/expressions/composition/imperative-analyzer.js.map +1 -1
- package/dist/core/serialization/cel-references.d.ts +1 -1
- package/dist/core/serialization/cel-references.d.ts.map +1 -1
- package/dist/core/serialization/cel-references.js +19 -3
- package/dist/core/serialization/cel-references.js.map +1 -1
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +19 -4
- package/dist/core/serialization/core.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/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/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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This factory handles deployment via Kro ResourceGraphDefinitions,
|
|
5
5
|
* using the Kro controller for dependency resolution and resource management.
|
|
6
6
|
*/
|
|
7
|
+
import type { AlchemyResourceDeclaration } from '../../alchemy/types.js';
|
|
7
8
|
import type { FactoryOptions, FactoryStatus, KroResourceFactory, RGDStatus } from '../types/deployment.js';
|
|
8
9
|
import type { Enhanced, KubernetesResource } from '../types/kubernetes.js';
|
|
9
10
|
import type { KroCompatibleType, MagicAssignableShape, SchemaDefinition, SchemaProxy } from '../types/serialization.js';
|
|
@@ -48,14 +49,12 @@ export declare class KroResourceFactoryImpl<TSpec extends KroCompatibleType, TSt
|
|
|
48
49
|
readonly mode: "kro";
|
|
49
50
|
readonly name: string;
|
|
50
51
|
readonly namespace: string;
|
|
51
|
-
readonly isAlchemyManaged: boolean;
|
|
52
52
|
readonly rgdName: string;
|
|
53
53
|
readonly schema: SchemaProxy<TSpec, TStatus>;
|
|
54
54
|
private readonly resources;
|
|
55
55
|
private readonly closures;
|
|
56
56
|
private readonly schemaDefinition;
|
|
57
57
|
private readonly statusMappings;
|
|
58
|
-
private readonly alchemyScope;
|
|
59
58
|
private readonly singletonDefinitions;
|
|
60
59
|
private readonly singletonOwnerStatuses;
|
|
61
60
|
private readonly logger;
|
|
@@ -84,7 +83,6 @@ export declare class KroResourceFactoryImpl<TSpec extends KroCompatibleType, TSt
|
|
|
84
83
|
private discoveredPlural;
|
|
85
84
|
private readonly kroCustomResourceProvider;
|
|
86
85
|
private readonly rgdProvider;
|
|
87
|
-
private readonly alchemyBridge;
|
|
88
86
|
constructor(name: string, resources: Record<string, KubernetesResource>, schemaDefinition: SchemaDefinition<TSpec, TStatus>, statusMappings: MagicAssignableShape<TStatus>, options?: FactoryOptions);
|
|
89
87
|
/** Extract nested composition status CEL mappings from the raw status object. */
|
|
90
88
|
private getNestedStatusCel;
|
|
@@ -142,6 +140,14 @@ export declare class KroResourceFactoryImpl<TSpec extends KroCompatibleType, TSt
|
|
|
142
140
|
instanceNameOverride?: string;
|
|
143
141
|
singletonSpecFingerprint?: string;
|
|
144
142
|
}): Promise<Enhanced<TSpec, TStatus>>;
|
|
143
|
+
/**
|
|
144
|
+
* Discover the singleton-owner definitions this composition depends on (deduped by key): from
|
|
145
|
+
* re-executing the composition fn under a discovery context, plus any injected definitions.
|
|
146
|
+
* Shared by {@link ensureSingletonOwners} (imperative deploy) and {@link toAlchemyResources}.
|
|
147
|
+
*/
|
|
148
|
+
private discoverSingletonDefinitions;
|
|
149
|
+
/** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
|
|
150
|
+
private singletonFactoryFor;
|
|
145
151
|
private ensureSingletonOwners;
|
|
146
152
|
private getSingletonOwnerInstancesForDriftCheck;
|
|
147
153
|
private isMissingSingletonOwnerCrdError;
|
|
@@ -154,12 +160,6 @@ export declare class KroResourceFactoryImpl<TSpec extends KroCompatibleType, TSt
|
|
|
154
160
|
* Deploy directly to Kubernetes using DirectDeploymentEngine
|
|
155
161
|
*/
|
|
156
162
|
private deployDirect;
|
|
157
|
-
/**
|
|
158
|
-
* Deploy using type-safe alchemy resource wrapping
|
|
159
|
-
*
|
|
160
|
-
* In alchemy mode, the RGD gets one typed alchemy Resource and each instance gets another
|
|
161
|
-
*/
|
|
162
|
-
private deployWithAlchemy;
|
|
163
163
|
/**
|
|
164
164
|
* Get all deployed instances
|
|
165
165
|
*/
|
|
@@ -173,7 +173,33 @@ export declare class KroResourceFactoryImpl<TSpec extends KroCompatibleType, TSt
|
|
|
173
173
|
scopes?: string[];
|
|
174
174
|
includeUnscopedResources?: boolean;
|
|
175
175
|
}): Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
|
|
178
|
+
* analog of the removed imperative `deployWithAlchemy(scope)`. Returns, in dependency order, a
|
|
179
|
+
* declaration for each discovered **singleton owner** (its own RGD + instance — mirroring
|
|
180
|
+
* `ensureSingletonOwners` in the imperative `deploy()` path), then the **RGD** (shared, deployed
|
|
181
|
+
* once), then the **CR instance** (one per `spec`, which `dependsOn` the RGD + the singletons),
|
|
182
|
+
* matching the old integration's per-resource state granularity. The caller instantiates
|
|
183
|
+
* each with `KroResource` inside their Stack (RGD first so reverse-topo removes the
|
|
184
|
+
* instance before the shared RGD):
|
|
185
|
+
*
|
|
186
|
+
* ```ts
|
|
187
|
+
* for (const { id, props } of await factory.toAlchemyResources(spec)) {
|
|
188
|
+
* yield* KroResource(id, props);
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* Each declaration carries serialized `kubeConfigOptions` (so reconcile can reconnect to the
|
|
193
|
+
* cluster after state rehydration) and `kroDeletion` (so delete is finalizer-safe / shared-RGD
|
|
194
|
+
* aware). The actual deploy/teardown runs in `kroProvider`'s reconcile/delete.
|
|
195
|
+
*/
|
|
196
|
+
toAlchemyResources(spec: TSpec, opts?: {
|
|
197
|
+
instanceNameOverride?: string;
|
|
198
|
+
singletonSpecFingerprint?: string;
|
|
199
|
+
}): Promise<AlchemyResourceDeclaration[]>;
|
|
200
|
+
/** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
|
|
176
201
|
private extractKubeConfigOptionsForAlchemy;
|
|
202
|
+
/** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
|
|
177
203
|
private createAlchemyKroDeletionOptions;
|
|
178
204
|
/**
|
|
179
205
|
* Get factory status
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kro-factory.d.ts","sourceRoot":"","sources":["../../../src/core/deployment/kro-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"kro-factory.d.ts","sourceRoot":"","sources":["../../../src/core/deployment/kro-factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,11 +18,13 @@ import { CRDInstanceError, ensureError, ResourceGraphFactoryError, TypeKroError,
|
|
|
18
18
|
import { applyAnalysisToResources } from '../expressions/composition/composition-analyzer.js';
|
|
19
19
|
import { createBunCompatibleKubernetesObjectApi } from '../kubernetes/index.js';
|
|
20
20
|
import { getComponentLogger } from '../logging/index.js';
|
|
21
|
+
import { createAlchemyResourceId } from '../../alchemy/utilities.js';
|
|
21
22
|
// Dependency inversion: kroCustomResource, resourceGraphDefinition, and
|
|
22
23
|
// alchemy bridge are injected via FactoryOptions providers (Phase 3.5)
|
|
23
24
|
// instead of dynamic import() from higher layers.
|
|
24
25
|
import { getMetadataField, setMetadataField } from '../metadata/index.js';
|
|
25
26
|
import { createSchemaProxy, DeploymentMode } from '../references/index.js';
|
|
27
|
+
import { isStaticExpression } from '../serialization/cel-references.js';
|
|
26
28
|
import { applyTernaryConditionalsToResources } from '../serialization/kro-post-processing.js';
|
|
27
29
|
import { generateKroSchemaFromArktype } from '../serialization/schema.js';
|
|
28
30
|
import { serializeResourceGraphToYaml } from '../serialization/yaml.js';
|
|
@@ -32,7 +34,7 @@ import { DirectDeploymentEngine } from './engine.js';
|
|
|
32
34
|
import { logHandleSnapshot } from './handle-tracing.js';
|
|
33
35
|
import { isNotFoundError } from './k8s-helpers.js';
|
|
34
36
|
import { waitForKroInstanceReady as waitForKroInstanceReadyShared } from './kro-readiness.js';
|
|
35
|
-
import { convertToKubernetesName, generateInstanceName, getSingletonInstanceName, pluralizeKind,
|
|
37
|
+
import { convertToKubernetesName, extractSerializableKubeConfigOptions, generateInstanceName, getSingletonInstanceName, pluralizeKind, validateSpec, } from './shared-utilities.js';
|
|
36
38
|
import { joinYamlDocuments, singletonOwnerInstanceYamls, singletonRgdYamls, } from './singleton-gitops.js';
|
|
37
39
|
import { assertNoDeployedSingletonSpecDrift, singletonSpecFingerprintAnnotationValue, } from './singleton-owner-drift.js';
|
|
38
40
|
/**
|
|
@@ -104,14 +106,12 @@ export class KroResourceFactoryImpl {
|
|
|
104
106
|
mode = 'kro';
|
|
105
107
|
name;
|
|
106
108
|
namespace;
|
|
107
|
-
isAlchemyManaged;
|
|
108
109
|
rgdName;
|
|
109
110
|
schema;
|
|
110
111
|
resources;
|
|
111
112
|
closures;
|
|
112
113
|
schemaDefinition;
|
|
113
114
|
statusMappings;
|
|
114
|
-
alchemyScope;
|
|
115
115
|
singletonDefinitions;
|
|
116
116
|
singletonOwnerStatuses = new Map();
|
|
117
117
|
logger = getComponentLogger('kro-factory');
|
|
@@ -142,12 +142,9 @@ export class KroResourceFactoryImpl {
|
|
|
142
142
|
// instead of dynamic import() from factories/ and alchemy/ layers.
|
|
143
143
|
kroCustomResourceProvider;
|
|
144
144
|
rgdProvider;
|
|
145
|
-
alchemyBridge;
|
|
146
145
|
constructor(name, resources, schemaDefinition, statusMappings, options = {}) {
|
|
147
146
|
this.name = name;
|
|
148
147
|
this.namespace = options.namespace || 'default';
|
|
149
|
-
this.alchemyScope = options.alchemyScope;
|
|
150
|
-
this.isAlchemyManaged = !!options.alchemyScope;
|
|
151
148
|
this.rgdName = convertToKubernetesName(name); // Convert to valid Kubernetes resource name
|
|
152
149
|
this.resources = resources;
|
|
153
150
|
this.closures = options.closures || {};
|
|
@@ -164,7 +161,6 @@ export class KroResourceFactoryImpl {
|
|
|
164
161
|
// Injected providers — fall back to dynamic import() for backward compatibility
|
|
165
162
|
this.kroCustomResourceProvider = options.kroCustomResourceProvider;
|
|
166
163
|
this.rgdProvider = options.rgdProvider;
|
|
167
|
-
this.alchemyBridge = options.alchemyBridge;
|
|
168
164
|
// Validate closures for Kro mode - detect KubernetesRef inputs and raise clear errors
|
|
169
165
|
this.validateClosuresForKroMode();
|
|
170
166
|
}
|
|
@@ -397,53 +393,49 @@ export class KroResourceFactoryImpl {
|
|
|
397
393
|
// Execute closures before RGD creation (Kro mode requirement)
|
|
398
394
|
await this.executeClosuresBeforeRGD(spec);
|
|
399
395
|
await this.ensureSingletonOwners(spec);
|
|
400
|
-
|
|
401
|
-
return this.deployWithAlchemy(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
405
|
-
}
|
|
396
|
+
return this.deployDirect(spec, opts?.instanceNameOverride, opts?.singletonSpecFingerprint);
|
|
406
397
|
}
|
|
407
|
-
|
|
408
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Discover the singleton-owner definitions this composition depends on (deduped by key): from
|
|
400
|
+
* re-executing the composition fn under a discovery context, plus any injected definitions.
|
|
401
|
+
* Shared by {@link ensureSingletonOwners} (imperative deploy) and {@link toAlchemyResources}.
|
|
402
|
+
*/
|
|
403
|
+
discoverSingletonDefinitions(spec) {
|
|
404
|
+
const discovered = new Map();
|
|
409
405
|
if (this.factoryOptions.compositionFn) {
|
|
410
|
-
const
|
|
411
|
-
runWithCompositionContext(
|
|
406
|
+
const ctx = createCompositionContext('singleton-owner-discovery');
|
|
407
|
+
runWithCompositionContext(ctx, () => {
|
|
412
408
|
this.factoryOptions.compositionFn?.(spec);
|
|
413
409
|
});
|
|
414
|
-
for (const [key, definition] of
|
|
415
|
-
|
|
410
|
+
for (const [key, definition] of ctx.singletonDefinitions ?? []) {
|
|
411
|
+
discovered.set(key, definition);
|
|
416
412
|
}
|
|
417
413
|
}
|
|
418
414
|
for (const definition of this.singletonDefinitions) {
|
|
419
|
-
if (!
|
|
420
|
-
|
|
421
|
-
}
|
|
415
|
+
if (!discovered.has(definition.key))
|
|
416
|
+
discovered.set(definition.key, definition);
|
|
422
417
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
418
|
+
return Array.from(discovered.values());
|
|
419
|
+
}
|
|
420
|
+
/** Build the KRO factory that owns a singleton's RGD + instance (its registry namespace). */
|
|
421
|
+
singletonFactoryFor(definition) {
|
|
422
|
+
return definition.composition.factory('kro', {
|
|
423
|
+
namespace: definition.registryNamespace,
|
|
424
|
+
waitForReady: true,
|
|
425
|
+
...(this.factoryOptions.timeout !== undefined ? { timeout: this.factoryOptions.timeout } : {}),
|
|
426
|
+
...(this.factoryOptions.kubeConfig !== undefined
|
|
427
|
+
? { kubeConfig: this.factoryOptions.kubeConfig }
|
|
428
|
+
: {}),
|
|
429
|
+
...(this.factoryOptions.skipTLSVerify !== undefined
|
|
430
|
+
? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
|
|
431
|
+
: {}),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
async ensureSingletonOwners(spec) {
|
|
435
|
+
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
432
436
|
await this.ensureTargetNamespace(definition.registryNamespace);
|
|
433
437
|
const singletonInstanceName = getSingletonInstanceName(definition.id);
|
|
434
|
-
const singletonFactory =
|
|
435
|
-
namespace: definition.registryNamespace,
|
|
436
|
-
waitForReady: true,
|
|
437
|
-
...(this.factoryOptions.timeout !== undefined
|
|
438
|
-
? { timeout: this.factoryOptions.timeout }
|
|
439
|
-
: {}),
|
|
440
|
-
...(this.factoryOptions.kubeConfig !== undefined
|
|
441
|
-
? { kubeConfig: this.factoryOptions.kubeConfig }
|
|
442
|
-
: {}),
|
|
443
|
-
...(this.factoryOptions.skipTLSVerify !== undefined
|
|
444
|
-
? { skipTLSVerify: this.factoryOptions.skipTLSVerify }
|
|
445
|
-
: {}),
|
|
446
|
-
});
|
|
438
|
+
const singletonFactory = this.singletonFactoryFor(definition);
|
|
447
439
|
try {
|
|
448
440
|
this.logger.info('Ensuring singleton owner boundary', {
|
|
449
441
|
singletonId: definition.id,
|
|
@@ -542,7 +534,6 @@ export class KroResourceFactoryImpl {
|
|
|
542
534
|
const deploymentContext = {
|
|
543
535
|
kubernetesApi: createBunCompatibleKubernetesObjectApi(kubeConfig),
|
|
544
536
|
kubeConfig: kubeConfig,
|
|
545
|
-
...(this.alchemyScope && { alchemyScope: this.alchemyScope }),
|
|
546
537
|
namespace: this.namespace,
|
|
547
538
|
deployedResources: new Map(), // Empty for pre-RGD execution
|
|
548
539
|
resolveReference: async (ref) => {
|
|
@@ -653,78 +644,6 @@ export class KroResourceFactoryImpl {
|
|
|
653
644
|
await deploymentEngine.dispose();
|
|
654
645
|
}
|
|
655
646
|
}
|
|
656
|
-
/**
|
|
657
|
-
* Deploy using type-safe alchemy resource wrapping
|
|
658
|
-
*
|
|
659
|
-
* In alchemy mode, the RGD gets one typed alchemy Resource and each instance gets another
|
|
660
|
-
*/
|
|
661
|
-
async deployWithAlchemy(spec, instanceNameOverride, singletonSpecFingerprint) {
|
|
662
|
-
validateAlchemyScope(this.alchemyScope, 'KRO Alchemy deployment');
|
|
663
|
-
const alchemyScope = this.alchemyScope;
|
|
664
|
-
// Use static registration functions
|
|
665
|
-
// Create deployer instance using DirectDeploymentEngine with KRO mode
|
|
666
|
-
const kroEngine = new DirectDeploymentEngine(this.getKubeConfig(), undefined, undefined, DeploymentMode.KRO);
|
|
667
|
-
const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
|
|
668
|
-
const kroDeletion = this.createAlchemyKroDeletionOptions();
|
|
669
|
-
try {
|
|
670
|
-
// 1. Ensure RGD is deployed via alchemy (once per factory). Reuse the
|
|
671
|
-
// normal serializer so externalRef/forEach/includeWhen/readyWhen and
|
|
672
|
-
// singleton owner boundaries match non-Alchemy KRO deploys.
|
|
673
|
-
const rgdManifest = yaml.load(this.buildRgdYaml());
|
|
674
|
-
// Register RGD type dynamically
|
|
675
|
-
const rgdFactory = this.rgdProvider ??
|
|
676
|
-
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
677
|
-
const rgdEnhanced = rgdFactory(rgdManifest);
|
|
678
|
-
const bridge = this.alchemyBridge ?? (await import('../../alchemy/deployment.js'));
|
|
679
|
-
const RGDProvider = bridge.ensureResourceTypeRegistered(rgdEnhanced);
|
|
680
|
-
const rgdId = bridge.createAlchemyResourceId(rgdEnhanced, this.namespace);
|
|
681
|
-
await alchemyScope.run(async () => {
|
|
682
|
-
await RGDProvider(rgdId, {
|
|
683
|
-
resource: rgdEnhanced,
|
|
684
|
-
namespace: this.namespace,
|
|
685
|
-
deploymentStrategy: 'kro',
|
|
686
|
-
kubeConfigOptions,
|
|
687
|
-
kroDeletion,
|
|
688
|
-
options: {
|
|
689
|
-
waitForReady: true,
|
|
690
|
-
timeout: DEFAULT_RGD_TIMEOUT, // RGD should be ready quickly
|
|
691
|
-
},
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
await this.waitForCRDReadyWithEngine(kroEngine);
|
|
695
|
-
// 2. Create instance via alchemy (once per deploy call)
|
|
696
|
-
await this.ensureTargetNamespace();
|
|
697
|
-
const instanceName = instanceNameOverride ?? generateInstanceName(spec, this.name);
|
|
698
|
-
const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, singletonSpecFingerprint);
|
|
699
|
-
// Register CRD instance type dynamically
|
|
700
|
-
// Cast required: crdInstanceManifest is a plain KubernetesResource, but alchemy functions
|
|
701
|
-
// expect Enhanced<unknown, unknown>. They only access kind/metadata.name for type inference.
|
|
702
|
-
const crdAsEnhanced = crdInstanceManifest;
|
|
703
|
-
const CRDInstanceProvider = bridge.ensureResourceTypeRegistered(crdAsEnhanced);
|
|
704
|
-
const instanceId = bridge.createAlchemyResourceId(crdAsEnhanced, this.namespace);
|
|
705
|
-
await alchemyScope.run(async () => {
|
|
706
|
-
await CRDInstanceProvider(instanceId, {
|
|
707
|
-
resource: crdAsEnhanced,
|
|
708
|
-
namespace: this.namespace,
|
|
709
|
-
deploymentStrategy: 'kro',
|
|
710
|
-
kubeConfigOptions,
|
|
711
|
-
kroDeletion,
|
|
712
|
-
options: {
|
|
713
|
-
waitForReady: false,
|
|
714
|
-
timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
715
|
-
},
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
if (this.factoryOptions.waitForReady ?? true) {
|
|
719
|
-
await this.waitForKroInstanceReady(instanceName, this.factoryOptions.timeout || DEFAULT_KRO_INSTANCE_TIMEOUT);
|
|
720
|
-
}
|
|
721
|
-
// Create Enhanced proxy for the deployed instance
|
|
722
|
-
return await this.createEnhancedProxy(spec, instanceName);
|
|
723
|
-
}
|
|
724
|
-
finally {
|
|
725
|
-
await kroEngine.dispose();
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
647
|
/**
|
|
729
648
|
* Get all deployed instances
|
|
730
649
|
*/
|
|
@@ -969,41 +888,106 @@ export class KroResourceFactoryImpl {
|
|
|
969
888
|
// KRO's finalizer processing handles deleting all child resources
|
|
970
889
|
// (including Namespaces) via its applyset — no manual cleanup needed.
|
|
971
890
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
891
|
+
/**
|
|
892
|
+
* Emit this factory's KRO deployment as declarative alchemy **v2** resources — the v2
|
|
893
|
+
* analog of the removed imperative `deployWithAlchemy(scope)`. Returns, in dependency order, a
|
|
894
|
+
* declaration for each discovered **singleton owner** (its own RGD + instance — mirroring
|
|
895
|
+
* `ensureSingletonOwners` in the imperative `deploy()` path), then the **RGD** (shared, deployed
|
|
896
|
+
* once), then the **CR instance** (one per `spec`, which `dependsOn` the RGD + the singletons),
|
|
897
|
+
* matching the old integration's per-resource state granularity. The caller instantiates
|
|
898
|
+
* each with `KroResource` inside their Stack (RGD first so reverse-topo removes the
|
|
899
|
+
* instance before the shared RGD):
|
|
900
|
+
*
|
|
901
|
+
* ```ts
|
|
902
|
+
* for (const { id, props } of await factory.toAlchemyResources(spec)) {
|
|
903
|
+
* yield* KroResource(id, props);
|
|
904
|
+
* }
|
|
905
|
+
* ```
|
|
906
|
+
*
|
|
907
|
+
* Each declaration carries serialized `kubeConfigOptions` (so reconcile can reconnect to the
|
|
908
|
+
* cluster after state rehydration) and `kroDeletion` (so delete is finalizer-safe / shared-RGD
|
|
909
|
+
* aware). The actual deploy/teardown runs in `kroProvider`'s reconcile/delete.
|
|
910
|
+
*/
|
|
911
|
+
async toAlchemyResources(spec, opts) {
|
|
912
|
+
const kubeConfigOptions = this.extractKubeConfigOptionsForAlchemy();
|
|
913
|
+
const kroDeletion = this.createAlchemyKroDeletionOptions();
|
|
914
|
+
// 0. Singleton owners. The imperative `deploy()` ensures shared singleton owners (each its own
|
|
915
|
+
// RGD + instance, in its registry namespace) via `ensureSingletonOwners` BEFORE the main
|
|
916
|
+
// instance; emit them as declarations too so the declarative path has the same boundaries.
|
|
917
|
+
// Their deterministic ids/names mean alchemy dedupes a singleton shared across compositions.
|
|
918
|
+
// (The runtime spec-drift check from `ensureSingletonOwners` is omitted here — `toAlchemyResources`
|
|
919
|
+
// stays cluster-free — but the spec-fingerprint annotation it relied on is still emitted.)
|
|
920
|
+
const singletonDeclarations = [];
|
|
921
|
+
const singletonInstanceIds = [];
|
|
922
|
+
for (const definition of this.discoverSingletonDefinitions(spec)) {
|
|
923
|
+
const singletonFactory = this.singletonFactoryFor(definition);
|
|
924
|
+
try {
|
|
925
|
+
const decls = await singletonFactory.toAlchemyResources(definition.spec, {
|
|
926
|
+
instanceNameOverride: getSingletonInstanceName(definition.id),
|
|
927
|
+
singletonSpecFingerprint: singletonSpecFingerprintAnnotationValue(definition.specFingerprint),
|
|
928
|
+
});
|
|
929
|
+
singletonDeclarations.push(...decls);
|
|
930
|
+
// `toAlchemyResources` returns the owner's OWN CR instance LAST (after any of ITS nested
|
|
931
|
+
// singletons + its RGD), so the consumer must wait on `decls[last]` — using "first decl
|
|
932
|
+
// with a dependency" would wrongly point at a nested singleton's instance.
|
|
933
|
+
const ownerInstance = decls[decls.length - 1];
|
|
934
|
+
if (ownerInstance)
|
|
935
|
+
singletonInstanceIds.push(ownerInstance.id);
|
|
936
|
+
}
|
|
937
|
+
finally {
|
|
938
|
+
await singletonFactory.dispose?.();
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
// 1. RGD declaration (deployed once per factory; shared by all instances). Reuse the normal
|
|
942
|
+
// serializer so externalRef/forEach/includeWhen/readyWhen + singleton boundaries match
|
|
943
|
+
// non-alchemy KRO deploys.
|
|
944
|
+
const rgdManifest = yaml.load(this.buildRgdYaml());
|
|
945
|
+
const rgdFactory = this.rgdProvider ??
|
|
946
|
+
(await import('../../factories/kro/resource-graph-definition.js')).resourceGraphDefinition;
|
|
947
|
+
const rgdEnhanced = rgdFactory(rgdManifest);
|
|
948
|
+
const rgdId = createAlchemyResourceId(rgdEnhanced, this.namespace);
|
|
949
|
+
const rgdDeclaration = {
|
|
950
|
+
id: rgdId,
|
|
951
|
+
dependsOn: [],
|
|
952
|
+
props: {
|
|
953
|
+
resource: rgdEnhanced,
|
|
954
|
+
namespace: this.namespace,
|
|
955
|
+
deploymentStrategy: 'kro',
|
|
956
|
+
kubeConfigOptions,
|
|
957
|
+
kroDeletion,
|
|
958
|
+
options: { waitForReady: true, timeout: DEFAULT_RGD_TIMEOUT },
|
|
959
|
+
},
|
|
960
|
+
};
|
|
961
|
+
// 2. CR instance declaration (one per deploy call). Depends on the RGD so alchemy applies the
|
|
962
|
+
// instance only after the RGD's CRD is established (else the CR apply races a missing kind).
|
|
963
|
+
const instanceName = opts?.instanceNameOverride ?? generateInstanceName(spec, this.name);
|
|
964
|
+
const crdInstanceManifest = this.createCustomResourceInstance(instanceName, spec, opts?.singletonSpecFingerprint);
|
|
965
|
+
// Cast: a plain KubernetesResource is fine — the alchemy path only reads kind/metadata.
|
|
966
|
+
const crdAsEnhanced = crdInstanceManifest;
|
|
967
|
+
const instanceDeclaration = {
|
|
968
|
+
id: createAlchemyResourceId(crdAsEnhanced, this.namespace),
|
|
969
|
+
// Wait for the RGD's CRD AND for any singleton owners (mirrors `ensureSingletonOwners` running
|
|
970
|
+
// before the main deploy), so the instance applies only once its dependencies exist.
|
|
971
|
+
dependsOn: [rgdId, ...singletonInstanceIds],
|
|
972
|
+
props: {
|
|
973
|
+
resource: crdAsEnhanced,
|
|
974
|
+
namespace: this.namespace,
|
|
975
|
+
deploymentStrategy: 'kro',
|
|
976
|
+
kubeConfigOptions,
|
|
977
|
+
kroDeletion,
|
|
978
|
+
options: {
|
|
979
|
+
waitForReady: false,
|
|
980
|
+
timeout: this.factoryOptions.timeout ?? DEFAULT_DEPLOYMENT_TIMEOUT,
|
|
1003
981
|
},
|
|
1004
|
-
}
|
|
982
|
+
},
|
|
1005
983
|
};
|
|
984
|
+
return [...singletonDeclarations, rgdDeclaration, instanceDeclaration];
|
|
985
|
+
}
|
|
986
|
+
/** Serialize the factory's cluster connection so an alchemy resource can reconnect after rehydration. */
|
|
987
|
+
extractKubeConfigOptionsForAlchemy() {
|
|
988
|
+
return extractSerializableKubeConfigOptions(this.getKubeConfig(), this.factoryOptions.skipTLSVerify === true ? true : undefined);
|
|
1006
989
|
}
|
|
990
|
+
/** Build the finalizer-safe, shared-RGD-aware deletion metadata for this factory's instances. */
|
|
1007
991
|
createAlchemyKroDeletionOptions() {
|
|
1008
992
|
return {
|
|
1009
993
|
apiVersion: this.schemaDefinition.apiVersion,
|
|
@@ -1024,7 +1008,6 @@ export class KroResourceFactoryImpl {
|
|
|
1024
1008
|
return {
|
|
1025
1009
|
name: this.name,
|
|
1026
1010
|
mode: this.mode,
|
|
1027
|
-
isAlchemyManaged: this.isAlchemyManaged,
|
|
1028
1011
|
namespace: this.namespace,
|
|
1029
1012
|
instanceCount: instances.length,
|
|
1030
1013
|
health: rgdStatus.phase === 'ready' ? 'healthy' : 'degraded',
|
|
@@ -1144,8 +1127,17 @@ export class KroResourceFactoryImpl {
|
|
|
1144
1127
|
}
|
|
1145
1128
|
const statusOverrides = this.factoryOptions.compositionAnalysis?.statusOverrides ?? [];
|
|
1146
1129
|
if (statusOverrides.length > 0) {
|
|
1147
|
-
|
|
1130
|
+
// Only resource-referencing (dynamic) overrides belong in the KRO schema;
|
|
1131
|
+
// schema-only ones (e.g. `spec.x ? a : b`) are rejected by KRO ("unknown
|
|
1132
|
+
// identifiers: [schema]") and are hydrated client-side instead, like any
|
|
1133
|
+
// other static status field.
|
|
1134
|
+
const resourceIdList = Object.keys(aspectResources);
|
|
1135
|
+
const nestedCel = this.getNestedStatusCel();
|
|
1136
|
+
const nestedCelForClassification = nestedCel && Object.keys(nestedCel).length > 0 ? nestedCel : undefined;
|
|
1148
1137
|
for (const override of statusOverrides) {
|
|
1138
|
+
if (isStaticExpression(override.celExpression, nestedCelForClassification, resourceIdList))
|
|
1139
|
+
continue;
|
|
1140
|
+
kroSchema.status ??= {};
|
|
1149
1141
|
const yamlSafe = override.celExpression.replace(/"([^"\\]*)"/g, "'$1'");
|
|
1150
1142
|
kroSchema.status[override.propertyPath] = yamlSafe;
|
|
1151
1143
|
}
|