AWS 環境の設定値をチェックするためのスクリプトを GitHub Copilot と一緒に作ってみた

2023.04.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、大前です。

構築した AWS 環境が意図した設定値で作成されているかを確認するためにスクリプトを作成する機会があったのですが、今話題の GitHub Copilot にスクリプトの作成を手伝ってもらったら効率が良くなるかと思い、試してみました。

(前提) チェックする AWS 環境

今回チェック対象となる AWS 環境は以下です。

上記環境を構築するための CloudFormation テンプレートは以下リポジトリに格納してあります。リソースの名前等に利用する Prefix や、VPC やサブネットに割り当てる CIDR を envfile というファイルに記載しています。

oomaeryousuke/check-aws-env-with-githubcopilot

envfile に設定値を記載した上で、/cloudformation 配下のテンプレートを適当な S3 バケットにアップロードし、下記のように deploy.sh を実行すれば AWS 環境が作成されます。

$ sh deploy.sh https://{CloudFormationテンプレートを配置したS3バケット名}.s3.{リージョン}.amazonaws.com

やってみた

スクリプトを作成する作業環境

今回はローカル端末にインストールされた Visual Studio Code に GitHub Copilot の拡張機能 をインストールして、作業を行いました。

詳細な使い方などについては、下記ブログ等を参照ください。

環境チェックスクリプトの用意

まずは最低限の記載のみをした、以下のスクリプトを用意します。(check.sh) ここに、AWS 環境をチェックするための記述を、GitHub Copilot を活用しながら記載していきます。

#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"

# 環境変数 読み込み
source ./envfile


VPC の設定値をチェックする記載を追加

今回利用する GitHub Copilot の機能は、Getting started with GitHub Copilot - GitHub Docs です。その名の通り、記載したコメントからソースコードをサジェストしてくれる機能となります。 今回はこれを駆使しまくって、効率よくスクリプトを作成していきたいと思います。

まずは、リソースの設定値を確認するには ID の取得が必要であるため、# VPC ID を Name タグで検索して取得 というコメントを記載し、サジェストの生成を実行してみます。

#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"

# 環境変数 読み込み
source ./envfile

# VPC ID を Name タグで検索して取得


サジェスト結果は以下の様になりました。GitHub Copilot はいくつか候補を出してくれるため、一部のみ記載しています。

特に指定しなくても、エラーハンドリングの記載もしてくれています。また、Name タグで検索する際の Value に Value=='${Prefix}-${Environment}-vpc' を指定していますが、これは、envfile に記載した Prefix や Environment の値を利用しています。

公式ドキュメントGitHub Copilot は、編集中のファイルや関連ファイルのコンテキストを分析し、テキスト エディター内から候補の提示を行います。 と記載があることからも、同じリポジトリ内のファイルであれば、適宜他のファイルも参照して、コードを生成してくれるようです。

Synthesizing 4/10 solutions

=======

# VPC ID を Name タグで検索して取得する
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-vpc']].VpcId" --output text --region "${REGION}")

# VPC ID が取得できなかった場合はエラー
if [ "${VPC_ID}" = "" ]; then
    echo "VPC ID が取得できませんでした"
    exit 1
fi

# VPC ID が取得できた場合は VPC が存在するということなので成功
echo "VPC ID: ${VPC_ID}"
exit 0

(略)


使いたい部分だけをコピーして、スクリプトに貼り付けていきます。続いて、ID が取得できたので envfile で指定している CIDR 値と一致しているか確認するため、# VPC の CIDR が envfile の値と一致するか確認 とコメントを記載し、サジェストを実行してみます.

#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"

# 環境変数 読み込み
source ./envfile

# VPC ID を Name タグで検索して取得
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-vpc']].VpcId" --output text)

# VPC ID が取得できなかった場合はエラー
if [ "${VPC_ID}" = "" ]; then
    echo "VPC ID が取得できませんでした"
    exit 1
