-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Add possibility to ignore specific unsupported resource types in CloudFormation #13496
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from localstack import config | ||
| from localstack.aws.api.cloudformation import ChangeSetType | ||
|
|
||
|
|
||
| def should_ignore_unsupported_resource_type( | ||
| resource_type: str, change_set_type: ChangeSetType | ||
| ) -> bool: | ||
| if config.CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES: | ||
| return True | ||
|
|
||
| match change_set_type: | ||
| case ChangeSetType.CREATE: | ||
| return resource_type in config.CFN_IGNORE_UNSUPPORTED_TYPE_CREATE | ||
| case ChangeSetType.UPDATE | ChangeSetType.IMPORT: | ||
| return resource_type in config.CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE | ||
| case _: | ||
| return False |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import textwrap | ||
| import json | ||
|
|
||
| import pytest | ||
| from botocore.exceptions import WaiterError | ||
|
|
||
| from localstack import config | ||
| from localstack.services.cloudformation.engine.v2 import ( | ||
|
|
@@ -22,7 +23,6 @@ | |
| CloudFormationResourcesSupportInLatest, | ||
| ) | ||
| from localstack.utils.strings import short_uid | ||
| from localstack.utils.sync import retry | ||
|
|
||
| UNSUPPORTED_RESOURCE_CASES = [ | ||
| ( | ||
|
|
@@ -86,6 +86,76 @@ def testing_catalog(monkeypatch): | |
| return plugin | ||
|
|
||
|
|
||
| @markers.aws.only_localstack | ||
| def test_ignore_unsupported_resources_toggle(testing_catalog, aws_client, monkeypatch, cleanups): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: in general I would prefer not defining inner functions (especially if they only have one use), and to use the waiters where possible. I don't find them easy to use, and the waiters code is more straightforward to read in the test itself. For the successful cases we swap a function definition and call with a single line, and for the failure cases using the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks I didn't like it too much either but also didn't know about the waiters. Will switch it over :)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've fixed it and also went ahead and fixed the old test to use the waiters. It's much cleaner now :) |
||
| unsupported_resource = "AWS::LatestService::NotSupported" | ||
|
|
||
| # template with one supported and one unsupported resource | ||
| bucket_name = f"cfn-toggle-{short_uid()}" | ||
| template_body = json.dumps( | ||
| { | ||
| "Resources": { | ||
| "SupportedBucket": { | ||
| "Type": "AWS::S3::Bucket", | ||
| "Properties": {"BucketName": bucket_name}, | ||
| }, | ||
| "Unsupported": {"Type": unsupported_resource}, | ||
| }, | ||
| } | ||
| ) | ||
|
|
||
| # 1) ignore lists empty -> change set should fail | ||
| monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES", False) | ||
| monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE", []) | ||
| stack_name_fail = f"stack-fail-{short_uid()}" | ||
| change_set_name_fail = f"cs-{short_uid()}" | ||
| response = aws_client.cloudformation.create_change_set( | ||
| StackName=stack_name_fail, | ||
| ChangeSetName=change_set_name_fail, | ||
| TemplateBody=template_body, | ||
| ChangeSetType="CREATE", | ||
| ) | ||
| cs_id_fail, stack_id_fail = response["Id"], response["StackId"] | ||
|
|
||
| waiter = aws_client.cloudformation.get_waiter("change_set_create_complete") | ||
| with pytest.raises(WaiterError) as exc_info: | ||
| waiter.wait( | ||
| ChangeSetName=cs_id_fail, | ||
| ) | ||
|
|
||
| assert exc_info.value.last_response["Status"] == "FAILED" | ||
| status_reason = exc_info.value.last_response["StatusReason"] | ||
| assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in status_reason | ||
| assert unsupported_resource in status_reason | ||
| cleanups.append(lambda: aws_client.cloudformation.delete_change_set(ChangeSetName=cs_id_fail)) | ||
| cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id_fail)) | ||
|
|
||
| # 2) add unsupported resource to create ignore list -> deployment succeeds and bucket is present | ||
| monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE", [unsupported_resource]) | ||
| stack_name_ok = f"stack-ok-{short_uid()}" | ||
| change_set_name_ok = f"cs-{short_uid()}" | ||
| response = aws_client.cloudformation.create_change_set( | ||
| StackName=stack_name_ok, | ||
| ChangeSetName=change_set_name_ok, | ||
| TemplateBody=template_body, | ||
| ChangeSetType="CREATE", | ||
| ) | ||
| cs_id_ok, stack_id_ok = response["Id"], response["StackId"] | ||
|
|
||
| waiter.wait( | ||
| ChangeSetName=cs_id_ok, | ||
| ) | ||
| aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id_ok) | ||
| aws_client.cloudformation.get_waiter("stack_create_complete").wait( | ||
| StackName=stack_name_ok, | ||
| ) | ||
|
|
||
| buckets = aws_client.s3.list_buckets()["Buckets"] | ||
| assert any(b["Name"] == bucket_name for b in buckets) | ||
|
|
||
| cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id_ok)) | ||
|
|
||
|
|
||
| @markers.aws.only_localstack | ||
| @pytest.mark.parametrize( | ||
| "unsupported_resource, expected_service", | ||
|
|
@@ -95,13 +165,10 @@ def test_catalog_reports_unsupported_resources_in_stack_status( | |
| testing_catalog, aws_client, unsupported_resource, expected_service, monkeypatch, cleanups | ||
| ): | ||
| monkeypatch.setattr(config, "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES", False) | ||
| template_body = textwrap.dedent( | ||
| f""" | ||
| AWSTemplateFormatVersion: '2010-09-09' | ||
| Resources: | ||
| Unsupported: | ||
| Type: {unsupported_resource} | ||
| """ | ||
| template_body = json.dumps( | ||
| { | ||
| "Resources": {"Unsupported": {"Type": unsupported_resource}}, | ||
| } | ||
| ) | ||
|
|
||
| stack_name = f"stack-{short_uid()}" | ||
|
|
@@ -118,29 +185,22 @@ def test_catalog_reports_unsupported_resources_in_stack_status( | |
| change_set_id = response["Id"] | ||
| stack_id = response["StackId"] | ||
|
|
||
| def _describe_failed_change_set(): | ||
| result = aws_client.cloudformation.describe_change_set(ChangeSetName=change_set_id) | ||
| status = result["Status"] | ||
| if status == "FAILED": | ||
| return result | ||
| if status == "CREATE_COMPLETE": | ||
| pytest.fail("expected change set creation to fail for unsupported resource") | ||
| raise Exception("gave up on waiting for change set creation to fail") | ||
|
|
||
| change_set = retry(_describe_failed_change_set, retries=20, sleep=2) | ||
|
|
||
| status_reason = change_set.get("StatusReason", "") | ||
| waiter = aws_client.cloudformation.get_waiter("change_set_create_complete") | ||
| with pytest.raises(WaiterError) as exc_info: | ||
| waiter.wait( | ||
| ChangeSetName=change_set_id, | ||
| ) | ||
| assert exc_info.value.last_response["Status"] == "FAILED" | ||
| status_reason = exc_info.value.last_response["StatusReason"] | ||
| assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in status_reason | ||
| assert unsupported_resource in status_reason | ||
|
|
||
| def _describe_failed_stack(): | ||
| stack = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0] | ||
| stack_status = stack["StackStatus"] | ||
| if stack_status in {"CREATE_FAILED", "ROLLBACK_COMPLETE"}: | ||
| return stack | ||
| raise Exception("gave on waiting for stack creation to fail for unsupported resource") | ||
| with pytest.raises(WaiterError) as exc_info: | ||
| aws_client.cloudformation.get_waiter("stack_create_complete").wait( | ||
| StackName=stack_id, | ||
| ) | ||
|
|
||
| stack_description = retry(_describe_failed_stack, retries=30, sleep=2) | ||
| stack_description = exc_info.value.last_response["Stacks"][0] | ||
| stack_status_reason = stack_description.get("StackStatusReason", "") | ||
| assert ChangeSetResourceSupportChecker.TITLE_MESSAGE in stack_status_reason | ||
| assert unsupported_resource in stack_status_reason | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.