Logo

dev-resources.site

for different kinds of informations.

Building applications with pipelines

Published at
2/1/2023
Categories
codecommit
codepipeline
cicd
featurebranch
Author
nr18
Author
4 person written this
nr18
open
Building applications with pipelines

In larger organizations a lot of things are in place. Think of things like deployment pipelines for your applications. But how do you set those up using the services AWS provides? In this blog post I will guide you to how I do it for my pet projects.

I am a big fan of CloudFormation so the examples I will show are CloudFormation snippets.

The git repository

I am using AWS CodeCommit as a source repository. The reason for this choice is that it integrates with AWS CodePipeline. We also want the ability to test our infrastructure. So I usually use feature branches to do that. Here the first challange appears. We only need 1 repository and a pipeline to deploy our infrastructure. But we also need a pipeline that deploys our feature branch. In CloudFormation you can do this using conditions.

Parameters:
  FeatureGitBranch:
    Type: String
    Default: ""
  ProjectName:
    Type: String
    Default: ""   
Conditions:
  IsMainBranchPipeline: !Equals [!Ref FeatureGitBranch, ""]
  IsFeatureBranchPipeline: !Not [Condition: IsMainBranchPipeline]
Enter fullscreen mode Exit fullscreen mode

By providing a FeatureGitBranch parameter we can now make some choices. For example we could only create the repository in the production pipeline.

Resources:
  PipelineRepo:
    Condition: IsMainBranchPipeline
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref ProjectName
      RepositoryDescription: <repository-description>
Enter fullscreen mode Exit fullscreen mode

The pipeline requirements

The pipeline uses a couple of resources. You can share these resources across your pipelines. So they are in a separate template and I deploy these resources once per region that I am using.

First you will need a S3 bucket to store the artifacts created by each stage.

 

ArtifactsBucket:
  Type: AWS::S3::Bucket
  DeletionPolicy: Retain
  UpdateReplacePolicy: Retain
  Properties:
    BucketName: !Sub codepipeline-artifacts-${AWS::AccountId}-${AWS::Region}
    VersioningConfiguration:
      Status: Enabled
    BucketEncryption:
      ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
Enter fullscreen mode Exit fullscreen mode

I am using a predictable bucket name so I can use it in my pipeline. Because bucket names need to have a globally unique name I also include the account id and region. 

You will also need an IAM role. You will use this role in your pipeline to deploy your CloudFormation template.

CloudFormationServiceRole:
 Type: AWS::IAM::Role
 Properties:
   RoleName: !Sub cloudformation-execution-role-${AWS::Region}
   AssumeRolePolicyDocument:
     Version: "2012-10-17"
     Statement:
       - Action: sts:AssumeRole
         Effect: Allow
         Principal: { Service: cloudformation.amazonaws.com }
   ManagedPolicyArns:
     - arn:aws:iam::aws:policy/AdministratorAccess
Enter fullscreen mode Exit fullscreen mode

Again I use a predictable name and I included the region. Since role names are unique in your account you can deploy this in every region. For the sake of simplicity I used the AdministratorAccess managed policy. You might want to limit this for your own scope.

The pipeline

The pipeline itself also needs a role. This role can pass the CloudFormation role and use the buckets created in the previous step.

CodePipelineExecutionRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Action: "sts:AssumeRole"
          Effect: Allow
          Principal:
            Service:
              - codepipeline.amazonaws.com
    Policies:
      - PolicyName: CodePipelineAccess
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action: "iam:PassRole"
              Resource: !Sub cloudformation-execution-role-${AWS::Region}
      - PolicyName: CodePipelineCodeAndS3Bucket
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Action:
                - s3:GetBucketAcl
                - s3:GetBucketLocation
              Effect: Allow
              Resource:
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${AWS::AccountId}-eu-west-1
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${AWS::AccountId}-eu-central-1
            - Action:
                - "s3:GetObject"
                - "s3:GetObjectVersion"
                - "s3:PutObject"
              Effect: Allow
              Resource:
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${AWS::AccountId}-eu-west-1/*
                - !Sub arn:${AWS::Partition}:s3:::codepipeline-artifacts-${AWS::AccountId}-eu-central-1/*
      - PolicyName: CodeCommitAccess
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Action:
                - codecommit:GitPull
                - codecommit:GetBranch
                - codecommit:GetCommit
                - codecommit:UploadArchive
                - codecommit:GetUploadArchiveStatus
              Effect: Allow
              Resource:
                - !Sub arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${ProjectName}
                - !Sub arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${ProjectName}/*
      - PolicyName: CodePipelineCodeBuildAndCloudformationAccess
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - "codebuild:StartBuild"
                - "codebuild:BatchGetBuilds"
              Resource:
                - !GetAtt CodeBuildProjectBuildAndPackage.Arn
            - Effect: Allow
              Action:
                - "cloudformation:DescribeStacks"
              Resource: !Sub "arn:${AWS::Partition}:cloudformation:eu-*:${AWS::AccountId}:stack/*"
            - Effect: Allow
              Action:
                - "cloudformation:CreateStack"
                - "cloudformation:DeleteStack"
                - "cloudformation:UpdateStack"
                - "cloudformation:CreateChangeSet"
                - "cloudformation:ExecuteChangeSet"
                - "cloudformation:DeleteChangeSet"
                - "cloudformation:DescribeChangeSet"
                - "cloudformation:SetStackPolicy"
                - "cloudformation:SetStackPolicy"
                - "cloudformation:ValidateTemplate"
              Resource:
                - !Sub "arn:${AWS::Partition}:cloudformation:eu-*:${AWS::AccountId}:stack/${ProjectName}-*/*"
Enter fullscreen mode Exit fullscreen mode

As you can see in the snippet the role is also allowed to start a CodeBuild project. And it can deploy a CloudFormation template.
You might have noticed that I used 2 regions in this role. This is because in this particular example I have a pipeline that uses 2 regions.

Because we use 2 regions we also need 2 artifact stores.

Pipeline:
  Type: AWS::CodePipeline::Pipeline
  Properties:
    ArtifactStores:
      - Region: eu-west-1
        ArtifactStore:
          Location: !Sub codepipeline-artifacts-${AWS::AccountId}-eu-west-1
          Type: S3
      - Region: eu-central-1
        ArtifactStore:
          Location: !Sub codepipeline-artifacts-${AWS::AccountId}-eu-central-1
          Type: S3
    RoleArn: !GetAtt CodePipelineExecutionRole.Arn
    RestartExecutionOnUpdate: true
    Stages:
      - Name: Source
        Actions:
          - Name: SourceCodeRepo
            ActionTypeId:
              Category: Source
              Owner: AWS
              Version: 1
              Provider: CodeCommit
            Configuration:
              PollForSourceChanges: False
              RepositoryName: !Ref ProjectName
              BranchName: !If [IsFeatureBranchPipeline, !Ref FeatureGitBranch, "main"]
            OutputArtifacts:
              - Name: SourceCodeAsZip
            RunOrder: 1
Enter fullscreen mode Exit fullscreen mode

Depending on the IsFeatureBranchPipeline condition we select the correct branch for this pipeline. Depending on your project you might need a build step. I use CodeBuild to run unit tests and build and package any resources needed.

      - Name: BuildAndPackage
        Actions:
          - Name: CodeBuild
            ActionTypeId:
              Category: Build
              Owner: AWS
              Provider: CodeBuild
              Version: "1"
            Configuration:
              ProjectName: !Ref CodeBuildProjectBuildAndPackage
            InputArtifacts:
              - Name: SourceCodeAsZip
            OutputArtifacts:
              - Name: BuildArtifactAsZip
Enter fullscreen mode Exit fullscreen mode

I did not include the CodeBuild project and IAM role snippet for CodeBuild. I will do a blog post on that in the future.

