AWSのCloudFrontを構築するスクリプトをRubyで作った

# 目的
CloudFrontでコンテンツを提供したいです。
ですが毎回コンソールで立ち上げるのはめんどうです。ある程度の雛形を作成しておいて、一部を書き換えることで簡単にデプロイするスクリプトを構築しました。
CloudFrontを構築したのちに、Route53にドメインを設定しています。

# コード
## CloudFront
まずベースとなるJSONを構築します。
以下のコマンドでゼロから雛形を生成できます。
```
aws cloudfront create-distribution-with-tags --generate-cli-skeleton
```
または既にあるCloudFrontから取得します。
```
aws cloudfront get-distribution --id xxxxxx
```
以下のようなJSONを用意します。
```json:cloud_front_base.json
{
    "Tags": {
        "Items": [
            {
                "Key": "Name",
                "Value": "test.example.com"
            },
            {
                "Key": "Service",
                "Value": "my_service"
            }
        ]
    },
    "DistributionConfig": {
        "CallerReference": "00000000-0000-0000-0000-000000000000",
        "Aliases": {
            "Quantity": 1,
            "Items": [
                "test.example.com"
            ]
        },
        "DefaultRootObject": "",
        "Origins": {
            "Quantity": 1,
            "Items": [
                {
                    "Id": "contents",
                    "DomainName": "my-bucket.s3.ap-northeast-1.amazonaws.com",
                    "OriginPath": "/contents",
                    "CustomHeaders": {
                        "Quantity": 0
                    },
                    "S3OriginConfig": {
                        "OriginAccessIdentity": "origin-access-identity/cloudfront/XXXXXXXXXX"
                    },
                    "ConnectionAttempts": 3,
                    "ConnectionTimeout": 10,
                    "OriginShield": {
                        "Enabled": false
                    }
                }
            ]
        },
        "OriginGroups": {
            "Quantity": 0
        },
        "DefaultCacheBehavior": {
            "TargetOriginId": "contents",
            "TrustedSigners": {
                "Enabled": false,
                "Quantity": 0
            },
            "TrustedKeyGroups": {
                "Enabled": false,
                "Quantity": 0
            },
            "ViewerProtocolPolicy": "https-only",
            "AllowedMethods": {
                "Quantity": 3,
                "Items": [
                    "HEAD",
                    "GET",
                    "OPTIONS"
                ],
                "CachedMethods": {
                    "Quantity": 2,
                    "Items": [
                        "HEAD",
                        "GET"
                    ]
                }
            },
            "SmoothStreaming": false,
            "Compress": true,
            "LambdaFunctionAssociations": {
                "Quantity": 0,
            },
            "FunctionAssociations": {
                "Quantity": 0
            },
            "FieldLevelEncryptionId": "",
            "CachePolicyId": "00000000-0000-0000-0000-000000000000"
        },
        "CustomErrorResponses": {
            "Quantity": 0
        },
        "Comment": "",
        "Logging": {
            "Enabled": false,
            "IncludeCookies": false,
            "Bucket": "",
            "Prefix": ""
        },
        "PriceClass": "PriceClass_200",
        "Enabled": true,
        "ViewerCertificate": {
            "CloudFrontDefaultCertificate": false,
            "ACMCertificateArn": "arn:aws:acm:us-east-1:9999999999:certificate/00000000-0000-0000-0000-000000000000",
            "SSLSupportMethod": "sni-only",
            "MinimumProtocolVersion": "TLSv1.2_2021",
            "Certificate": "arn:aws:acm:us-east-1:9999999999:certificate/00000000-0000-0000-0000-000000000000",
            "CertificateSource": "acm"
        },
        "Restrictions": {
            "GeoRestriction": {
                "RestrictionType": "none",
                "Quantity": 0
            }
        },
        "WebACLId": "",
        "HttpVersion": "http2",
        "IsIPV6Enabled": true
    }
}
```

RubyプログラムでJSONで変更したいところを修正します。

```ruby:cf.rb
require 'securerandom'
require 'json'

def read_json(path)
  File.open(path) do |j|
    hash = JSON.load(j)
  end
end

base = read_json("#{__dir__}/cloud_front_base.json")
config = base['DistributionConfig']
fqdn = ARGV[0]
comment = ARGV[1]
s3_path = ARGV[2]

config['CallerReference'] = SecureRandom.uuid
config['Comment'] = comment 
config['Aliases']['Items'][0] = fqdn 
config['Origins']['Items'][0]['OriginPath'] = s3_path
base['Tags']['Items'][0]['Value'] = fqdn 

puts JSON.pretty_generate(base)
```

## Route53
雛形は以下の通りです。
```json:route53_base.json
{
  "Comment": "",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "cf.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "example.com",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}
```

こちらもRubyプログラムでJSONで変更したいところを修正します。

```ruby:r53.rb
require 'securerandom'
require 'json'

def read_json(path)
  File.open(path) do |j|
    hash = JSON.load(j)
  end
end

base = read_json("#{__dir__}/route53_base.json")
fqdn = ARGV[0]
dns_name = ARGV[1]

base['Comment'] = fqdn
rrs = base['Changes'][0]['ResourceRecordSet']
rrs['Name'] = fqdn 
rrs['AliasTarget']['DNSName'] = dns_name

puts JSON.pretty_generate(base)
```
## シェルスクリプト
awsコマンドとjqはインストールしておいてください。

```sh:make.sh
#!/bin/bash
AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=xxxxx
HOST_ZONE_ID=Zxxxxxxxx

FQDN=$1
COMMENT=$2
S3_PATH=$3

# CloudFrontの構築
ruby cf.rb $FQDN $COMMENT $S3_PATH  > temp_cf.json
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  aws cloudfront create-distribution-with-tags \
  --distribution-config-with-tags file://temp_cf.json > result.json
CF_ID=$(cat result.json | jq -r '.Distribution.Id')
DNS_NAME=$(cat result.json | jq -r '.Distribution.DomainName')
echo $CF_ID > cf_id.txt

# Route53の登録
ruby r53.rb $FQDN $DNS_NAME > temp_r53.json
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
  AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
  aws route53 change-resource-record-sets \
  --hosted-zone-id $HOST_ZONE_ID \
  --change-batch file://temp_r53.json > result2.json

# あとかたずけ
rm temp_cf.json
rm result.json
rm temp_r53.json
rm result2.json
echo $CF_ID
```

以下で実際に構築を行います。

```shell
./make.sh "www.example.com" "www.example.comのサイト" "/contents"
```