@go-to-k/cdkd 0.220.3 → 0.220.5
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 +246 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1553,7 +1553,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
1553
1553
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
1554
1554
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
1555
1555
|
function getCdkdVersion() {
|
|
1556
|
-
return "0.220.
|
|
1556
|
+
return "0.220.5";
|
|
1557
1557
|
}
|
|
1558
1558
|
/**
|
|
1559
1559
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -1929,6 +1929,18 @@ function recordRunFailed(recorder, stackName, error) {
|
|
|
1929
1929
|
* lock + bounded retry. After exhaustion, the writer logs warn and
|
|
1930
1930
|
* continues; the index becomes stale until the next deploy/destroy
|
|
1931
1931
|
* updates it.
|
|
1932
|
+
*
|
|
1933
|
+
* Cross-region state bucket: like `S3StateBackend` and `LockManager`, the
|
|
1934
|
+
* store tolerates a state bucket that lives in a different AWS region from
|
|
1935
|
+
* the CLI's base region. Before its first S3 operation it resolves the
|
|
1936
|
+
* bucket's actual region via `GetBucketLocation` and, if it differs from
|
|
1937
|
+
* the supplied client's region, builds a private replacement client for
|
|
1938
|
+
* that region (issue #819 — without this, every index write/remove against
|
|
1939
|
+
* a cross-region bucket failed with S3's 301 PermanentRedirect, surfacing
|
|
1940
|
+
* as `Exports index ... failed (non-retryable): ... must be addressed using
|
|
1941
|
+
* the specified endpoint`; the index was silently never maintained
|
|
1942
|
+
* cross-region, although the canonical state.json — written through the
|
|
1943
|
+
* already-region-corrected `S3StateBackend` — stayed correct).
|
|
1932
1944
|
*/
|
|
1933
1945
|
/** Schema version for the exports index file. Separate from state.json's version. */
|
|
1934
1946
|
const EXPORT_INDEX_VERSION = 1;
|
|
@@ -1977,6 +1989,9 @@ var ExportIndexStore = class {
|
|
|
1977
1989
|
* where it actually matters. Reads (`lookup`) remain unsynchronized.
|
|
1978
1990
|
*/
|
|
1979
1991
|
writeChain = Promise.resolve();
|
|
1992
|
+
/** Memoization for the one-time bucket-region resolution (issue #819). */
|
|
1993
|
+
clientResolved = false;
|
|
1994
|
+
resolveInFlight = null;
|
|
1980
1995
|
constructor(s3Client, bucket, prefix, region, stateBackend, opts = {}) {
|
|
1981
1996
|
this.s3Client = s3Client;
|
|
1982
1997
|
this.bucket = bucket;
|
|
@@ -1993,6 +2008,72 @@ var ExportIndexStore = class {
|
|
|
1993
2008
|
return `${this.prefix}/_index/${this.region}/exports.json`;
|
|
1994
2009
|
}
|
|
1995
2010
|
/**
|
|
2011
|
+
* Resolve the state bucket's actual region and, if it differs from the
|
|
2012
|
+
* supplied client's configured region, replace the client reference with
|
|
2013
|
+
* a new S3Client pointed at the bucket's region (issue #819).
|
|
2014
|
+
*
|
|
2015
|
+
* Mirrors `LockManager.ensureClientForBucket()` (issue #803) with the same
|
|
2016
|
+
* two deliberate differences from `S3StateBackend`:
|
|
2017
|
+
*
|
|
2018
|
+
* - The replacement client reuses the original client's resolved
|
|
2019
|
+
* credentials provider, so `--profile` / static credentials carry over
|
|
2020
|
+
* without the four `ExportIndexStore` call sites having to thread client
|
|
2021
|
+
* options.
|
|
2022
|
+
* - The original client is NOT destroyed. It is the shared `AwsClients.s3`
|
|
2023
|
+
* instance that other components (state backend, lock manager) still hold
|
|
2024
|
+
* a reference to.
|
|
2025
|
+
*
|
|
2026
|
+
* `resolveBucketRegion` caches per bucket name for the process lifetime, so
|
|
2027
|
+
* when the state backend / lock manager have already resolved the same
|
|
2028
|
+
* bucket this incurs no additional `GetBucketLocation` call.
|
|
2029
|
+
*
|
|
2030
|
+
* Best-effort: if the supplied client does not expose the SDK
|
|
2031
|
+
* `config.region()` shape (e.g. a hand-rolled test double) the resolution
|
|
2032
|
+
* is skipped and the original client is used unchanged.
|
|
2033
|
+
*/
|
|
2034
|
+
async ensureClientForBucket() {
|
|
2035
|
+
if (this.clientResolved) return;
|
|
2036
|
+
if (this.resolveInFlight) return this.resolveInFlight;
|
|
2037
|
+
this.resolveInFlight = (async () => {
|
|
2038
|
+
try {
|
|
2039
|
+
const config = this.s3Client.config;
|
|
2040
|
+
if (!config || typeof config.region !== "function") {
|
|
2041
|
+
this.clientResolved = true;
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
const currentRegion = await config.region();
|
|
2045
|
+
const fallbackRegion = typeof currentRegion === "string" ? currentRegion : void 0;
|
|
2046
|
+
let probeCredentials;
|
|
2047
|
+
try {
|
|
2048
|
+
if (typeof config.credentials === "function") probeCredentials = await config.credentials();
|
|
2049
|
+
} catch {
|
|
2050
|
+
probeCredentials = void 0;
|
|
2051
|
+
}
|
|
2052
|
+
const bucketRegion = await resolveBucketRegion(this.bucket, {
|
|
2053
|
+
...probeCredentials && { credentials: probeCredentials },
|
|
2054
|
+
...fallbackRegion && { fallbackRegion }
|
|
2055
|
+
});
|
|
2056
|
+
if (bucketRegion !== currentRegion) {
|
|
2057
|
+
this.logger.debug(`State bucket '${this.bucket}' is in '${bucketRegion}' (exports-index client was '${String(currentRegion)}'); building a region-corrected S3 client for index operations.`);
|
|
2058
|
+
this.s3Client = new S3Client({
|
|
2059
|
+
region: bucketRegion,
|
|
2060
|
+
credentials: this.s3Client.config.credentials,
|
|
2061
|
+
logger: {
|
|
2062
|
+
debug: () => {},
|
|
2063
|
+
info: () => {},
|
|
2064
|
+
warn: () => {},
|
|
2065
|
+
error: () => {}
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
this.clientResolved = true;
|
|
2070
|
+
} finally {
|
|
2071
|
+
this.resolveInFlight = null;
|
|
2072
|
+
}
|
|
2073
|
+
})();
|
|
2074
|
+
return this.resolveInFlight;
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
1996
2077
|
* Look up an exported value by name. Returns the cached entry on hit,
|
|
1997
2078
|
* or `undefined` on miss. The caller is responsible for falling back
|
|
1998
2079
|
* to a state.json scan and (if found) patching the index via
|
|
@@ -2113,6 +2194,7 @@ var ExportIndexStore = class {
|
|
|
2113
2194
|
}
|
|
2114
2195
|
}
|
|
2115
2196
|
async readIndexRaw() {
|
|
2197
|
+
await this.ensureClientForBucket();
|
|
2116
2198
|
try {
|
|
2117
2199
|
const response = await this.s3Client.send(new GetObjectCommand({
|
|
2118
2200
|
Bucket: this.bucket,
|
|
@@ -2128,6 +2210,7 @@ var ExportIndexStore = class {
|
|
|
2128
2210
|
}
|
|
2129
2211
|
}
|
|
2130
2212
|
async writeIndex(next, expectedEtag) {
|
|
2213
|
+
await this.ensureClientForBucket();
|
|
2131
2214
|
const body = JSON.stringify(next, null, 2);
|
|
2132
2215
|
return (await this.s3Client.send(new PutObjectCommand({
|
|
2133
2216
|
Bucket: this.bucket,
|
|
@@ -19655,18 +19738,175 @@ var ECSProvider = class {
|
|
|
19655
19738
|
};
|
|
19656
19739
|
}
|
|
19657
19740
|
/**
|
|
19658
|
-
* Convert CFn Volumes to ECS SDK format
|
|
19741
|
+
* Convert CFn Volumes to ECS SDK format.
|
|
19742
|
+
*
|
|
19743
|
+
* Every nested volume-configuration block is PascalCase in the CFn
|
|
19744
|
+
* template and camelCase in the ECS SDK input, so each sub-block runs
|
|
19745
|
+
* through a dedicated converter — the same PascalCase->camelCase trap
|
|
19746
|
+
* already fixed for the ContainerDefinitions sub-arrays
|
|
19747
|
+
* (convertEnvironment / convertSecrets / convertMountPoints etc.).
|
|
19748
|
+
* Before issue #815, `Host` / `EFSVolumeConfiguration` were cast through
|
|
19749
|
+
* raw (so their nested keys reached the SDK still PascalCase) and
|
|
19750
|
+
* `DockerVolumeConfiguration` / `FSxWindowsFileServerVolumeConfiguration`
|
|
19751
|
+
* were not mapped at all (silently dropped).
|
|
19659
19752
|
*/
|
|
19660
19753
|
convertVolumes(volumes) {
|
|
19661
19754
|
if (!volumes) return void 0;
|
|
19662
19755
|
return volumes.map((v) => ({
|
|
19663
19756
|
name: v["Name"],
|
|
19664
|
-
host: v["Host"],
|
|
19665
|
-
|
|
19757
|
+
host: this.convertVolumeHost(v["Host"]),
|
|
19758
|
+
dockerVolumeConfiguration: this.convertDockerVolumeConfiguration(v["DockerVolumeConfiguration"]),
|
|
19759
|
+
efsVolumeConfiguration: this.convertEFSVolumeConfiguration(v["EFSVolumeConfiguration"]),
|
|
19760
|
+
fsxWindowsFileServerVolumeConfiguration: this.convertFSxWindowsVolumeConfiguration(v["FSxWindowsFileServerVolumeConfiguration"]),
|
|
19666
19761
|
configuredAtLaunch: this.coerceBool(v["ConfiguredAtLaunch"])
|
|
19667
19762
|
}));
|
|
19668
19763
|
}
|
|
19669
19764
|
/**
|
|
19765
|
+
* Convert CFn Volumes[].Host to ECS SDK format.
|
|
19766
|
+
* CFn: `{SourcePath}` -> SDK: `{sourcePath}`.
|
|
19767
|
+
*/
|
|
19768
|
+
convertVolumeHost(host) {
|
|
19769
|
+
if (!host) return void 0;
|
|
19770
|
+
return { sourcePath: host["SourcePath"] };
|
|
19771
|
+
}
|
|
19772
|
+
/**
|
|
19773
|
+
* Convert CFn Volumes[].DockerVolumeConfiguration to ECS SDK format.
|
|
19774
|
+
* CFn: `{Scope, Autoprovision, Driver, DriverOpts, Labels}`
|
|
19775
|
+
* -> SDK: `{scope, autoprovision, driver, driverOpts, labels}`.
|
|
19776
|
+
*/
|
|
19777
|
+
convertDockerVolumeConfiguration(config) {
|
|
19778
|
+
if (!config) return void 0;
|
|
19779
|
+
return {
|
|
19780
|
+
scope: config["Scope"],
|
|
19781
|
+
autoprovision: this.coerceBool(config["Autoprovision"]),
|
|
19782
|
+
driver: config["Driver"],
|
|
19783
|
+
driverOpts: config["DriverOpts"],
|
|
19784
|
+
labels: config["Labels"]
|
|
19785
|
+
};
|
|
19786
|
+
}
|
|
19787
|
+
/**
|
|
19788
|
+
* Convert CFn Volumes[].EFSVolumeConfiguration to ECS SDK format.
|
|
19789
|
+
* CFn: `{FilesystemId, RootDirectory, TransitEncryption,
|
|
19790
|
+
* TransitEncryptionPort, AuthorizationConfig}`
|
|
19791
|
+
* -> SDK: `{fileSystemId, rootDirectory, transitEncryption,
|
|
19792
|
+
* transitEncryptionPort, authorizationConfig}`.
|
|
19793
|
+
* Note the CFn property is `FilesystemId` (lowercase `s`) while the SDK
|
|
19794
|
+
* field is `fileSystemId` — they are not a simple first-letter case flip.
|
|
19795
|
+
*/
|
|
19796
|
+
convertEFSVolumeConfiguration(config) {
|
|
19797
|
+
if (!config) return void 0;
|
|
19798
|
+
return {
|
|
19799
|
+
fileSystemId: config["FilesystemId"],
|
|
19800
|
+
rootDirectory: config["RootDirectory"],
|
|
19801
|
+
transitEncryption: config["TransitEncryption"],
|
|
19802
|
+
transitEncryptionPort: config["TransitEncryptionPort"] !== void 0 ? Number(config["TransitEncryptionPort"]) : void 0,
|
|
19803
|
+
authorizationConfig: this.convertEFSAuthorizationConfig(config["AuthorizationConfig"])
|
|
19804
|
+
};
|
|
19805
|
+
}
|
|
19806
|
+
/**
|
|
19807
|
+
* Convert CFn EFSVolumeConfiguration.AuthorizationConfig to ECS SDK format.
|
|
19808
|
+
* CFn: `{AccessPointId, IAM}` -> SDK: `{accessPointId, iam}`.
|
|
19809
|
+
* Note the CFn key is `IAM` (all caps), NOT `Iam` — not a simple
|
|
19810
|
+
* first-letter case flip (verified against the CDK L1 `IAM` mapping).
|
|
19811
|
+
*/
|
|
19812
|
+
convertEFSAuthorizationConfig(config) {
|
|
19813
|
+
if (!config) return void 0;
|
|
19814
|
+
return {
|
|
19815
|
+
accessPointId: config["AccessPointId"],
|
|
19816
|
+
iam: config["IAM"]
|
|
19817
|
+
};
|
|
19818
|
+
}
|
|
19819
|
+
/**
|
|
19820
|
+
* Convert CFn Volumes[].FSxWindowsFileServerVolumeConfiguration to ECS
|
|
19821
|
+
* SDK format.
|
|
19822
|
+
* CFn: `{FileSystemId, RootDirectory, AuthorizationConfig}`
|
|
19823
|
+
* -> SDK: `{fileSystemId, rootDirectory, authorizationConfig}`.
|
|
19824
|
+
*/
|
|
19825
|
+
convertFSxWindowsVolumeConfiguration(config) {
|
|
19826
|
+
if (!config) return void 0;
|
|
19827
|
+
return {
|
|
19828
|
+
fileSystemId: config["FileSystemId"],
|
|
19829
|
+
rootDirectory: config["RootDirectory"],
|
|
19830
|
+
authorizationConfig: this.convertFSxWindowsAuthorizationConfig(config["AuthorizationConfig"])
|
|
19831
|
+
};
|
|
19832
|
+
}
|
|
19833
|
+
/**
|
|
19834
|
+
* Convert CFn FSxWindowsFileServerVolumeConfiguration.AuthorizationConfig
|
|
19835
|
+
* to ECS SDK format.
|
|
19836
|
+
* CFn: `{CredentialsParameter, Domain}`
|
|
19837
|
+
* -> SDK: `{credentialsParameter, domain}`.
|
|
19838
|
+
*/
|
|
19839
|
+
convertFSxWindowsAuthorizationConfig(config) {
|
|
19840
|
+
if (!config) return void 0;
|
|
19841
|
+
return {
|
|
19842
|
+
credentialsParameter: config["CredentialsParameter"],
|
|
19843
|
+
domain: config["Domain"]
|
|
19844
|
+
};
|
|
19845
|
+
}
|
|
19846
|
+
/**
|
|
19847
|
+
* Convert the camelCase SDK `volumes` shape returned by
|
|
19848
|
+
* DescribeTaskDefinition back to the PascalCase CFn template form, so the
|
|
19849
|
+
* `readCurrentState` snapshot matches the deploy-time template
|
|
19850
|
+
* representation for drift comparison (issue #815). Only volume keys
|
|
19851
|
+
* present on the SDK side are emitted, so a future field cdkd does not
|
|
19852
|
+
* map cannot surface as phantom drift. TaskDefinitions are immutable
|
|
19853
|
+
* replace-only today, so this is forward-looking normalization.
|
|
19854
|
+
*/
|
|
19855
|
+
volumesToCfn(volumes) {
|
|
19856
|
+
if (!volumes) return [];
|
|
19857
|
+
return volumes.map((v) => {
|
|
19858
|
+
const out = {};
|
|
19859
|
+
if (v.name !== void 0) out["Name"] = v.name;
|
|
19860
|
+
if (v.host !== void 0) {
|
|
19861
|
+
const host = {};
|
|
19862
|
+
if (v.host.sourcePath !== void 0) host["SourcePath"] = v.host.sourcePath;
|
|
19863
|
+
out["Host"] = host;
|
|
19864
|
+
}
|
|
19865
|
+
if (v.dockerVolumeConfiguration !== void 0) {
|
|
19866
|
+
const d = v.dockerVolumeConfiguration;
|
|
19867
|
+
const docker = {};
|
|
19868
|
+
if (d.scope !== void 0) docker["Scope"] = d.scope;
|
|
19869
|
+
if (d.autoprovision !== void 0) docker["Autoprovision"] = d.autoprovision;
|
|
19870
|
+
if (d.driver !== void 0) docker["Driver"] = d.driver;
|
|
19871
|
+
if (d.driverOpts !== void 0) docker["DriverOpts"] = d.driverOpts;
|
|
19872
|
+
if (d.labels !== void 0) docker["Labels"] = d.labels;
|
|
19873
|
+
out["DockerVolumeConfiguration"] = docker;
|
|
19874
|
+
}
|
|
19875
|
+
if (v.efsVolumeConfiguration !== void 0) {
|
|
19876
|
+
const e = v.efsVolumeConfiguration;
|
|
19877
|
+
const efs = {};
|
|
19878
|
+
if (e.fileSystemId !== void 0) efs["FilesystemId"] = e.fileSystemId;
|
|
19879
|
+
if (e.rootDirectory !== void 0) efs["RootDirectory"] = e.rootDirectory;
|
|
19880
|
+
if (e.transitEncryption !== void 0) efs["TransitEncryption"] = e.transitEncryption;
|
|
19881
|
+
if (e.transitEncryptionPort !== void 0) efs["TransitEncryptionPort"] = e.transitEncryptionPort;
|
|
19882
|
+
if (e.authorizationConfig !== void 0) {
|
|
19883
|
+
const a = e.authorizationConfig;
|
|
19884
|
+
const auth = {};
|
|
19885
|
+
if (a.accessPointId !== void 0) auth["AccessPointId"] = a.accessPointId;
|
|
19886
|
+
if (a.iam !== void 0) auth["IAM"] = a.iam;
|
|
19887
|
+
efs["AuthorizationConfig"] = auth;
|
|
19888
|
+
}
|
|
19889
|
+
out["EFSVolumeConfiguration"] = efs;
|
|
19890
|
+
}
|
|
19891
|
+
if (v.fsxWindowsFileServerVolumeConfiguration !== void 0) {
|
|
19892
|
+
const f = v.fsxWindowsFileServerVolumeConfiguration;
|
|
19893
|
+
const fsx = {};
|
|
19894
|
+
if (f.fileSystemId !== void 0) fsx["FileSystemId"] = f.fileSystemId;
|
|
19895
|
+
if (f.rootDirectory !== void 0) fsx["RootDirectory"] = f.rootDirectory;
|
|
19896
|
+
if (f.authorizationConfig !== void 0) {
|
|
19897
|
+
const a = f.authorizationConfig;
|
|
19898
|
+
const auth = {};
|
|
19899
|
+
if (a.credentialsParameter !== void 0) auth["CredentialsParameter"] = a.credentialsParameter;
|
|
19900
|
+
if (a.domain !== void 0) auth["Domain"] = a.domain;
|
|
19901
|
+
fsx["AuthorizationConfig"] = auth;
|
|
19902
|
+
}
|
|
19903
|
+
out["FSxWindowsFileServerVolumeConfiguration"] = fsx;
|
|
19904
|
+
}
|
|
19905
|
+
if (v.configuredAtLaunch !== void 0) out["ConfiguredAtLaunch"] = v.configuredAtLaunch;
|
|
19906
|
+
return out;
|
|
19907
|
+
});
|
|
19908
|
+
}
|
|
19909
|
+
/**
|
|
19670
19910
|
* Coerce a CFn boolean property to a real boolean at the wire boundary.
|
|
19671
19911
|
* CFn templates can carry booleans as the strings "true" / "false"
|
|
19672
19912
|
* (e.g. via Fn::Sub / parameter plumbing), so the SDK input must
|
|
@@ -19845,7 +20085,7 @@ var ECSProvider = class {
|
|
|
19845
20085
|
result["RequiresCompatibilities"] = td.requiresCompatibilities ? [...td.requiresCompatibilities] : [];
|
|
19846
20086
|
if (td.executionRoleArn !== void 0) result["ExecutionRoleArn"] = td.executionRoleArn;
|
|
19847
20087
|
if (td.taskRoleArn !== void 0) result["TaskRoleArn"] = td.taskRoleArn;
|
|
19848
|
-
result["Volumes"] = td.volumes
|
|
20088
|
+
result["Volumes"] = this.volumesToCfn(td.volumes);
|
|
19849
20089
|
result["PlacementConstraints"] = td.placementConstraints ?? [];
|
|
19850
20090
|
if (td.runtimePlatform) result["RuntimePlatform"] = td.runtimePlatform;
|
|
19851
20091
|
if (td.proxyConfiguration) result["ProxyConfiguration"] = td.proxyConfiguration;
|
|
@@ -53550,7 +53790,7 @@ function reorderArgs(argv) {
|
|
|
53550
53790
|
async function main() {
|
|
53551
53791
|
installPipeCloseHandler();
|
|
53552
53792
|
const program = new Command();
|
|
53553
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.220.
|
|
53793
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.220.5");
|
|
53554
53794
|
program.addCommand(createBootstrapCommand());
|
|
53555
53795
|
program.addCommand(createSynthCommand());
|
|
53556
53796
|
program.addCommand(createListCommand());
|