Skip to content

Commit 5e315c8

Browse files
Reference data event bus through SSM parameter (#731) (#736)
While testing the pipeline migration, we hit an issue where the event bus needs to be renamed, as it is using the pipeline stack as part of its name. This creates a cross stack reference issue since it is being referenced by several other stacks, and you cannot change a stack export value while it is still being referenced by other stacks. In order to address this, this PR replaces the direct reference with an SSM parameter, which can be changed without causing the stacks to fail deployment. This PR will need to be merged and deployed first, before the subsequent pipeline change can be run through. This change is a precursor to the following tickets: #705 #580 #424 Co-authored-by: Joshua Kravitz <[email protected]>
1 parent 310fe2b commit 5e315c8

File tree

6 files changed

+81
-15
lines changed

6 files changed

+81
-15
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from aws_cdk.aws_events import EventBus
2+
from aws_cdk.aws_ssm import StringParameter
3+
from constructs import Construct
4+
5+
DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME = '/deployment/event-bridge/event-bus/data-event-bus-arn'
6+
7+
8+
class SSMParameterUtility:
9+
"""
10+
Utility class for SSM parameter operations.
11+
12+
This class provides static methods for common SSM parameter operations,
13+
such as loading resources from SSM parameters to bypass cross-stack references.
14+
"""
15+
16+
@staticmethod
17+
def set_data_event_bus_arn_ssm_parameter(scope: Construct, data_event_bus: EventBus) -> StringParameter:
18+
return StringParameter(
19+
scope,
20+
'DataEventBusArnParameter',
21+
parameter_name=DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME,
22+
string_value=data_event_bus.event_bus_arn,
23+
)
24+
25+
@staticmethod
26+
def load_data_event_bus_from_ssm_parameter(scope: Construct) -> EventBus:
27+
"""
28+
Load the data event bus from an SSM parameter.
29+
30+
This pattern breaks cross-stack references by storing and retrieving
31+
the event bus ARN in SSM Parameter Store rather than using a direct reference,
32+
which helps avoid issues with CloudFormation stack updates.
33+
34+
Args:
35+
scope: The CDK construct scope
36+
37+
Returns:
38+
The EventBus construct
39+
"""
40+
data_event_bus_arn = StringParameter.from_string_parameter_name(
41+
scope,
42+
'DataEventBusArnParameter',
43+
string_parameter_name=DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME,
44+
)
45+
46+
return EventBus.from_event_bus_arn(scope, 'DataEventBus', event_bus_arn=data_event_bus_arn.string_value)

backend/compact-connect/stacks/api_stack/v1_api/query_providers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from cdk_nag import NagSuppressions
1919
from common_constructs.nodejs_function import NodejsFunction
2020
from common_constructs.python_function import PythonFunction
21+
from common_constructs.ssm_parameter_utility import SSMParameterUtility
2122
from common_constructs.stack import Stack
2223

2324
from stacks import persistent_stack as ps
@@ -47,13 +48,17 @@ def __init__(
4748
self.api_model = api_model
4849

4950
stack: Stack = Stack.of(resource)
51+
52+
# Load the data event bus from SSM parameter instead of direct reference
53+
data_event_bus = SSMParameterUtility.load_data_event_bus_from_ssm_parameter(stack)
54+
5055
lambda_environment = {
5156
'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name,
5257
'PROV_FAM_GIV_MID_INDEX_NAME': persistent_stack.provider_table.provider_fam_giv_mid_index_name,
5358
'PROV_DATE_OF_UPDATE_INDEX_NAME': persistent_stack.provider_table.provider_date_of_update_index_name,
5459
'SSN_TABLE_NAME': persistent_stack.ssn_table.table_name,
5560
'SSN_INDEX_NAME': persistent_stack.ssn_table.ssn_index_name,
56-
'EVENT_BUS_NAME': persistent_stack.data_event_bus.event_bus_name,
61+
'EVENT_BUS_NAME': data_event_bus.event_bus_name,
5762
'RATE_LIMITING_TABLE_NAME': persistent_stack.rate_limiting_table.table_name,
5863
'USER_POOL_ID': persistent_stack.staff_users.user_pool_id,
5964
'EMAIL_NOTIFICATION_SERVICE_LAMBDA_NAME': persistent_stack.email_notification_service_lambda.function_name,
@@ -83,7 +88,7 @@ def __init__(
8388
self._add_deactivate_privilege(
8489
method_options=admin_method_options,
8590
provider_data_table=persistent_stack.provider_table,
86-
event_bus=persistent_stack.data_event_bus,
91+
event_bus=data_event_bus,
8792
email_service_lambda=persistent_stack.email_notification_service_lambda,
8893
lambda_environment=lambda_environment,
8994
)

backend/compact-connect/stacks/ingest_stack.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from aws_cdk import Duration
66
from aws_cdk.aws_cloudwatch import Alarm, ComparisonOperator, Metric, Stats, TreatMissingData
77
from aws_cdk.aws_cloudwatch_actions import SnsAction
8-
from aws_cdk.aws_events import EventPattern, Rule
8+
from aws_cdk.aws_events import EventBus, EventPattern, Rule
99
from aws_cdk.aws_events_targets import SqsQueue
1010
from cdk_nag import NagSuppressions
1111
from common_constructs.python_function import PythonFunction
1212
from common_constructs.queued_lambda_processor import QueuedLambdaProcessor
13+
from common_constructs.ssm_parameter_utility import SSMParameterUtility
1314
from common_constructs.stack import AppStack, Stack
1415
from constructs import Construct
1516

@@ -27,9 +28,11 @@ def __init__(
2728
**kwargs,
2829
):
2930
super().__init__(scope, construct_id, environment_name=environment_name, **kwargs)
30-
self._add_v1_ingest_chain(persistent_stack)
31+
# We explicitly get the event bus arn from parameter store, to avoid issues with cross stack updates
32+
data_event_bus = SSMParameterUtility.load_data_event_bus_from_ssm_parameter(self)
33+
self._add_v1_ingest_chain(persistent_stack, data_event_bus)
3134

32-
def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
35+
def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus):
3336
ingest_handler = PythonFunction(
3437
self,
3538
'V1IngestHandler',
@@ -39,14 +42,14 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
3942
handler='ingest_license_message',
4043
timeout=Duration.minutes(1),
4144
environment={
42-
'EVENT_BUS_NAME': persistent_stack.data_event_bus.event_bus_name,
45+
'EVENT_BUS_NAME': data_event_bus.event_bus_name,
4346
'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name,
4447
**self.common_env_vars,
4548
},
4649
alarm_topic=persistent_stack.alarm_topic,
4750
)
4851
persistent_stack.provider_table.grant_read_write_data(ingest_handler)
49-
persistent_stack.data_event_bus.grant_put_events_to(ingest_handler)
52+
data_event_bus.grant_put_events_to(ingest_handler)
5053

5154
NagSuppressions.add_resource_suppressions_by_path(
5255
Stack.of(ingest_handler.role),
@@ -90,7 +93,7 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
9093
ingest_rule = Rule(
9194
self,
9295
'V1IngestEventRule',
93-
event_bus=persistent_stack.data_event_bus,
96+
event_bus=data_event_bus,
9497
event_pattern=EventPattern(detail_type=['license.ingest']),
9598
targets=[SqsQueue(processor.queue, dead_letter_queue=processor.dlq)],
9699
)
@@ -103,7 +106,7 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
103106
namespace='AWS/Events',
104107
metric_name='FailedInvocations',
105108
dimensions_map={
106-
'EventBusName': persistent_stack.data_event_bus.event_bus_name,
109+
'EventBusName': data_event_bus.event_bus_name,
107110
'RuleName': ingest_rule.rule_name,
108111
},
109112
period=Duration.minutes(5),

backend/compact-connect/stacks/persistent_stack/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from common_constructs.nodejs_function import NodejsFunction
1616
from common_constructs.python_function import COMMON_PYTHON_LAMBDA_LAYER_SSM_PARAMETER_NAME
1717
from common_constructs.security_profile import SecurityProfile
18+
from common_constructs.ssm_parameter_utility import SSMParameterUtility
1819
from common_constructs.stack import AppStack
1920
from constructs import Construct
2021

@@ -106,7 +107,18 @@ def __init__(
106107
auto_delete_objects=removal_policy == RemovalPolicy.DESTROY,
107108
)
108109

109-
self.data_event_bus = EventBus(self, 'DataEventBus')
110+
# This resource should not be referenced directly as a cross stack reference, any reference should
111+
# be made through the SSM parameter
112+
self._data_event_bus = EventBus(self, 'DataEventBus')
113+
# We Store the data event bus name in SSM Parameter Store
114+
# to avoid issues with cross stack references due to the fact that
115+
# you can't update a CloudFormation exported value that is being referenced by a resource in another stack.
116+
self.data_event_bus_arn_ssm_parameter = SSMParameterUtility.set_data_event_bus_arn_ssm_parameter(
117+
self, self._data_event_bus
118+
)
119+
# TODO - these are needed until pipeline migration effort is complete # noqa: FIX002
120+
self.export_value(self._data_event_bus.event_bus_arn)
121+
self.export_value(self._data_event_bus.event_bus_name)
110122

111123
self._add_data_resources(removal_policy=removal_policy)
112124
self._add_migrations()
@@ -241,7 +253,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
241253
self,
242254
'SSNTable',
243255
removal_policy=removal_policy,
244-
data_event_bus=self.data_event_bus,
256+
data_event_bus=self._data_event_bus,
245257
alarm_topic=self.alarm_topic,
246258
)
247259

@@ -255,7 +267,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
255267
bucket_encryption_key=self.ssn_table.key,
256268
removal_policy=removal_policy,
257269
auto_delete_objects=removal_policy == RemovalPolicy.DESTROY,
258-
event_bus=self.data_event_bus,
270+
event_bus=self._data_event_bus,
259271
license_preprocessing_queue=self.ssn_table.preprocessor_queue.queue,
260272
license_upload_role=self.ssn_table.license_upload_role,
261273
)
@@ -302,7 +314,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
302314
scope=self,
303315
construct_id='DataEventTable',
304316
encryption_key=self.shared_encryption_key,
305-
event_bus=self.data_event_bus,
317+
event_bus=self._data_event_bus,
306318
alarm_topic=self.alarm_topic,
307319
removal_policy=removal_policy,
308320
)

backend/compact-connect/stacks/persistent_stack/data_event_table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def __init__(
116116
namespace='AWS/Events',
117117
metric_name='FailedInvocations',
118118
dimensions_map={
119-
'EventBusName': stack.data_event_bus.event_bus_name,
119+
'EventBusName': event_bus.event_bus_name,
120120
'RuleName': event_receiver_rule.rule_name,
121121
},
122122
period=Duration.minutes(5),

backend/compact-connect/tests/app/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def _inspect_ssn_table(self, persistent_stack: PersistentStack, persistent_stack
212212

213213
def _inspect_data_events_table(self, persistent_stack: PersistentStack, persistent_stack_template: Template):
214214
# Ensure our DataEventTable and queues are created
215-
event_bus_logical_id = persistent_stack.get_logical_id(persistent_stack.data_event_bus.node.default_child)
215+
event_bus_logical_id = persistent_stack.get_logical_id(persistent_stack._data_event_bus.node.default_child) # noqa: SLF001 private_member_access
216216
queue_logical_id = persistent_stack.get_logical_id(
217217
persistent_stack.data_event_table.event_processor.queue.node.default_child
218218
)

0 commit comments

Comments
 (0)