CDKのAspectsでCDKバージョンアップ時のリソース不整合を解決する
背景
現在CDKのバージョンが更新できなくなってしまい困っている。
問題の背景、現状、対応を整理する。
問題の背景
以下にissueが上がっている問題です。
残念ながらPRが進んでいません。一度出されたPRはcloseされてしまっています。
CDKのバージョン2.154.0で追加された以下の変更点がきっかけとなっているようです。
s3: add skip destination validation property
どうやらS3 notification eventを設定するCDKのカスタムリソースの変更によりにSkipDestinationValidationが設定されるようになり、その結果リソースの状態が不良になってしまうようです。
以下のようなエラーが出ます。
Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2024/10/09/[$LATEST]ddb23884496c4b069ba07632c4d9da66 (RequestId: cc9644a1-8614-4593-a4e5-e84a90584352)
設定の異なるPutBucketNotificationConfigurationを同名で作ってしまっているのかなという感じです。
issueに紹介されているwork around
以下のように全てのNodeからS3BucketNotificationを検索して削除すればいいよ!
ということのようなのですが、残念ながら私のところではうまく動きませんでした。
public static Stack OverrideBucketNotificationProperties(this Stack instance)
{
var bucketNotifications = instance
.Node
.FindAll()
.Select(x => x.Node.DefaultChild)
.OfType<CfnResource>()
.Where(x => x.CfnResourceType == "Custom::S3BucketNotifications");
foreach (var resource in bucketNotifications)
resource.AddPropertyDeletionOverride("SkipDestinationValidation");
return instance;
}
ちなみにTypeScriptでこんなコードで試しました。
s3Notificationを定義しているStackで呼び出す
const bucketNotifications = this.node.findAll()
.filter(x => x.node.defaultChild)
.filter((x: any) => x.cfnResourceType === "Custom::S3BucketNotifications");
for (const resource of bucketNotifications) {
// escape hatchでCustom::S3BucketNotificationsリソースにアクセス
const cfn = resource.node.defaultChild as CfnResource;
cfn.addPropertyDeletionOverride("Properties.SkipDestinationValidation");
}
わからないのでclineに相談したところAspectsというものがあることを教えてもらいました。
Aspectsとは
ベストプラクティスに従っているかどうかチェックしたり、従うための調整を行う方法を紹介します
ということです。
何やら便利な仕組みのようです。
[CDK Aspects](https://docs.aws.amazon.com/cdk/latest/guide/aspects.html) は、特定のスコープ内のすべての construct に対して共通の操作を適用する方法です。
ほう、共通の操作を適用できるので、今回は共通の操作として SkipDestinationValidation
を削除すればいい感じでしょうか。
動作確認
まず冒頭に記載したエラーを再現させます。
最初に準備します。興味がある人は以下にサンプルコードをおいたので興味があれば試してもらえるとよいかと。
レポジトリをcloneします
ベース環境構築 with cdk 2.153.0
cdkをバージョン 2.153.0にします
git checkout aws-cdk-lib@2.153.0
cdkのディレクトリに入り、deployします
cd cdk && ./node_modules/aws-cdk/bin/cdk deploy --all
これで最低限の環境がawsにdeployされます。
エラーを起こす with cdk 2.154.0
cdkをバージョン 2.154.0にします
git checkout aws-cdk-lib@2.153.0
cdk-libを更新する
npm install && ./node_modules/aws-cdk/bin/cdk --version
cdkのディレクトリに入り、diffします
cd cdk && ./node_modules/aws-cdk/bin/cdk diff --all
以下のようにSkipDestinationValidation
falseが追加されます。
その下にはカスタムリソースのlambdaが更新されていることが記載されています。
これでdeployするとエラーになるはずです。
dik diff全出力
Stack CdkStack
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
Conditions
[~] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"il-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]} to {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-3"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-4"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"il-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]}]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}
Resources
[~] Custom::S3BucketNotifications bucket/Notifications bucketNotifications2CB09E7A may be replaced
└─ [+] SkipDestinationValidation (may cause replacement)
└─ false
[~] AWS::Lambda::Function BucketNotificationsHandler050a0587b7544547bf325f094a3db834 BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691
└─ [~] Code
└─ [~] .ZipFile:
├─ [-] import boto3 # type: ignore
import json
import logging
import urllib.request
s3 = boto3.client("s3")
EVENTBRIDGE_CONFIGURATION = 'EventBridgeConfiguration'
CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"]
def handler(event: dict, context):
response_status = "SUCCESS"
error_message = ""
try:
props = event["ResourceProperties"]
notification_configuration = props["NotificationConfiguration"]
managed = props.get('Managed', 'true').lower() == 'true'
stack_id = event['StackId']
old = event.get("OldResourceProperties", {}).get("NotificationConfiguration", {})
if managed:
config = handle_managed(event["RequestType"], notification_configuration)
else:
config = handle_unmanaged(props["BucketName"], stack_id, event["RequestType"], notification_configuration, old)
s3.put_bucket_notification_configuration(Bucket=props["BucketName"], NotificationConfiguration=config)
except Exception as e:
logging.exception("Failed to put bucket notification configuration")
response_status = "FAILED"
error_message = f"Error: {str(e)}. "
finally:
submit_response(event, context, response_status, error_message)
def handle_managed(request_type, notification_configuration):
if request_type == 'Delete':
return {}
return notification_configuration
def handle_unmanaged(bucket, stack_id, request_type, notification_configuration, old):
def get_id(n):
n['Id'] = ''
strToHash=json.dumps(n, sort_keys=True).replace('"Name": "prefix"', '"Name": "Prefix"').replace('"Name": "suffix"', '"Name": "Suffix"')
return f"{stack_id}-{hash(strToHash)}"
def with_id(n):
n['Id'] = get_id(n)
return n
external_notifications = {}
existing_notifications = s3.get_bucket_notification_configuration(Bucket=bucket)
for t in CONFIGURATION_TYPES:
if request_type == 'Update':
old_incoming_ids = [get_id(n) for n in old.get(t, [])]
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not get_id(n) in old_incoming_ids]
elif request_type == 'Delete':
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f"{stack_id}-")]
elif request_type == 'Create':
external_notifications[t] = [n for n in existing_notifications.get(t, [])]
if EVENTBRIDGE_CONFIGURATION in existing_notifications:
external_notifications[EVENTBRIDGE_CONFIGURATION] = existing_notifications[EVENTBRIDGE_CONFIGURATION]
if request_type == 'Delete':
return external_notifications
notifications = {}
for t in CONFIGURATION_TYPES:
external = external_notifications.get(t, [])
incoming = [with_id(n) for n in notification_configuration.get(t, [])]
notifications[t] = external + incoming
if EVENTBRIDGE_CONFIGURATION in notification_configuration:
notifications[EVENTBRIDGE_CONFIGURATION] = notification_configuration[EVENTBRIDGE_CONFIGURATION]
elif EVENTBRIDGE_CONFIGURATION in external_notifications:
notifications[EVENTBRIDGE_CONFIGURATION] = external_notifications[EVENTBRIDGE_CONFIGURATION]
return notifications
def submit_response(event: dict, context, response_status: str, error_message: str):
response_body = json.dumps(
{
"Status": response_status,
"Reason": f"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}",
"PhysicalResourceId": event.get("PhysicalResourceId") or event["LogicalResourceId"],
"StackId": event["StackId"],
"RequestId": event["RequestId"],
"LogicalResourceId": event["LogicalResourceId"],
"NoEcho": False,
}
).encode("utf-8")
headers = {"content-type": "", "content-length": str(len(response_body))}
try:
req = urllib.request.Request(url=event["ResponseURL"], headers=headers, data=response_body, method="PUT")
with urllib.request.urlopen(req) as response:
print(response.read().decode("utf-8"))
print("Status code: " + response.reason)
except Exception as e:
print("send(..) failed executing request.urlopen(..): " + str(e))
└─ [+] import boto3 # type: ignore
import json
import logging
import urllib.request
s3 = boto3.client("s3")
EVENTBRIDGE_CONFIGURATION = 'EventBridgeConfiguration'
CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"]
def handler(event: dict, context):
response_status = "SUCCESS"
error_message = ""
try:
props = event["ResourceProperties"]
notification_configuration = props["NotificationConfiguration"]
managed = props.get('Managed', 'true').lower() == 'true'
skipDestinationValidation = props.get('SkipDestinationValidation', 'false').lower() == 'true'
stack_id = event['StackId']
old = event.get("OldResourceProperties", {}).get("NotificationConfiguration", {})
if managed:
config = handle_managed(event["RequestType"], notification_configuration)
else:
config = handle_unmanaged(props["BucketName"], stack_id, event["RequestType"], notification_configuration, old)
s3.put_bucket_notification_configuration(Bucket=props["BucketName"], NotificationConfiguration=config, SkipDestinationValidation=skipDestinationValidation)
except Exception as e:
logging.exception("Failed to put bucket notification configuration")
response_status = "FAILED"
error_message = f"Error: {str(e)}. "
finally:
submit_response(event, context, response_status, error_message)
def handle_managed(request_type, notification_configuration):
if request_type == 'Delete':
return {}
return notification_configuration
def handle_unmanaged(bucket, stack_id, request_type, notification_configuration, old):
def get_id(n):
n['Id'] = ''
strToHash=json.dumps(n, sort_keys=True).replace('"Name": "prefix"', '"Name": "Prefix"').replace('"Name": "suffix"', '"Name": "Suffix"')
return f"{stack_id}-{hash(strToHash)}"
def with_id(n):
n['Id'] = get_id(n)
return n
external_notifications = {}
existing_notifications = s3.get_bucket_notification_configuration(Bucket=bucket)
for t in CONFIGURATION_TYPES:
if request_type == 'Update':
old_incoming_ids = [get_id(n) for n in old.get(t, [])]
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not get_id(n) in old_incoming_ids]
elif request_type == 'Delete':
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f"{stack_id}-")]
elif request_type == 'Create':
external_notifications[t] = [n for n in existing_notifications.get(t, [])]
if EVENTBRIDGE_CONFIGURATION in existing_notifications:
external_notifications[EVENTBRIDGE_CONFIGURATION] = existing_notifications[EVENTBRIDGE_CONFIGURATION]
if request_type == 'Delete':
return external_notifications
notifications = {}
for t in CONFIGURATION_TYPES:
external = external_notifications.get(t, [])
incoming = [with_id(n) for n in notification_configuration.get(t, [])]
notifications[t] = external + incoming
if EVENTBRIDGE_CONFIGURATION in notification_configuration:
notifications[EVENTBRIDGE_CONFIGURATION] = notification_configuration[EVENTBRIDGE_CONFIGURATION]
elif EVENTBRIDGE_CONFIGURATION in external_notifications:
notifications[EVENTBRIDGE_CONFIGURATION] = external_notifications[EVENTBRIDGE_CONFIGURATION]
return notifications
def submit_response(event: dict, context, response_status: str, error_message: str):
response_body = json.dumps(
{
"Status": response_status,
"Reason": f"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}",
"PhysicalResourceId": event.get("PhysicalResourceId") or event["LogicalResourceId"],
"StackId": event["StackId"],
"RequestId": event["RequestId"],
"LogicalResourceId": event["LogicalResourceId"],
"NoEcho": False,
}
).encode("utf-8")
headers = {"content-type": "", "content-length": str(len(response_body))}
try:
req = urllib.request.Request(url=event["ResponseURL"], headers=headers, data=response_body, method="PUT")
with urllib.request.urlopen(req) as response:
print(response.read().decode("utf-8"))
print("Status code: " + response.reason)
except Exception as e:
print("send(..) failed executing request.urlopen(..): " + str(e))
✨ Number of stacks with differences: 1
NOTICES (What's this? https://github.com/aws/aws-cdk/wiki/CLI-Notices)
32775 (cli): CLI versions and CDK library versions have diverged
Overview: Starting in CDK 2.179.0, CLI versions will no longer be in
lockstep with CDK library versions. CLI versions will now be
released as 2.1000.0 and continue with 2.1001.0, etc.
Affected versions: cli: >=2.0.0 <=2.1005.0
More information at: https://github.com/aws/aws-cdk/issues/32775
If you don’t want to see a notice anymore, use "cdk acknowledge <id>". For example, "cdk acknowledge 32775".
deployします。冒頭に紹介したエラーが出るはずです。
cd cdk && ./node_modules/aws-cdk/bin/cdk deploy --all
が.......
デプロイ成功しちゃった!はて・・・
(レポジトリ内のコードは修正済みなのでエラーになるはずです)
エラー発生に必要な要因
s3のスタックとイベントを設定するスタックを分けるとエラーが再現しました。
今回カスタムリソースが追加され、SkipDestinationValidationパラメータが追加されるものイベント側のスタックでした。以下の感じでしょうか。
- イベントスタックがS3イベントを作ろうとする
- この時S3イベントにSkipDestinationValidationが追加されるため別物とみなされる
- S3イベントがsuffix, prefixの重複で怒る
スタックを分けたところ無事(?)エラーになりました。
エラー全文
✨ Synthesis time: 3.09s
S3Stack: start: Building 0c7b3ebffc12e0b857da13ef775184fef7db10dd886cc60dfc425711f6cc2e73:current_account-current_region
S3Stack: success: Built 0c7b3ebffc12e0b857da13ef775184fef7db10dd886cc60dfc425711f6cc2e73:current_account-current_region
EventStack: start: Building 1e62040c67efe32598b3af212e219cb727380caeec89ac175e892ac16b13a278:current_account-current_region
EventStack: success: Built 1e62040c67efe32598b3af212e219cb727380caeec89ac175e892ac16b13a278:current_account-current_region
S3Stack: start: Publishing 0c7b3ebffc12e0b857da13ef775184fef7db10dd886cc60dfc425711f6cc2e73:current_account-current_region
S3Stack: success: Published 0c7b3ebffc12e0b857da13ef775184fef7db10dd886cc60dfc425711f6cc2e73:current_account-current_region
S3Stack
S3Stack: deploying... [1/2]
S3Stack: creating CloudFormation changeset...
✅ S3Stack
✨ Deployment time: 16.64s
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/S3Stack/e51f7f10-311b-11f0-8126-0eae179da5c7
✨ Total time: 19.74s
EventStack: start: Publishing 1e62040c67efe32598b3af212e219cb727380caeec89ac175e892ac16b13a278:current_account-current_region
EventStack: success: Published 1e62040c67efe32598b3af212e219cb727380caeec89ac175e892ac16b13a278:current_account-current_region
EventStack
EventStack: deploying... [2/2]
EventStack: creating CloudFormation changeset...
8:37:52 AM | UPDATE_FAILED | Custom::S3BucketNotifications | bucketNotifications2CB09E7A
Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if
the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2025/05/14/[$LATEST]fa766933c58149479401dff10511df2c (RequestId: 99c1a4c5-6baa-4530-ba50-77756db7b54a)
8:38:08 AM | UPDATE_FAILED | Custom::S3BucketNotifications | bucketNotifications2CB09E7A
Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if
the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2025/05/14/[$LATEST]d26d3384a07343f0915590ca64858c0d (RequestId: afe8ef94-0b22-45ae-b809-ccdff9e20b4c)
❌ EventStack failed: Error: The stack named EventStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [bucketNotifications2CB09E7A]. ): Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2025/05/14/[
at FullCloudFormationDeployment.monitorDeployment (/Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:447:10567)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Object.deployStack2 [as deployStack] (/Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:450:200276)
at async /Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:450:181698
❌ Deployment failed: Error: The stack named EventStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [bucketNotifications2CB09E7A]. ): Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2025/05/14/[
at FullCloudFormationDeployment.monitorDeployment (/Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:447:10567)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Object.deployStack2 [as deployStack] (/Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:450:200276)
at async /Users/xxxxxxxx/private-repos/cdk-aspects-sample/cdk/node_modules/aws-cdk/lib/index.js:450:181698
NOTICES (What's this? https://github.com/aws/aws-cdk/wiki/CLI-Notices)
32775 (cli): CLI versions and CDK library versions have diverged
Overview: Starting in CDK 2.179.0, CLI versions will no longer be in
lockstep with CDK library versions. CLI versions will now be
released as 2.1000.0 and continue with 2.1001.0, etc.
Affected versions: cli: >=2.0.0 <=2.1005.0
More information at: https://github.com/aws/aws-cdk/issues/32775
If you don’t want to see a notice anymore, use "cdk acknowledge <id>". For example, "cdk acknowledge 32775".
The stack named EventStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [bucketNotifications2CB09E7A]. ): Received response status [FAILED] from custom resource. Message returned: Error: An error occurred (InvalidArgument) when calling the PutBucketNotificationConfiguration operation: Configuration is ambiguously defined. Cannot have overlapping suffixes in two rules if the prefixes are overlapping for the same event type.. See the details in CloudWatch Log Stream: 2025/05/14/[
対策
でようやく対策です。
すごく簡単です。
以下のAspectを作って
import { IAspect, CfnResource } from 'aws-cdk-lib';
import { IConstruct } from 'constructs';
// Aspect定義
export class RemoveSkipDestinationValidationAspect implements IAspect {
visit(node: IConstruct): void {
if (
node instanceof CfnResource &&
node.cfnResourceType === 'Custom::S3BucketNotifications'
) {
node.addPropertyDeletionOverride('SkipDestinationValidation');
}
}
}
スタック作成時に適応するだけです
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { S3Stack } from '../lib/s3-stack';
import { EventStack } from '../lib/event-stack';
import { RemoveSkipDestinationValidationAspect } from '../lib/Aspect'
const app = new cdk.App();
const s3Stack = new S3Stack(app, 'S3Stack', {});
const eventStack = new EventStack(app, 'EventStack', {})
eventStack.addDependency(s3Stack);
// SkipDestinationValidationを削除
cdk.Aspects.of(eventStack).add(new RemoveSkipDestinationValidationAspect());
修正ずみのブランチに切り替えます。
git checkout main
cdk deployする前に前回失敗したイベント更新を復旧しておきます。
cdk diffしてみるとSkipDestinationValidation
の設定が消えていることがわかります。
無事デプロイに成功しました!
./node_modules/aws-cdk/bin/cdk deploy --all
ただこれだとSkipDestinationValidation
を明示的に変更したい場合に困るんでしょうか。
というかどんな時に変更するんだろう・・・?
まとめ
罠にハマり困ってしまいました 💦
CDKはなかなか足が重いところがあり困ることがよくありますね。
こういう時はAspectsが使えそうということがわかりました!
Discussion