fi

# VPC ID が envfile の値と一致するか確認


サジェスト結果は以下のようなものになりました。今回も一部のみ転載していますが、特に指示していないサブネットやセキュリティグループについてもチェックを行うようなコードを生成してくれました。

取得した VPC CIDR の比較対象である VPC_CIDR_BLOCK という変数は envfile 内の記載(VpcCidrBlock)に修正してあげる必要がありそうですが、生成されたコードはほぼそのまま使えそうです。

Synthesizing 7/10 solutions

=======

# VPC の CIDR が envfile の値と一致するか確認
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids "${VPC_ID}" --query "Vpcs[].CidrBlock" --output text)
if [ "${VPC_CIDR}" != "${VPC_CIDR_BLOCK}" ]; then
    echo "VPC の CIDR が一致しません"
    echo "envfile: ${VPC_CIDR_BLOCK}"
    echo "AWS: ${VPC_CIDR}"
    exit 1
fi

# VPC に紐づくサブネットの CIDR が envfile の値と一致するか確認
VPC_SUBNETS=$(aws ec2 describe-subnets --filters Name=vpc-id,Values="${VPC_ID}" --query "Subnets[].CidrBlock" --output text)
for VPC_SUBNET in ${VPC_SUBNETS}; do
    if ! echo "${VPC_SUBNETS}" | grep -q "${VPC_SUBNET}"; then
        echo "VPC のサブネットの CIDR が一致しません"
        echo "envfile: ${VPC_SUBNETS}"
        echo "AWS: ${VPC_SUBNET}"
        exit 1
    fi
done

# VPC に紐づくセキュリティグループの Name が envfile の値と一致するか確認
VPC_SECURITY_GROUPS=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values="${VPC_ID}" --query "SecurityGroups[].GroupName" --output text)
for VPC_SECURITY_GROUP in ${VPC_SECURITY_GROUPS}; do
    if ! echo "${VPC_SECURITY_GROUPS}" | grep -q "${VPC_SECURITY_GROUP}"; then
        echo "VPC のセキュリティグループの Name が一致しません"
        echo "envfile: ${VPC_SECURITY_GROUPS}"
        echo "AWS: ${VPC_SECURITY_GROUP}"
        exit 1
    fi
done

(略)


今回も必要な部分だけコピーして、スクリプトに貼り付けていきます。また、上述の VPC_CIDR_BLOCK などは修正します。

(略)

# VPC ID を Name タグで検索して取得
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-vpc']].VpcId" --output text)

# VPC ID が取得できなかった場合はエラー
if [ "${VPC_ID}" = "" ]; then
    echo "VPC ID が取得できませんでした"
    exit 1
fi

# VPC の CIDR が envfile の値と一致するか確認
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids "${VPC_ID}" --query "Vpcs[].CidrBlock" --output text)
if [ "${VPC_CIDR}" != "${VpcCidrBlock}" ]; then
    echo "VPC の CIDR が一致しません"
    echo "envfile: ${VpcCidrBlock}"
    echo "AWS: ${VPC_CIDR}"s
    exit 1
fi


これで VPC の設定値をチェックするためのコードは完成したので、チェック前後に echo コマンドを挟んだり、コメントの整頓をしておきます。

(略)

# VPC
echo "[START] check VPC"
## VPC ID を Name タグで検索して取得
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-vpc']].VpcId" --output text)

## VPC ID が取得できなかった場合はエラー
if [ "${VPC_ID}" = "" ]; then
    echo "VPC ID が取得できませんでした"
    exit 1
fi

## VPC の CIDR が envfile の値と一致するか確認
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids "${VPC_ID}" --query "Vpcs[].CidrBlock" --output text)
if [ "${VPC_CIDR}" != "${VpcCidrBlock}" ]; then
    echo "VPC の CIDR が一致しません"
    echo "envfile: ${VpcCidrBlock}"
    echo "AWS: ${VPC_CIDR}"
    exit 1
