Skip to content

Conversation

maxhoheiser
Copy link
Member

Motivation

InputTransformer map values from the event to keys defined in InputPathMap and these can then be used in InputTemplate to replace placeholders.
These placeholders can also be in a continuous string "string/" this was failing in localstack as raised here #11126

Changes

  • add support for string nested placeholders in json input templates
  • add support for string nested placeholders in string input templates

@maxhoheiser maxhoheiser added aws:events Amazon EventBridge semver: minor Non-breaking changes which can be included in minor releases, but not in patch releases labels Dec 6, 2024
@maxhoheiser maxhoheiser self-assigned this Dec 6, 2024
@maxhoheiser maxhoheiser requested a review from joe4dev as a code owner December 6, 2024 12:30
Copy link

github-actions bot commented Dec 6, 2024

LocalStack Community integration with Pro

    2 files  ±    0      2 suites  ±0   1h 8m 41s ⏱️ - 42m 32s
2 364 tests  - 1 540  2 269 ✅  - 1 328  95 💤  - 212  0 ❌ ±0 
2 366 runs   - 1 540  2 269 ✅  - 1 328  97 💤  - 212  0 ❌ ±0 

Results for commit bf6335f. ± Comparison against base commit 5399278.

This pull request removes 1555 and adds 15 tests. Note that renamed tests count towards both.
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_lambda_dynamodb
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_opensearch_crud
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_search_books
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_setup
tests.aws.scenario.kinesis_firehose.test_kinesis_firehose.TestKinesisFirehoseScenario ‑ test_kinesis_firehose_s3
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_destination_sns
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_infra
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_prefill_dynamodb_table
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]
…
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement["<listmulti> multiple list items"]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement["<listsingle> single list item <listmulti> multiple list items <systemstring> system account id <payload> payload <userId> user id"]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement["<listsingle> single list item"]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement["Payload of <payload> with path users-service/users/<userId> and <userId>"]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"id" : "<userId>"}]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"id" : <userId>}]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"method": "PUT", "nested": {"level1": {"level2": {"level3": "users-service/users/<userId>"} } }, "bod": "<userId>"}]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"method": "PUT", "path": "users-service/users/<userId>", "bod": <payload>}]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"method": "PUT", "path": "users-service/users/<userId>", "bod": [<userId>, "hardcoded"]}]
tests.aws.services.events.test_events_inputs.TestInputTransformer ‑ test_input_transformer_nested_keys_replacement[{"method": "PUT", "path": "users-service/users/<userId>", "id": <userId>, "body": <payload>}]
…

♻️ This comment has been updated with latest results.

@bentsku bentsku self-requested a review December 9, 2024 16:16
Copy link
Contributor

@bentsku bentsku left a comment

Choose a reason for hiding this comment

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

Nice fix!

I believe we might still have a few corner cases to cover, we could discover them with some parametrized unit tests for the is_nested_in_string function, and maybe even the replace_template_placeholders one.

I think we're not properly replacing dict values inside JSON templates string fields, and I'm not 100% certain of the is_nested_in_string check if the matched value is between quotes with nothing else, and it'd be great to validate those.

This documentation part is pretty useful to try to understand edge cases: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html#eb-transform-input-issues

I think we should have all the cases in this table: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html#eb-transform-target-input-template as part of unit tests for replace_template_placeholders, as they are pretty well defined. Some of those could maybe be tackled in a follow up, but it'd be good to know how far we support.

Thanks!

