SAM Pipelineで作成されるリソースを眺めてみる

2022.09.05

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「実際に手を動かす前に、SAM Pipelineの雰囲気をなんとなく掴みたい。。」

SAM Pipelineは対話形式で必要な情報を渡すと、サーバレスアプリケーションのデプロイパイプライン用のCloudFormation Templateを自動生成してくれます。

パイプライン作成をよしなにやってくれる機能ではありますが、中身もある程度知っておきたいものです。

勉強がてら作成してくれるリソースを眺めてみます。

準備: SAM Pipelineの作成

CodeCommitをソースにして、PipelineはCodePipelineを使用したものを作成します。

コマンドを実行して、CloudFormation Template等必要なファイルを用意します。

SAM Pipelineの使い方やデプロイの流れは以下の記事がわかりやすかったです。

% sam pipeline bootstrap
# 省略
[4] Summary
Below is the summary of the answers:
        1 - Account: 0000000
        2 - Stage configuration name: dev
        3 - Region: ap-northeast-1
        4 - Pipeline user: [to be created]
        5 - Pipeline execution role: [to be created]
        6 - CloudFormation execution role: [to be created]
        7 - Artifacts bucket: [to be created]
        8 - ECR image repository: [skipped]
% sam pipeline init
# 省略
SUMMARY
We will generate a pipeline config file based on the following information:
        What is the Git provider?: CodeCommit
        What is the CodeCommit repository name?: samp-app-test
        What is the Git branch used for production deployments?: main
        What is the template file path?: template.yaml
        Select an index or enter the stage 1's configuration name (as provided during the bootstrapping): sam-app-dev
        What is the sam application stack name for stage 1?: sam-app-prd
        What is the pipeline execution role ARN for stage 1?: arn:aws:iam::000000000:role/aws-sam-cli-managed-dev-pipe-PipelineExecutionRole-QO6TWPN0NBLJ
        What is the CloudFormation execution role ARN for stage 1?: arn:aws:iam::000000000:role/aws-sam-cli-managed-dev-p-CloudFormationExecutionR-7KYS28POR6G8
        What is the S3 bucket name for artifacts for stage 1?: aws-sam-cli-managed-dev-pipeline-artifactsbucket-ywqrfhm83rhl
        What is the ECR repository URI for stage 1?: 
        What is the AWS region for stage 1?: ap-northeast-1
        Select an index or enter the stage 2's configuration name (as provided during the bootstrapping): 1
        What is the sam application stack name for stage 2?: sam-app
        What is the pipeline execution role ARN for stage 2?: arn:aws:iam::000000000:role/aws-sam-cli-managed-dev-pipe-PipelineExecutionRole-QO6TWPN0NBLJ
        What is the CloudFormation execution role ARN for stage 2?: arn:aws:iam::000000000:role/aws-sam-cli-managed-dev-p-CloudFormationExecutionR-7KYS28POR6G8
        What is the S3 bucket name for artifacts for stage 2?: aws-sam-cli-managed-dev-pipeline-artifact
        What is the ECR repository URI for stage 2?: 
        What is the AWS region for stage 2?: ap-northeast-1

上記コマンドを実行すると、以下のようにファイルが作成されます。

├── ./assume-role.sh
├── ./codepipeline.yaml
└── ./pipeline
    ├── ./pipeline/buildspec_build_package.yml
    ├── ./pipeline/buildspec_deploy.yml
    ├── ./pipeline/buildspec_feature.yml
    ├── ./pipeline/buildspec_integration_test.yml
    └── ./pipeline/buildspec_unit_test.yml

初期生成される雛形ファイルの中身は、Githubからも確認できます。

Github aws/aws-sam-cli-pipeline-init-templates

眺めてみる

CloudFormatoin Tempalte(codepipeline.yaml)

まずは、CloudFormation Templateであるcodepipeline.yamlです。

codepipeline.yaml
AWSTemplateFormatVersion : '2010-09-09'
Description: >
  This template deploys a CodePipeline with its required resources.

  The following stages are predefined in this template:
  - Source
  - UpdatePipeline
  - BuildAndDeployFeatureStack (FeatureGitBranch only)
  - BuildAndPackage (MainGitBranch only)
  - DeployTest (MainGitBranch only)
  - DeployProd (MainGitBranch only)

  **WARNING** You will be billed for the AWS resources used if you create a stack from this template.


