概要
GitHub Actionsを使ってS3内のファイルを自動更新する方法についてNested Stackを使用する際に使用するS3内のCloudFormationテンプレートの自動更新を例に解説します
デプロイする用のロールとS3についてはCloudFormationを使って構築します
前提
- OIDCの設定済み
- CloudFormation、OIDCに関する知見を有している
実装
CloudFormationを使ったS3とIAMロールの作成
まず、S3とIAMロールをCloudFormationを使って作成します
以下がS3用のテンプレートです
AWSTemplateFormatVersion: 2010-09-09
Description: "S3 Bucket Factory Settings Stack"
# -------------------------------------
# Metadata
# -------------------------------------
Metadata:
AWS::CloudFormation::Interface:
# パラメータの並び順
ParameterGroups:
- Label:
default: "Project Configuration"
Parameters:
- ProjectName
- Environment
# -------------------------------------
# Parameters
# -------------------------------------
Parameters:
ProjectName:
Description: "Enter the project name. (ex: lc-inquiry-pro)"
Type: String
MinLength: 1
ConstraintDescription: "ProjectName must be entered."
Default: shun198
Environment:
Description: "Select a environment name."
Type: String
AllowedValues:
- dev
- stg
- prd
ConstraintDescription: "Environment name must be selected."
# -------------------------------------
# Resources
# -------------------------------------
Resources:
# -------------------------------------
# S3
# -------------------------------------
# For CloudFormation Templates
CloudFormationTemplatesBucket:
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
BucketName: !Sub cf-templates-${ProjectName}-${Environment}-${AWS::Region}
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: ExpiresAfter365DaysFor4thOlderVersion
Status: Enabled
NoncurrentVersionExpiration:
NewerNoncurrentVersions: 3
NoncurrentDays: 365
# -------------------------------------
# Outputs
# -------------------------------------
Outputs:
CloudFormationTemplatesBucketName:
Value: !Ref CloudFormationTemplatesBucket
CloudFormationTemplatesBucketArn:
Value: !GetAtt CloudFormationTemplatesBucket.Arn
続いてS3内のファイルを更新する用のIAMロールを作成します
AssumeRolePolicyDocumentのPrincipalには作成したOIDCのArnを指定します
ConditionのStringLikeの箇所には該当するリポジトリとブランチ、StringEqualsにはsts.amazonaws.comを指定します
今回は指定したリポジトリの全ブランチを指定したいのでRepositoryName:
の後ろにワイルドカードで*
を記載します
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: !Ref OIDCArn
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringLike:
token.actions.githubusercontent.com:sub: !Sub repo:${RepositoryName}:*
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
また、ポリシーについてはS3バケットとS3バケット内のリソースを対象にしており、今回は更新のみなのでs3.PutObjectのみ付与してます
Policies:
- PolicyName: !Sub S3DeployAccessPolicy-${ProjectName}-${Environment}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub arn:aws:s3:::${CloudFormationTemplateBucketName}
- !Sub arn:aws:s3:::${CloudFormationTemplateBucketName}/*
作成するテンプレートファイルは以下の通りです
AWSTemplateFormatVersion: 2010-09-09
Description: "IAM Role For Deployment"
# -------------------------------------
# Metadata
# -------------------------------------
Metadata:
AWS::CloudFormation::Interface:
# パラメータの並び順
ParameterGroups:
- Label:
default: "Project Configuration"
Parameters:
- ProjectName
- Environment
- Label:
default: "IAM Configuration"
Parameters:
- CloudFormationTemplateBucketName
- RepositoryName
- OIDCArn
# -------------------------------------
# Parameters
# -------------------------------------
Parameters:
ProjectName:
Description: "Enter the project name. (ex: lc-inquiry-pro)"
Type: String
MinLength: 1
ConstraintDescription: "ProjectName must be entered."
Default: shun198
Environment:
Description: "Select a environment name."
Type: String
AllowedValues:
- dev
- stg
- prd
ConstraintDescription: "Environment name must be selected."
CloudFormationTemplateBucketName:
Description: "Enter the cloudformation template S3 bucket name."
Type: String
MinLength: 1
ConstraintDescription: "S3 bucket name must be entered."
RepositoryName:
Description: "Enter the repository name."
Type: String
MinLength: 1
ConstraintDescription: "Repository name must be entered."
OIDCArn:
Description: "Enter the OIDC Arn. (ex: arn:aws:iam::XXXXXXXXXXXX:oidc-provider/token.actions.githubusercontent.com)"
Type: String
MinLength: 1
ConstraintDescription: "OIDC Arn must be entered."
# -------------------------------------
# Resources
# -------------------------------------
Resources:
# -------------------------------------
# IAM Role
# -------------------------------------
S3DeployIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub S3RoleForGitHubActions-${ProjectName}-${Environment}
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Federated: !Ref OIDCArn
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringLike:
token.actions.githubusercontent.com:sub: !Sub repo:${RepositoryName}:*
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
Path: /service-role/
Policies:
- PolicyName: !Sub S3DeployAccessPolicy-${ProjectName}-${Environment}
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:PutObject
Resource:
- !Sub arn:aws:s3:::${CloudFormationTemplateBucketName}
- !Sub arn:aws:s3:::${CloudFormationTemplateBucketName}/*
ワークフローの作成
commit履歴を見て追加、変更されたテンプレートファイルのみを検知してS3内のファイルを更新するワークフローを作成します
その際にcheckoutのfetch-depth: 0
のオプションを付与します
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
diffコマンドを使って変更されたテンプレート数を検知して個数を他のステップで使用できるよう$GITHUB_OUTPUT
へ出力します
- name: Check how many templates changed
id: templates
run: |
changed_templates=$(git diff HEAD^ --name-only | grep -c ${{ env.WORKING_DIRECTORY }}/)
echo changed_templates=$changed_templates >> "$GITHUB_OUTPUT"
変更されたテンプレートの数が1つ以上の場合はGitHub ActionsとAWS間で認証設定を行います
- name: configure aws credentials
if: ${{ steps.templates.outputs.changed_templates > 0 }};
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.UPLOAD_TEMPLATE_ROLE }}
aws-region: ${{ env.REGION_NAME }}
変更されたテンプレートの数が1つ以上の場合はdiffコマンドでテンプレート名を取得し、for文を使って順番にaws s3 cpコマンドで更新していきます
- name: Upload to S3
if: ${{ steps.templates.outputs.changed_templates > 0 }};
run: |
templates=$(git diff HEAD^ --name-status | grep ${{ env.WORKING_DIRECTORY }}/ | grep "^M\|^A" | cut -f2)
for template in $templates; do
aws s3 cp $template ${{ secrets.S3_BUCKET_URI }}
done
作成したワークフローは以下の通りです
name: Deploy CloudFormation Templates to S3
on:
push:
branches:
- develop
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
branches-ignore:
- "release/**"
- "doc/**"
env:
WORKING_DIRECTORY: cloudformation/templates
REGION_NAME: ap-northeast-1
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check how many templates changed
id: templates
run: |
changed_templates=$(git diff HEAD^ --name-only | grep -c ${{ env.WORKING_DIRECTORY }}/)
echo changed_templates=$changed_templates >> "$GITHUB_OUTPUT"
- name: configure aws credentials
if: ${{ steps.templates.outputs.changed_templates > 0 }};
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.UPLOAD_TEMPLATE_ROLE }}
aws-region: ${{ env.REGION_NAME }}
- name: Upload to S3
if: ${{ steps.templates.outputs.changed_templates > 0 }};
run: |
templates=$(git diff HEAD^ --name-status | grep ${{ env.WORKING_DIRECTORY }}/ | grep "^M\|^A" | cut -f2)
for template in $templates; do
aws s3 cp $template ${{ secrets.S3_BUCKET_URI }}
done
実際に実行してみよう!
以下のようにワークフローが正常に実行され、S3内のファイルが自動更新されたら成功です
参考