@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
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -2508,6 +2508,7 @@ import {
|
|
|
2508
2508
|
GetObjectCommand,
|
|
2509
2509
|
PutObjectCommand as PutObjectCommand2,
|
|
2510
2510
|
DeleteObjectCommand,
|
|
2511
|
+
HeadBucketCommand,
|
|
2511
2512
|
HeadObjectCommand as HeadObjectCommand2,
|
|
2512
2513
|
ListObjectsV2Command,
|
|
2513
2514
|
NoSuchKey
|
|
@@ -2524,6 +2525,28 @@ var S3StateBackend = class {
|
|
|
2524
2525
|
getStateKey(stackName) {
|
|
2525
2526
|
return `${this.config.prefix}/${stackName}/state.json`;
|
|
2526
2527
|
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Verify that the configured state bucket exists.
|
|
2530
|
+
*
|
|
2531
|
+
* Called early in deploy/destroy to fail fast before expensive work
|
|
2532
|
+
* (asset publishing, Docker builds) runs against a missing bucket.
|
|
2533
|
+
*/
|
|
2534
|
+
async verifyBucketExists() {
|
|
2535
|
+
try {
|
|
2536
|
+
await this.s3Client.send(new HeadBucketCommand({ Bucket: this.config.bucket }));
|
|
2537
|
+
} catch (error) {
|
|
2538
|
+
const name = error.name;
|
|
2539
|
+
if (name === "NotFound" || name === "NoSuchBucket") {
|
|
2540
|
+
throw new StateError(
|
|
2541
|
+
`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.`
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
throw new StateError(
|
|
2545
|
+
`Failed to verify state bucket '${this.config.bucket}': ${error instanceof Error ? error.message : String(error)}`,
|
|
2546
|
+
error instanceof Error ? error : void 0
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2527
2550
|
/**
|
|
2528
2551
|
* Check if state exists for a stack
|
|
2529
2552
|
*/
|
|
@@ -3109,6 +3132,11 @@ var TemplateParser = class {
|
|
|
3109
3132
|
// src/analyzer/dag-builder.ts
|
|
3110
3133
|
import graphlib from "graphlib";
|
|
3111
3134
|
var { Graph, alg } = graphlib;
|
|
3135
|
+
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
3136
|
+
"AWS::IAM::Policy",
|
|
3137
|
+
"AWS::IAM::RolePolicy",
|
|
3138
|
+
"AWS::IAM::ManagedPolicy"
|
|
3139
|
+
]);
|
|
3112
3140
|
var DagBuilder = class {
|
|
3113
3141
|
logger = getLogger().child("DagBuilder");
|
|
3114
3142
|
parser = new TemplateParser();
|
|
@@ -3149,6 +3177,7 @@ var DagBuilder = class {
|
|
|
3149
3177
|
}
|
|
3150
3178
|
}
|
|
3151
3179
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
3180
|
+
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
3152
3181
|
if (!alg.isAcyclic(graph)) {
|
|
3153
3182
|
const cycles = this.findCycles(graph);
|
|
3154
3183
|
throw new DependencyError(
|
|
@@ -3291,6 +3320,123 @@ var DagBuilder = class {
|
|
|
3291
3320
|
const deps = this.getAllDependencies(graph, resourceA);
|
|
3292
3321
|
return deps.has(resourceB);
|
|
3293
3322
|
}
|
|
3323
|
+
/**
|
|
3324
|
+
* Add implicit edges from IAM::Policy resources to Custom Resources whose
|
|
3325
|
+
* ServiceToken Lambda's execution role those policies attach to.
|
|
3326
|
+
*
|
|
3327
|
+
* Returns the number of edges added.
|
|
3328
|
+
*/
|
|
3329
|
+
addCustomResourcePolicyEdges(graph, template) {
|
|
3330
|
+
const rolePolicies = this.buildRolePoliciesMap(template);
|
|
3331
|
+
if (rolePolicies.size === 0) {
|
|
3332
|
+
return 0;
|
|
3333
|
+
}
|
|
3334
|
+
let added = 0;
|
|
3335
|
+
for (const logicalId of this.parser.getResourceIds(template)) {
|
|
3336
|
+
const resource = this.parser.getResource(template, logicalId);
|
|
3337
|
+
if (!resource || !this.isCustomResourceType(resource.Type)) {
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
const serviceToken = (resource.Properties ?? {})["ServiceToken"];
|
|
3341
|
+
const lambdaId = this.extractLogicalIdFromReference(serviceToken);
|
|
3342
|
+
if (!lambdaId)
|
|
3343
|
+
continue;
|
|
3344
|
+
const lambdaResource = this.parser.getResource(template, lambdaId);
|
|
3345
|
+
if (!lambdaResource || lambdaResource.Type !== "AWS::Lambda::Function") {
|
|
3346
|
+
continue;
|
|
3347
|
+
}
|
|
3348
|
+
const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})["Role"]);
|
|
3349
|
+
if (!roleId)
|
|
3350
|
+
continue;
|
|
3351
|
+
const policies = rolePolicies.get(roleId);
|
|
3352
|
+
if (!policies)
|
|
3353
|
+
continue;
|
|
3354
|
+
for (const policyId of policies) {
|
|
3355
|
+
if (policyId === logicalId)
|
|
3356
|
+
continue;
|
|
3357
|
+
if (!graph.hasNode(policyId))
|
|
3358
|
+
continue;
|
|
3359
|
+
if (graph.hasEdge(policyId, logicalId))
|
|
3360
|
+
continue;
|
|
3361
|
+
graph.setEdge(policyId, logicalId);
|
|
3362
|
+
added++;
|
|
3363
|
+
this.logger.debug(
|
|
3364
|
+
`Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
if (added > 0) {
|
|
3369
|
+
this.logger.debug(`Added ${added} implicit edges for custom resource policies`);
|
|
3370
|
+
}
|
|
3371
|
+
return added;
|
|
3372
|
+
}
|
|
3373
|
+
isCustomResourceType(type) {
|
|
3374
|
+
return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the
|
|
3378
|
+
* template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources
|
|
3379
|
+
* that attach to a role by Ref/GetAtt.
|
|
3380
|
+
*/
|
|
3381
|
+
buildRolePoliciesMap(template) {
|
|
3382
|
+
const map = /* @__PURE__ */ new Map();
|
|
3383
|
+
for (const [policyId, resource] of Object.entries(template.Resources)) {
|
|
3384
|
+
if (!IAM_ROLE_POLICY_TYPES.has(resource.Type))
|
|
3385
|
+
continue;
|
|
3386
|
+
for (const roleId of this.extractAttachedRoleIds(resource)) {
|
|
3387
|
+
let set = map.get(roleId);
|
|
3388
|
+
if (!set) {
|
|
3389
|
+
set = /* @__PURE__ */ new Set();
|
|
3390
|
+
map.set(roleId, set);
|
|
3391
|
+
}
|
|
3392
|
+
set.add(policyId);
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
return map;
|
|
3396
|
+
}
|
|
3397
|
+
/**
|
|
3398
|
+
* Extract the logical IDs of IAM::Role resources that a policy resource
|
|
3399
|
+
* attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)
|
|
3400
|
+
* and `RoleName: Ref` (IAM::RolePolicy) shapes.
|
|
3401
|
+
*/
|
|
3402
|
+
extractAttachedRoleIds(resource) {
|
|
3403
|
+
const ids = [];
|
|
3404
|
+
const props = resource.Properties ?? {};
|
|
3405
|
+
const roles = props["Roles"];
|
|
3406
|
+
if (Array.isArray(roles)) {
|
|
3407
|
+
for (const entry of roles) {
|
|
3408
|
+
const id = this.extractLogicalIdFromReference(entry);
|
|
3409
|
+
if (id)
|
|
3410
|
+
ids.push(id);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
const roleName = props["RoleName"];
|
|
3414
|
+
const roleNameId = this.extractLogicalIdFromReference(roleName);
|
|
3415
|
+
if (roleNameId)
|
|
3416
|
+
ids.push(roleNameId);
|
|
3417
|
+
return ids;
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.
|
|
3421
|
+
* Returns undefined for literals or intrinsics we can't statically resolve
|
|
3422
|
+
* (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.
|
|
3423
|
+
*/
|
|
3424
|
+
extractLogicalIdFromReference(value) {
|
|
3425
|
+
if (typeof value !== "object" || value === null)
|
|
3426
|
+
return void 0;
|
|
3427
|
+
const obj = value;
|
|
3428
|
+
if ("Ref" in obj && typeof obj["Ref"] === "string") {
|
|
3429
|
+
const ref = obj["Ref"];
|
|
3430
|
+
return ref.startsWith("AWS::") ? void 0 : ref;
|
|
3431
|
+
}
|
|
3432
|
+
if ("Fn::GetAtt" in obj) {
|
|
3433
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
3434
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string") {
|
|
3435
|
+
return getAtt[0];
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
return void 0;
|
|
3439
|
+
}
|
|
3294
3440
|
};
|
|
3295
3441
|
|
|
3296
3442
|
// src/analyzer/replacement-rules.ts
|