Skip to content

Commit 35ce335

Browse files
authored
Merge pull request #517 from NHSDigital/release/2025-02-26
Release/2025 02 26
2 parents 0d3b158 + 434c6c4 commit 35ce335

File tree

498 files changed

+3082
-21456
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

498 files changed

+3082
-21456
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ repos:
44
rev: v1.4.0
55
hooks:
66
- id: detect-secrets
7-
exclude: ".pre-commit-config.yaml|infrastructure/localstack/provider.tf|src/etl/sds/tests/changelog|src/etl/sds/worker/bulk/transform_bulk/tests|src/etl/sds/worker/bulk/tests/stage_data|src/api/tests/smoke_tests/test_smoke.py|archived_epr/src_old/api/tests/smoke_tests/test_smoke.py"
7+
exclude: ".pre-commit-config.yaml|infrastructure/localstack/provider.tf|archived_epr/src_old/etl/sds/tests/changelog|archived_epr/src_old/etl/sds/worker/bulk/transform_bulk/tests|archived_epr/src_old/etl/sds/worker/bulk/tests/stage_data|src/api/tests/smoke_tests/test_smoke.py|archived_epr/src_old/api/tests/smoke_tests/test_smoke.py"
88

99
- repo: https://github.com/prettier/pre-commit
1010
rev: 57f39166b5a5a504d6808b87ab98d41ebf095b46

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 2025-02-26
4+
- [PI-793] Remove EPR ETL
5+
- [PI-795] Remove EPR repository layers
6+
- [PI-834] Remove EPR domain logic
7+
- Dependabot: Update black
8+
- Dependabot: Update attrs
9+
310
## 2025-02-24
411
- [PI-794] Remove EPR S3 tests
512
- [PI-788] Create Product Search and Delete Flows for test UI

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2025.02.24
1+
2025.02.26
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.5.7
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
locals {
2+
region = "eu-west-2"
3+
project = "nhse-cpm"
4+
current_time = timestamp()
5+
workspace_type = var.workspace_type
6+
permission_resource_map = {
7+
kms = ["*"]
8+
dynamodb = ["${module.eprtable.dynamodb_table_arn}", "${module.eprtable.dynamodb_table_arn}/*", "${module.cpmtable.dynamodb_table_arn}", "${module.cpmtable.dynamodb_table_arn}/*"]
9+
}
10+
# e.g. api.cpm.dev.national.nhs.uk
11+
zone = var.domain
12+
13+
domain = "${terraform.workspace}.${var.domain}"
14+
etl_snapshot_bucket = contains(["int", "prod"], var.environment) ? "${local.project}--${replace(var.environment, "_", "-")}--snapshot" : "snapshot_not_required"
15+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
resource "aws_resourcegroups_group" "resource_group" {
2+
name = "${local.project}--${replace(terraform.workspace, "_", "-")}--resource-group"
3+
description = "${local.workspace_type} workspace resource group."
4+
tags = {
5+
Name = "${local.project}--${replace(terraform.workspace, "_", "-")}--resource-group"
6+
CreatedOn = var.updated_date
7+
LastUpdated = var.updated_date
8+
ExpirationDate = var.expiration_date
9+
}
10+
11+
lifecycle {
12+
ignore_changes = [tags["CreatedOn"]]
13+
}
14+
15+
resource_query {
16+
query = <<JSON
17+
{
18+
"ResourceTypeFilters": ["AWS::AllSupported"],
19+
"TagFilters": [
20+
{
21+
"Key": "Workspace",
22+
"Values": ["${replace(terraform.workspace, "_", "-")}"]
23+
}
24+
]
25+
}
26+
JSON
27+
}
28+
}
29+
30+
data "aws_secretsmanager_secret" "cpm_apigee_api_key" {
31+
name = "${var.environment}-apigee-cpm-apikey"
32+
}
33+
34+
module "eprtable" {
35+
source = "./modules/api_storage"
36+
name = "${local.project}--${replace(terraform.workspace, "_", "-")}--epr-table"
37+
environment = var.environment
38+
deletion_protection_enabled = var.deletion_protection_enabled
39+
kms_deletion_window_in_days = 7
40+
range_key = "sk"
41+
hash_key = "pk"
42+
43+
attributes = [
44+
{ name = "pk", type = "S" },
45+
{ name = "sk", type = "S" },
46+
{ name = "pk_read", type = "S" },
47+
{ name = "sk_read", type = "S" },
48+
]
49+
50+
global_secondary_indexes = [
51+
{
52+
name = "idx_gsi_read"
53+
hash_key = "pk_read"
54+
range_key = "sk_read"
55+
projection_type = "ALL"
56+
}
57+
]
58+
}
59+
60+
module "cpmtable" {
61+
source = "./modules/api_storage"
62+
name = "${local.project}--${replace(terraform.workspace, "_", "-")}--cpm-table"
63+
environment = var.environment
64+
deletion_protection_enabled = var.deletion_protection_enabled
65+
kms_deletion_window_in_days = 7
66+
range_key = "sk"
67+
hash_key = "pk"
68+
69+
attributes = [
70+
{ name = "pk", type = "S" },
71+
{ name = "sk", type = "S" },
72+
{ name = "pk_read_1", type = "S" },
73+
{ name = "sk_read_1", type = "S" },
74+
{ name = "pk_read_2", type = "S" },
75+
{ name = "sk_read_2", type = "S" },
76+
]
77+
78+
global_secondary_indexes = [
79+
{
80+
name = "idx_gsi_read_1"
81+
hash_key = "pk_read_1"
82+
range_key = "sk_read_1"
83+
projection_type = "ALL"
84+
},
85+
{
86+
name = "idx_gsi_read_2"
87+
hash_key = "pk_read_2"
88+
range_key = "sk_read_2"
89+
projection_type = "ALL"
90+
}
91+
]
92+
}
93+
94+
module "layers" {
95+
for_each = toset(var.layers)
96+
source = "./modules/api_worker/api_layer"
97+
name = each.key
98+
python_version = var.python_version
99+
layer_name = "${local.project}--${replace(terraform.workspace, "_", "-")}--${replace(each.key, "_", "-")}"
100+
source_path = "${path.module}/../../../src/layers/${each.key}/dist/${each.key}.zip"
101+
}
102+
103+
module "third_party_layers" {
104+
for_each = toset(var.third_party_layers)
105+
source = "./modules/api_worker/api_layer"
106+
name = each.key
107+
python_version = var.python_version
108+
layer_name = "${local.project}--${replace(terraform.workspace, "_", "-")}--${replace(each.key, "_", "-")}"
109+
source_path = "${path.module}/../../../src/layers/third_party/dist/${each.key}.zip"
110+
}
111+
112+
module "lambdas" {
113+
for_each = setsubtract(var.lambdas, ["authoriser"])
114+
source = "./modules/api_worker/api_lambda"
115+
python_version = var.python_version
116+
name = each.key
117+
lambda_name = "${local.project}--${replace(terraform.workspace, "_", "-")}--${replace(replace(replace(replace(each.key, "_", "-"), "DeviceReferenceData", "DeviceRefData"), "MessageHandlingSystem", "MHS"), "MessageSet", "MsgSet")}"
118+
119+
//Compact will remove all nulls from the list and create a new one - this is because TF throws an error if there is a null item in the list.
120+
layers = concat(
121+
compact([for instance in module.layers : contains(var.api_lambda_layers, instance.name) ? instance.layer_arn : null]),
122+
[element([for instance in module.third_party_layers : instance if instance.name == "third_party_core"], 0).layer_arn]
123+
)
124+
source_path = "${path.module}/../../../src/api/${each.key}/dist/${each.key}.zip"
125+
allowed_triggers = {
126+
"AllowExecutionFromAPIGateway-${replace(terraform.workspace, "_", "-")}--${replace(each.key, "_", "-")}" = {
127+
service = "apigateway"
128+
source_arn = "${module.api_entrypoint.execution_arn}/*/*/*"
129+
}
130+
}
131+
environment_variables = {
132+
DYNAMODB_TABLE = contains(["createProductTeam", "readProductTeam", "createCpmProduct", "readCpmProduct", "searchCpmProduct", "deleteCpmProduct"], each.key) ? module.cpmtable.dynamodb_table_name : module.eprtable.dynamodb_table_name
133+
}
134+
attach_policy_statements = length((fileset("${path.module}/../../../src/api/${each.key}/policies", "*.json"))) > 0
135+
policy_statements = {
136+
for file in fileset("${path.module}/../../../src/api/${each.key}/policies", "*.json") : replace(file, ".json", "") => {
137+
effect = "Allow"
138+
actions = jsondecode(file("${path.module}/../../../src/api/${each.key}/policies/${file}"))
139+
resources = local.permission_resource_map[replace(file, ".json", "")]
140+
}
141+
}
142+
memory_size = var.lambda_memory_size
143+
}
144+
145+
module "authoriser" {
146+
name = "authoriser"
147+
source = "./modules/api_worker/api_lambda"
148+
python_version = var.python_version
149+
lambda_name = "${local.project}--${replace(terraform.workspace, "_", "-")}--authoriser"
150+
source_path = "${path.module}/../../../src/api/authoriser/dist/authoriser.zip"
151+
environment_variables = {
152+
ENVIRONMENT = var.environment
153+
}
154+
layers = concat(
155+
compact([for instance in module.layers : contains(var.api_lambda_layers, instance.name) ? instance.layer_arn : null]),
156+
[element([for instance in module.third_party_layers : instance if instance.name == "third_party_core"], 0).layer_arn]
157+
)
158+
trusted_entities = [
159+
{
160+
type = "Service",
161+
identifiers = [
162+
"apigateway.amazonaws.com"
163+
]
164+
}
165+
]
166+
167+
attach_policy_json = true
168+
policy_json = <<-EOT
169+
{
170+
"Version": "2012-10-17",
171+
"Statement": [
172+
{
173+
"Action": "lambda:InvokeFunction",
174+
"Effect": "Allow",
175+
"Resource": "arn:aws:lambda:eu-west-2:${var.assume_account}:function:${local.project}--${replace(terraform.workspace, "_", "-")}--authoriser"
176+
},
177+
{
178+
"Action": "secretsmanager:GetSecretValue",
179+
"Effect": "Allow",
180+
"Resource": "${data.aws_secretsmanager_secret.cpm_apigee_api_key.arn}"
181+
}
182+
]
183+
}
184+
EOT
185+
}
186+
187+
module "domain" {
188+
source = "./modules/domain"
189+
domain = local.domain
190+
zone = local.zone
191+
}
192+
193+
module "api_entrypoint" {
194+
source = "./modules/api_entrypoint"
195+
assume_account = var.assume_account
196+
project = local.project
197+
name = "${local.project}--${replace(terraform.workspace, "_", "-")}--api-entrypoint"
198+
lambdas = setsubtract(var.lambdas, ["authoriser"])
199+
authoriser_metadata = module.authoriser.metadata
200+
domain = module.domain.domain_cert
201+
depends_on = [module.domain]
202+
}
203+
204+
data "aws_s3_bucket" "truststore_bucket" {
205+
bucket = "${local.project}--${replace(var.environment, "_", "-")}--truststore"
206+
}
207+
208+
209+
module "sds_etl" {
210+
source = "./modules/etl/sds"
211+
workspace_prefix = "${local.project}--${replace(terraform.workspace, "_", "-")}"
212+
assume_account = var.assume_account
213+
python_version = var.python_version
214+
event_layer_arn = element([for instance in module.layers : instance if instance.name == "event"], 0).layer_arn
215+
third_party_core_layer_arn = element([for instance in module.third_party_layers : instance if instance.name == "third_party_sds"], 0).layer_arn
216+
third_party_sds_update_layer_arn = element([for instance in module.third_party_layers : instance if instance.name == "third_party_sds_update"], 0).layer_arn
217+
domain_layer_arn = element([for instance in module.layers : instance if instance.name == "domain"], 0).layer_arn
218+
sds_layer_arn = element([for instance in module.layers : instance if instance.name == "sds"], 0).layer_arn
219+
table_name = module.eprtable.dynamodb_table_name
220+
table_arn = module.eprtable.dynamodb_table_arn
221+
is_persistent = var.workspace_type == "PERSISTENT"
222+
truststore_bucket = data.aws_s3_bucket.truststore_bucket
223+
etl_snapshot_bucket = local.etl_snapshot_bucket
224+
environment = var.environment
225+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
resource "aws_api_gateway_rest_api" "api_gateway_rest_api" {
2+
name = var.name
3+
description = "API Gateway Rest API - autogenerated from swagger"
4+
# UNCOMMENT THIS WHEN ENABLING CUSTOM DOMAINS
5+
# disable_execute_api_endpoint = true
6+
body = sensitive(local.swagger_file)
7+
8+
depends_on = [
9+
aws_cloudwatch_log_group.api_gateway_access_logs
10+
]
11+
12+
}
13+
14+
resource "aws_api_gateway_deployment" "api_gateway_deployment" {
15+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
16+
17+
triggers = {
18+
redeployment = sha1(jsonencode(aws_api_gateway_rest_api.api_gateway_rest_api.body))
19+
resource_change = "${md5(file("${path.module}/api_gateway.tf"))}"
20+
}
21+
22+
lifecycle {
23+
create_before_destroy = true
24+
}
25+
26+
depends_on = [
27+
aws_api_gateway_rest_api.api_gateway_rest_api,
28+
aws_cloudwatch_log_group.api_gateway_access_logs,
29+
aws_cloudwatch_log_group.api_gateway_execution_logs
30+
]
31+
}
32+
33+
resource "aws_api_gateway_stage" "api_gateway_stage" {
34+
deployment_id = aws_api_gateway_deployment.api_gateway_deployment.id
35+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
36+
stage_name = "production"
37+
xray_tracing_enabled = true
38+
39+
access_log_settings {
40+
destination_arn = aws_cloudwatch_log_group.api_gateway_access_logs.arn
41+
format = jsonencode({
42+
requestid : "$context.requestId",
43+
ip : "$context.identity.sourceIp",
44+
user_agent : "$context.identity.userAgent",
45+
request_time : "$context.requestTime",
46+
http_method : "$context.httpMethod",
47+
path : "$context.path",
48+
status : "$context.status",
49+
protocol : "$context.protocol",
50+
response_length : "$context.responseLength",
51+
x_correlationid : "$context.authorizer.x-correlation-id",
52+
nhsd_correlationid : "$context.authorizer.nhsd-correlation-id"
53+
environment : terraform.workspace
54+
})
55+
}
56+
57+
depends_on = [
58+
aws_api_gateway_deployment.api_gateway_deployment,
59+
aws_api_gateway_rest_api.api_gateway_rest_api,
60+
aws_cloudwatch_log_group.api_gateway_access_logs,
61+
aws_cloudwatch_log_group.api_gateway_execution_logs
62+
]
63+
}
64+
65+
resource "aws_api_gateway_method_settings" "api_gateway_method_settings" {
66+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
67+
stage_name = aws_api_gateway_stage.api_gateway_stage.stage_name
68+
method_path = "*/*"
69+
settings {
70+
logging_level = "INFO"
71+
data_trace_enabled = true
72+
}
73+
74+
depends_on = [
75+
aws_api_gateway_rest_api.api_gateway_rest_api,
76+
aws_api_gateway_stage.api_gateway_stage
77+
]
78+
}
79+
80+
resource "aws_api_gateway_gateway_response" "api_access_denied" {
81+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
82+
response_type = "ACCESS_DENIED"
83+
response_templates = {
84+
"application/json" = jsonencode({
85+
errors : [{
86+
code : "PROCESSING"
87+
message : "$context.authorizer.error"
88+
}]
89+
})
90+
}
91+
response_parameters = {
92+
"gatewayresponse.header.Access-Control-Allow-Origin" = "'*'"
93+
}
94+
}
95+
96+
resource "aws_api_gateway_gateway_response" "api_default_4xx" {
97+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
98+
response_type = "DEFAULT_4XX"
99+
response_templates = {
100+
"application/json" = jsonencode({
101+
errors : [{
102+
code : "PROCESSING"
103+
message : "$context.error.message"
104+
}]
105+
}) }
106+
response_parameters = { "gatewayresponse.header.Access-Control-Allow-Origin" = "'*'"
107+
}
108+
}
109+
110+
resource "aws_api_gateway_gateway_response" "api_default_5xx" {
111+
rest_api_id = aws_api_gateway_rest_api.api_gateway_rest_api.id
112+
response_type = "DEFAULT_5XX"
113+
response_templates = {
114+
"application/json" = jsonencode({
115+
errors : [{
116+
code : "PROCESSING"
117+
message : "exception"
118+
}]
119+
}) }
120+
response_parameters = { "gatewayresponse.header.Access-Control-Allow-Origin" = "'*'"
121+
}
122+
}

0 commit comments

Comments
 (0)