本記事は
IaCウィーク
10日目の記事です。
⚙️
9日目
▶▶ 本記事 ▶▶
11日目
💻

1. はじめに
こんにちは。入社 3 年目の松尾です。ポケモンのヌオーを愛する男です。
今回は IaC ウィークということで、AWS SAM に関する話をします。
CloudFormation であれば AWS マネジメントコンソール上で簡単に import できるのですが、AWS SAM の場合は前述の機能が使用できないため一筋縄ではいきません。
ですが、このブログの手順に沿って進めれば import できるはずです。ぜひご一読ください!

2. 結論
template_A から template_B にリソースを import する場合、以下の手順で import が可能です。
- リソース保持設定を活用し、template_A から対象のリソースを切り離す
- AWS CLI から template_B で変更セットを作成する
- AWS マネジメントコンソールから template_B で変更セットを適用する
- 実リソースと template_B の状態を合わせる
3. 前提
3.1. 想定する template.yaml
本記事では 2 種類の template.yaml を template_A, template_B と呼び、template_A から template_B に FunctionRole を import する想定で進みます。 template_A, template_B の具体的な内容は以下の通りです。
- template_A(Lambda と FunctionRole を宣言)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > SAM Template for ProjectA Resources: # AWS Lambda FunctionA: Type: AWS::Serverless::Function Properties: CodeUri: . Handler: app.lambda_handler Runtime: python3.12 FunctionName: "y-matsuo-blog-function-a" Role: !GetAtt FunctionRole.Arn # Lambda 用の IAMRole FunctionRole: Type: AWS::IAM::Role Properties: RoleName: "y-matsuo-blog-function-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: - lambda.amazonaws.com Policies: - PolicyName: BasicExecution PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" - "logs:DescribeLogGroups" Resource: "*"
- template_B(Lambda のみを宣言)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > SAM Template for ProjectB Resources: # AWS Lambda FunctionB: Type: AWS::Serverless::Function Properties: CodeUri: . Handler: app.lambda_handler Runtime: python3.12 FunctionName: "y-matsuo-blog-function-b" Role: "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role" # template_A のリソースの Arn
3.2. 初期状態
前述の template_A, template_B の内容をそれぞれ同一アカウント、同一リージョンに対し deploy します。
アーキテクチャ図は以下のようになりますが、AWS アカウントとリージョンは本記事において重要な意味を持たないため、本記事では右のように割愛します。

3.3. リソースを削除せず import できると嬉しいこと
3.1 項で述べたように、本記事では template_A から template_B に FunctionRole を import することを目的とします。
ただ、本項で説明する内容は Lambda と IAMRole のみではイメージしづらいので、対向サービスとして S3 bucket が存在することを想定してみます。
リソースを一度削除して再度構築する場合
まずは異なる SAM Template 間で最も簡単にリソースを移動する方法、即ち「削除して再度構築する」方法を考えてみます。
流れとしては、図 3-3-1 のように S3 bucket にアクセスする Lambda を template_A のものから template_B のものに変更し、図 3-3-2 のように FunctionRole を削除して再度構築する形になります。


