Skip to content

Commit b2892ae

Browse files
introduced firehose and audit bucket
1 parent cb32cf9 commit b2892ae

10 files changed

Lines changed: 69 additions & 10 deletions

File tree

infrastructure/modules/lambda/lambda.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ resource "aws_lambda_function" "eligibility_signposting_lambda" {
1919
variables = {
2020
PERSON_TABLE_NAME = var.eligibility_status_table_name,
2121
RULES_BUCKET_NAME = var.eligibility_rules_bucket_name,
22+
AUDIT_BUCKET_NAME = var.eligibility_audit_bucket_name
2223
ENV = var.environment
2324
LOG_LEVEL = var.log_level
2425
}

infrastructure/modules/lambda/variables.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ variable "eligibility_rules_bucket_name" {
3333
type = string
3434
}
3535

36+
variable "eligibility_audit_bucket_name" {
37+
description = "campaign config audit bucket name"
38+
type = string
39+
}
40+
3641
variable "eligibility_status_table_name" {
3742
description = "eligibility datastore table name"
3843
type = string

infrastructure/stacks/api-layer/lambda.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module "eligibility_signposting_lambda_function" {
2121
file_name = "../../../dist/lambda.zip"
2222
handler = "eligibility_signposting_api.app.lambda_handler"
2323
eligibility_rules_bucket_name = module.s3_rules_bucket.storage_bucket_name
24+
eligibility_audit_bucket_name = module.s3_audit_bucket.storage_bucket_name
2425
eligibility_status_table_name = module.eligibility_status_table.table_name
2526
log_level = "INFO"
2627
stack_name = local.stack_name

src/eligibility_signposting_api/config/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ def config() -> dict[str, Any]:
3434
"person_table_name": person_table_name,
3535
"s3_endpoint": None,
3636
"rules_bucket_name": rules_bucket_name,
37-
"audit_bucket_name":audit_bucket_name,
37+
"audit_bucket_name": audit_bucket_name,
38+
"firehose_endpoint": None,
3839
"log_level": log_level,
3940
}
4041

@@ -47,6 +48,7 @@ def config() -> dict[str, Any]:
4748
"s3_endpoint": URL(os.getenv("S3_ENDPOINT", "http://localhost:4566")),
4849
"rules_bucket_name": rules_bucket_name,
4950
"audit_bucket_name": audit_bucket_name,
51+
"firehose_endpoint": URL(os.getenv("FIREHOSE_ENDPOINT", "http://localhost:4566")),
5052
"log_level": log_level,
5153
}
5254

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
MAGIC_COHORT_LABEL = "elid_all_people"
22
RULE_STOP_DEFAULT = False
3-
NHS_NUMBER_HEADER_NAME = "nhs-login-nhs-number"
3+
NHS_NUMBER_HEADER = "nhs-login-nhs-number"
4+
5+
# Infra
6+
ELIGIBILITY_SIGNPOSTING_AUDIT_STREAM = "eligibility-signposting-audit-stream"

src/eligibility_signposting_api/repos/factory.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,11 @@ def dynamodb_resource_factory(
3535
def s3_service_factory(session: Session, s3_endpoint: Annotated[URL, Inject(param="s3_endpoint")]) -> BaseClient:
3636
endpoint_url = str(s3_endpoint) if s3_endpoint is not None else None
3737
return session.client("s3", endpoint_url=endpoint_url)
38+
39+
40+
@service(qualifier="firehose")
41+
def firehose_client_factory(
42+
session: Session, firehose_endpoint: Annotated[URL, Inject(param="firehose_endpoint")]
43+
) -> BaseClient:
44+
endpoint_url = str(firehose_endpoint) if firehose_endpoint is not None else None
45+
return session.client("firehose", endpoint_url=endpoint_url)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import json
2+
import logging
3+
from typing import Annotated
4+
5+
from botocore.client import BaseClient
6+
from wireup import Inject, service
7+
8+
from eligibility_signposting_api.config.contants import ELIGIBILITY_SIGNPOSTING_AUDIT_STREAM
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
@service
14+
class AuditService:
15+
def __init__(self, firehose: Annotated[BaseClient, Inject(qualifier="firehose")]) -> None:
16+
super().__init__()
17+
self.firehose = firehose
18+
self.delivery_stream_name = ELIGIBILITY_SIGNPOSTING_AUDIT_STREAM
19+
20+
def audit(self, audit_record: dict) -> None:
21+
"""
22+
Sends an audit record to the configured Firehose delivery stream.
23+
24+
Args:
25+
audit_record (dict): The audit data to send.
26+
27+
Returns:
28+
str: The Firehose record ID.
29+
"""
30+
response = self.firehose.put_record(
31+
DeliveryStreamName=self.delivery_stream_name,
32+
Record={"Data": (json.dumps(audit_record) + "\n").encode("utf-8")},
33+
)
34+
logger.info("Successfully sent to the Firehose", extra={"firehose_record_id": response["RecordId"]})

src/eligibility_signposting_api/wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from mangum.types import LambdaContext, LambdaEvent
66

7-
from eligibility_signposting_api.config.contants import NHS_NUMBER_HEADER_NAME
7+
from eligibility_signposting_api.config.contants import NHS_NUMBER_HEADER
88

99
logger = logging.getLogger(__name__)
1010

@@ -20,7 +20,7 @@ def wrapper(event: LambdaEvent, context: LambdaContext) -> dict[str, int | str]:
2020
headers = event.get("headers", {})
2121
path_params = event.get("pathParameters", {})
2222

23-
header_nhs = headers.get(NHS_NUMBER_HEADER_NAME)
23+
header_nhs = headers.get(NHS_NUMBER_HEADER)
2424
path_nhs = path_params.get("id")
2525

2626
logger.info("nhs numbers from the request", extra={"header_nhs": header_nhs, "path_nhs": path_nhs})

tests/integration/conftest.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,11 @@ def iam_client(boto3_session: Session, localstack: URL) -> BaseClient:
9696
def s3_client(boto3_session: Session, localstack: URL) -> BaseClient:
9797
return boto3_session.client("s3", endpoint_url=str(localstack))
9898

99+
99100
@pytest.fixture(scope="session")
100101
def firehose_client(boto3_session: Session, localstack: URL) -> BaseClient:
101-
session = Session()
102-
return session.client("firehose", endpoint_url=str(localstack))
102+
return boto3_session.client("firehose", endpoint_url=str(localstack))
103+
103104

104105
@pytest.fixture(scope="session")
105106
def iam_role(iam_client: BaseClient) -> Generator[str]:
@@ -194,6 +195,7 @@ def flask_function(lambda_client: BaseClient, iam_role: str, lambda_zip: Path) -
194195
"Variables": {
195196
"DYNAMODB_ENDPOINT": os.getenv("LOCALSTACK_INTERNAL_ENDPOINT", "http://localstack:4566/"),
196197
"S3_ENDPOINT": os.getenv("LOCALSTACK_INTERNAL_ENDPOINT", "http://localstack:4566/"),
198+
"FIREHOSE_ENDPOINT": os.getenv("LOCALSTACK_INTERNAL_ENDPOINT", "http://localstack:4566/"),
197199
"AWS_REGION": AWS_REGION,
198200
"LOG_LEVEL": "DEBUG",
199201
}
@@ -382,13 +384,15 @@ def rules_bucket(s3_client: BaseClient) -> Generator[BucketName]:
382384
yield bucket_name
383385
s3_client.delete_bucket(Bucket=bucket_name)
384386

387+
385388
@pytest.fixture(scope="session")
386389
def audit_bucket(s3_client: BaseClient) -> Generator[BucketName]:
387390
bucket_name = BucketName(os.getenv("AUDIT_BUCKET_NAME", "test-audit-bucket"))
388391
s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": AWS_REGION})
389392
yield bucket_name
390393
s3_client.delete_bucket(Bucket=bucket_name)
391394

395+
392396
@pytest.fixture(scope="class")
393397
def campaign_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generator[rules.CampaignConfig]:
394398
campaign: rules.CampaignConfig = rule.CampaignConfigFactory.build(
@@ -419,8 +423,9 @@ def campaign_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generato
419423

420424

421425
@pytest.fixture(scope="class")
422-
def campaign_config_with_magic_cohort(s3_client: BaseClient,
423-
rules_bucket: BucketName) -> Generator[rules.CampaignConfig]:
426+
def campaign_config_with_magic_cohort(
427+
s3_client: BaseClient, rules_bucket: BucketName
428+
) -> Generator[rules.CampaignConfig]:
424429
campaign: rules.CampaignConfig = rule.CampaignConfigFactory.build(
425430
target="COVID",
426431
iterations=[

tests/integration/lambda/test_app_running_as_lambda.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ def get_log_messages(flask_function: str, logs_client: BaseClient) -> list[str]:
155155
def test_given_nhs_number_in_path_matches_with_nhs_number_in_headers(
156156
lambda_client: BaseClient, # noqa:ARG001
157157
persisted_person: NHSNumber,
158-
campaign_config: CampaignConfig, # noqa:ARG001
159-
firehose_client: BaseClient, # noqa:ARG001
158+
campaign_config: CampaignConfig, # noqa:ARG001
159+
firehose_client: BaseClient, # noqa:ARG001
160160
api_gateway_endpoint: URL,
161161
):
162162
# Given

0 commit comments

Comments
 (0)