So lets go over to the fun part. The next stage is the actual deployment of the CloudFormation template. I am using 2 regions and I only want to deploy to these 2 regions for my production environment. I also have different parameters for my production environment. Again, I am using conditions to control this behaviour.

          - !If
            - IsMainBranchPipeline
            - Name: Production
              Actions:
                - Name: Ireland-CreateChangeSet
                  RunOrder: 1
                  Region: eu-west-1
                  InputArtifacts:
                    - Name: BuildArtifactAsZip
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_REPLACE
                    RoleArn: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-production
                    ChangeSetName: !Sub ${ProjectName}-production-ChangeSet
                    TemplatePath: BuildArtifactAsZip::packaged-template.yaml
                    Capabilities: CAPABILITY_NAMED_IAM
                    ParameterOverrides: |-
                      {
                        "EnvType": "production"
                      }
                - Name: Frankfurt-CreateChangeSet
                  RunOrder: 1
                  Region: eu-central-1
                  InputArtifacts:
                    - Name: BuildArtifactAsZip
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_REPLACE
                    RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-production
                    ChangeSetName: !Sub ${ProjectName}-production-ChangeSet
                    TemplatePath: BuildArtifactAsZip::packaged-template.yaml
                    Capabilities: CAPABILITY_NAMED_IAM
                    ParameterOverrides: |
                      {
                        "EnvType": "production"
                      }
                - Name: Ireland-ExecuteChangeSet
                  RunOrder: 2
                  Region: eu-west-1
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_EXECUTE
                    RoleArn: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-production
                    ChangeSetName: !Sub ${ProjectName}-production-ChangeSet
                - Name: Frankfurt-ExecuteChangeSet
                  RunOrder: 2
                  Region: eu-central-1
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_EXECUTE
                    RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-production
                    ChangeSetName: !Sub ${ProjectName}-production-ChangeSet
            - !Ref AWS::NoValue
Enter fullscreen mode Exit fullscreen mode

In the 1 action I create a change set. In the second action I execute it. Please note the RoleArn used here. You could also specify a role from a different account. This allows you to use a separate account for different environments. For example you could host the pipeline in a deployment account. And have separate development, testing, acceptance and production accounts.

The feature branch

So we now have a working pipeline for the production environment. How about the feature branch? For this we use again the conditions.

          - !If
            - IsFeatureBranchPipeline
            - Name: FeatureDevelopment
              Actions:
                - Name: Ireland-CreateChangeSet
                  RunOrder: 1
                  Region: eu-west-1
                  InputArtifacts:
                    - Name: BuildArtifactAsZip
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_REPLACE
                    RoleArn: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-${FeatureName}
                    ChangeSetName: !Sub ${ProjectName}-feature-ChangeSet
                    TemplatePath: BuildArtifactAsZip::packaged-template.yaml
                    Capabilities: CAPABILITY_NAMED_IAM
                    ParameterOverrides: |
                      {
                        "EnvType": "development"
                      }
                - Name: Ireland-ExecuteChangeSet
                  RunOrder: 2
                  Region: eu-west-1
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Provider: CloudFormation
                    Version: "1"
                  Configuration:
                    ActionMode: CHANGE_SET_EXECUTE
                    RoleArn: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cloudformation-execution-role
                    StackName: !Sub ${ProjectName}-${FeatureName}
                    ChangeSetName: !Sub ${ProjectName}-feature-ChangeSet
            - !Ref AWS::NoValue
Enter fullscreen mode Exit fullscreen mode

As you can see, the feature branch is only deployed to the eu-west-1 region. And it has set the EnvType parameter to development.

Pickup new commits

When you push your commits to the CodeCommit repository. You can use an event rule to trigger a new pipeline execution. To make this happen we need an IAM role and the rule itself.

CloudWatchEventRole:
   Type: AWS::IAM::Role
   Properties:
     AssumeRolePolicyDocument:
       Version: '2012-10-17'
       Statement:
       - Effect: Allow
         Principal: { Service: events.amazonaws.com }
         Action: sts:AssumeRole
     Policies:
       - PolicyName: root
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
             - Action: codepipeline:StartPipelineExecution
               Resource: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
               Effect: Allow

