Home > Software design >  CodeBuild: Artifacts upload location doesn't match
CodeBuild: Artifacts upload location doesn't match

Time:09-27

Here is my CodeBuild main page, which says "Artifacts upload location" is "alpha-artifact-bucket":

enter image description here

Here is one of the build run, which is not using above bucket:

enter image description here

What's the difference between the two? Why every build run use a random bucket?

Any way to enforce the CodeBuild use the specified S3 bucket "alpha-artifact-bucket"?

CDK code

CodeBuild stack: I deploy this stack to each AWS along the pipeline first, so that the pipeline stack will just query each AWS and find its corresponding CodeBuild, and add it as a "stage". The reason I'm doing this is because each AWS will have a dedicated CodeBuild stage which will need read some values from its SecretManger.

export interface CodeBuildStackProps extends Cdk.StackProps {
  readonly pipelineName: string;
  readonly pipelineRole: IAM.IRole;
  readonly pipelineStageInfo: PipelineStageInfo;
}

/**
 * This stack will create CodeBuild for the target AWS account.
 */
export class CodeBuildStack extends Cdk.Stack {
  constructor(scope: Construct, id: string, props: CodeBuildStackProps) {
    super(scope, id, props);

    // DeploymentRole will be assumed by PipelineRole to perform the CodeBuild step.
    const deploymentRoleArn: string = `arn:aws:iam::${props.env?.account}:role/${props.pipelineName}-DeploymentRole`;
    const deploymentRole = IAM.Role.fromRoleArn(
      this,
      `CodeBuild${props.pipelineStageInfo.stageName}DeploymentRoleConstructID`,
      deploymentRoleArn,
      {
        mutable: false,
        // Causes CDK to update the resource policy where required, instead of the Role
        addGrantsToResources: true,
      }
    );

    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });

    new CodeBuild.Project(
      this,
      `${props.pipelineStageInfo.stageName}ColdBuild`,
      {
        projectName: `${props.pipelineStageInfo.stageName}ColdBuild`,
        environment: {
          buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
        },
        buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
        role: deploymentRole,
        logging: {
          cloudWatch: {
            logGroup: new Logs.LogGroup(
              this,
              `${props.pipelineStageInfo.stageName}ColdBuildLogGroup`,
              {
                retention: Logs.RetentionDays.ONE_WEEK,
              }
            ),
          },
        },
      }
    );
  }
}

Pipeline Stack:

export interface PipelineStackProps extends CDK.StackProps {
  readonly description: string;
  readonly pipelineName: string;
}

/**
 * This stack will contain our pipeline..
 */
export class PipelineStack extends CDK.Stack {
  private readonly pipelineRole: IAM.IRole;
  constructor(scope: Construct, id: string, props: PipelineStackProps) {
    super(scope, id, props);

    // Get the pipeline role from pipeline AWS account.
    // The pipeline role will assume "Deployment Role" of each AWS account to perform the actual deployment.
    const pipelineRoleName: string =
      "eCommerceWebsitePipelineCdk-Pipeline-PipelineRole";
    this.pipelineRole = IAM.Role.fromRoleArn(
      this,
      pipelineRoleName,
      `arn:aws:iam::${this.account}:role/${pipelineRoleName}`,
      {
        mutable: false,
        // Causes CDK to update the resource policy where required, instead of the Role
        addGrantsToResources: true,
      }
    );

    // Initialize the pipeline.
    const pipeline = new codepipeline.Pipeline(this, props.pipelineName, {
      pipelineName: props.pipelineName,
      role: this.pipelineRole,
      restartExecutionOnUpdate: true,
    });

    // Add a pipeline Source stage to fetch source code from repository.
    const sourceCode = new codepipeline.Artifact();
    this.addSourceStage(pipeline, sourceCode);

    // For each AWS account, add a build stage and a deployment stage.
    pipelineStageInfoList.forEach((pipelineStageInfo: PipelineStageInfo) => {
      const deploymentRoleArn: string = `arn:aws:iam::${pipelineStageInfo.awsAccount}:role/${props.pipelineName}-DeploymentRole`;
      const deploymentRole: IAM.IRole = IAM.Role.fromRoleArn(
        this,
        `DeploymentRoleFor${pipelineStageInfo.stageName}`,
        deploymentRoleArn
      );
      const websiteArtifact = new codepipeline.Artifact();

      // Add build stage to build the website artifact for the target AWS.
      // Some environment variables will be retrieved from target AWS's secret manager.
      this.addBuildStage(
        pipelineStageInfo,
        pipeline,
        deploymentRole,
        sourceCode,
        websiteArtifact
      );

      // Add deployment stage to for the target AWS to do the actual deployment.
      this.addDeploymentStage(
        props,
        pipelineStageInfo,
        pipeline,
        deploymentRole,
        websiteArtifact
      );
    });
  }

