How to do full clone using CodePipeline, CodeBuild and CodeCommit

357 views Asked by At

I'm trying to do an end-to-end automated deployment using AWS CodePipeline, CodeBuild and CodeCommit but source stage is falling with this if I try to do a full clone using: OutputArtifactFormat = "CODEBUILD_CLONE_REF":

[Container] 2023/11/10 13:16:05.798892 Waiting for agent ping
[Container] 2023/11/10 13:16:06.799887 Waiting for DOWNLOAD_SOURCE
repository not found for primary source and source version xxxxx

The default CODE_ZIP works just fine.

I have already added codecommit:GitPull permission to CodeBuild service role and codecommit:GetRepository permission to CodePipeline service role.

Below is the related part of the code:

// CodeBuild Project
resource "aws_codebuild_project" "this" {
  name                   = var.app_name
  service_role           = aws_iam_role.codebuild.arn
  concurrent_build_limit = 1

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = var.build_image
    image_pull_credentials_type = "SERVICE_ROLE"
    privileged_mode             = false
    type                        = "ARM_CONTAINER"
  }

  artifacts {
    type = "CODEPIPELINE"
  }

  source {
    type      = "CODEPIPELINE"
    #location = var.code_commit_https_url
    buildspec = file("${path.module}/buildspec.yaml")
  }
}

// CodePipeline
resource "aws_codepipeline" "this" {
  name     = var.app_name
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = regex("[^:]+$", var.s3_bucket_arn)
    type     = "S3"

    encryption_key {
      id   = var.pipeline_key_arn
      type = "KMS"
    }
  }

  stage {
    name = "Source"
    action {
      category         = "Source"
      name             = "Source"
      output_artifacts = ["SOURCE_ARTIFACT"]
      owner            = "AWS"
      provider         = "CodeCommit"
      role_arn         = var.assume_role_arn
      run_order        = 1
      version          = "1"

      configuration = {
        RepositoryName       = var.git_repo_source
        BranchName           = var.git_repo_branch
        PollForSourceChanges = false
        OutputArtifactFormat = "CODEBUILD_CLONE_REF"
        #OutputArtifactFormat = "CODE_ZIP"
      }
    }
  }

  stage {
    name = "TerraformValidate"
    action { .... }
  }
  ....
}

(the var.git_repo_source is just the name of the repo, not the full https clone URL. If fails with invalid value if I use the full URL)

This AWS Example seems to be matching with what I'm doing in TF but still failing. What am I missing or doing wrong?

1

There are 1 answers

0
DarkTrick On

This is a general solution for any problems with cross-account pipelines with git submodules in CodeCommit

This is not directly an answer to the question, but will hopefully help anyone facing similar problems.

The problem:

AWS' support for submodules quite improvable.

Workaround:

Use a CodeBuild project, that will clone the repository and initialize all submodules.

Details

Create your pipeline like this

  • Source stage (modify)
  • Source 2 stage (new)
  • Build stage (no change)
  • Deploy stage (no change)

Source stage is the "native" source stage. I won't do a full clone. It's just being used to trigger the pipeline. If you don't need that, you can remove it completely.

Source2 stage will run the CodeBuild project that will clone the repo and initialize its submodules.

Implementation

CodeBuild Project for cloning a repo

The following code is fully functional CloudFormation source code. You should be able to copy/paste and run it. You will only need on deployment of the resources. No need to create a new one for each pipeline you're using.

AWSTemplateFormatVersion: '2010-09-09'

Description: Do a full repo clone with submodule initialization.

Resources:
  FullCloneGitRepositoryCodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-Role
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
                Service:
                      - codebuild.amazonaws.com
            Action: sts:AssumeRole

  FullCloneGitRepositoryCodeBuild:
    Type: AWS::CodeBuild::Project
    DependsOn: [
      FullCloneGitRepositoryCodeBuildRole
    ]
    Properties:
      ServiceRole: !Ref FullCloneGitRepositoryCodeBuildRole
      Artifacts:
          Type: CODEPIPELINE
      Environment:
          EnvironmentVariables:
            - Name: RepositoryAccessRole
              Value: ''
              Type: PLAINTEXT
            - Name: RepositoryHttpsUri
              Value: ''
              Type: PLAINTEXT
            - Name: RepositoryBranch
              Value: ''
              Type: PLAINTEXT
          # Use tiny ARM container for minimal cost
          Type: ARM_CONTAINER
          Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
          ComputeType: BUILD_GENERAL1_SMALL
      Source:
          Type: CODEPIPELINE
          BuildSpec: |
            version: 0.2
            phases:
              pre_build:
                commands:
                  - echo "Env var 'Repository access role' = ${RepositoryAccessRole}"
                  - echo "Env var 'Repository https uri' = ${RepositoryHttpsUri}"
                  - echo "Env var 'Repository branch' = ${RepositoryBranch}"

                  - assume_role_output=$(aws sts assume-role --role-arn "${RepositoryAccessRole}" --role-session-name CodeBuildSession)
                  - export AWS_ACCESS_KEY_ID=$(echo $assume_role_output | jq -r '.Credentials.AccessKeyId')
                  - export AWS_SECRET_ACCESS_KEY=$(echo $assume_role_output | jq -r '.Credentials.SecretAccessKey')
                  - export AWS_SESSION_TOKEN=$(echo $assume_role_output | jq -r '.Credentials.SessionToken')

                  - git config --global credential.helper '!aws codecommit credential-helper $@'
                  - git config --global credential.UseHttpPath true
              build:
                commands:
                  # Clone into a new directory. Reason: Just be safe in case the current directory is not empty
                  #                                     (E.g. If the pipeline used a source stage as pipeline trigger)
                  - git clone -b ${RepositoryBranch} --single-branch ${RepositoryHttpsUri} repo
                  - cd repo
                  - git submodule update --init --recursive

            # set all files and folders available as output artifacts
            #   (Otherwise they wont be accessible in the next pipeline stage)
            artifacts:
              files:
                - '**/*'
              base-directory: 'repo'