CloudWatchEventRule:
  Type: AWS::Events::Rule
  Properties:
    Description: Check CodeCommit Repo Changes
    EventPattern:
      detail-type:
        - "CodeCommit Repository State Change"
      source:
        - aws.codecommit
      resources:
        - !Sub arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${ProjectName}
      detail:
        event:
          anything-but:
            - referenceDeleted
        referenceType:
          - branch
        referenceName:
          - !If [IsFeatureBranchPipeline, !Ref FeatureGitBranch, "main"]
    Targets:
      - Id: codepipeline
        Arn: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
        RoleArn: !Sub ${CloudWatchEventRole.Arn}
Enter fullscreen mode Exit fullscreen mode

We are triggering on every event except when the branch removal. 

Conclusion 

You can store the template for the pipeline in your CodeCommit repository. When you create a feature branch you can deploy that template specifying the branch name. The pipeline can then deploy the project CloudFormation template. And with every commit that you push your feature environment will receive an update.

With this setup you have everything in a single repository. (Except for the shared S3 bucket and IAM role.) Having everything in a single repository makes it easier to maintain.

codepipeline Article's
30 articles in total
Favicon
Streamlining CI/CD with AWS CodePipeline and GitHub Actions: A DevOps Perspective
Favicon
New Free CI/CD Platform Enhances Mobile App Deployment: A Comprehensive Solution for Developers
Favicon
AWS CodePipeline introduces the Commands action that enables customer to easily run shell commands as part of pipeline execution
Favicon
AWS CodePipeline V2 type pipelines supports to automatically retry a stage if there is a failure in the stage.
Favicon
Automate Application Deployment to AWS Elastic Beanstalk with Terraform and CodePipeline
Favicon
Say Goodbye to Extra CodeBuild Projects: AWS CodePipeline’s New Commands Action Explained
Favicon
AWS CodePipeline V2 type pipelines introduces pipeline variable check rule
Favicon
Deploying Flutter Web to S3 with CodeCommit, Codepipeline, Codebuild, and CodeDeploy
Favicon
Update Github token in Codepipeline with Cloudformation
Favicon
Setting up CI/CD in AWS with CodeCommit, CodeDeploy, CodePipeline, ECR, and ECS
Favicon
Creating a Continuous Delivery Pipeline in AWS (Hands-On)
Favicon
Deploy NodeJS REST API on ECS Fargate using AWS CodePipeline
Favicon
Automatizando infraestructura tecnológica con DevOps
Favicon
Review of Elastic beanstalk, CodeDeploy, CodePipeline, CodeBuild
Favicon
Despliega tu Asistente de IA Generativa en AWS
Favicon
UI Devs on AWS Should Start with Amplify
Favicon
Blue-Green deployment with GitLab CI and CodePipeline on an AWS ECS cluster
Favicon
Part 1: Pipeline fun with AWS
Favicon
Extending CloudFormation's Power: Creating Custom Resources for Enhanced AWS Resource Management
Favicon
¿Que es la cultura DevOps?
Favicon
Receive Slack Notification of CodePipeline with SNS and Lambda
Favicon
Deploy S3 hosted application using CodePipeline
Favicon
Continuous Cloning of CodeCommit Repo in Multiple Regions Using CodeBuild and CodePipeline
Favicon
Cross account deployments using a Customer Managed KMS key
Favicon
3-part series - (3) Create a CodePipeline pipeline and deploy the application on the instance
Favicon
3-part series - (1) Launch an EC2 Instance, Install CodeDeploy Agent and upload App_Linux.zip file to version enabled S3 bucket
Favicon
6-part series - (5) Create a CodeBuild Project, upload the code and push it to the master repository
Favicon
6-part series - (4) Create, clone a CodeCommit Repo, move-push the Dockerfile and index.html to remote repository master branch
Favicon
Sexiest way to manage your AWS resources
Favicon
Building applications with pipelines

Featured ones: