Repository: awslabs/aws-bootstrap-kit (language: python)

Issue:
`OUSpec` not allowing for account-free OUs
## Description

I am trying to build a hierarchy that goes 2 level deep with some OUs being merely containers for other OUs (i.e. don't have any associated accounts).

For example, if I customize the [AWS Bootstrap Kit](https://github.com/aws-samples/aws-bootstrap-kit-examples) [`nestedOU`](https://github.com/aws-samples/aws-bootstrap-kit-examples/blob/c0a6ded33e3857a7747fbe469ed23187d1a6fb7e/source/1-SDLC-organization/bin/sdlc-organization.ts#L32) as follows : 

```typescript
const nestedOU = [
    {
        name: 'SharedServices',
        accounts: [
            {
                name: 'CICD',
                type: AccountType.CICD
            }
        ]
    },
    {
        name: 'SDLC',
        accounts: [
            {
                name: 'Dev',
                type: AccountType.PLAYGROUND
            }
        ]
    },
    {
        name: 'Tenants',                            <---------- not supported ???
        nestedOU: [
            {
                name: 'tenantA',
                accounts: [
                    {
                        name: 'Staging',
                        type: AccountType.STAGE,
                        stageName: 'staging',
                        stageOrder: 1,
                        hostedServices: ['ALL'],
                        existingAccountId: '1234567890'
                    },
                    {
                        name: 'Prod',
                        type: AccountType.STAGE,
                        stageName: 'prod',
                        stageOrder: 2,
                        hostedServices: ['ALL']
                    }
                ]
            }
        ]
    }
];
```

Then it seems that the current definition of `OUSpec` will get in the way and not allow me to specify an "account-free" OU (container for other OUs with accounts).

```typescript
/**
 * Organizational Unit Input details
 */
export interface OUSpec {
  /**
   * Name of the Organizational Unit
   */
  readonly name: string,

  /**
   * Accounts' specification inside in this Organizational Unit
   */
  readonly accounts: AccountSpec[],                                           <------- make optional ?

  /**
   * Specification of sub Organizational Unit
   */
  readonly nestedOU?: OUSpec[]
}
```

Is this a current limitation ?


Discussion / hints:
Hi @npvisual , thanks, good catch , indeed we should make accounts optional to support deepper OU tree. Will fix asap.
Was trying to fix it locally just to see what would happen and I am getting the following error during token resolution 🤷‍♂️ : 

```
{...}/aws-bootstrap-kit-examples/source/1-SDLC-organization/node_modules/aws-cdk-lib/core/lib/private/uniqueid.ts:42
    throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`);
          ^
Error: Resolution error: ID components may not include unresolved tokens: ${Token[TOKEN.1393]}SubZoneDelegationNSRecord.
```
thanks for the try. will fix tomorrow

On Mon, Aug 1, 2022, 21:35 Nicolas Philippe ***@***.***>
wrote:

> Was trying to fix it locally just to see what would happen and I am
> getting the following error during token resolution 🤷‍♂️ :
>
> {...}/aws-bootstrap-kit-examples/source/1-SDLC-organization/node_modules/aws-cdk-lib/core/lib/private/uniqueid.ts:42
>
>     throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`);
>
>           ^
>
> Error: Resolution error: ID components may not include unresolved tokens: ${Token[TOKEN.1393]}SubZoneDelegationNSRecord.
>
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/awslabs/aws-bootstrap-kit/issues/117#issuecomment-1201629148>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAJLIUJAJJIWMXHOTM7WLWDVXARJPANCNFSM55BYS23A>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>

After testing some more, it looks like the `unresolved tokens` issue above was not related to the nesting but rather to the use of `existingAccountId` in one of the `AccountSpec`.

