Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
02b0af2
refactor: api gateway from cpt for reuse
tstephen-nhs Mar 18, 2026
455b657
Merge branch 'main' into aea-6254-cdk-api-gateway
tstephen-nhs Mar 18, 2026
26fbada
feat: add state machine construct inc. api gateway endpoint
tstephen-nhs Mar 18, 2026
db95a9f
chore: fix 'any' use
tstephen-nhs Mar 18, 2026
d3594ca
Merge branch 'main' into aea-6254-cdk-api-gateway
tstephen-nhs Mar 24, 2026
f3a9207
Add a construct that creates a batch of SSM parameters, thier outputs…
wildjames Mar 24, 2026
c7dbb39
Hack around sonar complaint
wildjames Mar 24, 2026
f7e421d
Add some validation around parameters being unique, and unit tests wr…
wildjames Mar 24, 2026
2afccf1
Fallback to non optional descriptions and names
wildjames Mar 24, 2026
dc6f2d3
Merge in aea-6256-cdk-statemachine
wildjames Mar 24, 2026
bb83c29
fix: enforce expected role
tstephen-nhs Mar 25, 2026
74d0de2
fix: make LogGroup child of API gateway
tstephen-nhs Mar 25, 2026
c7d714d
chore: add sonarqube plugin for 'clean as you code'
tstephen-nhs Mar 25, 2026
f2e9325
fix: protect against enabling csoc with no destination
tstephen-nhs Mar 25, 2026
0ac46b1
chore: clean imports
tstephen-nhs Mar 25, 2026
b500519
chore: ignore SQ rather than add extra var declaration
tstephen-nhs Mar 25, 2026
734254b
docs: JS doc
tstephen-nhs Mar 26, 2026
e26e480
docs: example of ApiGateway use
tstephen-nhs Mar 26, 2026
50ebe0a
Merge branch 'aea-6254-cdk-api-gateway' into aea-6258-ssm-parameters
wildjames Mar 26, 2026
bd610d0
refactor: centralise constants
tstephen-nhs Mar 26, 2026
4912c82
Add jsdocs to the construct
wildjames Mar 26, 2026
a784142
Allow a fallback value on get env var
wildjames Mar 26, 2026
7b17511
Allow default values in env vars. Tests
wildjames Mar 26, 2026
503d44c
Merge branch 'main' into aea-6254-cdk-api-gateway
tstephen-nhs Mar 27, 2026
0ae7177
revert git secrets install
tstephen-nhs Mar 27, 2026
2b92465
fix: use postCreate to avoid git-secrets failing on second and subseq…
tstephen-nhs Mar 27, 2026
70e7f20
docs: add copilot instructions and teach it to write JSDoc
tstephen-nhs Mar 27, 2026
a5a01ac
Merge branch 'main' into aea-6254-cdk-api-gateway
tstephen-nhs Mar 27, 2026
3176a52
chore: adopt latest get-repo-config.yml
tstephen-nhs Mar 27, 2026
87cba70
chore: strip comment from devcontainer.json
tstephen-nhs Mar 27, 2026
dcab80b
Merge in latest from Tims branch
wildjames Mar 27, 2026
1f2297d
Resolve conflicts
wildjames Mar 27, 2026
f5a4f8d
Run linter
wildjames Mar 27, 2026
d15b6f7
Go away sonar
wildjames Mar 27, 2026
92376db
Address comments
wildjames Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions packages/cdkConstructs/src/constructs/SsmParametersConstruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {CfnOutput} from "aws-cdk-lib"
import {Effect, ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam"
import {StringParameter} from "aws-cdk-lib/aws-ssm"
import {Construct} from "constructs"

export interface SsmParameterDefinition {
/**
* Unique identifier used for construct and output logical IDs.
*/
readonly id: string
/**
* Suffix appended to stackName to create the parameter name.
* The final SSM parameter name is `${stackName}-${nameSuffix}`.
*/
readonly nameSuffix: string
/**
* Description stored with the SSM parameter.
*/
readonly description: string
/**
* Value stored in the SSM parameter.
*/
readonly value: string
/**
* Optional export suffix for the output containing the parameter name.
* Defaults to `${nameSuffix}Parameter`.
*/
readonly outputExportSuffix?: string
/**
* Optional output description.
*/
readonly outputDescription?: string
}

export interface SsmParametersConstructProps {
/**
* Prefix used in SSM parameter names and CloudFormation export names.
*/
readonly stackName: string
/**
* List of SSM parameters to create.
*/
readonly parameters: Array<SsmParameterDefinition>
Comment thread
wildjames marked this conversation as resolved.
/**
* Description for the managed policy that grants read access.
* @default "Allows reading SSM parameters"
*/
readonly readPolicyDescription?: string
/**
* Description for the output exporting the managed policy ARN.
* @default "Access to the parameters used by the integration"
*/
readonly readPolicyOutputDescription?: string
/**
* Export suffix for the output exporting the managed policy ARN.
* @default "GetParametersPolicy"
*/
readonly readPolicyExportSuffix?: string
}

/**
* Creates a bundle of SSM String parameters, a managed policy to read them,
* and CloudFormation outputs to export parameter names and policy ARN.
*/
export class SsmParametersConstruct extends Construct {
public readonly parameters: Record<string, StringParameter>
public readonly readParametersPolicy: ManagedPolicy

public constructor(scope: Construct, id: string, props: SsmParametersConstructProps) {
super(scope, id)

const {
stackName,
parameters,
readPolicyDescription = "Allows reading SSM parameters",
readPolicyOutputDescription = "Access to the parameters used by the integration",
readPolicyExportSuffix = "GetParametersPolicy"
} = props

if (parameters.length === 0) {
throw new Error("SsmParametersConstruct requires at least one parameter definition")
}

const createdParameters: Record<string, StringParameter> = {}

for (const parameter of parameters) {
const ssmParameter = new StringParameter(this, `${parameter.id}Parameter`, {
parameterName: `${stackName}-${parameter.nameSuffix}`,
description: parameter.description,
stringValue: parameter.value
})

createdParameters[parameter.id] = ssmParameter

new CfnOutput(this, `${parameter.id}ParameterNameOutput`, {
description: parameter.outputDescription ?? `Name of the SSM parameter holding ${parameter.nameSuffix}`,
value: ssmParameter.parameterName,
exportName: `${stackName}-${parameter.outputExportSuffix ?? `${parameter.nameSuffix}Parameter`}`

Check warning on line 98 in packages/cdkConstructs/src/constructs/SsmParametersConstruct.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-cdk-utils&issues=AZ0f92p6u7sWfngUMMDN&open=AZ0f92p6u7sWfngUMMDN&pullRequest=619
})
}
Comment thread
wildjames marked this conversation as resolved.

const readParametersPolicy = new ManagedPolicy(this, "GetParametersPolicy", {
description: readPolicyDescription,
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
actions: ["ssm:GetParameter", "ssm:GetParameters"],
resources: Object.values(createdParameters).map((parameter) => parameter.parameterArn)
})
]
})

