Skip to content

Commit abb3dce

Browse files
Add custom resource to manage OpenSearch indices
1 parent 88dad31 commit abb3dce

20 files changed

Lines changed: 736 additions & 22 deletions

backend/compact-connect/bin/compile_requirements.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/provi
1818
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/provider-data-v1/requirements.in
1919
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/purchases/requirements-dev.in
2020
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/purchases/requirements.in
21+
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/search/requirements-dev.in
22+
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/search/requirements.in
2123
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/staff-user-pre-token/requirements-dev.in
2224
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/staff-user-pre-token/requirements.in
2325
pip-compile --no-emit-index-url --upgrade --no-strip-extras lambdas/python/staff-users/requirements-dev.in

backend/compact-connect/bin/sync_deps.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pip-sync \
2222
lambdas/python/provider-data-v1/requirements.txt \
2323
lambdas/python/purchases/requirements-dev.txt \
2424
lambdas/python/purchases/requirements.txt \
25+
lambdas/python/search/requirements-dev.txt \
26+
lambdas/python/search/requirements.txt \
2527
lambdas/python/staff-user-pre-token/requirements-dev.txt \
2628
lambdas/python/staff-user-pre-token/requirements.txt \
2729
lambdas/python/staff-users/requirements-dev.txt \

backend/compact-connect/common_constructs/python_function.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from aws_cdk import Duration
66
from aws_cdk.aws_cloudwatch import Alarm, ComparisonOperator, Stats, TreatMissingData
77
from aws_cdk.aws_cloudwatch_actions import SnsAction
8-
from aws_cdk.aws_iam import IRole, Role, ServicePrincipal
8+
from aws_cdk.aws_iam import IRole, Role, ServicePrincipal, ManagedPolicy
99
from aws_cdk.aws_lambda import ILayerVersion, Runtime
1010
from aws_cdk.aws_lambda_python_alpha import PythonFunction as CdkPythonFunction
1111
from aws_cdk.aws_logs import ILogGroup, LogGroup, RetentionDays
@@ -81,6 +81,24 @@ def __init__(
8181
assumed_by=ServicePrincipal('lambda.amazonaws.com'),
8282
)
8383
log_group.grant_write(role)
84+
if 'vpc' in kwargs:
85+
# if the function is being created in a VPC, add the AWSLambdaVPCAccessExecutionRole policy to the role
86+
role.add_managed_policy(
87+
ManagedPolicy.from_aws_managed_policy_name('service-role/AWSLambdaVPCAccessExecutionRole')
88+
)
89+
NagSuppressions.add_resource_suppressions(
90+
role,
91+
suppressions=[
92+
{
93+
'id': 'AwsSolutions-IAM4',
94+
'appliesTo': [
95+
'Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'
96+
],
97+
'reason': "Lambdas deployed within a VPC require this policy to access the VPC.",
98+
},
99+
],
100+
)
101+
84102
# We can't directly grant a provided role permission to log to our log group, since that could create a
85103
# circular dependency with the stack the role came from. The role creator will have to be responsible for
86104
# setting its permissions.

backend/compact-connect/lambdas/python/common/cc_common/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ class _Config:
2222
presigned_post_ttl_seconds = 3600
2323
default_page_size = 100
2424

