初めに
先日のアップデートでCloudFormationが既に存在するカスタム名を指定しているリソースがテンプレートに含まれている場合、変更セット作成時に自動で取り込んでくれるようになりました。
これまでスタックに既存のリソースをインポートする場合、リソースのインポート操作を通常のスタックの作成とは別枠で行う必要があり(通常の変更と一緒にできない)、かつインポート時にはテンプレート自体とは別に取り込むリソースの識別子を入力する必要がありました。
今後はカスタム名が指定されているリソースについては、ImportExistingResources
パラメータをTrueにすることで上記事のような個別の指定は不要となりCloudFormation側が自動で判別して取り込んでくれるようになります。
なおカスタム名は一部のリソースに存在する個別の属性として名称が指定可能なものとなり対応リソースは以下ページをご参照ください。
試してみる
以前試したCloudFront+S3+WAFのテンプレートあったので、事前にS3バケットを作成した上で以下のテンプレートで変更セットを作成します。ハイライト部分が今回取り込み対象とするリソースです。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
Prefix:
Type: String
Mappings:
CloudFront:
ManagedCachePolicyId:
CachingDisabled: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
ManagedOriginRequestPolicy:
AllViewerAndCloudFrontHeaders202206: 33f36d7e-f396-46d9-90e0-52428a34d9dc
Resources:
#----------------------------------
#----- WAF
#----------------------------------
WAF:
Type: AWS::WAFv2::WebACL
Properties:
Name: !Sub ${Prefix}-web-acl
Description: !Sub WebACL for ${Prefix}
Scope: CLOUDFRONT
DefaultAction:
Allow: {}
VisibilityConfig:
CloudWatchMetricsEnabled: False
MetricName: !Sub ${Prefix}-web-acl
SampledRequestsEnabled: False
#----------------------------------
#----- CloudFront
#----------------------------------
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: True
Comment: !Sub Distribution for ${Prefix}
WebACLId: !Ref WAF
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
- PUT
- PATCH
- POST
- DELETE
CachePolicyId: !FindInMap [CloudFront, ManagedCachePolicyId, CachingDisabled]
OriginRequestPolicyId: !FindInMap [CloudFront, ManagedOriginRequestPolicy, AllViewerAndCloudFrontHeaders202206]
TargetOriginId: site-bucket
ViewerProtocolPolicy: redirect-to-https
DefaultRootObject: index.html
HttpVersion: http2
IPV6Enabled: False
Origins:
- Id: site-bucket
DomainName: !GetAtt SiteBucket.DomainName
OriginAccessControlId: !Ref CommonOAC
S3OriginConfig:
OriginAccessIdentity: ''
PriceClass: PriceClass_All
CommonOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub ${Prefix}-bucket-access-control
Description: !Sub bucket access control for ${Prefix}
OriginAccessControlOriginType: s3
SigningBehavior: never
SigningProtocol: sigv4
StaticContentsDefaultCachePolicy:
Type: AWS::CloudFront::CachePolicy
Properties:
CachePolicyConfig:
Name: !Sub ${Prefix}-static-content-cache-policy
Comment: !Sub cloudfront accesslog in ${Prefix}
ParametersInCacheKeyAndForwardedToOrigin:
EnableAcceptEncodingBrotli: False
EnableAcceptEncodingGzip: True
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: all
DefaultTTL: 300
MinTTL: 300
MaxTTL: 300
SiteBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub ${Prefix}-site-contents-{AWS::AccountId}
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: True
ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
SiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref SiteBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AccessFromCloudFront
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub arn:aws:s3:::${SiteBucket}/*
Condition:
StringEquals:
AWS:SourceArn: !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
バケットは事前に手動で作成しておきます。
今回のオプションはまだマネジメントコンソール上で指定が見当たらなかったのでAWS CLIで操作を行います。
なお現時点ではAWS CLIでもv1系のみが対応しており、v2系はUnknown options
扱いとなったためv1側で操作を行っています。
## v2は未対応
$ aws --version
aws-cli/2.13.37 Python/3.11.6 Darwin/22.5.0 exe/x86_64 prompt/off
$ aws create-change-set --region us-east-1 \
--stack-name new-custom-name-import-test \
--template-body auto-import.yml \
--parameters ParameterKey=Prefix,ParameterValue=auto-import \
--change-set-name import-`date +%Y%m%d-%H%M%S` \
--import-existing-resources
--change-set-type CREATE
...
Unknown options: --import-existing-resource
# v1は対応済み
$ /tmp/aws-cli-latest/bin/aws --version
aws-cli/1.30.3 Python/3.11.6 Darwin/22.5.0 botocore/1.32.3
$ /tmp/aws-cli-latest/bin/aws create-change-set --region us-east-1 \
--stack-name new-custom-name-import-test \
--template-body file://auto-import.yml \
--parameters ParameterKey=Prefix,ParameterValue=auto-import \
--change-set-name import-`date +%Y%m%d-%H%M%S` \
--import-existing-resources
--change-set-type CREATE
{
"Id": "arn:aws:cloudformation:us-east-1:xxxxx:changeSet/import-20231120-153127/xxxxx",
"StackId": "arn:aws:cloudformation:us-east-1:xxxxx:stack/new-custom-name-import-test/xxxxx"
}
マネジメントコンソール側で変更セットを確認してみるとS3がImportとなっていることが確認できます。
実行してみると取り込みイベントとしてS3バケットが含まれていることが確認できます。
DeletionPolicyの指定は必要
CloudFormationでインポートするリソースにはDeletionPolicy
の指定が必須という制約がありますが、これは今回の方法を使った場合でも同様です。
未指定の場合はAdd扱いではなく変更セットの作成自体がエラーとなります。
一部のリソースはうまく取り込めなかった
検証していた中でWAFやセキュリティグループについてはうまくImportとならずAddのままとなってしまいました。
この2つのリソースについてはカスタム名に対応してはいますが取り込みリソース指定の際ににID等の別の属性が必要となるのでこの辺りの関係でしょうか?
今回はここまで追い切ることができなかったため別途確認が必要な点とはなります。
終わりに
これまでリソースのインポートする形で新たにスタックを作成する場合は一度対象のリソースをコメントアウトしてデプロイし別途インポート処理が必要、既存の追加でも変更処理が入らないように構成する必要があり非常に手間でした。
現時点では全てのリソースが対応しているわけではないですが今回のアップデートによりそういった前作業を気にする必要がなく1デプロイで通せるようになったので非常に有難いアップデートです。
付録: RustでのCreateChangeSet
WAFの件でハマってる時にCloudTrail上でImportExistingResources
のパラメータが見えなくて本当に渡ってるか不安だった時に別口でrustのsdkも既に対応してそうだったのでそちらで行うようにもしていました。
結果的にはCloudTrailには表示されないパラメータでdescribe-change-set
で確認するとTrue
になったいたので不要となりましたが今後何かで使うこともあると思いますので個人的なメモとして残しておきます。
use std::{fs::File, io::Read};
use aws_config::BehaviorVersion;
use aws_sdk_cloudformation::{Client, Error, types::Parameter};
#[tokio::main]
async fn main() -> Result<(), Error> {
let mut contents = String::new();
let mut f = File::open("auto-import.yml").unwrap();
f.read_to_string(&mut contents).unwrap();
let shared_config = aws_config::load_defaults(BehaviorVersion::v2023_11_09()).await;
let client = Client::new(&shared_config);
#当初は新規作成の変更セットではなく既存の変更(追加)で考えていたため少し指定が異なります
let params = Parameter::builder()
.set_parameter_key(Some("Prefix".to_string()))
.use_previous_value(true)
.build();
let req = client.create_change_set()
.stack_name("custom-name-import-test")
.change_set_name("import1")
.template_body(contents)
.import_existing_resources(true)
.parameters(params);
let resp = req.send().await?;
println!("{:?}", resp);
Ok(())