@intentius/chant-lexicon-aws 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/deep-observe.d.ts +101 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +3 -3
- package/dist/manifest.json +1 -1
- package/dist/meta.json +6 -0
- package/dist/plugin.d.ts +3 -4
- package/dist/plugin.d.ts.map +1 -1
- package/dist/stack-errors.d.ts +13 -0
- package/dist/stack-errors.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/deep-observe.test.ts +484 -0
- package/src/deep-observe.ts +384 -0
- package/src/generated/lexicon-aws.json +6 -0
- package/src/index.ts +12 -0
- package/src/lifecycle-integration.test.ts +139 -5
- package/src/plugin.ts +61 -14
- package/src/stack-errors.ts +15 -0
package/src/plugin.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
2
|
import { detectTemplate } from "./detect";
|
|
3
|
-
import type { LexiconPlugin, IntrinsicDef, ResourceMetadata, ExportedTemplate, ResourceSelector, InitTemplateSet, StackStatusObservation } from "@intentius/chant/lexicon";
|
|
3
|
+
import type { LexiconPlugin, IntrinsicDef, ObservationResult, DeepObservationResult, ResourceMetadata, ExportedTemplate, ResourceSelector, InitTemplateSet, StackStatusObservation } from "@intentius/chant/lexicon";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
6
6
|
import type { TemplateParser } from "@intentius/chant/import/parser";
|
|
@@ -16,6 +16,8 @@ import { fileURLToPath } from "url";
|
|
|
16
16
|
import { awsSerializer } from "./serializer";
|
|
17
17
|
import { FLOCI_EMULATOR } from "./op/activities/floci";
|
|
18
18
|
import { applyAwsEndpointArgv } from "./components/cloud-executor";
|
|
19
|
+
import { stackDoesNotExist } from "./stack-errors";
|
|
20
|
+
import { awsDeepNormalizationHooks, observeResourcesDeepAws } from "./deep-observe";
|
|
19
21
|
import { awsReferenceCatalog } from "./reference-catalog";
|
|
20
22
|
import { resolveTemplateAttrs } from "./live-attrs";
|
|
21
23
|
import { CFParser } from "./import/parser";
|
|
@@ -24,12 +26,9 @@ import { parseStackTemplate } from "./import/live-export";
|
|
|
24
26
|
import { awsCompletions } from "./lsp/completions";
|
|
25
27
|
import { awsHover } from "./lsp/hover";
|
|
26
28
|
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
export function stackDoesNotExist(stderr: string): boolean {
|
|
31
|
-
return /does not exist/i.test(stderr);
|
|
32
|
-
}
|
|
29
|
+
/** Re-exported from ./stack-errors so the long-standing import path (and its
|
|
30
|
+
* tests) keep working now that the deep reader shares the classifier. */
|
|
31
|
+
export { stackDoesNotExist } from "./stack-errors";
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* AWS CloudFormation lexicon plugin.
|
|
@@ -534,8 +533,9 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
534
533
|
entityNames: string[];
|
|
535
534
|
stack?: string;
|
|
536
535
|
owned?: boolean;
|
|
537
|
-
}): Promise<
|
|
536
|
+
}): Promise<ObservationResult> {
|
|
538
537
|
const { getRuntime } = await import("@intentius/chant/runtime-adapter");
|
|
538
|
+
const { observation, unobservedAll } = await import("@intentius/chant/observation");
|
|
539
539
|
const rt = getRuntime();
|
|
540
540
|
const resources: Record<string, ResourceMetadata> = {};
|
|
541
541
|
|
|
@@ -544,7 +544,7 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
544
544
|
// determined here. Degrade to detect-only rather than silently filtering.
|
|
545
545
|
// eslint-disable-next-line no-console
|
|
546
546
|
console.warn(
|
|
547
|
-
"[aws] ownership filter unavailable on describeResources (no tags from describe-stack-resources) — returning all; use `chant import --from <env> --owned` for ownership-filtered export",
|
|
547
|
+
"[aws] ownership filter unavailable on describeResources (no tags from describe-stack-resources) — returning all, each with an explicit `unknown` verdict; use `chant import --from <env> --owned` for ownership-filtered export",
|
|
548
548
|
);
|
|
549
549
|
}
|
|
550
550
|
|
|
@@ -565,12 +565,26 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
565
565
|
if (listResult.exitCode !== 0) {
|
|
566
566
|
// A stack that doesn't exist yet is the pre-first-apply state: nothing is
|
|
567
567
|
// deployed for this env, so there are no live resources (every declared
|
|
568
|
-
// resource is "pending") — not an error.
|
|
569
|
-
//
|
|
568
|
+
// resource is "pending") — not an error. That is a real absence, so the
|
|
569
|
+
// empty result is the honest one and `create` is the right proposal.
|
|
570
570
|
if (stackDoesNotExist(listResult.stderr)) {
|
|
571
|
-
return resources;
|
|
571
|
+
return observation(resources);
|
|
572
572
|
}
|
|
573
|
-
|
|
573
|
+
// Any other failure (credentials, throttling, a region that can't be
|
|
574
|
+
// reached) establishes nothing about what is deployed. Reporting every
|
|
575
|
+
// declared entity as NOT-OBSERVED (#1089) is what keeps a broken read
|
|
576
|
+
// from arriving downstream as "none of this exists".
|
|
577
|
+
const reason = /credential|token|expired|AccessDenied|not authorized|UnauthorizedOperation/i.test(listResult.stderr)
|
|
578
|
+
? "no-credentials"
|
|
579
|
+
: "read-failed";
|
|
580
|
+
return observation(
|
|
581
|
+
{},
|
|
582
|
+
unobservedAll(
|
|
583
|
+
options.entityNames,
|
|
584
|
+
reason,
|
|
585
|
+
`describe-stack-resources failed for stack "${stackName}": ${listResult.stderr.trim().split("\n")[0] ?? ""}`,
|
|
586
|
+
),
|
|
587
|
+
);
|
|
574
588
|
}
|
|
575
589
|
|
|
576
590
|
const data = JSON.parse(listResult.stdout) as {
|
|
@@ -627,13 +641,46 @@ aws cloudformation wait stack-update-complete --stack-name my-app-prod`,
|
|
|
627
641
|
physicalId: stackResource.PhysicalResourceId,
|
|
628
642
|
status: stackResource.ResourceStatus,
|
|
629
643
|
lastUpdated: stackResource.Timestamp,
|
|
644
|
+
// Total verdict (#1089): describe-stack-resources returns no tags, so
|
|
645
|
+
// this path cannot read the ownership marker. Say `unknown` explicitly
|
|
646
|
+
// rather than leaving the field off and letting each consumer guess —
|
|
647
|
+
// the change set never escalates `unknown` to a delete.
|
|
648
|
+
ownership: "unknown",
|
|
630
649
|
attributes: Object.keys(attributes).length > 0 ? attributes : undefined,
|
|
631
650
|
};
|
|
632
651
|
}
|
|
633
652
|
|
|
634
|
-
|
|
653
|
+
// Every entity the stack answered for was answered for: an entity the
|
|
654
|
+
// template doesn't carry is genuinely not in this stack, which is an
|
|
655
|
+
// absence, not a hole.
|
|
656
|
+
return observation(resources);
|
|
635
657
|
},
|
|
636
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Property-level live read (#1015) via the Cloud Control API — past
|
|
661
|
+
* CloudFormation's view of the world, into the resource as the service
|
|
662
|
+
* actually holds it. Implementation in ./deep-observe.ts.
|
|
663
|
+
*/
|
|
664
|
+
async observeResourcesDeep(options: {
|
|
665
|
+
environment: string;
|
|
666
|
+
buildOutput: string;
|
|
667
|
+
entityNames: string[];
|
|
668
|
+
entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
669
|
+
stack?: string;
|
|
670
|
+
owned?: boolean;
|
|
671
|
+
}): Promise<DeepObservationResult> {
|
|
672
|
+
return observeResourcesDeepAws({
|
|
673
|
+
environment: options.environment,
|
|
674
|
+
entityNames: options.entityNames,
|
|
675
|
+
entities: options.entities,
|
|
676
|
+
stack: options.stack,
|
|
677
|
+
owned: options.owned,
|
|
678
|
+
});
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
/** The noise rules the deep pass applies to both the live and declared trees. */
|
|
682
|
+
deepNormalizationHooks: awsDeepNormalizationHooks,
|
|
683
|
+
|
|
637
684
|
async describeStackStatus(options: { environment: string; stack: string }): Promise<StackStatusObservation | null> {
|
|
638
685
|
const { getRuntime } = await import("@intentius/chant/runtime-adapter");
|
|
639
686
|
const rt = getRuntime();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudFormation CLI error classification, shared by every live read path.
|
|
3
|
+
*
|
|
4
|
+
* Lives in its own module (rather than in ./plugin.ts, where it started) so the
|
|
5
|
+
* deep reader (#1015) can classify the same failure the same way without
|
|
6
|
+
* importing the plugin that imports it. ./plugin.ts re-exports it, so the
|
|
7
|
+
* original import path is unchanged.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** True when a CloudFormation CLI error means the stack simply isn't there yet
|
|
11
|
+
* (`ValidationError … does not exist`) — the pre-first-apply state, which live
|
|
12
|
+
* queries should treat as "nothing deployed", not a failure. */
|
|
13
|
+
export function stackDoesNotExist(stderr: string): boolean {
|
|
14
|
+
return /does not exist/i.test(stderr);
|
|
15
|
+
}
|