@beesolve/aws-accounts 1.2.1 → 1.3.1

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.
@@ -82,27 +82,37 @@ async function executeOperation(props) {
82
82
  props.logger.log(
83
83
  `Moving "${props.operation.accountName}" (${props.operation.accountId}): ${props.operation.fromOuName} -> ${props.operation.toOuName}`
84
84
  );
85
+ const toOuId = resolveOrganizationalUnitId({
86
+ state: props.state,
87
+ organizationalUnitId: props.operation.toOuId,
88
+ organizationalUnitName: props.operation.toOuName
89
+ });
85
90
  await props.organizationsClient.send(
86
91
  new MoveAccountCommand({
87
92
  AccountId: props.operation.accountId,
88
93
  SourceParentId: props.operation.fromOuId,
89
- DestinationParentId: props.operation.toOuId
94
+ DestinationParentId: toOuId
90
95
  })
91
96
  );
92
97
  props.logger.log(`Done: "${props.operation.accountName}"`);
93
98
  return moveAccountInWorkingState({
94
99
  workingState: props.state,
95
100
  accountId: props.operation.accountId,
96
- parentId: props.operation.toOuId
101
+ parentId: toOuId
97
102
  });
98
103
  }
99
104
  if (props.operation.kind === "createOu") {
100
105
  props.logger.log(
101
106
  `Creating OU "${props.operation.ouName}" under ${props.operation.parentOuName}...`
102
107
  );
108
+ const parentOuId = resolveOrganizationalUnitId({
109
+ state: props.state,
110
+ organizationalUnitId: props.operation.parentOuId,
111
+ organizationalUnitName: props.operation.parentOuName
112
+ });
103
113
  const response = await props.organizationsClient.send(
104
114
  new CreateOrganizationalUnitCommand({
105
- ParentId: props.operation.parentOuId,
115
+ ParentId: parentOuId,
106
116
  Name: props.operation.ouName
107
117
  })
108
118
  );
@@ -117,7 +127,7 @@ async function executeOperation(props) {
117
127
  workingState: props.state,
118
128
  organizationalUnit: {
119
129
  id: createdOu.Id,
120
- parentId: props.operation.parentOuId,
130
+ parentId: parentOuId,
121
131
  arn: createdOu.Arn,
122
132
  name: createdOu.Name
123
133
  }
@@ -159,13 +169,18 @@ async function executeOperation(props) {
159
169
  });
160
170
  }
161
171
  if (props.operation.kind === "createAccount") {
172
+ const targetOuId = resolveOrganizationalUnitId({
173
+ state: props.state,
174
+ organizationalUnitId: props.operation.targetOuId,
175
+ organizationalUnitName: props.operation.targetOuName
176
+ });
162
177
  const result = await createAccountAndMoveToOu({
163
178
  organizationsClient: props.organizationsClient,
164
179
  logger: props.logger,
165
180
  accountName: props.operation.accountName,
166
181
  accountEmail: props.operation.accountEmail,
167
182
  sourceParentId: props.context.organization.rootId,
168
- destinationParentId: props.operation.targetOuId,
183
+ destinationParentId: targetOuId,
169
184
  timeoutInMs: props.runtime.createAccount.timeoutInMs,
170
185
  pollIntervalInMs: props.runtime.createAccount.pollIntervalInMs
171
186
  });
@@ -177,7 +192,7 @@ async function executeOperation(props) {
177
192
  name: result.account.name,
178
193
  email: result.account.email,
179
194
  status: result.account.status,
180
- parentId: props.operation.targetOuId,
195
+ parentId: targetOuId,
181
196
  tags: []
182
197
  }
183
198
  });
@@ -1305,6 +1320,20 @@ function resolveGroupByDisplayName(props) {
1305
1320
  }
1306
1321
  return group;
1307
1322
  }
1323
+ function resolveOrganizationalUnitId(props) {
1324
+ if (props.organizationalUnitId !== "__pending_creation__") {
1325
+ return props.organizationalUnitId;
1326
+ }
1327
+ const ou = Object.values(
1328
+ props.state.organization.organizationalUnitsById
1329
+ ).find((ou2) => ou2.name === props.organizationalUnitName);
1330
+ if (ou == null) {
1331
+ throw new Error(
1332
+ `Could not resolve OU "${props.organizationalUnitName}" in working state.`
1333
+ );
1334
+ }
1335
+ return ou.id;
1336
+ }
1308
1337
  function resolvePolicyId(props) {
1309
1338
  if (props.policyId !== "__pending_creation__") return props.policyId;
1310
1339
  const policy = props.state.organization.policiesByName[props.policyName];
package/dist/awsConfig.js CHANGED
@@ -1166,8 +1166,7 @@ function renderAwsConfigTs(props) {
1166
1166
  indentLevel: 0,
1167
1167
  withinInlinePolicy: false
1168
1168
  });
