-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Feature: implement list rule names by target #12632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: implement list rule names by target #12632
Conversation
Tests the following cases: - Listing rule names for a target (filtering out irrelevant rule). - Usage of limit and pagination. - Listing rule names when no matches for given target.
Also brings in a new _get_limited_list_and_next_token for convenience of filtering and paging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome to LocalStack! Thanks for raising your first Pull Request and landing in your contributions. Our team will reach out with any reviews or feedbacks that we have shortly. We recommend joining our Slack Community and share your PR on the #community channel to share your contributions with us. Please make sure you are following our contributing guidelines and our Code of Conduct.
All contributors have signed the CLA ✍️ ✅ |
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @etiago ! Great job on the first iteration here and thanks for tackling this. 🚀
Showing great attention already to cleaning up resources and covering different edge cases 👏
There's a few minor nits and things we should address before it being ready to merge, particularly for the tests to make sure they follow our best practices for parity testing.
The comments are not added exhaustively, so please extrapolate accordingly and adapt similar cases to the ones commented one.
Let me know if you have any questions 👍
# Clean up targets and rules | ||
for rule_name in rule_names + [other_rule]: | ||
try: | ||
# Remove targets first (only for rules that have targets) | ||
if rule_name in rule_names: # other_rule has no targets | ||
aws_client.events.remove_targets( | ||
Rule=rule_name, | ||
EventBusName=bus_name, | ||
Ids=[f"target-{rule_names.index(rule_name)}"], | ||
) | ||
# Delete rule | ||
aws_client.events.delete_rule( | ||
Name=rule_name, | ||
EventBusName=bus_name, | ||
Force=True, | ||
) | ||
except aws_client.events.exceptions.ResourceNotFoundException: | ||
# Ignore if rule or event bus was already deleted | ||
pass | ||
|
||
# Delete custom event bus if we created one | ||
if bus_name != "default": | ||
try: | ||
aws_client.events.delete_event_bus(Name=bus_name) | ||
except aws_client.events.exceptions.ResourceNotFoundException: | ||
# Ignore if event bus was already deleted | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 A general pattern we try to follow, is to be very defensive about cleanups to avoid leaking resources. This is particularly important when running tests against AWS, where they might continue incurring cost when leaked.
In that spirit we typically avoid cleanup code like this:
# creating resource_1
# creating dependent resource_2
...
delete_resource_2(...)
delete_resource_1(...)
...
even when wrapped in try/catch blocks.
Instead we have a pytest fixture called cleanups
which is a simple list of callables that are called in reverse order of insertion when the test has finished executing.
Given my example above, this would correspond to:
# creating resource_1
...
cleanups.append(lambda: delete_resource_1(...))
...
# creating dependent resource_2
...
cleanups.append(lambda: delete_resource_2(...))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense.
Having looked through my test again, and also looking through some of the existing tests, I think I don't even need to do this via cleanups
.
Best as I can tell, looking at the events_create_event_bus
fixture here for example, events_create_event_bus
actually yields a function which when I call it to create event buses, it also adds them to a list to keep track of them. And on tear down, it cycles through that list, cleaning them up.
So I think I can safely remove the whole cleanup code and as long as I use the existing fixtures to create resources, my tests don't need to explicitly worry with cleaningup.
|
||
@markers.aws.validated | ||
@pytest.mark.parametrize("bus_name", ["custom", "default"]) | ||
def test_list_rule_names_by_target_no_matches( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Is there a particular reason this deviates from the cleanup structure in your other tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason...
To be completely honest, looks like I "forgot" to do it for this test.
In hindsight, after reading your other comment above about cleanups, I now realise that this test is actually the correct one in that as long as resources are created using the existing fixtures, those resources are already tracked and automatically cleaned up at the end of the test.
assert len(response["RuleNames"]) == 2 | ||
assert "NextToken" in response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: While asserts are ok to leave in, we typically try to avoid it for testing API responses as it's more brittle than the snapshots and while technically redundant, it does come with an additional maintenance overhead, particularly when rewriting / refactoring a test. For parity tests a good guideline is to keep them mostly for asserting behavior or if you want to be extremely explicit in what edge case the test case is covering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good point... I just had a habit of asserting at the end of tests, but you're right, since we have the snapshots which do a 1:1 validation, the asserts add no value and I see what you mean about the maintenance cost going forwards.
Will remove these.
queue_name = f"queue-{short_uid()}" | ||
queue_url = sqs_create_queue(QueueName=queue_name) | ||
queue_arn = sqs_get_queue_arn(queue_url) | ||
snapshot.add_transformer(snapshot.transform.regex(queue_arn, "<queue-arn>")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't actually do anything here since we're not capturing any response with a queue ARN
snapshot.add_transformer(snapshot.transform.regex(queue_arn, "<queue-arn>")) |
Similar with the other test cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I just realised I left a few of these throughout the tests, but you're right, the response only ever contains the rule names, so there's no point in transforming anything other than the rules.
Will remove all the useless ones.
|
||
# Add the SQS queue as a target for this rule | ||
target_id = f"target-{i}" | ||
snapshot.add_transformer(snapshot.transform.regex(target_id, f"<target-id-{i}>")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like with the SQS ARN and EventBus name, we don't capture target ids in the response
snapshot.add_transformer(snapshot.transform.regex(target_id, f"<target-id-{i}>")) |
if bus_name == "custom": | ||
bus_name = f"bus-{short_uid()}" | ||
events_create_event_bus(Name=bus_name) | ||
snapshot.add_transformer(snapshot.transform.regex(bus_name, "<bus-name>")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
snapshot.add_transformer(snapshot.transform.regex(bus_name, "<bus-name>")) |
Same case as with the SQS ARN, we're not actually capturing a bus name anywhere in the collected snapshots here.
# Transform NextToken to a fixed value for snapshot comparison | ||
if "NextToken" in response: | ||
response["NextToken"] = "<next-token>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: Typically we'd solve this with a transformer. Any particular reason to avoid doing that here, as well as in the other cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason... I was stuck thinking of the regex
transformer which wouldn't work here since the NextToken
is different each time.
But since you mentioned this, I now spotted we have a jsonpath
transformer which makes this much simpler, since I can now just have a single
snapshot.add_transformer(
snapshot.transform.jsonpath(
jsonpath="$..NextToken",
value_replacement="<next-token>",
reference_replacement=True,
)
)
And this replaces all instances of NextToken
and keeps them numbered when verifying multiple times.
I regenerated the snapshot and it's all good now.
- Gets rid of useless transforms for results that aren't in the output. - Gets rid of explicit cleanups, since all resources are created by self-cleaning fixtures. - Changes NextToken to use a cleaner transform instead of explicitly changing it around. - Gets rid of asserts since they're redundant, given the snapshots do all of the matching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing the issues and great work on identifying that fixtures are doing their own cleanup already, reducing the need for manual cleanup!
Congratulations on the first contribution to LocalStack 🥳
Motivation
I'm raising this PR to increase LocalStack's coverage of AWS APIs. Currently ListRuleNamesByTarget is not implemented.
It fixes issue #12631 .
Changes
This PR makes it so that instead of returning
NotImplementedError
, LocalStack will start returning the actual list of EventBus rules for a given target.Testing
The changes are covered by snapshot/parity tests.
The happy path can also be tested manually by spinning up LocalStack and following these steps: