CodePipeline: как ссылаться на вложенные стеки CloudFormation из GitHub в качестве источника

Наши шаблоны CloudFormation хранятся на GitHub. Внутри CodePipeline мы используем GitHub в качестве источника, но мы не можем ссылаться на вложенные стеки CloudFormation, если они не хранятся на S3.

Как мы можем ссылаться на вложенные стеки CloudFormation при использовании GitHub в качестве источника в CodePipeline?

Если это невозможно, как мы можем загрузить шаблоны CloudFormation из GitHub в S3 между этапом Source (из GitHub) и этапом развертывания в CodePipeline?


person Sergej Tissen    schedule 11.01.2017    source источник


Ответы (2)


Я могу придумать два подхода для ссылки на вложенные стеки CloudFormation из источника GitHub для развертывания CodePipeline:

1. Хук Git перед фиксацией

Добавьте pre-commit на стороне клиента Git hook, который запускает _ 2_ в вашем шаблоне, зафиксировав сгенерированный шаблон со ссылкой S3 в вашем репозитории GitHub вместе с изменениями в исходном шаблоне.

Преимущество этого подхода заключается в том, что вы можете использовать существующую логику перезаписи шаблонов в aws cloudformation package, и вам не нужно изменять существующую конфигурацию CodePipeline.

2. Этап лямбда-конвейера

Добавьте этап конвейера на основе лямбда-выражения, который извлекает указанный файл шаблона вложенного стека из исходного артефакта 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}"
person wjordan    schedule 12.01.2017
comment
у вас есть пример кода - я работаю над реализацией той же функции. - person Eric Nord; 12.01.2017
comment
@EricNord Добавил полный пример кода выше. S3UploadObject - это реализация лямбда-функции для этапа конвейера. Надеюсь это поможет! - person wjordan; 12.01.2017

Помимо решения с лямбда-стадией, простой подход - использовать 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

Обратите внимание, что вам просто нужно указать относительный путь к дочернему шаблону вместо absolution s3 uri.

Добавьте этап CodeBuild со следующим buildspecification.yaml

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 используйте «Создать набор изменений» и «Выполнить набор изменений», чтобы создать стек. Обратите внимание, что «Создать или обновить стек» не работает для «Transform: 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

Во втором документе показано, как развернуть лямбда-функцию, но, по сути, это то же самое, что и ссылка на вложенный стек.

person GuaGua    schedule 23.02.2017