Skip to content

Commit daad389

Browse files
Split out fixSpec from deployApi
1 parent de588aa commit daad389

5 files changed

Lines changed: 247 additions & 144 deletions

File tree

.vscode/eps-cdk-utils.code-workspace

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
],
1212
"settings": {
1313
"files.exclude": {
14-
"packages/": true,
14+
"packages/": true,
1515
},
16-
"cSpell.words": [
16+
"cSpell.words": [
1717
"apigw",
1818
"ASID",
1919
"AWSKMS",
@@ -49,6 +49,7 @@
4949
"pollable",
5050
"powertools",
5151
"Prosthetist",
52+
"proxygen",
5253
"querystring",
5354
"reingest",
5455
"reingested",
Lines changed: 74 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
21
import {LambdaClient, InvokeCommand} from "@aws-sdk/client-lambda"
3-
import {getCFConfigValue, getCloudFormationExports, calculateVersionedStackName} from "../config"
2+
import {getCFConfigValue, getCloudFormationExports} from "../config"
3+
import {fixSpec} from "./fixSpec"
44

55
export type ApiConfig = {
6-
specification: string
6+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7+
spec: any
78
apiName: string
89
version: string
910
apigeeEnvironment: string
@@ -18,9 +19,31 @@ export type ApiConfig = {
1819
hiddenPaths: Array<string>
1920
}
2021

22+
const lambda = new LambdaClient({})
23+
24+
async function invokeLambda(
25+
dryRun: boolean,
26+
functionName: string,
27+
payload: unknown
28+
): Promise<void> {
29+
if (dryRun) {
30+
console.log(`Would invoke lambda ${functionName}`)
31+
return
32+
}
33+
const invokeResult = await lambda.send(new InvokeCommand({
34+
FunctionName: functionName,
35+
Payload: Buffer.from(JSON.stringify(payload))
36+
}))
37+
const responsePayload = Buffer.from(invokeResult.Payload!).toString()
38+
if (invokeResult.FunctionError) {
39+
throw new Error(`Error calling lambda ${functionName}: ${responsePayload}`)
40+
}
41+
console.log(`Lambda ${functionName} invoked successfully. Response:`, responsePayload)
42+
}
43+
2144
export async function deployApi(
2245
{
23-
specification,
46+
spec,
2447
apiName,
2548
version,
2649
apigeeEnvironment,
@@ -36,59 +59,16 @@ export async function deployApi(
3659
}: ApiConfig,
3760
dryRun: boolean
3861
): Promise<void> {
39-
const lambda = new LambdaClient({})
40-
async function invokeLambda(functionName: string, payload: unknown): Promise<void> {
41-
if (dryRun) {
42-
console.log(`Would invoke lambda ${functionName}`)
43-
return
44-
}
45-
46-
const invokeResult = await lambda.send(new InvokeCommand({
47-
FunctionName: functionName,
48-
Payload: Buffer.from(JSON.stringify(payload))
49-
}))
50-
const responsePayload = Buffer.from(invokeResult.Payload!).toString()
51-
if (invokeResult.FunctionError) {
52-
throw new Error(`Error calling lambda ${functionName}: ${responsePayload}`)
53-
}
54-
console.log(`Lambda ${functionName} invoked successfully. Response:`, responsePayload)
55-
}
56-
57-
let instance = apiName
58-
const spec = JSON.parse(specification)
59-
if (isPullRequest) {
60-
const pr_id = stackName.split("-").pop()
61-
instance = `${apiName}-pr-${pr_id}`
62-
spec.info.title = `[PR-${pr_id}] ${spec.info.title}`
63-
spec["x-nhsd-apim"].monitoring = false
64-
delete spec["x-nhsd-apim"].target.security.secret
65-
} else {
66-
stackName = calculateVersionedStackName(stackName, version)
67-
spec["x-nhsd-apim"].target.security.secret = mtlsSecretName
68-
}
69-
spec.info.version = version
70-
spec["x-nhsd-apim"].target.url = `https://${stackName}.${awsEnvironment}.eps.national.nhs.uk`
71-
72-
function replaceSchemeRefs(domain: string) {
73-
const schemes = ["nhs-cis2-aal3", "nhs-login-p9", "app-level3", "app-level0"]
74-
for (const scheme of schemes) {
75-
if (spec.components.securitySchemes[scheme]) {
76-
spec.components.securitySchemes[scheme] = {
77-
"$ref": `https://${domain}/components/securitySchemes/${scheme}`
78-
}
79-
}
80-
}
81-
}
82-
if (apigeeEnvironment === "prod") {
83-
spec.servers = [ {url: `https://api.service.nhs.uk/${instance}`} ]
84-
replaceSchemeRefs("proxygen.prod.api.platform.nhs.uk")
85-
} else {
86-
spec.servers = [ {url: `https://${apigeeEnvironment}.api.service.nhs.uk/${instance}`} ]
87-
replaceSchemeRefs("proxygen.ptl.api.platform.nhs.uk")
88-
}
89-
if (apigeeEnvironment.includes("sandbox")) {
90-
delete spec["x-nhsd-apim"]["target-attributes"] // Resolve issue with sandbox trying to look up app name
91-
}
62+
const instance = fixSpec(
63+
spec,
64+
apiName,
65+
version,
66+
apigeeEnvironment,
67+
isPullRequest,
68+
awsEnvironment,
69+
stackName,
70+
mtlsSecretName
71+
)
9272

9373
const exports = await getCloudFormationExports()
9474
const clientCertArn = getCFConfigValue(exports, `account-resources:${clientCertExportName}`)
@@ -104,32 +84,37 @@ export async function deployApi(
10484
spec_publish_lambda = "lambda-resources-ProxygenProdSpecPublish"
10585
}
10686

107-
// --- Store the secret used for mutual TLS ---
10887
if (!isPullRequest) {
10988
console.log("Store the secret used for mutual TLS to AWS using Proxygen proxy lambda")
110-
await invokeLambda(put_secret_lambda, {
89+
await invokeLambda(
90+
dryRun,
91+
put_secret_lambda,
92+
{
93+
apiName,
94+
environment: apigeeEnvironment,
95+
secretName: mtlsSecretName,
96+
secretKeyName: clientPrivateKeyArn,
97+
secretCertName: clientCertArn,
98+
kid: proxygenKid,
99+
proxygenSecretName: proxygenPrivateKeyArn
100+
}
101+
)
102+
}
103+
104+
console.log("Deploy the API instance using Proxygen proxy lambda")
105+
await invokeLambda(
106+
dryRun,
107+
instance_put_lambda,
108+
{
111109
apiName,
112110
environment: apigeeEnvironment,
113-
secretName: mtlsSecretName,
114-
secretKeyName: clientPrivateKeyArn,
115-
secretCertName: clientCertArn,
111+
specDefinition: spec,
112+
instance,
116113
kid: proxygenKid,
117114
proxygenSecretName: proxygenPrivateKeyArn
118-
})
119-
}
120-
121-
// --- Deploy the API instance ---
122-
console.log("Deploy the API instance using Proxygen proxy lambda")
123-
await invokeLambda(instance_put_lambda, {
124-
apiName,
125-
environment: apigeeEnvironment,
126-
specDefinition: spec,
127-
instance,
128-
kid: proxygenKid,
129-
proxygenSecretName: proxygenPrivateKeyArn
130-
})
115+
}
116+
)
131117

132-
// --- Publish the API spec to the catalogue ---
133118
let spec_publish_env
134119
if (apigeeEnvironment === "int") {
135120
console.log("Deploy the API spec to prod catalogue as it is int environment")
@@ -142,17 +127,19 @@ export async function deployApi(
142127
}
143128
if (spec_publish_env) {
144129
for (const path of hiddenPaths) {
145-
if (spec.paths[path]) {
146-
delete spec.paths[path]
147-
}
130+
delete spec.paths[path]
148131
}
149-
await invokeLambda(spec_publish_lambda, {
150-
apiName,
151-
environment: spec_publish_env,
152-
specDefinition: spec,
153-
instance,
154-
kid: proxygenKid,
155-
proxygenSecretName: proxygenPrivateKeyArn
156-
})
132+
await invokeLambda(
133+
dryRun,
134+
spec_publish_lambda,
135+
{
136+
apiName,
137+
environment: spec_publish_env,
138+
specDefinition: spec,
139+
instance,
140+
kid: proxygenKid,
141+
proxygenSecretName: proxygenPrivateKeyArn
142+
}
143+
)
157144
}
158145
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {calculateVersionedStackName} from "../config"
2+
3+
function replaceSchemeRefs(
4+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5+
spec: any,
6+
domain: string
7+
) {
8+
const schemes = ["nhs-cis2-aal3", "nhs-login-p9", "app-level3", "app-level0"]
9+
for (const scheme of schemes) {
10+
if (spec.components.securitySchemes[scheme]) {
11+
spec.components.securitySchemes[scheme] = {
12+
"$ref": `https://${domain}/components/securitySchemes/${scheme}`
13+
}
14+
}
15+
}
16+
}
17+
18+
export function fixSpec(
19+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20+
spec: any,
21+
apiName: string,
22+
version: string,
23+
apigeeEnvironment: string,
24+
isPullRequest: boolean,
25+
awsEnvironment: string,
26+
stackName: string,
27+
mtlsSecretName: string
28+
): string {
29+
let instance = apiName
30+
let stack = stackName
31+
if (isPullRequest) {
32+
const pr_id = stackName.split("-").pop()
33+
instance = `${apiName}-pr-${pr_id}`
34+
spec.info.title = `[PR-${pr_id}] ${spec.info.title}`
35+
spec["x-nhsd-apim"].monitoring = false
36+
delete spec["x-nhsd-apim"].target.security.secret
37+
} else {
38+
stack = calculateVersionedStackName(stackName, version)
39+
spec["x-nhsd-apim"].target.security.secret = mtlsSecretName
40+
}
41+
spec.info.version = version
42+
spec["x-nhsd-apim"].target.url = `https://${stack}.${awsEnvironment}.eps.national.nhs.uk`
43+
if (apigeeEnvironment === "prod") {
44+
spec.servers = [ {url: `https://api.service.nhs.uk/${instance}`} ]
45+
replaceSchemeRefs(spec, "proxygen.prod.api.platform.nhs.uk")
46+
} else {
47+
spec.servers = [ {url: `https://${apigeeEnvironment}.api.service.nhs.uk/${instance}`} ]
48+
replaceSchemeRefs(spec, "proxygen.ptl.api.platform.nhs.uk")
49+
}
50+
if (apigeeEnvironment.includes("sandbox")) {
51+
delete spec["x-nhsd-apim"]["target-attributes"] // Resolve issue with sandbox trying to look up app name
52+
}
53+
return instance
54+
}

packages/cdkConstructs/tests/specifications/deployApi.test.ts

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ const defaultExportsMap = {
7272

7373
function buildConfig(overrides: Partial<ApiConfig> = {}): ApiConfig {
7474
return {
75-
specification: JSON.stringify(createSpec()),
75+
spec: createSpec(),
7676
apiName: "eps",
7777
version: "1.0.0",
7878
apigeeEnvironment: "internal-dev",
@@ -133,14 +133,6 @@ describe("deployApi", () => {
133133
expect(functionNameFromCall(1)).toBe("lambda-resources-ProxygenPTLInstancePut")
134134
const instancePayload = payloadFromCall(1)
135135
expect(instancePayload.instance).toBe("eps")
136-
expect(instancePayload.specDefinition.info.version).toBe("2.0.0")
137-
expect(instancePayload.specDefinition["x-nhsd-apim"].target.security.secret).toBe("mtls/secret")
138-
expect(instancePayload.specDefinition["x-nhsd-apim"].target.url)
139-
.toBe("https://eps-stack-2-0-0.nonprod.eps.national.nhs.uk")
140-
expect(instancePayload.specDefinition.components.securitySchemes["nhs-cis2-aal3"].$ref)
141-
.toBe("https://proxygen.ptl.api.platform.nhs.uk/components/securitySchemes/nhs-cis2-aal3")
142-
expect(instancePayload.specDefinition.servers[0].url)
143-
.toBe("https://internal-dev.api.service.nhs.uk/eps")
144136

145137
expect(functionNameFromCall(2)).toBe("lambda-resources-ProxygenPTLSpecPublish")
146138
const publishPayload = payloadFromCall(2)
@@ -166,15 +158,9 @@ describe("deployApi", () => {
166158

167159
const instancePayload = payloadFromCall(0)
168160
expect(instancePayload.instance).toBe("eps-pr-456")
169-
expect(instancePayload.specDefinition.info.title).toBe("[PR-456] EPS API")
170-
expect(instancePayload.specDefinition["x-nhsd-apim"].monitoring).toBe(false)
171-
expect(instancePayload.specDefinition["x-nhsd-apim"].target.security.secret).toBeUndefined()
172-
expect(instancePayload.specDefinition["x-nhsd-apim"]["target-attributes"]).toBeUndefined()
173-
expect(instancePayload.specDefinition.servers[0].url)
174-
.toBe("https://sandbox.api.service.nhs.uk/eps-pr-456")
175161
})
176162

177-
test("uses prod lambdas and prod security scheme refs", async () => {
163+
test("uses prod lambdas for prod environment", async () => {
178164
await deployApi(
179165
buildConfig({
180166
version: "4.0.0",
@@ -189,12 +175,6 @@ describe("deployApi", () => {
189175
expect(lambdaSendMock).toHaveBeenCalledTimes(2)
190176
expect(functionNameFromCall(0)).toBe("lambda-resources-ProxygenProdMTLSSecretPut")
191177
expect(functionNameFromCall(1)).toBe("lambda-resources-ProxygenProdInstancePut")
192-
193-
const specPayload = payloadFromCall(1)
194-
expect(specPayload.specDefinition.servers[0].url)
195-
.toBe("https://api.service.nhs.uk/eps")
196-
expect(specPayload.specDefinition.components.securitySchemes["nhs-cis2-aal3"].$ref)
197-
.toBe("https://proxygen.prod.api.platform.nhs.uk/components/securitySchemes/nhs-cis2-aal3")
198178
})
199179

200180
test("publishes spec to prod catalogue for int environment", async () => {
@@ -216,38 +196,6 @@ describe("deployApi", () => {
216196
.toBe("https://sandbox.api.service.nhs.uk/eps")
217197
})
218198

219-
test("replaces all supported security scheme refs", async () => {
220-
const spec = createSpec({
221-
securitySchemes: {
222-
"nhs-cis2-aal3": {},
223-
"nhs-login-p9": {},
224-
"app-level3": {},
225-
"app-level0": {}
226-
}
227-
})
228-
await deployApi(
229-
buildConfig({
230-
specification: JSON.stringify(spec),
231-
apigeeEnvironment: "prod",
232-
awsEnvironment: "prod",
233-
stackName: "eps-prod-stack",
234-
proxygenKid: "kid-prod"
235-
}),
236-
false
237-
)
238-
const specPayload = payloadFromCall(1)
239-
const schemes = [
240-
"nhs-cis2-aal3",
241-
"nhs-login-p9",
242-
"app-level3",
243-
"app-level0"
244-
]
245-
for (const scheme of schemes) {
246-
expect(specPayload.specDefinition.components.securitySchemes[scheme].$ref)
247-
.toBe(`https://proxygen.prod.api.platform.nhs.uk/components/securitySchemes/${scheme}`)
248-
}
249-
})
250-
251199
test("removes hidden paths from published spec", async () => {
252200
const spec = createSpec({
253201
paths: {
@@ -257,7 +205,7 @@ describe("deployApi", () => {
257205
})
258206
await deployApi(
259207
buildConfig({
260-
specification: JSON.stringify(spec),
208+
spec,
261209
apigeeEnvironment: "int",
262210
stackName: "eps-int-stack",
263211
proxygenKid: "kid-int",

0 commit comments

Comments
 (0)