@go-to-k/cdkd 0.0.2 → 0.0.4
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/README.md +4 -4
- package/dist/cli.js +153 -1
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.0.4.tgz +0 -0
- package/dist/index.js +146 -0
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.0.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -306,16 +306,16 @@ Reproduce with `./tests/benchmark/run-benchmark.sh all`. See [tests/benchmark/RE
|
|
|
306
306
|
|
|
307
307
|
## Installation
|
|
308
308
|
|
|
309
|
-
### From npm
|
|
309
|
+
### From npm
|
|
310
310
|
|
|
311
311
|
```bash
|
|
312
|
-
npm i -g @go-to-k/cdkd
|
|
313
|
-
npm i -g @go-to-k/cdkd@0.
|
|
312
|
+
npm i -g @go-to-k/cdkd # latest release
|
|
313
|
+
npm i -g @go-to-k/cdkd@0.0.2 # pin to a specific version
|
|
314
314
|
```
|
|
315
315
|
|
|
316
316
|
The installed binary is `cdkd` — run it the same way in either install path.
|
|
317
317
|
|
|
318
|
-
>
|
|
318
|
+
> cdkd is an experimental / educational project and is not intended for production use — see the warning at the top of this README. Pin to a specific version if you need reproducible installs.
|
|
319
319
|
|
|
320
320
|
### From source
|
|
321
321
|
|
package/dist/cli.js
CHANGED
|
@@ -2893,6 +2893,7 @@ import {
|
|
|
2893
2893
|
GetObjectCommand,
|
|
2894
2894
|
PutObjectCommand as PutObjectCommand2,
|
|
2895
2895
|
DeleteObjectCommand,
|
|
2896
|
+
HeadBucketCommand as HeadBucketCommand2,
|
|
2896
2897
|
HeadObjectCommand as HeadObjectCommand2,
|
|
2897
2898
|
ListObjectsV2Command,
|
|
2898
2899
|
NoSuchKey
|
|
@@ -2909,6 +2910,28 @@ var S3StateBackend = class {
|
|
|
2909
2910
|
getStateKey(stackName) {
|
|
2910
2911
|
return `${this.config.prefix}/${stackName}/state.json`;
|
|
2911
2912
|
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Verify that the configured state bucket exists.
|
|
2915
|
+
*
|
|
2916
|
+
* Called early in deploy/destroy to fail fast before expensive work
|
|
2917
|
+
* (asset publishing, Docker builds) runs against a missing bucket.
|
|
2918
|
+
*/
|
|
2919
|
+
async verifyBucketExists() {
|
|
2920
|
+
try {
|
|
2921
|
+
await this.s3Client.send(new HeadBucketCommand2({ Bucket: this.config.bucket }));
|
|
2922
|
+
} catch (error) {
|
|
2923
|
+
const name = error.name;
|
|
2924
|
+
if (name === "NotFound" || name === "NoSuchBucket") {
|
|
2925
|
+
throw new StateError(
|
|
2926
|
+
`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.`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
throw new StateError(
|
|
2930
|
+
`Failed to verify state bucket '${this.config.bucket}': ${error instanceof Error ? error.message : String(error)}`,
|
|
2931
|
+
error instanceof Error ? error : void 0
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2912
2935
|
/**
|
|
2913
2936
|
* Check if state exists for a stack
|
|
2914
2937
|
*/
|
|
@@ -3496,6 +3519,11 @@ var TemplateParser = class {
|
|
|
3496
3519
|
|
|
3497
3520
|
// src/analyzer/dag-builder.ts
|
|
3498
3521
|
var { Graph, alg } = graphlib;
|
|
3522
|
+
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
3523
|
+
"AWS::IAM::Policy",
|
|
3524
|
+
"AWS::IAM::RolePolicy",
|
|
3525
|
+
"AWS::IAM::ManagedPolicy"
|
|
3526
|
+
]);
|
|
3499
3527
|
var DagBuilder = class {
|
|
3500
3528
|
logger = getLogger().child("DagBuilder");
|
|
3501
3529
|
parser = new TemplateParser();
|
|
@@ -3536,6 +3564,7 @@ var DagBuilder = class {
|
|
|
3536
3564
|
}
|
|
3537
3565
|
}
|
|
3538
3566
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
3567
|
+
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
3539
3568
|
if (!alg.isAcyclic(graph)) {
|
|
3540
3569
|
const cycles = this.findCycles(graph);
|
|
3541
3570
|
throw new DependencyError(
|
|
@@ -3678,6 +3707,123 @@ var DagBuilder = class {
|
|
|
3678
3707
|
const deps = this.getAllDependencies(graph, resourceA);
|
|
3679
3708
|
return deps.has(resourceB);
|
|
3680
3709
|
}
|
|
3710
|
+
/**
|
|
3711
|
+
* Add implicit edges from IAM::Policy resources to Custom Resources whose
|
|
3712
|
+
* ServiceToken Lambda's execution role those policies attach to.
|
|
3713
|
+
*
|
|
3714
|
+
* Returns the number of edges added.
|
|
3715
|
+
*/
|
|
3716
|
+
addCustomResourcePolicyEdges(graph, template) {
|
|
3717
|
+
const rolePolicies = this.buildRolePoliciesMap(template);
|
|
3718
|
+
if (rolePolicies.size === 0) {
|
|
3719
|
+
return 0;
|
|
3720
|
+
}
|
|
3721
|
+
let added = 0;
|
|
3722
|
+
for (const logicalId of this.parser.getResourceIds(template)) {
|
|
3723
|
+
const resource = this.parser.getResource(template, logicalId);
|
|
3724
|
+
if (!resource || !this.isCustomResourceType(resource.Type)) {
|
|
3725
|
+
continue;
|
|
3726
|
+
}
|
|
3727
|
+
const serviceToken = (resource.Properties ?? {})["ServiceToken"];
|
|
3728
|
+
const lambdaId = this.extractLogicalIdFromReference(serviceToken);
|
|
3729
|
+
if (!lambdaId)
|
|
3730
|
+
continue;
|
|
3731
|
+
const lambdaResource = this.parser.getResource(template, lambdaId);
|
|
3732
|
+
if (!lambdaResource || lambdaResource.Type !== "AWS::Lambda::Function") {
|
|
3733
|
+
continue;
|
|
3734
|
+
}
|
|
3735
|
+
const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})["Role"]);
|
|
3736
|
+
if (!roleId)
|
|
3737
|
+
continue;
|
|
3738
|
+
const policies = rolePolicies.get(roleId);
|
|
3739
|
+
if (!policies)
|
|
3740
|
+
continue;
|
|
3741
|
+
for (const policyId of policies) {
|
|
3742
|
+
if (policyId === logicalId)
|
|
3743
|
+
continue;
|
|
3744
|
+
if (!graph.hasNode(policyId))
|
|
3745
|
+
continue;
|
|
3746
|
+
if (graph.hasEdge(policyId, logicalId))
|
|
3747
|
+
continue;
|
|
3748
|
+
graph.setEdge(policyId, logicalId);
|
|
3749
|
+
added++;
|
|
3750
|
+
this.logger.debug(
|
|
3751
|
+
`Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
if (added > 0) {
|
|
3756
|
+
this.logger.debug(`Added ${added} implicit edges for custom resource policies`);
|
|
3757
|
+
}
|
|
3758
|
+
return added;
|
|
3759
|
+
}
|
|
3760
|
+
isCustomResourceType(type) {
|
|
3761
|
+
return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the
|
|
3765
|
+
* template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources
|
|
3766
|
+
* that attach to a role by Ref/GetAtt.
|
|
3767
|
+
*/
|
|
3768
|
+
buildRolePoliciesMap(template) {
|
|
3769
|
+
const map = /* @__PURE__ */ new Map();
|
|
3770
|
+
for (const [policyId, resource] of Object.entries(template.Resources)) {
|
|
3771
|
+
if (!IAM_ROLE_POLICY_TYPES.has(resource.Type))
|
|
3772
|
+
continue;
|
|
3773
|
+
for (const roleId of this.extractAttachedRoleIds(resource)) {
|
|
3774
|
+
let set = map.get(roleId);
|
|
3775
|
+
if (!set) {
|
|
3776
|
+
set = /* @__PURE__ */ new Set();
|
|
3777
|
+
map.set(roleId, set);
|
|
3778
|
+
}
|
|
3779
|
+
set.add(policyId);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
return map;
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Extract the logical IDs of IAM::Role resources that a policy resource
|
|
3786
|
+
* attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)
|
|
3787
|
+
* and `RoleName: Ref` (IAM::RolePolicy) shapes.
|
|
3788
|
+
*/
|
|
3789
|
+
extractAttachedRoleIds(resource) {
|
|
3790
|
+
const ids = [];
|
|
3791
|
+
const props = resource.Properties ?? {};
|
|
3792
|
+
const roles = props["Roles"];
|
|
3793
|
+
if (Array.isArray(roles)) {
|
|
3794
|
+
for (const entry of roles) {
|
|
3795
|
+
const id = this.extractLogicalIdFromReference(entry);
|
|
3796
|
+
if (id)
|
|
3797
|
+
ids.push(id);
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
const roleName = props["RoleName"];
|
|
3801
|
+
const roleNameId = this.extractLogicalIdFromReference(roleName);
|
|
3802
|
+
if (roleNameId)
|
|
3803
|
+
ids.push(roleNameId);
|
|
3804
|
+
return ids;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.
|
|
3808
|
+
* Returns undefined for literals or intrinsics we can't statically resolve
|
|
3809
|
+
* (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.
|
|
3810
|
+
*/
|
|
3811
|
+
extractLogicalIdFromReference(value) {
|
|
3812
|
+
if (typeof value !== "object" || value === null)
|
|
3813
|
+
return void 0;
|
|
3814
|
+
const obj = value;
|
|
3815
|
+
if ("Ref" in obj && typeof obj["Ref"] === "string") {
|
|
3816
|
+
const ref = obj["Ref"];
|
|
3817
|
+
return ref.startsWith("AWS::") ? void 0 : ref;
|
|
3818
|
+
}
|
|
3819
|
+
if ("Fn::GetAtt" in obj) {
|
|
3820
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
3821
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string") {
|
|
3822
|
+
return getAtt[0];
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
return void 0;
|
|
3826
|
+
}
|
|
3681
3827
|
};
|
|
3682
3828
|
|
|
3683
3829
|
// src/analyzer/replacement-rules.ts
|
|
@@ -26028,6 +26174,11 @@ async function deployCommand(stacks, options) {
|
|
|
26028
26174
|
...options.profile && { profile: options.profile }
|
|
26029
26175
|
});
|
|
26030
26176
|
setAwsClients(awsClients);
|
|
26177
|
+
const preflightStateBackend = new S3StateBackend(awsClients.s3, {
|
|
26178
|
+
bucket: stateBucket,
|
|
26179
|
+
prefix: options.statePrefix
|
|
26180
|
+
});
|
|
26181
|
+
await preflightStateBackend.verifyBucketExists();
|
|
26031
26182
|
let deployInterrupted = false;
|
|
26032
26183
|
const topLevelSigintHandler = () => {
|
|
26033
26184
|
if (deployInterrupted) {
|
|
@@ -26481,6 +26632,7 @@ async function destroyCommand(stackArgs, options) {
|
|
|
26481
26632
|
prefix: options.statePrefix
|
|
26482
26633
|
};
|
|
26483
26634
|
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig);
|
|
26635
|
+
await stateBackend.verifyBucketExists();
|
|
26484
26636
|
const lockManager = new LockManager(awsClients.s3, stateConfig);
|
|
26485
26637
|
const dagBuilder = new DagBuilder();
|
|
26486
26638
|
const providerRegistry = new ProviderRegistry();
|
|
@@ -26861,7 +27013,7 @@ function reorderArgs(argv) {
|
|
|
26861
27013
|
}
|
|
26862
27014
|
async function main() {
|
|
26863
27015
|
const program = new Command8();
|
|
26864
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.0.
|
|
27016
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.0.4");
|
|
26865
27017
|
program.addCommand(createBootstrapCommand());
|
|
26866
27018
|
program.addCommand(createSynthCommand());
|
|
26867
27019
|
program.addCommand(createDeployCommand());
|