-
Notifications
You must be signed in to change notification settings - Fork 0
/
parameter-resource.ts
99 lines (87 loc) · 2.81 KB
/
parameter-resource.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { CfnParameter, Stack } from "aws-cdk-lib"
import * as ssm from "aws-cdk-lib/aws-ssm"
import { Construct } from "constructs"
import { CrossRegionParameter } from "./cross-region-parameter"
type ReferenceToResource<T> = (
scope: Construct,
id: string,
reference: string,
) => T
export interface ParameterResourceProps<T> {
/**
* The nonce can be used to force the stack to update to check
* for a new value.
*
* @default Date.now().toString()
*/
nonce?: string
parameterName: string
resource: T
resourceToReference(resource: T): string
referenceToResource: ReferenceToResource<T>
/**
* List of regions that can retrieve this resource from an SSM Parameter.
*/
regions: string[]
}
/**
* Register SSM Parameters in other regions storing a reference to
* a resource, which can then be resolved in the other region by
* reading from the parameter.
*
* Storing the SSM Parameters in the other regions speeds up the
* resolving of parameters, since we can use CloudFormation SSM
* Parameters instead of custom resources.
*
* If the resource is in the same region, the resource will be returned
* like normally in CDK, causing an export/import if cross-stack.
*/
export class ParameterResource<T> extends Construct {
private readonly nonce: string
private readonly parameterName: string
private readonly resource: T
private readonly referenceToResource: ReferenceToResource<T>
private readonly regions: string[]
constructor(scope: Construct, id: string, props: ParameterResourceProps<T>) {
super(scope, id)
this.nonce = props.nonce ?? Date.now().toString()
this.parameterName = props.parameterName
this.resource = props.resource
this.referenceToResource = props.referenceToResource
this.regions = props.regions
const value = props.resourceToReference(props.resource)
for (const region of props.regions) {
new CrossRegionParameter(this, `Param${region}`, {
name: this.parameterName,
region,
value,
})
}
}
/**
* Get the resource by resolving the value from SSM Parameter Store
* in case we are cross-region.
*/
public get(scope: Construct, id: string): T {
const producerRegion = Stack.of(this).region
const consumerRegion = Stack.of(scope).region
// Fast-path: Same region.
if (producerRegion === consumerRegion) {
return this.resource
}
if (!this.regions.includes(consumerRegion)) {
throw new Error(
`The region ${consumerRegion} is not registered for the parameter`,
)
}
scope.node.addDependency(this)
new CfnParameter(scope, `${id}Nonce`, {
default: this.nonce,
})
const reference = ssm.StringParameter.valueForStringParameter(
scope,
this.parameterName,
)
return this.referenceToResource(scope, id, reference)
}
}