fi
echo "[FINISH] check VPC"


サブネットの設定値をチェックする記載を追加

続いて、サブネットの設定値をチェックするためのコードを生成していきます。# サブネットの CIDR が envfile の値と一致するか確認 という、VPC の時よりもざっくりしたコメントだけ記載してサジェストを表示させてみます。

(略)

# サブネットの CIDR が envfile の値と一致するか確認


サジェスト結果は以下の様になりました。特に何もコメントせずとも、サブネットの CIDR をチェックするために先にサブネット ID を取得していたり、VPC の設定値をチェックするためのコードと同じように処理の前後に echo "[START] check Subnet" などを挟んでくれています。

一方で、今回チェックしたいサブネットは 6つあるので、そこを修正していく必要がありそうです。

Synthesizing 3/10 solutions

=======

# サブネットの CIDR が envfile の値と一致するか確認
echo "[START] check Subnet"
## サブネット ID を Name タグで検索して取得
SUBNET_ID=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-public-subnet']].SubnetId" --output text)

## サブネット ID が取得できなかった場合はエラー
if [ "${SUBNET_ID}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi

## サブネットの CIDR が envfile の値と一致するか確認
SUBNET_CIDR=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR}" != "${PublicSubnetCidrBlock}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${PublicSubnetCidrBlock}"
    echo "AWS: ${SUBNET_CIDR}"
    exit 1
fi
echo "[FINISH] check Subnet"

(略)


とりあえず、サジェストされたコードを元に以下のコードを用意しました。サブネット ID を取得する部分だけ、4つ分のサブネット ID を取得するように追記しています。

(略)

# サブネット
echo "[START] check Subnet"
## サブネット ID を Name タグで検索して取得
### Public1a
SUBNET_ID_PUBLIC_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Public1c
SUBNET_ID_PUBLIC_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1a
SUBNET_ID_PRIVATE_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1c
SUBNET_ID_PRIVATE_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi

## サブネットの CIDR が envfile の値と一致するか確認
### Public1a
SUBNET_CIDR_PUBLIC_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1A}" != "${SubnetCidrBlockPublic1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1a}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1A}"
    exit 1
fi
echo "[FINISH] check Subnet"


この状態で、サブネット ID を取得している部分に、以下のように ### Protected とだけコメントを記載してサジェストをかけてみます。

(略)

# サブネット
echo "[START] check Subnet"
## サブネット ID を Name タグで検索して取得
### Public1a
SUBNET_ID_PUBLIC_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Public1c
SUBNET_ID_PUBLIC_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected
### Private1a
SUBNET_ID_PRIVATE_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1c
SUBNET_ID_PRIVATE_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi

## サブネットの CIDR が envfile の値と一致するか確認
### Public1a
SUBNET_CIDR_PUBLIC_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1A}" != "${SubnetCidrBlockPublic1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1a}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1A}"
    exit 1
fi
echo "[FINISH] check Subnet"


すると、残りのサブネットについても、既存のコードの命名規則などを踏襲してサジェストをしてくれます。

Synthesizing 10/10 solutions (Duplicates hidden)

=======

### Protected1a
SUBNET_ID_PROTECTED_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected1c
SUBNET_ID_PROTECTED_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi


同様に、取得したサブネット ID から CIDR を取得する部分についても、サブネットの名前だけをコメントしてサジェストをかけてみます。

(略)

## サブネットの CIDR が envfile の値と一致するか確認
### Public1a
SUBNET_CIDR_PUBLIC_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1A}" != "${SubnetCidrBlockPublic1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1a}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1A}"
    exit 1
fi
### Public1c
echo "[FINISH] check Subnet"


結果は以下の様になりました。指定した Public1c だけでなく、「他のサブネットも必要でしょ?」と言わんばかりに指定していない他のサブネットについても CIDR をチェックするコードを生成してくれました。

Private1a 以降が途切れていますが、これは再度 ### Private1a といったコメントを記載してサジェストすれば良さそうです。(途切れているのはサジェスト可能な文字数制限かな?と思いましたが、軽く調べても仕様が見つけられませんでした)

Synthesizing 4/10 solutions (Duplicates hidden)

=======

### Public1c
SUBNET_CIDR_PUBLIC_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1C}" != "${SubnetCidrBlockPublic1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1c}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1C}"
    exit 1
fi
### Protected1a
SUBNET_CIDR_PROTECTED_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1A}" != "${SubnetCidrBlockProtected1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1a}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1A}"
    exit 1
fi
### Protected1c
SUBNET_CIDR_PROTECTED_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1C}" != "${SubnetCidrBlockProtected1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1c}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1C}"
    exit 1
fi
### Private1a
SUBNET_CIDR_PRIVATE_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PRIVATE_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PRIVATE_1A}" != "${SubnetCidrBlockPrivate1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile:


生成したサジェストを利用して、以下のコードが作成できました。サブネット設定のチェックの様な、変数や名前だけが異なる同じようなコードをサクッと生成できるのは非常に便利だと感じました。

(略)

# サブネット
echo "[START] check Subnet"
## サブネット ID を Name タグで検索して取得
### Public1a
SUBNET_ID_PUBLIC_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Public1c
SUBNET_ID_PUBLIC_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected1a
SUBNET_ID_PROTECTED_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected1c
SUBNET_ID_PROTECTED_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1a
SUBNET_ID_PRIVATE_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1c
SUBNET_ID_PRIVATE_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi

## サブネットの CIDR が envfile の値と一致するか確認
### Public1a
SUBNET_CIDR_PUBLIC_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1A}" != "${SubnetCidrBlockPublic1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1a}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1A}"
    exit 1
fi
### Public1c
SUBNET_CIDR_PUBLIC_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1C}" != "${SubnetCidrBlockPublic1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1c}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1C}"
    exit 1
fi
### Protected1a
SUBNET_CIDR_PROTECTED_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1A}" != "${SubnetCidrBlockProtected1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1a}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1A}"
    exit 1
fi
### Protected1c
SUBNET_CIDR_PROTECTED_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1C}" != "${SubnetCidrBlockProtected1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1c}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1C}"
    exit 1
fi
### Private1a
SUBNET_CIDR_PRIVATE_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PRIVATE_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PRIVATE_1A}" != "${SubnetCidrBlockPrivate1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPrivate1a}"
    echo "AWS: ${SUBNET_CIDR_PRIVATE_1A}"
    exit 1
fi
### Private1c
SUBNET_CIDR_PRIVATE_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PRIVATE_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PRIVATE_1C}" != "${SubnetCidrBlockPrivate1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPrivate1c}"
    echo "AWS: ${SUBNET_CIDR_PRIVATE_1C}"
    exit 1
fi

echo "[FINISH] check Subnet"


インターネットゲートウェイの設定値をチェックする記載を追加してみる

続いて、インターネットゲートウェイの設定値チェックを行うコードを生成していきます。もう細かくコメント記載するのも面倒になってきたので、# IGW とだけコメントしてサジェストをお願いしちゃいます。

(略)

echo "[FINISH] check Subnet"

# IGW


結果は以下の様になりました。何も言わずとも既存のコードに合わせて インターネットゲートウェイの設定をチェックするコードを生成してくれています。さらにこの後チェックするつもりの NAT ゲートウェイについてもサジェストを行ってくれています。すごい。

Synthesizing 4/10 solutions

=======

# IGW
echo "[START] check IGW"
IGW_ID=$(aws ec2 describe-internet-gateways --query "InternetGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-igw']].InternetGatewayId" --output text)
if [ "${IGW_ID}" = "" ]; then
    echo "IGW ID が取得できませんでした"
    exit 1
