cdk-local 0.147.18 → 0.147.19
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/cli.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/local-studio-BBA_8Xvk.d.ts.map +1 -1
- package/dist/{local-studio-BIHN-Erp.js → local-studio-DOdzcjkE.js} +568 -114
- package/dist/local-studio-DOdzcjkE.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-BIHN-Erp.js.map +0 -1
|
@@ -245,15 +245,84 @@ function buildStsClientConfig(args) {
|
|
|
245
245
|
* SCOPE, stated so a later sweep finds a decision rather than an oversight:
|
|
246
246
|
* this module governs those nine plus sigv4-verify's one, and its
|
|
247
247
|
* {@link flattenToOneLine} additionally covers every other wire-derived value
|
|
248
|
-
* printed on those lines (the role ARN, on the failure AND success paths).
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* `
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
248
|
+
* printed on those lines (the role ARN, on the failure AND success paths).
|
|
249
|
+
*
|
|
250
|
+
* Issue #579 extended it to the AWS SDK error relays elsewhere under
|
|
251
|
+
* `src/local/**` (plus `local-studio.ts`'s image-context warn), so the policy
|
|
252
|
+
* is no longer scoped to `src/cli/commands/**`:
|
|
253
|
+
* `cfn-local-state-provider.ts` (`formatAwsErrorForWarn`, six callers),
|
|
254
|
+
* `ssm-parameter-resolver.ts` (whose `formatSsmError` was DELETED rather than
|
|
255
|
+
* fixed — it was a second spelling of the same forging vector),
|
|
256
|
+
* `state-resolver.ts`, `cloudfront-kvs-client.ts`, `cloudfront-s3-origin.ts`,
|
|
257
|
+
* `layer-arn-materializer.ts`, `ecr-puller.ts`, `ecs-secrets-resolver.ts` and
|
|
258
|
+
* `httpv2-service-integration.ts`. Each was decided on the SAME two axes below
|
|
259
|
+
* rather than rewritten mechanically, and a `catch` around a purely LOCAL
|
|
260
|
+
* operation was left alone: `agentcore-s3-bundle.ts`'s unzip and
|
|
261
|
+
* `layer-arn-materializer.ts`'s presigned-URL download / unzip see no
|
|
262
|
+
* credential chain and no service response, so there is nothing here for them.
|
|
263
|
+
*
|
|
264
|
+
* The test is what a `catch` CAN SEE, and applying it by LOCATION instead is
|
|
265
|
+
* how the sweep first got `agentcore-s3-bundle.ts` wrong. Its `try` does wrap
|
|
266
|
+
* `unzipSync` and nothing else — but that made the S3 `GetObject` above it
|
|
267
|
+
* UNCOVERED, not out of scope: it had no `catch` at all, so the raw SDK error
|
|
268
|
+
* propagated to `withErrorHandling` -> `formatError`, which prints
|
|
269
|
+
* `${error.name}: ${error.message}` unflattened and unclamped at DEFAULT
|
|
270
|
+
* level, and the default credential chain is what a run without
|
|
271
|
+
* `--assume-role` uses. "Outside that `catch`" is not "outside the sweep";
|
|
272
|
+
* ask which populations reach the site, and if the answer is "nothing catches
|
|
273
|
+
* it here", follow it to the frame that does.
|
|
274
|
+
*
|
|
275
|
+
* #579 also widened what LEVEL means, and the widening is the useful part:
|
|
276
|
+
* `httpv2-service-integration.ts`'s relay is not a log line at all, it is a
|
|
277
|
+
* served HTTP RESPONSE BODY — the widest reader in the sweep, since it needs
|
|
278
|
+
* neither `--verbose` nor access to the terminal, and the studio capture proxy
|
|
279
|
+
* records it onto the timeline besides. The axis is about READERS, not about
|
|
280
|
+
* logs.
|
|
281
|
+
*
|
|
282
|
+
* TWO CALLING CONVENTIONS this imposes, both found by #579 rather than
|
|
283
|
+
* anticipated here:
|
|
284
|
+
*
|
|
285
|
+
* 1. Render each failure ONCE. {@link describeAwsFailureForWarn} EMITS the
|
|
286
|
+
* `debug` line, so calling it twice for one error prints that line twice
|
|
287
|
+
* (`cfn-local-state-provider.ts`'s `load` did, having rendered the same
|
|
288
|
+
* failure for its warn and for `lastLoadError`).
|
|
289
|
+
* 2. Re-raise cdk-local's OWN throws ABOVE the relay, with an identifiable
|
|
290
|
+
* class. The discriminator is positive, so a `catch` that also catches
|
|
291
|
+
* the caller's own `throw new Error('...')` withholds text cdk-local
|
|
292
|
+
* wrote itself — which is the diagnostic loss this file's
|
|
293
|
+
* {@link describeCredentialLoadFailure} note calls "a real diagnostic
|
|
294
|
+
* loss, accepted". #579 stopped accepting it where it was cheap not to:
|
|
295
|
+
* `layer-arn-materializer.ts`'s ARN-shape and missing-`Content.Location`
|
|
296
|
+
* guards became `LayerMaterializationError`s and are re-raised, and
|
|
297
|
+
* `httpv2-service-integration.ts`'s missing-RequestParameter 400 became a
|
|
298
|
+
* `ServiceIntegrationRequestError` so an actionable 400 body did not
|
|
299
|
+
* degrade into a class name and a character count.
|
|
300
|
+
*
|
|
301
|
+
* TWO CATCH-ALL relays remain outside it, and both are UNCOVERED. An earlier
|
|
302
|
+
* revision of this note gave them opposite verdicts in adjacent paragraphs —
|
|
303
|
+
* clearing one and rejecting the other on the same evidence — which was simply
|
|
304
|
+
* wrong about the first, so both now read the same way:
|
|
305
|
+
*
|
|
306
|
+
* - `cloudfront-server.ts:129`'s `Request handling failed: ${err.message}`.
|
|
307
|
+
* It is what makes `cloudfront-kvs-client.ts`'s throw a DEFAULT-level
|
|
308
|
+
* line, and for THAT sub-path the text arrives pre-sanitized — but it is a
|
|
309
|
+
* catch-all over the whole request pipeline, so an origin fetch, an
|
|
310
|
+
* edge-function invoke or an SDK error raised anywhere below it still
|
|
311
|
+
* reaches the line RAW.
|
|
312
|
+
* - `ecs-service-emulator.ts`'s `logger.error` at three sites (`:909`,
|
|
313
|
+
* `:1508`, `:1599`). One of them does carry
|
|
314
|
+
* `ecs-secrets-resolver.ts`'s `EcsSecretsResolutionError`, which this
|
|
315
|
+
* module sanitized on the way in — and reasoning from that to "the relay
|
|
316
|
+
* is safe" is the SAME generalisation the bullet above rejects. All three
|
|
317
|
+
* are catch-alls over a whole reload or replica roll, relaying
|
|
318
|
+
* `err instanceof Error ? err.message : String(err)`, and what they
|
|
319
|
+
* usually carry is docker CLI stderr, which is multi-line.
|
|
320
|
+
*
|
|
321
|
+
* So: one sub-path arriving pre-sanitized never clears a catch-all. Both need
|
|
322
|
+
* the same per-occurrence call as everything else in this list. Neither is
|
|
323
|
+
* done here — `cloudfront-server.ts` is out of this change's scope, and the
|
|
324
|
+
* emulator's three are a different population (docker output, not SDK errors)
|
|
325
|
+
* that wants the flattening half rather than the withholding half.
|
|
257
326
|
*
|
|
258
327
|
* # The two axes that decide it
|
|
259
328
|
*
|
|
@@ -547,13 +616,27 @@ function sanitizeServiceExceptionMessage(message) {
|
|
|
547
616
|
* echo, plus the clamped {@link clampErrorCode} when the throw carries one.
|
|
548
617
|
*
|
|
549
618
|
* Withholding is NOT free, and the cost lands on cdk-local's own text as well
|
|
550
|
-
* as on the SDK's
|
|
551
|
-
* `AssumeRole(<arn>) returned no usable credentials
|
|
552
|
-
*
|
|
553
|
-
* loss
|
|
554
|
-
* print — is the deny-list this design rejects, wearing the
|
|
555
|
-
*
|
|
556
|
-
*
|
|
619
|
+
* as on the SDK's. The worked example used to be `role-arn.ts`'s
|
|
620
|
+
* `AssumeRole(<arn>) returned no usable credentials.`: a plain `Error` with no
|
|
621
|
+
* `$fault`, so it was withheld like any other, and the note recorded that as a
|
|
622
|
+
* real diagnostic loss accepted because the alternative — an allow-list of
|
|
623
|
+
* messages that may print — is the deny-list this design rejects, wearing the
|
|
624
|
+
* other sign. It also named the right fix: make cdk-local's own throws
|
|
625
|
+
* IDENTIFIABLE rather than guess at their text.
|
|
626
|
+
*
|
|
627
|
+
* Issue #579 did that, for exactly this example. `role-arn.ts` now throws
|
|
628
|
+
* `AssumeRoleFailure`, carrying a `detail` its relaying callers print verbatim
|
|
629
|
+
* — and the same pattern closed the identical loss in
|
|
630
|
+
* `layer-arn-materializer.ts`, `httpv2-service-integration.ts`,
|
|
631
|
+
* `ecr-puller.ts`, `local-run-task.ts` and `ecs-service-emulator.ts`. The
|
|
632
|
+
* general point stands and is what a new site should apply: the policy cannot
|
|
633
|
+
* tell cdk-local's own sentence from a hostile endpoint's, so a `catch` that
|
|
634
|
+
* can see both must make its own throws recognisable BEFORE the relay.
|
|
635
|
+
*
|
|
636
|
+
* The remaining ARN-length question — an ARN is structured, so the bound
|
|
637
|
+
* belongs at the three resolution points rather than at the log line — is
|
|
638
|
+
* tracked separately at
|
|
639
|
+
* https://github.com/go-to-k/cdk-local/issues/607.
|
|
557
640
|
*
|
|
558
641
|
* The LENGTH is reported, unlike in `ecs-secrets-resolver.ts`, and the
|
|
559
642
|
* difference is deliberate: there the user already knows which secret it is
|
|
@@ -622,6 +705,54 @@ function describeAwsFailureForWarn(err, operation) {
|
|
|
622
705
|
//#endregion
|
|
623
706
|
//#region src/utils/role-arn.ts
|
|
624
707
|
/**
|
|
708
|
+
* {@link assumeRoleCredentials} failed, and the failure is cdk-local's OWN
|
|
709
|
+
* rendering of it rather than a raw SDK error.
|
|
710
|
+
*
|
|
711
|
+
* Exists because this helper has TWO kinds of caller and they need opposite
|
|
712
|
+
* things (issue #579 review round 4, found by #570's own harness going red):
|
|
713
|
+
*
|
|
714
|
+
* - THREE paths are unguarded all the way to `formatError`
|
|
715
|
+
* (`local-start-api`'s `assumeLambdaExecutionRole`, and `assumeTaskRole` in
|
|
716
|
+
* both `local-run-task` and `ecs-service-emulator`). They need this helper
|
|
717
|
+
* to render the failure, which is why it no longer propagates unwrapped.
|
|
718
|
+
* - ONE path -- `local-invoke.ts`'s `resolveLambdaContainerEnv` -- ALREADY
|
|
719
|
+
* renders it, guarded by the #570 lane. Wrapping for the first group broke
|
|
720
|
+
* that one: the outer `describeAwsFailureForWarn` saw a plain `Error`
|
|
721
|
+
* (no `$fault`, because it is cdk-local's own), withheld the
|
|
722
|
+
* already-sanitized text, and turned `ExpiredTokenException: The security
|
|
723
|
+
* token ... is expired` into `Error; 138-character message withheld`. The
|
|
724
|
+
* policy withholding its OWN output is the worst of both branches.
|
|
725
|
+
*
|
|
726
|
+
* So `message` carries the framed sentence for the first group, and `detail`
|
|
727
|
+
* carries the bare half for a caller that supplies its own framing. A relay
|
|
728
|
+
* that re-renders `detail` would double-withhold it again; print it verbatim.
|
|
729
|
+
*
|
|
730
|
+
* BOTH of this helper's throws use it (issue #579 review round 5). The
|
|
731
|
+
* no-usable-credentials sibling was left a bare `Error` in round 4 and carried
|
|
732
|
+
* the identical defect one throw over: at `local-invoke.ts` it missed the
|
|
733
|
+
* `instanceof` test and rendered as `Error; 47-character message withheld`.
|
|
734
|
+
* That is the exact loss `credential-error.ts` documents as accepted and
|
|
735
|
+
* points here to fix, so it is fixed here rather than described again.
|
|
736
|
+
*
|
|
737
|
+
* NOT thrown when `makeError` is supplied -- that caller asked for its own
|
|
738
|
+
* class, and every `makeError` path today is in the unguarded group.
|
|
739
|
+
*/
|
|
740
|
+
var AssumeRoleFailure = class AssumeRoleFailure extends Error {
|
|
741
|
+
/**
|
|
742
|
+
* The bare half, with no `AssumeRole(<arn>) ...` framing, safe to print
|
|
743
|
+
* VERBATIM on a line that adds its own. Already policy-rendered when the
|
|
744
|
+
* cause was an SDK error; cdk-local's own literal otherwise. Never re-render
|
|
745
|
+
* it -- that is what double-withholds.
|
|
746
|
+
*/
|
|
747
|
+
detail;
|
|
748
|
+
constructor(message, detail) {
|
|
749
|
+
super(message);
|
|
750
|
+
this.name = "AssumeRoleFailure";
|
|
751
|
+
this.detail = detail;
|
|
752
|
+
Object.setPrototypeOf(this, AssumeRoleFailure.prototype);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
625
756
|
* Issue an STS AssumeRole for `roleArn` and return the temporary
|
|
626
757
|
* credentials (issue #509).
|
|
627
758
|
*
|
|
@@ -635,33 +766,65 @@ function describeAwsFailureForWarn(err, operation) {
|
|
|
635
766
|
*
|
|
636
767
|
* `sessionNameSuffix` distinguishes the callers in CloudTrail
|
|
637
768
|
* (`<resourceNamePrefix>-<suffix>-<epochMs>`). `makeError` lets a
|
|
638
|
-
* caller surface the
|
|
639
|
-
*
|
|
640
|
-
*
|
|
769
|
+
* caller surface the failure as its own error class (the ECS emulator
|
|
770
|
+
* throws `LocalStartServiceError`).
|
|
771
|
+
*
|
|
772
|
+
* CHANGED by issue #579 review round 4: "STS transport errors always
|
|
773
|
+
* propagate unwrapped" is no longer true, and it was the last instance of the
|
|
774
|
+
* `try { send } finally { destroy }` with no `catch` that the derived
|
|
775
|
+
* population found. Propagating unwrapped meant the raw SDK error reached
|
|
776
|
+
* `withErrorHandling` -> `formatError`, which prints
|
|
777
|
+
* `${error.name}: ${error.message}` UNFLATTENED and UNCLAMPED at DEFAULT
|
|
778
|
+
* level — so a `CredentialsProviderError` carrying a `credential_process`
|
|
779
|
+
* command line landed there verbatim, from every `--assume-role` /
|
|
780
|
+
* `--assume-task-role` path in the CLI. This is the file `/review-pr`'s
|
|
781
|
+
* `UP_PATHS` names as credential-material surface, which is exactly why it is
|
|
782
|
+
* the wrong place to leave the relay open.
|
|
783
|
+
*
|
|
784
|
+
* The no-usable-credentials check sits OUTSIDE the `try` rather than being
|
|
785
|
+
* re-raised through an `instanceof` guard, because `makeError` mints the
|
|
786
|
+
* CALLER's class and there is no sentinel this function could test for.
|
|
787
|
+
* `sts.destroy()` still runs first — the `finally` fires on the way out of the
|
|
788
|
+
* `try`, and the response has already been read by then.
|
|
789
|
+
*
|
|
790
|
+
* What that buys is narrower than an earlier revision of this note claimed,
|
|
791
|
+
* and the overclaim is worth correcting rather than deleting: being outside the
|
|
792
|
+
* `try` means this function never routes its own text through the withholding
|
|
793
|
+
* policy, which is what the THREE `formatError` paths need — they print
|
|
794
|
+
* `message` directly. It does NOT help a caller that RE-RENDERS the error,
|
|
795
|
+
* because such a caller tests a class rather than a line number. That is what
|
|
796
|
+
* {@link AssumeRoleFailure} is for, and why BOTH throws below use it.
|
|
641
797
|
*/
|
|
642
798
|
async function assumeRoleCredentials(opts) {
|
|
643
799
|
const sts = new STSClient(buildStsClientConfig({
|
|
644
800
|
region: opts.region,
|
|
645
801
|
profile: opts.profile
|
|
646
802
|
}));
|
|
803
|
+
const shownArn = flattenToOneLine(opts.roleArn);
|
|
804
|
+
let response;
|
|
647
805
|
try {
|
|
648
|
-
|
|
806
|
+
response = await sts.send(new AssumeRoleCommand({
|
|
649
807
|
RoleArn: opts.roleArn,
|
|
650
808
|
RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-${opts.sessionNameSuffix}-${Date.now()}`,
|
|
651
809
|
DurationSeconds: 3600
|
|
652
|
-
}))
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
return {
|
|
658
|
-
accessKeyId: creds.AccessKeyId,
|
|
659
|
-
secretAccessKey: creds.SecretAccessKey,
|
|
660
|
-
sessionToken: creds.SessionToken
|
|
661
|
-
};
|
|
810
|
+
}));
|
|
811
|
+
} catch (err) {
|
|
812
|
+
const detail = describeAwsFailureForWarn(err, "STS AssumeRole");
|
|
813
|
+
const message = `AssumeRole(${shownArn}) failed: ${detail}`;
|
|
814
|
+
throw opts.makeError ? opts.makeError(message) : new AssumeRoleFailure(message, detail);
|
|
662
815
|
} finally {
|
|
663
816
|
sts.destroy();
|
|
664
817
|
}
|
|
818
|
+
const creds = response.Credentials;
|
|
819
|
+
if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) {
|
|
820
|
+
const message = `AssumeRole(${shownArn}) returned no usable credentials.`;
|
|
821
|
+
throw opts.makeError ? opts.makeError(message) : new AssumeRoleFailure(message, "the response carried no usable credentials");
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
accessKeyId: creds.AccessKeyId,
|
|
825
|
+
secretAccessKey: creds.SecretAccessKey,
|
|
826
|
+
sessionToken: creds.SessionToken
|
|
827
|
+
};
|
|
665
828
|
}
|
|
666
829
|
/**
|
|
667
830
|
* Resolve the role-arn argument (CLI flag or `CDKL_ROLE_ARN` env var) and,
|
|
@@ -692,27 +855,31 @@ async function applyRoleArnIfSet(opts) {
|
|
|
692
855
|
const roleArn = opts.roleArn || process.env[`${getEmbedConfig().envPrefix}_ROLE_ARN`];
|
|
693
856
|
if (!roleArn) return;
|
|
694
857
|
const logger = getLogger().child("role-arn");
|
|
695
|
-
|
|
858
|
+
const shownArn = flattenToOneLine(roleArn);
|
|
859
|
+
logger.debug(`Assuming role ${shownArn}...`);
|
|
696
860
|
const sts = new STSClient(buildStsClientConfig({
|
|
697
861
|
region: opts.region,
|
|
698
862
|
profile: opts.profile
|
|
699
863
|
}));
|
|
864
|
+
let response;
|
|
700
865
|
try {
|
|
701
|
-
|
|
866
|
+
response = await sts.send(new AssumeRoleCommand({
|
|
702
867
|
RoleArn: roleArn,
|
|
703
868
|
RoleSessionName: `${getEmbedConfig().binaryName}-${Date.now()}`,
|
|
704
869
|
DurationSeconds: 3600
|
|
705
870
|
}));
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing credentials fields for role ${roleArn}`);
|
|
709
|
-
process.env["AWS_ACCESS_KEY_ID"] = AccessKeyId;
|
|
710
|
-
process.env["AWS_SECRET_ACCESS_KEY"] = SecretAccessKey;
|
|
711
|
-
process.env["AWS_SESSION_TOKEN"] = SessionToken;
|
|
712
|
-
logger.info(`Assumed role ${roleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
871
|
+
} catch (err) {
|
|
872
|
+
throw new Error(`AssumeRole(${shownArn}) failed: ${describeAwsFailureForWarn(err, "STS AssumeRole")}`);
|
|
713
873
|
} finally {
|
|
714
874
|
sts.destroy();
|
|
715
875
|
}
|
|
876
|
+
if (!response.Credentials) throw new Error(`AssumeRole returned no credentials for role ${shownArn}`);
|
|
877
|
+
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
878
|
+
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing credentials fields for role ${shownArn}`);
|
|
879
|
+
process.env["AWS_ACCESS_KEY_ID"] = AccessKeyId;
|
|
880
|
+
process.env["AWS_SECRET_ACCESS_KEY"] = SecretAccessKey;
|
|
881
|
+
process.env["AWS_SESSION_TOKEN"] = SessionToken;
|
|
882
|
+
logger.info(`Assumed role ${shownArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
716
883
|
}
|
|
717
884
|
|
|
718
885
|
//#endregion
|
|
@@ -2674,13 +2841,35 @@ async function dispatchSfnStopExecution(params, region) {
|
|
|
2674
2841
|
async function dispatchAppConfigGetConfiguration(params, region) {
|
|
2675
2842
|
return errorResponse(501, `AppConfig-GetConfiguration is recognized but ${getEmbedConfig().productName} does not yet bundle @aws-sdk/client-appconfig. Use the deployed API for this subtype, or open an issue if you need local emulation.`);
|
|
2676
2843
|
}
|
|
2844
|
+
/**
|
|
2845
|
+
* A REQUEST-shape rejection raised by cdk-local itself, before any AWS call.
|
|
2846
|
+
*
|
|
2847
|
+
* Issue #579 — it needs its own class so {@link translateSdkError} can tell it
|
|
2848
|
+
* apart from a relayed SDK failure. Its message is cdk-local's own literal and
|
|
2849
|
+
* names the missing parameter, so it is the diagnosis and must reach the
|
|
2850
|
+
* client verbatim; the credential-error policy withholds every non-service
|
|
2851
|
+
* exception, which would otherwise have turned this 400 into a class name and
|
|
2852
|
+
* a character count. `credential-error.ts` names exactly this remedy: make
|
|
2853
|
+
* cdk-local's own throws identifiable rather than guess at their text.
|
|
2854
|
+
*
|
|
2855
|
+
* The ONE remaining cdk-local-authored throw inside the guarded `try` is
|
|
2856
|
+
* `getClient`'s `unknown service '<x>'`, and it is left withheld deliberately:
|
|
2857
|
+
* its `switch` is exhaustive over the dispatched subtypes, so the arm is dead
|
|
2858
|
+
* code guarding against a future subtype added without a client case. If it
|
|
2859
|
+
* ever does fire, the developer who added that subtype reads the full text on
|
|
2860
|
+
* the `debug` line the helper emits.
|
|
2861
|
+
*/
|
|
2862
|
+
var ServiceIntegrationRequestError = class ServiceIntegrationRequestError extends Error {
|
|
2863
|
+
statusCode = 400;
|
|
2864
|
+
constructor(message) {
|
|
2865
|
+
super(message);
|
|
2866
|
+
this.name = "ServiceIntegrationRequestError";
|
|
2867
|
+
Object.setPrototypeOf(this, ServiceIntegrationRequestError.prototype);
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2677
2870
|
function requireParams(params, required) {
|
|
2678
2871
|
const missing = required.filter((k) => !params[k] || params[k].trim() === "");
|
|
2679
|
-
if (missing.length > 0) {
|
|
2680
|
-
const err = /* @__PURE__ */ new Error(`missing required RequestParameter(s): ${missing.join(", ")}`);
|
|
2681
|
-
err.statusCode = 400;
|
|
2682
|
-
throw err;
|
|
2683
|
-
}
|
|
2872
|
+
if (missing.length > 0) throw new ServiceIntegrationRequestError(`missing required RequestParameter(s): ${missing.join(", ")}`);
|
|
2684
2873
|
}
|
|
2685
2874
|
function splitCsv(value) {
|
|
2686
2875
|
return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -2726,14 +2915,38 @@ function errorResponse(statusCode, message) {
|
|
|
2726
2915
|
* Translate an AWS SDK error to an HTTP response. AWS SDK v3 surfaces
|
|
2727
2916
|
* errors as instances carrying `$metadata.httpStatusCode` + `name`;
|
|
2728
2917
|
* we honor the status code when present, default to 500.
|
|
2729
|
-
|
|
2918
|
+
*
|
|
2919
|
+
* Issue #579 routed the two relayed fields through the credential-error
|
|
2920
|
+
* policy. LEVEL: this one is NOT a log line — it is a RESPONSE BODY served on
|
|
2921
|
+
* the local API port, which makes it the widest reader of the set: the studio
|
|
2922
|
+
* capture proxy records a served response body onto the timeline, so the text
|
|
2923
|
+
* reaches the same browser the log ring does, and any HTTP client reaches it
|
|
2924
|
+
* without `--verbose`. RECONSTRUCTION: the `catch` wraps the per-subtype
|
|
2925
|
+
* dispatch, i.e. one `client.send(...)`, so a `CredentialsProviderError`
|
|
2926
|
+
* carrying a `credential_process` command line would have been JSON-encoded
|
|
2927
|
+
* into that body verbatim. A modeled service exception's message is what a
|
|
2928
|
+
* real API Gateway service integration surfaces and is the diagnosis, so it
|
|
2929
|
+
* keeps printing; `code` is `err.name`, wire-derived, and is clamped.
|
|
2930
|
+
*/
|
|
2931
|
+
/** A value usable as an HTTP status line without raising `ERR_HTTP_INVALID_STATUS_CODE`. */
|
|
2932
|
+
function isHttpStatus(value) {
|
|
2933
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 100 && value < 600;
|
|
2934
|
+
}
|
|
2730
2935
|
function translateSdkError(subtype, err) {
|
|
2936
|
+
if (err instanceof ServiceIntegrationRequestError) return {
|
|
2937
|
+
statusCode: err.statusCode,
|
|
2938
|
+
body: JSON.stringify({
|
|
2939
|
+
message: err.message,
|
|
2940
|
+
code: err.name
|
|
2941
|
+
}),
|
|
2942
|
+
headers: { "content-type": "application/json" }
|
|
2943
|
+
};
|
|
2731
2944
|
if (err && typeof err === "object") {
|
|
2732
2945
|
const e = err;
|
|
2733
|
-
const status =
|
|
2946
|
+
const status = isHttpStatus(e.statusCode) ? e.statusCode : isHttpStatus(e.$metadata?.httpStatusCode) ? e.$metadata.httpStatusCode : 500;
|
|
2734
2947
|
const body = {
|
|
2735
|
-
message: e.message
|
|
2736
|
-
code:
|
|
2948
|
+
message: isAwsServiceException(err) ? sanitizeServiceExceptionMessage(typeof e.message === "string" ? e.message : stringifyThrown(err)) : describeAwsFailureForWarn(err, `${subtype} service integration`),
|
|
2949
|
+
code: clampErrorName(err)
|
|
2737
2950
|
};
|
|
2738
2951
|
logger.debug(`[${subtype}] SDK error (${status}): ${stringifyValue(body)}`);
|
|
2739
2952
|
return {
|
|
@@ -2742,7 +2955,7 @@ function translateSdkError(subtype, err) {
|
|
|
2742
2955
|
headers: { "content-type": "application/json" }
|
|
2743
2956
|
};
|
|
2744
2957
|
}
|
|
2745
|
-
return errorResponse(500, `Unexpected error invoking ${subtype}: ${
|
|
2958
|
+
return errorResponse(500, `Unexpected error invoking ${subtype}: ${describeAwsFailureForWarn(err, `${subtype} service integration`)}`);
|
|
2746
2959
|
}
|
|
2747
2960
|
/**
|
|
2748
2961
|
* Apply HTTP API v2 `ResponseParameters` mapping (per AWS docs:
|
|
@@ -4709,7 +4922,7 @@ async function resolveSsmParameters(client, refs, label) {
|
|
|
4709
4922
|
const invalid = resp.InvalidParameters ?? [];
|
|
4710
4923
|
if (invalid.length > 0) logger.warn(`${label}: SSM GetParameters reported invalid parameter name(s): ${invalid.join(", ")}. Ref to the matching CloudFormation parameter(s) will warn-and-drop (was the SSM parameter created?).`);
|
|
4711
4924
|
} catch (err) {
|
|
4712
|
-
logger.warn(`${label}: SSM GetParameters(${names.join(", ")}) failed: ${
|
|
4925
|
+
logger.warn(`${label}: SSM GetParameters(${flattenToOneLine(names.join(", "))}) failed: ${describeAwsFailureForWarn(err, "SSM GetParameters")}. Ref to the matching CloudFormation parameter(s) will warn-and-drop (grant ssm:GetParameters or override via --env-vars).`);
|
|
4713
4926
|
continue;
|
|
4714
4927
|
}
|
|
4715
4928
|
for (const p of resolved) {
|
|
@@ -4725,17 +4938,6 @@ async function resolveSsmParameters(client, refs, label) {
|
|
|
4725
4938
|
}
|
|
4726
4939
|
return out;
|
|
4727
4940
|
}
|
|
4728
|
-
/**
|
|
4729
|
-
* Format an SSM SDK error as `<name>: <message>` so the warn names the
|
|
4730
|
-
* error class (e.g. `AccessDeniedException`, `ThrottlingException`).
|
|
4731
|
-
* Mirrors `formatAwsErrorForWarn` in `cfn-local-state-provider.ts`.
|
|
4732
|
-
* Exported for unit testing.
|
|
4733
|
-
*/
|
|
4734
|
-
function formatSsmError(err) {
|
|
4735
|
-
if (!(err instanceof Error)) return String(err);
|
|
4736
|
-
const name = err.name && err.name !== "Error" ? err.name : void 0;
|
|
4737
|
-
return name !== void 0 ? `${name}: ${err.message}` : err.message;
|
|
4738
|
-
}
|
|
4739
4941
|
|
|
4740
4942
|
//#endregion
|
|
4741
4943
|
//#region src/local/cfn-local-state-provider.ts
|
|
@@ -4890,7 +5092,7 @@ var CfnLocalStateProvider = class {
|
|
|
4890
5092
|
try {
|
|
4891
5093
|
return (await client.send(new GetFunctionConfigurationCommand({ FunctionName: functionPhysicalId }))).Environment?.Variables ?? {};
|
|
4892
5094
|
} catch (err) {
|
|
4893
|
-
logger.warn(`${this.label}: GetFunctionConfiguration(${functionPhysicalId}) failed: ${formatAwsErrorForWarn(err)}. Intrinsic-valued env vars that need the deployed value will warn-and-drop (grant lambda:GetFunctionConfiguration or override via --env-vars).`);
|
|
5095
|
+
logger.warn(`${this.label}: GetFunctionConfiguration(${flattenToOneLine(functionPhysicalId)}) failed: ${formatAwsErrorForWarn(err, "Lambda GetFunctionConfiguration")}. Intrinsic-valued env vars that need the deployed value will warn-and-drop (grant lambda:GetFunctionConfiguration or override via --env-vars).`);
|
|
4894
5096
|
return;
|
|
4895
5097
|
}
|
|
4896
5098
|
}
|
|
@@ -4913,7 +5115,7 @@ var CfnLocalStateProvider = class {
|
|
|
4913
5115
|
if (typeof resp.Role === "string" && resp.Role.startsWith("arn:")) return resp.Role;
|
|
4914
5116
|
return;
|
|
4915
5117
|
} catch (err) {
|
|
4916
|
-
logger.warn(`${this.label}: GetFunctionConfiguration(${functionPhysicalId}) for --assume-role auto-resolve failed: ${formatAwsErrorForWarn(err)}. Pass the ARN explicitly: --assume-role <arn>.`);
|
|
5118
|
+
logger.warn(`${this.label}: GetFunctionConfiguration(${flattenToOneLine(functionPhysicalId)}) for --assume-role auto-resolve failed: ${formatAwsErrorForWarn(err, "Lambda GetFunctionConfiguration (--assume-role auto-resolve)")}. Pass the ARN explicitly: --assume-role <arn>.`);
|
|
4917
5119
|
return;
|
|
4918
5120
|
}
|
|
4919
5121
|
}
|
|
@@ -4936,7 +5138,7 @@ var CfnLocalStateProvider = class {
|
|
|
4936
5138
|
if (typeof resp.roleArn === "string" && resp.roleArn.startsWith("arn:")) return resp.roleArn;
|
|
4937
5139
|
return;
|
|
4938
5140
|
} catch (err) {
|
|
4939
|
-
logger.warn(`${this.label}: GetAgentRuntime(${runtimePhysicalId}) for --assume-role auto-resolve failed: ${formatAwsErrorForWarn(err)}. Pass the ARN explicitly: --assume-role <arn>.`);
|
|
5141
|
+
logger.warn(`${this.label}: GetAgentRuntime(${flattenToOneLine(runtimePhysicalId)}) for --assume-role auto-resolve failed: ${formatAwsErrorForWarn(err, "bedrock-agentcore-control GetAgentRuntime (--assume-role auto-resolve)")}. Pass the ARN explicitly: --assume-role <arn>.`);
|
|
4940
5142
|
return;
|
|
4941
5143
|
}
|
|
4942
5144
|
}
|
|
@@ -4961,8 +5163,9 @@ var CfnLocalStateProvider = class {
|
|
|
4961
5163
|
try {
|
|
4962
5164
|
resourceMap = buildResourceStateMap(await fetchAllStackResources(client, this.cfnStackName));
|
|
4963
5165
|
} catch (err) {
|
|
4964
|
-
|
|
4965
|
-
|
|
5166
|
+
const detail = formatAwsErrorForWarn(err, "CloudFormation ListStackResources");
|
|
5167
|
+
this.lastLoadError = `ListStackResources(${this.cfnStackName}) failed: ${detail} (region='${this.region}')`;
|
|
5168
|
+
logger.warn(`${this.label}: ListStackResources(${this.cfnStackName}) failed: ${detail}. Was the stack deployed in region '${this.region}'? Falling back.`);
|
|
4966
5169
|
return;
|
|
4967
5170
|
}
|
|
4968
5171
|
let outputs;
|
|
@@ -4973,7 +5176,7 @@ var CfnLocalStateProvider = class {
|
|
|
4973
5176
|
outputs = {};
|
|
4974
5177
|
} else outputs = buildOutputsMap(stack.Outputs ?? []);
|
|
4975
5178
|
} catch (err) {
|
|
4976
|
-
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Outputs will be empty (Fn::GetStackOutput cannot resolve).`);
|
|
5179
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err, "CloudFormation DescribeStacks")}. Outputs will be empty (Fn::GetStackOutput cannot resolve).`);
|
|
4977
5180
|
outputs = {};
|
|
4978
5181
|
}
|
|
4979
5182
|
return {
|
|
@@ -5023,7 +5226,7 @@ var CfnLocalStateProvider = class {
|
|
|
5023
5226
|
const ensureExports = () => {
|
|
5024
5227
|
if (exportsPromise) return exportsPromise;
|
|
5025
5228
|
exportsPromise = fetchAllExports(client).catch((err) => {
|
|
5026
|
-
logger.warn(`${label}: ListExports (${region}) failed: ${formatAwsErrorForWarn(err)}. Fn::ImportValue intrinsics will warn-and-drop.`);
|
|
5229
|
+
logger.warn(`${label}: ListExports (${region}) failed: ${formatAwsErrorForWarn(err, "CloudFormation ListExports")}. Fn::ImportValue intrinsics will warn-and-drop.`);
|
|
5027
5230
|
});
|
|
5028
5231
|
return exportsPromise;
|
|
5029
5232
|
};
|
|
@@ -5163,24 +5366,36 @@ async function fetchAllExports(client) {
|
|
|
5163
5366
|
* with bucket / region context), so the CFn provider extracts the
|
|
5164
5367
|
* pieces directly here.
|
|
5165
5368
|
*
|
|
5166
|
-
* Issue #578
|
|
5167
|
-
*
|
|
5168
|
-
*
|
|
5169
|
-
*
|
|
5170
|
-
*
|
|
5171
|
-
*
|
|
5172
|
-
*
|
|
5173
|
-
*
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5369
|
+
* Issue #578 flattened the result to ONE LINE, because this warn is relayed
|
|
5370
|
+
* onto a `cdkl studio` serve child's stdout where `studio-serve-manager`
|
|
5371
|
+
* splits on `\n`: an embedded newline puts line 2 on the stream with no
|
|
5372
|
+
* `WARN: ` prefix, so it clears the diagnostic bound and matches a ready
|
|
5373
|
+
* pattern at `^`.
|
|
5374
|
+
*
|
|
5375
|
+
* Issue #579 finished the job. Flattening closed the forged-LINE half while
|
|
5376
|
+
* leaving the OTHER two open: the wire-derived `err.name` was interpolated
|
|
5377
|
+
* UNCLAMPED (`@aws-sdk/core` builds it from `x-amzn-errortype` with no length
|
|
5378
|
+
* cap and no character class), and `err.message` printed verbatim whatever
|
|
5379
|
+
* population it came from. LEVEL: every caller below is a DEFAULT-level warn.
|
|
5380
|
+
* RECONSTRUCTION: each `catch` wraps a `client.send(...)` — CloudFormation,
|
|
5381
|
+
* Lambda, or bedrock-agentcore-control — which resolves the credential chain
|
|
5382
|
+
* before the request goes out, so the `catch` sees both a
|
|
5383
|
+
* `CredentialsProviderError` (whose message can be a `credential_process`
|
|
5384
|
+
* command line) and a modeled service exception whose message IS the
|
|
5385
|
+
* diagnosis. That is exactly {@link describeAwsFailureForWarn}'s split, so
|
|
5386
|
+
* this is now a thin decoration over it rather than a second spelling of the
|
|
5387
|
+
* policy: `operation` names the call for the `debug` line the helper emits on
|
|
5388
|
+
* the withheld branch.
|
|
5389
|
+
*
|
|
5390
|
+
* The HTTP status is KEPT and moved to a suffix. Unlike the name it is not
|
|
5391
|
+
* body-derived — the SDK reads it off the HTTP response line — but it is
|
|
5392
|
+
* still range-guarded to an integer status, since `$metadata` is a plain
|
|
5393
|
+
* object a hostile shape could carry anything on.
|
|
5394
|
+
*/
|
|
5395
|
+
function formatAwsErrorForWarn(err, operation) {
|
|
5396
|
+
const described = describeAwsFailureForWarn(err, operation);
|
|
5397
|
+
const status = err?.$metadata?.httpStatusCode;
|
|
5398
|
+
return typeof status === "number" && Number.isInteger(status) && status >= 100 && status < 600 ? `${described} (HTTP ${status})` : described;
|
|
5184
5399
|
}
|
|
5185
5400
|
|
|
5186
5401
|
//#endregion
|
|
@@ -5398,6 +5613,109 @@ function toFlagName(field) {
|
|
|
5398
5613
|
//#endregion
|
|
5399
5614
|
//#region src/local/state-resolver.ts
|
|
5400
5615
|
/**
|
|
5616
|
+
* State-driven env-var resolution for `cdkl invoke --from-state`.
|
|
5617
|
+
*
|
|
5618
|
+
* The PR 1 env-resolver classifies any non-literal env-var value (a CFn
|
|
5619
|
+
* intrinsic like `Ref` / `Fn::GetAtt` / `Fn::Sub`) as "unresolved" and
|
|
5620
|
+
* drops it. That's correct when there's no source of truth for the
|
|
5621
|
+
* deployed value — which is also the SAM behavior — but it's wrong when
|
|
5622
|
+
* cdk-local has already deployed the stack and the AWS-current physical IDs
|
|
5623
|
+
* sit in the cdk-local state file.
|
|
5624
|
+
*
|
|
5625
|
+
* `--from-state` closes that gap: it loads the host's S3 state for the target
|
|
5626
|
+
* stack and substitutes `Ref` / `Fn::GetAtt` / `Fn::Sub` placeholders in
|
|
5627
|
+
* the function's `Properties.Environment.Variables` with the deployed
|
|
5628
|
+
* values. The result feeds back into the existing
|
|
5629
|
+
* `resolveEnvVars(...)` pipeline so `--env-vars` overrides still take
|
|
5630
|
+
* precedence — that ordering matters because users routinely override a
|
|
5631
|
+
* single variable while using `--from-state` to recover the rest.
|
|
5632
|
+
*
|
|
5633
|
+
* Scope:
|
|
5634
|
+
*
|
|
5635
|
+
* - `Ref: <LogicalId>` — substituted with `state.resources[id].physicalId`.
|
|
5636
|
+
* - `Fn::GetAtt: [<LogicalId>, <attr>]` (and the `"LogicalId.attr"`
|
|
5637
|
+
* string form) — substituted with
|
|
5638
|
+
* `state.resources[id].attributes[attr]`. We deliberately do NOT
|
|
5639
|
+
* synthesize attributes the provider would normally compute (e.g.
|
|
5640
|
+
* IAM role ARNs derived from physicalId + accountId) — `--from-state`
|
|
5641
|
+
* surfaces only what cdk-local recorded at deploy time, which is what the
|
|
5642
|
+
* deployed Lambda's env actually saw.
|
|
5643
|
+
* - `Fn::Sub: '<template>'` (and the two-argument `[template, vars]`
|
|
5644
|
+
* form) — `${LogicalId}` / `${LogicalId.attr}` placeholders are
|
|
5645
|
+
* substituted in place; pseudo parameters (`${AWS::AccountId}` /
|
|
5646
|
+
* `${AWS::Region}` / `${AWS::Partition}` / `${AWS::URLSuffix}`) are
|
|
5647
|
+
* substituted from the optional `pseudoParameters` bag; unrelated
|
|
5648
|
+
* placeholders (mapping references, parameters) are left untouched
|
|
5649
|
+
* and the value is treated as unresolved.
|
|
5650
|
+
* - `Fn::Join: [<delimiter>, [<elements>...]]` — every element is
|
|
5651
|
+
* recursively resolved via this same module and joined with the
|
|
5652
|
+
* delimiter. Closes Gap 1 of issue #286 (the SSM Parameter
|
|
5653
|
+
* `ecs.Secret.fromSsmParameter` shape CDK synthesizes is
|
|
5654
|
+
* `Fn::Join` with pseudo-parameter `Ref`s + a `Ref` to the
|
|
5655
|
+
* parameter; without Fn::Join support the secret silently drops).
|
|
5656
|
+
* - `Fn::Select: [<index>, <list>]` — picks the indexed element of an
|
|
5657
|
+
* array. Index may be a number or a numeric string (CFn templates
|
|
5658
|
+
* often carry the string form). The list argument may be a literal
|
|
5659
|
+
* array of intrinsics OR an intrinsic that resolves to a
|
|
5660
|
+
* `string[]` (today: only `Fn::Split`). Out-of-bounds / negative /
|
|
5661
|
+
* non-finite index reports unresolved. Closes issue #636 — the
|
|
5662
|
+
* `Fn.select(N, Fn.split(':', secret.secretArn))` shape CDK
|
|
5663
|
+
* synthesizes for parsing secret ARN segments.
|
|
5664
|
+
* - `Fn::Split: [<delimiter>, <string>]` — splits a string into a
|
|
5665
|
+
* `string[]`. **Only valid INSIDE `Fn::Select`** — at the env-var
|
|
5666
|
+
* top level the result is an array which can't be an env-var
|
|
5667
|
+
* value, so the top-level dispatcher reports unresolved with a
|
|
5668
|
+
* clear reason.
|
|
5669
|
+
* - `Ref: AWS::AccountId` / `AWS::Region` / `AWS::Partition` /
|
|
5670
|
+
* `AWS::URLSuffix` — substituted from the optional
|
|
5671
|
+
* `pseudoParameters` bag the caller supplies. When the bag is
|
|
5672
|
+
* missing (or the specific key isn't set), the placeholder reports
|
|
5673
|
+
* unresolved — same warn-and-drop policy as every other miss.
|
|
5674
|
+
*
|
|
5675
|
+
* Cross-stack intrinsics (when the caller supplies a `crossStackResolver`
|
|
5676
|
+
* on the `SubstitutionContext` via the async API):
|
|
5677
|
+
*
|
|
5678
|
+
* - `Fn::ImportValue: '<exportName>'` (or an intrinsic-valued argument
|
|
5679
|
+
* that resolves to a string against state + pseudo parameters) —
|
|
5680
|
+
* looked up via `crossStackResolver.resolveImport(exportName)`. The
|
|
5681
|
+
* resolver typically reads the host's persistent exports index,
|
|
5682
|
+
* falling back to a per-stack state.json scan on index miss
|
|
5683
|
+
* (closes issue #454).
|
|
5684
|
+
* - `Fn::GetStackOutput: { StackName, OutputName, Region? }` — looked
|
|
5685
|
+
* up via `crossStackResolver.resolveGetStackOutput(stackName, region,
|
|
5686
|
+
* outputName)`. The resolver typically reads the producer stack's
|
|
5687
|
+
* state.json from S3 directly. `Region` defaults to the consumer's
|
|
5688
|
+
* deploy region when omitted.
|
|
5689
|
+
*
|
|
5690
|
+
* Both resolvers return `string | undefined`; an `undefined` value
|
|
5691
|
+
* reports unresolved per the standard warn-and-drop policy. Cross-account
|
|
5692
|
+
* `Fn::GetStackOutput.RoleArn` is rejected at the resolver layer (cdk-local
|
|
5693
|
+
* uses S3 state, not CloudFormation; cross-account would require
|
|
5694
|
+
* assuming the role and reading the producer account's separate state
|
|
5695
|
+
* bucket — tracked under #449).
|
|
5696
|
+
*
|
|
5697
|
+
* Only the async API (`substituteAgainstStateAsync` /
|
|
5698
|
+
* `substituteEnvVarsFromStateAsync`) handles these. The legacy sync API
|
|
5699
|
+
* surfaces them as `unresolved` ("unsupported intrinsic") so existing
|
|
5700
|
+
* callers (e.g. `ecs-task-resolver.ts`'s sync container parsing) stay
|
|
5701
|
+
* unchanged and the per-key warn-and-drop UX still fires.
|
|
5702
|
+
*
|
|
5703
|
+
* Out of scope (deferred):
|
|
5704
|
+
*
|
|
5705
|
+
* - Cross-region `Fn::ImportValue` (tracked under #451).
|
|
5706
|
+
* - Cross-account `Fn::GetStackOutput.RoleArn` (tracked under #449).
|
|
5707
|
+
* - Other intrinsics (`Fn::If`, `Fn::FindInMap`, etc.). Anything
|
|
5708
|
+
* beyond the supported set is reported as unresolved and the env
|
|
5709
|
+
* var is dropped, matching PR 1's "warn-and-drop" semantics.
|
|
5710
|
+
*
|
|
5711
|
+
* Failure mode: per-key best-effort. When a substitution can't be
|
|
5712
|
+
* produced (state missing for the referenced logical ID, attribute not
|
|
5713
|
+
* captured at deploy time, unsupported intrinsic in `Fn::Sub`), the key
|
|
5714
|
+
* is reported as unresolved and the caller drops it from the env block
|
|
5715
|
+
* with a warn. We never throw out of substitution — a bad reference in
|
|
5716
|
+
* one env var must not abort the whole `cdkl invoke` call.
|
|
5717
|
+
*/
|
|
5718
|
+
/**
|
|
5401
5719
|
* Substitute a single env-var / secret-ValueFrom value (which may be a
|
|
5402
5720
|
* CFn intrinsic) against the provided state-recorded resources map and
|
|
5403
5721
|
* optional pseudo-parameter bag.
|
|
@@ -5842,7 +6160,7 @@ async function resolveImportValueAsync(arg, context) {
|
|
|
5842
6160
|
} catch (err) {
|
|
5843
6161
|
return {
|
|
5844
6162
|
kind: "unresolved",
|
|
5845
|
-
reason: `Fn::ImportValue '${exportName}': lookup failed: ${err
|
|
6163
|
+
reason: `Fn::ImportValue '${flattenToOneLine(exportName)}': lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveImport (Fn::ImportValue)")}`
|
|
5846
6164
|
};
|
|
5847
6165
|
}
|
|
5848
6166
|
if (resolved === void 0) return {
|
|
@@ -5917,7 +6235,7 @@ async function resolveGetStackOutputAsync(arg, context) {
|
|
|
5917
6235
|
} catch (err) {
|
|
5918
6236
|
return {
|
|
5919
6237
|
kind: "unresolved",
|
|
5920
|
-
reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): lookup failed: ${err
|
|
6238
|
+
reason: `Fn::GetStackOutput '${flattenToOneLine(stackName)}.${flattenToOneLine(outputName)}' (${flattenToOneLine(region)}): lookup failed: ${describeAwsFailureForWarn(err, "CrossStackResolver.resolveGetStackOutput (Fn::GetStackOutput)")}`
|
|
5921
6239
|
};
|
|
5922
6240
|
}
|
|
5923
6241
|
if (resolved === void 0) return {
|
|
@@ -7406,6 +7724,9 @@ async function pullEcrImage(imageUri, options) {
|
|
|
7406
7724
|
if (!identity.Account) throw new LocalInvokeBuildError("STS GetCallerIdentity returned no Account. Verify your AWS credentials.");
|
|
7407
7725
|
callerAccount = identity.Account;
|
|
7408
7726
|
CALLER_IDENTITY_CACHE.set(callerIdentityKey, callerAccount);
|
|
7727
|
+
} catch (err) {
|
|
7728
|
+
if (err instanceof LocalInvokeBuildError) throw err;
|
|
7729
|
+
throw new LocalInvokeBuildError(`STS GetCallerIdentity failed while preparing the ECR pull: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity (ECR pull)")}. Verify your AWS credentials.`);
|
|
7409
7730
|
} finally {
|
|
7410
7731
|
sts.destroy();
|
|
7411
7732
|
}
|
|
@@ -7471,7 +7792,8 @@ async function assumeRoleForEcr(roleArn, callerRegion, profile, logger) {
|
|
|
7471
7792
|
};
|
|
7472
7793
|
} catch (err) {
|
|
7473
7794
|
if (err instanceof LocalInvokeBuildError) throw err;
|
|
7474
|
-
|
|
7795
|
+
const reason = describeAwsFailureForWarn(err, "STS AssumeRole (ECR pull)");
|
|
7796
|
+
throw new LocalInvokeBuildError(`Failed to assume role ${flattenToOneLine(roleArn)} for ECR pull: ${reason}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
|
|
7475
7797
|
} finally {
|
|
7476
7798
|
sts.destroy();
|
|
7477
7799
|
}
|
|
@@ -7483,7 +7805,9 @@ async function assumeRoleForEcr(roleArn, callerRegion, profile, logger) {
|
|
|
7483
7805
|
*/
|
|
7484
7806
|
async function ecrLogin(client, accountId, region) {
|
|
7485
7807
|
getLogger().child("ecr-puller").debug(`ECR login (account=${accountId}, region=${region})`);
|
|
7486
|
-
const authData = (await client.send(new GetAuthorizationTokenCommand({})))
|
|
7808
|
+
const authData = (await client.send(new GetAuthorizationTokenCommand({})).catch((err) => {
|
|
7809
|
+
throw new LocalInvokeBuildError(`ECR GetAuthorizationToken failed (account=${accountId}, region=${region}): ${describeAwsFailureForWarn(err, "ECR GetAuthorizationToken")}. Verify the credentials can call ecr:GetAuthorizationToken.`);
|
|
7810
|
+
})).authorizationData?.[0];
|
|
7487
7811
|
if (!authData?.authorizationToken) throw new LocalInvokeBuildError("Failed to get ECR authorization token");
|
|
7488
7812
|
const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
|
|
7489
7813
|
if (!username || password === void 0) throw new LocalInvokeBuildError("ECR authorization token has unexpected shape (missing username/password)");
|
|
@@ -15942,6 +16266,45 @@ var LayerMaterializationError = class LayerMaterializationError extends Error {
|
|
|
15942
16266
|
Object.setPrototypeOf(this, LayerMaterializationError.prototype);
|
|
15943
16267
|
}
|
|
15944
16268
|
};
|
|
16269
|
+
/**
|
|
16270
|
+
* cdk-local's OWN rejection raised INSIDE a guarded region, still needing the
|
|
16271
|
+
* call site's framing (issue #579, review round 2).
|
|
16272
|
+
*
|
|
16273
|
+
* Two classes are needed, not one, because the guarded regions contain BOTH
|
|
16274
|
+
* kinds of cdk-local throw and they want opposite handling:
|
|
16275
|
+
*
|
|
16276
|
+
* - ALREADY FRAMED — the ARN-shape guard raises
|
|
16277
|
+
* `Layer <arn>: not a layer-version ARN ...`, which names the layer and the
|
|
16278
|
+
* problem and fires before any AWS call. Re-raising it INTACT is right;
|
|
16279
|
+
* wrapping it would double the `Layer <arn>:` prefix and, worse, prepend
|
|
16280
|
+
* `GetLayerVersion failed` to a failure where no call was ever made.
|
|
16281
|
+
* - UNFRAMED — `AssumeRole returned no Credentials` and
|
|
16282
|
+
* `GetLayerVersion response did not include Content.Location` are bare
|
|
16283
|
+
* sentences that mean nothing without the site's `Layer <arn>: <call>
|
|
16284
|
+
* failed: ... <remedy>` envelope.
|
|
16285
|
+
*
|
|
16286
|
+
* A first pass at #579 made both `LayerMaterializationError` and re-raised
|
|
16287
|
+
* both, which silently DROPPED the envelope (and the `looksLikeAccessDenied`
|
|
16288
|
+
* hint) from the second group. The existing tests only substring-match the
|
|
16289
|
+
* inner sentence, so nothing went red. Hence a distinct type rather than a
|
|
16290
|
+
* shared one: the catch keeps the message verbatim — it is cdk-local's own
|
|
16291
|
+
* text, never anything off the wire, so the credential-error policy has
|
|
16292
|
+
* nothing to decide about it — and still applies the framing.
|
|
16293
|
+
*/
|
|
16294
|
+
var UnframedLayerError = class UnframedLayerError extends Error {
|
|
16295
|
+
constructor(message) {
|
|
16296
|
+
super(message);
|
|
16297
|
+
this.name = "UnframedLayerError";
|
|
16298
|
+
Object.setPrototypeOf(this, UnframedLayerError.prototype);
|
|
16299
|
+
}
|
|
16300
|
+
};
|
|
16301
|
+
/**
|
|
16302
|
+
* The detail half of a layer failure message: cdk-local's own text kept
|
|
16303
|
+
* verbatim, anything else rendered by the shared credential-error policy.
|
|
16304
|
+
*/
|
|
16305
|
+
function layerFailureDetail(err, operation) {
|
|
16306
|
+
return err instanceof UnframedLayerError ? err.message : describeAwsFailureForWarn(err, operation);
|
|
16307
|
+
}
|
|
15945
16308
|
async function materializeLayerFromArn(layer, options = {}) {
|
|
15946
16309
|
const logger = getLogger();
|
|
15947
16310
|
let credentials;
|
|
@@ -15949,14 +16312,16 @@ async function materializeLayerFromArn(layer, options = {}) {
|
|
|
15949
16312
|
credentials = await assumeRoleForLayer(options.roleArn, layer.region, options);
|
|
15950
16313
|
logger.debug(`Layer ${layer.arn}: assumed role ${options.roleArn} for GetLayerVersion`);
|
|
15951
16314
|
} catch (err) {
|
|
15952
|
-
|
|
16315
|
+
if (err instanceof LayerMaterializationError) throw err;
|
|
16316
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: STS AssumeRole(${flattenToOneLine(options.roleArn)}) failed: ${layerFailureDetail(err, "STS AssumeRole (--layer-role-arn)")}. Check the role trust policy permits your principal and sts:AssumeRole is allowed.`);
|
|
15953
16317
|
}
|
|
15954
16318
|
let presignedUrl;
|
|
15955
16319
|
try {
|
|
15956
16320
|
presignedUrl = await fetchLayerContentUrl(layer, credentials, options);
|
|
15957
16321
|
} catch (err) {
|
|
16322
|
+
if (err instanceof LayerMaterializationError) throw err;
|
|
15958
16323
|
const hint = looksLikeAccessDenied(err) ? " GetLayerVersion access denied; check the credentials / role can read the layer (grant lambda:GetLayerVersion on the layer ARN, or pass --layer-role-arn <arn> to assume a role in the layer account)." : "";
|
|
15959
|
-
throw new LayerMaterializationError(`Layer ${layer.arn}: GetLayerVersion failed in region ${layer.region}: ${
|
|
16324
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: GetLayerVersion failed in region ${layer.region}: ${layerFailureDetail(err, "Lambda GetLayerVersion")}.${hint}`);
|
|
15960
16325
|
}
|
|
15961
16326
|
let zipBytes;
|
|
15962
16327
|
try {
|
|
@@ -15986,7 +16351,7 @@ async function fetchLayerContentUrl(layer, credentials, options) {
|
|
|
15986
16351
|
const versionLessArn = versionSuffix[1];
|
|
15987
16352
|
const command = await buildGetLayerVersionCommand(versionLessArn, Number(layer.version));
|
|
15988
16353
|
const url = (await client.send(command))?.Content?.Location;
|
|
15989
|
-
if (!url || typeof url !== "string") throw new
|
|
16354
|
+
if (!url || typeof url !== "string") throw new UnframedLayerError("GetLayerVersion response did not include Content.Location (presigned ZIP URL)");
|
|
15990
16355
|
return url;
|
|
15991
16356
|
} finally {
|
|
15992
16357
|
client.destroy?.();
|
|
@@ -15997,7 +16362,7 @@ async function assumeRoleForLayer(roleArn, region, options) {
|
|
|
15997
16362
|
try {
|
|
15998
16363
|
const command = await buildAssumeRoleCommand(roleArn);
|
|
15999
16364
|
const creds = (await client.send(command))?.Credentials;
|
|
16000
|
-
if (!creds?.AccessKeyId || !creds.SecretAccessKey) throw new
|
|
16365
|
+
if (!creds?.AccessKeyId || !creds.SecretAccessKey) throw new UnframedLayerError("AssumeRole returned no Credentials");
|
|
16001
16366
|
return {
|
|
16002
16367
|
accessKeyId: creds.AccessKeyId,
|
|
16003
16368
|
secretAccessKey: creds.SecretAccessKey,
|
|
@@ -16040,7 +16405,7 @@ async function buildAssumeRoleCommand(roleArn) {
|
|
|
16040
16405
|
async function downloadPresignedZip(presignedUrl, options) {
|
|
16041
16406
|
if (options.fetchZip) return options.fetchZip(presignedUrl);
|
|
16042
16407
|
const response = await fetch(presignedUrl);
|
|
16043
|
-
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText} from layer Content.Location URL`);
|
|
16408
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} ${flattenToOneLine(response.statusText)} from layer Content.Location URL`);
|
|
16044
16409
|
const buf = await response.arrayBuffer();
|
|
16045
16410
|
return new Uint8Array(buf);
|
|
16046
16411
|
}
|
|
@@ -16114,6 +16479,27 @@ async function inflateRaw(data) {
|
|
|
16114
16479
|
});
|
|
16115
16480
|
});
|
|
16116
16481
|
}
|
|
16482
|
+
/**
|
|
16483
|
+
* Message of a thrown value, for the two throws that are NOT AWS SDK relays.
|
|
16484
|
+
*
|
|
16485
|
+
* Issue #579 deliberately left these two on `errMsg`: the presigned-URL
|
|
16486
|
+
* download is a plain HTTPS fetch (the URL already carries its authorization,
|
|
16487
|
+
* so no credential chain is resolved and no modeled service exception is
|
|
16488
|
+
* parsed) and the unzip is a purely LOCAL operation. Neither `catch` can see a
|
|
16489
|
+
* `credential_process` command line, so there is nothing for the
|
|
16490
|
+
* credential-error policy to DECIDE about them. The two AWS calls in this
|
|
16491
|
+
* module — STS `AssumeRole` and `lambda:GetLayerVersion` — go through
|
|
16492
|
+
* `describeAwsFailureForWarn` instead.
|
|
16493
|
+
*
|
|
16494
|
+
* CORRECTED in review round 2: an earlier revision of this note claimed these
|
|
16495
|
+
* catches see nothing wire-derived at all, which was stronger than the code.
|
|
16496
|
+
* The download `catch` DOES see wire-derived text — `downloadPresignedZip`
|
|
16497
|
+
* raises `HTTP <status> <statusText> ...`, and the reason phrase is whatever
|
|
16498
|
+
* the presigned host sent. Nothing there needs WITHHOLDING (no credential
|
|
16499
|
+
* chain, no secret), but it does need FLATTENING, which is applied at that
|
|
16500
|
+
* throw rather than here, because `errMsg` is also used by the unzip `catch`
|
|
16501
|
+
* whose input is a local `fflate` error.
|
|
16502
|
+
*/
|
|
16117
16503
|
function errMsg(err) {
|
|
16118
16504
|
return err instanceof Error ? err.message : String(err);
|
|
16119
16505
|
}
|
|
@@ -18791,7 +19177,7 @@ async function resolveLambdaContainerEnv(lambda, options, profileCredentials, ex
|
|
|
18791
19177
|
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
18792
19178
|
assumeSucceeded = true;
|
|
18793
19179
|
} catch (err) {
|
|
18794
|
-
const reason = describeAwsFailureForWarn(err, "STS AssumeRole");
|
|
19180
|
+
const reason = err instanceof AssumeRoleFailure ? err.detail : describeAwsFailureForWarn(err, "STS AssumeRole");
|
|
18795
19181
|
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(resolvedAssumeRoleArn)}) failed: ${reason}. Falling back to the developer's shell credentials.`);
|
|
18796
19182
|
}
|
|
18797
19183
|
}
|
|
@@ -19308,9 +19694,18 @@ async function downloadAndExtractS3Bundle(location, options = {}) {
|
|
|
19308
19694
|
}).then(() => void 0)
|
|
19309
19695
|
};
|
|
19310
19696
|
}
|
|
19697
|
+
/**
|
|
19698
|
+
* `s3://<bucket>/<key>[?versionId=...]` for a log or error line.
|
|
19699
|
+
*
|
|
19700
|
+
* FLATTENED (issue #579 review round 3). Under `--from-cfn-stack` the bucket
|
|
19701
|
+
* and key are resolved from template intrinsics against DEPLOYED state — a
|
|
19702
|
+
* `Ref` / `Fn::ImportValue` answered by `ListStackResources` / `ListExports` —
|
|
19703
|
+
* so they are wire-derived on that path, and this string now lands on a
|
|
19704
|
+
* default-level error line as well as the `info` progress line it always fed.
|
|
19705
|
+
*/
|
|
19311
19706
|
function formatRef(location) {
|
|
19312
|
-
const version = location.versionId ? `?versionId=${location.versionId}` : "";
|
|
19313
|
-
return `s3://${location.bucket}/${location.key}${version}`;
|
|
19707
|
+
const version = location.versionId ? `?versionId=${flattenToOneLine(location.versionId)}` : "";
|
|
19708
|
+
return `s3://${flattenToOneLine(location.bucket)}/${flattenToOneLine(location.key)}${version}`;
|
|
19314
19709
|
}
|
|
19315
19710
|
/**
|
|
19316
19711
|
* Guard against zip-slip: reject an entry whose normalized path escapes the
|
|
@@ -19342,6 +19737,9 @@ function defaultFetchObject$1(options) {
|
|
|
19342
19737
|
}));
|
|
19343
19738
|
if (!res.Body) throw new CdkLocalError(`S3 GetObject for ${formatRef(location)} returned an empty body.`, "LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_EMPTY_BODY");
|
|
19344
19739
|
return await res.Body.transformToByteArray();
|
|
19740
|
+
} catch (err) {
|
|
19741
|
+
if (err instanceof CdkLocalError) throw err;
|
|
19742
|
+
throw new CdkLocalError(`S3 GetObject for ${formatRef(location)} failed: ${describeAwsFailureForWarn(err, "S3 GetObject (fromS3 bundle)")}`, "LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_FETCH_FAILED");
|
|
19345
19743
|
} finally {
|
|
19346
19744
|
client.destroy();
|
|
19347
19745
|
}
|
|
@@ -20456,7 +20854,8 @@ async function resolveHostCredentialsForSigV4(options, resolved, loaded, region,
|
|
|
20456
20854
|
if (assumeRoleArn) try {
|
|
20457
20855
|
return await assumeAgentCoreExecutionRole(assumeRoleArn, region, options.profile);
|
|
20458
20856
|
} catch (err) {
|
|
20459
|
-
|
|
20857
|
+
const sigv4Detail = err instanceof AssumeRoleFailure ? err.detail : describeAwsFailureForWarn(err, "STS AssumeRole (--sigv4 signing)");
|
|
20858
|
+
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(assumeRoleArn)}) failed for --sigv4 signing: ${sigv4Detail}. Falling back to ${options.profile ? `--profile ${options.profile}` : "shell credentials"}.`);
|
|
20460
20859
|
}
|
|
20461
20860
|
if (options.profile) {
|
|
20462
20861
|
const creds = await resolveProfileCredentials(options.profile);
|
|
@@ -20579,7 +20978,8 @@ async function resolveAgentCoreCodeImageFromS3(resolved, code, s3Source, options
|
|
|
20579
20978
|
if (assumeRoleArn) try {
|
|
20580
20979
|
credentials = await assumeAgentCoreExecutionRole(assumeRoleArn, region, options.profile);
|
|
20581
20980
|
} catch (err) {
|
|
20582
|
-
|
|
20981
|
+
const bundleDetail = err instanceof AssumeRoleFailure ? err.detail : describeAwsFailureForWarn(err, "STS AssumeRole (fromS3 bundle download)");
|
|
20982
|
+
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(assumeRoleArn)}) failed for the fromS3 bundle download: ${bundleDetail}. Falling back to ${options.profile ? `--profile ${options.profile}` : "the default credentials"}.`);
|
|
20583
20983
|
}
|
|
20584
20984
|
const bundle = await downloadAndExtractS3Bundle(location, {
|
|
20585
20985
|
...region !== void 0 && { region },
|
|
@@ -20777,7 +21177,8 @@ async function applyAgentCoreCredentialEnv(dockerEnv, args) {
|
|
|
20777
21177
|
if (stsRegion) dockerEnv["AWS_REGION"] = stsRegion;
|
|
20778
21178
|
assumeSucceeded = true;
|
|
20779
21179
|
} catch (err) {
|
|
20780
|
-
|
|
21180
|
+
const assumeDetail = err instanceof AssumeRoleFailure ? err.detail : describeAwsFailureForWarn(err, "STS AssumeRole");
|
|
21181
|
+
logger.warn(`--assume-role: STS AssumeRole(${flattenToOneLine(args.assumeRoleArn)}) failed: ${assumeDetail}. Falling back to the developer's shell credentials.`);
|
|
20781
21182
|
}
|
|
20782
21183
|
}
|
|
20783
21184
|
if (!assumeSucceeded) {
|
|
@@ -20937,7 +21338,7 @@ async function assumeAgentCoreExecutionRole(roleArn, region, profile) {
|
|
|
20937
21338
|
RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-invoke-agentcore-${Date.now()}`,
|
|
20938
21339
|
DurationSeconds: 3600
|
|
20939
21340
|
}))).Credentials;
|
|
20940
|
-
if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new
|
|
21341
|
+
if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new AssumeRoleFailure(`AssumeRole(${flattenToOneLine(roleArn)}) returned no usable credentials.`, "the response carried no usable credentials");
|
|
20941
21342
|
return {
|
|
20942
21343
|
accessKeyId: creds.AccessKeyId,
|
|
20943
21344
|
secretAccessKey: creds.SecretAccessKey,
|
|
@@ -21914,7 +22315,7 @@ async function resolveSecretsManager(entry, shape, client) {
|
|
|
21914
22315
|
try {
|
|
21915
22316
|
secretString = (await client.send(new GetSecretValueCommand({ SecretId: shape.baseArn }))).SecretString;
|
|
21916
22317
|
} catch (err) {
|
|
21917
|
-
throw new EcsSecretsResolutionError(`Failed to resolve Secrets Manager secret for container '${entry.containerName}' / env '${entry.name}' (${shape.baseArn}): ${err
|
|
22318
|
+
throw new EcsSecretsResolutionError(`Failed to resolve Secrets Manager secret for container '${entry.containerName}' / env '${entry.name}' (${shape.baseArn}): ${describeAwsFailureForWarn(err, "SecretsManager GetSecretValue")}`);
|
|
21918
22319
|
}
|
|
21919
22320
|
if (secretString === void 0) throw new EcsSecretsResolutionError(`Secrets Manager returned no SecretString for container '${entry.containerName}' / env '${entry.name}' (${shape.baseArn}). Binary secrets are not supported.`);
|
|
21920
22321
|
if (shape.jsonKey === void 0) return secretString;
|
|
@@ -21940,7 +22341,7 @@ async function resolveSsm(entry, shape, client) {
|
|
|
21940
22341
|
return value;
|
|
21941
22342
|
} catch (err) {
|
|
21942
22343
|
if (err instanceof EcsSecretsResolutionError) throw err;
|
|
21943
|
-
throw new EcsSecretsResolutionError(`Failed to resolve SSM parameter for container '${entry.containerName}' / env '${entry.name}' (${shape.name}): ${err
|
|
22344
|
+
throw new EcsSecretsResolutionError(`Failed to resolve SSM parameter for container '${entry.containerName}' / env '${entry.name}' (${shape.name}): ${describeAwsFailureForWarn(err, "SSM GetParameter (ECS secret)")}`);
|
|
21944
22345
|
}
|
|
21945
22346
|
}
|
|
21946
22347
|
|
|
@@ -24053,6 +24454,22 @@ const defaultReadContainerLogsImpl = async (containerId) => {
|
|
|
24053
24454
|
* `read` is injectable so the unit test can assert the formatting without
|
|
24054
24455
|
* a real container; production callers use the default `docker logs`
|
|
24055
24456
|
* reader.
|
|
24457
|
+
*
|
|
24458
|
+
* Issue #579 — ONE `logger.warn` PER TAIL LINE, not one warn carrying an
|
|
24459
|
+
* embedded `\n`. LEVEL: a default-level warn, and under `cdkl studio` the
|
|
24460
|
+
* serve child's stdout is mirrored into a log ring served over HTTP. The
|
|
24461
|
+
* studio serve manager reads that stdout with `streamLines`, which splits on
|
|
24462
|
+
* `\n`, and it declines to pattern-match a line carrying cdk-local's own
|
|
24463
|
+
* `WARN: ` prefix (issue #578). A multi-line warn defeats that bound from the
|
|
24464
|
+
* inside: the logger prefixes the MESSAGE, so line 2 onward arrives
|
|
24465
|
+
* prefix-less and is matched at `^` — and the content is the CONTAINER's own
|
|
24466
|
+
* stdout, i.e. whatever the application printed. An app that logs
|
|
24467
|
+
* `Server listening on http://10.0.0.9:3000` (which is exactly what a web
|
|
24468
|
+
* framework prints, and this tail fires when such an app has just crashed)
|
|
24469
|
+
* would re-point the capture proxy. RECONSTRUCTION does not arise: this is
|
|
24470
|
+
* container output, not an AWS SDK failure, so nothing is withheld — the whole
|
|
24471
|
+
* tail still prints, one prefixed line at a time. Each line is additionally
|
|
24472
|
+
* flattened, so a `\r` or U+2028 inside one cannot re-split it downstream.
|
|
24056
24473
|
*/
|
|
24057
24474
|
async function printExitedContainerLogs(replicaIndex, containerId, logger, read = defaultReadContainerLogsImpl) {
|
|
24058
24475
|
let raw;
|
|
@@ -24064,7 +24481,12 @@ async function printExitedContainerLogs(replicaIndex, containerId, logger, read
|
|
|
24064
24481
|
}
|
|
24065
24482
|
const tail = raw.trimEnd();
|
|
24066
24483
|
if (tail.length === 0) return;
|
|
24067
|
-
logger.warn(`Replica ${replicaIndex} essential container logs (last ${EXIT_LOG_TAIL_LINES} lines)
|
|
24484
|
+
logger.warn(`Replica ${replicaIndex} essential container logs (last ${EXIT_LOG_TAIL_LINES} lines):`);
|
|
24485
|
+
for (const line of tail.split(/\r?\n/)) {
|
|
24486
|
+
const flat = flattenToOneLine(line).trimEnd();
|
|
24487
|
+
if (flat.trim().length === 0) continue;
|
|
24488
|
+
logger.warn(`Replica ${replicaIndex} | ${flat}`);
|
|
24489
|
+
}
|
|
24068
24490
|
}
|
|
24069
24491
|
const defaultSleepImpl = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
24070
24492
|
let sleepImpl = defaultSleepImpl;
|
|
@@ -27742,6 +28164,9 @@ async function resolvePlaceholderAccount$1(arn, region, profile) {
|
|
|
27742
28164
|
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
27743
28165
|
if (!account) throw new LocalStartServiceError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'.`);
|
|
27744
28166
|
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
28167
|
+
} catch (err) {
|
|
28168
|
+
if (err instanceof LocalStartServiceError) throw err;
|
|
28169
|
+
throw new LocalStartServiceError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN '${flattenToOneLine(arn)}': ${describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)")}. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
27745
28170
|
} finally {
|
|
27746
28171
|
sts.destroy();
|
|
27747
28172
|
}
|
|
@@ -28289,6 +28714,18 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
|
|
|
28289
28714
|
}
|
|
28290
28715
|
}
|
|
28291
28716
|
/**
|
|
28717
|
+
* cdk-local's own rejection from {@link resolvePlaceholderAccount}, so the
|
|
28718
|
+
* relay below can tell it apart from a wire-derived SDK failure and re-raise it
|
|
28719
|
+
* intact (issue #579).
|
|
28720
|
+
*/
|
|
28721
|
+
var PlaceholderAccountError = class PlaceholderAccountError extends Error {
|
|
28722
|
+
constructor(message) {
|
|
28723
|
+
super(message);
|
|
28724
|
+
this.name = "PlaceholderAccountError";
|
|
28725
|
+
Object.setPrototypeOf(this, PlaceholderAccountError.prototype);
|
|
28726
|
+
}
|
|
28727
|
+
};
|
|
28728
|
+
/**
|
|
28292
28729
|
* If `arn` contains the `${AWS::AccountId}` placeholder emitted by the
|
|
28293
28730
|
* resolver for inline same-stack IAM Roles, substitute the live caller
|
|
28294
28731
|
* account via STS `GetCallerIdentity`. Otherwise pass through unchanged.
|
|
@@ -28302,8 +28739,13 @@ async function resolvePlaceholderAccount(arn, region, profile) {
|
|
|
28302
28739
|
}));
|
|
28303
28740
|
try {
|
|
28304
28741
|
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
28305
|
-
if (!account) throw new
|
|
28742
|
+
if (!account) throw new PlaceholderAccountError(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
28306
28743
|
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
28744
|
+
} catch (err) {
|
|
28745
|
+
if (err instanceof PlaceholderAccountError) throw err;
|
|
28746
|
+
const shownArn = flattenToOneLine(arn);
|
|
28747
|
+
const detail = describeAwsFailureForWarn(err, "STS GetCallerIdentity (task-role placeholder)");
|
|
28748
|
+
throw new PlaceholderAccountError(`--assume-task-role: STS GetCallerIdentity failed while resolving placeholder ARN '${shownArn}': ${detail}. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
28307
28749
|
} finally {
|
|
28308
28750
|
sts.destroy();
|
|
28309
28751
|
}
|
|
@@ -31155,9 +31597,9 @@ function createS3OriginReader(bucketName, options = {}) {
|
|
|
31155
31597
|
};
|
|
31156
31598
|
if (direct.kind === "denied" && !deniedWarned) {
|
|
31157
31599
|
deniedWarned = true;
|
|
31158
|
-
getLogger().warn(`S3 denied reading '${key}' from bucket '${bucketName}'. If this is an OAC-locked / private bucket your credentials cannot read, point the origin at a local directory with --origin <originId>=<dir> (or use credentials with s3:GetObject on the bucket). ${getEmbedConfig().cliName} start-cloudfront reads the origin from real S3.`);
|
|
31600
|
+
getLogger().warn(`S3 denied reading '${flattenToOneLine(key)}' from bucket '${flattenToOneLine(bucketName)}'. If this is an OAC-locked / private bucket your credentials cannot read, point the origin at a local directory with --origin <originId>=<dir> (or use credentials with s3:GetObject on the bucket). ${getEmbedConfig().cliName} start-cloudfront reads the origin from real S3.`);
|
|
31159
31601
|
}
|
|
31160
|
-
if (direct.kind === "error") getLogger().warn(`S3 read of '${key}' from bucket '${bucketName}' failed: ${direct.message}`);
|
|
31602
|
+
if (direct.kind === "error") getLogger().warn(`S3 read of '${flattenToOneLine(key)}' from bucket '${flattenToOneLine(bucketName)}' failed: ${direct.message}`);
|
|
31161
31603
|
}
|
|
31162
31604
|
for (const candidate of resolveErrorResponseCandidates(input.customErrorResponses)) {
|
|
31163
31605
|
const page = await fetchObject(candidate.errorKey);
|
|
@@ -31166,7 +31608,7 @@ function createS3OriginReader(bucketName, options = {}) {
|
|
|
31166
31608
|
headers: { "content-type": contentTypeForKey(candidate.errorKey) },
|
|
31167
31609
|
body: page.body
|
|
31168
31610
|
};
|
|
31169
|
-
if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page '${candidate.errorKey}' from bucket '${bucketName}' (${page.kind}); falling through.`);
|
|
31611
|
+
if (page.kind === "denied" || page.kind === "error") getLogger().warn(`S3 could not read custom-error page '${flattenToOneLine(candidate.errorKey)}' from bucket '${flattenToOneLine(bucketName)}' (${page.kind}); falling through.`);
|
|
31170
31612
|
}
|
|
31171
31613
|
return {
|
|
31172
31614
|
statusCode: 404,
|
|
@@ -31234,6 +31676,18 @@ function defaultFetchObject(bucketName, options) {
|
|
|
31234
31676
|
* Classify an S3 SDK error: a missing key (`NoSuchKey` / 404) is `not-found`
|
|
31235
31677
|
* (try the SPA fallback), an `AccessDenied` / 403 is `denied` (a credential /
|
|
31236
31678
|
* OAC config problem), anything else is `error`.
|
|
31679
|
+
*
|
|
31680
|
+
* Issue #579 — the `error` branch's `message` is the only one that reaches a
|
|
31681
|
+
* log line (the reader prints it in a DEFAULT-level `S3 read of ... failed:`
|
|
31682
|
+
* warn), so it is rendered by {@link describeAwsFailureForWarn}. LEVEL: a
|
|
31683
|
+
* default-level warn, mirrored into the studio log ring. RECONSTRUCTION: the
|
|
31684
|
+
* `catch` this classifies for wraps `client.send(GetObjectCommand)`, which
|
|
31685
|
+
* resolves the credential chain before the request goes out — a
|
|
31686
|
+
* `CredentialsProviderError` here is COMMON, since the whole point of this
|
|
31687
|
+
* origin is reading a deployed bucket with the developer's own credentials —
|
|
31688
|
+
* alongside a modeled S3 exception whose message is the diagnosis. The
|
|
31689
|
+
* `not-found` / `denied` branches carry no message at all and are untouched:
|
|
31690
|
+
* their names are structural discriminators, not text that gets printed.
|
|
31237
31691
|
*/
|
|
31238
31692
|
function classifyS3Error(err) {
|
|
31239
31693
|
const e = err;
|
|
@@ -31243,7 +31697,7 @@ function classifyS3Error(err) {
|
|
|
31243
31697
|
if (status === 403 || name === "AccessDenied" || name === "Forbidden") return { kind: "denied" };
|
|
31244
31698
|
return {
|
|
31245
31699
|
kind: "error",
|
|
31246
|
-
message: err
|
|
31700
|
+
message: describeAwsFailureForWarn(err, "S3 GetObject")
|
|
31247
31701
|
};
|
|
31248
31702
|
}
|
|
31249
31703
|
|
|
@@ -31305,7 +31759,7 @@ function createDeployedKvsDataSource(options) {
|
|
|
31305
31759
|
}))).Value;
|
|
31306
31760
|
} catch (err) {
|
|
31307
31761
|
if (isKeyNotFound(err)) return void 0;
|
|
31308
|
-
throw new Error(`cf.kvs().get('${key}') against ${options.kvsArn} failed: ${err
|
|
31762
|
+
throw new Error(`cf.kvs().get('${key}') against ${flattenToOneLine(options.kvsArn)} failed: ${describeAwsFailureForWarn(err, "CloudFront KeyValueStore GetKey")}`);
|
|
31309
31763
|
}
|
|
31310
31764
|
}
|
|
31311
31765
|
};
|
|
@@ -31332,7 +31786,7 @@ async function resolveDeployedKvsArnByName(name, options = {}) {
|
|
|
31332
31786
|
...item.Id !== void 0 && { id: item.Id }
|
|
31333
31787
|
};
|
|
31334
31788
|
} catch (err) {
|
|
31335
|
-
getLogger().debug(`ListKeyValueStores lookup for '${name}' failed: ${
|
|
31789
|
+
getLogger().debug(`ListKeyValueStores lookup for '${name}' failed: ${flattenToOneLine(stringifyThrown(err))}`);
|
|
31336
31790
|
}
|
|
31337
31791
|
}
|
|
31338
31792
|
/** True when a GetKey error means the key (or store) was not found, not a real failure. */
|
|
@@ -38520,7 +38974,7 @@ async function prepareEcsImageContexts(args) {
|
|
|
38520
38974
|
const ctx = await buildEcsImageResolutionContext(stack, stateProvider, options);
|
|
38521
38975
|
contextByStack.set(stack.stackName, ctx);
|
|
38522
38976
|
} catch (err) {
|
|
38523
|
-
logger.warn(`studio: could not build deployed-state image context for stack '${stack.stackName}'; ECS services in it resolve against the synthed template only. ${err
|
|
38977
|
+
logger.warn(`studio: could not build deployed-state image context for stack '${stack.stackName}'; ECS services in it resolve against the synthed template only. ${describeAwsFailureForWarn(err, "deployed-state image context (CloudFormation + SSM)")}`);
|
|
38524
38978
|
contextByStack.set(stack.stackName, void 0);
|
|
38525
38979
|
} finally {
|
|
38526
38980
|
stateProvider?.dispose();
|
|
@@ -38998,4 +39452,4 @@ function addStudioSpecificOptions(cmd) {
|
|
|
38998
39452
|
|
|
38999
39453
|
//#endregion
|
|
39000
39454
|
export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, describeCredentialLoadFailure as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, resolveProfileCredentials as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, buildStsClientConfig as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, describeAwsFailureForWarn as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
|
|
39001
|
-
//# sourceMappingURL=local-studio-
|
|
39455
|
+
//# sourceMappingURL=local-studio-DOdzcjkE.js.map
|