Skip to content
Merged
Show file tree
Hide file tree
Changes from 32 commits
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
1,375 changes: 74 additions & 1,301 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 20 additions & 5 deletions packages/cdkConstructs/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ import {CloudFormationClient, DescribeStacksCommand} from "@aws-sdk/client-cloud
import {S3Client, HeadObjectCommand} from "@aws-sdk/client-s3"
import {StandardStackProps} from "../apps/createApp"

export function getConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): string {
export function getConfigFromEnvVar(
varName: string,
prefix: string = "CDK_CONFIG_",
defaultValue: string | undefined = undefined
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the idiomatic way to do this would be to use an options object, but that would break downstream things and for this I don't want to mess about with that if we can avoid it...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defaultValue: string | undefined = undefined
defaultValue: string | undefined

explicitly initialising to undefined feels a bit excessive.
There a couple of these

): string {
const value = process.env[prefix + varName]
if (!value) {
if (defaultValue !== undefined) {
return defaultValue
}
throw new Error(`Environment variable ${prefix}${varName} is not set`)
}
Comment thread
wildjames marked this conversation as resolved.
return value
}

export function getBooleanConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): boolean {
const value = getConfigFromEnvVar(varName, prefix)
export function getBooleanConfigFromEnvVar(
varName: string,
prefix: string = "CDK_CONFIG_",
defaultValue: string | undefined = undefined
): boolean {
const value = getConfigFromEnvVar(varName, prefix, defaultValue)
return value.toLowerCase().trim() === "true"
}

export function getNumberConfigFromEnvVar(varName: string, prefix: string = "CDK_CONFIG_"): number {
const value = getConfigFromEnvVar(varName, prefix)
export function getNumberConfigFromEnvVar(
varName: string,
prefix: string = "CDK_CONFIG_",
defaultValue: string | undefined = undefined
): number {
const value = getConfigFromEnvVar(varName, prefix, defaultValue)
return Number(value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export class StateMachineEndpoint extends Construct {
]
}), {
methodResponses: [
{ statusCode: "200" },
{ statusCode: "400" },
{ statusCode: "500" }
{statusCode: "200"},
{statusCode: "400"},
{statusCode: "500"}
Comment thread
wildjames marked this conversation as resolved.
]
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @returns API Gateway request mapping template for StartExecution payloads.
*/
export const stateMachineRequestTemplate = (stateMachineArn: string) => {
return `## Velocity Template used for API Gateway request mapping template
return `## Velocity Template used for API Gateway request mapping template
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one was correct before? 2 space indent

## "@@" is used here as a placeholder for '"' to avoid using escape characters.

#set($includeHeaders = true)
Expand Down
172 changes: 172 additions & 0 deletions packages/cdkConstructs/src/constructs/SsmParametersConstruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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"

/**
* Definition for a single SSM String parameter and its output export metadata.
*
* @property id Unique identifier used for construct and output logical IDs.
* @property nameSuffix Suffix appended to stackName to create the parameter name.
* The final SSM parameter name is `${stackName}-${nameSuffix}`.
* @property description Description stored with the SSM parameter.
Comment thread
wildjames marked this conversation as resolved.
* @property value Value stored in the SSM parameter.
* @property outputExportSuffix Optional export suffix for the output containing
* the parameter name. Defaults to `nameSuffix`.
* @property outputDescription Optional output description. Defaults to
* `description`.
*/
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.
* @default nameSuffix value
*/
readonly outputExportSuffix?: string
/**
* Optional output description.
* @default description value
*/
readonly outputDescription?: string
}

/**
* Properties used to configure {@link SsmParametersConstruct}.
*
* @property namePrefix Prefix used in SSM parameter names and CloudFormation
* export names.
* @property parameters List of SSM parameters to create.
* @property readPolicyDescription Description for the managed policy that grants
* read access. Defaults to "Allows reading SSM parameters".
* @property readPolicyOutputDescription Description for the output exporting the
* managed policy ARN. Defaults to "Access to the parameters used by the integration".
* @property readPolicyExportSuffix Export suffix for the output exporting the
* managed policy ARN.
*/
export interface SsmParametersConstructProps {
/**
* Prefix used in SSM parameter names and CloudFormation export names.
*/
readonly namePrefix: 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.
*/
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

/**
* Creates SSM String parameters, a managed read policy, and CloudFormation outputs.
*
* @param scope CDK construct scope.
* @param id Unique construct identifier.
* @param props Configuration for parameter names, values, and exported outputs.
* @throws {Error} Throws when no parameter definitions are provided.
* @throws {Error} Throws when duplicate parameter IDs or parameter names are detected.
*/
public constructor(scope: Construct, id: string, props: SsmParametersConstructProps) {
super(scope, id)

const {
namePrefix: stackName,
parameters,
readPolicyExportSuffix,
readPolicyDescription = "Allows reading SSM parameters",
readPolicyOutputDescription = "Access to the parameters used by the integration"
Comment thread
wildjames marked this conversation as resolved.
} = props

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

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

const seenIds = new Set<string>()
const seenNames = new Set<string>()

for (const parameter of parameters) {
const parameterId = `${parameter.id}Parameter`
if (seenIds.has(parameterId)) {
throw new Error(`Duplicate parameter id detected: ${parameter.id}.`)
}
seenIds.add(parameterId)

const parameterName = `${stackName}-${parameter.nameSuffix}`
if (seenNames.has(parameterName)) {
throw new Error(`Duplicate parameter name detected: ${parameterName}.`)
}
seenNames.add(parameterName)

const ssmParameter = new StringParameter(this, parameterId, {
parameterName,
description: parameter.description,
stringValue: parameter.value
})

createdParameters[parameter.id] = ssmParameter

new CfnOutput(this, `${parameter.id}ParameterNameOutput`, {
description: parameter.outputDescription ?? parameter.description,
value: ssmParameter.parameterName,
exportName: `${stackName}-${parameter.outputExportSuffix ?? parameter.nameSuffix}`
})
}
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
Expand Up @@ -7,6 +7,7 @@ export * from "./constructs/RestApiGateway/accessLogFormat.js"
export * from "./constructs/RestApiGateway/LambdaEndpoint.js"
export * from "./constructs/RestApiGateway/StateMachineEndpoint.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
25 changes: 25 additions & 0 deletions packages/cdkConstructs/tests/config/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ describe("config helpers", () => {
expect(getConfigFromEnvVar("STACK_NAME")).toBe("primary")
})

test("getConfigFromEnvVar returns the default value when env var is not set", () => {
delete process.env.CDK_CONFIG_MISSING

expect(getConfigFromEnvVar("MISSING", "CDK_CONFIG_", "fallback")).toBe("fallback")
})

test("getConfigFromEnvVar throws when value is missing", () => {
delete process.env.CDK_CONFIG_MISSING

Expand All @@ -114,12 +120,31 @@ describe("config helpers", () => {
expect(getBooleanConfigFromEnvVar("OTHER_FLAG")).toBe(false)
})

test("getBooleanConfigFromEnvVar uses default value when env var is not set", () => {
delete process.env.CDK_CONFIG_BOOL_MISSING

expect(getBooleanConfigFromEnvVar("BOOL_MISSING", "CDK_CONFIG_", "true")).toBe(true)
expect(getBooleanConfigFromEnvVar("BOOL_MISSING", "CDK_CONFIG_", "false")).toBe(false)
})

test("getNumberConfigFromEnvVar parses numeric strings", () => {
process.env.CDK_CONFIG_TIMEOUT = "45"

expect(getNumberConfigFromEnvVar("TIMEOUT")).toBe(45)
})

test("getNumberConfigFromEnvVar uses default value when env var is not set", () => {
delete process.env.CDK_CONFIG_NUM_MISSING

expect(getNumberConfigFromEnvVar("NUM_MISSING", "CDK_CONFIG_", "99")).toBe(99)
})

test("getConfigFromEnvVar ignores default value when env var is set", () => {
process.env.CDK_CONFIG_STACK_NAME = "primary"

expect(getConfigFromEnvVar("STACK_NAME", "CDK_CONFIG_", "ignored")).toBe("primary")
})

test("getTrustStoreVersion returns the version ID from S3", async () => {
mockCloudFormationSend.mockResolvedValueOnce({
Stacks: [{
Expand Down
Loading
Loading