@go-to-k/cdkd 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7601,7 +7601,7 @@ Error: ${err.message || "Unknown error"}`,
7601
7601
  * resource type that goes through CC API — the majority of cdkd's surface.
7602
7602
  * SDK Providers add their own `readCurrentState` incrementally (PR D).
7603
7603
  */
7604
- async readCurrentState(physicalId, _logicalId, resourceType) {
7604
+ async readCurrentState(physicalId, _logicalId, resourceType, _properties) {
7605
7605
  try {
7606
7606
  const response = await this.cloudControlClient.send(
7607
7607
  new GetResourceCommand2({
@@ -9169,6 +9169,9 @@ import {
9169
9169
  DeleteGroupPolicyCommand,
9170
9170
  PutUserPolicyCommand,
9171
9171
  DeleteUserPolicyCommand,
9172
+ GetRolePolicyCommand,
9173
+ GetGroupPolicyCommand,
9174
+ GetUserPolicyCommand,
9172
9175
  NoSuchEntityException as NoSuchEntityException2
9173
9176
  } from "@aws-sdk/client-iam";
9174
9177
  init_aws_clients();
@@ -9517,6 +9520,93 @@ var IAMPolicyProvider = class {
9517
9520
  );
9518
9521
  }
9519
9522
  }
9523
+ /**
9524
+ * Read the AWS-current IAM inline policy in CFn-property shape.
9525
+ *
9526
+ * `AWS::IAM::Policy` is an inline policy attached to one or more roles /
9527
+ * groups / users via `PutRolePolicy` / `PutGroupPolicy` / `PutUserPolicy`.
9528
+ * Each attachment is a separate API call, but the same `PolicyDocument`
9529
+ * is replicated across every target.
9530
+ *
9531
+ * Strategy: pick the FIRST target from `properties.Roles` / `Groups` /
9532
+ * `Users` (in that order), call `Get*Policy(target, policyName)`, and
9533
+ * surface the URL-decoded + JSON-parsed `PolicyDocument`. Roles / Groups /
9534
+ * Users are echoed back from state since AWS doesn't return them. This is
9535
+ * defensible because:
9536
+ * - Drift on `PolicyDocument`: caught — the document is the same on
9537
+ * every target, so reading any one of them surfaces the divergence.
9538
+ * - Drift on the target list (a role removed / added out-of-band): NOT
9539
+ * caught. There's no API to enumerate every role / group / user that
9540
+ * has an inline policy of a given name; cdkd would need to walk the
9541
+ * entire account. Out of scope for v1.
9542
+ *
9543
+ * Returns `undefined` when the resolved target has no inline policy of
9544
+ * that name (`NoSuchEntityException`) — signals "drift unknown" rather
9545
+ * than firing a false positive.
9546
+ */
9547
+ async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
9548
+ if (!properties)
9549
+ return void 0;
9550
+ const policyDocument = properties["PolicyDocument"];
9551
+ if (!policyDocument)
9552
+ return void 0;
9553
+ const policyName = physicalId.includes(":") ? physicalId.split(":")[0] : physicalId;
9554
+ const roles = properties["Roles"];
9555
+ const groups = properties["Groups"];
9556
+ const users = properties["Users"];
9557
+ let liveDocument;
9558
+ try {
9559
+ if (roles && roles.length > 0) {
9560
+ const resp = await this.iamClient.send(
9561
+ new GetRolePolicyCommand({ RoleName: roles[0], PolicyName: policyName })
9562
+ );
9563
+ liveDocument = this.decodePolicyDocument(resp.PolicyDocument);
9564
+ } else if (groups && groups.length > 0) {
9565
+ const resp = await this.iamClient.send(
9566
+ new GetGroupPolicyCommand({ GroupName: groups[0], PolicyName: policyName })
9567
+ );
9568
+ liveDocument = this.decodePolicyDocument(resp.PolicyDocument);
9569
+ } else if (users && users.length > 0) {
9570
+ const resp = await this.iamClient.send(
9571
+ new GetUserPolicyCommand({ UserName: users[0], PolicyName: policyName })
9572
+ );
9573
+ liveDocument = this.decodePolicyDocument(resp.PolicyDocument);
9574
+ } else {
9575
+ return void 0;
9576
+ }
9577
+ } catch (err) {
9578
+ if (err instanceof NoSuchEntityException2)
9579
+ return void 0;
9580
+ throw err;
9581
+ }
9582
+ if (liveDocument === void 0)
9583
+ return void 0;
9584
+ const result = {
9585
+ PolicyName: policyName,
9586
+ PolicyDocument: liveDocument
9587
+ };
9588
+ if (roles)
9589
+ result["Roles"] = roles;
9590
+ if (groups)
9591
+ result["Groups"] = groups;
9592
+ if (users)
9593
+ result["Users"] = users;
9594
+ return result;
9595
+ }
9596
+ /**
9597
+ * IAM Get*Policy returns the policy document as a URL-encoded JSON string
9598
+ * (per RFC 3986). Decode and parse it back into the object shape cdkd
9599
+ * state holds, so the drift comparator sees apples-to-apples.
9600
+ */
9601
+ decodePolicyDocument(raw) {
9602
+ if (!raw)
9603
+ return void 0;
9604
+ try {
9605
+ return JSON.parse(decodeURIComponent(raw));
9606
+ } catch {
9607
+ return raw;
9608
+ }
9609
+ }
9520
9610
  /**
9521
9611
  * Adopt an existing IAM inline policy into cdkd state.
9522
9612
  *
@@ -14710,6 +14800,7 @@ var LambdaFunctionProvider = class {
14710
14800
  // src/provisioning/providers/lambda-permission-provider.ts
14711
14801
  import {
14712
14802
  AddPermissionCommand,
14803
+ GetPolicyCommand,
14713
14804
  RemovePermissionCommand,
14714
14805
  ResourceNotFoundException as ResourceNotFoundException2
14715
14806
  } from "@aws-sdk/client-lambda";
@@ -14900,6 +14991,87 @@ var LambdaPermissionProvider = class {
14900
14991
  );
14901
14992
  }
14902
14993
  }
14994
+ /**
14995
+ * Read the AWS-current Lambda permission in CFn-property shape.
14996
+ *
14997
+ * `AWS::Lambda::Permission` has no per-statement Get API — `GetPolicy` on
14998
+ * the parent function returns the entire resource-based policy as a JSON
14999
+ * string, and we have to scan its `Statement` array for the one with our
15000
+ * `Sid` (cdkd's physicalId).
15001
+ *
15002
+ * Returns `undefined` when:
15003
+ * - `properties.FunctionName` is missing (sub-resource needs the parent).
15004
+ * - The function has no policy at all (`ResourceNotFoundException`) or
15005
+ * the matching `Sid` isn't present.
15006
+ *
15007
+ * The reverse-mapping from policy statement back to CFn shape:
15008
+ * - `Action` → `Sid`'s `Action` (string or first element if array).
15009
+ * - `Principal` → `Service` / `AWS` / `*` (CFn flat string form).
15010
+ * - `Condition.ArnLike.AWS:SourceArn` → `SourceArn`.
15011
+ * - `Condition.StringEquals.AWS:SourceAccount` → `SourceAccount`.
15012
+ * - `Condition.StringEquals.aws:PrincipalOrgID` → `PrincipalOrgID`.
15013
+ * - `Condition.ArnLike.AWS:SourceAccount` is left alone — drift on the
15014
+ * condition operator key would be confusing here.
15015
+ */
15016
+ async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
15017
+ const functionName = properties?.["FunctionName"];
15018
+ if (!functionName)
15019
+ return void 0;
15020
+ const statementId = physicalId.includes("|") ? physicalId.split("|").pop() : physicalId;
15021
+ let policyDoc;
15022
+ try {
15023
+ const resp = await this.lambdaClient.send(
15024
+ new GetPolicyCommand({ FunctionName: functionName })
15025
+ );
15026
+ policyDoc = resp.Policy;
15027
+ } catch (err) {
15028
+ if (err instanceof ResourceNotFoundException2)
15029
+ return void 0;
15030
+ throw err;
15031
+ }
15032
+ if (!policyDoc)
15033
+ return void 0;
15034
+ let parsed;
15035
+ try {
15036
+ parsed = JSON.parse(policyDoc);
15037
+ } catch {
15038
+ return void 0;
15039
+ }
15040
+ const statement = parsed.Statement?.find((s) => s.Sid === statementId);
15041
+ if (!statement)
15042
+ return void 0;
15043
+ const result = { FunctionName: functionName };
15044
+ if (statement.Action !== void 0) {
15045
+ result["Action"] = Array.isArray(statement.Action) ? statement.Action[0] : statement.Action;
15046
+ }
15047
+ if (statement.Principal !== void 0) {
15048
+ const p = statement.Principal;
15049
+ if (typeof p === "string") {
15050
+ result["Principal"] = p;
15051
+ } else if (typeof p === "object") {
15052
+ const value = p["Service"] ?? p["AWS"] ?? p["Federated"] ?? void 0;
15053
+ if (value !== void 0) {
15054
+ result["Principal"] = Array.isArray(value) ? value[0] : value;
15055
+ }
15056
+ }
15057
+ }
15058
+ const condition = statement.Condition;
15059
+ if (condition) {
15060
+ const sourceArn = condition["ArnLike"]?.["AWS:SourceArn"] ?? condition["StringEquals"]?.["AWS:SourceArn"];
15061
+ if (sourceArn !== void 0) {
15062
+ result["SourceArn"] = Array.isArray(sourceArn) ? sourceArn[0] : sourceArn;
15063
+ }
15064
+ const sourceAccount = condition["StringEquals"]?.["AWS:SourceAccount"];
15065
+ if (sourceAccount !== void 0) {
15066
+ result["SourceAccount"] = Array.isArray(sourceAccount) ? sourceAccount[0] : sourceAccount;
15067
+ }
15068
+ const orgId = condition["StringEquals"]?.["aws:PrincipalOrgID"];
15069
+ if (orgId !== void 0) {
15070
+ result["PrincipalOrgID"] = Array.isArray(orgId) ? orgId[0] : orgId;
15071
+ }
15072
+ }
15073
+ return result;
15074
+ }
14903
15075
  /**
14904
15076
  * Adopt an existing Lambda permission into cdkd state.
14905
15077
  *
@@ -20815,11 +20987,14 @@ import {
20815
20987
  GetAccountCommand,
20816
20988
  CreateResourceCommand as CreateResourceCommand2,
20817
20989
  DeleteResourceCommand as DeleteResourceCommand2,
20990
+ GetResourceCommand as GetResourceCommand3,
20818
20991
  CreateDeploymentCommand,
20819
20992
  DeleteDeploymentCommand,
20993
+ GetDeploymentCommand,
20820
20994
  CreateStageCommand,
20821
20995
  UpdateStageCommand,
20822
20996
  DeleteStageCommand,
20997
+ GetStageCommand,
20823
20998
  PutMethodCommand,
20824
20999
  DeleteMethodCommand,
20825
21000
  GetMethodCommand,
@@ -20827,6 +21002,7 @@ import {
20827
21002
  PutMethodResponseCommand,
20828
21003
  CreateAuthorizerCommand,
20829
21004
  DeleteAuthorizerCommand,
21005
+ GetAuthorizerCommand,
20830
21006
  NotFoundException as NotFoundException3
20831
21007
  } from "@aws-sdk/client-api-gateway";
20832
21008
  init_aws_clients();
@@ -21822,26 +21998,126 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21822
21998
  * - `AWS::ApiGateway::Method` → `GetMethod`. PhysicalId is the composite
21823
21999
  * `restApiId|resourceId|httpMethod`, so we have everything needed
21824
22000
  * without `Properties`.
21825
- *
21826
- * **Out of scope** (returns `undefined`, falls back to "drift unknown"):
21827
22001
  * - `AWS::ApiGateway::Authorizer` / `Resource` / `Deployment` / `Stage`:
21828
- * each needs the parent `RestApiId` to issue a `Get*` call, but cdkd's
21829
- * `readCurrentState` interface does not pass `Properties` (only the
21830
- * physicalId, which for these types is just the sub-resource id).
21831
- * CC API drift detection picks up `AWS::ApiGateway::RestApi` itself
21832
- * once the user works through the SDK provider boundary; per-sub
21833
- * drift detection here would need a contract change.
22002
+ * each uses `properties.RestApiId` (passed through PR G's signature
22003
+ * extension) to issue the appropriate `Get*` call.
21834
22004
  */
21835
- async readCurrentState(physicalId, _logicalId, resourceType) {
22005
+ async readCurrentState(physicalId, _logicalId, resourceType, properties) {
21836
22006
  switch (resourceType) {
21837
22007
  case "AWS::ApiGateway::Account":
21838
22008
  return this.readCurrentStateAccount();
21839
22009
  case "AWS::ApiGateway::Method":
21840
22010
  return this.readCurrentStateMethod(physicalId);
22011
+ case "AWS::ApiGateway::Authorizer":
22012
+ return this.readCurrentStateAuthorizer(physicalId, properties);
22013
+ case "AWS::ApiGateway::Resource":
22014
+ return this.readCurrentStateResource(physicalId, properties);
22015
+ case "AWS::ApiGateway::Deployment":
22016
+ return this.readCurrentStateDeployment(physicalId, properties);
22017
+ case "AWS::ApiGateway::Stage":
22018
+ return this.readCurrentStateStage(physicalId, properties);
21841
22019
  default:
21842
22020
  return void 0;
21843
22021
  }
21844
22022
  }
22023
+ async readCurrentStateAuthorizer(physicalId, properties) {
22024
+ const restApiId = properties?.["RestApiId"];
22025
+ if (!restApiId)
22026
+ return void 0;
22027
+ try {
22028
+ const resp = await this.apiGatewayClient.send(
22029
+ new GetAuthorizerCommand({ restApiId, authorizerId: physicalId })
22030
+ );
22031
+ const result = { RestApiId: restApiId };
22032
+ if (resp.name !== void 0)
22033
+ result["Name"] = resp.name;
22034
+ if (resp.type !== void 0)
22035
+ result["Type"] = resp.type;
22036
+ if (resp.providerARNs !== void 0 && resp.providerARNs.length > 0) {
22037
+ result["ProviderARNs"] = [...resp.providerARNs];
22038
+ }
22039
+ if (resp.authorizerUri !== void 0)
22040
+ result["AuthorizerUri"] = resp.authorizerUri;
22041
+ if (resp.authorizerCredentials !== void 0) {
22042
+ result["AuthorizerCredentials"] = resp.authorizerCredentials;
22043
+ }
22044
+ if (resp.identitySource !== void 0)
22045
+ result["IdentitySource"] = resp.identitySource;
22046
+ if (resp.identityValidationExpression !== void 0) {
22047
+ result["IdentityValidationExpression"] = resp.identityValidationExpression;
22048
+ }
22049
+ if (resp.authorizerResultTtlInSeconds !== void 0) {
22050
+ result["AuthorizerResultTtlInSeconds"] = resp.authorizerResultTtlInSeconds;
22051
+ }
22052
+ return result;
22053
+ } catch (err) {
22054
+ if (err instanceof NotFoundException3)
22055
+ return void 0;
22056
+ throw err;
22057
+ }
22058
+ }
22059
+ async readCurrentStateResource(physicalId, properties) {
22060
+ const restApiId = properties?.["RestApiId"];
22061
+ if (!restApiId)
22062
+ return void 0;
22063
+ try {
22064
+ const resp = await this.apiGatewayClient.send(
22065
+ new GetResourceCommand3({ restApiId, resourceId: physicalId })
22066
+ );
22067
+ const result = { RestApiId: restApiId };
22068
+ if (resp.parentId !== void 0)
22069
+ result["ParentId"] = resp.parentId;
22070
+ if (resp.pathPart !== void 0)
22071
+ result["PathPart"] = resp.pathPart;
22072
+ return result;
22073
+ } catch (err) {
22074
+ if (err instanceof NotFoundException3)
22075
+ return void 0;
22076
+ throw err;
22077
+ }
22078
+ }
22079
+ async readCurrentStateDeployment(physicalId, properties) {
22080
+ const restApiId = properties?.["RestApiId"];
22081
+ if (!restApiId)
22082
+ return void 0;
22083
+ try {
22084
+ const resp = await this.apiGatewayClient.send(
22085
+ new GetDeploymentCommand({ restApiId, deploymentId: physicalId })
22086
+ );
22087
+ const result = { RestApiId: restApiId };
22088
+ if (resp.description !== void 0 && resp.description !== "") {
22089
+ result["Description"] = resp.description;
22090
+ }
22091
+ return result;
22092
+ } catch (err) {
22093
+ if (err instanceof NotFoundException3)
22094
+ return void 0;
22095
+ throw err;
22096
+ }
22097
+ }
22098
+ async readCurrentStateStage(physicalId, properties) {
22099
+ const restApiId = properties?.["RestApiId"];
22100
+ if (!restApiId)
22101
+ return void 0;
22102
+ try {
22103
+ const resp = await this.apiGatewayClient.send(
22104
+ new GetStageCommand({ restApiId, stageName: physicalId })
22105
+ );
22106
+ const result = { RestApiId: restApiId };
22107
+ if (resp.stageName !== void 0)
22108
+ result["StageName"] = resp.stageName;
22109
+ if (resp.deploymentId !== void 0)
22110
+ result["DeploymentId"] = resp.deploymentId;
22111
+ if (resp.description !== void 0 && resp.description !== "") {
22112
+ result["Description"] = resp.description;
22113
+ }
22114
+ return result;
22115
+ } catch (err) {
22116
+ if (err instanceof NotFoundException3)
22117
+ return void 0;
22118
+ throw err;
22119
+ }
22120
+ }
21845
22121
  async readCurrentStateAccount() {
21846
22122
  try {
21847
22123
  const resp = await this.apiGatewayClient.send(new GetAccountCommand({}));
@@ -21923,12 +22199,16 @@ import {
21923
22199
  DeleteApiCommand,
21924
22200
  CreateStageCommand as CreateStageCommand2,
21925
22201
  DeleteStageCommand as DeleteStageCommand2,
22202
+ GetStageCommand as GetStageCommand2,
21926
22203
  CreateIntegrationCommand,
21927
22204
  DeleteIntegrationCommand,
22205
+ GetIntegrationCommand,
21928
22206
  CreateRouteCommand as CreateRouteCommand2,
21929
22207
  DeleteRouteCommand as DeleteRouteCommand2,
22208
+ GetRouteCommand,
21930
22209
  CreateAuthorizerCommand as CreateAuthorizerCommand2,
21931
22210
  DeleteAuthorizerCommand as DeleteAuthorizerCommand2,
22211
+ GetAuthorizerCommand as GetAuthorizerCommand2,
21932
22212
  GetApiCommand,
21933
22213
  GetApisCommand,
21934
22214
  NotFoundException as NotFoundException4
@@ -22472,18 +22752,27 @@ var ApiGatewayV2Provider = class {
22472
22752
  * **Coverage**:
22473
22753
  * - `AWS::ApiGatewayV2::Api` → `GetApi`. PhysicalId is the apiId,
22474
22754
  * self-sufficient.
22475
- *
22476
- * **Out of scope** (returns `undefined`, falls back to "drift unknown"):
22477
22755
  * - `AWS::ApiGatewayV2::Stage` / `Integration` / `Route` / `Authorizer`:
22478
- * each needs the parent `ApiId` to issue a `Get*` call, but cdkd's
22479
- * `readCurrentState` interface does not pass `Properties` (only the
22480
- * physicalId, which for these types is just the sub-resource id).
22481
- * Per-sub drift detection here would need a contract change.
22756
+ * each uses `properties.ApiId` (passed through PR G's signature
22757
+ * extension) to issue the appropriate `Get*` call.
22482
22758
  */
22483
- async readCurrentState(physicalId, _logicalId, resourceType) {
22484
- if (resourceType !== "AWS::ApiGatewayV2::Api") {
22485
- return void 0;
22759
+ async readCurrentState(physicalId, _logicalId, resourceType, properties) {
22760
+ switch (resourceType) {
22761
+ case "AWS::ApiGatewayV2::Api":
22762
+ return this.readApi(physicalId);
22763
+ case "AWS::ApiGatewayV2::Stage":
22764
+ return this.readStage(physicalId, properties);
22765
+ case "AWS::ApiGatewayV2::Integration":
22766
+ return this.readIntegration(physicalId, properties);
22767
+ case "AWS::ApiGatewayV2::Route":
22768
+ return this.readRoute(physicalId, properties);
22769
+ case "AWS::ApiGatewayV2::Authorizer":
22770
+ return this.readAuthorizer(physicalId, properties);
22771
+ default:
22772
+ return void 0;
22486
22773
  }
22774
+ }
22775
+ async readApi(physicalId) {
22487
22776
  try {
22488
22777
  const resp = await this.getClient().send(new GetApiCommand({ ApiId: physicalId }));
22489
22778
  const result = {};
@@ -22503,6 +22792,108 @@ var ApiGatewayV2Provider = class {
22503
22792
  throw err;
22504
22793
  }
22505
22794
  }
22795
+ async readStage(physicalId, properties) {
22796
+ const apiId = properties?.["ApiId"];
22797
+ if (!apiId)
22798
+ return void 0;
22799
+ try {
22800
+ const resp = await this.getClient().send(
22801
+ new GetStageCommand2({ ApiId: apiId, StageName: physicalId })
22802
+ );
22803
+ const result = { ApiId: apiId };
22804
+ if (resp.StageName !== void 0)
22805
+ result["StageName"] = resp.StageName;
22806
+ if (resp.AutoDeploy !== void 0)
22807
+ result["AutoDeploy"] = resp.AutoDeploy;
22808
+ if (resp.Description !== void 0 && resp.Description !== "") {
22809
+ result["Description"] = resp.Description;
22810
+ }
22811
+ return result;
22812
+ } catch (err) {
22813
+ if (err instanceof NotFoundException4)
22814
+ return void 0;
22815
+ throw err;
22816
+ }
22817
+ }
22818
+ async readIntegration(physicalId, properties) {
22819
+ const apiId = properties?.["ApiId"];
22820
+ if (!apiId)
22821
+ return void 0;
22822
+ try {
22823
+ const resp = await this.getClient().send(
22824
+ new GetIntegrationCommand({ ApiId: apiId, IntegrationId: physicalId })
22825
+ );
22826
+ const result = { ApiId: apiId };
22827
+ if (resp.IntegrationType !== void 0)
22828
+ result["IntegrationType"] = resp.IntegrationType;
22829
+ if (resp.IntegrationUri !== void 0)
22830
+ result["IntegrationUri"] = resp.IntegrationUri;
22831
+ if (resp.IntegrationMethod !== void 0)
22832
+ result["IntegrationMethod"] = resp.IntegrationMethod;
22833
+ if (resp.PayloadFormatVersion !== void 0) {
22834
+ result["PayloadFormatVersion"] = resp.PayloadFormatVersion;
22835
+ }
22836
+ return result;
22837
+ } catch (err) {
22838
+ if (err instanceof NotFoundException4)
22839
+ return void 0;
22840
+ throw err;
22841
+ }
22842
+ }
22843
+ async readRoute(physicalId, properties) {
22844
+ const apiId = properties?.["ApiId"];
22845
+ if (!apiId)
22846
+ return void 0;
22847
+ try {
22848
+ const resp = await this.getClient().send(
22849
+ new GetRouteCommand({ ApiId: apiId, RouteId: physicalId })
22850
+ );
22851
+ const result = { ApiId: apiId };
22852
+ if (resp.RouteKey !== void 0)
22853
+ result["RouteKey"] = resp.RouteKey;
22854
+ if (resp.Target !== void 0)
22855
+ result["Target"] = resp.Target;
22856
+ if (resp.AuthorizationType !== void 0)
22857
+ result["AuthorizationType"] = resp.AuthorizationType;
22858
+ if (resp.AuthorizerId !== void 0)
22859
+ result["AuthorizerId"] = resp.AuthorizerId;
22860
+ return result;
22861
+ } catch (err) {
22862
+ if (err instanceof NotFoundException4)
22863
+ return void 0;
22864
+ throw err;
22865
+ }
22866
+ }
22867
+ async readAuthorizer(physicalId, properties) {
22868
+ const apiId = properties?.["ApiId"];
22869
+ if (!apiId)
22870
+ return void 0;
22871
+ try {
22872
+ const resp = await this.getClient().send(
22873
+ new GetAuthorizerCommand2({ ApiId: apiId, AuthorizerId: physicalId })
22874
+ );
22875
+ const result = { ApiId: apiId };
22876
+ if (resp.AuthorizerType !== void 0)
22877
+ result["AuthorizerType"] = resp.AuthorizerType;
22878
+ if (resp.Name !== void 0)
22879
+ result["Name"] = resp.Name;
22880
+ if (resp.IdentitySource !== void 0 && resp.IdentitySource.length > 0) {
22881
+ result["IdentitySource"] = [...resp.IdentitySource];
22882
+ }
22883
+ if (resp.JwtConfiguration)
22884
+ result["JwtConfiguration"] = resp.JwtConfiguration;
22885
+ if (resp.AuthorizerUri !== void 0)
22886
+ result["AuthorizerUri"] = resp.AuthorizerUri;
22887
+ if (resp.AuthorizerPayloadFormatVersion !== void 0) {
22888
+ result["AuthorizerPayloadFormatVersion"] = resp.AuthorizerPayloadFormatVersion;
22889
+ }
22890
+ return result;
22891
+ } catch (err) {
22892
+ if (err instanceof NotFoundException4)
22893
+ return void 0;
22894
+ throw err;
22895
+ }
22896
+ }
22506
22897
  // ─── Import ───────────────────────────────────────────────────────
22507
22898
  /**
22508
22899
  * Adopt an existing API Gateway V2 resource into cdkd state.
@@ -30120,7 +30511,7 @@ var AppSyncProvider = class {
30120
30511
  *
30121
30512
  * Returns `undefined` when the parent resource is gone (`NotFoundException`).
30122
30513
  */
30123
- async readCurrentState(physicalId, _logicalId, resourceType) {
30514
+ async readCurrentState(physicalId, _logicalId, resourceType, _properties) {
30124
30515
  switch (resourceType) {
30125
30516
  case "AWS::AppSync::GraphQLApi":
30126
30517
  return this.readGraphQLApi(physicalId);
@@ -31849,10 +32240,15 @@ var KinesisStreamProvider = class {
31849
32240
  * mode reports an empty list).
31850
32241
  *
31851
32242
  * Returns `undefined` when the stream is gone (`ResourceNotFoundException`).
31852
- * Only `AWS::Kinesis::Stream` is supported (the provider does not handle
31853
- * `AWS::Kinesis::StreamConsumer`).
32243
+ *
32244
+ * `AWS::Kinesis::StreamConsumer` is intentionally not handled here: this
32245
+ * provider only registers `AWS::Kinesis::Stream`, so consumer resources
32246
+ * route to the CC API fallback for drift detection (CC API's `GetResource`
32247
+ * surfaces every Kinesis consumer attribute the user can configure). A
32248
+ * dedicated SDK impl would require building out create/update/delete first;
32249
+ * out of scope for PR G.
31854
32250
  */
31855
- async readCurrentState(physicalId, _logicalId, resourceType) {
32251
+ async readCurrentState(physicalId, _logicalId, resourceType, _properties) {
31856
32252
  if (resourceType !== "AWS::Kinesis::Stream")
31857
32253
  return void 0;
31858
32254
  let stream;
@@ -37654,6 +38050,128 @@ function isPlainObject(value) {
37654
38050
  return typeof value === "object" && value !== null;
37655
38051
  }
37656
38052
 
38053
+ // src/analyzer/drift-cc-api-deny-list.ts
38054
+ var CC_API_FALLBACK_DENY_LIST = {
38055
+ // AWS::IAM::ManagedPolicy: PolicyDocument round-trips through CC API
38056
+ // URL-encoded; cdkd state stores it as a parsed JSON object. Without
38057
+ // a per-type decoder, every comparison sees a string-vs-object
38058
+ // mismatch and fires drift on every run. The IAM Role provider's
38059
+ // first-class readCurrentState handles its inline AssumeRolePolicy
38060
+ // the same way (URL-decode + JSON-parse); the same fix pattern is
38061
+ // needed for ManagedPolicy when an SDK provider is added.
38062
+ "AWS::IAM::ManagedPolicy": "PolicyDocument is URL-encoded JSON in CC API responses, but cdkd state stores it as a parsed object \u2014 needs per-type decode",
38063
+ // AWS::ApiGateway::RestApi: the `Body` property (OpenAPI spec object
38064
+ // when supplied via `Body` rather than `BodyS3Location`) is write-only
38065
+ // — `GetRestApi` does NOT return it, but cdkd state preserves the
38066
+ // object the user passed in. CC API GetResource inherits this — the
38067
+ // returned shape omits `Body`, so every drift run flags it as
38068
+ // missing-on-AWS. Comparison silently dropping it would also be
38069
+ // wrong; the right move is a dedicated SDK provider that knows to
38070
+ // skip `Body` entirely.
38071
+ "AWS::ApiGateway::RestApi": "Body / BodyS3Location are write-only inputs not returned by CC API GetResource; cdkd state preserves them",
38072
+ // AWS::CloudFormation::Stack: nested stacks aren't supported by cdkd's
38073
+ // provider registry at all; the deploy / destroy paths reject them.
38074
+ // Listing here is defense-in-depth — if a user manually crafts state
38075
+ // with one, drift via CC API would compare CFn-template-input
38076
+ // properties against CC API's stack-output shape (CC API's
38077
+ // `AWS::CloudFormation::Stack` reports outputs / status, not the
38078
+ // template parameters cdkd would have stored).
38079
+ "AWS::CloudFormation::Stack": "CC API returns runtime stack state (outputs/status), not the template parameters cdkd state stores",
38080
+ // AWS::EC2::LaunchTemplate: `LaunchTemplateData` ships with deeply
38081
+ // structured sub-objects that CC API normalizes into a versioned shape
38082
+ // — every UpdateLaunchTemplate (and even GetLaunchTemplate) bumps the
38083
+ // returned default version, and CC API attaches a synthetic
38084
+ // `LatestVersionNumber` / `DefaultVersionNumber` next to the template
38085
+ // data that drift would surface as drift on the parent. Until an SDK
38086
+ // provider strips those, deny.
38087
+ "AWS::EC2::LaunchTemplate": "CC API returns version-bumped LaunchTemplateData with synthetic LatestVersionNumber that diverges from the CFn input shape"
38088
+ };
38089
+
38090
+ // src/analyzer/cc-api-strip.ts
38091
+ var ALWAYS_STRIPPED_FIELDS = /* @__PURE__ */ new Set([
38092
+ // Timestamps — AWS-managed, change on every modification. Names are
38093
+ // unambiguous: no CFn template ever exposes a "CreationDate" /
38094
+ // "LastModifiedTime" as a settable input.
38095
+ "CreationDate",
38096
+ "CreationTime",
38097
+ "CreatedTime",
38098
+ "CreatedDate",
38099
+ "CreatedAt",
38100
+ "LastModifiedDate",
38101
+ "LastModifiedTime",
38102
+ "LastModified",
38103
+ "LastUpdatedTime",
38104
+ "LastUpdatedDate",
38105
+ "UpdatedAt",
38106
+ // Owner / account / principal info — derived from the calling
38107
+ // principal, never user-set in a CFn template. `CreatedBy` /
38108
+ // `OwnerArn` are unique enough that no settable CFn property
38109
+ // collides with them.
38110
+ "OwnerId",
38111
+ "OwnerAccountId",
38112
+ "CreatedBy",
38113
+ "OwnerArn",
38114
+ // Lambda-specific generated identifiers. `RevisionId` rotates on
38115
+ // every operation; `LastUpdateStatus*` mirror runtime state. None
38116
+ // are settable in a CFn template.
38117
+ "RevisionId",
38118
+ "LastUpdateStatus",
38119
+ "LastUpdateStatusReason",
38120
+ "LastUpdateStatusReasonCode",
38121
+ // CloudFormation/Cloud Control passthrough metadata — never
38122
+ // appears as a settable input in a CFn template body.
38123
+ "StackId",
38124
+ "PhysicalResourceId",
38125
+ "LogicalResourceId"
38126
+ // Notes on intentional EXCLUSIONS:
38127
+ //
38128
+ // `State` / `Status` / `StateReason` / `StatusReason` — these
38129
+ // names ARE used by some CFn types as settable nested properties
38130
+ // (e.g. `AWS::ECS::CapacityProvider.AutoScalingGroupProvider.ManagedScaling.Status`,
38131
+ // `AWS::S3::Bucket.VersioningConfiguration.Status`). Stripping them
38132
+ // globally would cause false-positive drift on a clean stack.
38133
+ // The comparator already ignores AWS-only top-level `Status` values
38134
+ // because state doesn't carry them; only the nested-name-collision
38135
+ // cases would have leaked through, and excluding them here protects
38136
+ // those.
38137
+ //
38138
+ // `Arn` — many CFn types accept `Arn` as a settable property and
38139
+ // cdkd state may record it at create time. Drift on `Arn` is
38140
+ // genuine drift the user wants to see.
38141
+ //
38142
+ // `VersionId` / `GenerationId` / `ETag` — narrow utility (only S3
38143
+ // / KMS / ImageBuilder use these in their Get* responses), and at
38144
+ // least `VersionId` IS a settable input on `AWS::S3::Bucket`'s
38145
+ // versioning config. Per-provider readCurrentState handles them.
38146
+ //
38147
+ // `AccountId` / `StackName` — also collide with settable inputs
38148
+ // on a few CFn types (`AWS::CloudWatch::CrossAccountSharingRule.AccountId`,
38149
+ // `AWS::CloudFormation::HookDefaultVersion.StackName`).
38150
+ //
38151
+ // `StartTime` / `EndTime` — used as settable inputs in scheduling
38152
+ // shapes (e.g. `AWS::AutoScaling::ScheduledAction.StartTime`).
38153
+ ]);
38154
+ function stripCcApiAwsManagedFields(resourceType, awsProps) {
38155
+ return stripWalk(awsProps);
38156
+ }
38157
+ function stripWalk(value) {
38158
+ if (value === null || value === void 0)
38159
+ return value;
38160
+ if (Array.isArray(value)) {
38161
+ return value.map(stripWalk);
38162
+ }
38163
+ if (typeof value === "object") {
38164
+ const out = {};
38165
+ for (const [key, child] of Object.entries(value)) {
38166
+ if (ALWAYS_STRIPPED_FIELDS.has(key))
38167
+ continue;
38168
+ out[key] = stripWalk(child);
38169
+ }
38170
+ return out;
38171
+ }
38172
+ return value;
38173
+ }
38174
+
37657
38175
  // src/cli/commands/drift.ts
37658
38176
  var DriftDetectedError = class _DriftDetectedError extends CdkdError {
37659
38177
  silent = true;
@@ -37696,6 +38214,7 @@ async function driftCommand(stacks, options) {
37696
38214
  const providerRegistry = new ProviderRegistry();
37697
38215
  registerAllProviders(providerRegistry);
37698
38216
  providerRegistry.setCustomResourceResponseBucket(bucket);
38217
+ const ccApiFallback = new CloudControlProvider();
37699
38218
  const stateRefs = await stateBackend.listStacks();
37700
38219
  const targetRefs = resolveTargetRefs(stacks, stateRefs, options);
37701
38220
  const reports = [];
@@ -37709,7 +38228,8 @@ async function driftCommand(stacks, options) {
37709
38228
  ref.stackName,
37710
38229
  ref.region,
37711
38230
  stateBackend,
37712
- providerRegistry
38231
+ providerRegistry,
38232
+ ccApiFallback
37713
38233
  );
37714
38234
  reports.push(report);
37715
38235
  }
@@ -37780,7 +38300,7 @@ function resolveTargetRefs(stacks, stateRefs, options) {
37780
38300
  }
37781
38301
  return out;
37782
38302
  }
37783
- async function runDriftForStack(stackName, region, stateBackend, providerRegistry) {
38303
+ async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback) {
37784
38304
  const result = await stateBackend.getState(stackName, region);
37785
38305
  if (!result) {
37786
38306
  throw new Error(
@@ -37806,22 +38326,39 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
37806
38326
  });
37807
38327
  continue;
37808
38328
  }
37809
- let readCurrentState = provider.readCurrentState?.bind(provider);
37810
- if (!readCurrentState) {
37811
- const ccApiProvider = providerRegistry.getCloudControlProvider?.();
37812
- if (ccApiProvider?.readCurrentState) {
37813
- readCurrentState = ccApiProvider.readCurrentState.bind(ccApiProvider);
38329
+ let aws;
38330
+ if (provider.readCurrentState) {
38331
+ aws = await provider.readCurrentState(
38332
+ resource.physicalId,
38333
+ logicalId,
38334
+ resource.resourceType,
38335
+ resource.properties ?? {}
38336
+ );
38337
+ } else {
38338
+ if (CC_API_FALLBACK_DENY_LIST[resource.resourceType]) {
38339
+ outcomes.push({
38340
+ kind: "unsupported",
38341
+ logicalId,
38342
+ resourceType: resource.resourceType
38343
+ });
38344
+ continue;
37814
38345
  }
37815
- }
37816
- if (!readCurrentState) {
37817
- outcomes.push({
37818
- kind: "unsupported",
38346
+ const ccApiAws = await ccApiFallback.readCurrentState(
38347
+ resource.physicalId,
37819
38348
  logicalId,
37820
- resourceType: resource.resourceType
37821
- });
37822
- continue;
38349
+ resource.resourceType,
38350
+ resource.properties ?? {}
38351
+ );
38352
+ if (ccApiAws === void 0) {
38353
+ outcomes.push({
38354
+ kind: "unsupported",
38355
+ logicalId,
38356
+ resourceType: resource.resourceType
38357
+ });
38358
+ continue;
38359
+ }
38360
+ aws = stripCcApiAwsManagedFields(resource.resourceType, ccApiAws);
37823
38361
  }
37824
- const aws = await readCurrentState(resource.physicalId, logicalId, resource.resourceType);
37825
38362
  if (aws === void 0) {
37826
38363
  outcomes.push({
37827
38364
  kind: "unsupported",
@@ -41359,7 +41896,7 @@ function reorderArgs(argv) {
41359
41896
  }
41360
41897
  async function main() {
41361
41898
  const program = new Command14();
41362
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.42.0");
41899
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.44.0");
41363
41900
  program.addCommand(createBootstrapCommand());
41364
41901
  program.addCommand(createSynthCommand());
41365
41902
  program.addCommand(createListCommand());