Skip to content

Commit 1fbdbd5

Browse files
Merge branch 'master' into feature/BFD-4248_GitHub-BuildAndDeploy-Slack-Notification
2 parents f6dd4f3 + 25c214c commit 1fbdbd5

9 files changed

Lines changed: 144 additions & 62 deletions

File tree

ops/services/01-config/values/prod.sopsw.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@
3737
/bfd/${env}/idr-pipeline/sensitive/idr_warehouse: ENC[AES256_GCM,data:Tc9eFpY0/bFvtoYq5QRcVA==,iv:N6vX8xfP/Ek3ir2x+bymiLvQ96PutJnv/Rn8aYlI+io=,tag:3u1Ic6iwwcAsavfFt/qwrg==,type:str]
3838
/bfd/${env}/idr-pipeline/sensitive/idr_schema: ENC[AES256_GCM,data:f3/iwiAAhKV8FJ6xrx+CnAlDIrT5,iv:6GpQvT8Ws05Rrvq6ON6Qzcr+dTIbZNLxRTFFE/EHIps=,tag:rrhG+2C/qryS5q4Fjo+tAg==,type:str]
3939
/bfd/${env}/idr-pipeline/sensitive/idr_private_key: ENC[AES256_GCM,data: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,iv:bzDTkn40uAVkDjmo7ZpTYWBI6oKnOiiFH/fz30IGJcQ=,tag:nWtcMVlmvN8eNoEFDXebhw==,type:str]
40+
#ENC[AES256_GCM,data:pCpJ6Jk2r+OPKxDjZqdUV/JBHqOnPH7vrdjG3Wd30E2F0+BrUd/c59V3lr+Linth47SPHCf2rtq5Pq4AFXVf4a3guKnJeyUBWa68KEJ4SaGkaEbf7SMfPAgD,iv:EuhRs+vk854QGFCIE9IZrWJ2r+G6dPzZSFCxD8WZio0=,tag:R+2KelQqkkyc4XtMOm8CVA==,type:comment]
41+
#ENC[AES256_GCM,data:rTj1oATAQN9IdgLM4bjojK/tsABwGk+S4o93v073BVEFttAGn6m1YJgwJFtwjDyrMMiOdo1WF8h4wxAL5MZ2VwGDVqLt0UFhOLgkO9wBtwH1AopJKoGwBh0BT1A=,iv:46pYoHfoEFwr59bGhrO6m3NStA2dXTZ50WGj47ixrDM=,tag:fhFqZGgvYjMJFl5shjVrfw==,type:comment]
42+
#ENC[AES256_GCM,data:z9HsAv+tJMSuM2Luoh3Q3jp8sduHKzyc29QxraWETso+cMLgrwwzF4lBHy3GIjE=,iv:yxC+SJqAWwRgrKNPbzNJq61vpbNNIOM+qTFVRt62Kyo=,tag:37BAnhpXsh/7puYe/1uiRw==,type:comment]
43+
#ENC[AES256_GCM,data:yNP7CnQXf6gNlvjqlDsFRGAwJuafTLunohJ6FdFECLpOqhZr/zr9ZUZ+l27G1t0eKM93DYa57yGns8ZlwUsIvxRFTrcl62jAodSdp2UzaIckN6EZTBiIy+SFE4ODNAsb,iv:+1l1nvr+8YEz1moHUywr1wHPlr8t5bhdEAHYpX+wiw0=,tag:Qyh2eJ8l6YAc2dXwtMrDoQ==,type:comment]
44+
#ENC[AES256_GCM,data:Jfc409d1xuWgeQOzB8CTNsbxzc3Euyx6IEXPWM0WbqfiPntFszrZSYnmyCNJTaCZEcsqFJn2utH0S5RceSgie+amR2MqazlrdeKa8n3DoWTucfT4cTjjY55ZAqTaqmUN,iv:1Nc+cavlzl8WDOIvmm7PVtlRbNyfIN9Iy79+LkscJTk=,tag:MfSTT7oIZFuQBrbJzaC4vA==,type:comment]
45+
#ENC[AES256_GCM,data:Rev3SAMLh2S8Ry/WYMAqXktBjEcdJiD6f75tGIV6s9aW6Nt5TW+cnsy5qJ4TAafZvbBNkkV1USVM4vfAhRMcAae0jCutp8TH5KCKkzPdM2+McFyzzhYKT88/wtE7+9U=,iv:nGGwQ7TF59x3VoDzHXt7v6HksjgV9y3lT7d3EOEH+KA=,tag:6z/SAecq1mWNehgqsUYKnQ==,type:comment]
46+
#ENC[AES256_GCM,data:DAQFMnUgbUm1+y1QuKLXSV+eI+kgV+L+n+KlCPjT8d2qL+umDOulsNe3YA5Ql14lpe44FllfxMrOEVcNMGnG7FjK4+88eSvi54hGZ7Bns7vWKT+gux83xGTrYMzpJ5idLYs=,iv:/a+V3Bv7+31fg6Szgi6qW9ea7Zl6drqpb+wodTaIqQc=,tag:7TxkgNxy+bo4Ksd738m72w==,type:comment]
47+
/bfd/${env}/idr-pipeline/sensitive/queue/authorized_roles_json: ENC[AES256_GCM,data:o5SA8pnhyRSa7JlZtLGHlBFPOu4msQ92q31escmAOtcH9LkVYNHDUz8ys68ETT826YiixX2x+x2YJlMkTfGTy1wmOm+qP60zu3j6MQANGOQzeUbeeE6ZUi+1pZxHRTFlrZe7MEoO830uVk9ZhhLccNV66gj5M+fpmP93fAEu+GXb6HeANa+9UYyGSiT2/JwyHnNiafg/Pv/kpHQw67n1/Og7jOjT1FYv5TuafdJ8oDO61A04AzwK6x6NQB5aC0e9Pv0QQkuqKMSH9QK52vkFTdI0KgErkIJ+DVCfYOX1nhcaCsQ5gqUdYV/VVIyaDo1vhWUDIQAeogaGx5McxQxaWHeP9u3I1tmbwgnN+KXJnp45FuPXmLRUap401ZZJye6G4CUYU1FLPdEUVVTNeXZ7xU7WHZpSVYzwMMLYNR6DAUw72NmfUNH2WsxoEI+qEQ==,iv:+5PCJnxmUy/zd3NTkOfM83mxvOFb6PK4G+F/gg75eR8=,tag:C2z/AST+rDG3Htwlx8Gu1Q==,type:str]
4048
/bfd/${env}/locust/sensitive/db/username: ENC[AES256_GCM,data:sVBEfHaf0fKJQ0uZ0YB5Jg==,iv:B7BSyFiEfcjwuHVtexxJkMXOD+LKC47lcWFljN99QRg=,tag:gUn0m38afE3OqGHFdZO0Fg==,type:str]
4149
/bfd/${env}/locust/sensitive/db/password: ENC[AES256_GCM,data:LinHkvQ8yLpMjszLNe7NttT9P70B2A1P,iv:Vd3KTMptphUEHnaBpq3T5oEvb3F4iAQ8d0jGQVsY8nk=,tag:e53OrbnYq1yAW3slSW7YIA==,type:str]
4250
/bfd/${env}/locust/sensitive/cert/pem_data: ENC[AES256_GCM,data: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,iv:Cy6XH2/MTi06bTQ184PMHzefI8aj6jkaXQZSPKf0ZL8=,tag:hU2DO0bGK/AF4QXC3iMdhA==,type:str]
@@ -716,4 +724,4 @@ sops:
716724
enc: AQICAHiCa0SxyWv/VuVkwfKgPAG3j8l+Be+oAWTYKNLFuuPQsAFZJNm31fbu7Nvpys8buYBtAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMkDoVrjbvMHHf89niAgEQgDtLxEX5HqaUAYPuSxtrimw50F/mjAZf6AWYpoHuxEUXgLs++BECkiao5TpAFXAQIWX+OYjQXENt2iA4yA==
717725
aws_profile: ""
718726
unencrypted_regex: /nonsensitive/
719-
version: 3.12.1
727+
version: 3.12.2

ops/services/02-bene-prefs/lambda.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
locals {
2-
particpating_partners = "bcda"
2+
participating_partners = ["bcda", "ab2d"]
33

44
lambda_full_name = "${local.name_prefix}-function"
55
lambda_source_path = "${path.module}/lambda_src/bene_prefs"
@@ -66,7 +66,7 @@ resource "aws_lambda_function" "this" {
6666
variables = {
6767
BFD_ENV = local.env
6868
AWS_CURRENT_REGION = local.region
69-
PARTNERS = local.particpating_partners
69+
PARTNERS = join(",", local.participating_partners)
7070
}
7171
}
7272

ops/services/02-bene-prefs/lambda_src/bene_prefs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
!.gitignore
33
!.python-version
44
!README.md
5+
!app
56
!app/__init__.py
67
!app/main.py
78
!app/templates/_sql.j2

ops/services/02-bene-prefs/lambda_src/bene_prefs/app/main.py

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import os
2+
from calendar import EPOCH
23
from datetime import UTC, datetime
34
from io import BytesIO
45
from pathlib import Path
6+
from typing import Any
57

68
import boto3
79
import snowflake.connector
10+
from aws_lambda_powertools import Logger
11+
from aws_lambda_powertools.utilities.parameters import SSMProvider
12+
from aws_lambda_powertools.utilities.typing import LambdaContext
813
from botocore.config import Config
914
from botocore.exceptions import ClientError
1015
from cryptography.hazmat.backends import default_backend
1116
from cryptography.hazmat.primitives import serialization
1217
from jinja2 import Environment, PackageLoader, StrictUndefined
1318
from snowflake.connector import DictCursor
1419

15-
from aws_lambda_powertools import Logger
16-
from aws_lambda_powertools.utilities.typing import LambdaContext
17-
from aws_lambda_powertools.utilities.parameters import SSMProvider
18-
1920
REGION = os.environ.get("AWS_CURRENT_REGION", "us-east-1")
2021
BFD_ENV = os.environ.get("BFD_ENV", "prod")
2122
PARTNERS = os.environ.get("PARTNERS", "")
@@ -43,7 +44,7 @@
4344
logger = Logger()
4445

4546

46-
def execute_query(query: str) -> list:
47+
def execute_query(query: str) -> list[dict[str, Any]]:
4748
"""Execute the given query and return the resultant rows from Snowflake.
4849
4950
Args:
@@ -53,54 +54,51 @@ def execute_query(query: str) -> list:
5354
List: The results of the query.
5455
"""
5556
try:
56-
account = SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_account", decrypt=True)
57-
database = SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_database", decrypt=True)
58-
private_key_raw = SSM.get(
59-
f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_private_key", decrypt=True
57+
account = str(SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_account", decrypt=True))
58+
database = str(SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_database", decrypt=True))
59+
private_key_raw = str(
60+
SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_private_key", decrypt=True)
61+
)
62+
schema = str(SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_schema", decrypt=True))
63+
user = str(SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_username", decrypt=True))
64+
warehouse = str(
65+
SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_warehouse", decrypt=True)
6066
)
61-
schema = SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_schema", decrypt=True)
62-
user = SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_username", decrypt=True)
63-
warehouse = SSM.get(f"/bfd/{BFD_ENV}/idr-pipeline/sensitive/idr_warehouse", decrypt=True)
6467
except Exception as exc:
65-
raise ValueError(f"Missing snowflake configuration: {exc}")
68+
raise ValueError(f"Missing snowflake configuration: {exc}") from exc
69+
70+
# Load and prepare private key for authentication
71+
private_key = serialization.load_pem_private_key(
72+
private_key_raw.encode(),
73+
password=None,
74+
backend=default_backend(),
75+
)
76+
private_key_bytes = private_key.private_bytes(
77+
encoding=serialization.Encoding.DER,
78+
format=serialization.PrivateFormat.PKCS8,
79+
encryption_algorithm=serialization.NoEncryption(),
80+
)
6681

6782
try:
68-
# Load and prepare private key for authentication
69-
private_key = serialization.load_pem_private_key(
70-
private_key_raw.encode(),
71-
password=None,
72-
backend=default_backend(),
73-
)
74-
private_key_bytes = private_key.private_bytes(
75-
encoding=serialization.Encoding.DER,
76-
format=serialization.PrivateFormat.PKCS8,
77-
encryption_algorithm=serialization.NoEncryption(),
78-
)
79-
8083
# Establish connection
81-
conn = snowflake.connector.connect(
84+
with snowflake.connector.connect(
8285
account=account,
8386
user=user,
8487
private_key=private_key_bytes,
8588
warehouse=warehouse,
8689
database=database,
8790
schema=schema,
88-
)
89-
90-
cursor = conn.cursor(DictCursor)
91-
cursor.execute(query)
92-
results = cursor.fetchall()
93-
cursor.close()
94-
return results
95-
96-
except snowflake.connector.errors.Error as e:
91+
) as conn:
92+
cursor = conn.cursor(DictCursor)
93+
cursor.execute(query)
94+
results = cursor.fetchall()
95+
cursor.close()
96+
97+
return results
98+
except snowflake.connector.errors.Error:
9799
logger.exception("Snowflake error: {e}")
98100
raise
99101

100-
finally:
101-
if "conn" in locals():
102-
conn.close()
103-
104102

105103
class PartnerPreferences:
106104
def __init__(self, partner: str) -> None:
@@ -122,9 +120,10 @@ def last_execution(self) -> str | None:
122120
return self._execution
123121

124122
except ClientError as e:
125-
self._logger.exception(f"""
126-
Error retrieving last execution: {e.response["Error"]["Message"]}
127-
""")
123+
self._logger.exception(
124+
"Error retrieving last execution: %s",
125+
e.response["Error"]["Message"], # pyright: ignore[reportAttributeAccessIssue]
126+
)
128127
raise
129128

130129
return self._execution
@@ -140,8 +139,8 @@ def partner_code(self) -> str:
140139
codes = {"ab2d": "MED_AB2D", "bcda": "MED", "dpc": "MED_DPC"}
141140
try:
142141
code = codes[self.partner]
143-
except KeyError as exc:
144-
self._logger.exception(f"Invalid partner: {exc}")
142+
except KeyError:
143+
self._logger.exception("Invalid partner: %s", self.partner)
145144
raise
146145

147146
return code
@@ -161,13 +160,14 @@ def _set_last_execution(self, timestamp: str | None = None) -> None:
161160
)
162161

163162
self._logger.info(
164-
f"Updating last_exeuction from {self.last_execution} to {latest_timestamp}"
163+
"Updating last_execution from %s to %s", self.last_execution, latest_timestamp
165164
)
166165
self._execution = latest_timestamp
167166

168167
except ClientError as e:
169168
self._logger.exception(
170-
f"Error updating last execution: {e.response['Error']['Message']}"
169+
"Error updating last execution: %s",
170+
e.response["Error"]["Message"], # pyright: ignore[reportAttributeAccessIssue]
171171
)
172172
raise
173173

@@ -181,7 +181,7 @@ def _store_preferences(
181181
if store_local:
182182
local_file = Path(file_name).name
183183
with Path.open(Path(local_file), "wb") as local:
184-
self._logger.info(f"Storing local file:{local_file}")
184+
self._logger.info("Storing local file: %s", local_file)
185185
local.write(preferences_data.encode("utf-8"))
186186

187187
if store_remote:
@@ -191,7 +191,7 @@ def _store_preferences(
191191

192192
bucket = SSM.get(f"/bfd/{BFD_ENV}/bene-prefs/{self.partner}/nonsensitive/bucket")
193193

194-
self._logger.info(f"Storing remote file: s3://{bucket}/{file_name}")
194+
self._logger.info("Storing remote file: s3://%s/%s", bucket, file_name)
195195
S3.upload_fileobj(buffer, bucket, file_name)
196196

197197
def generate_preferences(
@@ -204,16 +204,21 @@ def generate_preferences(
204204
"""Generate and store the preferences report in AWS S3.
205205
206206
Args:
207-
timestamp_range (tuple): Tuple bounding (lower, upper) ISO 8601 timestamp strings. Default range between last execution and utc-now.
208-
set_last_execution (bool): When `True`, update last_execution in DynamoDB . Default is `True`.
207+
timestamp_range (tuple): Tuple bounding (lower, upper) ISO 8601 timestamp strings.
208+
Default range between last execution and utc-now.
209+
set_last_execution (bool): When `True`, update last_execution in DynamoDB . Default is
210+
`True`.
209211
store_local (bool): When `True`, store preferences locally. Default is `False`.
210212
store_remote (bool): When `True`, write the preferences to S3. Default is `True`.
211213
"""
212214
query_since_timestamp = timestamp_range[0] or self.last_execution
213215
query_until_timestamp = timestamp_range[1] or datetime.now(UTC).isoformat()
214216

215217
self._logger.info(
216-
f"Rendering query template {self.query_template.filename} with query_since_timestamp={query_since_timestamp}, query_until_timestamp={query_until_timestamp}"
218+
"Rendering query template %s with query_since_timestamp=%s, query_until_timestamp=%s",
219+
self.query_template.filename,
220+
query_since_timestamp,
221+
query_until_timestamp,
217222
)
218223

219224
query = self.query_template.render(
@@ -227,14 +232,25 @@ def generate_preferences(
227232
results = execute_query(query)
228233

229234
self._logger.info(
230-
f"Rendering prefs template {self.prefs_template.filename} of {len(results)} records with data=<redacted>, extract_date={YYYYMMDD}"
235+
"Rendering prefs template %s of %d records with data=<redacted>, extract_date=%s",
236+
self.prefs_template.filename,
237+
len(results),
238+
YYYYMMDD,
231239
)
232240
preferences_data = self.prefs_template.render(data=results, extract_date=YYYYMMDD)
233241

234242
report_time = datetime.now(UTC).strftime("%H%M%S")
235243

236244
self._logger.info(
237-
f"Rendering file_name template {self.file_name_template.filename} with partner={self.partner}, env_indicator={self.environment_indicator}, report_date={YYYYMMDD}, report_time={report_time}"
245+
(
246+
"Rendering file_name template %s with partner=%s, env_indicator=%s, report_date=%s,"
247+
" report_time=%s"
248+
),
249+
self.file_name_template.filename,
250+
self.partner,
251+
self.environment_indicator,
252+
YYYYMMDD,
253+
report_time,
238254
)
239255

240256
file_name = self.file_name_template.render(
@@ -250,17 +266,18 @@ def generate_preferences(
250266

251267
if set_last_execution:
252268
if results:
269+
max_insert_datetime: datetime | None = max(
270+
results, key=lambda x: x.get("IDR_INSRT_TS", datetime(EPOCH, 1, 1))
271+
).get("IDR_INSRT_TS")
253272
max_insert_timestamp = (
254-
max(results, key=lambda x: x.get("IDR_INSRT_TS"))
255-
.get("IDR_INSRT_TS")
256-
.isoformat()
273+
max_insert_datetime.isoformat() if max_insert_datetime else None
257274
)
258275
self._set_last_execution(max_insert_timestamp)
259276
else:
260277
self._logger.info("Empty results. Skipping set of last execution.")
261278

262279

263-
def handler(event: dict, context: LambdaContext) -> None:
280+
def handler(event: dict[str, Any], context: LambdaContext) -> None: # noqa: ARG001
264281
"""Lambda event handler function.
265282
266283
Args:
@@ -270,6 +287,8 @@ def handler(event: dict, context: LambdaContext) -> None:
270287
Raises:
271288
RuntimeError: If any required environment variables are undefined
272289
"""
290+
# PARTNERS is a comma-separated list of participating partners. The value of PARTNERS is derived
291+
# directly from the bene-prefs Terraservice
273292
partners = [p.strip() for p in PARTNERS.split(",")]
274293

275294
if "bcda" in partners:

0 commit comments

Comments
 (0)