⛓️

AWSリソースのデプロイ(CircleCI + CloudFormation + cfn-lint + awspec)

2023/08/20に公開

概要

GitHubのtestブランチへのコミットを契機にcfn-lintでCFnテンプレートの静的解析を実施します。
testブランチでの静的解析に成功したら、testブランチの変更をmainブランチにプルリクエストし、mainブランチにマージされたらcfn-lint → CFnのデプロイ → awspecでの単体テストを実施します。

認証には以下の記事で準備したOIDCを使用します。
https://zenn.dev/jnxjez/articles/e270170893adcd

IAMロールには上記の記事から以下の権限を追加しています。

  • arn:aws:iam::aws:policy/AWSCloudFormationFullAccess
  • arn:aws:iam::aws:policy/AmazonVPCFullAccess

ファイル構成

% tree -aFI .git  
./
├── .circleci/
│   └── config.yml
├── CFn/
│   ├── Oidc.yml
│   └── Vpc.yml
└── awspec/
    ├── .rspec
    ├── Rakefile
    └── spec/
        ├── .gitignore
        ├── spec_helper.rb
        └── vpc_spec.rb

5 directories, 8 files

準備

CFnテンプレートの準備

cfn-lintとawspecの動作確認をしたいだけなので、VPCを作るだけのCFn/Vpc.ymlを用意しました。

Vpc.yml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  TestVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
       - Key: Name
         Value: TestVPC

.circleci/config.ymlの準備

config.ymlは長いので下に折りたたんでいます。

config.yml
config.yml
version: 2.1

orbs:
  aws-cli: circleci/aws-cli@4.0.0

jobs:
  cfnlint:
    docker:
      - image: cimg/python:3.11.4
    steps:
      - checkout
      - run:
          name: Install cfn-lint
          command: pip3 install cfn-lint==0.79.7
      - run:
          name: Execute cfn-lint
          command: cfn-lint ./CFn/Vpc.yml
          
  vpcstack:
    executor: aws-cli/default
    steps:
      - checkout
      - aws-cli/install
      - aws-cli/assume_role_with_web_identity:
          role_arn: $AWS_OIDC_ROLE_ARN
      - run: 
          name: Deploy VPC
          command: aws cloudformation deploy --template-file ./CFn/Vpc.yml --stack-name VpcStack  --no-fail-on-empty-changeset --region ap-northeast-1
  
  awspec:
    docker:
      - image: cimg/ruby:3.1.4
    steps:
      - checkout
      - aws-cli/install
      - aws-cli/assume_role_with_web_identity:
          role_arn: $AWS_OIDC_ROLE_ARN
      - run:
          name: Install awspec
          command: gem install awspec -v 1.29.2
      - run:
          name: execute awspec
          command: |
            export AWS_REGION=ap-northeast-1
            cd ./awspec
            rake spec

workflows:
  lint:
    jobs:
      - cfnlint:
          filters:
            branches:
              only:
                - test

  lint_deploy_test:
    jobs:
      - cfnlint:
          filters:
            branches:
              only:
                - main

      - vpcstack:
          requires:
            - cfnlint
          filters:
            branches:
              only:
                - main

      - awspec:
          requires:
            - vpcstack
          filters:
            branches:
              only:
                - main

awspecを実行するDockerイメージにはcimg/ruby:3.1.4を使用しています。
最初は3.2.2を使用しましたが、以下のエラーが発生したため3.1.4に変更しています。
Rubyの3.2からexistsがなくなったのが原因のようです。

NoMethodError:
undefined method `exists?' for File:Class

  awspec:
    docker:
      - image: cimg/ruby:3.1.4

awspec実行時に実行リージョンの指定が必要だったため、AWS_REGION環境変数を指定しています。

      - run:
         name: execute awspec
         command: |
           export AWS_REGION=ap-northeast-1
           cd ./awspec
           rake spec

awspecの準備

awspec/spec/spec_helper.rbの編集

awspec initコマンドで作成されたspec_helper.rbは認証情報が書かれたファイルを検索します。
OIDC認証を使用する場合はファイルが存在せずエラーとなるので、Awsecrets.loadをコメントアウトしました。

spec_helper.rb
require 'awspec'
#Awsecrets.load(secrets_path: File.expand_path('./secrets.yml', File.dirname(__FILE__)))

awspec/spec/vpc_spec.rbの作成

動作確認が目的なので、以下のコマンドでAWSに構築済みのVPCからvpc_spec.rbを作成しました。

$ awspec generate vpc vpc-052218b0ffdddd2c0

作成されたvpc_spec.rbはdescribe以下の行しか出力されないので、require 'spec_helper'をファイルの頭に追記しています。

vpc_spec.rb
require 'spec_helper'

describe vpc('TestVPC') do
  it { should exist }
  it { should be_available }
  its(:vpc_id) { should eq 'vpc-052218b0ffdddd2c0' }
  its(:cidr_block) { should eq '10.0.0.0/16' }
  it { should have_route_table('rtb-062c5ebc2e422c701') }
  it { should have_network_acl('acl-0da41742791c315c8') }
  it { should have_vpc_attribute('enableDnsHostnames') }
  it { should have_vpc_attribute('enableDnsSupport') }
end

動作確認

testブランチへのコミット

testブランチにコミットし、lintワークフローのcfnlintジョブが正常に終了することを確認できました。

mainブランチへのマージ

mainブランチでプルリクエストをマージし、lint_deploy_testワークフローが実行されました。

vpcstackジョブが25秒で終わっているのは、VPCを事前に構築済みのためです。

awspecも動作することを確認できました。
(実環境からテストを作成しているので成功するのは当然ですが。。。)

参考

https://github.com/k1LoW/awspec

Discussion