Former2 CLIでAWSリソースからテンプレートファイルを出力してみる

# 概要

- AWSで既に作成された環境を`Former2 CLI`を使用してテンプレートファイルとして出力したい
  - `Former2`のGUIでやりたい方は[この記事が丁寧なのでオススメです](https://qiita.com/leomaro7/items/b95880f137c63881f164#former2)

## 説明

- `Former2`とは?
  - 既存のAWSリソースからIaCのテンプレートファイルを作成するサードパーティ製ツール
    - GCPでいうところの`Terraformer`
  - former2は`CloudFormation`だけでなく、`Terraform`や`AWS CDK`のテンプレートファイルとして出力できる
    - 以下対応しているテンプレートファイルの一覧
      - CloudFormation
      - Terraform
      - Troposphere
      - CDK (Cfn Primitives) - TypeScript, Python, Java, C#
      - CDK for Terraform - TypeScript
      - Pulumi - TypeScript
      - Diagram - embedded version of draw.io
- `Former2 CLI`とは?
  - その名の通り`Former2`を`CommandLine`で使用するためのライブラリ
    - 公式に **`Former2 CLI`はまだ実験的なものなので使用は自己責任で**、と書いてあるのでリスクが怖い場合使わなければいいと思います。(今のところ自分の環境は特に不具合なしです)
      - [former2/README.md at master · iann0036/former2](https://github.com/iann0036/former2/blob/master/cli/README.md#usage)
  - `Former2 CLI`が対応しているAWSのリソース一覧
    - [former2/README.md at master · iann0036/former2](https://github.com/iann0036/former2/blob/master/cli/README.md#service-names)
    - 大抵のサービスは対応しているようにみえる


## 内容

- AWS開発用に自作したDocker環境で行います
  - [I-s-23/aws-container-env: aws-cli development environment to Remote Container](https://github.com/I-s-23/aws-container-env)
  - AWSでIaCやCICDを行うために普段使いしている環境で以下の通り必要な物は揃っておりますのでよかったら使って下さい
    - aws-cli
    - sam-cli
    - cloudFormation(Linterや入力補完などの設定)
    - Former2 CLI
    - node.js
    - python3

- 作業手順
  - AWSアカウント作成＆最低限の設定を行う
    - [AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ - Qiita](https://qiita.com/tmknom/items/303db2d1d928db720888)
    - この記事を参考にGUIで行いました。
  - `Former2 CLI`が使用するIAMのユーザー作成＆権限付与
  - Docker環境で`Former2 CLI`実行

### `Former2 CLI`が使用するIAMのユーザー作成＆権限付与

- [Former2 CLI公式がReadOnlyAccessの権限を推奨している](https://github.com/iann0036/former2/blob/master/cli/README.md#usage)ので`ReadOnlyAccess`権限を付与
  - ポリシーで`ReadOnlyAccess`検索すればAWSのリソースすべてに対する`ReadOnlyAccess`を検索可能なのでそれを押下する(コンソールだと下の方に`ReadOnlyAccess`権限があります)
  - ![ReadOnlyAccess](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169887/d2b5beb2-f2dc-998b-1b6a-c15ecb0ac5b3.png)

 
### Docker環境で`Former2 CLI`実行

- `RemoteContainer` 環境を使用する
  - `GitHub`から`RemoteContainer` 環境のリポジトリをCloneする
    - [I-s-23/aws-container-env: aws-cli development environment to Remote Container](https://github.com/I-s-23/aws-container-env)

    ```bash
    git clone git@github.com:I-s-23/aws-container-env.git
    ```

  - `VSCode`でCloneしたリポジトリを開く
  - コマンドパレットを開く
    - Windows:`Ctrl + Shift + P`
    - MAC:`Cmd + Shift + P`
  - `Remote-Containers; Open Folderin Container`と入力する
  - `.devcontainer`ディレクトリの一つ上の階層を選択
  - これでRemoteContainer の環境が起動します
- `aws-cli`の認証を行う
  - コンテナ内で以下のコマンドを実行してAWSの認証を通します
    - コマンド押下後、`AWS Access Key ID`と`AWS Secret Access Key`の入力を求められるので、事前に作成したIAMユーザーのアクセスキーを入力します

  ```bash
  aws configure
  ```

- `Former2 CLI`でテンプレートファイルを出力する
  - `--output-cloudformation`でCloudFormationで出力するテンプレートファイル名を指定
  - `--output-terraform`はTerraformで出力する場合のオプション
  - [オプションについての詳細](https://github.com/iann0036/former2/blob/master/cli/README.md#options)
- 以下のコマンドは`Former2 CLI`が対応しているAWS内のすべてのリソースを出力するコマンドなので実行に時間がかかります

```bash
former2 generate \
  --output-cloudformation "cloudformation.yml" \
  --output-terraform "terraform.hcl" \
  --output-raw-data "debug.json" \
  --sort-output
```

- コマンドの実行結果です(アカウント IDなど秘匿情報は手動で隠しております)
  - [この記事](https://qiita.com/tmknom/items/303db2d1d928db720888)の内容しか行っていないので`CloudTrailTrail`の設定やIAMユーザー、Log出力のS3バケットくらいしかないですね

```yaml:cloudformation.yml
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
    Generator: "former2"
Description: ""
Resources:
    IAMAccessKey:
        Type: "AWS::IAM::AccessKey"
        Properties:
            Status: "Inactive"
            UserName: "AWSTESTUser"

    IAMAccessKey2:
        Type: "AWS::IAM::AccessKey"
        Properties:
            Status: "Active"
            UserName: "AWSTESTUser"

    CloudTrailTrail:
        Type: "AWS::CloudTrail::Trail"
        Properties:
            TrailName: "cloudtrail-management-events"
            S3BucketName: !Ref S3Bucket
            IncludeGlobalServiceEvents: true
            IsMultiRegionTrail: true
            EnableLogFileValidation: false
            IsLogging: true

    IAMUser:
        Type: "AWS::IAM::User"
        Properties:
            Path: "/"
            UserName: "AWSTESTUser"
            ManagedPolicyArns: 
              - "arn:aws:iam::aws:policy/ReadOnlyAccess"

    OpsWorksUserProfile:
        Type: "AWS::OpsWorks::UserProfile"
        Properties:
            AllowSelfManagement: false
            IamUserArn: "arn:aws:iam::************:user/AWSTESTUser"
            SshUsername: "AWSTESTUser"

    S3StorageLens:
        Type: "AWS::S3::StorageLens"
        Properties:
            StorageLensConfiguration: 
                AccountLevel: 
                    BucketLevel: {}
                Id: "default-account-dashboard"
                IsEnabled: true

    S3BucketPolicy:
        Type: "AWS::S3::BucketPolicy"
        Properties:
            Bucket: !Ref S3Bucket
            PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Sid: "AWSCloudTrailAclCheck20150319"
                    Effect: "Allow"
                    Principal: 
                        Service: "cloudtrail.amazonaws.com"
                    Action: "s3:GetBucketAcl"
                    Resource: !Sub "arn:aws:s3:::${S3Bucket}"
                  - 
                    Sid: "AWSCloudTrailWrite20150319"
                    Effect: "Allow"
                    Principal: 
                        Service: "cloudtrail.amazonaws.com"
                    Action: "s3:PutObject"
                    Resource: !Sub "arn:aws:s3:::${S3Bucket}/AWSLogs/************/*"
                    Condition: 
                        StringEquals: 
                            "s3:x-amz-acl": "bucket-owner-full-control"

    S3Bucket:
        Type: "AWS::S3::Bucket"
        Properties:
            BucketName: "aws-cloudtrail-logs-************-b134f084"

    ElastiCacheUser:
        Type: "AWS::ElastiCache::User"
        Properties:
            UserId: "default"
            UserName: "default"
            Engine: "redis"
            AccessString: "on ~* +@all"
            NoPasswordRequired: true
```

- Terraformの形式でもちゃんと出力されているようです

```hcl:terraform.hcl
# https://www.terraform.io/downloads.html

provider "aws" {
    region = "us-east-1"
}

resource "aws_iam_access_key" "IAMAccessKey" {
    status = "Inactive"
    user = "AWSTESTUser"
}

resource "aws_iam_access_key" "IAMAccessKey2" {
    status = "Active"
    user = "AWSTESTUser"
}

resource "aws_cloudtrail" "CloudTrailTrail" {
    name = "management-events"
    s3_bucket_name = "aws-cloudtrail-logs-***********-b134f084"
    include_global_service_events = true
    is_multi_region_trail = true
    enable_log_file_validation = false
    enable_logging = true
}

resource "aws_iam_user" "IAMUser" {
    path = "/"
    name = "AWSTESTUser"
    tags {}
}

resource "aws_opsworks_user_profile" "OpsWorksUserProfile" {
    allow_self_management = false
    user_arn = "arn:aws:iam::***********:user/AWSTESTUser"
    ssh_username = "AWSTESTUser"
}

resource "aws_s3_bucket_policy" "S3BucketPolicy" {
    bucket = "aws-cloudtrail-logs-***********-b134f084"
    policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AWSCloudTrailAclCheck20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:GetBucketAcl\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-***********-b134f084\"},{\"Sid\":\"AWSCloudTrailWrite20150319\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudtrail.amazonaws.com\"},\"Action\":\"s3:PutObject\",\"Resource\":\"arn:aws:s3:::aws-cloudtrail-logs-***********-b134f084/AWSLogs/***********/*\",\"Condition\":{\"StringEquals\":{\"s3:x-amz-acl\":\"bucket-owner-full-control\"}}}]}"
}

resource "aws_s3_bucket" "S3Bucket" {
    bucket = "aws-cloudtrail-logs-***********-b134f084"
}
```

- もちろん特定のサービスを指定して出力することも可能です
  - `--services`で対象にしたいAWSのリソースを指定
    - [former2/README.md at master · iann0036/former2](https://github.com/iann0036/former2/blob/master/cli/README.md#service-names)
  - `--exclude-services`で対象外にしたいAWSのリソースを指定
- `EC2`を対象としてテンプレートファイルを出力する場合の例

```bash
former2 generate \
  --output-cloudformation "cloudformation.yml" \
  --services "EC2" \
  --sort-output
```

- EC2関連のリソースのみがテンプレートファイルとして出力されているのがわかります

```yaml:cloudformation.yml
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
    Generator: "former2"
Description: ""
Resources:
    EC2NetworkInterface:
        Type: "AWS::EC2::NetworkInterface"
        Properties:
            Description: ""
            PrivateIpAddress: !GetAtt EC2Instance.PrivateIp
            PrivateIpAddresses: 
              - 
                PrivateIpAddress: !GetAtt EC2Instance.PrivateIp
                Primary: true
            SubnetId: "subnet-836082d9"
            SourceDestCheck: true
            GroupSet: 
              - "sg-4b844008"

    EC2NetworkInterfaceAttachment:
        Type: "AWS::EC2::NetworkInterfaceAttachment"
        Properties:
            NetworkInterfaceId: "eni-0686f848cd1ac4064"
            DeviceIndex: 0
            InstanceId: !Ref EC2Instance
            DeleteOnTermination: true

    EC2Instance:
        Type: "AWS::EC2::Instance"
        Properties:
            ImageId: "ami-00f045aed21a55240"
            InstanceType: "t2.micro"
            AvailabilityZone: "ap-northeast-1c"
            Tenancy: "default"
            SubnetId: "subnet-836082d9"
            EbsOptimized: false
            SecurityGroupIds: 
              - "sg-4b844008"
            SourceDestCheck: true
            BlockDeviceMappings: 
              - 
                DeviceName: "/dev/xvda"
                Ebs: 
                    Encrypted: false
                    VolumeSize: 8
                    SnapshotId: "snap-0ac31d416f3b41c6c"
                    VolumeType: "gp2"
                    DeleteOnTermination: true
            HibernationOptions: 
                Configured: false
            EnclaveOptions: 
                Enabled: false

    EC2Volume:
        Type: "AWS::EC2::Volume"
        Properties:
            AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
            Encrypted: false
            Size: 8
            VolumeType: "gp2"
            SnapshotId: "snap-0ac31d416f3b41c6c"
            MultiAttachEnabled: false

    EC2VolumeAttachment:
        Type: "AWS::EC2::VolumeAttachment"
        Properties:
            VolumeId: !Ref EC2Volume
            InstanceId: !Ref EC2Instance
            Device: "/dev/xvda"
```

## 所感

- AWSを使っているけどIaCはしていない、というプロジェクトなどで特にオススメのツールです
- AWS公式のツールではないらしいが、OSSなので最高
- 多種多様なAWSのリソースに対応していて便利
- テンプレートファイルの出力形式に関して`CloudFormation`や`Terraform`だけでなく`AWS CDK`とかにも対応しているのが素晴らしい
- 初めて使うAWSのリソースとかをとりあえずGUIで設定後、`Former2`でIaCにするのが便利な使い方と思った。
- 自分は`CloudFormation`しかわかっていないので同じリソースで`Terraform`のテンプレートファイルとかと比較すれば`Terraform`の勉強にもよさそうだと思いました

## 余談

### Former2 CLIのインストールに関して

- 本記事のようにRemoteContainer のDocker環境でなくとも`npm`か`Former2`公式のコンテナを使用することも可能
  - どちらの場合でもAWSの認証を通さないと使えないので`aws-cli`のインストール推奨
  - `~/.aws/credentials`にAWSの認証情報があればいいので、この部分をコンテナ内にマウントする方法もある

- `npm`でのインストール方法
  - `npm`の場合ローカル環境に`Node.js`が必要

```bash
npm install -g former2
```

- 公式のDockerを使用
  - [公式のコンテナの使用方法](https://github.com/iann0036/former2/blob/master/cli/README.md#usage)

```bash
docker build https://github.com/iann0036/former2.git#master:cli -t iann0036/former2:latest
```

### 経緯

- 今回は社内や社外から`Former2`が使えそうか知りたいという意見がきたのでQiitaとして投稿しました
  - あと `Former2`の使い方説明の記事は何個かあったが`Former2 CLI`の記事が見つからなかったというのもあります
- ちなみに弊社で`Former2`の需要が発生した経緯は以下の感じです
  - 協力会社と開発したAWS環境で業務効率化のためCICDを行いたいという依頼がくる
  - 協力会社も弊社もGUIで環境設定や変更を行っていたらしく、`CloudFormation`や`aws-cli`の使い方がわからないという返答
  - 誰もCLIでの操作方法を知らないのであれば`Terraformer`のように既存のAWSリソースからIaC化すればいいと考えて調査する
  - [Former2がよさそうと気付いて試す](https://qiita.com/leomaro7/items/b95880f137c63881f164)

## 参考資料

- [AWS CloudFormation を開発・テストする際に役立つツール纏め - Qiita](https://qiita.com/leomaro7/items/b95880f137c63881f164)
- [former2/README.md at master · iann0036/former2](https://github.com/iann0036/former2/blob/master/cli/README.md)