# To deploy this template and connect to the main git branch, run this against the leading account:
# `sam deploy -t codepipeline.yaml --stack-name <stack-name> --capabilities=CAPABILITY_IAM`.

# If later you need to deploy a new CodePipeline to connect to a non-main git branch, run
# 
# sam deploy -t codepipeline.yaml --stack-name <stack-name> --capabilities=CAPABILITY_IAM \
#   --parameter-overrides="FeatureGitBranch=<branch-name>"
# 


Parameters:
  GitProviderType:
    Type: String
    Default: "CodeCommit"
  CodeCommitRepositoryName:
    Type: String
    Default: "samp-app-test"
  MainGitBranch:
    Type: String
    Default: "main"
  SamTemplate:
    Type: String
    Default: "template.yaml"
  TestingRegion:
    Type: String
    Default: "ap-northeast-1"
  TestingStackName:
    Type: String
    Default: "sam-app-prd"
  TestingPipelineExecutionRole:
    Type: String
    Default: "arn:aws:iam::00000000000:role/aws-sam-cli-managed-dev-pipe-PipelineExecutionRole-QO6TWPN0NBLJ"
  TestingCloudFormationExecutionRole:
    Type: String
    Default: "arn:aws:iam::00000000000:role/aws-sam-cli-managed-dev-p-CloudFormationExecutionR-7KYS28POR6G8"
  TestingArtifactBucket:
    Type: String
    Default: "bucket"
  TestingImageRepository:
    Type: String
    # If there are functions with "Image" PackageType in your template,
    # Update the line below with image repository URL and add "--image-repository ${TESTING_IMAGE_REPOSITORY}" to
    # prod "sam package" and "sam deploy" commands in buildspec files (in pipeline/).
    Default: ""
  ProdRegion:
    Type: String
    Default: "ap-northeast-1"
  ProdStackName:
    Type: String
    Default: "sam-app"
  ProdPipelineExecutionRole:
    Type: String
    Default: "arn:aws:iam::00000000000:role/aws-sam-cli-managed-dev-pipe-PipelineExecutionRole-QO6TWPN0NBLJ"
  ProdCloudFormationExecutionExeRole:
    Type: String
    Default: "arn:aws:iam::00000000000:role/aws-sam-cli-managed-dev-p-CloudFormationExecutionR-7KYS28POR6G8"
  ProdArtifactBucket:
    Type: String
    Default: "bucket"
  ProdImageRepository:
    Type: String
    # If there are functions with "Image" PackageType in your template,
    # Update the line below with image repository URL and add "--image-repository ${PROD_IMAGE_REPOSITORY}" to
    # prod "sam package" and "sam deploy" commands in buildspec files (in pipeline/).
    Default: ""
  # CodeStarConnectionArn and FeatureGitBranch are required for pipelines for feature branches
  CodeStarConnectionArn:
    Type: String
    Default: ""
  FeatureGitBranch:
    Type: String
    Default: ""

Conditions:
  IsMainBranchPipeline: !Equals [!Ref FeatureGitBranch, ""]
  IsFeatureBranchPipeline: !Not [Condition: IsMainBranchPipeline]