> thanks for the try. will fix tomorrow
> […](#)


Running a `cdk diff` on the SDLC example with a nested OU works fine : 

```
1-SDLC-organization % cdk --profile main-admin diff
Stack AWSBootstrapKit-LandingZone-PipelineStack
IAM Statement Changes
....
Outputs
[+] Output PipelineConsoleUrl PipelineConsoleUrl: {"Value":{"Fn::Join":["",["https://",{"Ref":"AWS::Region"},".console.aws.amazon.com/codesuite/codepipeline/pipelines/AWSBootstrapKit-LandingZone/view?region=",{"Ref":"AWS::Region"}]]}}

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}
```

Diff of the changes used : 

```diff
diff --git a/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts b/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
index b71a505..b572682 100644
--- a/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
+++ b/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
@@ -87,7 +87,7 @@ export interface OUSpec {
   /**
    * Accounts' specification inside in this Organizational Unit
    */
-  readonly accounts: AccountSpec[],
+  readonly accounts?: AccountSpec[],

   /**
    * Specification of sub Organizational Unit
@@ -150,7 +150,8 @@ export class AwsOrganizationsStack extends Stack {

     previousSequentialConstruct = organizationalUnit;

-    oUSpec.accounts.forEach(accountSpec => {
+    oUSpec.accounts?.forEach(accountSpec => {
       let accountEmail: string;
       if(accountSpec.email)
       {
```

Fix patch:
diff --git a/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts b/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
index b71a505..e6a9f48 100644
--- a/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
+++ b/source/aws-bootstrap-kit/lib/aws-organizations-stack.ts
@@ -87,7 +87,7 @@ export interface OUSpec {
   /**
    * Accounts' specification inside in this Organizational Unit
    */
-  readonly accounts: AccountSpec[],
+  readonly accounts?: AccountSpec[],
 
   /**
    * Specification of sub Organizational Unit
@@ -150,7 +150,7 @@ export class AwsOrganizationsStack extends Stack {
 
     previousSequentialConstruct = organizationalUnit;
 
-    oUSpec.accounts.forEach(accountSpec => {
+    oUSpec.accounts?.forEach(accountSpec => {
       let accountEmail: string;
       if(accountSpec.email)
       {


Test patch:
diff --git a/source/aws-bootstrap-kit/test/unit/aws-organizations-stack.test.ts b/source/aws-bootstrap-kit/test/unit/aws-organizations-stack.test.ts
index 3481d71..640ceee 100644
--- a/source/aws-bootstrap-kit/test/unit/aws-organizations-stack.test.ts
+++ b/source/aws-bootstrap-kit/test/unit/aws-organizations-stack.test.ts
@@ -55,6 +55,65 @@ const awsOrganizationsStackProps: AwsOrganizationsStackProps = {
   ]
 };
 
+
+
+test("when I define nestedOUs it create the right OUs", () => {
+
+  const stack = new Stack();
+  let awsOrganizationsStackProps: AwsOrganizationsStackProps;
+  awsOrganizationsStackProps = {
+      email: "test@test.com",
+      nestedOU: [
+          {
+              name: 'SDLC',
+              nestedOU:[{
+                  name: 'App1',
+                  accounts: [
+                    {
+                        name: 'Account1',
+                        type: AccountType.PLAYGROUND,
+                        hostedServices: ['app1']
+                    }
+                ]
+              },
+              {
+                name: 'Sandbox',
+                accounts: [
+                  {
+                      name: 'App2',
+                      type: AccountType.PLAYGROUND,
+                      hostedServices: ['app2']
+                  }
+              ]
+            }]
+          },
+          {
+              name: 'Prod',
+              accounts: [
+                  {
+                      name: 'Account3',
+                      type: AccountType.STAGE,
+                      stageOrder: 3,
+                      stageName: 'stage3',
+                      hostedServices: ['app1', 'app2']
+                  }
+              ]
+          }
+      ]
+  };
+
+
+  const awsOrganizationsStack = new AwsOrganizationsStack(stack, "AWSOrganizationsStack", awsOrganizationsStackProps);
+
+  expect(awsOrganizationsStack.templateOptions.description).toMatch(`(version:${version})`);
+
+  expect(awsOrganizationsStack).toHaveResource("Custom::OrganizationCreation");
+
+  expect(awsOrganizationsStack).toCountResources("Custom::AccountCreation", 3);
+  expect(awsOrganizationsStack).toCountResources("Custom::OUCreation", 4);
+});
+
+
 test("when I define 1 OU with 3 accounts (1 existing) and 1 OU with 1 account then the stack should have 2 OU constructs and 3 account constructs", () => {
 
     const stack = new Stack();