Skip to content

Reference data event bus through SSM parameter #731

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions backend/compact-connect/common_constructs/ssm_parameter_utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from aws_cdk.aws_events import EventBus
from aws_cdk.aws_ssm import StringParameter
from constructs import Construct

DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME = '/deployment/event-bridge/event-bus/data-event-bus-arn'


class SSMParameterUtility:
"""
Utility class for SSM parameter operations.

This class provides static methods for common SSM parameter operations,
such as loading resources from SSM parameters to bypass cross-stack references.
"""

@staticmethod
def set_data_event_bus_arn_ssm_parameter(scope: Construct, data_event_bus: EventBus) -> StringParameter:
return StringParameter(
scope,
'DataEventBusArnParameter',
parameter_name=DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME,
string_value=data_event_bus.event_bus_arn,
)

@staticmethod
def load_data_event_bus_from_ssm_parameter(scope: Construct) -> EventBus:
"""
Load the data event bus from an SSM parameter.

This pattern breaks cross-stack references by storing and retrieving
the event bus ARN in SSM Parameter Store rather than using a direct reference,
which helps avoid issues with CloudFormation stack updates.

Args:
scope: The CDK construct scope

Returns:
The EventBus construct
"""
data_event_bus_arn = StringParameter.from_string_parameter_name(
scope,
'DataEventBusArnParameter',
string_parameter_name=DATA_EVENT_BUS_ARN_SSM_PARAMETER_NAME,
)

return EventBus.from_event_bus_arn(scope, 'DataEventBus', event_bus_arn=data_event_bus_arn.string_value)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from cdk_nag import NagSuppressions
from common_constructs.nodejs_function import NodejsFunction
from common_constructs.python_function import PythonFunction
from common_constructs.ssm_parameter_utility import SSMParameterUtility
from common_constructs.stack import Stack

from stacks import persistent_stack as ps
Expand Down Expand Up @@ -48,13 +49,17 @@ def __init__(
self.api_model = api_model

stack: Stack = Stack.of(resource)

# Load the data event bus from SSM parameter instead of direct reference
data_event_bus = SSMParameterUtility.load_data_event_bus_from_ssm_parameter(stack)

lambda_environment = {
'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name,
'PROV_FAM_GIV_MID_INDEX_NAME': persistent_stack.provider_table.provider_fam_giv_mid_index_name,
'PROV_DATE_OF_UPDATE_INDEX_NAME': persistent_stack.provider_table.provider_date_of_update_index_name,
'SSN_TABLE_NAME': persistent_stack.ssn_table.table_name,
'SSN_INDEX_NAME': persistent_stack.ssn_table.ssn_index_name,
'EVENT_BUS_NAME': persistent_stack.data_event_bus.event_bus_name,
'EVENT_BUS_NAME': data_event_bus.event_bus_name,
'RATE_LIMITING_TABLE_NAME': persistent_stack.rate_limiting_table.table_name,
'USER_POOL_ID': persistent_stack.staff_users.user_pool_id,
'EMAIL_NOTIFICATION_SERVICE_LAMBDA_NAME': persistent_stack.email_notification_service_lambda.function_name,
Expand Down Expand Up @@ -85,7 +90,7 @@ def __init__(
self._add_deactivate_privilege(
method_options=admin_method_options,
provider_data_table=persistent_stack.provider_table,
event_bus=persistent_stack.data_event_bus,
event_bus=data_event_bus,
email_service_lambda=persistent_stack.email_notification_service_lambda,
staff_users_table=persistent_stack.staff_users.user_table,
lambda_environment=lambda_environment,
Expand Down
17 changes: 10 additions & 7 deletions backend/compact-connect/stacks/ingest_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from aws_cdk import Duration
from aws_cdk.aws_cloudwatch import Alarm, ComparisonOperator, Metric, Stats, TreatMissingData
from aws_cdk.aws_cloudwatch_actions import SnsAction
from aws_cdk.aws_events import EventPattern, Rule
from aws_cdk.aws_events import EventBus, EventPattern, Rule
from aws_cdk.aws_events_targets import SqsQueue
from cdk_nag import NagSuppressions
from common_constructs.python_function import PythonFunction
from common_constructs.queued_lambda_processor import QueuedLambdaProcessor
from common_constructs.ssm_parameter_utility import SSMParameterUtility
from common_constructs.stack import AppStack, Stack
from constructs import Construct

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

def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack, data_event_bus: EventBus):
ingest_handler = PythonFunction(
self,
'V1IngestHandler',
Expand All @@ -39,14 +42,14 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
handler='ingest_license_message',
timeout=Duration.minutes(1),
environment={
'EVENT_BUS_NAME': persistent_stack.data_event_bus.event_bus_name,
'EVENT_BUS_NAME': data_event_bus.event_bus_name,
'PROVIDER_TABLE_NAME': persistent_stack.provider_table.table_name,
**self.common_env_vars,
},
alarm_topic=persistent_stack.alarm_topic,
)
persistent_stack.provider_table.grant_read_write_data(ingest_handler)
persistent_stack.data_event_bus.grant_put_events_to(ingest_handler)
data_event_bus.grant_put_events_to(ingest_handler)

NagSuppressions.add_resource_suppressions_by_path(
Stack.of(ingest_handler.role),
Expand Down Expand Up @@ -90,7 +93,7 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
ingest_rule = Rule(
self,
'V1IngestEventRule',
event_bus=persistent_stack.data_event_bus,
event_bus=data_event_bus,
event_pattern=EventPattern(detail_type=['license.ingest']),
targets=[SqsQueue(processor.queue, dead_letter_queue=processor.dlq)],
)
Expand All @@ -103,7 +106,7 @@ def _add_v1_ingest_chain(self, persistent_stack: ps.PersistentStack):
namespace='AWS/Events',
metric_name='FailedInvocations',
dimensions_map={
'EventBusName': persistent_stack.data_event_bus.event_bus_name,
'EventBusName': data_event_bus.event_bus_name,
'RuleName': ingest_rule.rule_name,
},
period=Duration.minutes(5),
Expand Down
20 changes: 16 additions & 4 deletions backend/compact-connect/stacks/persistent_stack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from common_constructs.nodejs_function import NodejsFunction
from common_constructs.python_function import COMMON_PYTHON_LAMBDA_LAYER_SSM_PARAMETER_NAME
from common_constructs.security_profile import SecurityProfile
from common_constructs.ssm_parameter_utility import SSMParameterUtility
from common_constructs.stack import AppStack
from constructs import Construct

Expand Down Expand Up @@ -106,7 +107,18 @@ def __init__(
auto_delete_objects=removal_policy == RemovalPolicy.DESTROY,
)

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

self._add_data_resources(removal_policy=removal_policy)
self._add_migrations()
Expand Down Expand Up @@ -243,7 +255,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
self,
'SSNTable',
removal_policy=removal_policy,
data_event_bus=self.data_event_bus,
data_event_bus=self._data_event_bus,
alarm_topic=self.alarm_topic,
)

Expand All @@ -257,7 +269,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
bucket_encryption_key=self.ssn_table.key,
removal_policy=removal_policy,
auto_delete_objects=removal_policy == RemovalPolicy.DESTROY,
event_bus=self.data_event_bus,
event_bus=self._data_event_bus,
license_preprocessing_queue=self.ssn_table.preprocessor_queue.queue,
license_upload_role=self.ssn_table.license_upload_role,
)
Expand Down Expand Up @@ -304,7 +316,7 @@ def _add_data_resources(self, removal_policy: RemovalPolicy):
scope=self,
construct_id='DataEventTable',
encryption_key=self.shared_encryption_key,
event_bus=self.data_event_bus,
event_bus=self._data_event_bus,
alarm_topic=self.alarm_topic,
removal_policy=removal_policy,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def __init__(
namespace='AWS/Events',
metric_name='FailedInvocations',
dimensions_map={
'EventBusName': stack.data_event_bus.event_bus_name,
'EventBusName': event_bus.event_bus_name,
'RuleName': event_receiver_rule.rule_name,
},
period=Duration.minutes(5),
Expand Down
2 changes: 1 addition & 1 deletion backend/compact-connect/tests/app/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def _inspect_ssn_table(self, persistent_stack: PersistentStack, persistent_stack

def _inspect_data_events_table(self, persistent_stack: PersistentStack, persistent_stack_template: Template):
# Ensure our DataEventTable and queues are created
event_bus_logical_id = persistent_stack.get_logical_id(persistent_stack.data_event_bus.node.default_child)
event_bus_logical_id = persistent_stack.get_logical_id(persistent_stack._data_event_bus.node.default_child) # noqa: SLF001 private_member_access
queue_logical_id = persistent_stack.get_logical_id(
persistent_stack.data_event_table.event_processor.queue.node.default_child
)
Expand Down
Loading