@go-to-k/cdkd 0.9.0 → 0.10.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/cli.js +186 -25
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.10.0.tgz +0 -0
- package/dist/index.js +163 -15
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.9.0.tgz +0 -0
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -783,10 +783,73 @@ Caused by: ${error.cause.message}`;
|
|
|
783
783
|
}
|
|
784
784
|
return String(error);
|
|
785
785
|
}
|
|
786
|
+
function normalizeAwsError(err, context = {}) {
|
|
787
|
+
if (!(err instanceof Error)) {
|
|
788
|
+
return new Error(String(err));
|
|
789
|
+
}
|
|
790
|
+
const isUnknown = err.name === "Unknown" || err.message === "UnknownError";
|
|
791
|
+
if (!isUnknown)
|
|
792
|
+
return err;
|
|
793
|
+
const meta = err.$metadata;
|
|
794
|
+
const status = meta?.httpStatusCode;
|
|
795
|
+
const bucket = context.bucket ?? "<unknown bucket>";
|
|
796
|
+
const operation = context.operation ?? "operation";
|
|
797
|
+
switch (status) {
|
|
798
|
+
case 301: {
|
|
799
|
+
const responseHeaders = err.$response?.headers;
|
|
800
|
+
const region = responseHeaders?.["x-amz-bucket-region"] ?? responseHeaders?.["X-Amz-Bucket-Region"];
|
|
801
|
+
const where = region ? ` (in ${region})` : "";
|
|
802
|
+
return new Error(
|
|
803
|
+
`Bucket '${bucket}'${where} is in a different region than the client. cdkd resolves this automatically; if you see this message, please report it.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
case 403:
|
|
807
|
+
return new Error(
|
|
808
|
+
`Access denied to bucket '${bucket}'. Verify credentials and bucket policy.`
|
|
809
|
+
);
|
|
810
|
+
case 404:
|
|
811
|
+
return new Error(`Bucket '${bucket}' does not exist.`);
|
|
812
|
+
default: {
|
|
813
|
+
const statusStr = status !== void 0 ? `HTTP ${status}` : "unknown HTTP status";
|
|
814
|
+
return new Error(
|
|
815
|
+
`S3 error during ${operation} on '${bucket}' (${statusStr}). See CloudTrail for details.`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
786
820
|
|
|
787
821
|
// src/index.ts
|
|
788
822
|
init_aws_clients();
|
|
789
823
|
|
|
824
|
+
// src/utils/aws-region-resolver.ts
|
|
825
|
+
import { GetBucketLocationCommand, S3Client as S3Client2 } from "@aws-sdk/client-s3";
|
|
826
|
+
var cache = /* @__PURE__ */ new Map();
|
|
827
|
+
async function resolveBucketRegion(bucketName, opts = {}) {
|
|
828
|
+
const cached = cache.get(bucketName);
|
|
829
|
+
if (cached)
|
|
830
|
+
return cached;
|
|
831
|
+
const promise = (async () => {
|
|
832
|
+
const client = new S3Client2({
|
|
833
|
+
region: "us-east-1",
|
|
834
|
+
...opts.profile && { profile: opts.profile },
|
|
835
|
+
...opts.credentials && { credentials: opts.credentials }
|
|
836
|
+
});
|
|
837
|
+
try {
|
|
838
|
+
const response = await client.send(new GetBucketLocationCommand({ Bucket: bucketName }));
|
|
839
|
+
return response.LocationConstraint || "us-east-1";
|
|
840
|
+
} catch {
|
|
841
|
+
return opts.fallbackRegion ?? "us-east-1";
|
|
842
|
+
} finally {
|
|
843
|
+
client.destroy();
|
|
844
|
+
}
|
|
845
|
+
})();
|
|
846
|
+
cache.set(bucketName, promise);
|
|
847
|
+
return promise;
|
|
848
|
+
}
|
|
849
|
+
function clearBucketRegionCache() {
|
|
850
|
+
cache.clear();
|
|
851
|
+
}
|
|
852
|
+
|
|
790
853
|
// src/synthesis/synthesizer.ts
|
|
791
854
|
import { existsSync as existsSync3, mkdirSync, statSync } from "node:fs";
|
|
792
855
|
import { resolve as resolve3 } from "node:path";
|
|
@@ -2083,7 +2146,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
2083
2146
|
// src/assets/file-asset-publisher.ts
|
|
2084
2147
|
import { createReadStream, statSync as statSync2 } from "node:fs";
|
|
2085
2148
|
import { join as join4, basename } from "node:path";
|
|
2086
|
-
import { S3Client as
|
|
2149
|
+
import { S3Client as S3Client3, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
2087
2150
|
var FileAssetPublisher = class {
|
|
2088
2151
|
logger = getLogger().child("FileAssetPublisher");
|
|
2089
2152
|
/**
|
|
@@ -2104,7 +2167,7 @@ var FileAssetPublisher = class {
|
|
|
2104
2167
|
this.logger.debug(
|
|
2105
2168
|
`Publishing file asset ${asset.displayName || assetHash} \u2192 s3://${bucketName}/${objectKey}`
|
|
2106
2169
|
);
|
|
2107
|
-
const client = new
|
|
2170
|
+
const client = new S3Client3({
|
|
2108
2171
|
region: destRegion
|
|
2109
2172
|
});
|
|
2110
2173
|
try {
|
|
@@ -2653,6 +2716,7 @@ var AssetPublisher = class {
|
|
|
2653
2716
|
|
|
2654
2717
|
// src/state/s3-state-backend.ts
|
|
2655
2718
|
import {
|
|
2719
|
+
S3Client as S3Client4,
|
|
2656
2720
|
GetObjectCommand,
|
|
2657
2721
|
PutObjectCommand as PutObjectCommand2,
|
|
2658
2722
|
DeleteObjectCommand,
|
|
@@ -2670,11 +2734,14 @@ var STATE_SCHEMA_VERSION_CURRENT = 2;
|
|
|
2670
2734
|
var LEGACY_KEY_DEPTH = 2;
|
|
2671
2735
|
var NEW_KEY_DEPTH = 3;
|
|
2672
2736
|
var S3StateBackend = class {
|
|
2673
|
-
constructor(s3Client, config) {
|
|
2737
|
+
constructor(s3Client, config, clientOpts = {}) {
|
|
2674
2738
|
this.s3Client = s3Client;
|
|
2675
2739
|
this.config = config;
|
|
2740
|
+
this.clientOpts = clientOpts;
|
|
2676
2741
|
}
|
|
2677
2742
|
logger = getLogger().child("S3StateBackend");
|
|
2743
|
+
clientResolved = false;
|
|
2744
|
+
resolveInFlight = null;
|
|
2678
2745
|
/**
|
|
2679
2746
|
* Get the new (region-scoped) S3 key for a stack's state file.
|
|
2680
2747
|
*/
|
|
@@ -2688,13 +2755,73 @@ var S3StateBackend = class {
|
|
|
2688
2755
|
getLegacyStateKey(stackName) {
|
|
2689
2756
|
return `${this.config.prefix}/${stackName}/state.json`;
|
|
2690
2757
|
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Resolve the state bucket's actual region and, if it differs from the
|
|
2760
|
+
* client's currently-configured region, replace the S3Client with one
|
|
2761
|
+
* pointed at the bucket's region.
|
|
2762
|
+
*
|
|
2763
|
+
* This is idempotent: subsequent calls return immediately. Concurrent
|
|
2764
|
+
* callers (e.g. when several public methods race during a parallel deploy)
|
|
2765
|
+
* share a single in-flight resolution promise so we never issue more than
|
|
2766
|
+
* one `GetBucketLocation` per backend.
|
|
2767
|
+
*
|
|
2768
|
+
* Errors from `GetBucketLocation` are deliberately swallowed by
|
|
2769
|
+
* `resolveBucketRegion` — the resolver returns `fallbackRegion` so the
|
|
2770
|
+
* caller can surface the more actionable downstream error (e.g. the
|
|
2771
|
+
* `HeadBucket` 404 routed via `normalizeAwsError`).
|
|
2772
|
+
*/
|
|
2773
|
+
async ensureClientForBucket() {
|
|
2774
|
+
if (this.clientResolved)
|
|
2775
|
+
return;
|
|
2776
|
+
if (this.resolveInFlight)
|
|
2777
|
+
return this.resolveInFlight;
|
|
2778
|
+
this.resolveInFlight = (async () => {
|
|
2779
|
+
try {
|
|
2780
|
+
const currentRegion = await this.s3Client.config.region();
|
|
2781
|
+
const fallbackRegion = typeof currentRegion === "string" ? currentRegion : void 0;
|
|
2782
|
+
const bucketRegion = await resolveBucketRegion(this.config.bucket, {
|
|
2783
|
+
...this.clientOpts.profile && { profile: this.clientOpts.profile },
|
|
2784
|
+
...this.clientOpts.credentials && { credentials: this.clientOpts.credentials },
|
|
2785
|
+
...fallbackRegion && { fallbackRegion }
|
|
2786
|
+
});
|
|
2787
|
+
if (bucketRegion !== currentRegion) {
|
|
2788
|
+
this.logger.debug(
|
|
2789
|
+
`State bucket '${this.config.bucket}' is in '${bucketRegion}' (client was '${currentRegion}'); rebuilding S3 client.`
|
|
2790
|
+
);
|
|
2791
|
+
const oldClient = this.s3Client;
|
|
2792
|
+
this.s3Client = new S3Client4({
|
|
2793
|
+
region: bucketRegion,
|
|
2794
|
+
...this.clientOpts.profile && { profile: this.clientOpts.profile },
|
|
2795
|
+
...this.clientOpts.credentials && { credentials: this.clientOpts.credentials },
|
|
2796
|
+
// Suppress "Are you using a Stream of unknown length" warning,
|
|
2797
|
+
// matching the suppression in AwsClients.
|
|
2798
|
+
logger: { debug: () => {
|
|
2799
|
+
}, info: () => {
|
|
2800
|
+
}, warn: () => {
|
|
2801
|
+
}, error: () => {
|
|
2802
|
+
} }
|
|
2803
|
+
});
|
|
2804
|
+
oldClient.destroy();
|
|
2805
|
+
}
|
|
2806
|
+
this.clientResolved = true;
|
|
2807
|
+
} finally {
|
|
2808
|
+
this.resolveInFlight = null;
|
|
2809
|
+
}
|
|
2810
|
+
})();
|
|
2811
|
+
return this.resolveInFlight;
|
|
2812
|
+
}
|
|
2691
2813
|
/**
|
|
2692
2814
|
* Verify that the configured state bucket exists.
|
|
2693
2815
|
*
|
|
2694
2816
|
* Called early in deploy/destroy to fail fast before expensive work
|
|
2695
2817
|
* (asset publishing, Docker builds) runs against a missing bucket.
|
|
2818
|
+
*
|
|
2819
|
+
* Errors are routed through {@link normalizeAwsError} so the AWS SDK v3
|
|
2820
|
+
* synthetic `UnknownError` (e.g. cross-region HEAD) becomes a concrete
|
|
2821
|
+
* "Bucket does not exist" / "Access denied" / "different region" message.
|
|
2696
2822
|
*/
|
|
2697
2823
|
async verifyBucketExists() {
|
|
2824
|
+
await this.ensureClientForBucket();
|
|
2698
2825
|
try {
|
|
2699
2826
|
await this.s3Client.send(new HeadBucketCommand({ Bucket: this.config.bucket }));
|
|
2700
2827
|
} catch (error) {
|
|
@@ -2704,9 +2831,13 @@ var S3StateBackend = class {
|
|
|
2704
2831
|
`State bucket '${this.config.bucket}' does not exist. Run 'cdkd bootstrap' to create it, or specify an existing bucket via --state-bucket, CDKD_STATE_BUCKET, or cdk.json context.cdkd.stateBucket.`
|
|
2705
2832
|
);
|
|
2706
2833
|
}
|
|
2834
|
+
const normalized = normalizeAwsError(error, {
|
|
2835
|
+
bucket: this.config.bucket,
|
|
2836
|
+
operation: "HeadBucket"
|
|
2837
|
+
});
|
|
2707
2838
|
throw new StateError(
|
|
2708
|
-
`Failed to verify state bucket '${this.config.bucket}': ${
|
|
2709
|
-
|
|
2839
|
+
`Failed to verify state bucket '${this.config.bucket}': ${normalized.message}`,
|
|
2840
|
+
normalized
|
|
2710
2841
|
);
|
|
2711
2842
|
}
|
|
2712
2843
|
}
|
|
@@ -2719,6 +2850,7 @@ var S3StateBackend = class {
|
|
|
2719
2850
|
* state without forcing a write-through migration first.
|
|
2720
2851
|
*/
|
|
2721
2852
|
async stateExists(stackName, region) {
|
|
2853
|
+
await this.ensureClientForBucket();
|
|
2722
2854
|
const newKey = this.getStateKey(stackName, region);
|
|
2723
2855
|
if (await this.headObject(newKey)) {
|
|
2724
2856
|
return true;
|
|
@@ -2741,6 +2873,7 @@ var S3StateBackend = class {
|
|
|
2741
2873
|
* preserve the quotes — they are required for `IfMatch` conditions.
|
|
2742
2874
|
*/
|
|
2743
2875
|
async getState(stackName, region) {
|
|
2876
|
+
await this.ensureClientForBucket();
|
|
2744
2877
|
const newKey = this.getStateKey(stackName, region);
|
|
2745
2878
|
try {
|
|
2746
2879
|
this.logger.debug(`Getting state for stack: ${stackName} (${region})`);
|
|
@@ -2800,6 +2933,7 @@ var S3StateBackend = class {
|
|
|
2800
2933
|
* @returns New ETag (with quotes, e.g., `"abc123"`)
|
|
2801
2934
|
*/
|
|
2802
2935
|
async saveState(stackName, region, state, options = {}) {
|
|
2936
|
+
await this.ensureClientForBucket();
|
|
2803
2937
|
const newKey = this.getStateKey(stackName, region);
|
|
2804
2938
|
const { expectedEtag, migrateLegacy } = options;
|
|
2805
2939
|
const body = {
|
|
@@ -2855,9 +2989,13 @@ var S3StateBackend = class {
|
|
|
2855
2989
|
`State has been modified by another process. Expected ETag: ${expectedEtag}, but state has changed.`
|
|
2856
2990
|
);
|
|
2857
2991
|
}
|
|
2992
|
+
const normalized = normalizeAwsError(error, {
|
|
2993
|
+
bucket: this.config.bucket,
|
|
2994
|
+
operation: "PutObject"
|
|
2995
|
+
});
|
|
2858
2996
|
throw new StateError(
|
|
2859
|
-
`Failed to save state for stack '${stackName}' (${region}): ${
|
|
2860
|
-
|
|
2997
|
+
`Failed to save state for stack '${stackName}' (${region}): ${normalized.message}`,
|
|
2998
|
+
normalized
|
|
2861
2999
|
);
|
|
2862
3000
|
}
|
|
2863
3001
|
}
|
|
@@ -2869,6 +3007,7 @@ var S3StateBackend = class {
|
|
|
2869
3007
|
* field is left alone.
|
|
2870
3008
|
*/
|
|
2871
3009
|
async deleteState(stackName, region) {
|
|
3010
|
+
await this.ensureClientForBucket();
|
|
2872
3011
|
try {
|
|
2873
3012
|
this.logger.debug(`Deleting state: ${stackName} (${region})`);
|
|
2874
3013
|
await this.s3Client.send(
|
|
@@ -2888,9 +3027,13 @@ var S3StateBackend = class {
|
|
|
2888
3027
|
}
|
|
2889
3028
|
this.logger.debug(`State deleted: ${stackName} (${region})`);
|
|
2890
3029
|
} catch (error) {
|
|
3030
|
+
const normalized = normalizeAwsError(error, {
|
|
3031
|
+
bucket: this.config.bucket,
|
|
3032
|
+
operation: "DeleteObject"
|
|
3033
|
+
});
|
|
2891
3034
|
throw new StateError(
|
|
2892
|
-
`Failed to delete state for stack '${stackName}' (${region}): ${
|
|
2893
|
-
|
|
3035
|
+
`Failed to delete state for stack '${stackName}' (${region}): ${normalized.message}`,
|
|
3036
|
+
normalized
|
|
2894
3037
|
);
|
|
2895
3038
|
}
|
|
2896
3039
|
}
|
|
@@ -2909,6 +3052,7 @@ var S3StateBackend = class {
|
|
|
2909
3052
|
* shows up exactly once.
|
|
2910
3053
|
*/
|
|
2911
3054
|
async listStacks() {
|
|
3055
|
+
await this.ensureClientForBucket();
|
|
2912
3056
|
try {
|
|
2913
3057
|
this.logger.debug("Listing all stacks");
|
|
2914
3058
|
const prefix = `${this.config.prefix}/`;
|
|
@@ -2959,10 +3103,11 @@ var S3StateBackend = class {
|
|
|
2959
3103
|
this.logger.debug(`Found ${refs.length} stack(s) across regions`);
|
|
2960
3104
|
return refs;
|
|
2961
3105
|
} catch (error) {
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
);
|
|
3106
|
+
const normalized = normalizeAwsError(error, {
|
|
3107
|
+
bucket: this.config.bucket,
|
|
3108
|
+
operation: "ListObjectsV2"
|
|
3109
|
+
});
|
|
3110
|
+
throw new StateError(`Failed to list stacks: ${normalized.message}`, normalized);
|
|
2966
3111
|
}
|
|
2967
3112
|
}
|
|
2968
3113
|
/**
|
|
@@ -6260,7 +6405,7 @@ Error: ${err.message || "Unknown error"}`,
|
|
|
6260
6405
|
import { InvokeCommand } from "@aws-sdk/client-lambda";
|
|
6261
6406
|
import { PublishCommand } from "@aws-sdk/client-sns";
|
|
6262
6407
|
import {
|
|
6263
|
-
S3Client as
|
|
6408
|
+
S3Client as S3Client6,
|
|
6264
6409
|
PutObjectCommand as PutObjectCommand4,
|
|
6265
6410
|
GetObjectCommand as GetObjectCommand3,
|
|
6266
6411
|
DeleteObjectCommand as DeleteObjectCommand3
|
|
@@ -6329,7 +6474,7 @@ var CustomResourceProvider = class _CustomResourceProvider {
|
|
|
6329
6474
|
setResponseBucket(bucket, bucketRegion) {
|
|
6330
6475
|
this.responseBucket = bucket;
|
|
6331
6476
|
if (bucketRegion) {
|
|
6332
|
-
this.s3Client = new
|
|
6477
|
+
this.s3Client = new S3Client6(bucketRegion ? { region: bucketRegion } : {});
|
|
6333
6478
|
}
|
|
6334
6479
|
}
|
|
6335
6480
|
/**
|
|
@@ -8844,11 +8989,14 @@ export {
|
|
|
8844
8989
|
SynthesisError,
|
|
8845
8990
|
Synthesizer,
|
|
8846
8991
|
TemplateParser,
|
|
8992
|
+
clearBucketRegionCache,
|
|
8847
8993
|
formatError,
|
|
8848
8994
|
getAwsClients,
|
|
8849
8995
|
getLogger,
|
|
8850
8996
|
isCdkdError,
|
|
8997
|
+
normalizeAwsError,
|
|
8851
8998
|
resetAwsClients,
|
|
8999
|
+
resolveBucketRegion,
|
|
8852
9000
|
setAwsClients,
|
|
8853
9001
|
setLogger
|
|
8854
9002
|
};
|