1169
- return `import * as v from "valibot";
1170
- import { awsConfigSchema, iam, type AwsConfig } from "./aws.config.types.js";
1169
+ return `import { iam, type AwsConfig } from "./aws.config.types.js";
1171
1170
 
1172
1171
  /**
1173
1172
  * Human-editable AWS config.
@@ -1179,7 +1178,7 @@ import { awsConfigSchema, iam, type AwsConfig } from "./aws.config.types.js";
1179
1178
  * "Graveyard" is bootstrap-managed and used internally as the account-removal sink;
1180
1179
  * it is intentionally omitted from generated organizationalUnits in this file.
1181
1180
  */
1182
- const awsConfig: AwsConfig = v.parse(awsConfigSchema, ${serializedConfig} satisfies AwsConfig);
1181
+ const awsConfig: AwsConfig = ${serializedConfig} satisfies AwsConfig;
1183
1182
 
1184
1183
  export default awsConfig;
1185
1184
  `;
package/dist/diff.js CHANGED
@@ -60,6 +60,11 @@ function diffStates(props) {
60
60
  const nextAccountIds = new Set(
61
61
  nextOrganization.accounts.filter((account) => account.id !== pendingCreationId).map((account) => account.id)
62
62
  );
63
+ const ouNamesBeingCreated = new Set(
64
+ nextOrganization.organizationalUnits.filter(
65
+ (ou) => !currentOrganization.organizationalUnitByName.has(ou.name)
66
+ ).map((ou) => ou.name)
67
+ );
63
68
  for (const nextAccount of nextOrganization.accounts) {
64
69
  const currentAccount = nextAccount.id !== pendingCreationId ? currentOrganization.accountById.get(nextAccount.id) : void 0;
65
70
  if (currentAccount == null) {
@@ -74,11 +79,21 @@ function diffStates(props) {
74
79
  organizationalUnitNameById: nextOrganization.organizationalUnitNameById,
75
80
  organizationalUnitId: nextAccount.parentId
76
81
  }) === false) {
77
- unsupported.push({
78
- kind: "newAccountWithUnknownOu",
79
- category: "unsupportedMutation",
80
- description: `new account "${nextAccount.name}" has unresolved target OU "${targetOuName}" (${nextAccount.parentId})`
81
- });
82
+ if (ouNamesBeingCreated.has(targetOuName)) {
83
+ operations.push({
84
+ kind: "createAccount",
85
+ accountName: nextAccount.name,
86
+ accountEmail: nextAccount.email,
87
+ targetOuId: nextAccount.parentId,
88
+ targetOuName
89
+ });
90
+ } else {
91
+ unsupported.push({
92
+ kind: "newAccountWithUnknownOu",
93
+ category: "unsupportedMutation",
94
+ description: `new account "${nextAccount.name}" has unresolved target OU "${targetOuName}" (${nextAccount.parentId})`
95
+ });
96
+ }
82
97
  continue;
83
98
  }
84
99
  operations.push({
@@ -121,7 +136,7 @@ function diffStates(props) {
121
136
  });
122
137
  continue;
123
138
  }
124
- if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId || nextAccount.parentId === pendingCreationId) {
139
+ if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId) {
125
140
  continue;
126
141
  }
127
142
  const fromOuName = resolveOrganizationalUnitName({
@@ -134,6 +149,41 @@ function diffStates(props) {
134
149
  rootId: nextOrganization.rootId,
135
150
  organizationalUnitId: nextAccount.parentId
136
151
  });
152
+ if (nextAccount.parentId === pendingCreationId) {
153
+ if (ouNamesBeingCreated.has(toOuName)) {
154
+ if (currentAccount.name !== nextAccount.name) {
155
+ operations.push({
156
+ kind: "updateAccountName",
157
+ accountId: nextAccount.id,
158
+ fromAccountName: currentAccount.name,
159
+ toAccountName: nextAccount.name
160
+ });
161
+ }
162
+ operations.push({
163
+ kind: "moveAccount",
164
+ accountId: nextAccount.id,
165
+ accountName: nextAccount.name,
166
+ fromOuId: currentAccount.parentId,
167
+ fromOuName,
168
+ toOuId: nextAccount.parentId,
169
+ toOuName
170
+ });
171
+ diffAlternateContacts({
172
+ operations,
173
+ accountId: nextAccount.id,
174
+ accountName: nextAccount.name,
175
+ currentContacts: currentAccount.alternateContacts ?? [],
176
+ nextContacts: nextAccount.alternateContacts ?? []
177
+ });
178
+ } else {
179
+ unsupported.push({
180
+ kind: "existingAccountWithUnknownTargetOu",
181
+ category: "unsupportedMutation",
182
+ description: `existing account "${nextAccount.name}" has unresolved target OU "${toOuName}" (${nextAccount.parentId})`
183
+ });
184
+ }
185
+ continue;
186
+ }
137
187
  if (currentAccount.name !== nextAccount.name) {
138
188
  operations.push({
139
189
  kind: "updateAccountName",
@@ -310,11 +360,20 @@ function diffStates(props) {
310
360
  organizationalUnitNameById: nextOrganization.organizationalUnitNameById,
311
361
  organizationalUnitId: addedOrganizationalUnit.parentId
312
362
  }) === false) {
313
- unsupported.push({
314
- kind: "newOuWithUnknownParent",
315
- category: "unsupportedMutation",
316
- description: `new OU "${addedOrganizationalUnit.name}" has unresolved parent "${parentOuName}" (${addedOrganizationalUnit.parentId})`
317
- });
363
+ if (ouNamesBeingCreated.has(parentOuName) && parentOuName !== addedOrganizationalUnit.name) {
364
+ operations.push({
365
+ kind: "createOu",
366
+ ouName: addedOrganizationalUnit.name,
367
+ parentOuId: addedOrganizationalUnit.parentId,
368
+ parentOuName
369
+ });
370
+ } else {
371
+ unsupported.push({
372
+ kind: "newOuWithUnknownParent",
373
+ category: "unsupportedMutation",
374
+ description: `new OU "${addedOrganizationalUnit.name}" has unresolved parent "${parentOuName}" (${addedOrganizationalUnit.parentId})`
375
+ });
376
+ }
318
377
  continue;
319
378
  }
320
379
  operations.push({
@@ -864,6 +923,27 @@ function diffStates(props) {
864
923
  }
865
924
  return getOperationSortKey(left).localeCompare(getOperationSortKey(right));
866
925
  });
926
+ const createOuOps = operations.filter(
927
+ (op) => op.kind === "createOu"
928
+ );
929
+ const otherOps = operations.filter((op) => op.kind !== "createOu");
930
+ if (createOuOps.some((op) => op.parentOuId === pendingCreationId)) {
931
+ let visitOu2 = function(op) {
932
+ if (visited.has(op.ouName)) return;
933
+ visited.add(op.ouName);
934
+ if (op.parentOuId === pendingCreationId) {
935
+ const parent = createOuByName.get(op.parentOuName);
936
+ if (parent != null) visitOu2(parent);
937
+ }
938
+ topoSorted.push(op);
939
+ };
940
+ var visitOu = visitOu2;
941
+ const createOuByName = new Map(createOuOps.map((op) => [op.ouName, op]));
942
+ const visited = /* @__PURE__ */ new Set();
943
+ const topoSorted = [];
944
+ for (const op of createOuOps) visitOu2(op);
945
+ operations.splice(0, operations.length, ...topoSorted, ...otherOps);
946
+ }
867
947
  unsupported.sort((left, right) => {
868
948
  const kindComparison = left.kind.localeCompare(right.kind);
869
949
  if (kindComparison !== 0) {
@@ -318,6 +318,7 @@ const unsupportedDiffKindSchema = v.picklist([
318
318
  "reparentedOu",
319
319
  "newOuWithUnknownParent",
320
320
  "newAccountWithUnknownOu",
321
+ "existingAccountWithUnknownTargetOu",
321
322
  "removedOu"
322
323
  ]);
323
324
  const unsupportedDiffCategorySchema = v.picklist([
package/dist/scanLogic.js CHANGED
@@ -528,27 +528,34 @@ async function getInlinePolicyForPermissionSet(props) {
528
528
  return inlinePolicy != null && inlinePolicy.length > 0 ? inlinePolicy : null;
529
529
  }
530
530
  async function getPermissionsBoundaryForPermissionSet(props) {
531
- const response = await props.ssoAdminClient.send(
532
- new GetPermissionsBoundaryForPermissionSetCommand({
533
- InstanceArn: props.instanceArn,
534
- PermissionSetArn: props.permissionSetArn
535
- })
536
- );
537
- const boundary = response.PermissionsBoundary;
538
- if (boundary == null) {
531
+ try {
532
+ const response = await props.ssoAdminClient.send(
533
+ new GetPermissionsBoundaryForPermissionSetCommand({
534
+ InstanceArn: props.instanceArn,
535
+ PermissionSetArn: props.permissionSetArn
536
+ })
537
+ );
538
+ const boundary = response.PermissionsBoundary;
539
+ if (boundary == null) {
540
+ return null;
541
+ }
542
+ if (boundary.ManagedPolicyArn != null) {
543
+ return { managedPolicyArn: boundary.ManagedPolicyArn };
544
+ }
545
+ const ref = boundary.CustomerManagedPolicyReference;
546
+ if (ref?.Name != null) {
547
+ return {
548
+ customerManagedPolicyName: ref.Name,
549
+ customerManagedPolicyPath: ref.Path ?? "/"
550
+ };
551
+ }
539
552
  return null;
553
+ } catch (err) {
554
+ if (err != null && typeof err === "object" && "name" in err && err.name === "ResourceNotFoundException") {
555
+ return null;
556
+ }
557
+ throw err;
540
558
  }
541
- if (boundary.ManagedPolicyArn != null) {
542
- return { managedPolicyArn: boundary.ManagedPolicyArn };
543
- }
544
- const ref = boundary.CustomerManagedPolicyReference;
545
- if (ref?.Name != null) {
546
- return {
547
- customerManagedPolicyName: ref.Name,
548
- customerManagedPolicyPath: ref.Path ?? "/"
549
- };
550
- }
551
- return null;
552
559
  }
553
560
  async function listManagedPoliciesInPermissionSet(props) {
554
561
  const managedPolicies = [];
@@ -957,6 +957,7 @@ var unsupportedDiffKindSchema = picklist([
957
957
  "reparentedOu",
958
958
  "newOuWithUnknownParent",
959
959
  "newAccountWithUnknownOu",
960
+ "existingAccountWithUnknownTargetOu",
960
961
  "removedOu"
961
962
  ]);
962
963
  var unsupportedDiffCategorySchema = picklist([
@@ -2313,27 +2314,34 @@ async function getInlinePolicyForPermissionSet(props) {
2313
2314
  return inlinePolicy != null && inlinePolicy.length > 0 ? inlinePolicy : null;
2314
2315
  }
2315
2316
  async function getPermissionsBoundaryForPermissionSet(props) {
2316
- const response = await props.ssoAdminClient.send(
2317
- new GetPermissionsBoundaryForPermissionSetCommand({
2318
- InstanceArn: props.instanceArn,
2319
- PermissionSetArn: props.permissionSetArn
2320
- })
2321
- );
2322
- const boundary = response.PermissionsBoundary;
2323
- if (boundary == null) {
2317
+ try {
2318
+ const response = await props.ssoAdminClient.send(
2319
+ new GetPermissionsBoundaryForPermissionSetCommand({
2320
+ InstanceArn: props.instanceArn,
2321
+ PermissionSetArn: props.permissionSetArn
2322
+ })
2323
+ );
2324
+ const boundary = response.PermissionsBoundary;
2325
+ if (boundary == null) {
2326
+ return null;
2327
+ }
2328
+ if (boundary.ManagedPolicyArn != null) {
2329
+ return { managedPolicyArn: boundary.ManagedPolicyArn };
2330
+ }
2331
+ const ref = boundary.CustomerManagedPolicyReference;
2332
+ if (ref?.Name != null) {
2333
+ return {
2334
+ customerManagedPolicyName: ref.Name,
2335
+ customerManagedPolicyPath: ref.Path ?? "/"
2336
+ };
2337
+ }
2324
2338
  return null;
2339
+ } catch (err) {
2340
+ if (err != null && typeof err === "object" && "name" in err && err.name === "ResourceNotFoundException") {
2341
+ return null;
2342
+ }
2343
+ throw err;
2325
2344
  }
2326
- if (boundary.ManagedPolicyArn != null) {
2327
- return { managedPolicyArn: boundary.ManagedPolicyArn };
2328
- }
2329
- const ref = boundary.CustomerManagedPolicyReference;
2330
- if (ref?.Name != null) {
2331
- return {
2332
- customerManagedPolicyName: ref.Name,
2333
- customerManagedPolicyPath: ref.Path ?? "/"
2334
- };
2335
- }
2336
- return null;
2337
2345
  }
2338
2346
  async function listManagedPoliciesInPermissionSet(props) {
2339
2347
  const managedPolicies = [];
@@ -2659,27 +2667,37 @@ async function executeOperation(props) {
2659
2667
  props.logger.log(
2660
2668
  `Moving "${props.operation.accountName}" (${props.operation.accountId}): ${props.operation.fromOuName} -> ${props.operation.toOuName}`
2661
2669
  );
2670
+ const toOuId = resolveOrganizationalUnitId({
2671
+ state: props.state,
2672
+ organizationalUnitId: props.operation.toOuId,
2673
+ organizationalUnitName: props.operation.toOuName
2674
+ });
2662
2675
  await props.organizationsClient.send(
2663
2676
  new MoveAccountCommand2({
2664
2677
  AccountId: props.operation.accountId,
2665
2678
  SourceParentId: props.operation.fromOuId,
2666
- DestinationParentId: props.operation.toOuId
2679
+ DestinationParentId: toOuId
2667
2680
  })
2668
2681
  );
2669
2682
  props.logger.log(`Done: "${props.operation.accountName}"`);
2670
2683
  return moveAccountInWorkingState({
2671
2684
  workingState: props.state,
2672
2685
  accountId: props.operation.accountId,
2673
- parentId: props.operation.toOuId
2686
+ parentId: toOuId
2674
2687
  });
2675
2688
  }
2676
2689
  if (props.operation.kind === "createOu") {
2677
2690
  props.logger.log(
2678
2691
  `Creating OU "${props.operation.ouName}" under ${props.operation.parentOuName}...`
2679
2692
  );
2693
+ const parentOuId = resolveOrganizationalUnitId({
2694
+ state: props.state,
2695
+ organizationalUnitId: props.operation.parentOuId,
2696
+ organizationalUnitName: props.operation.parentOuName
2697
+ });
2680
2698
  const response = await props.organizationsClient.send(
2681
2699
  new CreateOrganizationalUnitCommand({
2682
- ParentId: props.operation.parentOuId,
2700
+ ParentId: parentOuId,
2683
2701
  Name: props.operation.ouName
2684
2702
  })
2685
2703
  );
@@ -2694,7 +2712,7 @@ async function executeOperation(props) {
2694
2712
  workingState: props.state,
2695
2713
  organizationalUnit: {
2696
2714
  id: createdOu.Id,
2697
- parentId: props.operation.parentOuId,
2715
+ parentId: parentOuId,
2698
2716
  arn: createdOu.Arn,
2699
2717
  name: createdOu.Name
2700
2718
  }
@@ -2736,13 +2754,18 @@ async function executeOperation(props) {
2736
2754
  });
2737
2755
  }
2738
2756
  if (props.operation.kind === "createAccount") {
2757
+ const targetOuId = resolveOrganizationalUnitId({
2758
+ state: props.state,
2759
+ organizationalUnitId: props.operation.targetOuId,
2760
+ organizationalUnitName: props.operation.targetOuName
2761
+ });
2739
2762
  const result = await createAccountAndMoveToOu({
2740
2763
  organizationsClient: props.organizationsClient,
2741
2764
  logger: props.logger,
2742
2765
  accountName: props.operation.accountName,
2743
2766
  accountEmail: props.operation.accountEmail,
2744
2767
  sourceParentId: props.context.organization.rootId,
2745
- destinationParentId: props.operation.targetOuId,
2768
+ destinationParentId: targetOuId,
2746
2769
  timeoutInMs: props.runtime.createAccount.timeoutInMs,
2747
2770
  pollIntervalInMs: props.runtime.createAccount.pollIntervalInMs
2748
2771
  });
@@ -2754,7 +2777,7 @@ async function executeOperation(props) {
2754
2777
  name: result.account.name,
2755
2778
  email: result.account.email,
2756
2779
  status: result.account.status,
2757
- parentId: props.operation.targetOuId,
2780
+ parentId: targetOuId,
2758
2781
  tags: []
2759
2782
  }
2760
2783
  });
@@ -3882,6 +3905,20 @@ function resolveGroupByDisplayName(props) {
3882
3905
  }
3883
3906
  return group;
3884
3907
  }
3908
+ function resolveOrganizationalUnitId(props) {
3909
+ if (props.organizationalUnitId !== "__pending_creation__") {
3910
+ return props.organizationalUnitId;
3911
+ }
3912
+ const ou = Object.values(
3913
+ props.state.organization.organizationalUnitsById
3914
+ ).find((ou2) => ou2.name === props.organizationalUnitName);
3915
+ if (ou == null) {
3916
+ throw new Error(
3917
+ `Could not resolve OU "${props.organizationalUnitName}" in working state.`
3918
+ );
3919
+ }
3920
+ return ou.id;
3921
+ }
3885
3922
  function resolvePolicyId(props) {
3886
3923
  if (props.policyId !== "__pending_creation__") return props.policyId;
3887
3924
  const policy = props.state.organization.policiesByName[props.policyName];
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beesolve/aws-accounts",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "AWS Organizations and IAM Identity Center management CLI",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {