Skip to content

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

Conversation

etiago
Copy link
Contributor

@etiago etiago commented May 17, 2025

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:

awslocal sqs create-queue --queue-name test-sqs-queue
QUEUE_ARN=$(awslocal sqs get-queue-attributes \
  --queue-url http://sqs.eu-west-2.localhost.localstack.cloud:4566/000000000000/test-sqs-queue \
  --attribute-names QueueArn \
  --query 'Attributes.QueueArn' \
  --output text)
awslocal events create-event-bus --name test-event-bus
awslocal events put-rule \
  --name rule-to-sqs \
  --event-bus-name test-event-bus \
  --event-pattern '{"source": ["test-source"]}'
RULE_ARN=$(aws events describe-rule \
  --name rule-to-sqs \
  --event-bus-name test-event-bus \
  --query 'Arn' \
  --output text)
awslocal events put-targets \
  --rule rule-to-sqs \
  --event-bus-name test-event-bus \
  --targets "Id"="1","Arn"="$QUEUE_ARN"

# Verify that the correct target ARN returns the rule:
awslocal events list-rule-names-by-target --target-arn $QUEUE_ARN --event-bus-name test-event-bus

# Verify that a non-existant target ARN returns an empty rule list:
awslocal events list-rule-names-by-target --target-arn "non-existant-arn" --event-bus-name test-event-bus

etiago added 2 commits May 17, 2025 05:56
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.
Copy link
Collaborator

@localstack-bot localstack-bot left a 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.

@localstack-bot
Copy link
Collaborator

localstack-bot commented May 17, 2025

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@etiago etiago changed the title Te/implement list rule names by target Feature: implement list rule names by target May 17, 2025
@etiago
Copy link
Contributor Author

etiago commented May 17, 2025

I have read the CLA Document and I hereby sign the CLA

localstack-bot added a commit that referenced this pull request May 17, 2025
@dominikschubert dominikschubert self-requested a review May 19, 2025 06:10
@dominikschubert dominikschubert added semver: minor Non-breaking changes which can be included in minor releases, but not in patch releases aws:events Amazon EventBridge labels May 19, 2025
Copy link
Member

@dominikschubert dominikschubert left a 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 👍

Comment on lines 1816 to 1842
# 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
Copy link
Member

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(...))

Copy link
Contributor Author

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(
Copy link
Member

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?

Copy link
Contributor Author

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.

Comment on lines 1929 to 1930
assert len(response["RuleNames"]) == 2
assert "NextToken" in response
Copy link
Member

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.

Copy link
Contributor Author

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>"))
Copy link
Member

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

Suggested change
snapshot.add_transformer(snapshot.transform.regex(queue_arn, "<queue-arn>"))

Similar with the other test cases

Copy link
Contributor Author

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}>"))
Copy link
Member

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

Suggested change
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>"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Comment on lines 1906 to 1908
# Transform NextToken to a fixed value for snapshot comparison
if "NextToken" in response:
response["NextToken"] = "<next-token>"
Copy link
Member

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?

Copy link
Contributor Author

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.
Copy link
Member

@dominikschubert dominikschubert left a 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 🥳

@dominikschubert dominikschubert merged commit 73789e1 into localstack:master May 20, 2025
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aws:events Amazon EventBridge semver: minor Non-breaking changes which can be included in minor releases, but not in patch releases
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants