constructor()

in packages/pipeline/src/feature-branches.ts [84:392]


  constructor(scope: Construct, id: string, props: FeatureBranchesProps) {
    super(scope, id);

    const buildCommands: string[] =
      props.synthShellStepPartialProps?.commands &&
      props.synthShellStepPartialProps.commands.length > 0
        ? props.synthShellStepPartialProps.commands
        : ["npx nx run-many --target=build --all"];

    const installCommands: string[] =
      props.synthShellStepPartialProps?.installCommands &&
      props.synthShellStepPartialProps.installCommands.length > 0
        ? props.synthShellStepPartialProps.installCommands
        : [
            "npm install -g aws-cdk",
            "yarn install --frozen-lockfile || npx projen && yarn install --frozen-lockfile",
          ];

    const cdkCommand = props.cdkCommand ?? "npx cdk";

    const createFeatureBranchProject = new Project(
      this,
      "CreateFeatureBranchProject",
      {
        ...props.codeBuildDefaults,
        description: "Build project to deploy feature branch pipelines",
        source: Source.codeCommit({ repository: props.codeRepository }),
        environment: {
          buildImage: LinuxBuildImage.STANDARD_7_0,
          computeType: ComputeType.SMALL,
          ...props.codeBuildDefaults?.buildEnvironment,
          privileged: props.dockerEnabledForSynth,
        },
        buildSpec: BuildSpec.fromObjectToYaml({
          version: "0.2",
          phases: {
            install: {
              commands: installCommands,
            },
            build: {
              commands: [
                ...buildCommands,
                `cd ${props.cdkSrcDir}`,
                `${cdkCommand} synth`,
                `${cdkCommand} deploy --require-approval=never`,
              ],
            },
          },
          artifacts: {
            files: ["**/*"],
          },
        }),
      }
    );

    if (props.codeBuildDefaults?.rolePolicy) {
      props.codeBuildDefaults.rolePolicy.forEach((policy) => {
        createFeatureBranchProject.addToRolePolicy(policy);
      });
    }

    createFeatureBranchProject.addToRolePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        actions: ["sts:AssumeRole"],
        resources: [`arn:*:iam::${Stack.of(this).account}:role/*`],
        conditions: {
          "ForAnyValue:StringEquals": {
            "iam:ResourceTag/aws-cdk:bootstrap-role": [
              "image-publishing",
              "file-publishing",
              "deploy",
            ],
          },
        },
      })
    );

    const createFeatureBranchFunction = new Function(
      this,
      "LambdaTriggerCreateBranch",
      {
        runtime: Runtime.PYTHON_3_12,
        code: Code.fromAsset(path.join(__dirname, "lambda/create_branch")),
        handler: "create_branch.handler",
        environment: {
          CODEBUILD_PROJECT: createFeatureBranchProject.projectName,
          MAIN_BRANCH: props.defaultBranchName,
        },
      }
    );

    createFeatureBranchFunction.addToRolePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        actions: ["codebuild:StartBuild"],
        resources: [createFeatureBranchProject.projectArn],
      })
    );

    const destroyFeatureBranchFunction = new Function(
      this,
      "LambdaTriggerDestroyBranch",
      {
        runtime: Runtime.PYTHON_3_12,
        code: Code.fromAsset(path.join(__dirname, "lambda/destroy_branch")),
        handler: "destroy_branch.handler",
        environment: {
          MAIN_BRANCH: props.defaultBranchName,
          REPO_NAME: props.codeRepository.repositoryName,
        },
      }
    );
    destroyFeatureBranchFunction.addToRolePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        actions: ["cloudformation:DeleteStack"],
        resources: [
          Stack.of(this).formatArn({
            service: "cloudformation",
            resource: "stack",
            resourceName: "*/*",
          }),
        ],
        conditions: {
          "ForAllValues:StringEquals": {
            "aws:TagKeys": ["FeatureBranch", "RepoName"],
          },
        },
      })
    );
    destroyFeatureBranchFunction.addToRolePolicy(
      new PolicyStatement({
        effect: Effect.ALLOW,
        actions: ["tag:GetResources"],
        resources: ["*"],
      })
    );

    props.codeRepository.onReferenceCreated("BranchCreateTrigger", {
      target: new LambdaFunction(createFeatureBranchFunction),
      description: "AWS CodeCommit reference created event.",
      eventPattern: {
        detail: {
          referenceType: ["branch"],
          referenceName: props.branchNamePrefixes.map((prefix) => ({ prefix })),
        },
      },
    });

    props.codeRepository.onReferenceDeleted("BranchDestroyTrigger", {
      target: new LambdaFunction(destroyFeatureBranchFunction),
      description: "AWS CodeCommit reference deleted event.",
      eventPattern: {
        detail: {
          referenceType: ["branch"],
          referenceName: props.branchNamePrefixes.map((prefix) => {
            return { prefix };
          }),
        },
      },
    });

    const stack = Stack.of(this);

    ["AwsSolutions-IAM5", "AwsPrototyping-IAMNoWildcardPermissions"].forEach(
      (RuleId) => {
        NagSuppressions.addResourceSuppressions(
          createFeatureBranchProject.role!,
          [
            {
              id: RuleId,
              reason:
                "CodeBuild requires get, list, and pull access to the CodeCommit repository.",
              appliesTo: [
                "Action::codecommit:Get*",
                "Action::codecommit:List*",
              ],
            },
            {
              id: RuleId,
              reason:
                "CodeBuild requires access to create report groups that are dynamically determined.",
              appliesTo: [
                {
                  regex: `/^Resource::arn:${PDKNag.getStackPartitionRegex(
                    stack
                  )}:codebuild:${PDKNag.getStackRegionRegex(
                    stack
                  )}:${PDKNag.getStackAccountRegex(
                    stack
                  )}:report-group/<[a-zA-Z0-9]*CreateFeatureBranchProject.*>-\\*$/g`,
                },
              ],
            },
            {
              id: RuleId,
              reason:
                "CodeBuild requires access to manage logs and streams whose names are dynamically determined.",
              appliesTo: [
                {
                  regex: `/^Resource::arn:${PDKNag.getStackPartitionRegex(
                    stack
                  )}:logs:${PDKNag.getStackRegionRegex(
                    stack
                  )}:${PDKNag.getStackAccountRegex(
                    stack
                  )}:log-group:/aws/codebuild/<[a-zA-Z0-9]*CreateFeatureBranchProject.*>:\\*$/g`,
                },
              ],
            },
            {
              id: RuleId,
              reason:
                "CodeBuild requires access to assume a role from within the current account limited by a condition in order to deploy.",
              appliesTo: [
                {
                  regex: `/^Resource::arn:\\*:iam::${PDKNag.getStackAccountRegex(
                    stack
                  )}:role/\\*$/g`,
                },
              ],
            },
          ],
          true
        );

        NagSuppressions.addResourceSuppressions(
          destroyFeatureBranchFunction.role!,
          [
            {
              id: RuleId,
              reason:
                "The DestroyBranch Lambda requires access to delete any stacks with specific tags.",
              appliesTo: [
                {
                  regex: `/^Resource::arn:${PDKNag.getStackPartitionRegex(
                    stack
                  )}:cloudformation:${PDKNag.getStackRegionRegex(
                    stack
                  )}:${PDKNag.getStackAccountRegex(stack)}:stack/\\*/\\*$/g`,
                },
              ],
            },
            {
              id: RuleId,
              reason:
                "The DestroyBranch Lambda requires access to look up CloudFormation stacks by tag. The Resource Group Tagging API must use 'Resource': '*'.",
              appliesTo: ["Resource::*"],
            },
          ],
          true
        );
      }
    );

    ["AwsSolutions-IAM4", "AwsPrototyping-IAMNoManagedPolicies"].forEach(
      (RuleId) => {
        NagSuppressions.addResourceSuppressions(
          createFeatureBranchFunction,
          [
            {
              id: RuleId,
              reason:
                "Lambda functions use the default AWS LambdaBasicExecutionRole managed role.",
              appliesTo: [
                {
                  regex: `/^Policy::arn:${PDKNag.getStackPartitionRegex(
                    stack
                  )}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole$/g`,
                },
              ],
            },
          ],
          true
        );
        NagSuppressions.addResourceSuppressions(
          destroyFeatureBranchFunction,
          [
            {
              id: RuleId,
              reason:
                "Lambda functions use the default AWS LambdaBasicExecutionRole managed role.",
              appliesTo: [
                {
                  regex: `/^Policy::arn:${PDKNag.getStackPartitionRegex(
                    stack
                  )}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole$/g`,
                },
              ],
            },
          ],
          true
        );
      }
    );

    [
      "AwsSolutions-CB4",
      "AwsPrototyping-CodeBuildProjectKMSEncryptedArtifacts",
    ].forEach((RuleId) => {
      NagSuppressions.addResourceSuppressions(createFeatureBranchProject, [
        {
          id: RuleId,
          reason: "Encryption of Codebuild is not required.",
        },
      ]);
    });
  }