Outputs:
  FullCloneGitRepositoryCodeBuild:
    Value: !GetAtt FullCloneGitRepositoryCodeBuild.Arn
    Description: |
      Utility Function..
      Clone a CodeCommit Repository and all submodules into a CodePipeline Artifact.
    Export:
      Name: !Sub ${AWS::StackName}-stage-arn

  FullCloneGitRepositoryCodeBuildName:
    Value: !Ref FullCloneGitRepositoryCodeBuild
    Description: CodeBuild project name.
    Export:
      Name: !Sub ${AWS::StackName}-stage-name

  FullCloneGitRepositoryCodeBuildRole:
    Value: !GetAtt FullCloneGitRepositoryCodeBuildRole.Arn
    Description: |
      Role use for cloning the CodeCommit repository.
      Attach necessary policies to this role in your pipeline.
    Export:
      Name: !Sub ${AWS::StackName}-role-arn

Important

  • To specify repo, branch and repo access role, use the three environment variables of the project. Specify them in the pipeline step Source2 (see below)
  • The role created in this template does not have any policies. Create the policy at a different location (i.e. during the pipeline creating) and attach it to the role. Background for this architecture: Make the CodeBuild project reusable among all pipelines being used.

Role Policy

The role policy is a little bit more tricky. The code below will probably not work for you as copy/paste, but it might give you a lead. Variables are explained in the bottom.

FullCloneGitRepoPolicy:
    Type: AWS::IAM::Policy
    Properties:
        PolicyName: !Sub ${AWS::StackName}-FullCloneGitRepoPolicy
        PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Sid: LoggingPermissions
              Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource:
                -  !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${FullCloneGitRepositoryCodeBuildProjectName}
                -  !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${FullCloneGitRepositoryCodeBuildProjectName}:*
            - Sid: PipelineArtifactStoreAccess
              Effect: Allow
              Action:
                - s3:GetObject
                - s3:GetObjectVersion
                - s3:GetBucketAcl
                - s3:GetBucketLocation
                - s3:PutObject
              Resource:
                - !Sub "arn:aws:s3:::${PipelineArtifactStore}"
                - !Sub "arn:aws:s3:::${PipelineArtifactStore}/*"
            - Sid: PipelineArtifactStoreEncryption
              Effect: Allow
              Action:
                # need to read from the store
                - kms:Decrypt
                # needed to write to the store
                - kms:DescribeKey
                - kms:GenerateDataKey*
                - kms:Encrypt
                - kms:ReEncrypt*
              Resource: !Ref PipelineArtifactStoreEncryptionKeyArn
            - Sid: AssumeRole
              Effect: Allow
              Action:
                - sts:AssumeRole
              Resource:
                - !Ref SourceCodeRepoAccessRole

            - Sid: CodebuildPermissions
              Effect: Allow
              Action:
                - codebuild:*
                - codebuild:StartBuild
                - codebuild:BatchGetBuilds
                - codebuild:StopBuild
              Resource:
                - !Sub arn:aws:codebuild:${AWS:Region}:${AWS::AccountId}:project/${FullCloneGitRepositoryCodeBuildProjectName}

        Roles:
          - !Sub ${FullCloneGitRepositoryCodeBuildRoleName}

Variables:

  • FullCloneGitRepositoryCodeBuildProjectName: CodeBuild project name of the cloudformation template above, for cloning.
  • FullCloneGitRepositoryCodeBuildRoleName: Role created in the cloudformation template above, for cloning.
  • SourceCodeRepoAccessRole: ARN of the Role that is able access the source code repository. For cross-account pipelines, this will be a role in the AWS account that contains the repositories.
  • PipelineArtifactStore: S3 bucket name where pipeline artifacts will be stored. This bucket is in the same AWS account as the pipeline is located.
  • PipelineArtifactStoreEncryptionKeyArn: KMS encryption key used for access to the pipeline artifact store. This is necessary when working with cross-account pipelines.

CodePipeline

The code below shows the definition of the Source2 stage. Pay attentien how the three variables for repo uri, branch name and repo access role are being conveyed.

      # "Source2", because I use a non-full-clone Source step as a trigger for the pipeline to run
      - Name: Source2
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Sub ${GetSubmodulesCodeBuild}
                EnvironmentVariables: !Sub |
                  [
                    {
                      "name": "RepositoryAccessRole",
                      "value": "${SourceCodeRepoAccessRole}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "RepositoryHttpsUri",
                      "value": "${SourceCodeRepoHttpsUri}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "RepositoryBranch",
                      "value": "${SourceCodeRepoBranchName}",
                      "type": "PLAINTEXT"
                    }
                  ]
              Namespace: Source2VariableNamespace
              # InputArtifacts are only stated because they have to be (what a useless constraint...). We don't use the output of the source stage.
              InputArtifacts:
                   - Name: SourceOutput
              OutputArtifacts:
                   - Name: Source2Output