Skip to content

Commit 0728630

Browse files
committed
move API gateway to stateless stack
1 parent 1a42b7f commit 0728630

9 files changed

Lines changed: 219 additions & 3 deletions

File tree

.github/workflows/cdk_release_code.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ on:
2929
IS_PULL_REQUEST:
3030
type: boolean
3131
required: true
32+
TRUSTSTORE_FILE:
33+
type: string
34+
required: true
35+
FORWARD_CSOC_LOGS:
36+
type: boolean
37+
required: true
38+
DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE:
39+
type: boolean
40+
required: true
41+
EXPOSE_GET_STATUS_UPDATES:
42+
type: boolean
43+
required: true
3244
pinned_image:
3345
required: true
3446
type: string
@@ -91,6 +103,10 @@ jobs:
91103
CDK_CONFIG_environment: "${{ inputs.AWS_ENVIRONMENT }}"
92104
CDK_CONFIG_logRetentionInDays: "${{ inputs.LOG_RETENTION_IN_DAYS }}"
93105
CDK_CONFIG_logLevel: "${{ inputs.LOG_LEVEL }}"
106+
CDK_CONFIG_trustStoreFile: "${{ inputs.TRUSTSTORE_FILE }}"
107+
CDK_CONFIG_forwardCsocLogs: "${{ inputs.FORWARD_CSOC_LOGS }}"
108+
CDK_CONFIG_deployCheckPrescriptionStatusUpdate: "${{ inputs.DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE }}"
109+
CDK_CONFIG_exposeGetStatusUpdates: "${{ inputs.EXPOSE_GET_STATUS_UPDATES }}"
94110
REQUIRE_APPROVAL: "never"
95111

96112
# later, there will be API deployment steps c.f. https://github.com/NHSDigital/electronic-prescription-service-clinical-prescription-tracker/blob/main/.github/workflows/cdk_release_code.yml

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ jobs:
8181
LOG_RETENTION_IN_DAYS: "30"
8282
LOG_LEVEL: DEBUG
8383
IS_PULL_REQUEST: false
84+
TRUSTSTORE_FILE: psu-truststore.pem
85+
FORWARD_CSOC_LOGS: false
86+
DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true
87+
EXPOSE_GET_STATUS_UPDATES: false
8488
secrets:
8589
CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }}
8690

.github/workflows/pull_request.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ jobs:
124124
LOG_RETENTION_IN_DAYS: "30"
125125
LOG_LEVEL: DEBUG
126126
IS_PULL_REQUEST: true
127+
TRUSTSTORE_FILE: psu-truststore.pem
128+
FORWARD_CSOC_LOGS: false
129+
DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true
130+
EXPOSE_GET_STATUS_UPDATES: false
127131
secrets:
128132
CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }}
129133

.github/workflows/release.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ jobs:
8383
LOG_RETENTION_IN_DAYS: "30"
8484
LOG_LEVEL: DEBUG
8585
IS_PULL_REQUEST: false
86+
TRUSTSTORE_FILE: psu-truststore.pem
87+
FORWARD_CSOC_LOGS: false
88+
DEPLOY_CHECK_PRESCRIPTION_STATUS_UPDATE: true
89+
EXPOSE_GET_STATUS_UPDATES: false
8690
secrets:
8791
CLOUD_FORMATION_DEPLOY_ROLE: ${{ secrets.DEV_CLOUD_FORMATION_DEPLOY_ROLE }}
8892

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export CDK_CONFIG_isPullRequest=true
88
export CDK_CONFIG_environment=dev
99
export CDK_CONFIG_logRetentionInDays=30
1010
export CDK_CONFIG_logLevel=DEBUG
11+
export CDK_CONFIG_trustStoreFile=psu-truststore.pem
12+
export CDK_CONFIG_forwardCsocLogs=false
13+
export CDK_CONFIG_deployCheckPrescriptionStatusUpdate=true
14+
export CDK_CONFIG_exposeGetStatusUpdates=false
1115

1216
guard-%:
1317
@ if [ "${${*}}" = "" ]; then \