  // Add Source stage to fetch code from GitHub repository.
  private addSourceStage(
    pipeline: codepipeline.Pipeline,
    sourceCode: codepipeline.Artifact
  ) {
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.GitHubSourceAction({
          actionName: "Checkout",
          owner: "yangliu",
          repo: "eCommerceWebsite",
          branch: "main",
          oauthToken: CDK.SecretValue.secretsManager(
            "eCommerceWebsite-GitHubToken"
          ),
          output: sourceCode,
          trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
        }),
      ],
    });
  }

  private addBuildStage(
    pipelineStageInfo: PipelineStageInfo,
    pipeline: codepipeline.Pipeline,
    deploymentRole: IAM.IRole,
    sourceCode: codepipeline.Artifact,
    websiteArtifact: codepipeline.Artifact
  ) {
    const stage = new CDK.Stage(this, `${pipelineStageInfo.stageName}BuildId`, {
      env: {
        account: pipelineStageInfo.awsAccount,
      },
    });
    const buildStage = pipeline.addStage(stage);
    const targetProject: CodeBuild.IProject = CodeBuild.Project.fromProjectName(
      this,
      `CodeBuildProject${pipelineStageInfo.stageName}`,
      `${pipelineStageInfo.stageName}ColdBuild`
    );

    buildStage.addAction(
      new codepipeline_actions.CodeBuildAction({
        actionName: `BuildArtifactForAAAA${pipelineStageInfo.stageName}`,
        project: targetProject,
        input: sourceCode,
        outputs: [websiteArtifact],
        
        role: deploymentRole,
      })
    );
  }

  private addDeploymentStage(
    props: PipelineStackProps,
    pipelineStageInfo: PipelineStageInfo,
    pipeline: codepipeline.Pipeline,
    deploymentRole: IAM.IRole,
    websiteArtifact: codepipeline.Artifact
  ) {
    const websiteBucket = S3.Bucket.fromBucketName(
      this,
      `${pipelineStageInfo.websiteBucketName}ConstructId`,
      `${pipelineStageInfo.websiteBucketName}`
    );
    const pipelineStage = new PipelineStage(this, pipelineStageInfo.stageName, {
      stageName: pipelineStageInfo.stageName,
      pipelineName: props.pipelineName,
      websiteDomain: pipelineStageInfo.websiteDomain,
      websiteBucket: websiteBucket,
      env: {
        account: pipelineStageInfo.awsAccount,
        region: pipelineStageInfo.awsRegion,
      },
    });
    const stage = pipeline.addStage(pipelineStage);
    stage.addAction(
      new codepipeline_actions.S3DeployAction({
        actionName: `DeploymentFor${pipelineStageInfo.stageName}`,
        input: websiteArtifact,
        bucket: websiteBucket,
        role: deploymentRole,
      })
    );
  }
}

buildspec.yml:

version: 0.2
env:
  secrets-manager:
    REACT_APP_DOMAIN: "REACT_APP_DOMAIN"
    REACT_APP_BACKEND_SERVICE_API: "REACT_APP_BACKEND_SERVICE_API"
    REACT_APP_GOOGLE_MAP_API_KEY: "REACT_APP_GOOGLE_MAP_API_KEY"
phases:
  install:
    runtime-versions:
      nodejs: 14
    commands:
      - echo Performing yarn install
      - yarn install
  build:
    commands:
      - yarn build

artifacts:
  base-directory: ./build
  files:
    - "**/*"

cache:
  paths:
    - "./node_modules/**/*"

CodePudding user response:

I figured this out. aws-codepipeline pipeline has a built-in artifacts bucket : CDK's CodePipeline or CodeBuildStep are leaving an S3 bucket behind, is there a way of automatically removing it?. That is different from the CodeBuild artifacts.

Because my pipeline role in Account A need to assume the deployment role in Account B to perform the CodeBuild step(of Account B), I need grant the deployment role in Account B the write permission to the pipeline's built-in artifacts bucket. So I need do this:

pipeline.artifactBucket.grantReadWrite(deploymentRole);
  • Related