constructor()

in src/core/cdk/src/initial-setup.ts [85:1158]


    constructor(scope: cdk.Construct, id: string, props: PipelineProps) {
      super(scope, id);

      const { enablePrebuiltProject } = props;
      const bootStrapStackName = `${props.acceleratorPrefix}CDKToolkit`;

      const lambdaPath = require.resolve('@aws-accelerator/accelerator-runtime');
      const lambdaDir = path.dirname(lambdaPath);
      const lambdaCode = lambda.Code.fromAsset(lambdaDir);

      const stack = cdk.Stack.of(this);
      const installerCmk = kms.Alias.fromAliasName(this, 'InstallerCmk', props.installerCmk);
      const parametersTable = new dynamodb.Table(this, 'ParametersTable', {
        tableName: createName({
          name: 'Parameters',
          suffixLength: 0,
        }),
        partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      const outputsTable = new dynamodb.Table(this, 'Outputs', {
        tableName: createName({
          name: 'Outputs',
          suffixLength: 0,
        }),
        partitionKey: {
          name: 'id',
          type: dynamodb.AttributeType.STRING,
        },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      const outputUtilsTable = new dynamodb.Table(this, 'OutputUtils', {
        tableName: createName({
          name: 'Output-Utils',
          suffixLength: 0,
        }),
        partitionKey: {
          name: 'id',
          type: dynamodb.AttributeType.STRING,
        },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      // Tables required for VPC Cidr mappings for VPC, Account and OU
      const vpcCidrPoolTable = new dynamodb.Table(this, 'CidrVpcAssign', {
        tableName: createName({
          name: VPC_CIDR_POOL_TABLE,
          suffixLength: 0,
        }),
        partitionKey: {
          name: 'id',
          type: dynamodb.AttributeType.STRING,
        },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      const subnetCidrPoolTable = new dynamodb.Table(this, 'CidrSubnetAssign', {
        tableName: createName({
          name: SUBNET_CIDR_POOL_TABLE,
          suffixLength: 0,
        }),
        partitionKey: {
          name: 'id',
          type: dynamodb.AttributeType.STRING,
        },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      const cidrPoolTable = new dynamodb.Table(this, 'CidrPoolTable', {
        tableName: createName({
          name: CIDR_POOL_TABLE,
          suffixLength: 0,
        }),
        partitionKey: {
          name: 'id',
          type: dynamodb.AttributeType.STRING,
        },
        encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
        encryptionKey: installerCmk,
        pointInTimeRecovery: true,
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      });

      // This is the maximum time before a build times out
      // The role used by the build should allow this session duration
      const buildTimeout = cdk.Duration.hours(4);

      // The pipeline stage `InstallRoles` will allow the pipeline role to assume a role in the sub accounts
      const pipelineRole = new iam.Role(this, 'Role', {
        roleName: createRoleName('L-SFN-MasterRole'),
        assumedBy: new iam.CompositePrincipal(
          // TODO Only add root role for development environments
          new iam.ServicePrincipal('codebuild.amazonaws.com'),
          new iam.ServicePrincipal('lambda.amazonaws.com'),
          new iam.ServicePrincipal('events.amazonaws.com'),
        ),
        managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')],
        maxSessionDuration: buildTimeout,
      });

      // S3 working bucket
      const s3WorkingBucket = new s3.Bucket(this, 'WorkingBucket', {
        blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
        encryption: s3.BucketEncryption.S3_MANAGED,
        removalPolicy: cdk.RemovalPolicy.RETAIN,
        lifecycleRules: [
          {
            id: '7DaysDelete',
            enabled: true,
            expiration: cdk.Duration.days(7),
          },
        ],
      });
      s3WorkingBucket.addToResourcePolicy(
        new iam.PolicyStatement({
          actions: ['s3:GetObject*', 's3:PutObject*', 's3:DeleteObject*', 's3:GetBucket*', 's3:List*'],
          resources: [s3WorkingBucket.arnForObjects('*'), s3WorkingBucket.bucketArn],
          principals: [pipelineRole],
        }),
      );
      // Allow only https requests
      s3WorkingBucket.addToResourcePolicy(
        new iam.PolicyStatement({
          actions: ['s3:*'],
          resources: [s3WorkingBucket.bucketArn, s3WorkingBucket.arnForObjects('*')],
          principals: [new iam.AnyPrincipal()],
          conditions: {
            Bool: {
              'aws:SecureTransport': 'false',
            },
          },
          effect: iam.Effect.DENY,
        }),
      );
      //

      // Add a suffix to the CodeBuild project so it creates a new project as it's not able to update the `baseImage`
      const projectNameSuffix = enablePrebuiltProject ? 'Prebuilt' : '';
      const projectConstructor = enablePrebuiltProject ? PrebuiltCdkDeployProject : CdkDeployProject;
      const project = new projectConstructor(this, `CdkDeploy${projectNameSuffix}`, {
        projectName: createName({
          name: `Deploy${projectNameSuffix}`,
          region: false,
          account: false,
        }),
        role: pipelineRole,
        projectRoot: props.solutionRoot,
        packageManager: 'pnpm',
        commands: ['cd src/deployments/cdk', 'sh codebuild-deploy.sh'],
        timeout: buildTimeout,
        computeType: props.codebuildComputeType,
        environment: {
          ACCELERATOR_NAME: props.acceleratorName,
          ACCELERATOR_PREFIX: props.acceleratorPrefix,
          ACCELERATOR_EXECUTION_ROLE_NAME: props.stateMachineExecutionRole,
          CDK_PLUGIN_ASSUME_ROLE_NAME: props.stateMachineExecutionRole,
          CDK_PLUGIN_ASSUME_ROLE_DURATION: `${buildTimeout.toSeconds()}`,
          ACCOUNTS_ITEM_ID: 'accounts',
          LIMITS_ITEM_ID: 'limits',
          ORGANIZATIONS_ITEM_ID: 'organizations',
          DYNAMODB_PARAMETERS_TABLE_NAME: parametersTable.tableName,
          VPC_CIDR_ASSIGNED_POOL: vpcCidrPoolTable.tableName,
          SUBNET_CIDR_ASSIGNED_POOL: subnetCidrPoolTable.tableName,
          CIDR_POOL: cidrPoolTable.tableName,
          DEPLOY_STACK_PAGE_SIZE: props.pageSize,
          COMPUTE_TYPE: props.codebuildComputeType,
        },
      });

      const getOrCreateConfigurationTask = new CodeTask(this, 'Get or Create Configuration from S3', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.getOrCreateConfig',
          role: pipelineRole,
        },
        functionPayload: {
          repositoryName: props.configRepositoryName,
          s3Bucket: props.configS3Bucket,
          branchName: props.configBranchName,
          acceleratorVersion: props.acceleratorVersion!,
          'smInput.$': '$',
          acceleratorPrefix: props.acceleratorPrefix,
          acceleratorName: props.acceleratorName,
          region: cdk.Aws.REGION,
          'executionArn.$': '$$.Execution.Id',
          'stateMachineArn.$': '$$.StateMachine.Id',
        },
        resultPath: '$.configuration',
      });

      const compareConfigurationsTask = new CodeTask(this, 'Compare Configurations', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.compareConfigurationsStep',
          role: pipelineRole,
        },
        functionPayload: {
          'inputConfig.$': '$.configuration.smInput',
          region: cdk.Aws.REGION,
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          'acceleratorVersion.$': '$.configuration.acceleratorVersion',
          'baseline.$': '$.configuration.baselineOutput.baseline',
          parametersTableName: parametersTable.tableName,
          vpcCidrPoolAssignedTable: vpcCidrPoolTable.tableName,
          subnetCidrPoolAssignedTable: subnetCidrPoolTable.tableName,
          outputTableName: outputsTable.tableName,
        },
        resultPath: 'DISCARD',
      });

      const getBaseLineTask = new CodeTask(this, 'Get Baseline From Configuration', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.getBaseline',
          role: pipelineRole,
        },
        functionPayload: {
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          'acceleratorVersion.$': '$.configuration.acceleratorVersion',
          outputTableName: outputsTable.tableName,
          'storeAllOutputs.$': '$.configuration.storeAllOutputs',
          vpcCidrPoolAssignedTable: vpcCidrPoolTable.tableName,
          subnetCidrPoolAssignedTable: subnetCidrPoolTable.tableName,
          cidrPoolsTable: cidrPoolTable.tableName,
        },
        resultPath: '$.configuration.baselineOutput',
      });

      const loadOrgConfigurationTask = new CodeTask(this, 'Load Organization Configuration', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.loadOrganizationConfigurationStep',
          role: pipelineRole,
        },
        functionPayload: {
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          'baseline.$': '$.configuration.baselineOutput.baseline',
          'storeAllOutputs.$': '$.configuration.baselineOutput.storeAllOutputs',
          'phases.$': '$.configuration.baselineOutput.phases',
          'acceleratorVersion.$': '$.configuration.acceleratorVersion',
          'configRootFilePath.$': '$.configuration.configRootFilePath',
          'organizationAdminRole.$': '$.configuration.baselineOutput.organizationAdminRole',
          'smInput.$': '$.configuration.smInput',
          acceleratorPrefix: props.acceleratorPrefix,
          parametersTableName: parametersTable.tableName,
        },
        resultPath: '$.configuration',
      });

      // TODO We might want to load this from the Control Tower api
      const avmProductName = 'AWS Control Tower Account Factory';
      const avmPortfolioName = 'AWS Control Tower Account Factory Portfolio';

      const addRoleToServiceCatalog = new CodeTask(this, 'Add Execution Role to Service Catalog', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.addRoleToServiceCatalogStep',
          role: pipelineRole,
        },
        functionPayload: {
          roleArn: pipelineRole.roleArn,
          portfolioName: avmPortfolioName,
        },
        inputPath: '$.configuration',
        resultPath: 'DISCARD',
      });

      const createControlTowerAccountStateMachine = new sfn.StateMachine(
        scope,
        `${props.acceleratorPrefix}CTCreateAccount_sm`,
        {
          stateMachineName: `${props.acceleratorPrefix}CTCreateAccount_sm`,
          definition: new CreateControlTowerAccountTask(scope, 'Create CT Account', {
            lambdaCode,
            role: pipelineRole,
          }),
        },
      );

      const createControlTowerAccountTask = new tasks.StepFunctionsStartExecution(this, 'Create Account', {
        stateMachine: createControlTowerAccountStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          avmProductName,
          avmPortfolioName,
          'account.$': '$',
        }),
      });

      const createControlTowerAccountsTask = new sfn.Map(this, 'Create Accounts', {
        itemsPath: '$.configuration.accounts',
        resultPath: 'DISCARD',
        maxConcurrency: 1,
      });

      createControlTowerAccountsTask.iterator(createControlTowerAccountTask);

      const createOrganizationAccountStateMachine = new sfn.StateMachine(
        scope,
        `${props.acceleratorPrefix}OrgCreateAccount_sm`,
        {
          stateMachineName: `${props.acceleratorPrefix}OrgCreateAccount_sm`,
          definition: new CreateOrganizationAccountTask(scope, 'Create Org Account', {
            lambdaCode,
            role: pipelineRole,
          }),
        },
      );

      const createOrganizationAccountsTask = new sfn.Map(this, 'Create Organization Accounts', {
        itemsPath: '$.configuration.accounts',
        resultPath: 'DISCARD',
        maxConcurrency: 1,
        parameters: {
          'account.$': '$$.Map.Item.Value',
          'organizationalUnits.$': '$.configuration.organizationalUnits',
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          acceleratorPrefix: props.acceleratorPrefix,
          acceleratorName: props.acceleratorName,
          region: cdk.Aws.REGION,
          'organizationAdminRole.$': '$.configuration.organizationAdminRole',
        },
      });

      const createOrganizationAccountTask = new tasks.StepFunctionsStartExecution(this, 'Create Organization Account', {
        stateMachine: createOrganizationAccountStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'createAccountConfiguration.$': '$',
        }),
      });

      createOrganizationAccountsTask.iterator(createOrganizationAccountTask);

      const loadOrganizationsTask = new CodeTask(this, 'Load Organizational Units', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.loadOrganizations',
          role: pipelineRole,
        },
        functionPayload: {
          parametersTableName: parametersTable.tableName,
          itemId: 'organizations',
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
        },
        resultPath: 'DISCARD',
      });

      const loadAccountsTask = new CodeTask(this, 'Load Accounts', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.loadAccountsStep',
          role: pipelineRole,
        },
        functionPayload: {
          parametersTableName: parametersTable.tableName,
          itemId: 'accounts',
          accountsItemsCountId: 'accounts-items-count',
          // Sending required Inputs seperately to omit unnecesary inputs from SM Input
          'configRepositoryName.$': '$.configuration.configRepositoryName',
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          'acceleratorVersion.$': '$.configuration.acceleratorVersion',
          'baseline.$': '$.configuration.baseline',
          'phases.$': '$.configuration.phases',
          'storeAllOutputs.$': '$.configuration.storeAllOutputs',
          'regions.$': '$.configuration.regions',
          'accounts.$': '$.configuration.accounts',
          'configRootFilePath.$': '$.configuration.configRootFilePath',
          'organizationAdminRole.$': '$.configuration.organizationAdminRole',
          'smInput.$': '$.configuration.smInput',
        },
        resultPath: '$',
      });

      const bootstrapOperationsTemplate = new s3assets.Asset(this, 'CloudFormationOperationsBootstrapTemplate', {
        path: path.join(__dirname, 'assets', 'operations-cdk-bucket.yml'),
      });

      const bootstrapAccountTemplate = new s3assets.Asset(this, 'CloudFormationBootstrapTemplate', {
        path: path.join(__dirname, 'assets', 'account-cdk-bootstrap.yml'),
      });

      const cdkBootstrapStateMachine = new sfn.StateMachine(this, `${props.acceleratorPrefix}CDKBootstrap_sm`, {
        stateMachineName: `${props.acceleratorPrefix}CDKBootstrap_sm`,
        definition: new CDKBootstrapTask(this, 'CDKBootstrap', {
          lambdaCode,
          role: pipelineRole,
          acceleratorPrefix: props.acceleratorPrefix,
          operationsBootstrapObjectKey: bootstrapOperationsTemplate.s3ObjectKey,
          s3BucketName: bootstrapOperationsTemplate.s3BucketName,
          assumeRoleName: props.stateMachineExecutionRole,
          accountBootstrapObjectKey: bootstrapAccountTemplate.s3ObjectKey,
          bootStrapStackName,
        }),
      });

      const cdkBootstrapTask = new tasks.StepFunctionsStartExecution(this, 'Bootstrap Environment', {
        stateMachine: cdkBootstrapStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'accounts.$': '$.accounts',
          'regions.$': '$.regions',
          accountsTableName: parametersTable.tableName,
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
        }),
        resultPath: 'DISCARD',
      });

      const installCfnRoleMasterTemplate = new s3assets.Asset(this, 'CloudFormationExecutionRoleTemplate', {
        path: path.join(__dirname, 'assets', 'cfn-execution-role-master.template.json'),
      });
      installCfnRoleMasterTemplate.bucket.grantRead(pipelineRole);

      const installCfnRoleMasterStateMachine = new sfn.StateMachine(
        this,
        `${props.acceleratorPrefix}InstallCloudFormationExecutionRoleMaster_sm`,
        {
          stateMachineName: `${props.acceleratorPrefix}InstallCfnRoleMaster_sm`,
          definition: new CreateStackTask(this, 'Install CloudFormation Execution Role', {
            lambdaCode,
            role: pipelineRole,
          }),
        },
      );

      const installCfnRoleMasterTask = new tasks.StepFunctionsStartExecution(
        this,
        'Install CloudFormation Role in Master',
        {
          stateMachine: installCfnRoleMasterStateMachine,
          integrationPattern: sfn.IntegrationPattern.RUN_JOB,
          input: sfn.TaskInput.fromObject({
            stackName: `${props.acceleratorPrefix}CloudFormationStackSetExecutionRole`,
            stackCapabilities: ['CAPABILITY_NAMED_IAM'],
            stackTemplate: {
              s3BucketName: installCfnRoleMasterTemplate.s3BucketName,
              s3ObjectKey: installCfnRoleMasterTemplate.s3ObjectKey,
            },
            stackParameters: {
              'RoleName.$': '$.configuration.organizationAdminRole',
            },
          }),
          resultPath: 'DISCARD',
        },
      );

      const accountsPath = path.join(__dirname, 'assets', 'execution-role.template.json');
      const executionRoleContent = fs.readFileSync(accountsPath);

      const installRolesStateMachine = new sfn.StateMachine(this, `${props.acceleratorPrefix}InstallRoles_sm`, {
        stateMachineName: `${props.acceleratorPrefix}InstallRoles_sm`,
        definition: new CreateStackTask(this, 'Install', {
          lambdaCode,
          role: pipelineRole,
          suffix: 'ExecutionRole',
        }),
      });

      const installRolesTask = new tasks.StepFunctionsStartExecution(this, 'Install Execution Roles', {
        stateMachine: installRolesStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          stackName: `${props.acceleratorPrefix}PipelineRole`,
          stackCapabilities: ['CAPABILITY_NAMED_IAM'],
          stackParameters: {
            RoleName: props.stateMachineExecutionRole,
            MaxSessionDuration: `${buildTimeout.toSeconds()}`,
            // TODO Only add root role for development environments
            AssumedByRoleArn: `arn:aws:iam::${stack.account}:root,${pipelineRole.roleArn}`,
            AcceleratorPrefix: props.acceleratorPrefix.endsWith('-')
              ? props.acceleratorPrefix.slice(0, -1).toLowerCase()
              : props.acceleratorPrefix.toLowerCase(),
          },
          stackTemplate: executionRoleContent.toString(),
          'accountId.$': '$.accountId',
          'assumeRoleName.$': '$.organizationAdminRole',
        }),
        resultPath: 'DISCARD',
      });

      const installExecRolesInAccounts = new sfn.Map(this, `Install Execution Roles Map`, {
        itemsPath: '$.accounts',
        resultPath: 'DISCARD',
        maxConcurrency: 40,
        parameters: {
          'accountId.$': '$$.Map.Item.Value',
          'organizationAdminRole.$': '$.organizationAdminRole',
        },
      });

      installExecRolesInAccounts.iterator(installRolesTask);

      const deleteVpcSfn = new sfn.StateMachine(this, 'Delete Default Vpcs Sfn', {
        stateMachineName: `${props.acceleratorPrefix}DeleteDefaultVpcs_sfn`,
        definition: new RunAcrossAccountsTask(this, 'DeleteDefaultVPCs', {
          lambdaCode,
          role: pipelineRole,
          lambdaPath: 'index.deleteDefaultVpcs',
          name: 'Delete Default VPC',
        }),
      });

      const deleteVpcTask = new tasks.StepFunctionsStartExecution(this, 'Delete Default Vpcs', {
        stateMachine: deleteVpcSfn,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'accounts.$': '$.accounts',
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          'baseline.$': '$.baseline',
          acceleratorPrefix: props.acceleratorPrefix,
          assumeRoleName: props.stateMachineExecutionRole,
        }),
        resultPath: 'DISCARD',
      });

      const loadLimitsTask = new CodeTask(this, 'Load Limits', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.loadLimitsStep',
          role: pipelineRole,
        },
        functionPayload: {
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          parametersTableName: parametersTable.tableName,
          itemId: 'limits',
          assumeRoleName: props.stateMachineExecutionRole,
        },
        resultPath: '$.limits',
      });

      const validateOuConfiguration = new CodeTask(this, 'OU Validation', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.ouValidation',
          role: pipelineRole,
        },
        functionPayload: {
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configuration.configFilePath',
          'configCommitId.$': '$.configuration.configCommitId',
          acceleratorPrefix: props.acceleratorPrefix,
          parametersTableName: parametersTable.tableName,
          organizationsItemId: 'organizations',
          accountsItemId: 'accounts',
          configBranch: props.configBranchName,
          'configRootFilePath.$': '$.configuration.configRootFilePath',
          'baseline.$': '$.configuration.baselineOutput.baseline',
        },
        resultPath: '$.configuration.configCommitId',
      });

      const addScpTask = new CodeTask(this, 'Add SCPs to Organization', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.addScpStep',
          role: pipelineRole,
        },
        functionPayload: {
          acceleratorPrefix: props.acceleratorPrefix,
          acceleratorName: props.acceleratorName,
          region: cdk.Aws.REGION,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          parametersTableName: parametersTable.tableName,
          outputTableName: outputsTable.tableName,
          'organizationAdminRole.$': '$.organizationAdminRole',
          'baseline.$': '$.baseline',
        },
        resultPath: 'DISCARD',
      });

      const storeOutputsToSsmStateMachine = new sfn.StateMachine(
        this,
        `${props.acceleratorPrefix}StoreOutputsToSsm_sm`,
        {
          stateMachineName: `${props.acceleratorPrefix}StoreOutputsToSsm_sm`,
          definition: new StoreOutputsToSSMTask(this, 'StoreOutputsToSSM', {
            acceleratorPrefix: props.acceleratorPrefix,
            lambdaCode,
            role: pipelineRole,
          }),
        },
      );

      const storeAllOutputsToSsmTask = new tasks.StepFunctionsStartExecution(this, 'Store Outputs to SSM', {
        stateMachine: storeOutputsToSsmStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'accounts.$': '$.accounts',
          'regions.$': '$.regions',
          acceleratorPrefix: props.acceleratorPrefix,
          assumeRoleName: props.stateMachineExecutionRole,
          outputsTableName: outputsTable.tableName,
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          outputUtilsTableName: outputUtilsTable.tableName,
          accountsTableName: parametersTable.tableName,
          s3WorkingBucket: s3WorkingBucket.bucketName,
        }),
        resultPath: 'DISCARD',
      });

      const detachQuarantineScpTask = new CodeTask(this, 'Detach Quarantine SCP', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.detachQuarantineScp',
          role: pipelineRole,
        },
        functionPayload: {
          acceleratorPrefix: props.acceleratorPrefix,
          parametersTableName: parametersTable.tableName,
        },
        resultPath: 'DISCARD',
      });
      // detachQuarantineScpTask.next(storeAllOutputsToSsmTask);

      const enableTrustedAccessForServicesTask = new CodeTask(this, 'Enable Trusted Access For Services', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.enableTrustedAccessForServicesStep',
          role: pipelineRole,
        },
        functionPayload: {
          parametersTableName: parametersTable.tableName,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
        },
        resultPath: '$.installerVersion',
      });

      const createDeploymentTask = (phase: number, loadOutputs: boolean = true) => {
        const environment = {
          ACCELERATOR_PHASE: { type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, value: `${phase}` },
          CONFIG_REPOSITORY_NAME: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.configRepositoryName'),
          },
          CONFIG_FILE_PATH: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.configFilePath'),
          },
          CONFIG_COMMIT_ID: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.configCommitId'),
          },
          ACCELERATOR_BASELINE: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.baseline'),
          },
          CONFIG_ROOT_FILE_PATH: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.configRootFilePath'),
          },
          INSTALLER_VERSION: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.installerVersion'),
          },
          ACCELERATOR_PIPELINE_ROLE_NAME: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: pipelineRole.roleName,
          },
          ACCELERATOR_STATE_MACHINE_NAME: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: props.stateMachineName,
          },
          CONFIG_BRANCH_NAME: { type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, value: props.configBranchName },
          STACK_OUTPUT_TABLE_NAME: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: outputsTable.tableName,
          },
          BOOTSTRAP_STACK_NAME: { type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, value: bootStrapStackName },
          SCOPE: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.scope'),
          },
          MODE: { type: codebuild.BuildEnvironmentVariableType.PLAINTEXT, value: sfn.JsonPath.stringAt('$.mode') },
          CDK_DEBUG: {
            type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
            value: sfn.JsonPath.stringAt('$.verbose'),
          },
        };

        const deployTask = new tasks.CodeBuildStartBuild(this, `Deploy Phase ${phase}`, {
          project: project.resource,
          integrationPattern: sfn.IntegrationPattern.RUN_JOB,
          environmentVariablesOverride: environment,
          resultPath: 'DISCARD',
        });

        return deployTask;
      };

      const storeOutputsStateMachine = new sfn.StateMachine(this, `${props.acceleratorPrefix}StoreOutputs_sm`, {
        stateMachineName: `${props.acceleratorPrefix}StoreOutputs_sm`,
        definition: new StoreOutputsTask(this, 'StoreOutputs', {
          acceleratorPrefix: props.acceleratorPrefix,
          lambdaCode,
          role: pipelineRole,
        }),
      });

      const createStoreOutputTask = (phase: number) => {
        const storeOutputsTask = new tasks.StepFunctionsStartExecution(this, `Store Phase ${phase} Outputs`, {
          stateMachine: storeOutputsStateMachine,
          integrationPattern: sfn.IntegrationPattern.RUN_JOB,
          input: sfn.TaskInput.fromObject({
            'accounts.$': '$.accounts',
            'regions.$': '$.regions',
            acceleratorPrefix: props.acceleratorPrefix,
            assumeRoleName: props.stateMachineExecutionRole,
            outputsTable: outputsTable.tableName,
            phaseNumber: phase,
            configRepositoryName: props.configRepositoryName,
            'configFilePath.$': '$.configFilePath',
            'configCommitId.$': '$.configCommitId',
          }),
          resultPath: 'DISCARD',
        });
        return storeOutputsTask;
      };

      const storeAllPhaseOutputs = new sfn.Map(this, `Store All Phase Outputs Map`, {
        itemsPath: '$.phases',
        resultPath: 'DISCARD',
        maxConcurrency: 1,
        parameters: {
          'accounts.$': '$.accounts',
          'regions.$': '$.regions',
          acceleratorPrefix: props.acceleratorPrefix,
          assumeRoleName: props.stateMachineExecutionRole,
          outputsTable: outputsTable.tableName,
          configRepositoryName: props.configRepositoryName,
          'phaseNumber.$': '$$.Map.Item.Value',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
        },
      });

      const storeAllOutputsTask = new tasks.StepFunctionsStartExecution(this, `Store All Phase Outputs`, {
        stateMachine: storeOutputsStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'accounts.$': '$.accounts',
          'regions.$': '$.regions',
          acceleratorPrefix: props.acceleratorPrefix,
          assumeRoleName: props.stateMachineExecutionRole,
          outputsTable: outputsTable.tableName,
          configRepositoryName: props.configRepositoryName,
          'phaseNumber.$': '$.phaseNumber',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
        }),
        resultPath: 'DISCARD',
      });
      storeAllPhaseOutputs.iterator(storeAllOutputsTask);

      // TODO Create separate state machine for deployment
      const deployPhaseRolesTask = createDeploymentTask(-1, false);
      const storePreviousOutput = createStoreOutputTask(-1);
      const deployPhase0Task = createDeploymentTask(0);
      const storePhase0Output = createStoreOutputTask(0);
      const deployPhase1Task = createDeploymentTask(1);
      const storePhase1Output = createStoreOutputTask(1);
      const deployPhase2Task = createDeploymentTask(2);
      const storePhase2Output = createStoreOutputTask(2);
      const deployPhase3Task = createDeploymentTask(3);
      const storePhase3Output = createStoreOutputTask(3);
      const deployPhase4Task = createDeploymentTask(4);
      const storePhase4Output = createStoreOutputTask(4);
      const deployPhase5Task = createDeploymentTask(5);
      const storePhase5Output = createStoreOutputTask(5);

      const createConfigRecorderSfn = new sfn.StateMachine(this, 'Create Config Recorder Sfn', {
        stateMachineName: `${props.acceleratorPrefix}CreateConfigRecorder_sfn`,
        definition: new RunAcrossAccountsTask(this, 'CreateConfigRecorder', {
          lambdaCode,
          role: pipelineRole,
          lambdaPath: 'index.createConfigRecorder',
          name: 'Create Config Recorder',
          functionPayload: {
            outputTableName: outputsTable.tableName,
          },
        }),
      });

      const createConfigRecordersTask = new tasks.StepFunctionsStartExecution(this, 'Create Config Recorders', {
        stateMachine: createConfigRecorderSfn,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          'accounts.$': '$.accounts',
          configRepositoryName: props.configRepositoryName,
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          'baseline.$': '$.baseline',
          outputTableName: outputsTable.tableName,
          acceleratorPrefix: props.acceleratorPrefix,
          assumeRoleName: props.stateMachineExecutionRole,
        }),
        resultPath: 'DISCARD',
      });

      // TODO We could put this task in a map task and apply to all accounts individually
      const accountDefaultSettingsTask = new CodeTask(this, 'Account Default Settings', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.accountDefaultSettingsStep',
          role: pipelineRole,
        },
        functionPayload: {
          assumeRoleName: props.stateMachineExecutionRole,
          parametersTableName: parametersTable.tableName,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          'baseline.$': '$.baseline',
          outputTableName: outputsTable.tableName,
        },
        resultPath: 'DISCARD',
      });

      const rdgwArtifactsFolderPath = path.join(__dirname, '..', '..', '..', '..', 'reference-artifacts', 'scripts');
      const rdgwScripts = fs.readdirSync(rdgwArtifactsFolderPath);

      const verifyFilesTask = new CodeTask(this, 'Verify Files', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.verifyFilesStep',
          role: pipelineRole,
        },
        functionPayload: {
          assumeRoleName: props.stateMachineExecutionRole,
          parametersTableName: parametersTable.tableName,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          outputTableName: outputsTable.tableName,
          rdgwScripts,
        },
        resultPath: 'DISCARD',
      });

      const addTagsToSharedResourcesTask = new CodeTask(this, 'Add Tags to Shared Resources', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.addTagsToSharedResourcesStep',
          role: pipelineRole,
        },
        functionPayload: {
          assumeRoleName: props.stateMachineExecutionRole,
          outputTableName: outputsTable.tableName,
        },
        resultPath: 'DISCARD',
      });

      const enableDirectorySharingTask = new CodeTask(this, 'Enable Directory Sharing', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.enableDirectorySharingStep',
          role: pipelineRole,
        },
        functionPayload: {
          parametersTableName: parametersTable.tableName,
          assumeRoleName: props.stateMachineExecutionRole,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          outputTableName: outputsTable.tableName,
        },
        resultPath: 'DISCARD',
      });

      const createAdConnectorStateMachine = new sfn.StateMachine(scope, 'CreateAdConnectorStateMachine', {
        stateMachineName: `${props.acceleratorPrefix}CreateAdConnector_sm`,
        definition: new CreateAdConnectorTask(scope, 'CreateAD', {
          lambdaCode,
          role: pipelineRole,
        }),
      });

      const createAdConnectorTask = new tasks.StepFunctionsStartExecution(this, 'Create AD Connector', {
        stateMachine: createAdConnectorStateMachine,
        integrationPattern: sfn.IntegrationPattern.RUN_JOB,
        input: sfn.TaskInput.fromObject({
          acceleratorPrefix: props.acceleratorPrefix,
          parametersTableName: parametersTable.tableName,
          assumeRoleName: props.stateMachineExecutionRole,
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          outputTableName: outputsTable.tableName,
        }),
        resultPath: 'DISCARD',
      });

      const storeCommitIdTask = new CodeTask(this, 'Store CommitId', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.storeCommitIdStep',
          role: pipelineRole,
        },
        functionPayload: {
          'configRepositoryName.$': '$.configRepositoryName',
          'configFilePath.$': '$.configFilePath',
          'configCommitId.$': '$.configCommitId',
          acceleratorVersion: props.acceleratorVersion,
          vpcCidrPoolAssignedTable: vpcCidrPoolTable.tableName,
          subnetCidrPoolAssignedTable: subnetCidrPoolTable.tableName,
          outputTableName: outputsTable.tableName,
          parametersTableName: parametersTable.tableName,
        },
        resultPath: 'DISCARD',
      });

      const commonStep1 = addScpTask.startState
        .next(deployPhase1Task)
        .next(storePhase1Output)
        .next(accountDefaultSettingsTask)
        .next(deployPhase2Task)
        .next(storePhase2Output)
        .next(deployPhase3Task)
        .next(storePhase3Output)
        .next(deployPhase4Task)
        .next(storePhase4Output)
        .next(addTagsToSharedResourcesTask)
        .next(enableDirectorySharingTask)
        .next(deployPhase5Task)
        .next(storePhase5Output)
        .next(createAdConnectorTask)
        .next(storeCommitIdTask)
        .next(detachQuarantineScpTask)
        .next(storeAllOutputsToSsmTask);

      const commonStep2 = deployPhaseRolesTask
        .next(storePreviousOutput)
        .next(deployPhase0Task)
        .next(storePhase0Output)
        .next(verifyFilesTask)
        .next(createConfigRecordersTask)
        .next(commonStep1);

      const storeAllOutputsChoice = new sfn.Choice(this, 'Store All Phase Outputs?')
        .when(sfn.Condition.booleanEquals('$.storeAllOutputs', true), storeAllPhaseOutputs.next(commonStep2))
        .otherwise(commonStep2)
        .afterwards();

      const commonDefinition = loadOrganizationsTask.startState
        .next(loadAccountsTask)
        .next(installExecRolesInAccounts)
        .next(cdkBootstrapTask)
        .next(deleteVpcTask)
        .next(loadLimitsTask)
        .next(enableTrustedAccessForServicesTask)
        .next(storeAllOutputsChoice);

      const cloudFormationExecRoleManagementChoice = new sfn.Choice(
        this,
        'Install CloudFormation Role in Management Account?',
      )
        .when(
          sfn.Condition.booleanEquals('$.configuration.installCloudFormationMasterRole', true),
          installCfnRoleMasterTask,
        )
        .otherwise(commonDefinition)
        .afterwards();
      installCfnRoleMasterTask.next(commonDefinition);
      // Control Tower Setup
      const ctDefinition = addRoleToServiceCatalog.startState;
      addRoleToServiceCatalog.next(createControlTowerAccountsTask);

      const baseLineChoice = new sfn.Choice(this, 'Baseline?')
        .when(sfn.Condition.stringEquals('$.configuration.baseline', 'CONTROL_TOWER'), ctDefinition)
        .otherwise(createOrganizationAccountsTask)
        .afterwards()
        .next(cloudFormationExecRoleManagementChoice);

      const notificationTopic = new sns.Topic(this, 'MainStateMachineStatusTopic', {
        displayName: `${props.acceleratorPrefix}-MainStateMachine-Status_topic`,
        topicName: `${props.acceleratorPrefix}-MainStateMachine-Status_topic`,
      });

      new sns.Subscription(this, 'MainStateMachineStatusTopicSubscription', {
        topic: notificationTopic,
        protocol: sns.SubscriptionProtocol.EMAIL,
        endpoint: props.notificationEmail,
      });

      const fail = new sfn.Fail(this, 'Failed');

      const notifySmFailure = new CodeTask(this, 'Execution Failed', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.notifySMFailure',
          role: pipelineRole,
        },
        functionPayload: {
          notificationTopicArn: notificationTopic.topicArn,
          'error.$': '$.Error',
          'cause.$': '$.Cause',
          'executionId.$': '$$.Execution.Id',
          acceleratorVersion: props.acceleratorVersion,
        },
        resultPath: 'DISCARD',
      });
      notifySmFailure.next(fail);

      const notifySmSuccess = new CodeTask(this, 'Deploy Success', {
        functionProps: {
          code: lambdaCode,
          handler: 'index.notifySMSuccess',
          role: pipelineRole,
        },
        functionPayload: {
          notificationTopicArn: notificationTopic.topicArn,
          parametersTableName: parametersTable.tableName,
          'acceleratorVersion.$': '$[0].acceleratorVersion',
        },
        resultPath: 'DISCARD',
      });

      // Full StateMachine Execution starts from getOrCreateConfigurationTask and wrapped in parallel task for try/catch
      getOrCreateConfigurationTask
        .next(getBaseLineTask)
        .next(compareConfigurationsTask)
        .next(validateOuConfiguration)
        .next(loadOrgConfigurationTask)
        .next(baseLineChoice);

      const mainTryCatch = new sfn.Parallel(this, 'Main Try Catch block to Notify users');
      mainTryCatch.branch(getOrCreateConfigurationTask);
      mainTryCatch.addCatch(notifySmFailure);
      mainTryCatch.next(notifySmSuccess);

      new sfn.StateMachine(this, 'StateMachine', {
        stateMachineName: props.stateMachineName,
        definition: sfn.Chain.start(mainTryCatch),
      });
    }