5

我们的 CloudFormation 模板存储在 GitHub 中。在 CodePipeline 中,我们使用 GitHub 作为源,但当嵌套 CloudFormation 堆栈未存储在 S3 上时,我们无法引用它们。

在 CodePipeline 中使用 GitHub 作为源时,我们如何引用 CloudFormation 嵌套堆栈?

如果这不可能,我们如何在 CodePipeline 中的 Source Stage(来自 GitHub)和 Deploy Stage 之间将 CloudFormation 模板从 GitHub 上传到 S3?

4

2 回答 2

7

我可以想到两种方法来引用来自 GitHub 源代码的嵌套 CloudFormation Stacks 以进行 CodePipeline 部署:

1. 预提交 Git 钩子

添加一个在您的模板上运行的pre-commit客户端Git 挂钩aws cloudformation package,提交一个生成的模板,其中包含对您的 GitHub 存储库的 S3 引用以及对源模板的更改。

这种方法的好处是您可以利用现有的模板重写逻辑aws cloudformation package,而不必修改现有的 CodePipeline 配置。

2. Lambda流水线阶段

添加一个基于 Lambda 的管道阶段,该阶段从 GitHub 源工件中提取指定的嵌套堆栈模板文件,并将其上传到父堆栈模板中引用的 S3 中的指定位置。

这种方法的好处是管道将保持完全独立,无需提交者需要任何额外的预处理步骤。

我已经发布了一个完整的参考示例实现wjordan/aws-codepipeline-nested-stack

启动堆栈

AWSTemplateFormatVersion: 2010-09-09
Description: Infrastructure Continuous Delivery with CodePipeline and CloudFormation, with a project containing a nested stack.
Parameters:
  ArtifactBucket:
    Type: String
    Description: Name of existing S3 bucket for storing pipeline artifacts
  StackFilename:
    Type: String
    Default: cfn-template.yml
    Description: CloudFormation stack template filename in the Git repo
  GitHubOwner:
    Type: String
    Description: GitHub repository owner
  GitHubRepo:
    Type: String
    Default: aws-codepipeline-nested-stack
    Description: GitHub repository name
  GitHubBranch:
    Type: String
    Default: master
    Description: GitHub repository branch
  GitHubToken:
    Type: String
    Description: GitHub repository OAuth token
  NestedStackFilename:
    Type: String
    Description: GitHub filename (and S3 Object Key) for nested stack template.
    Default: nested.yml
