Infrastracture ComposerからGUIでCI/CDパイプラインを作成する

# はじめに
CloudFormationを使ってCI/CDパイプラインを作成する機会がありましたが、正直なところコードを書くのが少し面倒でした。  
そこで、「どうせなら効率よく作成したい！」という思いから、**Infrastructure Composer**を試してみることにしました。  
本記事では、その動機や背景、実際に試してみた内容を紹介します。

# Infrastructure Composerとは
**Infrastructure Composer**は、AWSが提供するIaCツールである**CloudFormation**の一機能です。  
コードを書く代わりに、**GUI上でリソースを視覚的に設計**し、その結果をCloudFormationテンプレートとしてエクスポートできる便利なツールです。

### 特徴
- **コード不要**: コードを書くのが苦手な方でもGUI操作だけでテンプレートを作成可能。  
- **視覚的な設計**: リソース間の依存関係や全体構成を視覚的に確認しながら作業が進められる。  
- **エクスポート機能**: 完成した構成をそのままCloudFormationテンプレート（YAML/JSON形式）としてダウンロード可能。

### こんな人におすすめ
- コードよりも直感的な操作を重視したい人。  
- AWSリソースの設計を構成図としてイメージしながら進めたい人。  
- CloudFormationに慣れていない初心者。  

### 注意点
- **対応リソースに制限あり**: 一部のAWSリソースはまだInfrastructure Composerでサポートされていません。  
- **複雑な構成には非推奨**: GUIでの操作が煩雑になる場合や、高度なカスタマイズが必要な場合は、手動でテンプレートを作成した方が効率的です。

Infrastructure Composerを活用することで、複雑なコードを書くことなく、シンプルなAWS環境をすばやく構築することが可能です。本記事では、このツールを使った実際の操作手順や注意点について解説します。

# 前提条件
今回はTerraformをデプロイするためのCI/CD基盤を構築します。

本題から逸れるため、あまり深くは記載しませんが、
TerraformをAWS環境上で実行する際には、状態管理ファイルであるtfstateを保管するS3バケットと、それの書き込みを管理するDynamoDBを配置するのがベストプラクティスです。

https://developer.hashicorp.com/terraform/language/backend/s3

この背景を踏まえつつ、Infrastructure Composerを活用して効率的にテンプレートを作成する方法をお伝えします。

---


# やってみる

## 事前準備
それでは今回必要なリソースの一覧を列挙します。
 - Codepipeline
 - CodeBuild(plan/静的テスト用)
 - CodeBuild(apply用)
 - S3(アーティファクト用)
 - S3(tfstate保管用)
 - S3(Terraformコード格納用)
 - DynamoDB(tfstateロック用)

構成図はこのようになります。（draw.ioで作成）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/c5dd3c60-f46a-78ac-4442-27271710159b.png)

この構成図を元にInfrastructure Conposerにリソースを配置していきます。

## リソースの配置
まずリソースを配置していきます。リソースの配置はサイドバーからドラック＆ドロップでできます。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/26555371-6331-8e60-6212-0363b6ac2280.png)

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/1ff57fd0-6886-2cfc-1fc6-97f7fd9f9153.png)

これを必要なリソース分繰り返していきます。
最終的には以下のような形になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/7a825528-272e-ef45-d2e4-1e7376b979df.png)


## リソースのパラメータを入力
では各リソースのパラメータを入力していきます。
パラメータはリソースをクリックすると、右側にサイドバーが出現し、設定できるようになります。
![画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/7d24ba1f-4e32-8776-088a-6d2ec82c25ce.png)

ここからはCloudFormaionのドキュメントを参照し、パラメータを入力していきます。

またほとんどのリソースはIaCとして記載する必要がありますが、Enhanced componentsと呼ばれるコンポーネントは、パラメータを入力するだけで自動でIaCコードが生成されるみたいです。

![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/b0b2f716-554a-d338-4707-9bff92df1a65.png)

現時点でEnhanced componentsは以下のリソースをサポートしています。
 - APIGateway
 - Cognito UserPool
 - Cognito UserPoolClient
 - DynamoDB Table
 - EventBridge EventRole
 - Kinesis Stream
 - Lambda Function
 - Lambda Layer
 - RDS Database
 - S3 Bucket
 - SNS Topic
 - SQS Queue
 - StepFunctions State machine

主にサーバレス系のサービスが網羅されているような印象を受けました。

## CloudFormationコードの出力
最終的にGUI上ではこのようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/07bb3e34-4a30-b9fb-6779-cee219e7b581.png)

CloudFormationコードは左上のTempleteから確認できます。
![画像6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2625964/9f30cc22-a54f-34fe-29cf-e2ee77144d04.png)


