forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontext-provider.ts
187 lines (161 loc) · 5.4 KB
/
context-provider.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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import { Construct, Node } from 'constructs';
import { Annotations } from './annotations';
import { Stack } from './stack';
import { Token } from './token';
import * as cxschema from '../../cloud-assembly-schema';
import * as cxapi from '../../cx-api';
/**
*/
export interface GetContextKeyOptions {
/**
* The context provider to query.
*/
readonly provider: string;
/**
* Provider-specific properties.
*/
readonly props?: { [key: string]: any };
/**
* Whether to include the stack's account and region automatically.
*
* @default true
*/
readonly includeEnvironment?: boolean;
}
/**
*/
export interface GetContextValueOptions extends GetContextKeyOptions {
/**
* The value to return if the context value was not found and a missing
* context is reported.
*/
readonly dummyValue: any;
/**
* When True, the context provider will not throw an error if missing context
* is reported
*
* @default false
*/
readonly ignoreErrorOnMissingContext?: boolean;
}
/**
*/
export interface GetContextKeyResult {
readonly key: string;
readonly props: { [key: string]: any };
}
/**
*/
export interface GetContextValueResult {
readonly value?: any;
}
/**
* Base class for the model side of context providers
*
* Instances of this class communicate with context provider plugins in the 'cdk
* toolkit' via context variables (input), outputting specialized queries for
* more context variables (output).
*
* ContextProvider needs access to a Construct to hook into the context mechanism.
*
*/
export class ContextProvider {
/**
* @returns the context key or undefined if a key cannot be rendered (due to tokens used in any of the props)
*/
public static getKey(scope: Construct, options: GetContextKeyOptions): GetContextKeyResult {
const stack = Stack.of(scope);
const props = options.includeEnvironment ?? true
? { account: stack.account, region: stack.region, ...options.props }
: (options.props ?? {});
if (Object.values(props).find(x => Token.isUnresolved(x))) {
throw new Error(
`Cannot determine scope for context provider ${options.provider}.\n` +
'This usually happens when one or more of the provider props have unresolved tokens');
}
const propStrings = propsToArray(props);
return {
key: `${options.provider}:${propStrings.join(':')}`,
props,
};
}
public static getValue(scope: Construct, options: GetContextValueOptions): GetContextValueResult {
const stack = Stack.of(scope);
if (Token.isUnresolved(stack.account) || Token.isUnresolved(stack.region)) {
throw new Error(`Cannot retrieve value from context provider ${options.provider} since account/region ` +
'are not specified at the stack level. Configure "env" with an account and region when ' +
'you define your stack.' +
'See https://docs.aws.amazon.com/cdk/latest/guide/environments.html for more details.');
}
const { key, props } = this.getKey(scope, options);
const value = Node.of(scope).tryGetContext(key);
const providerError = extractProviderError(value);
// if context is missing or an error occurred during context retrieval,
// report and return a dummy value.
if (value === undefined || providerError !== undefined) {
// build a version of the props which includes the dummyValue and ignoreError flag
const extendedProps: { [p: string]: any } = {
dummyValue: options.dummyValue,
ignoreErrorOnMissingContext: options.ignoreErrorOnMissingContext,
...props,
};
// We'll store the extendedProps in the missingContextKey report
// so that we can retrieve the dummyValue and ignoreError flag
// in the aws-cdk's ssm-context and kms key provider
stack.reportMissingContextKey({
key,
provider: options.provider as cxschema.ContextProvider,
props: extendedProps as cxschema.ContextQueryProperties,
});
if (providerError !== undefined) {
Annotations.of(scope).addError(providerError);
}
return { value: options.dummyValue };
}
return { value };
}
private constructor() { }
}
/**
* If the context value represents an error, return the error message
*/
function extractProviderError(value: any): string | undefined {
if (typeof value === 'object' && value !== null) {
return value[cxapi.PROVIDER_ERROR_KEY];
}
return undefined;
}
/**
* Quote colons in all strings so that we can undo the quoting at a later point
*
* We'll use $ as a quoting character, for no particularly good reason other
* than that \ is going to lead to quoting hell when the keys are stored in JSON.
*/
function colonQuote(xs: string): string {
return xs.replace(/\$/g, '$$').replace(/:/g, '$:');
}
function propsToArray(props: {[key: string]: any}, keyPrefix = ''): string[] {
const ret: string[] = [];
for (const key of Object.keys(props)) {
// skip undefined values
if (props[key] === undefined) {
continue;
}
switch (typeof props[key]) {
case 'object': {
ret.push(...propsToArray(props[key], `${keyPrefix}${key}.`));
break;
}
case 'string': {
ret.push(`${keyPrefix}${key}=${colonQuote(props[key])}`);
break;
}
default: {
ret.push(`${keyPrefix}${key}=${JSON.stringify(props[key])}`);
break;
}
}
}
ret.sort();
return ret;
}