Resources:
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt [PipelineRole, Arn]
      ArtifactStore: 
        Type: S3
        Location: !Ref ArtifactBucket
      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
            OAuthToken: !Ref GitHubToken
          OutputArtifacts: [Name: Template]
          RunOrder: 1
      - Name: Deploy
        Actions:
        - Name: S3Upload
          ActionTypeId:
            Category: Invoke
            Owner: AWS
            Provider: Lambda
            Version: 1
          InputArtifacts: [Name: Template]
          Configuration:
            FunctionName: !Ref S3UploadObject
            UserParameters: !Ref NestedStackFilename
          RunOrder: 1
        - Name: Deploy
          RunOrder: 2
          ActionTypeId:
            Category: Deploy
            Owner: AWS
            Version: 1
            Provider: CloudFormation
          InputArtifacts: [Name: Template]
          Configuration:
            ActionMode: REPLACE_ON_FAILURE
            RoleArn: !GetAtt [CFNRole, Arn]
            StackName: !Ref AWS::StackName
            TemplatePath: !Sub "Template::${StackFilename}"
            Capabilities: CAPABILITY_IAM
            ParameterOverrides: !Sub |
              {
                "ArtifactBucket": "${ArtifactBucket}",
                "StackFilename": "${StackFilename}",
                "GitHubOwner": "${GitHubOwner}",
                "GitHubRepo": "${GitHubRepo}",
                "GitHubBranch": "${GitHubBranch}",
                "GitHubToken": "${GitHubToken}",
                "NestedStackFilename": "${NestedStackFilename}"
              }
  CFNRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action: ['sts:AssumeRole']
          Effect: Allow
          Principal: {Service: [cloudformation.amazonaws.com]}
        Version: '2012-10-17'
      Path: /
      ManagedPolicyArns:
      # TODO grant least privilege to only allow managing your CloudFormation stack resources
      - "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'
      Path: /
      Policies:
        - PolicyName: CodePipelineAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Action:
                - 's3:*'
                - 'cloudformation:*'
                - 'iam:PassRole'
                - 'lambda:*'
                Effect: Allow
                Resource: '*'
  Dummy:
    Type: AWS::CloudFormation::WaitConditionHandle
  NestedStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://s3.amazonaws.com/${ArtifactBucket}/${NestedStackFilename}"
  S3UploadObject:
    Type: AWS::Lambda::Function
    Properties:
      Description: Extracts and uploads the specified InputArtifact file to S3.
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var exec = require('child_process').exec;
          var AWS = require('aws-sdk');
          var codePipeline = new AWS.CodePipeline();
          exports.handler = function(event, context, callback) {
            var job = event["CodePipeline.job"];
            var s3Download = new AWS.S3({
                credentials: job.data.artifactCredentials,
                signatureVersion: 'v4'
            });
            var s3Upload = new AWS.S3({
                signatureVersion: 'v4'
            });
            var jobId = job.id;
            function respond(e) {
              var params = {jobId: jobId};
              if (e) {
                params['failureDetails'] = {
                  message: JSON.stringify(e),
                  type: 'JobFailed',
                  externalExecutionId: context.invokeid
                };
                codePipeline.putJobFailureResult(params, (err, data) => callback(e));
              } else {
                codePipeline.putJobSuccessResult(params, (err, data) => callback(e));
              }
            }
            var filename = job.data.actionConfiguration.configuration.UserParameters;
            var location = job.data.inputArtifacts[0].location.s3Location;
            var bucket = location.bucketName;
            var key = location.objectKey;
            var tmpFile = '/tmp/file.zip';
            s3Download.getObject({Bucket: bucket, Key: key})
              .createReadStream()
              .pipe(require('fs').createWriteStream(tmpFile))
              .on('finish', ()=>{
                exec(`unzip -p ${!tmpFile} ${!filename}`, (err, stdout)=>{
                if (err) { respond(err); }
                s3Upload.putObject({Bucket: bucket, Key: filename, Body: stdout}, (err, data) => respond(err));
              });
            });
          };
      Timeout: 30
      Runtime: nodejs4.3
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      - "arn:aws:iam::aws:policy/AWSCodePipelineCustomActionAccess"
      Policies:
      - PolicyName: S3Policy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - 's3:PutObject'
                - 's3:PutObjectAcl'
              Resource: !Sub "arn:aws:s3:::${ArtifactBucket}/${NestedStackFilename}"
于 2017-01-12T00:53:35.843 回答
7

除了具有 lambda 阶段的解决方案外,一种简单的方法是使用 CodeBuild 和 AWS SAM。

在主 CloudFormation 模板(我们称之为 main.yaml)中,使用“Transform: AWS::Serverless-2016-10-31”

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  NestedTemplate:
    AWS::CloudFormation::Stack
    Properties:
      TemplateUri: ./nested-template.yaml

请注意,您只需要放置子模板的相对路径而不是绝对的 s3 uri。

使用以下 buildspecification.yaml 添加 CodeBuild 阶段

version: 0.1
phases:
  build:
    commands:
      aws cloudformation package --template-file main.yaml --output-template-file transformed_main.yaml --s3-bucket my_bucket

artifacts:
  type: zip
  files:
    - transformed_main.yaml

构建命令“aws cloudformation package”会将nested-template.yaml 上传到s3 存储桶“my_bucket”,并将绝对s3 uri 注入转换后的模板。

在 CloudFormation 部署阶段,使用“创建更改集”和“执行更改集”来创建堆栈。请注意,“创建或更新堆栈”不适用于“转换:AWS::Serverless-2016-10-31”。

以下是您可能会发现有用的文档:

  1. http://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
  2. http://docs.aws.amazon.com/lambda/latest/dg/automating-deployment.html

第二个文档展示了如何部署 lambda 函数,但引用嵌套堆栈本质上是相同的。

于 2017-02-23T00:47:02.873 回答