packages/cdk/bin/PsuStatelessApp.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import {calculateVersionedStackName, createApp, getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs"
1+
import {
2+
calculateVersionedStackName,
3+
createApp,
4+
getBooleanConfigFromEnvVar,
5+
getConfigFromEnvVar,
6+
getNumberConfigFromEnvVar
7+
} from "@nhsdigital/eps-cdk-constructs"
28
import {PsuStatelessStack} from "../stacks/PsuStatelessStack"
39

410
async function main() {
@@ -11,7 +17,13 @@ async function main() {
1117

1218
new PsuStatelessStack(app, "PsuStatelessStack", {
1319
...props,
14-
stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props)
20+
stackName: calculateVersionedStackName(getConfigFromEnvVar("stackName"), props),
21+
logRetentionInDays: getNumberConfigFromEnvVar("logRetentionInDays"),
22+
mutualTlsTrustStoreKey: props.isPullRequest ? undefined : getConfigFromEnvVar("trustStoreFile"),
23+
csocApiGatewayDestination: "arn:aws:logs:eu-west-2:693466633220:destination:api_gateway_log_destination",
24+
forwardCsocLogs: getBooleanConfigFromEnvVar("forwardCsocLogs"),
25+
deployCheckPrescriptionStatusUpdate: getBooleanConfigFromEnvVar("deployCheckPrescriptionStatusUpdate"),
26+
exposeGetStatusUpdates: getBooleanConfigFromEnvVar("exposeGetStatusUpdates")
1527
})
1628
}
1729

packages/cdk/resources/Apis.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {LambdaIntegration, PassthroughBehavior, StepFunctionsIntegration} from "aws-cdk-lib/aws-apigateway"
2+
import {IManagedPolicy} from "aws-cdk-lib/aws-iam"
3+
import {HttpMethod} from "aws-cdk-lib/aws-lambda"
4+
import {Construct} from "constructs"
5+
import {
6+
ExpressStateMachine,
7+
LambdaEndpoint,
8+
RestApiGateway,
9+
StateMachineEndpoint,
10+
TypescriptLambdaFunction
11+
} from "@nhsdigital/eps-cdk-constructs"
12+
13+
export interface ApisProps {
14+
readonly stackName: string
15+
readonly logRetentionInDays: number
16+
readonly mutualTlsTrustStoreKey: string | undefined
17+
readonly forwardCsocLogs: boolean
18+
readonly csocApiGatewayDestination: string
19+
readonly deployCheckPrescriptionStatusUpdate: boolean
20+
readonly exposeGetStatusUpdates: boolean
21+
functions: {[key: string]: TypescriptLambdaFunction}
22+
stateMachines: {[key: string]: ExpressStateMachine}
23+
}
24+
25+
export class Apis extends Construct {
26+
public constructor(scope: Construct, id: string, props: ApisProps) {
27+
super(scope, id)
28+
29+
// Collect execution policies for all resources that need API Gateway access
30+
const executionPolicies: Array<IManagedPolicy> = [
31+
props.stateMachines.updatePrescriptionStatus.executionPolicy,
32+
props.stateMachines.format1UpdatePrescriptionsStatus.executionPolicy,
33+
props.functions.status.executionPolicy,
34+
props.functions.capabilityStatement.executionPolicy,
35+
props.functions.nhsNotifyUpdateCallback.executionPolicy
36+
]
37+
38+
if (props.exposeGetStatusUpdates) {
39+
executionPolicies.push(props.functions.getStatusUpdates.executionPolicy)
40+
}
41+
42+
if (props.deployCheckPrescriptionStatusUpdate) {
43+
executionPolicies.push(props.functions.checkPrescriptionStatusUpdates.executionPolicy)
44+
}
45+
46+
const apiGateway = new RestApiGateway(this, "RestApiGateway", {
47+
stackName: props.stackName,
48+
logRetentionInDays: props.logRetentionInDays,
49+
mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey,
50+
forwardCsocLogs: props.forwardCsocLogs,
51+
csocApiGatewayDestination: props.csocApiGatewayDestination,
52+
executionPolicies
53+
})
54+
55+
const rootResource = apiGateway.api.root
56+
57+
// POST / — UpdatePrescriptionStatus state machine integration (root resource)
58+
rootResource.addMethod(
59+
HttpMethod.POST,
60+
StepFunctionsIntegration.startExecution(
61+
props.stateMachines.updatePrescriptionStatus.stateMachine,
62+
{
63+
credentialsRole: apiGateway.role,
64+
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH
65+
}
66+
),
67+
{
68+
methodResponses: [
69+
{statusCode: "200"},
70+
{statusCode: "400"},
71+
{statusCode: "500"}
72+
]
73+
}
74+
)
75+
76+
// POST /format-1 — Format1 state machine integration
77+
new StateMachineEndpoint(this, "Format1UpdatePrescriptionStatusEndpoint", {
78+
parentResource: rootResource,
79+
resourceName: "format-1",
80+
method: HttpMethod.POST,
81+
restApiGatewayRole: apiGateway.role,
82+
stateMachine: props.stateMachines.format1UpdatePrescriptionsStatus
83+
})
84+
85+
// POST /notification-delivery-status-callback — Lambda proxy integration
86+
new LambdaEndpoint(this, "NotificationDeliveryStatusCallbackEndpoint", {
87+
parentResource: rootResource,
88+
resourceName: "notification-delivery-status-callback",
89+
method: HttpMethod.POST,
90+
restApiGatewayRole: apiGateway.role,
91+
lambdaFunction: props.functions.nhsNotifyUpdateCallback
92+
})
93+
94+
// GET /_status — Lambda proxy integration
95+
new LambdaEndpoint(this, "StatusEndpoint", {
96+
parentResource: rootResource,
97+
resourceName: "_status",
98+
method: HttpMethod.GET,
99+
restApiGatewayRole: apiGateway.role,
100+
lambdaFunction: props.functions.status
101+
})
102+
103+
// GET /metadata — Lambda proxy integration
104+
new LambdaEndpoint(this, "CapabilityStatementEndpoint", {
105+
parentResource: rootResource,
106+
resourceName: "metadata",
107+
method: HttpMethod.GET,
108+
restApiGatewayRole: apiGateway.role,
109+
lambdaFunction: props.functions.capabilityStatement
110+
})
111+
112+
// GET /checkprescriptionstatusupdates — conditional Lambda proxy integration
113+
if (props.deployCheckPrescriptionStatusUpdate) {
114+
new LambdaEndpoint(this, "CheckPrescriptionStatusUpdatesEndpoint", {
115+
parentResource: rootResource,
116+
resourceName: "checkprescriptionstatusupdates",
117+
method: HttpMethod.GET,
118+
restApiGatewayRole: apiGateway.role,
119+
lambdaFunction: props.functions.checkPrescriptionStatusUpdates
120+
})
121+
}
122+
123+
// POST /get-status-updates — conditional Lambda integration (non-proxy)
124+
if (props.exposeGetStatusUpdates) {
125+
const getStatusUpdatesResource = rootResource.addResource("get-status-updates")
126+
getStatusUpdatesResource.addMethod(
127+
HttpMethod.POST,
128+
new LambdaIntegration(props.functions.getStatusUpdates.function, {
129+
credentialsRole: apiGateway.role,
130+
proxy: false,
131+
requestTemplates: {
132+
"application/json": "$input.json('$')"
133+
},
134+
integrationResponses: [
135+
{
136+
statusCode: "200",
137+
responseTemplates: {
138+
"application/json": "$input.body"
139+
}
140+
}
141+
]
142+
}),
143+
{
144+
methodResponses: [
145+
{statusCode: "200"}
146+
]
147+
}
148+
)
149+
}
150+
}
151+
}

packages/cdk/stacks/PsuStatelessStack.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,33 @@ import {StandardStackProps} from "@nhsdigital/eps-cdk-constructs"
44

55
export interface PsuStatelessStackProps extends StandardStackProps {
66
readonly stackName: string
7+
readonly logRetentionInDays: number
8+
readonly mutualTlsTrustStoreKey: string | undefined
9+
readonly forwardCsocLogs: boolean
10+
readonly csocApiGatewayDestination: string
11+
readonly deployCheckPrescriptionStatusUpdate: boolean
12+
readonly exposeGetStatusUpdates: boolean
713
}
814

915
export class PsuStatelessStack extends Stack {
1016
public constructor(scope: App, id: string, props: PsuStatelessStackProps) {
1117
super(scope, id, props)
1218

13-
// Resources will be added here as they are migrated from SAM templates
19+
// Apis construct will be instantiated here once Functions and StateMachines are migrated:
20+
//
21+
// const functions = new Functions(this, "Functions", { ... })
22+
// const stateMachines = new StateMachines(this, "StateMachines", { ... })
23+
// new Apis(this, "Apis", {
24+
// stackName: props.stackName,
25+
// logRetentionInDays: props.logRetentionInDays,
26+
// mutualTlsTrustStoreKey: props.mutualTlsTrustStoreKey,
27+
// forwardCsocLogs: props.forwardCsocLogs,
28+
// csocApiGatewayDestination: props.csocApiGatewayDestination,
29+
// deployCheckPrescriptionStatusUpdate: props.deployCheckPrescriptionStatusUpdate,
30+
// exposeGetStatusUpdates: props.exposeGetStatusUpdates,
31+
// functions: functions.functions,
32+
// stateMachines: stateMachines.stateMachines,
33+
// })
1434

1535
nagSuppressions(this)
1636
}

packages/cdk/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"include": [
2929
"stacks/**/*",
3030
"bin/**/*",
31+
"resources/**/*",
3132
"nagSuppressions.ts"
3233
],
3334
"exclude": [

0 commit comments

Comments
 (0)