@go-to-k/cdkd 0.51.0 → 0.51.2

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 CHANGED
@@ -8481,6 +8481,7 @@ import {
8481
8481
  UpdateAssumeRolePolicyCommand,
8482
8482
  DeleteRoleCommand,
8483
8483
  GetRoleCommand,
8484
+ GetRolePolicyCommand,
8484
8485
  PutRolePolicyCommand,
8485
8486
  DeleteRolePolicyCommand,
8486
8487
  ListRolePoliciesCommand,
@@ -9093,8 +9094,14 @@ var IAMRoleProvider = class {
9093
9094
  * against state's already-parsed object.
9094
9095
  *
9095
9096
  * Coverage and shape decisions:
9096
- * - `RoleName`, `Description`, `MaxSessionDuration`, `Path`,
9097
- * `PermissionsBoundary` — straight from `Role.*`.
9097
+ * - `RoleName`, `Description`, `MaxSessionDuration`, `Path` — straight
9098
+ * from `Role.*`.
9099
+ * - `PermissionsBoundary` — emitted as `'' ` placeholder when AWS has
9100
+ * none, so a console-side ADD on a role that was deployed without a
9101
+ * boundary surfaces as drift. (The drift comparator's top-level walk
9102
+ * is state-keys-only; without the always-emit placeholder a fresh
9103
+ * `PermissionsBoundary` on the AWS side would never enter
9104
+ * `observedProperties` and the comparator would silently ignore it.)
9098
9105
  * - `AssumeRolePolicyDocument` — `Role.AssumeRolePolicyDocument` is a
9099
9106
  * URL-encoded JSON string; we URL-decode + JSON-parse so cdkd state's
9100
9107
  * object form compares cleanly. (Both shapes — string and object — are
@@ -9102,20 +9109,23 @@ var IAMRoleProvider = class {
9102
9109
  * after intrinsic resolution.)
9103
9110
  * - `ManagedPolicyArns` — array of ARN strings from
9104
9111
  * `ListAttachedRolePolicies`.
9105
- * - `Policies` (inline policies with `PolicyDocument` bodies) is
9106
- * intentionally omitted: surfacing names without bodies guarantees a
9107
- * PolicyDocument-shaped drift on every role, and fetching every body
9108
- * costs one extra `GetRolePolicy` per inline policy. Out of scope for
9109
- * v1 drift detection on inline IAM policy bodies can ship in a
9110
- * follow-up.
9112
+ * - `Policies` inline policies surfaced as `[{PolicyName, PolicyDocument}]`.
9113
+ * `ListRolePolicies` for names + `GetRolePolicy` per name for the
9114
+ * body (URL-decoded + JSON-parsed). Ordering is reconciled against
9115
+ * state's `Policies` array (when supplied via the `properties`
9116
+ * parameter) so a state-vs-AWS positional compare doesn't fire false
9117
+ * drift purely from `ListRolePolicies` returning lexicographic order;
9118
+ * AWS-only policies (added via console) are appended at the end so
9119
+ * they still surface as drift via length / content mismatch.
9111
9120
  * - `Tags` is surfaced via `ListRoleTags` (paginated). CDK's `aws:*`
9112
9121
  * auto-tags are filtered out by `normalizeAwsTagsToCfn` so they don't
9113
- * fire false-positive drift; the result key is omitted entirely when
9114
- * AWS reports no user tags (matches `create()`'s behavior).
9122
+ * fire false-positive drift; always emitted (even when empty) so a
9123
+ * console-side tag ADD on an originally-untagged role surfaces as
9124
+ * drift on the v3 observedProperties baseline.
9115
9125
  *
9116
9126
  * Returns `undefined` when the role is gone (`NoSuchEntityException`).
9117
9127
  */
9118
- async readCurrentState(physicalId, _logicalId, _resourceType) {
9128
+ async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
9119
9129
  let role;
9120
9130
  try {
9121
9131
  const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));
@@ -9136,9 +9146,7 @@ var IAMRoleProvider = class {
9136
9146
  }
9137
9147
  if (role.Path !== void 0)
9138
9148
  result["Path"] = role.Path;
9139
- if (role.PermissionsBoundary?.PermissionsBoundaryArn !== void 0) {
9140
- result["PermissionsBoundary"] = role.PermissionsBoundary.PermissionsBoundaryArn;
9141
- }
9149
+ result["PermissionsBoundary"] = role.PermissionsBoundary?.PermissionsBoundaryArn ?? "";
9142
9150
  if (role.AssumeRolePolicyDocument) {
9143
9151
  try {
9144
9152
  result["AssumeRolePolicyDocument"] = JSON.parse(
@@ -9158,6 +9166,59 @@ var IAMRoleProvider = class {
9158
9166
  if (!(err instanceof NoSuchEntityException))
9159
9167
  throw err;
9160
9168
  }
9169
+ try {
9170
+ const policyNames = [];
9171
+ let policyMarker;
9172
+ while (true) {
9173
+ const listResp = await this.iamClient.send(
9174
+ new ListRolePoliciesCommand({
9175
+ RoleName: physicalId,
9176
+ ...policyMarker ? { Marker: policyMarker } : {}
9177
+ })
9178
+ );
9179
+ for (const name of listResp.PolicyNames ?? [])
9180
+ policyNames.push(name);
9181
+ if (!listResp.IsTruncated)
9182
+ break;
9183
+ policyMarker = listResp.Marker;
9184
+ }
9185
+ const bodies = /* @__PURE__ */ new Map();
9186
+ await Promise.all(
9187
+ policyNames.map(async (name) => {
9188
+ const resp = await this.iamClient.send(
9189
+ new GetRolePolicyCommand({ RoleName: physicalId, PolicyName: name })
9190
+ );
9191
+ if (!resp.PolicyDocument)
9192
+ return;
9193
+ let parsed;
9194
+ try {
9195
+ parsed = JSON.parse(decodeURIComponent(resp.PolicyDocument));
9196
+ } catch {
9197
+ parsed = resp.PolicyDocument;
9198
+ }
9199
+ bodies.set(name, parsed);
9200
+ })
9201
+ );
9202
+ const statePolicies = properties?.["Policies"] ?? [];
9203
+ const remaining = new Set(bodies.keys());
9204
+ const inline = [];
9205
+ for (const sp of statePolicies) {
9206
+ const name = sp?.PolicyName;
9207
+ if (typeof name !== "string")
9208
+ continue;
9209
+ if (bodies.has(name)) {
9210
+ inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });
9211
+ remaining.delete(name);
9212
+ }
9213
+ }
9214
+ for (const name of [...remaining].sort()) {
9215
+ inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });
9216
+ }
9217
+ result["Policies"] = inline;
9218
+ } catch (err) {
9219
+ if (!(err instanceof NoSuchEntityException))
9220
+ throw err;
9221
+ }
9161
9222
  try {
9162
9223
  const collected = [];
9163
9224
  let marker;
@@ -9185,18 +9246,6 @@ var IAMRoleProvider = class {
9185
9246
  }
9186
9247
  return result;
9187
9248
  }
9188
- /**
9189
- * `Policies` (inline policy bodies) are intentionally omitted from
9190
- * `readCurrentState`: surfacing the names without bodies would
9191
- * guarantee a `PolicyDocument`-shaped drift on every role, and
9192
- * fetching every body costs one extra `GetRolePolicy` per inline
9193
- * policy. Tell the drift comparator to skip the whole subtree until a
9194
- * dedicated PR adds proper inline-policy drift via per-name
9195
- * `GetRolePolicy`.
9196
- */
9197
- getDriftUnknownPaths() {
9198
- return ["Policies"];
9199
- }
9200
9249
  /**
9201
9250
  * Adopt an existing IAM role into cdkd state.
9202
9251
  *
@@ -9259,7 +9308,7 @@ import {
9259
9308
  DeleteGroupPolicyCommand,
9260
9309
  PutUserPolicyCommand,
9261
9310
  DeleteUserPolicyCommand,
9262
- GetRolePolicyCommand,
9311
+ GetRolePolicyCommand as GetRolePolicyCommand2,
9263
9312
  GetGroupPolicyCommand,
9264
9313
  GetUserPolicyCommand,
9265
9314
  NoSuchEntityException as NoSuchEntityException2
@@ -9648,7 +9697,7 @@ var IAMPolicyProvider = class {
9648
9697
  try {
9649
9698
  if (roles && roles.length > 0) {
9650
9699
  const resp = await this.iamClient.send(
9651
- new GetRolePolicyCommand({ RoleName: roles[0], PolicyName: policyName })
9700
+ new GetRolePolicyCommand2({ RoleName: roles[0], PolicyName: policyName })
9652
9701
  );
9653
9702
  liveDocument = this.decodePolicyDocument(resp.PolicyDocument);
9654
9703
  } else if (groups && groups.length > 0) {
@@ -10023,9 +10072,11 @@ import {
10023
10072
  PutUserPolicyCommand as PutUserPolicyCommand2,
10024
10073
  DeleteUserPolicyCommand as DeleteUserPolicyCommand2,
10025
10074
  ListUserPoliciesCommand,
10075
+ GetUserPolicyCommand as GetUserPolicyCommand2,
10026
10076
  PutGroupPolicyCommand as PutGroupPolicyCommand2,
10027
10077
  DeleteGroupPolicyCommand as DeleteGroupPolicyCommand2,
10028
10078
  ListGroupPoliciesCommand,
10079
+ GetGroupPolicyCommand as GetGroupPolicyCommand2,
10029
10080
  CreateLoginProfileCommand,
10030
10081
  UpdateLoginProfileCommand,
10031
10082
  AddUserToGroupCommand,
@@ -11052,17 +11103,21 @@ var IAMUserGroupProvider = class {
11052
11103
  * UserToGroupAddition in CFn-property shape.
11053
11104
  *
11054
11105
  * - **AWS::IAM::User**: `GetUser` for `UserName`, `Path`,
11055
- * `PermissionsBoundary` (re-shaped from `PermissionsBoundary.Arn`);
11106
+ * `PermissionsBoundary` (re-shaped from `PermissionsBoundary.Arn`,
11107
+ * always-emit `''` placeholder so console-side ADD on a user
11108
+ * deployed without a boundary surfaces as drift);
11056
11109
  * `ListAttachedUserPolicies` for `ManagedPolicyArns`;
11057
- * `ListGroupsForUser` for `Groups`. `Tags`, `LoginProfile`, and
11058
- * `Policies` (inline policy bodies) are intentionally omitted
11059
- * same shape decisions as `iam-role-provider` (LoginProfile contains a
11060
- * one-time password and inline policy bodies cost N extra round-trips
11061
- * that are out of scope for v1).
11110
+ * `ListGroupsForUser` for `Groups`; `ListUserPolicies` +
11111
+ * `GetUserPolicy` per name for inline `Policies` (URL-decoded +
11112
+ * JSON-parsed, capped at IAM's documented 10-per-user limit, order
11113
+ * reconciled against state's `Policies` array). `Tags` and
11114
+ * `LoginProfile` remain omitted Tags will land in a follow-up,
11115
+ * LoginProfile contains a one-time password we never want to surface
11116
+ * through drift.
11062
11117
  * - **AWS::IAM::Group**: `GetGroup` for `GroupName`, `Path`;
11063
- * `ListAttachedGroupPolicies` for `ManagedPolicyArns`. `Policies`
11064
- * (inline policy bodies) is omitted for the same reason as User /
11065
- * Role.
11118
+ * `ListAttachedGroupPolicies` for `ManagedPolicyArns`;
11119
+ * `ListGroupPolicies` + `GetGroupPolicy` per name for inline
11120
+ * `Policies`.
11066
11121
  * - **AWS::IAM::UserToGroupAddition**: SKIPPED — returns `undefined`
11067
11122
  * because the resource is metadata-only (group-membership attachments
11068
11123
  * written via `AddUserToGroup`). A meaningful drift check would
@@ -11074,12 +11129,12 @@ var IAMUserGroupProvider = class {
11074
11129
  * Returns `undefined` when the user / group is gone
11075
11130
  * (`NoSuchEntityException`).
11076
11131
  */
11077
- async readCurrentState(physicalId, logicalId, resourceType) {
11132
+ async readCurrentState(physicalId, logicalId, resourceType, properties) {
11078
11133
  switch (resourceType) {
11079
11134
  case "AWS::IAM::User":
11080
- return this.readUserCurrentState(physicalId);
11135
+ return this.readUserCurrentState(physicalId, properties);
11081
11136
  case "AWS::IAM::Group":
11082
- return this.readGroupCurrentState(physicalId);
11137
+ return this.readGroupCurrentState(physicalId, properties);
11083
11138
  case "AWS::IAM::UserToGroupAddition":
11084
11139
  return void 0;
11085
11140
  default:
@@ -11089,7 +11144,7 @@ var IAMUserGroupProvider = class {
11089
11144
  return void 0;
11090
11145
  }
11091
11146
  }
11092
- async readUserCurrentState(physicalId) {
11147
+ async readUserCurrentState(physicalId, properties) {
11093
11148
  let user;
11094
11149
  try {
11095
11150
  const resp = await this.iamClient.send(new GetUserCommand({ UserName: physicalId }));
@@ -11106,9 +11161,7 @@ var IAMUserGroupProvider = class {
11106
11161
  result["UserName"] = user.UserName;
11107
11162
  if (user.Path !== void 0)
11108
11163
  result["Path"] = user.Path;
11109
- if (user.PermissionsBoundary?.PermissionsBoundaryArn !== void 0) {
11110
- result["PermissionsBoundary"] = user.PermissionsBoundary.PermissionsBoundaryArn;
11111
- }
11164
+ result["PermissionsBoundary"] = user.PermissionsBoundary?.PermissionsBoundaryArn ?? "";
11112
11165
  try {
11113
11166
  const attached = await this.iamClient.send(
11114
11167
  new ListAttachedUserPoliciesCommand({ UserName: physicalId })
@@ -11129,9 +11182,20 @@ var IAMUserGroupProvider = class {
11129
11182
  if (!(err instanceof NoSuchEntityException4))
11130
11183
  throw err;
11131
11184
  }
11185
+ try {
11186
+ const inline = await this.collectInlinePolicies(
11187
+ "user",
11188
+ physicalId,
11189
+ properties?.["Policies"] ?? []
11190
+ );
11191
+ result["Policies"] = inline;
11192
+ } catch (err) {
11193
+ if (!(err instanceof NoSuchEntityException4))
11194
+ throw err;
11195
+ }
11132
11196
  return result;
11133
11197
  }
11134
- async readGroupCurrentState(physicalId) {
11198
+ async readGroupCurrentState(physicalId, properties) {
11135
11199
  let group;
11136
11200
  try {
11137
11201
  const resp = await this.iamClient.send(new GetGroupCommand({ GroupName: physicalId }));
@@ -11158,8 +11222,83 @@ var IAMUserGroupProvider = class {
11158
11222
  if (!(err instanceof NoSuchEntityException4))
11159
11223
  throw err;
11160
11224
  }
11225
+ try {
11226
+ const inline = await this.collectInlinePolicies(
11227
+ "group",
11228
+ physicalId,
11229
+ properties?.["Policies"] ?? []
11230
+ );
11231
+ result["Policies"] = inline;
11232
+ } catch (err) {
11233
+ if (!(err instanceof NoSuchEntityException4))
11234
+ throw err;
11235
+ }
11161
11236
  return result;
11162
11237
  }
11238
+ /**
11239
+ * Shared inline-policy fetcher for User / Group readCurrentState.
11240
+ * Mirrors `IAMRoleProvider.readCurrentState`'s inline-policy handling:
11241
+ * paginated `List*Policies` for names → parallel `Get*Policy` per name
11242
+ * for bodies (URL-decoded + JSON-parsed) → reconcile order against
11243
+ * `statePolicies` so a positional compare doesn't fire false drift on
11244
+ * the lexicographic order returned by AWS.
11245
+ */
11246
+ async collectInlinePolicies(kind, physicalId, statePolicies) {
11247
+ const policyNames = [];
11248
+ let marker;
11249
+ while (true) {
11250
+ const listResp = kind === "user" ? await this.iamClient.send(
11251
+ new ListUserPoliciesCommand({
11252
+ UserName: physicalId,
11253
+ ...marker ? { Marker: marker } : {}
11254
+ })
11255
+ ) : await this.iamClient.send(
11256
+ new ListGroupPoliciesCommand({
11257
+ GroupName: physicalId,
11258
+ ...marker ? { Marker: marker } : {}
11259
+ })
11260
+ );
11261
+ for (const name of listResp.PolicyNames ?? [])
11262
+ policyNames.push(name);
11263
+ if (!listResp.IsTruncated)
11264
+ break;
11265
+ marker = listResp.Marker;
11266
+ }
11267
+ const bodies = /* @__PURE__ */ new Map();
11268
+ await Promise.all(
11269
+ policyNames.map(async (name) => {
11270
+ const resp = kind === "user" ? await this.iamClient.send(
11271
+ new GetUserPolicyCommand2({ UserName: physicalId, PolicyName: name })
11272
+ ) : await this.iamClient.send(
11273
+ new GetGroupPolicyCommand2({ GroupName: physicalId, PolicyName: name })
11274
+ );
11275
+ if (!resp.PolicyDocument)
11276
+ return;
11277
+ let parsed;
11278
+ try {
11279
+ parsed = JSON.parse(decodeURIComponent(resp.PolicyDocument));
11280
+ } catch {
11281
+ parsed = resp.PolicyDocument;
11282
+ }
11283
+ bodies.set(name, parsed);
11284
+ })
11285
+ );
11286
+ const remaining = new Set(bodies.keys());
11287
+ const inline = [];
11288
+ for (const sp of statePolicies) {
11289
+ const name = sp?.PolicyName;
11290
+ if (typeof name !== "string")
11291
+ continue;
11292
+ if (bodies.has(name)) {
11293
+ inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });
11294
+ remaining.delete(name);
11295
+ }
11296
+ }
11297
+ for (const name of [...remaining].sort()) {
11298
+ inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });
11299
+ }
11300
+ return inline;
11301
+ }
11163
11302
  // ─── Import dispatch ──────────────────────────────────────────────
11164
11303
  /**
11165
11304
  * Adopt an existing IAM user / group / user-to-group addition into cdkd state.
@@ -15700,6 +15839,7 @@ import {
15700
15839
  DeleteEventSourceMappingCommand,
15701
15840
  UpdateEventSourceMappingCommand,
15702
15841
  GetEventSourceMappingCommand,
15842
+ ListTagsCommand as ListTagsCommand2,
15703
15843
  TagResourceCommand as TagResourceCommand3,
15704
15844
  UntagResourceCommand as UntagResourceCommand3,
15705
15845
  ResourceNotFoundException as ResourceNotFoundException4
@@ -16013,21 +16153,24 @@ var LambdaEventSourceMappingProvider = class {
16013
16153
  * `LastProcessingResult`, `State`, `StateTransitionReason`,
16014
16154
  * `EventSourceMappingArn`) are filtered at the wire layer.
16015
16155
  *
16016
- * `FunctionName` is surfaced as the AWS `FunctionArn` (which is what
16017
- * `GetEventSourceMapping` returns) cdkd state typically holds this
16018
- * resolved ARN form already after intrinsic resolution. The drift
16019
- * comparator can match against both forms when state holds a name vs an
16020
- * ARN; mismatched-shape false positives are out of scope for v1.
16156
+ * `FunctionName`: AWS's `GetEventSourceMapping` always returns the
16157
+ * resolved ARN. cdkd state typically holds the same ARN after intrinsic
16158
+ * resolution, but a hand-authored state might carry the bare function
16159
+ * name. We surface the form that matches state when possible: if the
16160
+ * `properties?.FunctionName` is the bare name AND the AWS-current
16161
+ * ARN's last segment matches that name, emit the bare name; otherwise
16162
+ * emit the ARN. (The two forms address the same Lambda function — the
16163
+ * shape-mismatch was the only reason a clean run fired drift.)
16021
16164
  *
16022
- * `Tags` is omitted: cdkd `create()` reshapes CFn tag arrays into a
16023
- * tags map at create time, but `GetEventSourceMapping` does not return
16024
- * tags (`ListTags(Resource: arn)` does). Same shape-decision rationale
16025
- * as Lambda function tags drift — out of scope for v1.
16165
+ * `Tags` are surfaced via a follow-up `ListTags(Resource=<ESM ARN>)`
16166
+ * call. Always-emit `[]` so a console-side tag ADD on a previously-
16167
+ * untagged event source mapping is detectable on the v3
16168
+ * observedProperties baseline.
16026
16169
  *
16027
16170
  * Returns `undefined` when the mapping is gone
16028
16171
  * (`ResourceNotFoundException`).
16029
16172
  */
16030
- async readCurrentState(physicalId, _logicalId, _resourceType) {
16173
+ async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
16031
16174
  let resp;
16032
16175
  try {
16033
16176
  resp = await this.lambdaClient.send(new GetEventSourceMappingCommand({ UUID: physicalId }));
@@ -16037,8 +16180,15 @@ var LambdaEventSourceMappingProvider = class {
16037
16180
  throw err;
16038
16181
  }
16039
16182
  const result = {};
16040
- if (resp.FunctionArn !== void 0)
16041
- result["FunctionName"] = resp.FunctionArn;
16183
+ if (resp.FunctionArn !== void 0) {
16184
+ const stateFn = properties?.["FunctionName"];
16185
+ const arnTail = resp.FunctionArn.split(":").pop();
16186
+ if (typeof stateFn === "string" && !stateFn.includes(":") && stateFn === arnTail) {
16187
+ result["FunctionName"] = stateFn;
16188
+ } else {
16189
+ result["FunctionName"] = resp.FunctionArn;
16190
+ }
16191
+ }
16042
16192
  if (resp.EventSourceArn !== void 0)
16043
16193
  result["EventSourceArn"] = resp.EventSourceArn;
16044
16194
  if (resp.BatchSize !== void 0)
@@ -16097,6 +16247,20 @@ var LambdaEventSourceMappingProvider = class {
16097
16247
  const enabled = resp.State === "Enabled" || resp.State === "Enabling" || resp.State === "Updating";
16098
16248
  result["Enabled"] = enabled;
16099
16249
  }
16250
+ let tags = [];
16251
+ if (resp.EventSourceMappingArn) {
16252
+ try {
16253
+ const tagsResp = await this.lambdaClient.send(
16254
+ new ListTagsCommand2({ Resource: resp.EventSourceMappingArn })
16255
+ );
16256
+ const tagMap = tagsResp.Tags ?? {};
16257
+ tags = Object.entries(tagMap).filter(([k]) => !k.startsWith("aws:")).map(([Key, Value]) => ({ Key, Value })).sort((a, b) => a.Key.localeCompare(b.Key));
16258
+ } catch (err) {
16259
+ if (err instanceof ResourceNotFoundException4)
16260
+ return void 0;
16261
+ }
16262
+ }
16263
+ result["Tags"] = tags;
16100
16264
  return result;
16101
16265
  }
16102
16266
  /**
@@ -16128,7 +16292,7 @@ import {
16128
16292
  DeleteLayerVersionCommand,
16129
16293
  GetLayerVersionByArnCommand,
16130
16294
  ListLayersCommand,
16131
- ListTagsCommand as ListTagsCommand2,
16295
+ ListTagsCommand as ListTagsCommand3,
16132
16296
  ResourceNotFoundException as ResourceNotFoundException5
16133
16297
  } from "@aws-sdk/client-lambda";
16134
16298
  init_aws_clients();
@@ -16387,7 +16551,7 @@ var LambdaLayerVersionProvider = class {
16387
16551
  continue;
16388
16552
  try {
16389
16553
  const tagsResp = await this.lambdaClient.send(
16390
- new ListTagsCommand2({ Resource: layer.LayerArn })
16554
+ new ListTagsCommand3({ Resource: layer.LayerArn })
16391
16555
  );
16392
16556
  if (tagsResp.Tags?.[CDK_PATH_TAG] === input.cdkPath) {
16393
16557
  return {
@@ -16866,7 +17030,9 @@ var DynamoDBTableProvider = class {
16866
17030
  import {
16867
17031
  CreateLogGroupCommand,
16868
17032
  DeleteLogGroupCommand,
17033
+ DescribeIndexPoliciesCommand,
16869
17034
  DescribeLogGroupsCommand,
17035
+ GetDataProtectionPolicyCommand,
16870
17036
  ListTagsForResourceCommand as ListTagsForResourceCommand2,
16871
17037
  PutRetentionPolicyCommand,
16872
17038
  DeleteRetentionPolicyCommand,
@@ -17108,19 +17274,6 @@ var LogsLogGroupProvider = class {
17108
17274
  }
17109
17275
  return this.buildArn(physicalId);
17110
17276
  }
17111
- /**
17112
- * Drift comparator skip-list: properties readCurrentState deliberately
17113
- * cannot round-trip from AWS yet. `DataProtectionPolicy` lives behind
17114
- * its own `GetDataProtectionPolicy` API call (not in
17115
- * `DescribeLogGroups` output) — declaring it here prevents
17116
- * guaranteed false-positive drift on every clean run for log groups
17117
- * deployed with a data-protection policy. Lifting this guard requires
17118
- * a per-group `GetDataProtectionPolicy` round-trip in
17119
- * `readCurrentState`.
17120
- */
17121
- getDriftUnknownPaths() {
17122
- return ["DataProtectionPolicy"];
17123
- }
17124
17277
  /**
17125
17278
  * Read the AWS-current log group configuration in CFn-property shape.
17126
17279
  *
@@ -17131,10 +17284,26 @@ var LogsLogGroupProvider = class {
17131
17284
  * `RetentionInDays`).
17132
17285
  *
17133
17286
  * Coverage: `LogGroupName`, `KmsKeyId`, `RetentionInDays`,
17134
- * `LogGroupClass`, `Tags`. Other handledProperties (`DataProtectionPolicy`,
17135
- * `FieldIndexPolicies`, `ResourcePolicyDocument`,
17136
- * `DeletionProtectionEnabled`, `BearerTokenAuthenticationEnabled`) need
17137
- * their own per-property API call and are out of scope for v1.
17287
+ * `LogGroupClass`, `Tags`, `DataProtectionPolicy` (via
17288
+ * `GetDataProtectionPolicy`, JSON-parsed back to the object form
17289
+ * cdkd state holds), `DeletionProtectionEnabled` and
17290
+ * `BearerTokenAuthenticationEnabled` (both surfaced directly from
17291
+ * `DescribeLogGroups` — the SDK's LogGroup type carries them as
17292
+ * `deletionProtectionEnabled` / `bearerTokenAuthenticationEnabled`),
17293
+ * and `FieldIndexPolicies` (via `DescribeIndexPolicies`, filtered to
17294
+ * log-group-level policies and JSON-parsed). Still out of scope:
17295
+ * `ResourcePolicyDocument` (managed by the separate
17296
+ * `AWS::Logs::ResourcePolicy` resource type — account-wide, not
17297
+ * per-log-group).
17298
+ *
17299
+ * Known limitation: cdkd's `create()` / `update()` flows do NOT yet
17300
+ * apply `FieldIndexPolicies` / `DeletionProtectionEnabled` /
17301
+ * `BearerTokenAuthenticationEnabled` — they're in `handledProperties`
17302
+ * to prevent CC API fallback but no actual `PutIndexPolicy` /
17303
+ * `PutLogGroupDeletionProtection` / `PutBearerTokenAuthentication`
17304
+ * calls fire. Surfacing these in `readCurrentState` means a user
17305
+ * who templates them will see drift on the first run; a follow-up
17306
+ * needs to wire the create/update flow.
17138
17307
  *
17139
17308
  * Tags are read via `ListTagsForResource` (using the log-group ARN from
17140
17309
  * the same `DescribeLogGroups` response). CDK's `aws:*` auto-tags are
@@ -17158,6 +17327,8 @@ var LogsLogGroupProvider = class {
17158
17327
  result["RetentionInDays"] = found.retentionInDays ?? 0;
17159
17328
  if (found.logGroupClass !== void 0)
17160
17329
  result["LogGroupClass"] = found.logGroupClass;
17330
+ result["DeletionProtectionEnabled"] = found.deletionProtectionEnabled ?? false;
17331
+ result["BearerTokenAuthenticationEnabled"] = found.bearerTokenAuthenticationEnabled ?? false;
17161
17332
  let tags = [];
17162
17333
  if (found.arn) {
17163
17334
  const arnForTags = found.arn.replace(/:\*$/, "");
@@ -17173,6 +17344,39 @@ var LogsLogGroupProvider = class {
17173
17344
  }
17174
17345
  }
17175
17346
  result["Tags"] = tags;
17347
+ let dpp = "";
17348
+ try {
17349
+ const dppResp = await this.logsClient.send(
17350
+ new GetDataProtectionPolicyCommand({ logGroupIdentifier: physicalId })
17351
+ );
17352
+ if (dppResp.policyDocument) {
17353
+ try {
17354
+ dpp = JSON.parse(dppResp.policyDocument);
17355
+ } catch {
17356
+ dpp = dppResp.policyDocument;
17357
+ }
17358
+ }
17359
+ } catch {
17360
+ }
17361
+ result["DataProtectionPolicy"] = dpp;
17362
+ let fieldIndexPolicies = [];
17363
+ try {
17364
+ const idxResp = await this.logsClient.send(
17365
+ new DescribeIndexPoliciesCommand({ logGroupIdentifiers: [physicalId] })
17366
+ );
17367
+ const logGroupLevel = (idxResp.indexPolicies ?? []).filter((p) => p.source !== "ACCOUNT");
17368
+ fieldIndexPolicies = logGroupLevel.map((p) => {
17369
+ if (!p.policyDocument)
17370
+ return void 0;
17371
+ try {
17372
+ return JSON.parse(p.policyDocument);
17373
+ } catch {
17374
+ return p.policyDocument;
17375
+ }
17376
+ }).filter((p) => p !== void 0);
17377
+ } catch {
17378
+ }
17379
+ result["FieldIndexPolicies"] = fieldIndexPolicies;
17176
17380
  return result;
17177
17381
  } catch (err) {
17178
17382
  if (err instanceof ResourceNotFoundException7)
@@ -18246,10 +18450,21 @@ var SSMParameterProvider = class {
18246
18450
  *
18247
18451
  * `Name` is set to the physical id. `Tags` is surfaced via a follow-up
18248
18452
  * `ListTagsForResource(ResourceType=Parameter)` call, with CDK's `aws:*`
18249
- * auto-tags filtered out. `Policies` is intentionally out of scope:
18250
- * `DescribeParameters.Policies` returns a structured array but cdkd state
18251
- * holds the raw JSON string the user typed — comparing the two accurately
18252
- * needs more work.
18453
+ * auto-tags filtered out.
18454
+ *
18455
+ * `Policies` is surfaced from the same `DescribeParameters` response.
18456
+ * AWS returns `Parameters[0].Policies` as
18457
+ * `[{PolicyText, PolicyType, PolicyStatus}]`; cdkd state holds a JSON
18458
+ * string of the user-templated policy array (CFn's documented shape).
18459
+ * To compare cleanly we parse each `PolicyText` (itself JSON) into
18460
+ * objects, drop the AWS-managed `PolicyStatus` (Pending / InSync /
18461
+ * Expired), and emit the parsed object array. On the v3
18462
+ * `observedProperties` baseline this matches `observedProperties` (which
18463
+ * stored our parsed output at deploy time) exactly. On the v2 fallback
18464
+ * baseline (state.properties = JSON string) the comparator reports a
18465
+ * one-time drift on first run; users resolve via
18466
+ * `cdkd state refresh-observed`. Always-emit `[]` placeholder for
18467
+ * console-side ADD detection.
18253
18468
  *
18254
18469
  * **Note**: For `SecureString` parameters, AWS returns the encrypted
18255
18470
  * blob in `Value` (we pass `WithDecryption: false`). cdkd state usually
@@ -18281,6 +18496,7 @@ var SSMParameterProvider = class {
18281
18496
  result["Value"] = param.Value;
18282
18497
  if (param.DataType !== void 0)
18283
18498
  result["DataType"] = param.DataType;
18499
+ let policiesEmitted = false;
18284
18500
  try {
18285
18501
  const desc = await this.ssmClient.send(
18286
18502
  new DescribeParametersCommand({
@@ -18293,8 +18509,22 @@ var SSMParameterProvider = class {
18293
18509
  if (meta?.Tier !== void 0) {
18294
18510
  result["Tier"] = meta.Tier;
18295
18511
  }
18512
+ const parsedPolicies = [];
18513
+ for (const p of meta?.Policies ?? []) {
18514
+ if (!p.PolicyText)
18515
+ continue;
18516
+ try {
18517
+ parsedPolicies.push(JSON.parse(p.PolicyText));
18518
+ } catch {
18519
+ parsedPolicies.push(p.PolicyText);
18520
+ }
18521
+ }
18522
+ result["Policies"] = parsedPolicies;
18523
+ policiesEmitted = true;
18296
18524
  } catch {
18297
18525
  }
18526
+ if (!policiesEmitted)
18527
+ result["Policies"] = [];
18298
18528
  try {
18299
18529
  const tagsResp = await this.ssmClient.send(
18300
18530
  new ListTagsForResourceCommand4({
@@ -24782,10 +25012,13 @@ var AgentCoreRuntimeProvider = class {
24782
25012
  *
24783
25013
  * `ProtocolConfiguration` parity: `create()` accepts a CFn-style string
24784
25014
  * (`"HTTP"`) and converts it to `{serverProtocol: "HTTP"}` for the SDK.
24785
- * The SDK returns the object form. We surface the object form here; if
24786
- * cdkd state holds the original string the comparator will report drift
24787
- * — users can inspect and dismiss this case manually. (A more elaborate
24788
- * shape negotiation belongs in a follow-up that knows about both forms.)
25015
+ * The SDK returns the object form. We surface the **string form** here
25016
+ * (extract `serverProtocol` from the SDK object) since CFn's
25017
+ * `AWS::BedrockAgentCore::Runtime.ProtocolConfiguration` is documented
25018
+ * as a string and that's what cdkd state typically holds after CDK
25019
+ * synth. If state happens to carry the object form (legacy / hand-
25020
+ * authored), the comparator will report a one-time drift the user can
25021
+ * resolve via `cdkd state refresh-observed`.
24789
25022
  *
24790
25023
  * `ClientToken` is omitted: AWS does not surface it back via
24791
25024
  * `GetAgentRuntime` (it's an idempotency token only meaningful at create
@@ -24820,7 +25053,13 @@ var AgentCoreRuntimeProvider = class {
24820
25053
  result["AuthorizerConfiguration"] = camelToPascalCaseKeys(resp.authorizerConfiguration);
24821
25054
  }
24822
25055
  if (resp.protocolConfiguration !== void 0) {
24823
- result["ProtocolConfiguration"] = camelToPascalCaseKeys(resp.protocolConfiguration);
25056
+ const proto = resp.protocolConfiguration;
25057
+ const keys = Object.keys(proto);
25058
+ if (keys.length === 1 && keys[0] === "serverProtocol" && typeof proto["serverProtocol"] === "string") {
25059
+ result["ProtocolConfiguration"] = proto["serverProtocol"];
25060
+ } else {
25061
+ result["ProtocolConfiguration"] = camelToPascalCaseKeys(resp.protocolConfiguration);
25062
+ }
24824
25063
  }
24825
25064
  if (resp.lifecycleConfiguration !== void 0) {
24826
25065
  result["LifecycleConfiguration"] = camelToPascalCaseKeys(resp.lifecycleConfiguration);
@@ -26406,6 +26645,7 @@ import {
26406
26645
  CreateLoadBalancerCommand,
26407
26646
  DeleteLoadBalancerCommand,
26408
26647
  DescribeLoadBalancersCommand as DescribeLoadBalancersCommand2,
26648
+ DescribeLoadBalancerAttributesCommand,
26409
26649
  CreateTargetGroupCommand,
26410
26650
  DeleteTargetGroupCommand,
26411
26651
  ModifyTargetGroupCommand,
@@ -26498,7 +26738,13 @@ var ELBv2Provider = class {
26498
26738
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
26499
26739
  switch (resourceType) {
26500
26740
  case "AWS::ElasticLoadBalancingV2::LoadBalancer":
26501
- return this.updateLoadBalancer(logicalId, physicalId, resourceType, properties);
26741
+ return this.updateLoadBalancer(
26742
+ logicalId,
26743
+ physicalId,
26744
+ resourceType,
26745
+ properties,
26746
+ previousProperties
26747
+ );
26502
26748
  case "AWS::ElasticLoadBalancingV2::TargetGroup":
26503
26749
  return this.updateTargetGroup(
26504
26750
  logicalId,
@@ -26603,14 +26849,44 @@ var ELBv2Provider = class {
26603
26849
  );
26604
26850
  }
26605
26851
  }
26606
- updateLoadBalancer(logicalId, _physicalId, _resourceType, _properties) {
26607
- return Promise.reject(
26608
- new ResourceUpdateNotSupportedError(
26852
+ async updateLoadBalancer(logicalId, physicalId, _resourceType, properties, previousProperties) {
26853
+ const newAttrs = properties["LoadBalancerAttributes"] ?? [];
26854
+ const oldAttrs = previousProperties["LoadBalancerAttributes"] ?? [];
26855
+ const stripAttrs = (p) => {
26856
+ const { LoadBalancerAttributes: _, ...rest } = p;
26857
+ return rest;
26858
+ };
26859
+ if (JSON.stringify(stripAttrs(properties)) !== JSON.stringify(stripAttrs(previousProperties))) {
26860
+ throw new ResourceUpdateNotSupportedError(
26609
26861
  "AWS::ElasticLoadBalancingV2::LoadBalancer",
26610
26862
  logicalId,
26611
- "ELBv2 LoadBalancer in-place updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
26612
- )
26613
- );
26863
+ "ELBv2 LoadBalancer in-place updates are only supported for LoadBalancerAttributes; for Name / Type / Scheme / Subnets / SecurityGroups / IpAddressType / Tags, re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
26864
+ );
26865
+ }
26866
+ const newMap = new Map(newAttrs.map((a) => [a.Key, a.Value]));
26867
+ const oldMap = new Map(oldAttrs.map((a) => [a.Key, a.Value]));
26868
+ const submitted = [];
26869
+ for (const [k, v] of newMap) {
26870
+ if (oldMap.get(k) !== v)
26871
+ submitted.push({ Key: k, Value: v });
26872
+ }
26873
+ for (const [k] of oldMap) {
26874
+ if (!newMap.has(k))
26875
+ submitted.push({ Key: k, Value: "" });
26876
+ }
26877
+ if (submitted.length > 0) {
26878
+ const { ModifyLoadBalancerAttributesCommand } = await import("@aws-sdk/client-elastic-load-balancing-v2");
26879
+ await this.getClient().send(
26880
+ new ModifyLoadBalancerAttributesCommand({
26881
+ LoadBalancerArn: physicalId,
26882
+ Attributes: submitted
26883
+ })
26884
+ );
26885
+ this.logger.debug(
26886
+ `Applied ${submitted.length} LoadBalancerAttributes change(s) for ${logicalId}`
26887
+ );
26888
+ }
26889
+ return { physicalId, wasReplaced: false };
26614
26890
  }
26615
26891
  async deleteLoadBalancer(logicalId, physicalId, resourceType, context) {
26616
26892
  this.logger.debug(`Deleting LoadBalancer ${logicalId}: ${physicalId}`);
@@ -26956,10 +27232,12 @@ var ELBv2Provider = class {
26956
27232
  * Dispatch per resource type:
26957
27233
  * - `LoadBalancer` → `DescribeLoadBalancers` (Name, Subnets via
26958
27234
  * `AvailabilityZones[].SubnetId`, SecurityGroups, Scheme, Type,
26959
- * IpAddressType). LoadBalancerAttributes is omitted for v1 — it
26960
- * requires a separate `DescribeLoadBalancerAttributes` call and the
26961
- * drift comparator only descends into keys present in state, so an
26962
- * absent key cannot fire false drift.
27235
+ * IpAddressType) plus `DescribeLoadBalancerAttributes` for the full
27236
+ * `LoadBalancerAttributes` `[{Key, Value}]` array (sorted by Key for
27237
+ * stable positional compare). AWS returns every attribute valid for
27238
+ * this LB type including defaults the user did not template; on the
27239
+ * v3 observedProperties baseline that's load-bearing — a console-side
27240
+ * change to ANY attribute (templated or not) surfaces as drift.
26963
27241
  * - `TargetGroup` → `DescribeTargetGroups` (Protocol, Port, VpcId,
26964
27242
  * TargetType, ProtocolVersion, HealthCheck*, Matcher, Name).
26965
27243
  * - `Listener` → `DescribeListeners` (LoadBalancerArn, Certificates,
@@ -27009,6 +27287,18 @@ var ELBv2Provider = class {
27009
27287
  result["Type"] = lb.Type;
27010
27288
  if (lb.IpAddressType !== void 0)
27011
27289
  result["IpAddressType"] = lb.IpAddressType;
27290
+ try {
27291
+ const attrsResp = await this.getClient().send(
27292
+ new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: physicalId })
27293
+ );
27294
+ const attrs = (attrsResp.Attributes ?? []).filter(
27295
+ (a) => typeof a.Key === "string" && typeof a.Value === "string"
27296
+ ).map((a) => ({ Key: a.Key, Value: a.Value })).sort((a, b) => a.Key.localeCompare(b.Key));
27297
+ result["LoadBalancerAttributes"] = attrs;
27298
+ } catch (err) {
27299
+ if (this.isNotFoundError(err))
27300
+ return void 0;
27301
+ }
27012
27302
  await this.attachTags(result, physicalId);
27013
27303
  return result;
27014
27304
  }
@@ -28889,8 +29179,10 @@ var Route53Provider = class {
28889
29179
  * PrivateZone}, VPCs from `VPCs[]`, HostedZoneTags via
28890
29180
  * `ListTagsForResource(ResourceType=hostedzone, ResourceId=<idTail>)`
28891
29181
  * with `aws:*` filtered out and the key omitted when empty).
28892
- * QueryLoggingConfig is skipped because it's a separate
28893
- * `ListQueryLoggingConfigs` call and the v1 surface does not surface it.
29182
+ * QueryLoggingConfig is surfaced via a follow-up
29183
+ * `ListQueryLoggingConfigs(HostedZoneId)` (filter to the one
29184
+ * config per zone — CFn enforces 0 or 1 — and reshape
29185
+ * `CloudWatchLogsLogGroupArn` to match cdkd state).
28894
29186
  * - `RecordSet` → `ListResourceRecordSets` filtered to the exact
28895
29187
  * `(name, type)` pair from the composite physicalId
28896
29188
  * (`{zoneId}|{name}|{type}`). Surfaces TTL, ResourceRecords (with
@@ -28955,6 +29247,24 @@ var Route53Provider = class {
28955
29247
  `Route53 ListTagsForResource(${idTail}) failed: ${err instanceof Error ? err.message : String(err)}`
28956
29248
  );
28957
29249
  }
29250
+ try {
29251
+ const qlcResp = await this.getClient().send(
29252
+ new ListQueryLoggingConfigsCommand({ HostedZoneId: idTail })
29253
+ );
29254
+ const qlc = qlcResp.QueryLoggingConfigs?.[0];
29255
+ if (qlc?.CloudWatchLogsLogGroupArn) {
29256
+ result["QueryLoggingConfig"] = {
29257
+ CloudWatchLogsLogGroupArn: qlc.CloudWatchLogsLogGroupArn
29258
+ };
29259
+ } else {
29260
+ result["QueryLoggingConfig"] = {};
29261
+ }
29262
+ } catch (err) {
29263
+ this.logger.debug(
29264
+ `Route53 ListQueryLoggingConfigs(${idTail}) failed: ${err instanceof Error ? err.message : String(err)}`
29265
+ );
29266
+ result["QueryLoggingConfig"] = {};
29267
+ }
28958
29268
  return result;
28959
29269
  }
28960
29270
  async readRecordSet(physicalId) {
@@ -32726,6 +33036,8 @@ import {
32726
33036
  KMSClient as KMSClient2,
32727
33037
  CreateKeyCommand,
32728
33038
  DescribeKeyCommand,
33039
+ GetKeyPolicyCommand,
33040
+ GetKeyRotationStatusCommand,
32729
33041
  ListAliasesCommand as ListAliasesCommand2,
32730
33042
  ListKeysCommand,
32731
33043
  ListResourceTagsCommand,
@@ -33193,6 +33505,36 @@ var KMSProvider = class {
33193
33505
  if (md.Origin !== void 0)
33194
33506
  result["Origin"] = md.Origin;
33195
33507
  if (md.KeyId) {
33508
+ try {
33509
+ const policyResp = await this.getClient().send(
33510
+ new GetKeyPolicyCommand({ KeyId: md.KeyId, PolicyName: "default" })
33511
+ );
33512
+ if (policyResp.Policy) {
33513
+ try {
33514
+ result["KeyPolicy"] = JSON.parse(policyResp.Policy);
33515
+ } catch {
33516
+ result["KeyPolicy"] = policyResp.Policy;
33517
+ }
33518
+ }
33519
+ } catch (err) {
33520
+ if (err instanceof NotFoundException5)
33521
+ return void 0;
33522
+ }
33523
+ const isSymmetric = md.KeySpec === void 0 || md.KeySpec === "SYMMETRIC_DEFAULT";
33524
+ if (isSymmetric) {
33525
+ try {
33526
+ const rotationResp = await this.getClient().send(
33527
+ new GetKeyRotationStatusCommand({ KeyId: md.KeyId })
33528
+ );
33529
+ result["EnableKeyRotation"] = rotationResp.KeyRotationEnabled ?? false;
33530
+ if (rotationResp.RotationPeriodInDays !== void 0) {
33531
+ result["RotationPeriodInDays"] = rotationResp.RotationPeriodInDays;
33532
+ }
33533
+ } catch (err) {
33534
+ if (err instanceof NotFoundException5)
33535
+ return void 0;
33536
+ }
33537
+ }
33196
33538
  try {
33197
33539
  const tagsResp = await this.getClient().send(
33198
33540
  new ListResourceTagsCommand({ KeyId: md.KeyId })
@@ -33211,28 +33553,17 @@ var KMSProvider = class {
33211
33553
  * drift comparator skips them instead of firing guaranteed false-
33212
33554
  * positive drift on every clean run.
33213
33555
  *
33214
- * - `KeyPolicy`: cdkd does NOT call `GetKeyPolicy` in `readCurrentState`.
33215
- * The policy body needs JSON parsing for comparison and a separate
33216
- * SDK call; deferred to a follow-up. Until then, any user who
33217
- * templates `KeyPolicy` would see guaranteed drift.
33218
- * - `EnableKeyRotation` / `RotationPeriodInDays`: cdkd does NOT call
33219
- * `GetKeyRotationStatus`. Same reason — deferred to a follow-up.
33220
- * `EnableKeyRotation` is also a Class 1 candidate (only valid for
33221
- * `KeySpec=SYMMETRIC_DEFAULT`); when we lift this gap the read side
33222
- * must gate the emit on the discriminator.
33223
33556
  * - `BypassPolicyLockoutSafetyCheck` / `PendingWindowInDays`: not part
33224
33557
  * of the persisted AWS state visible via `DescribeKey` — both are
33225
33558
  * create / delete-time-only inputs.
33559
+ *
33560
+ * `KeyPolicy`, `EnableKeyRotation`, and `RotationPeriodInDays` are now
33561
+ * read by `readCurrentState` (`GetKeyPolicy` and `GetKeyRotationStatus`
33562
+ * respectively), so they no longer need to be declared here.
33226
33563
  */
33227
33564
  getDriftUnknownPaths(resourceType) {
33228
33565
  if (resourceType === "AWS::KMS::Key") {
33229
- return [
33230
- "KeyPolicy",
33231
- "EnableKeyRotation",
33232
- "RotationPeriodInDays",
33233
- "BypassPolicyLockoutSafetyCheck",
33234
- "PendingWindowInDays"
33235
- ];
33566
+ return ["BypassPolicyLockoutSafetyCheck", "PendingWindowInDays"];
33236
33567
  }
33237
33568
  return [];
33238
33569
  }
@@ -33821,6 +34152,7 @@ import {
33821
34152
  DescribeAccessPointsCommand,
33822
34153
  DescribeLifecycleConfigurationCommand,
33823
34154
  DescribeBackupPolicyCommand,
34155
+ DescribeMountTargetSecurityGroupsCommand,
33824
34156
  FileSystemNotFound,
33825
34157
  MountTargetNotFound,
33826
34158
  AccessPointNotFound
@@ -34247,8 +34579,10 @@ var EFSProvider = class {
34247
34579
  * the corresponding key without failing the whole snapshot.
34248
34580
  * - `AccessPoint` → `DescribeAccessPoints` filtered by id (PosixUser,
34249
34581
  * RootDirectory).
34250
- * - `MountTarget` → `DescribeMountTargets` (FileSystemId, SubnetId).
34251
- * SecurityGroups requires a separate call and is omitted for v1.
34582
+ * - `MountTarget` → `DescribeMountTargets` (FileSystemId, SubnetId)
34583
+ * plus `DescribeMountTargetSecurityGroups` for the SG list (always-
34584
+ * emit `[]` when AWS reports none so a console-side ADD on a
34585
+ * previously-unconfigured mount target is detectable).
34252
34586
  *
34253
34587
  * `FileSystemTags` (the CFn property name on `AWS::EFS::FileSystem`) is
34254
34588
  * surfaced from the same `DescribeFileSystems` response — `aws:*`
@@ -34405,6 +34739,17 @@ var EFSProvider = class {
34405
34739
  result["FileSystemId"] = mt.FileSystemId;
34406
34740
  if (mt.SubnetId !== void 0)
34407
34741
  result["SubnetId"] = mt.SubnetId;
34742
+ let securityGroups = [];
34743
+ try {
34744
+ const sgResp = await this.getClient().send(
34745
+ new DescribeMountTargetSecurityGroupsCommand({ MountTargetId: physicalId })
34746
+ );
34747
+ securityGroups = (sgResp.SecurityGroups ?? []).filter(
34748
+ (s) => typeof s === "string"
34749
+ );
34750
+ } catch {
34751
+ }
34752
+ result["SecurityGroups"] = securityGroups;
34408
34753
  return result;
34409
34754
  }
34410
34755
  /**
@@ -35014,8 +35359,9 @@ import {
35014
35359
  GetTrailCommand,
35015
35360
  GetTrailStatusCommand,
35016
35361
  GetEventSelectorsCommand,
35362
+ GetInsightSelectorsCommand,
35017
35363
  ListTrailsCommand,
35018
- ListTagsCommand as ListTagsCommand3,
35364
+ ListTagsCommand as ListTagsCommand4,
35019
35365
  AddTagsCommand as AddTagsCommand2,
35020
35366
  RemoveTagsCommand as RemoveTagsCommand2,
35021
35367
  TrailNotFoundException
@@ -35186,15 +35532,13 @@ var CloudTrailProvider = class {
35186
35532
  const newInsightSelectors = properties["InsightSelectors"];
35187
35533
  const oldInsightSelectors = previousProperties["InsightSelectors"];
35188
35534
  if (JSON.stringify(newInsightSelectors) !== JSON.stringify(oldInsightSelectors)) {
35189
- if (newInsightSelectors && newInsightSelectors.length > 0) {
35190
- this.logger.debug(`Updating insight selectors for CloudTrail Trail ${logicalId}`);
35191
- await this.getClient().send(
35192
- new PutInsightSelectorsCommand({
35193
- TrailName: physicalId,
35194
- InsightSelectors: newInsightSelectors
35195
- })
35196
- );
35197
- }
35535
+ this.logger.debug(`Updating insight selectors for CloudTrail Trail ${logicalId}`);
35536
+ await this.getClient().send(
35537
+ new PutInsightSelectorsCommand({
35538
+ TrailName: physicalId,
35539
+ InsightSelectors: newInsightSelectors ?? []
35540
+ })
35541
+ );
35198
35542
  }
35199
35543
  const oldIsLogging = previousProperties["IsLogging"];
35200
35544
  if (isLogging !== oldIsLogging) {
@@ -35327,8 +35671,10 @@ var CloudTrailProvider = class {
35327
35671
  * auto-tags are filtered out and the result key is omitted when AWS
35328
35672
  * reports no user tags.
35329
35673
  *
35330
- * `InsightSelectors` is skipped for v1 (separate call + shape mapping
35331
- * still TBD).
35674
+ * `InsightSelectors` is surfaced via a follow-up `GetInsightSelectors`
35675
+ * call — same shape on both sides (`[{InsightType}]`). The key is
35676
+ * always emitted (`[]` when AWS reports none) so a console-side ADD
35677
+ * is detectable on the v3 observedProperties baseline.
35332
35678
  *
35333
35679
  * Returns `undefined` when the trail is gone (`TrailNotFoundException`).
35334
35680
  */
@@ -35377,11 +35723,22 @@ var CloudTrailProvider = class {
35377
35723
  }
35378
35724
  } catch {
35379
35725
  }
35726
+ let insightSelectors = [];
35727
+ try {
35728
+ const insight = await this.getClient().send(
35729
+ new GetInsightSelectorsCommand({ TrailName: physicalId })
35730
+ );
35731
+ insightSelectors = (insight.InsightSelectors ?? []).map((s) => ({
35732
+ ...s.InsightType !== void 0 && { InsightType: s.InsightType }
35733
+ }));
35734
+ } catch {
35735
+ }
35736
+ result["InsightSelectors"] = insightSelectors;
35380
35737
  let tags = [];
35381
35738
  if (trail.TrailARN) {
35382
35739
  try {
35383
35740
  const tagsResp = await this.getClient().send(
35384
- new ListTagsCommand3({ ResourceIdList: [trail.TrailARN] })
35741
+ new ListTagsCommand4({ ResourceIdList: [trail.TrailARN] })
35385
35742
  );
35386
35743
  tags = normalizeAwsTagsToCfn(tagsResp.ResourceTagList?.[0]?.TagsList);
35387
35744
  } catch (err) {
@@ -35417,7 +35774,7 @@ var CloudTrailProvider = class {
35417
35774
  continue;
35418
35775
  try {
35419
35776
  const tagsResp = await this.getClient().send(
35420
- new ListTagsCommand3({ ResourceIdList: [trail.TrailARN] })
35777
+ new ListTagsCommand4({ ResourceIdList: [trail.TrailARN] })
35421
35778
  );
35422
35779
  const list2 = tagsResp.ResourceTagList?.[0];
35423
35780
  if (matchesCdkPath(list2?.TagsList, input.cdkPath)) {
@@ -35720,12 +36077,21 @@ var CodeBuildProvider = class {
35720
36077
  *
35721
36078
  * Issues `BatchGetProjects` and re-shapes the SDK's camelCase response back
35722
36079
  * to CFn's PascalCase shape (the `mapProperties` helper above goes the
35723
- * other way at create time). The drift comparator only descends into
35724
- * keys present in cdkd state, so we focus on the high-value top-level
35725
- * fields and the most commonly-set `Source` / `Artifacts` /
35726
- * `Environment` sub-fields. Less common nested config (full
35727
- * `LogsConfig`, `VpcConfig` rebuild, secondary sources/artifacts, etc.)
35728
- * is left to a follow-up surfacing them with a partial shape would
36080
+ * other way at create time). Coverage targets every user-controllable
36081
+ * top-level field via the always-emit pattern (PR #145):
36082
+ * - `Source` / `Artifacts` / `Environment` (with `EnvironmentVariables`
36083
+ * sub-array): full reshape to CFn shape.
36084
+ * - `LogsConfig` (`CloudWatchLogs` + `S3Logs` sub-shapes): always-emit
36085
+ * placeholder so a console-side enable on a previously-default
36086
+ * project surfaces as drift.
36087
+ * - `VpcConfig` (VpcId / Subnets / SecurityGroupIds): always-emit
36088
+ * placeholder.
36089
+ * - `Cache` (Type / Location / Modes): always-emit placeholder.
36090
+ *
36091
+ * Still v1-omitted (known gaps, follow-up): `SecondarySources` /
36092
+ * `SecondaryArtifacts` / `SecondarySourceVersions` / `FileSystemLocations` /
36093
+ * `Triggers` / `BuildBatchConfig` / `ResourceAccessRole`. These are
36094
+ * rarely set in practice and surfacing them with partial shape would
35729
36095
  * fire false drift on every project that uses them.
35730
36096
  *
35731
36097
  * Tags are surfaced from the same `BatchGetProjects` response (CodeBuild
@@ -35840,6 +36206,143 @@ var CodeBuildProvider = class {
35840
36206
  });
35841
36207
  result["Environment"] = env;
35842
36208
  }
36209
+ {
36210
+ const logs = {};
36211
+ const cw = {
36212
+ Status: project.logsConfig?.cloudWatchLogs?.status ?? "ENABLED"
36213
+ };
36214
+ if (project.logsConfig?.cloudWatchLogs?.groupName !== void 0) {
36215
+ cw["GroupName"] = project.logsConfig.cloudWatchLogs.groupName;
36216
+ }
36217
+ if (project.logsConfig?.cloudWatchLogs?.streamName !== void 0) {
36218
+ cw["StreamName"] = project.logsConfig.cloudWatchLogs.streamName;
36219
+ }
36220
+ logs["CloudWatchLogs"] = cw;
36221
+ const s3 = {
36222
+ Status: project.logsConfig?.s3Logs?.status ?? "DISABLED"
36223
+ };
36224
+ if (project.logsConfig?.s3Logs?.location !== void 0) {
36225
+ s3["Location"] = project.logsConfig.s3Logs.location;
36226
+ }
36227
+ if (project.logsConfig?.s3Logs?.encryptionDisabled !== void 0) {
36228
+ s3["EncryptionDisabled"] = project.logsConfig.s3Logs.encryptionDisabled;
36229
+ }
36230
+ if (project.logsConfig?.s3Logs?.bucketOwnerAccess !== void 0) {
36231
+ s3["BucketOwnerAccess"] = project.logsConfig.s3Logs.bucketOwnerAccess;
36232
+ }
36233
+ logs["S3Logs"] = s3;
36234
+ result["LogsConfig"] = logs;
36235
+ }
36236
+ if (project.vpcConfig?.vpcId !== void 0) {
36237
+ const vpc = { VpcId: project.vpcConfig.vpcId };
36238
+ vpc["Subnets"] = project.vpcConfig.subnets ?? [];
36239
+ vpc["SecurityGroupIds"] = project.vpcConfig.securityGroupIds ?? [];
36240
+ result["VpcConfig"] = vpc;
36241
+ } else {
36242
+ result["VpcConfig"] = {};
36243
+ }
36244
+ {
36245
+ const cache2 = {
36246
+ Type: project.cache?.type ?? "NO_CACHE"
36247
+ };
36248
+ if (project.cache?.location !== void 0)
36249
+ cache2["Location"] = project.cache.location;
36250
+ if (project.cache?.modes !== void 0)
36251
+ cache2["Modes"] = project.cache.modes;
36252
+ result["Cache"] = cache2;
36253
+ }
36254
+ result["SecondarySources"] = (project.secondarySources ?? []).map((s) => {
36255
+ const out = {};
36256
+ if (s.type !== void 0)
36257
+ out["Type"] = s.type;
36258
+ if (s.location !== void 0)
36259
+ out["Location"] = s.location;
36260
+ if (s.buildspec !== void 0)
36261
+ out["BuildSpec"] = s.buildspec;
36262
+ if (s.gitCloneDepth !== void 0)
36263
+ out["GitCloneDepth"] = s.gitCloneDepth;
36264
+ if (s.insecureSsl !== void 0)
36265
+ out["InsecureSsl"] = s.insecureSsl;
36266
+ if (s.reportBuildStatus !== void 0)
36267
+ out["ReportBuildStatus"] = s.reportBuildStatus;
36268
+ if (s.sourceIdentifier !== void 0)
36269
+ out["SourceIdentifier"] = s.sourceIdentifier;
36270
+ return out;
36271
+ });
36272
+ result["SecondaryArtifacts"] = (project.secondaryArtifacts ?? []).map((a) => {
36273
+ const out = {};
36274
+ if (a.type !== void 0)
36275
+ out["Type"] = a.type;
36276
+ if (a.location !== void 0)
36277
+ out["Location"] = a.location;
36278
+ if (a.path !== void 0)
36279
+ out["Path"] = a.path;
36280
+ if (a.name !== void 0)
36281
+ out["Name"] = a.name;
36282
+ if (a.namespaceType !== void 0)
36283
+ out["NamespaceType"] = a.namespaceType;
36284
+ if (a.packaging !== void 0)
36285
+ out["Packaging"] = a.packaging;
36286
+ if (a.encryptionDisabled !== void 0)
36287
+ out["EncryptionDisabled"] = a.encryptionDisabled;
36288
+ if (a.overrideArtifactName !== void 0) {
36289
+ out["OverrideArtifactName"] = a.overrideArtifactName;
36290
+ }
36291
+ if (a.artifactIdentifier !== void 0)
36292
+ out["ArtifactIdentifier"] = a.artifactIdentifier;
36293
+ return out;
36294
+ });
36295
+ result["SecondarySourceVersions"] = (project.secondarySourceVersions ?? []).map((v) => {
36296
+ const out = {};
36297
+ if (v.sourceIdentifier !== void 0)
36298
+ out["SourceIdentifier"] = v.sourceIdentifier;
36299
+ if (v.sourceVersion !== void 0)
36300
+ out["SourceVersion"] = v.sourceVersion;
36301
+ return out;
36302
+ });
36303
+ result["FileSystemLocations"] = (project.fileSystemLocations ?? []).map((f) => {
36304
+ const out = {};
36305
+ if (f.type !== void 0)
36306
+ out["Type"] = f.type;
36307
+ if (f.location !== void 0)
36308
+ out["Location"] = f.location;
36309
+ if (f.mountPoint !== void 0)
36310
+ out["MountPoint"] = f.mountPoint;
36311
+ if (f.identifier !== void 0)
36312
+ out["Identifier"] = f.identifier;
36313
+ if (f.mountOptions !== void 0)
36314
+ out["MountOptions"] = f.mountOptions;
36315
+ return out;
36316
+ });
36317
+ if (project.buildBatchConfig) {
36318
+ const bbc = {};
36319
+ if (project.buildBatchConfig.serviceRole !== void 0) {
36320
+ bbc["ServiceRole"] = project.buildBatchConfig.serviceRole;
36321
+ }
36322
+ if (project.buildBatchConfig.restrictions !== void 0) {
36323
+ const r = {};
36324
+ if (project.buildBatchConfig.restrictions.maximumBuildsAllowed !== void 0) {
36325
+ r["MaximumBuildsAllowed"] = project.buildBatchConfig.restrictions.maximumBuildsAllowed;
36326
+ }
36327
+ if (project.buildBatchConfig.restrictions.computeTypesAllowed !== void 0) {
36328
+ r["ComputeTypesAllowed"] = project.buildBatchConfig.restrictions.computeTypesAllowed;
36329
+ }
36330
+ bbc["Restrictions"] = r;
36331
+ }
36332
+ if (project.buildBatchConfig.timeoutInMins !== void 0) {
36333
+ bbc["TimeoutInMins"] = project.buildBatchConfig.timeoutInMins;
36334
+ }
36335
+ if (project.buildBatchConfig.batchReportMode !== void 0) {
36336
+ bbc["BatchReportMode"] = project.buildBatchConfig.batchReportMode;
36337
+ }
36338
+ if (project.buildBatchConfig.combineArtifacts !== void 0) {
36339
+ bbc["CombineArtifacts"] = project.buildBatchConfig.combineArtifacts;
36340
+ }
36341
+ result["BuildBatchConfig"] = bbc;
36342
+ } else {
36343
+ result["BuildBatchConfig"] = {};
36344
+ }
36345
+ result["ResourceAccessRole"] = project.resourceAccessRole ?? "";
35843
36346
  const tags = normalizeAwsTagsToCfn(project.tags);
35844
36347
  result["Tags"] = tags;
35845
36348
  return result;
@@ -43979,7 +44482,7 @@ function reorderArgs(argv) {
43979
44482
  }
43980
44483
  async function main() {
43981
44484
  const program = new Command14();
43982
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.51.0");
44485
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.51.2");
43983
44486
  program.addCommand(createBootstrapCommand());
43984
44487
  program.addCommand(createSynthCommand());
43985
44488
  program.addCommand(createListCommand());