しかしこの手順の場合は図 3-3-2 に記載がある通り、S3 bucket policy に記述した Arn の情報が削除のタイミングで dummy text に置き換わってしまいます。
このままでは Lambda から S3 bucket にアクセス出来ません。
- y-matsuo-blog-function-role が存在する状態の y-matsuo-test-bucket の bucket policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role" }, "Action": [ "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::y-matsuo-test-bucket/*", "arn:aws:s3:::y-matsuo-test-bucket" ] } ] }
- y-matsuo-blog-function-role を削除した後の y-matsuo-test-bucket の bucket policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "AWS": "AROA4VDBLY5MR4HUFCCJB" }, "Action": "s3:ListBucket", "Resource": [ "arn:aws:s3:::y-matsuo-test-bucket/*", "arn:aws:s3:::y-matsuo-test-bucket" ] } ] }
このような影響が起こり得ると考えると、リソースの削除を避けたい場面は多々あると思います。
- S3 bucket を自身が管理していない場合
- bucket policy を修正したくない場合
- リソースを削除することで同様の影響が多数発生する場合
このような場合はリソースを削除するのではなく、import したいですよね。
リソースを削除せず import する場合
次に異なる SAM Template 間でリソースを削除せず移動する方法について考えてみます。
図 3-3-1 の流れは前述と同様で、そこから図 3-3-3 の流れに進みます。
本来 SAM Template から記述を削除し deploy した場合、実リソースは削除されてしまいますが、特定の設定を追加することで削除せず stack から切り外すのみの処理を行うように出来ます。
この対応によって template_A から独立した FunctionRole を template_B に import することで、リソースの削除を避けつつ template_B に移動させることができます。

4. 手順
4.1. template_A から FunctionRole を切り離す
template_B に import するに当たって、template_A から切り離す必要があります。
まずは template_A.yaml の該当のリソースに対し、以下のようなリソース保持設定を追加します。
DeletionPolicy: Retain UpdateReplacePolicy: Retain
一部省略しますが、template_A の 5,6 行目に追加しています。
- template_A
︙ # Lambda 用の IAMRole FunctionRole: Type: AWS::IAM::Role DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: RoleName: "y-matsuo-blog-function-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: - lambda.amazonaws.com Policies: ︙
この状態で deploy を行います。
sam build sam deploy
CloudFormation の変更セットからは図 4-1-1, 図 4-1-2 のような差分が確認できます。


AWS マネジメントコンソール上からは確認できませんが、これでリソース保持設定を追加できているはずです。
この状態でリソースの記述を削除し deploy すると template_A から切り離す事ができます。
本記事では FunctionRole の記述をコメントアウトし、deploy してみます。
注意点として、FunctionRole の記述を template_A から削除するため、Lambda の Role オプションに指定する IAMRole は Arn を直書きするように変更してください。(11 行目)
- template_A
︙ Resources: # AWS Lambda FunctionA: Type: AWS::Serverless::Function Properties: CodeUri: . Handler: app.lambda_handler Runtime: python3.12 FunctionName: "y-matsuo-blog-function-a" Role: "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role" ︙ # # Lambda 用の IAMRole # FunctionRole: # Type: AWS::IAM::Role # DeletionPolicy: Retain # UpdateReplacePolicy: Retain # Properties: # RoleName: "y-matsuo-blog-function-role" # AssumeRolePolicyDocument: # Version: "2012-10-17" # Statement: # - Effect: "Allow" # Action: "sts:AssumeRole" # Principal: # Service: # - lambda.amazonaws.com # Policies: ︙
この状態で deploy を行います。
sam build sam deploy
CloudFormation の変更セットからは図 4-1-3 のような差分が確認できます。
アクション Remove とありますが、ポリシーアクション Retain となっている事が確認できると思います。
この変更セットを適用してもリソースは削除されず、template_A から切り離す挙動となります。

4.2. template_B で変更セットを作成する
template_B で FunctionRole を import するために、AWS CLI から変更セットを作成します。
変更セット作成のためには、import するリソース情報を追記した package.yaml と import するリソースを示す ResourcesToImport.json の作成をする必要があります。
[注意]以降は template_B のディレクトリで作業してください。
package.yaml を作成する
まずは package.yaml を作成します。
sam build sam package --template-file template.yaml --output-template-file package.yaml --s3-bucket y-matsuo-sam-deploy --region ap-northeast-1 --s3-prefix blog-B
コマンドを打つと package.yaml が生成されます。
- package.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: 'SAM Template for ProjectB ' Resources: FunctionB: Type: AWS::Serverless::Function Properties: CodeUri: s3://y-matsuo-sam-deploy/blog-B/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Handler: app.lambda_handler Runtime: python3.12 FunctionName: y-matsuo-blog-function-b Role: arn:aws:iam::123456789012:role/y-matsuo-blog-function-role Metadata: SamResourceId: FunctionB
このファイルを指定して deploy します。
この時点での変更セットは Lambda の差分のみとなります。(図 4-2-1)
sam deploy --template-file package.yaml

次に package.yaml に、import する予定のリソース(4.1 項でコメントアウトした内容)の情報を追記します。
- package.yaml
︙ FunctionB: Type: AWS::Serverless::Function Properties: CodeUri: s3://y-matsuo-sam-deploy/blog-B/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Handler: app.lambda_handler Runtime: python3.12 FunctionName: y-matsuo-blog-function-b Role: arn:aws:iam::123456789012:role/y-matsuo-blog-function-role Metadata: SamResourceId: FunctionB FunctionRole: Type: AWS::IAM::Role DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: RoleName: "y-matsuo-blog-function-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: - lambda.amazonaws.com Policies: - PolicyName: BasicExecution PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" - "logs:DescribeLogGroups" Resource: "*"
注意点として、package.yaml を使用して deploy する場合は samconfig.toml を読み込まない点が挙げられます。
例えば samconfig.toml で parameter_overrides を使用している場合、package.yaml で parameter を宣言する際に default として指定します。
本記事の内容では parameter を使用していないため、追記はしていません。
- parameter を default 付きで宣言する例
︙ Parameters: Environment: Type: String AllowedValues: - dev - live Default: dev Resources: ︙
ResourcesToImport.json を作成
変更セット作成時に import するリソースを JSON 形式で指定する必要があります。
コマンド実行時に JSON 文を入れ込むと可読性が低くなるため、JSON ファイルを作成しておきコマンド実行時に指定する方式を取ります。
- ResourcesToImport.json
[
{
"ResourceType": "AWS::IAM::Role",
"LogicalResourceId": "FunctionRole",
"ResourceIdentifier": {
"RoleName": "y-matsuo-blog-function-role"
}
}
]
AWS CLI から変更セットを作成し deploy する
AWS CLI で変更セットを作成します。
aws cloudformation create-change-set --stack-name y-matsuo-project-B --region ap-northeast-1 --change-set-name import-iam-role-change-set --resources-to-import file://ResourcesToImport.json --capabilities CAPABILITY_NAMED_IAM --change-set-type IMPORT --template-body file://package.yaml
CloudFormation の変更セットからは図 4-2-2 のような差分が確認できます。

アクション Import が確認できますね。
右上の「変更セットを実行」をクリックするとポップアップが出てきます。(図 4-2-3)

ほとんどの場合共にデフォルトの選択肢で問題ないと思うので、変更セットを実行をクリックします。

FunctionRole が import できました!
4.3. template_B に import したリソースの情報を追記する
4.2 項では template.yaml ではなく package.yaml に情報を追記したため、この時点で template_B は更新されていません。
実リソースの import は完了していますが、template.yaml の帳尻を合わせる必要があります。
必須ではないですが、FunctionRole の情報を追記するため、Lambda の Role オプションに指定する IAMRole は GetAtt で指定するようにしても良いですね。(11 行目)
- template_B
︙ Resources: # AWS Lambda FunctionB: Type: AWS::Serverless::Function Properties: CodeUri: . Handler: app.lambda_handler Runtime: python3.12 FunctionName: "y-matsuo-blog-function-b" Role: !GetAtt FunctionRole.Arn # Lambda 用の IAMRole FunctionRole: Type: AWS::IAM::Role DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: RoleName: "y-matsuo-blog-function-role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: Service: - lambda.amazonaws.com Policies: - PolicyName: BasicExecution PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" - "logs:DescribeLogGroups" Resource: "*"
この状態で deploy を行います。
sam build sam deploy
実リソースに template 側を合わせた形になるため、変更セットは Lambda の差分と IAMRole へのメタデータの追加のみとなるはずです。(図 4-3-1)

以上で実リソースと AWS SAM の template 共に作業完了です!
5. おわりに
いかがでしたでしょうか。
AWS SAM で作成したリソースも CloudFormation からサクッと import させて欲しい所ですが、できないものは仕方ないですね。
少々手間はかかりますが、本記事の手順で import してみてください!