作成したテンプレートは以下のようになります。
<details><summary>テンプレート</summary>

```templete.yml
Resources:
  Project:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: myProjectName
      Description: A description about my project
      ServiceRole: !GetAtt Role.Arn
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:1.0
      Source:
        Type: CODEPIPELINE
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: '1'
                Provider: S3
              OutputArtifacts:
                - Name: SourceOutput
              Configuration:
                S3Bucket: !GetAtt artifactFileBucket.bucket
                S3ObjectKey: SourceArtifact
              RunOrder: 1
        - Name: Plan
          Actions:
            - Name: PlanAction
              InputArtifacts:
                - Name: SourceOutput
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: '1'
                Provider: CodeBuild
              RunOrder: 1
              OutputArtifacts:
                - Name: PlanOutput
        - Name: Apply
          Actions:
            - Name: ApplyAction
              InputArtifacts:
                - Name: PlanOutput
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: '1'
                Provider: CodeBuild
              RunOrder: 1
      RoleArn: !GetAtt Role3.Arn
  Project2:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: myProjectName
      Description: A description about my project
      ServiceRole: !GetAtt Role2.Arn
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:1.0
      Source:
        Type: CODEPIPELINE
  Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - sts:AssumeRole
  Policy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: build-plan-policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - '*'
            Resource: '*'
  Role2:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - sts:AssumeRole
  Role3:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action:
              - sts:AssumeRole
  Policy2:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: build-apply-policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - '*'
            Resource: '*'
  Policy3:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: pipeline-policy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - '*'
            Resource: '*'
  stateRockDynamoDB:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: RockID
          AttributeType: S
      BillingMode: PAY_PER_REQUEST
      KeySchema:
        - AttributeName: RockID
          KeyType: HASH
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES
  stateFileBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${AWS::StackName}-stateFileBucket-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: alias/aws/s3
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  stateFileBucketBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref stateFileBucket
      PolicyDocument:
        Id: RequireEncryptionInTransit
        Version: '2012-10-17'
        Statement:
          - Principal: '*'
            Action: '*'
            Effect: Deny
            Resource:
              - !GetAtt stateFileBucket.Arn
              - !Sub ${stateFileBucket.Arn}/*
            Condition:
              Bool:
                aws:SecureTransport: 'false'
  codeFileBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${AWS::StackName}-codeFileBucket-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: alias/aws/s3
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  codeFileBucketBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref codeFileBucket
      PolicyDocument:
        Id: RequireEncryptionInTransit
        Version: '2012-10-17'
        Statement:
          - Principal: '*'
            Action: '*'
            Effect: Deny
            Resource:
              - !GetAtt codeFileBucket.Arn
              - !Sub ${codeFileBucket.Arn}/*
            Condition:
              Bool:
                aws:SecureTransport: 'false'
  artifactFileBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${AWS::StackName}-artifactFileBucket-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: alias/aws/s3
      PublicAccessBlockConfiguration:
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  artifactFileBucketBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref artifactFileBucket
      PolicyDocument:
        Id: RequireEncryptionInTransit
        Version: '2012-10-17'
        Statement:
          - Principal: '*'
            Action: '*'
            Effect: Deny
            Resource:
              - !GetAtt artifactFileBucket.Arn
              - !Sub ${artifactFileBucket.Arn}/*
            Condition:
              Bool:
                aws:SecureTransport: 'false'
Transform: AWS::Serverless-2016-10-31
```
</details>

## 感想

正直まだ使いこなせているわけではないですが、良かった点と微妙な点を挙げてみます。

:::note info
良かった点
 - 構成図からCloudFormationコードを作成できる開発者体験
 - 開発環境構築なしで、CloudFormationのLinterのような機能が使える
 - CloudFormationコードから構成図への展開が出来る
 - かなり多くのリソースをサポートしている
:::

:::note alert
微妙だった点
 - ほとんどのリソースは結局CloudFormationコードを書かなければならない
 - 自動で整列したときに、縦並びになってしまい見づらい
:::

私としては、ChatGPT等の生成AIを活用しながらコードを書いていく方が、より早く開発できると感じました。
また、CICDを構築するというケースだとうまく活用できませんでした。EnhancedComponentsで定義されているサーバレス系のリソースの場合、上手く活用できる可能性がありそうです。

## まとめ
GUIからCloudFormationコードを出力できるInfrastracture Composerを使ってみました。
面白い開発者体験だと感じましたが、結局CloudFormationコードを書かなければならないというところは変わっていないため、活用が難しそうです。
ぜひ皆さんも活用してみてください。