『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

troposphereで作ったAWS CloudFormationのテンプレートをCircleCIのジョブで検証する

はじめに

世の中にはとても便利なものがありふれており...

今回は CircleCIAWS CloudFormation (以下 CFn) をメインにタイトルのようなことをしていくやつです。

みんな大好き CircleCI

circleci.com

aws.amazon.com

troposhpere っていうのは CFn のテンプレートを Python を使って生成するツールです。

github.com

テンプレートを自分でゴリゴリ書けるぜ!!Foooooo!!な方は別に troposphere 使わんでもいいと思うし自分で書けばいいと思う。

環境

準備

AWS のアカウントは持っている前提です。また IAM CFn S3 へのアクセス権限が必要になります。ここでは詳細な権限は省略します。

CircleCI からアクセスするユーザを作る

ここは、ぶっちゃけパパっと Web から作ってもいいと思うんですけどどうせなら CFn で作りましょうよということで。

from awacs.aws import Action, Allow, PolicyDocument, Statement
from troposphere import Ref, Template
from troposphere.iam import PolicyType, User
from troposphere.validators import iam_user_name


circleci_user = User(
    'CircleCIUser', UserName=iam_user_name('circleci')
)

circleci_user_policy = PolicyType(
    'CircleCIUserPolicy', PolicyName='CircleCIUserPolicy', Users=[Ref(circleci_user)],
    PolicyDocument=PolicyDocument(
        Statement=[
            Statement(
                Effect=Allow, Action=[
                    Action(prefix='s3', action='Get*'),
                    Action(prefix='s3', action='PutObject'),
                    Action(prefix='s3', action='DeleteObject'),
                    Action(prefix='s3', action='ListBucket'),
                ],
                Resource=[
                    'arn:aws:s3:::{{ YOUR_BUCKET }}',
                    'arn:aws:s3:::{{ YOUR_BUCKET }}/*',
                ]
            ),
            Statement(
                Effect=Allow, Action=[
                    Action(prefix='cloudformation', action='Describe*'),
                    Action(prefix='cloudformation', action='EstimateTemplateCost'),
                    Action(prefix='cloudformation', action='Get*'),
                    Action(prefix='cloudformation', action='List*'),
                    Action(prefix='cloudformation', action='ValidateTemplate'),
                    Action(prefix='cloudformation', action='DetectStackDrift'),
                    Action(prefix='cloudformation', action='DetectStackResourceDrift'),
                ], Resource=['*']
            )
        ]
    )
)

t = Template()
t.add_resource(circleci_user)
t.add_resource(circleci_user_policy)

print(t.to_yaml(cleanup=True))

という感じで。

{{ YOUR_BUCKET }} は使用する S3 のバケットです。

ファイルに書き出す際は、1番上に

AWSTemplateFormatVersion: 2010-09-09

を入れておくといいでしょう。

本当はこれ自体をはじめから CFn の検証に当てたいんですけどまぁ別にそんな大したことないしとりあえず、 CFn のスタック生成に当てて作っちゃいましょう!

f:id:nanashinodonbee:20191021033553p:plain

続いて circleci ユーザのアクセスキーを生成して IAM から取得してメモしておきましょう!

CircleCI の設定

Web から Git の WebHook 等の設定をしています。
先程メモした、 circleci ユーザのアクセスキー等を CircleCI のプロジェクトの設定画面の Environment Variables に設定しておきましょう!

f:id:nanashinodonbee:20191021034137p:plain

.circleci/config.yml はこんな感じです。

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:3.7.4

    working_directory: ~/repo

    steps:
      - checkout

      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "poetry.lock" }}

      - run:
          name: Install dependencies
          command: |
            pip install -U pip==19.3
            pip install poetry
            poetry install

      - save_cache:
          paths:
            - /home/circleci/.cache/pypoetry/virtualenvs/project-py3.7
          key: v1-dependencies-{{ checksum "poetry.lock" }}

      - run:
          name: Export all configurations
          command: |
            poetry run python all_export.py -e  # ここですべての設定を書き出しています

      - run:
          name: Create aws credentials & config
          command: |
            mkdir -p ~/.aws
            echo "[default]" > ~/.aws/credentials
            echo "aws_access_key_id = ${AWS_ACCESS_KEY_ID}" >> ~/.aws/credentials
            echo "aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}" >> ~/.aws/credentials
            cat >> ~/.aws/config << EOF
            [profile default]
            region = ap-northeast-1
            output = json
            EOF

      - run:
          name: Upload to S3 & Validate outputs
          command: |
            poetry run aws s3 sync outputs/ s3://{{ YOUR_BUCKET }}/ --exclude "*" --include "*.yml" --cache-control "max-age=0"
            poetry run aws cloudformation validate-template --template-url https://{{ YOUR_BUCKET }}.s3-{{ YOUR_REGION }}.amazonaws.com/{{ FILE_1 }}.yml
            poetry run aws cloudformation validate-template --template-url https://{{ YOUR_BUCKET }}.s3-{{ YOUR_REGION }}.amazonaws.com/{{ FILE_2 }}.yml
            poetry run aws cloudformation validate-template --template-url https://{{ YOUR_BUCKET }}.s3-{{ YOUR_REGION }}.amazonaws.com/{{ FILE_3 }}.yml

      - store_artifacts:
          path: outputs

途中で、 ~/.aws/credentials~/.aws/config を作っているのですが、もしかしたら環境変数を設定していたら必要ないかも🤔

pypi.org

なぜここでこんな面倒臭いことをしてるかというと、 CircleCI のプロジェクト設定画面にある AWS Permissions に設定をするとどうやらジョブ(or Workflow ここは検証してない)に付き、1回しか反映されないっていう仕様らしいので生成した。

outputs/ に検証したいファイルを一旦書き出しを行って、 awscli によって *.yml に一致するものを同期するようにした。あとは CFn の検証にかけるだけ。おわおわり。

最後に

今、お仕事で 3,000 行近くの CFn テンプレートを生成していて、私にはそんなことをできる力はないのでググってたら troposphere というツールを発見した。
なので検証も同時にできると嬉しいなって思って CircleCI を使ってテストという感じで実装をしてみた。
ちなみに CFn の検証に失敗するとしっかりと終了コードが 0 以外になってくれるので CircleCI 上でもテスト失敗という感じで返してくれる。

f:id:nanashinodonbee:20191021035440p:plain

今度もしかしたら書くかもしれないけど CloudFront + S3 + AWS Lambda + Route53 による CFn のテンプレート生成のときに循環依存などの検証で今回の CircleCI での運用がありがたい感じに作用してくれた。

おまけ

私は、 Python を使ったのプロジェクトの開発は PyCharm を使う人間なのだが、 Run/Debug Configurationsexport upload validate という Configurations をそれぞれ作って PyCharm でも一応テストできるようにはしてある。
主に Python を書いている iOS アプリエンジニアでした。