fi
IGW_ATTACHED=$(aws ec2 describe-internet-gateways --internet-gateway-ids "${IGW_ID}" --query "InternetGateways[].Attachments[].State" --output text)
if [ "${IGW_ATTACHED}" != "available" ]; then
    echo "IGW がアタッチされていません"
    exit 1
fi
echo "[FINISH] check IGW"

# NAT
echo "[START] check NAT"
NAT_EIP_ALLOCATION_ID=$(aws ec2 describe-addresses --query "Addresses[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-nat-eip']].AllocationId" --output text)
if [ "${NAT_EIP_ALLOCATION_ID}" = "" ]; then
    echo "Elastic IP allocation ID が取得できませんでした"
    exit 1
fi
NAT_EIP_PUBLIC_IP=$(aws ec2 describe-addresses --query "Addresses[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-nat-eip']].PublicIp" --output text)
if [ "${NAT_EIP_PUBLIC_IP}" = "" ]; then
    echo "Elastic IP address が取得できませんでした"
    exit 1
fi
NAT_EIP_ASSOCIATION_ID=$(aws ec2 describe-addresses --query "Addresses[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-nat-eip']].AssociationId" --output text)
if [ "${NAT_EIP_ASSOCIATION_ID}" = "" ]; then
    echo "Elastic IP association ID が取得できませんでした"
    exit 1
