@cdktn/hcl2cdk 0.24.0-pre.45 → 0.24.0-pre.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +355 -0
- package/README.md +1 -1
- package/build/__tests__/expressions.test.js +10 -19
- package/build/__tests__/functions.test.js +8 -18
- package/build/__tests__/testHelpers.js +3 -2
- package/build/coerceType.js +11 -21
- package/build/dynamic-blocks.js +3 -3
- package/build/expressions.js +13 -22
- package/build/function-bindings/functions.generated.js +2 -2
- package/build/generation.js +24 -34
- package/build/index.js +15 -25
- package/build/iteration.js +7 -6
- package/build/jsii-rosetta-workarounds.js +6 -5
- package/build/partialCode.js +11 -20
- package/build/provider.js +4 -3
- package/build/references.js +6 -5
- package/build/schema.js +8 -18
- package/build/terraformSchema.js +4 -4
- package/build/utils.js +3 -3
- package/build/variables.js +12 -21
- package/package.json +20 -17
- package/package.sh +1 -1
- package/src/__tests__/coerceType.test.ts +207 -0
- package/src/__tests__/expressionToTs.test.ts +1167 -0
- package/src/__tests__/expressions.test.ts +541 -0
- package/src/__tests__/findExpressionType.test.ts +112 -0
- package/src/__tests__/functions.test.ts +768 -0
- package/src/__tests__/generation.test.ts +72 -0
- package/src/__tests__/jsii-rosetta-workarounds.test.ts +145 -0
- package/src/__tests__/partialCode.test.ts +432 -0
- package/src/__tests__/terraformSchema.test.ts +107 -0
- package/src/__tests__/testHelpers.ts +11 -0
- package/src/coerceType.ts +261 -0
- package/src/dynamic-blocks.ts +61 -0
- package/src/expressions.ts +968 -0
- package/src/function-bindings/functions.generated.ts +1139 -0
- package/src/function-bindings/functions.ts +104 -0
- package/src/generation.ts +1189 -0
- package/src/index.ts +584 -0
- package/src/iteration.ts +156 -0
- package/src/jsii-rosetta-workarounds.ts +145 -0
- package/src/partialCode.ts +132 -0
- package/src/provider.ts +60 -0
- package/src/references.ts +193 -0
- package/src/schema.ts +74 -0
- package/src/terraformSchema.ts +182 -0
- package/src/types.ts +58 -0
- package/src/utils.ts +19 -0
- package/src/variables.ts +214 -0
- package/test/__snapshots__/backends.test.ts.snap +70 -0
- package/test/__snapshots__/externals.test.ts.snap +37 -0
- package/test/__snapshots__/granular-imports.test.ts.snap +180 -0
- package/test/__snapshots__/imports.test.ts.snap +159 -0
- package/test/__snapshots__/iteration.test.ts.snap +532 -0
- package/test/__snapshots__/jsiiLanguage.test.ts.snap +347 -0
- package/test/__snapshots__/locals.test.ts.snap +55 -0
- package/test/__snapshots__/modules.test.ts.snap +127 -0
- package/test/__snapshots__/outputs.test.ts.snap +77 -0
- package/test/__snapshots__/partialCode.test.ts.snap +120 -0
- package/test/__snapshots__/provider.test.ts.snap +128 -0
- package/test/__snapshots__/references.test.ts.snap +376 -0
- package/test/__snapshots__/resource-meta-properties.test.ts.snap +342 -0
- package/test/__snapshots__/resources.test.ts.snap +613 -0
- package/test/__snapshots__/tfExpressions.test.ts.snap +537 -0
- package/test/__snapshots__/typeCoercion.test.ts.snap +253 -0
- package/test/__snapshots__/variables.test.ts.snap +150 -0
- package/test/backends.test.ts +75 -0
- package/test/convertProject.test.ts +257 -0
- package/test/externals.test.ts +35 -0
- package/test/globalSetup.ts +224 -0
- package/test/globalTeardown.ts +11 -0
- package/test/granular-imports.test.ts +161 -0
- package/test/hcl2cdk.test.ts +88 -0
- package/test/helpers/convert.ts +543 -0
- package/test/helpers/tmp.ts +25 -0
- package/test/imports.test.ts +141 -0
- package/test/iteration.test.ts +342 -0
- package/test/jsiiLanguage.test.ts +73 -0
- package/test/locals.test.ts +47 -0
- package/test/modules.test.ts +143 -0
- package/test/outputs.test.ts +69 -0
- package/test/partialCode.test.ts +25 -0
- package/test/provider.test.ts +106 -0
- package/test/references.test.ts +287 -0
- package/test/resource-meta-properties.test.ts +288 -0
- package/test/resources.test.ts +551 -0
- package/test/tfExpressions.test.ts +300 -0
- package/test/typeCoercion.test.ts +154 -0
- package/test/variables.test.ts +96 -0
|
@@ -0,0 +1,1189 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import generate from "@babel/generator";
|
|
4
|
+
import template from "@babel/template";
|
|
5
|
+
import * as t from "@babel/types";
|
|
6
|
+
import { DirectedGraph } from "graphology";
|
|
7
|
+
import prettier from "prettier";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
TerraformResourceBlock,
|
|
11
|
+
ProgramScope,
|
|
12
|
+
ResourceScope,
|
|
13
|
+
ImportableConstruct,
|
|
14
|
+
AttributePath,
|
|
15
|
+
} from "./types";
|
|
16
|
+
import { camelCase, logger, pascalCase, uniqueId } from "./utils";
|
|
17
|
+
import {
|
|
18
|
+
Resource,
|
|
19
|
+
TerraformConfig,
|
|
20
|
+
Module,
|
|
21
|
+
Provider,
|
|
22
|
+
Variable,
|
|
23
|
+
Output,
|
|
24
|
+
Import,
|
|
25
|
+
} from "./schema";
|
|
26
|
+
import { convertTerraformExpressionToTs, expressionAst } from "./expressions";
|
|
27
|
+
import { Reference } from "./types";
|
|
28
|
+
import { findUsedReferences } from "./references";
|
|
29
|
+
import {
|
|
30
|
+
TerraformModuleConstraint,
|
|
31
|
+
escapeAttributeName,
|
|
32
|
+
} from "@cdktn/provider-generator";
|
|
33
|
+
import {
|
|
34
|
+
getTypeAtPath,
|
|
35
|
+
isMapAttribute,
|
|
36
|
+
getDesiredType,
|
|
37
|
+
} from "./terraformSchema";
|
|
38
|
+
import { Errors, AttributeType, BlockType, Schema } from "@cdktn/commons";
|
|
39
|
+
import { TFExpressionSyntaxTree as tex } from "@cdktn/hcl2json";
|
|
40
|
+
import { extractDynamicBlocks, isNestedDynamicBlock } from "./dynamic-blocks";
|
|
41
|
+
import {
|
|
42
|
+
constructAst,
|
|
43
|
+
referenceToVariableName,
|
|
44
|
+
variableName,
|
|
45
|
+
} from "./variables";
|
|
46
|
+
import { snakeCase } from "cdktn/lib/util";
|
|
47
|
+
import { fillWithConfigAccessors } from "./partialCode";
|
|
48
|
+
|
|
49
|
+
function getReference(graph: DirectedGraph, id: string) {
|
|
50
|
+
logger.debug(`Finding reference for ${id}`);
|
|
51
|
+
const neighbors = graph.outNeighbors(id);
|
|
52
|
+
|
|
53
|
+
if (neighbors.length > 0) {
|
|
54
|
+
logger.debug(`Found neighbors ${neighbors} for ${id}`);
|
|
55
|
+
const edge = graph.directedEdge(id, neighbors[0]);
|
|
56
|
+
|
|
57
|
+
if (edge) {
|
|
58
|
+
logger.debug(`Found first edge ${edge} for ${id}`);
|
|
59
|
+
logger.debug(
|
|
60
|
+
`Returning reference ${graph.getEdgeAttribute(edge, "ref")}`,
|
|
61
|
+
);
|
|
62
|
+
return graph.getEdgeAttribute(edge, "ref") as Reference;
|
|
63
|
+
} else {
|
|
64
|
+
logger.debug(`Found no edge for ${id}`);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function attributeNameToCdktfName(name: string) {
|
|
73
|
+
return escapeAttributeName(camelCase(name));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const valueToTs = async (
|
|
77
|
+
scope: ResourceScope,
|
|
78
|
+
item: TerraformResourceBlock,
|
|
79
|
+
path: string,
|
|
80
|
+
isModule = false,
|
|
81
|
+
): Promise<t.Expression> => {
|
|
82
|
+
switch (typeof item) {
|
|
83
|
+
case "string":
|
|
84
|
+
if (
|
|
85
|
+
(await findUsedReferences(scope.nodeIds, item)).some((ref) =>
|
|
86
|
+
path.startsWith(ref.referencee.id),
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
return t.stringLiteral(item);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return await convertTerraformExpressionToTs(scope, `"${item}"`, () =>
|
|
93
|
+
getDesiredType(scope, path),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
case "boolean":
|
|
97
|
+
return await convertTerraformExpressionToTs(scope, `${item}`, () =>
|
|
98
|
+
getDesiredType(scope, path),
|
|
99
|
+
);
|
|
100
|
+
case "number":
|
|
101
|
+
return await convertTerraformExpressionToTs(scope, `${item}`, () =>
|
|
102
|
+
getDesiredType(scope, path),
|
|
103
|
+
);
|
|
104
|
+
case "object": {
|
|
105
|
+
if (item === undefined || item === null) {
|
|
106
|
+
return t.nullLiteral();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// For iterators and dynamic blocks we put the correct TS expression in the config ahead of time
|
|
110
|
+
if (t.isNode(item) && t.isExpression(item)) {
|
|
111
|
+
return item;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const attributeType = getTypeAtPath(scope.providerSchema, path);
|
|
115
|
+
|
|
116
|
+
function shouldRemoveArrayBasedOnType(
|
|
117
|
+
attributeType: Schema | AttributeType | BlockType | null,
|
|
118
|
+
): boolean {
|
|
119
|
+
if (!attributeType) {
|
|
120
|
+
return false; // The default assumption is we need the array
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// maps and object don't need to be wrapped in an array
|
|
124
|
+
if (
|
|
125
|
+
Array.isArray(attributeType) &&
|
|
126
|
+
(attributeType[0] === "map" || attributeType[0] === "object")
|
|
127
|
+
) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If it's a block type with max_items = 1 we don't need to wrap it in an array
|
|
132
|
+
if (
|
|
133
|
+
typeof attributeType === "object" &&
|
|
134
|
+
"max_items" in attributeType &&
|
|
135
|
+
attributeType.max_items === 1
|
|
136
|
+
) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const unwrappedItem =
|
|
144
|
+
Array.isArray(item) &&
|
|
145
|
+
(shouldRemoveArrayBasedOnType(attributeType) ||
|
|
146
|
+
path.endsWith("lifecycle") ||
|
|
147
|
+
path.endsWith("connection"))
|
|
148
|
+
? item[0]
|
|
149
|
+
: item;
|
|
150
|
+
|
|
151
|
+
if (Array.isArray(unwrappedItem)) {
|
|
152
|
+
return t.arrayExpression(
|
|
153
|
+
await Promise.all(
|
|
154
|
+
unwrappedItem.map((i) => valueToTs(scope, i, `${path}.[]`)),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return t.objectExpression(
|
|
160
|
+
(
|
|
161
|
+
await Promise.all(
|
|
162
|
+
Object.entries(unwrappedItem).map(async ([key, value]) => {
|
|
163
|
+
if (value === undefined) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (key === "dynamic") {
|
|
168
|
+
const { for_each: _for_each, ...others } = value as any;
|
|
169
|
+
const dynamicRef = Object.keys(others)[0];
|
|
170
|
+
return t.objectProperty(
|
|
171
|
+
t.identifier(
|
|
172
|
+
scope.withinOverrideExpression
|
|
173
|
+
? dynamicRef
|
|
174
|
+
: escapeAttributeName(camelCase(dynamicRef)),
|
|
175
|
+
),
|
|
176
|
+
t.arrayExpression(),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const itemPath = `${path}.${key}`;
|
|
181
|
+
const itemAttributeType = getTypeAtPath(
|
|
182
|
+
scope.providerSchema,
|
|
183
|
+
itemPath,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const typeMetadata = getTypeAtPath(
|
|
187
|
+
scope.providerSchema,
|
|
188
|
+
itemPath,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const isSingleItemBlock =
|
|
192
|
+
typeMetadata &&
|
|
193
|
+
typeof typeMetadata === "object" &&
|
|
194
|
+
Object.prototype.hasOwnProperty.call(typeMetadata, "max_items")
|
|
195
|
+
? (typeMetadata as any).max_items === 1
|
|
196
|
+
: false;
|
|
197
|
+
|
|
198
|
+
const shouldBeArray =
|
|
199
|
+
typeof value === "object" &&
|
|
200
|
+
!Array.isArray(value) &&
|
|
201
|
+
!(t.isNode(value) && t.isExpression(value)) &&
|
|
202
|
+
!isSingleItemBlock &&
|
|
203
|
+
// Map type attributes must not be wrapped in arrays
|
|
204
|
+
!isMapAttribute(itemAttributeType) &&
|
|
205
|
+
key !== "tags" &&
|
|
206
|
+
key !== "forEach" &&
|
|
207
|
+
key !== "lifecycle";
|
|
208
|
+
|
|
209
|
+
const keepKeyName: boolean =
|
|
210
|
+
!isModule &&
|
|
211
|
+
key !== "depends_on" &&
|
|
212
|
+
!path.includes("lifecycle") &&
|
|
213
|
+
(key === "for_each" ||
|
|
214
|
+
!typeMetadata ||
|
|
215
|
+
isMapAttribute(attributeType)) &&
|
|
216
|
+
!(path.startsWith("var.") && path.includes("validation"));
|
|
217
|
+
|
|
218
|
+
return t.objectProperty(
|
|
219
|
+
t.stringLiteral(
|
|
220
|
+
keepKeyName ? key : attributeNameToCdktfName(key),
|
|
221
|
+
),
|
|
222
|
+
shouldBeArray
|
|
223
|
+
? t.arrayExpression([await valueToTs(scope, value, itemPath)])
|
|
224
|
+
: await valueToTs(scope, value, itemPath),
|
|
225
|
+
);
|
|
226
|
+
}),
|
|
227
|
+
)
|
|
228
|
+
).filter((expr) => expr !== undefined) as t.ObjectProperty[],
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
throw new Error("Unsupported type " + item);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
export async function backendToExpression(
|
|
236
|
+
scope: ProgramScope,
|
|
237
|
+
tf: TerraformConfig["backend"],
|
|
238
|
+
): Promise<t.Statement[]> {
|
|
239
|
+
return (
|
|
240
|
+
await Promise.all(
|
|
241
|
+
Object.entries(tf || {}).map(async ([type, [config]]) => {
|
|
242
|
+
const backendIdentifier = pascalCase(`${type}Backend`);
|
|
243
|
+
scope.importables.push({
|
|
244
|
+
constructName: backendIdentifier,
|
|
245
|
+
provider: "cdktn",
|
|
246
|
+
});
|
|
247
|
+
return t.expressionStatement(
|
|
248
|
+
t.newExpression(t.identifier(backendIdentifier), [
|
|
249
|
+
t.thisExpression(),
|
|
250
|
+
t.objectExpression(
|
|
251
|
+
(
|
|
252
|
+
await Promise.all(
|
|
253
|
+
Object.entries(config).map(async ([property, value]) =>
|
|
254
|
+
t.objectProperty(
|
|
255
|
+
t.identifier(camelCase(property)),
|
|
256
|
+
await valueToTs(
|
|
257
|
+
scope,
|
|
258
|
+
value,
|
|
259
|
+
"path-for-backends-can-be-ignored",
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
)
|
|
264
|
+
).reduce(
|
|
265
|
+
(carry, item) => [...carry, item],
|
|
266
|
+
[] as t.ObjectProperty[],
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
]),
|
|
270
|
+
);
|
|
271
|
+
}),
|
|
272
|
+
)
|
|
273
|
+
).reduce((carry, item) => [...carry, item], [] as t.Statement[]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function addOverrideExpression(
|
|
277
|
+
variable: string,
|
|
278
|
+
path: string,
|
|
279
|
+
value: t.Expression,
|
|
280
|
+
explanatoryComment?: string,
|
|
281
|
+
) {
|
|
282
|
+
const ast = t.expressionStatement(
|
|
283
|
+
t.callExpression(
|
|
284
|
+
t.memberExpression(t.identifier(variable), t.identifier("addOverride")),
|
|
285
|
+
[t.stringLiteral(path), value],
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (explanatoryComment) {
|
|
290
|
+
t.addComment(ast, "leading", explanatoryComment);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return ast;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function addOverrideLogicalIdExpression(variable: string, logicalId: string) {
|
|
297
|
+
const ast = t.expressionStatement(
|
|
298
|
+
t.callExpression(
|
|
299
|
+
t.memberExpression(
|
|
300
|
+
t.identifier(variable),
|
|
301
|
+
t.identifier("overrideLogicalId"),
|
|
302
|
+
),
|
|
303
|
+
[t.stringLiteral(logicalId)],
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
t.addComment(
|
|
308
|
+
ast,
|
|
309
|
+
"leading",
|
|
310
|
+
"This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.",
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
return ast;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getRemoteStateType(item: Resource) {
|
|
317
|
+
const backendRecord = item.find((val) => val.backend);
|
|
318
|
+
if (backendRecord) {
|
|
319
|
+
const backend = backendRecord.backend;
|
|
320
|
+
switch (backend) {
|
|
321
|
+
case "remote":
|
|
322
|
+
return "";
|
|
323
|
+
case "etcdv3":
|
|
324
|
+
return "_etcd_v3";
|
|
325
|
+
default:
|
|
326
|
+
return `_${backend}`;
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function resourceType(provider: string, name: string[], item: Resource) {
|
|
334
|
+
switch (provider) {
|
|
335
|
+
case "data.terraform":
|
|
336
|
+
return `cdktn.data_terraform_${name.join("_")}${getRemoteStateType(
|
|
337
|
+
item,
|
|
338
|
+
)}`;
|
|
339
|
+
case "null":
|
|
340
|
+
return `NullProvider.${name.join("_")}`;
|
|
341
|
+
default:
|
|
342
|
+
return `${provider}.${name.join("_")}`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function mapConfigPerResourceType(resource: string, item: Resource[0]) {
|
|
347
|
+
// Backends have a slightly different API
|
|
348
|
+
if (resource.startsWith("cdktn.data_terraform_")) {
|
|
349
|
+
return item.config;
|
|
350
|
+
}
|
|
351
|
+
return item;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const loopComment = `In most cases loops should be handled in the programming language context and
|
|
355
|
+
not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input
|
|
356
|
+
you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source
|
|
357
|
+
you need to keep this like it is.`;
|
|
358
|
+
export async function resource(
|
|
359
|
+
scope: ProgramScope,
|
|
360
|
+
type: string,
|
|
361
|
+
key: string,
|
|
362
|
+
id: string,
|
|
363
|
+
item: Resource,
|
|
364
|
+
graph: DirectedGraph,
|
|
365
|
+
): Promise<t.Statement[]> {
|
|
366
|
+
const [provider, ...name] = type.split("_");
|
|
367
|
+
const resource = resourceType(provider, name, item);
|
|
368
|
+
|
|
369
|
+
if (!provider) {
|
|
370
|
+
throw new Error(`Could not parse resource type '${type}'`);
|
|
371
|
+
}
|
|
372
|
+
let expressions: t.Statement[] = [];
|
|
373
|
+
const varName = variableName(scope, resource, key);
|
|
374
|
+
const { for_each, count, provisioner, ...config } = item[0];
|
|
375
|
+
const mappedConfig = mapConfigPerResourceType(resource, config);
|
|
376
|
+
|
|
377
|
+
let forEachIteratorName: string | undefined;
|
|
378
|
+
if (for_each) {
|
|
379
|
+
forEachIteratorName = variableName(
|
|
380
|
+
scope,
|
|
381
|
+
resource,
|
|
382
|
+
`${key}_for_each_iterator`,
|
|
383
|
+
);
|
|
384
|
+
const referenceAst = await convertTerraformExpressionToTs(
|
|
385
|
+
scope,
|
|
386
|
+
`"${for_each}"`,
|
|
387
|
+
() => ["list", "dynamic"],
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
scope.importables.push({
|
|
391
|
+
provider: "cdktn",
|
|
392
|
+
constructName: "TerraformIterator",
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const iterator = t.variableDeclaration("const", [
|
|
396
|
+
t.variableDeclarator(
|
|
397
|
+
t.identifier(forEachIteratorName),
|
|
398
|
+
t.callExpression(
|
|
399
|
+
t.memberExpression(
|
|
400
|
+
t.identifier("TerraformIterator"),
|
|
401
|
+
t.identifier("fromList"),
|
|
402
|
+
),
|
|
403
|
+
|
|
404
|
+
[referenceAst],
|
|
405
|
+
),
|
|
406
|
+
),
|
|
407
|
+
]);
|
|
408
|
+
t.addComment(iterator, "leading", loopComment);
|
|
409
|
+
expressions.push(iterator);
|
|
410
|
+
|
|
411
|
+
mappedConfig.forEach = t.identifier(forEachIteratorName);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let countIteratorName: string | undefined;
|
|
415
|
+
if (count) {
|
|
416
|
+
countIteratorName = variableName(scope, resource, `${key}_count`);
|
|
417
|
+
const referenceAst = await convertTerraformExpressionToTs(
|
|
418
|
+
scope,
|
|
419
|
+
`"${count}"`,
|
|
420
|
+
() => "number",
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
scope.importables.push({
|
|
424
|
+
provider: "cdktn",
|
|
425
|
+
constructName: "TerraformCount",
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const iterator = t.variableDeclaration("const", [
|
|
429
|
+
t.variableDeclarator(
|
|
430
|
+
t.identifier(countIteratorName),
|
|
431
|
+
t.callExpression(
|
|
432
|
+
t.memberExpression(
|
|
433
|
+
t.identifier("TerraformCount"),
|
|
434
|
+
t.identifier("of"),
|
|
435
|
+
),
|
|
436
|
+
[referenceAst],
|
|
437
|
+
),
|
|
438
|
+
),
|
|
439
|
+
]);
|
|
440
|
+
t.addComment(iterator, "leading", loopComment);
|
|
441
|
+
mappedConfig.count = t.identifier(countIteratorName);
|
|
442
|
+
expressions.push(iterator);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const dynBlocks = extractDynamicBlocks(mappedConfig);
|
|
446
|
+
const nestedDynamicBlocks = dynBlocks.filter((block) =>
|
|
447
|
+
isNestedDynamicBlock(dynBlocks, block),
|
|
448
|
+
);
|
|
449
|
+
const dynamicBlocksUsingOverrides = dynBlocks.filter(
|
|
450
|
+
(block) =>
|
|
451
|
+
// nested blocks need overrides
|
|
452
|
+
nestedDynamicBlocks.includes(block) ||
|
|
453
|
+
// blocks that contain nested blocks need them as well
|
|
454
|
+
nestedDynamicBlocks.some((nestedBlock) =>
|
|
455
|
+
nestedBlock.path.startsWith(block.path),
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
// all others can be handled by the CDKTN runtime
|
|
459
|
+
const dynamicBlocksUsingRuntime = dynBlocks.filter(
|
|
460
|
+
(block) => !dynamicBlocksUsingOverrides.includes(block),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
for (const [i, block] of dynamicBlocksUsingRuntime.entries()) {
|
|
464
|
+
const dynamicBlockIteratorName = variableName(
|
|
465
|
+
scope,
|
|
466
|
+
resource,
|
|
467
|
+
`${key}_dynamic_iterator_${i}`,
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const referenceAst = await convertTerraformExpressionToTs(
|
|
471
|
+
scope,
|
|
472
|
+
`"${block.for_each}"`,
|
|
473
|
+
() => ["list", "dynamic"],
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
scope.importables.push({
|
|
477
|
+
provider: "cdktn",
|
|
478
|
+
constructName: "TerraformIterator",
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const iterator = t.variableDeclaration("const", [
|
|
482
|
+
t.variableDeclarator(
|
|
483
|
+
t.identifier(dynamicBlockIteratorName),
|
|
484
|
+
t.callExpression(
|
|
485
|
+
t.memberExpression(
|
|
486
|
+
t.identifier("TerraformIterator"),
|
|
487
|
+
t.identifier("fromList"),
|
|
488
|
+
),
|
|
489
|
+
[referenceAst],
|
|
490
|
+
),
|
|
491
|
+
),
|
|
492
|
+
]);
|
|
493
|
+
t.addComment(iterator, "leading", loopComment);
|
|
494
|
+
expressions.push(iterator);
|
|
495
|
+
const dynamicCallExpression = t.callExpression(
|
|
496
|
+
t.memberExpression(
|
|
497
|
+
t.identifier(dynamicBlockIteratorName),
|
|
498
|
+
t.identifier("dynamic"),
|
|
499
|
+
),
|
|
500
|
+
[
|
|
501
|
+
await valueToTs(
|
|
502
|
+
{
|
|
503
|
+
...scope,
|
|
504
|
+
scopedVariables: {
|
|
505
|
+
[block.scopedVar]: dynamicBlockIteratorName,
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
fillWithConfigAccessors(
|
|
509
|
+
scope,
|
|
510
|
+
Array.isArray(block.content) ? block.content[0] : block.content,
|
|
511
|
+
block.path.replace(block.scopedVar, ""),
|
|
512
|
+
),
|
|
513
|
+
block.path.replace(block.scopedVar, ""),
|
|
514
|
+
false,
|
|
515
|
+
),
|
|
516
|
+
],
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const parts = block.path
|
|
520
|
+
.replace(`dynamic.${block.scopedVar}`, "")
|
|
521
|
+
.split(".")
|
|
522
|
+
.filter((p) => p.length > 0);
|
|
523
|
+
|
|
524
|
+
const parent = parts.reduce((acc, part) => {
|
|
525
|
+
if (Array.isArray(acc) && !Number.isNaN(parseInt(part, 10))) {
|
|
526
|
+
return acc[parseInt(part, 10)];
|
|
527
|
+
} else {
|
|
528
|
+
return acc[part];
|
|
529
|
+
}
|
|
530
|
+
}, mappedConfig);
|
|
531
|
+
parent[block.scopedVar] = dynamicCallExpression;
|
|
532
|
+
delete parent.dynamic;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const overrideReference = dynamicBlocksUsingOverrides.length
|
|
536
|
+
? {
|
|
537
|
+
start: 0,
|
|
538
|
+
end: 0,
|
|
539
|
+
referencee: {
|
|
540
|
+
id: `${type}.${key}`,
|
|
541
|
+
full: `${type}.${key}`,
|
|
542
|
+
},
|
|
543
|
+
}
|
|
544
|
+
: undefined;
|
|
545
|
+
|
|
546
|
+
if (provisioner) {
|
|
547
|
+
mappedConfig.provisioners = await Promise.all(
|
|
548
|
+
Object.entries(provisioner).flatMap(([type, p]: [string, any]) =>
|
|
549
|
+
p.map((pp: Record<string, any>) =>
|
|
550
|
+
valueToTs(
|
|
551
|
+
scope,
|
|
552
|
+
{ type, ...pp },
|
|
553
|
+
"path-for-provisioners-can-be-ignored",
|
|
554
|
+
),
|
|
555
|
+
),
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const importGraphId = `import.${resource.replace(".", "_")}.${key}`;
|
|
561
|
+
const importDefinition: Import | undefined = graph.hasNode(importGraphId)
|
|
562
|
+
? graph.getNodeAttribute(importGraphId, "value")
|
|
563
|
+
: undefined;
|
|
564
|
+
|
|
565
|
+
expressions = expressions.concat(
|
|
566
|
+
await asExpression(
|
|
567
|
+
{ ...scope, forEachIteratorName, countIteratorName },
|
|
568
|
+
resource,
|
|
569
|
+
key,
|
|
570
|
+
mappedConfig,
|
|
571
|
+
false,
|
|
572
|
+
false,
|
|
573
|
+
getReference(graph, id) || overrideReference,
|
|
574
|
+
importDefinition,
|
|
575
|
+
),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// Check for dynamic blocks
|
|
579
|
+
expressions = expressions.concat(
|
|
580
|
+
await Promise.all(
|
|
581
|
+
dynamicBlocksUsingOverrides.map(async ({ path, for_each, content }) => {
|
|
582
|
+
// We need to let the expression conversion know all available
|
|
583
|
+
// dynamic block names, so we don't replace them. The "dynamic-block"
|
|
584
|
+
// scoped variable indicates to the expression conversion to use the
|
|
585
|
+
// key name instead of an iterator
|
|
586
|
+
const scopedVariablesInPath = Object.fromEntries(
|
|
587
|
+
path
|
|
588
|
+
.substring(1) // The path starts with a dot that results in an empty split
|
|
589
|
+
.split(".")
|
|
590
|
+
.filter(
|
|
591
|
+
(p) => !["dynamic", "content"].includes(p) && isNaN(parseInt(p)),
|
|
592
|
+
)
|
|
593
|
+
.map((p) => [p, "dynamic-block"]),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
return addOverrideExpression(
|
|
597
|
+
varName,
|
|
598
|
+
path.substring(1), // The path starts with a dot that we don't want
|
|
599
|
+
await valueToTs(
|
|
600
|
+
{
|
|
601
|
+
...scope,
|
|
602
|
+
withinOverrideExpression: true,
|
|
603
|
+
scopedVariables: scopedVariablesInPath,
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
for_each,
|
|
607
|
+
content,
|
|
608
|
+
},
|
|
609
|
+
"path-for-dynamic-blocks-can-be-ignored",
|
|
610
|
+
),
|
|
611
|
+
loopComment,
|
|
612
|
+
);
|
|
613
|
+
}),
|
|
614
|
+
),
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
return expressions;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function asExpression(
|
|
621
|
+
scope: ResourceScope,
|
|
622
|
+
type: string,
|
|
623
|
+
name: string,
|
|
624
|
+
config: TerraformResourceBlock,
|
|
625
|
+
isModuleImport: boolean,
|
|
626
|
+
isProvider: boolean,
|
|
627
|
+
reference?: Reference,
|
|
628
|
+
imported?: Import,
|
|
629
|
+
) {
|
|
630
|
+
const { providers, ...otherOptions } = config as any;
|
|
631
|
+
|
|
632
|
+
const constructId = uniqueId(scope.constructs, name);
|
|
633
|
+
const overrideId = !isProvider && constructId !== name;
|
|
634
|
+
|
|
635
|
+
const completeObject = fillWithConfigAccessors(scope, otherOptions, type);
|
|
636
|
+
|
|
637
|
+
const expression = t.newExpression(
|
|
638
|
+
constructAst(scope, type, isModuleImport),
|
|
639
|
+
[
|
|
640
|
+
t.thisExpression(),
|
|
641
|
+
t.stringLiteral(constructId),
|
|
642
|
+
|
|
643
|
+
await valueToTs(
|
|
644
|
+
scope,
|
|
645
|
+
{
|
|
646
|
+
...completeObject,
|
|
647
|
+
providers:
|
|
648
|
+
providers && Object.keys(providers).length
|
|
649
|
+
? Object.entries(providers).map(([key, value]) => ({
|
|
650
|
+
moduleAlias: key,
|
|
651
|
+
provider: value,
|
|
652
|
+
}))
|
|
653
|
+
: undefined,
|
|
654
|
+
},
|
|
655
|
+
`${type}`,
|
|
656
|
+
isModuleImport,
|
|
657
|
+
),
|
|
658
|
+
],
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const statements = [];
|
|
662
|
+
const varName = reference
|
|
663
|
+
? referenceToVariableName(scope, reference)
|
|
664
|
+
: variableName(scope, type, name);
|
|
665
|
+
|
|
666
|
+
if (reference || overrideId || imported) {
|
|
667
|
+
statements.push(
|
|
668
|
+
t.variableDeclaration("const", [
|
|
669
|
+
t.variableDeclarator(t.identifier(varName), expression),
|
|
670
|
+
]),
|
|
671
|
+
);
|
|
672
|
+
} else {
|
|
673
|
+
statements.push(t.expressionStatement(expression));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (overrideId) {
|
|
677
|
+
statements.push(addOverrideLogicalIdExpression(varName, name));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (imported) {
|
|
681
|
+
// Adds myVar.importFrom("my-arn")
|
|
682
|
+
const importExpression = t.expressionStatement(
|
|
683
|
+
t.callExpression(
|
|
684
|
+
t.memberExpression(t.identifier(varName), t.identifier("importFrom")),
|
|
685
|
+
[t.stringLiteral(imported.id)],
|
|
686
|
+
),
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
if (imported.provider) {
|
|
690
|
+
t.addComment(
|
|
691
|
+
importExpression,
|
|
692
|
+
"leading",
|
|
693
|
+
`This import was configured with a provider. CDKTN does support this, but the cdktn convert command does not yet. Please add the provider reference manually. See https://cdktn.io/docs/concepts/resources#importing-resources for more information.`,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
statements.push(importExpression);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return statements;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export async function output(
|
|
704
|
+
scope: ProgramScope,
|
|
705
|
+
key: string,
|
|
706
|
+
_id: string,
|
|
707
|
+
item: Output,
|
|
708
|
+
_graph: DirectedGraph,
|
|
709
|
+
) {
|
|
710
|
+
const [{ value, description, sensitive }] = item;
|
|
711
|
+
|
|
712
|
+
return asExpression(
|
|
713
|
+
scope,
|
|
714
|
+
"cdktn.TerraformOutput",
|
|
715
|
+
key,
|
|
716
|
+
{
|
|
717
|
+
value,
|
|
718
|
+
description,
|
|
719
|
+
sensitive,
|
|
720
|
+
},
|
|
721
|
+
false,
|
|
722
|
+
false,
|
|
723
|
+
undefined,
|
|
724
|
+
undefined,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export async function variableTypeToAst(
|
|
729
|
+
scope: ProgramScope,
|
|
730
|
+
type: string,
|
|
731
|
+
): Promise<t.Expression> {
|
|
732
|
+
const addVariableTypeToImports = () =>
|
|
733
|
+
scope.importables.push({
|
|
734
|
+
constructName: "VariableType",
|
|
735
|
+
provider: "cdktn",
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
function parsedTypeToAst(type: tex.ExpressionType): t.Expression {
|
|
739
|
+
if (tex.isScopeTraversalExpression(type)) {
|
|
740
|
+
addVariableTypeToImports();
|
|
741
|
+
switch (type.meta.value) {
|
|
742
|
+
case "string":
|
|
743
|
+
return t.identifier("VariableType.STRING");
|
|
744
|
+
case "number":
|
|
745
|
+
return t.identifier("VariableType.NUMBER");
|
|
746
|
+
case "bool":
|
|
747
|
+
return t.identifier("VariableType.BOOL");
|
|
748
|
+
case "any":
|
|
749
|
+
default:
|
|
750
|
+
return t.identifier("VariableType.ANY");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (tex.isFunctionCallExpression(type)) {
|
|
755
|
+
addVariableTypeToImports();
|
|
756
|
+
switch (type.meta.name) {
|
|
757
|
+
case "list":
|
|
758
|
+
case "set":
|
|
759
|
+
case "map":
|
|
760
|
+
case "tuple":
|
|
761
|
+
case "object":
|
|
762
|
+
return t.callExpression(
|
|
763
|
+
t.identifier(`VariableType.${type.meta.name}`),
|
|
764
|
+
type.children.map((child) => parsedTypeToAst(child)),
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (tex.isObjectExpression(type)) {
|
|
770
|
+
return t.objectExpression(
|
|
771
|
+
Object.entries(type.meta.items).map(([key, value]) =>
|
|
772
|
+
t.objectProperty(
|
|
773
|
+
t.stringLiteral(key),
|
|
774
|
+
// This does not deal with complex types nested within objects
|
|
775
|
+
// If such a type is found it will result in an Any type
|
|
776
|
+
// e.g. { foo: list(string) } will result in { foo: any }
|
|
777
|
+
parsedTypeToAst({
|
|
778
|
+
type: "scopeTraversal",
|
|
779
|
+
meta: { value },
|
|
780
|
+
} as any),
|
|
781
|
+
),
|
|
782
|
+
),
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
addVariableTypeToImports();
|
|
787
|
+
return t.identifier("VariableType.ANY");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return parsedTypeToAst(await expressionAst(type));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export async function variable(
|
|
794
|
+
scope: ProgramScope,
|
|
795
|
+
key: string,
|
|
796
|
+
id: string,
|
|
797
|
+
item: Variable,
|
|
798
|
+
graph: DirectedGraph,
|
|
799
|
+
) {
|
|
800
|
+
const [{ type, ...props }] = item;
|
|
801
|
+
|
|
802
|
+
if (!getReference(graph, id)) {
|
|
803
|
+
return [];
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return asExpression(
|
|
807
|
+
scope,
|
|
808
|
+
id,
|
|
809
|
+
key,
|
|
810
|
+
{ ...props, type: type ? await variableTypeToAst(scope, type) : undefined },
|
|
811
|
+
false,
|
|
812
|
+
false,
|
|
813
|
+
getReference(graph, id),
|
|
814
|
+
undefined,
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
export async function local(
|
|
819
|
+
scope: ProgramScope,
|
|
820
|
+
key: string,
|
|
821
|
+
id: string,
|
|
822
|
+
item: TerraformResourceBlock,
|
|
823
|
+
graph: DirectedGraph,
|
|
824
|
+
): Promise<t.VariableDeclaration[]> {
|
|
825
|
+
logger.debug(`Initializing local resource ${key} with id ${id}`);
|
|
826
|
+
if (!getReference(graph, id)) {
|
|
827
|
+
logger.debug(`No reference found for ${key}`);
|
|
828
|
+
return [];
|
|
829
|
+
}
|
|
830
|
+
return [
|
|
831
|
+
t.variableDeclaration("const", [
|
|
832
|
+
t.variableDeclarator(
|
|
833
|
+
t.identifier(variableName(scope, "local", key)),
|
|
834
|
+
await valueToTs(scope, item, "path-for-local-blocks-can-be-ignored"),
|
|
835
|
+
),
|
|
836
|
+
]),
|
|
837
|
+
];
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
export async function modules(
|
|
841
|
+
scope: ProgramScope,
|
|
842
|
+
key: string,
|
|
843
|
+
id: string,
|
|
844
|
+
item: Module,
|
|
845
|
+
graph: DirectedGraph,
|
|
846
|
+
) {
|
|
847
|
+
const [{ source, version: _version, ...props }] = item;
|
|
848
|
+
|
|
849
|
+
const moduleConstraint = new TerraformModuleConstraint(source);
|
|
850
|
+
|
|
851
|
+
return asExpression(
|
|
852
|
+
scope,
|
|
853
|
+
moduleConstraint.className,
|
|
854
|
+
key,
|
|
855
|
+
props,
|
|
856
|
+
true,
|
|
857
|
+
false,
|
|
858
|
+
getReference(graph, id),
|
|
859
|
+
undefined,
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
export async function imports(
|
|
864
|
+
scope: ProgramScope,
|
|
865
|
+
_id: string,
|
|
866
|
+
item: Import,
|
|
867
|
+
graph: DirectedGraph,
|
|
868
|
+
) {
|
|
869
|
+
// Move from ${aws_instance.example} to aws_instance.example
|
|
870
|
+
const target =
|
|
871
|
+
item.to.startsWith("${") && item.to.endsWith("}")
|
|
872
|
+
? item.to.substring(2, item.to.length - 1)
|
|
873
|
+
: item.to;
|
|
874
|
+
|
|
875
|
+
// Check if the import goes into a module
|
|
876
|
+
if (target.startsWith("module.")) {
|
|
877
|
+
return [
|
|
878
|
+
t.addComment(
|
|
879
|
+
t.emptyStatement(),
|
|
880
|
+
"leading",
|
|
881
|
+
`CDKTN does not support imports into modules yet, please remove the import block importing ${item.id} into ${target} from your configuration`,
|
|
882
|
+
),
|
|
883
|
+
];
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// We now know that the import goes into a resource, e.g. aws_instance.example
|
|
887
|
+
const [resourceTypeIdentifier, resourceName] = target.split(".");
|
|
888
|
+
if (resourceName.includes("[")) {
|
|
889
|
+
return [
|
|
890
|
+
t.addComment(
|
|
891
|
+
t.emptyStatement(),
|
|
892
|
+
"leading",
|
|
893
|
+
`CDKTN does not support imports into resources with count or for_each yet, please remove the import block importing ${item.id} into ${target} from your configuration`,
|
|
894
|
+
),
|
|
895
|
+
];
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Check if we have a existing resource config with the given name
|
|
899
|
+
if (graph.hasNode(target)) {
|
|
900
|
+
// We will handle this case in the resource function
|
|
901
|
+
// so we can skip over it
|
|
902
|
+
return [];
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const [provider, ...resourceTypeNameParts] =
|
|
906
|
+
resourceTypeIdentifier.split("_");
|
|
907
|
+
|
|
908
|
+
const constructId = uniqueId(scope.constructs, camelCase(resourceName));
|
|
909
|
+
const constructClass = constructAst(
|
|
910
|
+
scope,
|
|
911
|
+
`${provider}.${resourceTypeNameParts.join("_")}`,
|
|
912
|
+
false,
|
|
913
|
+
);
|
|
914
|
+
return [
|
|
915
|
+
t.expressionStatement(
|
|
916
|
+
t.callExpression(
|
|
917
|
+
t.memberExpression(
|
|
918
|
+
constructClass,
|
|
919
|
+
t.identifier("generateConfigForImport"),
|
|
920
|
+
),
|
|
921
|
+
[
|
|
922
|
+
t.thisExpression(),
|
|
923
|
+
t.stringLiteral(constructId),
|
|
924
|
+
t.stringLiteral(item.id),
|
|
925
|
+
],
|
|
926
|
+
),
|
|
927
|
+
),
|
|
928
|
+
];
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export async function provider(
|
|
932
|
+
scope: ProgramScope,
|
|
933
|
+
key: string,
|
|
934
|
+
id: string,
|
|
935
|
+
item: Provider[0],
|
|
936
|
+
graph: DirectedGraph,
|
|
937
|
+
) {
|
|
938
|
+
const { version: _version, ...props } = item;
|
|
939
|
+
|
|
940
|
+
const importKey = key === "null" ? "NullProvider" : key;
|
|
941
|
+
|
|
942
|
+
return asExpression(
|
|
943
|
+
scope,
|
|
944
|
+
`${importKey}.${pascalCase(key)}Provider`,
|
|
945
|
+
key,
|
|
946
|
+
props,
|
|
947
|
+
false,
|
|
948
|
+
true,
|
|
949
|
+
getReference(graph, id),
|
|
950
|
+
undefined,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
export const cdktfImport = template(
|
|
955
|
+
`import * as cdktn from "cdktn"`,
|
|
956
|
+
)() as t.Statement;
|
|
957
|
+
|
|
958
|
+
export const constructsImport = template(
|
|
959
|
+
`import * as constructs from "constructs"`,
|
|
960
|
+
)() as t.Statement;
|
|
961
|
+
|
|
962
|
+
export const providerImports = (providers: string[]) =>
|
|
963
|
+
providers.map((providerName) => {
|
|
964
|
+
const parts = providerName.split("/");
|
|
965
|
+
const name = parts.length > 1 ? parts[1] : parts[0];
|
|
966
|
+
const importName = name === "null" ? "NullProvider" : name;
|
|
967
|
+
return template(
|
|
968
|
+
`import * as ${importName} from "./.gen/providers/${name.replace(
|
|
969
|
+
"./",
|
|
970
|
+
"",
|
|
971
|
+
)}"`,
|
|
972
|
+
)() as t.Statement;
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
export const moduleImports = (modules: Record<string, Module> | undefined) => {
|
|
976
|
+
const uniqueModules = new Set<string>();
|
|
977
|
+
Object.values(modules || {}).map(([module]) =>
|
|
978
|
+
uniqueModules.add(module.source),
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const imports: t.Statement[] = [];
|
|
982
|
+
uniqueModules.forEach((m) => {
|
|
983
|
+
const moduleConstraint = new TerraformModuleConstraint(m);
|
|
984
|
+
imports.push(
|
|
985
|
+
template.ast(
|
|
986
|
+
`import * as ${moduleConstraint.className} from "./.gen/modules/${moduleConstraint.fileName}"`,
|
|
987
|
+
) as t.Statement,
|
|
988
|
+
);
|
|
989
|
+
});
|
|
990
|
+
return imports;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
export async function gen(statements: t.Statement[]) {
|
|
994
|
+
logger.debug(`Generating code for ${JSON.stringify(statements, null, 2)}`);
|
|
995
|
+
const code = prettier.format(generate(t.program(statements) as any).code, {
|
|
996
|
+
parser: "babel",
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
logger.debug(`Generated code:\n${code}`);
|
|
1000
|
+
|
|
1001
|
+
return code;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
export function addImportForCodeContainer(
|
|
1005
|
+
scope: ProgramScope,
|
|
1006
|
+
codeContainer: string,
|
|
1007
|
+
) {
|
|
1008
|
+
switch (codeContainer) {
|
|
1009
|
+
case "constructs.Construct":
|
|
1010
|
+
scope.importables.push({
|
|
1011
|
+
provider: "constructs",
|
|
1012
|
+
constructName: "Construct",
|
|
1013
|
+
});
|
|
1014
|
+
break;
|
|
1015
|
+
|
|
1016
|
+
case "cdktn.TerraformStack":
|
|
1017
|
+
scope.importables.push({
|
|
1018
|
+
provider: "cdktn",
|
|
1019
|
+
constructName: "TerraformStack",
|
|
1020
|
+
});
|
|
1021
|
+
break;
|
|
1022
|
+
|
|
1023
|
+
case "cdktf.TerraformStack":
|
|
1024
|
+
scope.importables.push({
|
|
1025
|
+
provider: "cdktf",
|
|
1026
|
+
constructName: "TerraformStack",
|
|
1027
|
+
});
|
|
1028
|
+
break;
|
|
1029
|
+
default:
|
|
1030
|
+
throw Errors.Internal("Unsupported code container: " + codeContainer);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export function wrapCodeInConstructor(
|
|
1035
|
+
codeContainer: string,
|
|
1036
|
+
code: t.Statement[],
|
|
1037
|
+
className: string,
|
|
1038
|
+
configTypeName?: string,
|
|
1039
|
+
): t.Statement {
|
|
1040
|
+
let baseContainerClass: t.Identifier;
|
|
1041
|
+
switch (codeContainer) {
|
|
1042
|
+
case "constructs.Construct":
|
|
1043
|
+
baseContainerClass = t.identifier("Construct");
|
|
1044
|
+
break;
|
|
1045
|
+
|
|
1046
|
+
case "cdktn.TerraformStack":
|
|
1047
|
+
baseContainerClass = t.identifier("TerraformStack");
|
|
1048
|
+
break;
|
|
1049
|
+
default:
|
|
1050
|
+
throw Errors.Internal("Unsupported code container: " + codeContainer);
|
|
1051
|
+
}
|
|
1052
|
+
if (configTypeName) {
|
|
1053
|
+
return template.statement(
|
|
1054
|
+
`
|
|
1055
|
+
class %%className%% extends %%base%% {
|
|
1056
|
+
constructor(scope: Construct, name: string, config: ${configTypeName}) {
|
|
1057
|
+
super(scope, name);
|
|
1058
|
+
%%code%%
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
`,
|
|
1062
|
+
{ syntacticPlaceholders: true, plugins: ["typescript"] },
|
|
1063
|
+
)({
|
|
1064
|
+
code,
|
|
1065
|
+
base: baseContainerClass,
|
|
1066
|
+
className: t.identifier(className),
|
|
1067
|
+
}) as t.Statement;
|
|
1068
|
+
}
|
|
1069
|
+
return template.statement(
|
|
1070
|
+
`
|
|
1071
|
+
class %%className%% extends %%base%% {
|
|
1072
|
+
constructor(scope: Construct, name: string) {
|
|
1073
|
+
super(scope, name);
|
|
1074
|
+
%%code%%
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
`,
|
|
1078
|
+
{ syntacticPlaceholders: true, plugins: ["typescript"] },
|
|
1079
|
+
)({
|
|
1080
|
+
code,
|
|
1081
|
+
base: baseContainerClass,
|
|
1082
|
+
className: t.identifier(className),
|
|
1083
|
+
}) as t.Statement;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export const providerConstructImports = (importable: ImportableConstruct[]) => {
|
|
1087
|
+
let provider = importable[0].provider;
|
|
1088
|
+
let namespace = importable[0].namespace;
|
|
1089
|
+
const names = importable.map((i) => i.constructName);
|
|
1090
|
+
|
|
1091
|
+
if (provider === "cdktn" || provider === "constructs") {
|
|
1092
|
+
return template(
|
|
1093
|
+
`import { ${names.join(", ")} } from "${provider}"`,
|
|
1094
|
+
)() as t.Statement;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (namespace) {
|
|
1098
|
+
namespace = snakeCase(namespace).replace(/_/g, "-");
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Special cases to undo provider names that we override
|
|
1102
|
+
if (provider === "NullProvider") {
|
|
1103
|
+
provider = "null";
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return template(
|
|
1107
|
+
`import { ${names.join(
|
|
1108
|
+
", ",
|
|
1109
|
+
)} } from "./.gen/providers/${provider}/${namespace}"`,
|
|
1110
|
+
)() as t.Statement;
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
export function buildImports(importables: ImportableConstruct[]) {
|
|
1114
|
+
const groupedImportables = importables.reduce(
|
|
1115
|
+
(acc, importable) => {
|
|
1116
|
+
const ns = importable.namespace || "";
|
|
1117
|
+
// Doing some hacky ordering of the imports to make them look a bit nicer
|
|
1118
|
+
const prefix =
|
|
1119
|
+
importable.provider === "constructs"
|
|
1120
|
+
? "1"
|
|
1121
|
+
: importable.provider === "cdktn"
|
|
1122
|
+
? "2"
|
|
1123
|
+
: importable.provider === "cdktf"
|
|
1124
|
+
? "3"
|
|
1125
|
+
: "4";
|
|
1126
|
+
const groupName = `${prefix}.${importable.provider}.${ns}`;
|
|
1127
|
+
const fullName = `${importable.provider}.${ns}.${importable.constructName}`;
|
|
1128
|
+
|
|
1129
|
+
if (acc[groupName]) {
|
|
1130
|
+
const existsAlready = acc[groupName].some(
|
|
1131
|
+
(importable) =>
|
|
1132
|
+
`${importable.provider}.${ns}.${importable.constructName}` ===
|
|
1133
|
+
fullName,
|
|
1134
|
+
);
|
|
1135
|
+
if (existsAlready) {
|
|
1136
|
+
return acc;
|
|
1137
|
+
}
|
|
1138
|
+
acc[groupName].push(importable);
|
|
1139
|
+
acc[groupName].sort();
|
|
1140
|
+
} else {
|
|
1141
|
+
acc[groupName] = [importable];
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return acc;
|
|
1145
|
+
},
|
|
1146
|
+
{} as Record<string, ImportableConstruct[]>,
|
|
1147
|
+
);
|
|
1148
|
+
|
|
1149
|
+
let commentAdded = false;
|
|
1150
|
+
const constructImports = Object.keys(groupedImportables)
|
|
1151
|
+
.sort()
|
|
1152
|
+
.map((groupName) => {
|
|
1153
|
+
const importStatement = providerConstructImports(
|
|
1154
|
+
groupedImportables[groupName],
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
if (groupName.startsWith("4.") && !commentAdded) {
|
|
1158
|
+
commentAdded = true;
|
|
1159
|
+
t.addComment(
|
|
1160
|
+
importStatement,
|
|
1161
|
+
"leading",
|
|
1162
|
+
`\n* Provider bindings are generated by running \`cdktn get\`.
|
|
1163
|
+
* See https://cdktn.io/docs/concepts/providers#import-providers for more details.\n`,
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
return importStatement;
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
return constructImports;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
export function generateConfigType(
|
|
1173
|
+
name: string,
|
|
1174
|
+
config: Record<string, AttributePath>,
|
|
1175
|
+
): t.Statement {
|
|
1176
|
+
return t.tsInterfaceDeclaration(
|
|
1177
|
+
t.identifier(name),
|
|
1178
|
+
undefined,
|
|
1179
|
+
undefined,
|
|
1180
|
+
t.tsInterfaceBody(
|
|
1181
|
+
Object.entries(config).map(([key, _value]) =>
|
|
1182
|
+
t.tsPropertySignature(
|
|
1183
|
+
t.identifier(key),
|
|
1184
|
+
t.tSTypeAnnotation(t.tsAnyKeyword()), // TODO: Try to make this better than any
|
|
1185
|
+
),
|
|
1186
|
+
),
|
|
1187
|
+
),
|
|
1188
|
+
);
|
|
1189
|
+
}
|