@@ -248,3 +248,32 @@ def get_trace_header_encoded_region_account(
return json.dumps({"original_id": original_id, "original_account": source_account_id})
else:
return json.dumps({"original_account": source_account_id})


def is_nested_in_string(template, match) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

I would like to get unit tests for this function, as it is quite complex and could break in unexpected ways, and just to validate its limits. Do you think that'd be possible to add to the PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

added unit test for the function test_is_nested_in_string

Copy link
Contributor

@bentsku bentsku Dec 16, 2024

Choose a reason for hiding this comment

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

Awesome, thanks! I'll wait for the pipeline to be green and review the changes today. Thank you!

Comment on lines 88 to 96
if is_json_template:
if is_nested_in_string(template, match):
return value # Return raw string for nested placeholders
else:
return to_json_str(value)
if isinstance(value, datetime.datetime):
return event_time_to_time_string(value)
if isinstance(value, dict):
return dict_to_simple_string(value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the AWS documentation, it seems that if you set a dict value inside a string in a json_template, it will also strip the quotes out of it, like it would do in a regular string templates. It would be good to validate this in AWS.

I think the dict_to_simple_string should also be used in the is_nested_in_string condition if the value is a string, because now you'll return a dict out of the replace function.

Copy link
Member Author

@maxhoheiser maxhoheiser Dec 13, 2024

Choose a reason for hiding this comment

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

If I understand you correctly this scenario should be covered by your proposed test cast '{"method": "PUT", "path": "users-service/users/<payload>", "bod": "<payload>"}, since payload is a dictionary and it is set in the middle part in the string "users-service/users/<payload>"

Copy link
Member Author

Choose a reason for hiding this comment

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

This will interestingly fail for AWS since it will not be correctly transformed since it would result in a string that is not a json string but the system expects a json string

I added a test for this failure also

Comment on lines 473 to 502
[
'{"method": "PUT", "path": "users-service/users/<userId>", "bod": <payload>}',
'"Payload of <payload> with path users-service/users/<userId>"',
],
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a few cases here, just to test the limit, and some are given by the docs:

  • '{"method": "PUT", "path": "users-service/users/<payload>", "bod": "<payload>"}
  • '{"method": "PUT", "path": "users-service/users/<userId>", "bod": "<userId>"}',
  • '{"method": "PUT", "path": "users-service/users/<userId>", "bod": [<userId>, "hardcoded"]}',

There's also this big one with multiline text in the documentation that raises questions for me:
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html#eb-transform-input-issues

{
  "detail": {
    "severity": ["HIGH"],
    "status": ["ACTIVE"]
  },
  "detail-type": ["Inspector2 Finding"],
  "source": ["inspector2"]
}

Input Path

{
  "account": "$.detail.awsAccountId",
  "ami": "$.detail.resources[0].details.awsEc2Instance.imageId",
  "arn": "$.detail.findingArn",
  "description": "$.detail.description",
  "instance": "$.detail.resources[0].id",
  "platform": "$.detail.resources[0].details.awsEc2Instance.platform",
  "region": "$.detail.resources[0].region",
  "severity": "$.detail.severity",
  "time": "$.time",
  "title": "$.detail.title",
  "type": "$.detail.type"
}

Input template

"<severity> severity finding <title>"
"Description: <description>"
"ARN: \"<arn>\""
"Type: <type>"
"AWS Account: <account>"
"Region: <region>"
"EC2 Instance: <instance>"
"Platform: <platform>"
"AMI: <ami>"

Copy link
Member Author

Choose a reason for hiding this comment

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

added something similar to as a test case also with nested lists and target replacement values

):
return False

return left_quote != -1 and template[left_quote + 1 : right_quote].strip() != match.group(0)
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure I fully grasp this condition, does it mean that if we have "<placeholder>" we want to return False and add quotes to the value again? This might be worth to test it

@@ -273,3 +273,7 @@ def is_nested_in_string(template, match) -> bool:
return False

return left_quote != -1 and template[left_quote + 1 : right_quote].strip() != match.group(0)


def dict_to_simple_string(d):
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I missed this one too: I think this does not cover all use case if there are nested values here.
It seems what AWS does is stripped all quotes from the JSON string, it might be easier that way.

Because what if you have a nested dict values here? I don't think that works, same if v is a list with strings inside.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a test case for nested dicts in string transformer '{"method": "PUT", "nested": {"level1": {"level2": {"level3": "users-service/users/<userId>"} } }, "bod": "<userId>"}',

@maxhoheiser maxhoheiser force-pushed the bugfix/eventbridge/transformer-issue-with-nested-key branch from 7b32f39 to 1ca23e1 Compare December 13, 2024 12:27
@maxhoheiser maxhoheiser force-pushed the bugfix/eventbridge/transformer-issue-with-nested-key branch from 0b5aec2 to 29cfd7f Compare December 20, 2024 13:20
@maxhoheiser maxhoheiser force-pushed the bugfix/eventbridge/transformer-issue-with-nested-key branch from a9edd1a to f96f363 Compare December 27, 2024 10:25
Copy link
Contributor

@bentsku bentsku left a comment

Choose a reason for hiding this comment

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

Wow, quite a lot of coverage now! Thank you for addressing the comments, I know it was a lot 😅 I think this is really great, thanks for fixing this issue! This was quite the rabbit hole...
I only have a small nit regarding the log statement dumping JSON.

I've done a run for -ext, and it was all green, so we should be on the safe side! LGTM! And congrats for this quite big addition! 😄

@maxhoheiser maxhoheiser merged commit 3e1471a into master Jan 2, 2025
31 checks passed
@maxhoheiser maxhoheiser deleted the bugfix/eventbridge/transformer-issue-with-nested-key branch January 2, 2025 12:51
@alexrashed alexrashed mentioned this pull request Jan 7, 2025
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.

2 participants