fi
NAT_EIP_DOMAIN=$(aws ec2 describe-addresses --query "Addresses[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-nat-eip']].Domain" --output

(略)


NAT ゲートウェイのチェックまで考えてくれる GitHub Copilot くんの好意は嬉しいですが、一旦 インターネットゲートウェイの部分だけコードを利用させてもらいます。インターネットゲートウェイについては 1分もかからずに設定をチェックするコードが生成できました。

(略)

# IGW
echo "[START] check IGW"
## IGW ID を取得
IGW_ID=$(aws ec2 describe-internet-gateways --query "InternetGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-igw']].InternetGatewayId" --output text)
if [ "${IGW_ID}" = "" ]; then
    echo "IGW ID が取得できませんでした"
    exit 1
fi
## IGW が VPC にアタッチされているか確認
IGW_ATTACHED=$(aws ec2 describe-internet-gateways --internet-gateway-ids "${IGW_ID}" --query "InternetGateways[].Attachments[].VpcId" --output text)
if [ "${IGW_ATTACHED}" != "${VPC_ID}" ]; then
    echo "IGW が VPC にアタッチされていません"
    exit 1
fi
echo "[FINISH] check IGW"


NAT ゲートウェイの設定値をチェックする記載を追加してみる

最後に NAT ゲートウェイです。インターネットゲートウェイと同様に、# NAT ゲートウェイ とだけコメントをしてサジェストを実行します。

(略)

echo "[FINISH] check IGW"

# NAT ゲートウェイ


結果は下記の様になりました。NAT ゲートウェイはパブリックサブネット上にしか作成しないため、## NAT ゲートウェイが Protected1a のサブネットにアタッチされているか確認 辺りのサジェスト結果は不要なコードになりますが、基本的にそのまま利用できるコードを生成してくれています。

Synthesizing 5/10 solutions

=======

# NAT ゲートウェイ
echo "[START] check NAT Gateway"
## NAT ゲートウェイ ID を取得
NAT_GATEWAY_ID=$(aws ec2 describe-nat-gateways --query "NatGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-natgw-1a']].NatGatewayId" --output text)
if [ "${NAT_GATEWAY_ID}" = "" ]; then
    echo "NAT ゲートウェイ ID が取得できませんでした"
    exit 1
fi
## NAT ゲートウェイが Public1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1A}" ]; then
    echo "NAT ゲートウェイが Public1a のサブネットにアタッチされていません"
    exit 1
fi
## NAT ゲートウェイが Public1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1A}" ]; then
    echo "NAT ゲートウェイが Public1a のサブネットにアタッチされていません"
    exit 1
fi
## NAT ゲートウェイが Protected1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" !=

=======

(略)


必要な部分のみ抜き出し、以下のコードを作成しました。

(略)

# NAT ゲートウェイ
echo "[START] check NAT Gateway"
## NAT ゲートウェイ ID を取得
NAT_GATEWAY_ID=$(aws ec2 describe-nat-gateways --query "NatGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-ngw-1a']].NatGatewayId" --output text)
if [ "${NAT_GATEWAY_ID}" = "" ]; then
    echo "NAT ゲートウェイ ID が取得できませんでした"
    exit 1
fi
## NAT ゲートウェイが Public1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1A}" ]; then
    echo "NAT ゲートウェイが Public1a のサブネットにアタッチされていません"
    exit 1
fi


最後に、NAT ゲートウェイは envfile で MultiAz という変数が "true" の場合のみ 2つのパブリックサブネット上に作成される様に CloudFormation テンプレートを構成しています。そのため、## マルチAZの時のみパブリックサブネット1cにNATゲートウェイがアタッチされているか確認 というコメントを記載して、上記条件を満たすようなコードを生成してもらいます。

(略)

## NAT ゲートウェイが Public1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1A}" ]; then
    echo "NAT ゲートウェイが Public1a のサブネットにアタッチされていません"
    exit 1
fi

## マルチAZの時のみパブリックサブネット1cにNATゲートウェイがアタッチされているか確認


結果は以下の様になりました。いい感じに if 文を組み込んで、コードを生成してくれています。if 文の条件として利用している MultiAZ は、envfile 上では MultiAz(Z が小文字)であるため修正が必要ですが、他は既存のコードを踏襲してくれているため、問題なくそのまま利用できそうです。

Synthesizing 4/10 solutions

=======

## マルチAZの時のみパブリックサブネット1cにNATゲートウェイがアタッチされているか確認
if [ "${MultiAZ}" = "true" ]; then
    ## NAT ゲートウェイ ID を取得
    NAT_GATEWAY_ID=$(aws ec2 describe-nat-gateways --query "NatGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-ngw-1c']].NatGatewayId" --output text)
    if [ "${NAT_GATEWAY_ID}" = "" ]; then
        echo "NAT ゲートウェイ ID が取得できませんでした"
        exit 1
    fi
    ## NAT ゲートウェイが Public1c のサブネットにアタッチされているか確認
    NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
    if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1C}" ]; then
        echo "NAT ゲートウェイが Public1c のサブネットにアタッチされていません"
        exit 1
    fi
fi
echo "[FINISH] check NAT Gateway"

(略)


これで、AWS 環境の設定値をチェックするためのスクリプトが完成しました。完成したスクリプトは以下の様になりました。(長いので折りたたんでいます)

そこそこ行数のあるスクリプトではありますが、多分 30分もかからず生成できています。GitHub Copilot の便利さを身をもって体感することができました。

check.sh
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"

# 環境変数 読み込み
source ./envfile

# VPC
echo "[START] check VPC"
## VPC ID を Name タグで検索して取得
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-vpc']].VpcId" --output text)

## VPC ID が取得できなかった場合はエラー
if [ "${VPC_ID}" = "" ]; then
    echo "VPC ID が取得できませんでした"
    exit 1
fi

## VPC の CIDR が envfile の値と一致するか確認
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids "${VPC_ID}" --query "Vpcs[].CidrBlock" --output text)
if [ "${VPC_CIDR}" != "${VpcCidrBlock}" ]; then
    echo "VPC の CIDR が一致しません"
    echo "envfile: ${VpcCidrBlock}"
    echo "AWS: ${VPC_CIDR}"
    exit 1
fi
echo "[FINISH] check VPC"

# サブネット
echo "[START] check Subnet"
## サブネット ID を Name タグで検索して取得
### Public1a
SUBNET_ID_PUBLIC_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Public1c
SUBNET_ID_PUBLIC_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-public-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PUBLIC_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected1a
SUBNET_ID_PROTECTED_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Protected1c
SUBNET_ID_PROTECTED_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-protected-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PROTECTED_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1a
SUBNET_ID_PRIVATE_1A=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1a']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1A}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi
### Private1c
SUBNET_ID_PRIVATE_1C=$(aws ec2 describe-subnets --query "Subnets[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-subnet-private-1c']].SubnetId" --output text)
if [ "${SUBNET_ID_PRIVATE_1C}" = "" ]; then
    echo "Subnet ID が取得できませんでした"
    exit 1
fi

## サブネットの CIDR が envfile の値と一致するか確認
### Public1a
SUBNET_CIDR_PUBLIC_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1A}" != "${SubnetCidrBlockPublic1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1a}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1A}"
    exit 1
fi
### Public1c
SUBNET_CIDR_PUBLIC_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PUBLIC_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PUBLIC_1C}" != "${SubnetCidrBlockPublic1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPublic1c}"
    echo "AWS: ${SUBNET_CIDR_PUBLIC_1C}"
    exit 1
fi
### Protected1a
SUBNET_CIDR_PROTECTED_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1A}" != "${SubnetCidrBlockProtected1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1a}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1A}"
    exit 1
fi
### Protected1c
SUBNET_CIDR_PROTECTED_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PROTECTED_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PROTECTED_1C}" != "${SubnetCidrBlockProtected1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockProtected1c}"
    echo "AWS: ${SUBNET_CIDR_PROTECTED_1C}"
    exit 1
fi
### Private1a
SUBNET_CIDR_PRIVATE_1A=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PRIVATE_1A}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PRIVATE_1A}" != "${SubnetCidrBlockPrivate1a}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPrivate1a}"
    echo "AWS: ${SUBNET_CIDR_PRIVATE_1A}"
    exit 1
fi
### Private1c
SUBNET_CIDR_PRIVATE_1C=$(aws ec2 describe-subnets --subnet-ids "${SUBNET_ID_PRIVATE_1C}" --query "Subnets[].CidrBlock" --output text)
if [ "${SUBNET_CIDR_PRIVATE_1C}" != "${SubnetCidrBlockPrivate1c}" ]; then
    echo "Subnet の CIDR が一致しません"
    echo "envfile: ${SubnetCidrBlockPrivate1c}"
    echo "AWS: ${SUBNET_CIDR_PRIVATE_1C}"
    exit 1
fi

echo "[FINISH] check Subnet"

# IGW
echo "[START] check IGW"
## IGW ID を取得
IGW_ID=$(aws ec2 describe-internet-gateways --query "InternetGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-igw']].InternetGatewayId" --output text)
if [ "${IGW_ID}" = "" ]; then
    echo "IGW ID が取得できませんでした"
    exit 1
fi
## IGW が VPC にアタッチされているか確認
IGW_ATTACHED=$(aws ec2 describe-internet-gateways --internet-gateway-ids "${IGW_ID}" --query "InternetGateways[].Attachments[].VpcId" --output text)
if [ "${IGW_ATTACHED}" != "${VPC_ID}" ]; then
    echo "IGW が VPC にアタッチされていません"
    exit 1
fi
echo "[FINISH] check IGW"

# NAT ゲートウェイ
echo "[START] check NAT Gateway"
## NAT ゲートウェイ ID を取得
NAT_GATEWAY_ID=$(aws ec2 describe-nat-gateways --query "NatGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-ngw-1a']].NatGatewayId" --output text)
if [ "${NAT_GATEWAY_ID}" = "" ]; then
    echo "NAT ゲートウェイ ID が取得できませんでした"
    exit 1