25+
@property
26+
def environment_region(self):
27+
"""
28+
Returns the region name of the region the lambda is running in.
29+
"""
30+
return os.environ['AWS_REGION']
31+
32+
@property
33+
def opensearch_host_endpoint(self):
34+
"""
35+
Returns the region name of the region the lambda is running in.
36+
"""
37+
return os.environ['OPENSEARCH_HOST_ENDPOINT']
38+
2539
@cached_property
2640
def cognito_client(self):
2741
return boto3.client('cognito-idp')
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env python3
2+
from abc import ABC, abstractmethod
3+
from typing import TypedDict
4+
5+
from aws_lambda_powertools.logging.lambda_context import build_lambda_context_model
6+
from aws_lambda_powertools.utilities.typing import LambdaContext
7+
from cc_common.config import logger
8+
9+
10+
class CustomResourceResponse(TypedDict, total=False):
11+
"""Return body for the custom resource handler."""
12+
13+
PhysicalResourceId: str
14+
Data: dict
15+
NoEcho: bool
16+
17+
18+
class CustomResourceHandler(ABC):
19+
"""Base class for custom resource migrations.
20+
21+
This class provides a framework for implementing CloudFormation custom resources.
22+
It handles the routing of CloudFormation events to appropriate methods and provides a consistent
23+
logging pattern.
24+
25+
Subclasses must implement the on_create, on_update, and on_delete methods.
26+
27+
Instances of this class are callable and can be used directly as Lambda handlers.
28+
"""
29+
30+
def __init__(self, handler_name: str):
31+
"""Initialize the custom resource handler.
32+
33+
:type handler_name: str
34+
"""
35+
self.handler_name = handler_name
36+
37+
def __call__(self, event: dict, _context: LambdaContext) -> CustomResourceResponse | None:
38+
return self._on_event(event, _context)
39+
40+
def _on_event(self, event: dict, _context: LambdaContext) -> CustomResourceResponse | None:
41+
"""CloudFormation event handler using the CDK provider framework.
42+
See: https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.custom_resources/README.html
43+
44+
This method routes the event to the appropriate handler method based on the request type.
45+
46+
:param event: The lambda event with properties in ResourceProperties
47+
:type event: dict
48+
:param _context: Lambda context
49+
:type _context: LambdaContext
50+
:return: Optional result from the handler method
51+
:rtype: Optional[CustomResourceResponse]
52+
:raises ValueError: If the request type is not supported
53+
"""
54+
55+
# @logger.inject_lambda_context doesn't work on instance methods, so we'll build the context manually
56+
lambda_context = build_lambda_context_model(_context)
57+
logger.structure_logs(**lambda_context.__dict__)
58+
59+
logger.info(f'{self.handler_name} handler started')
60+
61+
properties = event.get('ResourceProperties', {})
62+
request_type = event['RequestType']
63+
64+
match request_type:
65+
case 'Create':
66+
try:
67+
resp = self.on_create(properties)
68+
except Exception as e:
69+
logger.error(f'Error in {self.handler_name} creation', exc_info=e)
70+
raise
71+
case 'Update':
72+
try:
73+
resp = self.on_update(properties)
74+
except Exception as e:
75+
logger.error(f'Error in {self.handler_name} update', exc_info=e)
76+
raise
77+
case 'Delete':
78+
try:
79+
resp = self.on_delete(properties)
80+
except Exception as e:
81+
logger.error(f'Error in {self.handler_name} delete', exc_info=e)
82+
raise
83+
case _:
84+
raise ValueError(f'Unexpected request type: {request_type}')
85+
86+
logger.info(f'{self.handler_name} handler complete')
87+
return resp
88+
89+
@abstractmethod
90+
def on_create(self, properties: dict) -> CustomResourceResponse | None:
91+
"""Handle Create events.
92+
93+
This method should be implemented by subclasses to perform the migration when a resource is being created.
94+
95+
:param properties: The ResourceProperties from the CloudFormation event
96+
:type properties: dict
97+
:return: Any result to be returned to CloudFormation
98+
:rtype: Optional[CustomResourceResponse]
99+
"""
100+
101+
@abstractmethod
102+
def on_update(self, properties: dict) -> CustomResourceResponse | None:
103+
"""Handle Update events.
104+
105+
This method should be implemented by subclasses to perform the migration when a resource is being updated.
106+
107+
:param properties: The ResourceProperties from the CloudFormation event
108+
:type properties: dict
109+
:return: Any result to be returned to CloudFormation
110+
:rtype: Optional[CustomResourceResponse]
111+
"""
112+
113+
@abstractmethod
114+
def on_delete(self, properties: dict) -> CustomResourceResponse | None:
115+
"""Handle Delete events.
116+
117+
This method should be implemented by subclasses to handle deletion of the migration. In many cases, this can
118+
be a no-op as the migration is temporary and deletion should have no effect.
119+
120+
:param properties: The ResourceProperties from the CloudFormation event
121+
:type properties: dict
122+
:return: Any result to be returned to CloudFormation
123+
:rtype: Optional[CustomResourceResponse]
124+
"""

backend/compact-connect/lambdas/python/search/handlers/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)