Resources:
  #   ____
  # / ___|  ___  _   _ _ __ ___ ___
  # \___ \ / _ \| | | | '__/ __/ _ \
  #   ___) | (_) | |_| | | | (_|  __/
  # |____/ \___/ \__,_|_|  \___\___|
  CloudWatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        -
          PolicyName: cwe-pipeline-execution
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              -
                Effect: Allow
                Action: codepipeline:StartPipelineExecution
                Resource: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}"

  CloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - 'CodeCommit Repository State Change'
        resources:
          - !Sub "arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName}"
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - !If [IsFeatureBranchPipeline, !Ref FeatureGitBranch, !Ref MainGitBranch]
      Targets:
        - Arn: !Sub "arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}"
          RoleArn: !GetAtt CloudWatchEventRole.Arn
          Id: codepipeline-AppPipeline

  #  ____  _            _ _
  # |  _ \(_)_ __   ___| (_)_ __   ___
  # | |_) | | '_ \ / _ | | | '_ \ / _ \
  # |  __/| | |_) |  __| | | | | |  __/
  # |_|   |_| .__/ \___|_|_|_| |_|\___|
  #         |_|
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref PipelineArtifactsBucket
        Type: S3
      RoleArn: !GetAtt CodePipelineExecutionRole.Arn
      RestartExecutionOnUpdate: true
      Stages:
        - Name: Source
          Actions:
            - Name: SourceCodeRepo
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: "1"
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                PollForSourceChanges: false
                BranchName: !If [IsFeatureBranchPipeline, !Ref FeatureGitBranch, !Ref MainGitBranch]
              OutputArtifacts:
                - Name: SourceCodeAsZip
              RunOrder: 1
        - Name: UpdatePipeline
          Actions:
            - Name: CreateChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                RoleArn: !GetAtt PipelineStackCloudFormationExecutionRole.Arn
                StackName: !Ref AWS::StackName
                ChangeSetName: !Sub ${AWS::StackName}-ChangeSet
                TemplatePath: SourceCodeAsZip::codepipeline.yaml
                Capabilities: CAPABILITY_NAMED_IAM
                ParameterOverrides: !Sub |
                  {
                    "FeatureGitBranch": "${FeatureGitBranch}",
                    "CodeStarConnectionArn": "${CodeStarConnectionArn}"
                  }
              InputArtifacts:
                - Name: SourceCodeAsZip
              RunOrder: 1
            - Name: ExecuteChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                RoleArn: !GetAtt PipelineStackCloudFormationExecutionRole.Arn
                StackName: !Ref AWS::StackName
                ChangeSetName: !Sub ${AWS::StackName}-ChangeSet
              OutputArtifacts:
                - Name: !Sub ${AWS::StackName}ChangeSet
              RunOrder: 2

        # Uncomment and modify the following step for running the unit-tests
        # - Name: UnitTest
        #   Actions:
        #     - Name: UnitTest
        #       ActionTypeId:
        #         Category: Build
        #         Owner: AWS
        #         Provider: CodeBuild
        #         Version: "1"
        #       Configuration:
        #         ProjectName: !Ref CodeBuildProjectUnitTest
        #       InputArtifacts:
        #         - Name: SourceCodeAsZip

        - !If
          - IsFeatureBranchPipeline
          - Name: BuildAndDeployFeatureStack
            Actions:
              - Name: CodeBuild
                ActionTypeId:
                  Category: Build
                  Owner: AWS
                  Provider: CodeBuild
                  Version: "1"
                Configuration:
                  ProjectName: !Ref CodeBuildProjectBuildAndDeployFeature
                InputArtifacts:
                  - Name: SourceCodeAsZip
          - !Ref AWS::NoValue

        - !If
          - IsMainBranchPipeline
          - Name: BuildAndPackage
            Actions:
              - Name: CodeBuild
                ActionTypeId:
                  Category: Build
                  Owner: AWS
                  Provider: CodeBuild
                  Version: "1"
                Configuration:
                  ProjectName: !Ref CodeBuildProjectBuildAndPackage
                InputArtifacts:
                  - Name: SourceCodeAsZip
                OutputArtifacts:
                  - Name: BuildArtifactAsZip
          - !Ref AWS::NoValue

        - !If
          - IsMainBranchPipeline
          - Name: DeployTest
            Actions:
              - Name: DeployTest
                ActionTypeId:
                  Category: Build
                  Owner: AWS
                  Provider: CodeBuild
                  Version: "1"
                Configuration:
                  ProjectName: !Ref CodeBuildProjectDeploy
                  EnvironmentVariables: !Sub |
                    [
                      {"name": "ENV_TEMPLATE", "value": "packaged-test.yaml"},
                      {"name": "ENV_REGION", "value": "${TestingRegion}"},
                      {"name": "ENV_STACK_NAME", "value": "${TestingStackName}"},
                      {"name": "ENV_PIPELINE_EXECUTION_ROLE", "value": "${TestingPipelineExecutionRole}"},
                      {"name": "ENV_CLOUDFORMATION_EXECUTION_ROLE", "value": "${TestingCloudFormationExecutionRole}"},
                      {"name": "ENV_BUCKET", "value": "${TestingArtifactBucket}"},
                      {"name": "ENV_IMAGE_REPOSITORY", "value": "${TestingImageRepository}"}
                    ]
                InputArtifacts:
                  - Name: BuildArtifactAsZip
                RunOrder: 1
              # Uncomment the following step for running the integration tests
              # - Name: IntegrationTest
              #   ActionTypeId:
              #     Category: Build
              #     Owner: AWS
              #     Provider: CodeBuild
              #     Version: "1"
              #   Configuration:
              #     ProjectName: !Ref CodeBuildProjectIntegrationTest
              #   InputArtifacts:
              #     - Name: SourceCodeAsZip
              #   RunOrder: 2
          - !Ref AWS::NoValue

        - !If
          - IsMainBranchPipeline
          - Name: DeployProd
            Actions:
              # uncomment this to have a manual approval step before deployment to production
              # - Name: ManualApproval
              #   ActionTypeId:
              #    Category: Approval
              #    Owner: AWS
              #    Provider: Manual
              #    Version: "1"
              #   RunOrder: 1
              - Name: DeployProd
                ActionTypeId:
                  Category: Build
                  Owner: AWS
                  Provider: CodeBuild
                  Version: "1"
                RunOrder: 2 # keeping run order as 2 in case manual approval is enabled
                Configuration:
                  ProjectName: !Ref CodeBuildProjectDeploy
                  EnvironmentVariables: !Sub |
                    [
                      {"name": "ENV_TEMPLATE", "value": "packaged-prod.yaml"},
                      {"name": "ENV_REGION", "value": "${ProdRegion}"},
                      {"name": "ENV_STACK_NAME", "value": "${ProdStackName}"},
                      {"name": "ENV_PIPELINE_EXECUTION_ROLE", "value": "${ProdPipelineExecutionRole}"},
                      {"name": "ENV_CLOUDFORMATION_EXECUTION_ROLE", "value": "${ProdCloudFormationExecutionExeRole}"},
                      {"name": "ENV_BUCKET", "value": "${ProdArtifactBucket}"},
                      {"name": "ENV_IMAGE_REPOSITORY", "value": "${ProdImageRepository}"}
                    ]
                InputArtifacts:
                  - Name: BuildArtifactAsZip
          - !Ref AWS::NoValue

  PipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      VersioningConfiguration:
        Status: Enabled
      LoggingConfiguration:
        DestinationBucketName:
          !Ref PipelineArtifactsLoggingBucket
        LogFilePrefix: "artifacts-logs"
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  PipelineArtifactsBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref PipelineArtifactsBucket
      PolicyDocument:
        Statement:
          - Effect: "Deny"
            Action: "s3:*"
            Principal: "*"
            Resource:
              - !Sub  "${PipelineArtifactsBucket.Arn}/*"
              - !GetAtt PipelineArtifactsBucket.Arn
            Condition:
              Bool:
                aws:SecureTransport: false
          - Action:
              - s3:*
            Effect: Allow
            Resource:
              - !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactsBucket}
              - !Sub arn:${AWS::Partition}:s3:::${PipelineArtifactsBucket}/*
            Principal:
              AWS:
                - !GetAtt CodePipelineExecutionRole.Arn

  PipelineArtifactsLoggingBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      AccessControl: "LogDeliveryWrite"
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  PipelineArtifactsLoggingBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref PipelineArtifactsLoggingBucket
      PolicyDocument:
        Statement:
          - Effect: "Deny"
            Action: "s3:*"
            Principal: "*"
            Resource:
              - !Sub "${PipelineArtifactsLoggingBucket.Arn}/*"
              - !GetAtt PipelineArtifactsLoggingBucket.Arn
            Condition:
              Bool:
                aws:SecureTransport: false

  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: "*"
        - PolicyName: CodeCommitAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'codecommit:CancelUploadArchive'
                  - 'codecommit:GetBranch'
                  - 'codecommit:GetCommit'
                  - 'codecommit:GetUploadArchiveStatus'
                  - 'codecommit:UploadArchive'
                Resource:
                  - !Sub "arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName}"
        - PolicyName: CodePipelineCodeAndS3Bucket
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
                Effect: Allow
                Resource:
                  Fn::GetAtt:
                    - PipelineArtifactsBucket
                    - Arn
              - Action:
                  - "s3:GetObject"
                  - "s3:GetObjectVersion"
                  - "s3:PutObject"
                Effect: Allow
                Resource:
                  Fn::Sub: ${PipelineArtifactsBucket.Arn}/*

        - PolicyName: CodePipelineCodeBuildAndCloudformationAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "codebuild:StartBuild"
                  - "codebuild:BatchGetBuilds"
                Resource:
                  # Uncomment the line below to enable the unit-tests
                  # - !GetAtt CodeBuildProjectUnitTest.Arn
                  - !If
                    - IsFeatureBranchPipeline
                    - !GetAtt CodeBuildProjectBuildAndDeployFeature.Arn
                    - !Ref AWS::NoValue
                  - !If
                    - IsMainBranchPipeline
                    - !GetAtt CodeBuildProjectBuildAndPackage.Arn
                    - !Ref AWS::NoValue
                  # Uncomment the following step for running the integration tests
                  # - !If
                  #   - IsMainBranchPipeline
                  #   - !GetAtt CodeBuildProjectIntegrationTest.Arn
                  #   - !Ref AWS::NoValue
                  - !If
                    - IsMainBranchPipeline
                    - !GetAtt CodeBuildProjectDeploy.Arn
                    - !Ref AWS::NoValue
              - Effect: Allow
                Action:
                  - "cloudformation:CreateStack"
                  - "cloudformation:DescribeStacks"
                  - "cloudformation:DeleteStack"
                  - "cloudformation:UpdateStack"
                  - "cloudformation:CreateChangeSet"
                  - "cloudformation:ExecuteChangeSet"
                  - "cloudformation:DeleteChangeSet"
                  - "cloudformation:DescribeChangeSet"
                  - "cloudformation:SetStackPolicy"
                  - "cloudformation:SetStackPolicy"
                  - "cloudformation:ValidateTemplate"
                Resource:
                  - !Sub "arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*"

  # PipelineStackCloudFormationExecutionRole is used for the pipeline to self mutate
  PipelineStackCloudFormationExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          Action: "sts:AssumeRole"
          Effect: Allow
          Principal:
            Service: cloudformation.amazonaws.com
      Policies:
        - PolicyName: GrantCloudFormationFullAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: '*'
                Resource: '*'

  #   ____          _      ____        _ _     _
  #  / ___|___   __| | ___| __ ) _   _(_| | __| |
  # | |   / _ \ / _` |/ _ |  _ \| | | | | |/ _` |
  # | |__| (_) | (_| |  __| |_) | |_| | | | (_| |
  #  \____\___/ \__,_|\___|____/ \__,_|_|_|\__,_|
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Tags:
        - Key: Role
          Value: aws-sam-pipeline-codebuild-service-role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
      Policies:
        - PolicyName: CodeBuildLogs
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*"
        - PolicyName: CodeBuildArtifactsBucket
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "s3:GetObject"
                  - "s3:GetObjectVersion"
                  - "s3:PutObject"
                Resource:
                  - !Sub "arn:${AWS::Partition}:s3:::${PipelineArtifactsBucket}/*"
        - PolicyName: AssumeStagePipExecutionRoles
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action:
                  - sts:AssumeRole
                Effect: Allow
                Resource: "*"
                Condition:
                  StringEquals:
                    aws:ResourceTag/Role: pipeline-execution-role

  # Uncomment and modify the following step for running the unit-tests
  # CodeBuildProjectUnitTest:
  #   Type: AWS::CodeBuild::Project
  #   Properties:
  #     Artifacts:
  #       Type: CODEPIPELINE
  #     Environment:
  #       Type: LINUX_CONTAINER
  #       ComputeType: BUILD_GENERAL1_SMALL
  #       Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
  #     ServiceRole: !GetAtt CodeBuildServiceRole.Arn
  #     Source:
  #       Type: CODEPIPELINE
  #       BuildSpec: pipeline/buildspec_unit_test.yml

  CodeBuildProjectBuildAndDeployFeature:
    Condition: IsFeatureBranchPipeline
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: SAM_TEMPLATE
            Value: !Ref SamTemplate
          - Name: TESTING_REGION
            Value: !Ref TestingRegion
          - Name: TESTING_PIPELINE_EXECUTION_ROLE
            Value: !Ref TestingPipelineExecutionRole
          - Name: TESTING_CLOUDFORMATION_EXECUTION_ROLE
            Value: !Ref TestingCloudFormationExecutionRole
          - Name: TESTING_ARTIFACT_BUCKET
            Value: !Ref TestingArtifactBucket
          - Name: TESTING_IMAGE_REPOSITORY
            Value: !Ref TestingImageRepository
          - Name: FEATURE_BRANCH_NAME
            Value: !Ref FeatureGitBranch
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: pipeline/buildspec_feature.yml

  CodeBuildProjectBuildAndPackage:
    Condition: IsMainBranchPipeline
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
        PrivilegedMode: true
        EnvironmentVariables:
          - Name: SAM_TEMPLATE
            Value: !Ref SamTemplate
          - Name: TESTING_REGION
            Value: !Ref TestingRegion
          - Name: PROD_REGION
            Value: !Ref ProdRegion
          - Name: TESTING_PIPELINE_EXECUTION_ROLE
            Value: !Ref TestingPipelineExecutionRole
          - Name: PROD_PIPELINE_EXECUTION_ROLE
            Value: !Ref ProdPipelineExecutionRole
          - Name: TESTING_ARTIFACT_BUCKET
            Value: !Ref TestingArtifactBucket
          - Name: PROD_ARTIFACT_BUCKET
            Value: !Ref ProdArtifactBucket
          - Name: TESTING_IMAGE_REPOSITORY
            Value: !Ref TestingImageRepository
          - Name: PROD_IMAGE_REPOSITORY
            Value: !Ref ProdImageRepository
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: pipeline/buildspec_build_package.yml

  # Uncomment and modify the following step for running the integration tests
  # CodeBuildProjectIntegrationTest:
  #   Condition: IsMainBranchPipeline
  #   Type: AWS::CodeBuild::Project
  #   Properties:
  #     Artifacts:
  #       Type: CODEPIPELINE
  #     Environment:
  #       Type: LINUX_CONTAINER
  #       ComputeType: BUILD_GENERAL1_SMALL
  #       Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
  #     ServiceRole: !GetAtt CodeBuildServiceRole.Arn
  #     Source:
  #       Type: CODEPIPELINE
  #       BuildSpec: pipeline/buildspec_integration_test.yml

  CodeBuildProjectDeploy:
    Condition: IsMainBranchPipeline
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: pipeline/buildspec_deploy.yml

AWS CloudFormation Designer では以下のように表示されました。

このCFnに含まれているリソースは以下になります。

  • CodePipeline * 1
  • Cloudwatch Event * 1
  • S3 Bucket/BucketPolicy * 2
  • CodeBuildProject * 3
  • IAM Role * 4

デプロイパイプライン用のCodePipelineと、CodeCommitの変更をトリガーにCodePipelineを発火させるための CloudwatchEventが1つずつ作成されます。

S3 Bucketはsamのartifact保存用バケットと、そのバケットのロギング用のバケットが作成されています。

今回の設定では、CodeBuildは3つ作成されました。

  • featureブランチのビルド・デプロイ
  • mainブランチのビルド・パッケージ
  • mainブランチのデプロイ

unit testやintegretion test用のbuildspecのファイルは作成されていますが、CloudFormation上ではコメントアウトされています。

IAMロールは、4つ作成されます。

  • CloudwatchEvent
    • CodePipelineのスタート   - CodeCommitの変更を検知してCodePipelineを発火させる
  • CodePipeline
    • CodeCommitへのアクセス
    • Artifact Bucketの読み書き
    • CodeBuild実行
  • CodeBuild
    • Artifact Bucketの読み書き
    • CloudwatchLogsへのログ出力
    • sam deploy用のIAMロールの引き受け(assume-role)
  • Cloudformation     - CloudFormationのフルアクセス

    • sam deployでCloudformationを実行するIAMロールを指定

buildspec(pipeline配下)

pipeline
├── pipeline/buildspec_build_package.yml
├── pipeline/buildspec_deploy.yml
├── pipeline/buildspec_feature.yml # 今回は使用しない
├── pipeline/buildspec_integration_test.yml # デフォルトでは使用するCodeBuildがコメントアウトされている
└── pipeline/buildspec_unit_test.yml # デフォルトでは使用するCodeBuildがコメントアウトされている

今回は、デフォルトで無効化されている buildspec_integration_test.yml と buildspec_unit_test.yml の説明は省きます。 (testの部分は初期ではechoしているだけなので、ユーザー自身で編集する必要があります。)

同じく、buildspec_feature.yml も説明を省きます。 Featureブランチをトリガーにしたデプロイ設定する場合、今回はmainブランチだけをデプロイのトリガーにしています。

上記リンクのGithub上の雛形からも内容を確認できます。

ステージ「BuildAndPackage」では、sam buildとsam pacakgeコマンドを実行してデプロイに必要なパッケージの用意をしています。 本番とテストどちらも一つのビルドプロジェクトで作成しています。

buildspec_build_package.yml
version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.8
    commands:
      - pip install --upgrade pip
      - pip install --upgrade awscli aws-sam-cli
      # Enable docker https://docs.aws.amazon.com/codebuild/latest/userguide/sample-docker-custom-image.html
      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 &
      - timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
  build:
    commands:
      - sam build --use-container --template ${SAM_TEMPLATE}
      - . ./assume-role.sh ${TESTING_PIPELINE_EXECUTION_ROLE} test-package
      - sam package --s3-bucket ${TESTING_ARTIFACT_BUCKET}
                    --region ${TESTING_REGION}
                    --output-template-file packaged-test.yaml
      - . ./assume-role.sh ${PROD_PIPELINE_EXECUTION_ROLE} prod-package
      - sam package --s3-bucket ${PROD_ARTIFACT_BUCKET}
                    --region ${PROD_REGION}
                    --output-template-file packaged-prod.yaml
artifacts:
  files:
    - packaged-test.yaml
    - packaged-prod.yaml
    - assume-role.sh
    - pipeline/*

ステージ「DeployTest」「DeployProd」では、前のステージで作成したパッケージを使用してsam deployを実行しています。 CodePipeline側で環境変数を渡して、本番とテストの環境差異(スタック名や、template)を表現しています。

buildspec_deploy.yml
version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.8
    commands:
      - pip install --upgrade pip
      - pip install --upgrade awscli aws-sam-cli
  build:
    commands:
      - . ./assume-role.sh ${ENV_PIPELINE_EXECUTION_ROLE} deploy
      - sam deploy --stack-name ${ENV_STACK_NAME}
                    --template ${ENV_TEMPLATE}
                    --capabilities CAPABILITY_IAM
                    --region ${ENV_REGION}
                    --s3-bucket ${ENV_BUCKET}
                    --no-fail-on-empty-changeset
                    --role-arn ${ENV_CLOUDFORMATION_EXECUTION_ROLE}

assume-role.shとIAMロール

buildspec上でたびたび出てきた、assume-role.sh についてです。

その名の通り、ロールを引き受けて一時的に発行されるIAMアクセスキー等をセットするスクリプトです。

assume-role.sh

#!/bin/bash
ROLE=$1
SESSION_NAME=$2

# Unset AWS credentials stored in env so that every time this script runs,
# it will use the AWS CodeBuild service role to assume the target IAM roles.
unset AWS_SESSION_TOKEN
unset AWS_ACCESS_KEY_ID
unset AWS_SECRET_ACCESS_KEY

cred=$(aws sts assume-role --role-arn "$ROLE" \
                           --role-session-name "$SESSION_NAME" \
                           --query '[Credentials.AccessKeyId,Credentials.SecretAccessKey,Credentials.SessionToken]' \
                           --output text)

ACCESS_KEY_ID=$(echo "$cred" | awk '{ print $1 }')
export AWS_ACCESS_KEY_ID=$ACCESS_KEY_ID

SECRET_ACCESS_KEY=$(echo "$cred" | awk '{ print $2 }')
export AWS_SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY

SESSION_TOKEN=$(echo "$cred" | awk '{ print $3 }')
export AWS_SESSION_TOKEN=$SESSION_TOKEN

作成されるCodeBuildには、共通のIAMロールがついています。 このIAMロール自体には、CloudFormationを更新することができる権限はついていません。

sam deployにはCloudFormationを更新する権限が必要です。

ステージ:BuildAndPackage/DeployTest/DeployProdのCodeBuildで、それぞれCloudFormationとS3を操作可能なIAMロールを引き受けています。

CodeBuildServiceRole ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Condition": {
                "StringEquals": {
                    "aws:ResourceTag/Role": "pipeline-execution-role"
                }
            },
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}
PipelineExecutionRole 信頼関係
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::00000000:user/aws-sam-cli-managed-dev-pipeline-reso-PipelineUser-1H2HRIQ8A3JGV"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::00000000:root"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "aws:PrincipalTag/Role": "aws-sam-pipeline-codebuild-service-role"
                }
            }
        }
    ]
}

おわりに

SAM Pipelineで作成されるリソースについてでした。

ちなみに、sam pipeline init時のプロンプトをカスタマイズすることができるそうです。 利用頻度が多い場合は、この機能を使ってテンプレートを作成するのもいいかもしれません。

スターターパイプラインのカスタマイズ - AWS Serverless Application Model

この記事が、SAM Pipelineの雰囲気を掴むのに役立てば幸いです。

以上、AWS事業本部の佐藤(@chari7311)でした。