fi
## NAT ゲートウェイが Public1a のサブネットにアタッチされているか確認
NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1A}" ]; then
    echo "NAT ゲートウェイが Public1a のサブネットにアタッチされていません"
    exit 1
fi

## マルチAZの時のみパブリックサブネット1cにNATゲートウェイがアタッチされているか確認
if [ "${MultiAz}" = "true" ]; then
    ## NAT ゲートウェイ ID を取得
    NAT_GATEWAY_ID=$(aws ec2 describe-nat-gateways --query "NatGateways[?Tags[?Key=='Name' && Value=='${Prefix}-${Environment}-ngw-1c']].NatGatewayId" --output text)
    if [ "${NAT_GATEWAY_ID}" = "" ]; then
        echo "NAT ゲートウェイ ID が取得できませんでした"
        exit 1
    fi
    ## NAT ゲートウェイが Public1c のサブネットにアタッチされているか確認
    NAT_GATEWAY_ATTACHED_SUBNET=$(aws ec2 describe-nat-gateways --nat-gateway-ids "${NAT_GATEWAY_ID}" --query "NatGateways[].SubnetId" --output text)
    if [ "${NAT_GATEWAY_ATTACHED_SUBNET}" != "${SUBNET_ID_PUBLIC_1C}" ]; then
        echo "NAT ゲートウェイが Public1c のサブネットにアタッチされていません"
        exit 1
    fi
fi
echo "[FINISH] check NAT Gateway"

作成したスクリプトを実行してみる

作成したスクリプトを実際に実行して、正しく動作するか確認してみます。今回は CloudShell 上に作成したスクリプトと、設定値を記載した envfile をアップロードして実行します。

結果は以下の通りとなりました。問題なく動作していそうです。

$ sh check.sh 
[START] check VPC
[FINISH] check VPC
[START] check Subnet
[FINISH] check Subnet
[START] check IGW
[FINISH] check IGW
[START] check NAT Gateway
[FINISH] check NAT Gateway
$


試しに、envfile を一部書き換えて、チェックが失敗する様にしてみます。SubnetCidrBlockPrivate1c の CIDR を 10.10.32.0/24 から 10.10.30.0/24 に書き換えて、再度スクリプトを実行してみます。

#!/bin/bash

# アカウント ID
export AccountId="000011112222"

# システム名
export Prefix="blog"

# 環境名
export Environment="sample"

# マルチAZ or シングルAZ( true | false )
export MultiAz="false"

# VPC CIDR
export VpcCidrBlock="10.10.0.0/16"

# Subnet CIDR
export SubnetCidrBlockPublic1a="10.10.1.0/24"
export SubnetCidrBlockPublic1c="10.10.2.0/24"
export SubnetCidrBlockProtected1a="10.10.4.0/24"
export SubnetCidrBlockProtected1c="10.10.8.0/24"
export SubnetCidrBlockPrivate1a="10.10.16.0/24"
export SubnetCidrBlockPrivate1c="10.10.30.0/24"


ちゃんと間違っているパラメータの検出もしてくれました。

$ sh check.sh 
[START] check VPC
[FINISH] check VPC
[START] check Subnet
Subnet の CIDR が一致しません
envfile: 10.10.30.0/24
AWS: 10.10.32.0/24
$

おわりに

GitHub Copilot を利用して、AWS 環境の設定値をチェックするスクリプトを作成してみました。

利用する前は、単純にパブリックな情報から AWS CLI のコマンドをサジェストしてくれるくらいかと思っていたのですが、既存のコードから命名規則やソースコードの構造を解析して、コードを生成してくれるのは驚きでした。

実際に使ってみて非常に作業効率が上がったことも体験できましたし、これを一度体験してしまうと GitHub Copilot 無しの生活には戻れない気がします。

GitHub Copilot は今後も色々と発展していくと思いますので、今後も楽しみです。

以上、AWS 事業本部の大前でした。