Using CodePipeline

By Rich

Mon Mar 15 2021

I recentled needed to setup S3 and CloudFormation to host a static website built with React.

AWSTemplateFormatVersion: "2010-09-09"
Description: Graphboss CI/CD pipeline
Parameters:
  GitHubOAuthToken:
    Type: String
    NoEcho: true
    MinLength: 40
    MaxLength: 40
    AllowedPattern: "[a-z0-9]*"

  GitHubOwner:
    Type: String
    AllowedPattern: "[A-Za-z0-9-]+"

  GitHubRepo:
    Type: String
    AllowedPattern: "[A-Za-z0-9-]+"

  GitHubBranch:
    Type: String
    AllowedPattern: "[A-Za-z0-9-]+"

Resources:
  ArtifactStoreBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref ArtifactStoreBucket
        Type: S3
      Name: !Sub "${GitHubRepo}-${GitHubBranch}"
      RestartExecutionOnUpdate: true
      RoleArn: !GetAtt PipelineRole.Arn
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Version: "1"
                Provider: GitHub
              Configuration:
                Owner: !Ref GitHubOwner
                Repo: !Ref GitHubRepo
                Branch: !Ref GitHubBranch
                PollForSourceChanges: false
                OAuthToken: !Ref GitHubOAuthToken
              OutputArtifacts:
                - Name: SourceCode
              RunOrder: 1
        # - Name: CodePipeline
        #   Actions:
        #     - Name: Update-Stack
        #       ActionTypeId:
        #         Category: Deploy
        #         Owner: AWS
        #         Provider: CloudFormation
        #         Version: "1"
        #       InputArtifacts:
        #         - Name: SourceCode
        #       Configuration:
        #         ActionMode: CREATE_UPDATE
        #         Capabilities: CAPABILITY_IAM,CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND
        #         RoleArn: !GetAtt CloudformationRole.Arn
        #         StackName: !Ref AWS::StackName
        #         TemplatePath: "SourceCode::template.yaml"
        #         ParameterOverrides: |
        #           {
        #             "GitHubOwner": !Ref GitHubOwner,
        #             "GitHubBranch": { "Ref": "GitHubBranch" },
        #             "GitHubOAuthToken": { "Ref": "GitHubOAuthToken" },
        #             "GitHubRepo":  { "Ref": "GitHubRepo" },
        #           }
        #       RunOrder: 1
        - Name: Build
          Actions:
            - Name: Website
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: SourceCode
              OutputArtifacts:
                - Name: BuildWebsite
              RunOrder: 1
        - Name: Deploy
          Actions:
            - Name: Website
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: S3
                Version: "1"
              InputArtifacts:
                - Name: BuildWebsite
              Configuration:
                BucketName: !Ref HostingBucket
                Extract: true
                CannedACL: "private"
                CacheControl: "no-cache"
              RunOrder: 1

  GithubWebhook:
    Type: "AWS::CodePipeline::Webhook"
    Properties:
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: !Ref GitHubOAuthToken
      RegisterWithThirdParty: true
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: refs/heads/{Branch}
      TargetPipeline: !Ref Pipeline
      TargetAction: Source
      TargetPipelineVersion: !GetAtt Pipeline.Version

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:5.0
        EnvironmentVariables:
          - Name: ARTIFACT_BUCKET
            Value: !Ref ArtifactStoreBucket
          - Name: ARTIFACT_BUCKET_PREFIX
            Value: PackagedCode
      Name: !Sub "${GitHubRepo}-${GitHubBranch}"
      ServiceRole:
        Fn::GetAtt:
          - CodeBuildRole
          - Arn

  CloudformationRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

  PipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          Effect: Allow
          Principal:
            Service: codebuild.amazonaws.com
          Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

  HostingBucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private

  HostingBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      PolicyDocument:
        Id: PolicyForCloudFrontPrivateContent
        Version: 2012-10-17
        Statement:
          - Sid: PublicReadForGetBucketObjects
            Effect: Allow
            Principal:
              CanonicalUser: !GetAtt CloudFrontOAI.S3CanonicalUserId
            Action: "s3:GetObject"
            Resource: !Join
              - ""
              - - "arn:aws:s3:::"
                - !Ref HostingBucket
                - /*
      Bucket: !Ref HostingBucket

  CloudFrontCertificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: graphboss.com
      DomainValidationOptions:
        - DomainName: graphboss.com
          HostedZoneId: Z1OTIL07BUWDR5
        - DomainName: www.graphboss.com
          HostedZoneId: Z1OTIL07BUWDR5
      SubjectAlternativeNames:
        - www.graphboss.com
      ValidationMethod: DNS

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: CDN for website
        Aliases:
          - graphboss.com
          - www.graphboss.com
        ViewerCertificate:
          AcmCertificateArn: !Ref CloudFrontCertificate
          SslSupportMethod: sni-only
        Enabled: true
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
            - OPTIONS
          ForwardedValues:
            QueryString: true
          TargetOriginId: HostingBucketOrigin
          ViewerProtocolPolicy: redirect-to-https
        DefaultRootObject: index.html
        CustomErrorResponses:
          - ErrorCode: 400
            ResponseCode: 200
            ResponsePagePath: /index.html
          - ErrorCode: 403
            ResponseCode: 200
            ResponsePagePath: /index.html
          - ErrorCode: 404
            ResponseCode: 200
            ResponsePagePath: /index.html
          - ErrorCode: 405
            ResponseCode: 200
            ResponsePagePath: /index.html
          - ErrorCode: 414
            ResponseCode: 200
            ResponsePagePath: /index.html
          - ErrorCode: 416
            ResponseCode: 200
            ResponsePagePath: /index.html
        Origins:
          - DomainName: !GetAtt HostingBucket.DomainName
            Id: HostingBucketOrigin
            S3OriginConfig:
              OriginAccessIdentity:
                Fn::Join:
                  - "/"
                  - - origin-access-identity/cloudfront
                    - !Ref CloudFrontOAI

  CloudFrontOAI:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Allow access to the Graphboss S3 bucket from CloudFront

Outputs:
  OAI:
    Value: !Ref CloudFrontOAI
    Description: OAI

  S3CanonicalUserId:
    Value: !GetAtt CloudFrontOAI.S3CanonicalUserId
    Description: S3CanonicalUserId

  CloudFrontDistributionURL:
    Value: !Join
      - ""
      - - "https://"
        - !GetAtt
          - CloudFrontDistribution
          - DomainName
    Description: CloudFront URL for the website

  WebsiteURL:
    Value: !GetAtt
      - HostingBucket
      - WebsiteURL
    Description: URL for website hosted on S3

  S3BucketSecureURL:
    Value: !Join
      - ""
      - - "https://"
        - !GetAtt
          - HostingBucket
          - DomainName
    Description: Name of S3 bucket to hold website content

Want to learn more about serverless applications and devops with AWS?

Sign up for our newsletter.