new CfnOutput(this, "ReadParametersPolicyOutput", {
description: readPolicyOutputDescription,
value: readParametersPolicy.managedPolicyArn,
exportName: `${stackName}-${readPolicyExportSuffix}`
})

this.parameters = createdParameters
this.readParametersPolicy = readParametersPolicy
}
}
1 change: 1 addition & 0 deletions packages/cdkConstructs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Export all constructs
export * from "./constructs/TypescriptLambdaFunction.js"
export * from "./constructs/PythonLambdaFunction.js"
export * from "./constructs/SsmParametersConstruct.js"
export * from "./apps/createApp.js"
export * from "./config/index.js"
export * from "./utils/helpers.js"
Expand Down
126 changes: 126 additions & 0 deletions packages/cdkConstructs/tests/constructs/ssmParametersConstruct.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {App, Stack} from "aws-cdk-lib"
import {Template} from "aws-cdk-lib/assertions"
import {
beforeAll,
describe,
expect,
test
} from "vitest"

import {SsmParametersConstruct} from "../../src/constructs/SsmParametersConstruct"

describe("SsmParametersConstruct", () => {
let template: Template

beforeAll(() => {
const app = new App()
const stack = new Stack(app, "parameterStack")

new SsmParametersConstruct(stack, "TestingParameters", {

Check warning on line 19 in packages/cdkConstructs/tests/constructs/ssmParametersConstruct.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either remove this useless object instantiation of "SsmParametersConstruct" or use it.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_eps-cdk-utils&issues=AZ0f92nHu7sWfngUMMDM&open=AZ0f92nHu7sWfngUMMDM&pullRequest=619
stackName: "mock-stack",
parameters: [
{
id: "MockParam1",
nameSuffix: "MockParam1",
description: "Description for mock parameter 1",
value: "mock-value-1",
outputExportSuffix: "MockParam1Parameter",
outputDescription: "Name of the SSM parameter holding MockParam1"
},
{
id: "MockParam2",
nameSuffix: "MockParam2",
description: "Description for mock parameter 2",
value: "mock-value-2",
outputExportSuffix: "MockParam2Parameter",
outputDescription: "Name of the SSM parameter holding MockParam2"
},
{
id: "MockParam3",
nameSuffix: "MockParam3",
description: "Description for mock parameter 3",
value: "mock-value-3",
outputExportSuffix: "MockParam3Parameter",
outputDescription: "Name of the SSM parameter holding MockParam3"
}
],
readPolicyDescription: "Mock policy description",
readPolicyOutputDescription: "Mock read policy output description",
readPolicyExportSuffix: "MockGetParametersPolicy"
})

Comment thread
wildjames marked this conversation as resolved.
template = Template.fromStack(stack)
})
Comment thread
wildjames marked this conversation as resolved.

test("creates all SSM parameters", () => {
template.hasResourceProperties("AWS::SSM::Parameter", {
Name: "mock-stack-MockParam1",
Type: "String",
Value: "mock-value-1"
})

template.hasResourceProperties("AWS::SSM::Parameter", {
Name: "mock-stack-MockParam2",
Type: "String",
Value: "mock-value-2"
})

template.hasResourceProperties("AWS::SSM::Parameter", {
Name: "mock-stack-MockParam3",
Type: "String",
Value: "mock-value-3"
})
})

test("creates read policy with GetParameter actions for all parameters", () => {
const policies = template.findResources("AWS::IAM::ManagedPolicy", {
Properties: {
Description: "Mock policy description"
}
})

expect(Object.keys(policies)).toHaveLength(1)

const policy = Object.values(policies)[0] as {
Properties: {
PolicyDocument: {
Statement: Array<{
Action: Array<string>
Resource: Array<unknown>
}>
}
}
}

const statement = policy.Properties.PolicyDocument.Statement[0]
expect(statement.Action).toEqual(["ssm:GetParameter", "ssm:GetParameters"])
expect(statement.Resource).toHaveLength(3)
})

test("exports parameter names and policy ARN", () => {
const outputs = template.toJSON().Outputs as Record<string, {
Description?: string
Export?: {
Name?: string
}
}>

const exportedNames = Object.values(outputs)
.map((output) => output.Export?.Name)
.filter((name): name is string => name !== undefined)

const descriptions = Object.values(outputs)
.map((output) => output.Description)
.filter((description): description is string => description !== undefined)

expect(exportedNames).toContain("mock-stack-MockParam1Parameter")
expect(exportedNames).toContain("mock-stack-MockParam2Parameter")
expect(exportedNames).toContain("mock-stack-MockParam3Parameter")
expect(exportedNames).toContain("mock-stack-MockGetParametersPolicy")

expect(descriptions).toContain("Name of the SSM parameter holding MockParam1")
expect(descriptions).toContain("Name of the SSM parameter holding MockParam2")
expect(descriptions).toContain("Name of the SSM parameter holding MockParam3")
expect(descriptions).toContain("Mock read policy output description")
})
})
Loading