@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,88 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { convert } from "../src/index";
|
|
4
|
+
|
|
5
|
+
describe("convert", () => {
|
|
6
|
+
it("exposes stats for telemetry", async () => {
|
|
7
|
+
const { stats } = await convert(
|
|
8
|
+
`
|
|
9
|
+
resource "aws_kms_key" "examplekms" {
|
|
10
|
+
description = "KMS key 1"
|
|
11
|
+
deletion_window_in_days = 7
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resource "aws_s3_bucket" "examplebucket" {
|
|
15
|
+
bucket = "examplebuckettftest"
|
|
16
|
+
acl = "private"
|
|
17
|
+
}
|
|
18
|
+
resource "aws_s3_bucket" "examplebucket_two" {
|
|
19
|
+
bucket = "examplebuckettftest"
|
|
20
|
+
acl = "private"
|
|
21
|
+
}
|
|
22
|
+
resource "aws_s3_bucket" "examplebucket_three" {
|
|
23
|
+
bucket = "examplebuckettftest"
|
|
24
|
+
acl = "private"
|
|
25
|
+
}
|
|
26
|
+
resource "internal_cloud_s3_bucket" "examplebucket" {
|
|
27
|
+
bucket = "examplebuckettftest"
|
|
28
|
+
acl = "private"
|
|
29
|
+
}
|
|
30
|
+
resource "internal_cloud_s3_bucket_object" "examplebucket_two" {
|
|
31
|
+
bucket = "examplebuckettftest"
|
|
32
|
+
acl = "private"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
resource "aws_s3_bucket_object" "examplebucket_object" {
|
|
36
|
+
key = "someobject"
|
|
37
|
+
bucket = element(aws_s3_bucket.examplebucket, 0).id
|
|
38
|
+
source = "index.html"
|
|
39
|
+
kms_key_id = aws_kms_key.examplekms.arn
|
|
40
|
+
}
|
|
41
|
+
resource "aws_s3_bucket_object" "examplebucket_object_two" {
|
|
42
|
+
key = "someobject"
|
|
43
|
+
bucket = element(aws_s3_bucket.examplebucket, 0).id
|
|
44
|
+
source = "index.html"
|
|
45
|
+
kms_key_id = aws_kms_key.examplekms.arn
|
|
46
|
+
}
|
|
47
|
+
resource "google_compute_autoscaler" "example" {
|
|
48
|
+
name = "example-autoscaler"
|
|
49
|
+
zone = "us-east1-b"
|
|
50
|
+
|
|
51
|
+
autoscaling_policy = {
|
|
52
|
+
max_replicas = 8
|
|
53
|
+
min_replicas = 2
|
|
54
|
+
cooldown_period = 60
|
|
55
|
+
|
|
56
|
+
cpu_utilization = {
|
|
57
|
+
target = 0.5
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
{ language: "typescript", providerSchema: {} },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(stats).toMatchInlineSnapshot(`
|
|
66
|
+
{
|
|
67
|
+
"convertedLines": 54,
|
|
68
|
+
"data": {},
|
|
69
|
+
"language": "typescript",
|
|
70
|
+
"numberOfModules": 0,
|
|
71
|
+
"numberOfProviders": 3,
|
|
72
|
+
"resources": {
|
|
73
|
+
"aws": {
|
|
74
|
+
"kms_key": 1,
|
|
75
|
+
"s3_bucket": 3,
|
|
76
|
+
"s3_bucket_object": 2,
|
|
77
|
+
},
|
|
78
|
+
"google": {
|
|
79
|
+
"compute_autoscaler": 1,
|
|
80
|
+
},
|
|
81
|
+
"other": {
|
|
82
|
+
"other": 1,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { convert } from "../../src/index";
|
|
4
|
+
import * as fs from "fs-extra";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import execa from "execa";
|
|
7
|
+
import {
|
|
8
|
+
LANGUAGES,
|
|
9
|
+
TerraformModuleConstraint,
|
|
10
|
+
TerraformProviderConstraint,
|
|
11
|
+
} from "@cdktn/commons";
|
|
12
|
+
import { readSchema } from "@cdktn/provider-schema";
|
|
13
|
+
import type { FixturesManifest } from "../globalSetup";
|
|
14
|
+
import { createTmpHelper } from "./tmp";
|
|
15
|
+
|
|
16
|
+
const tmp = createTmpHelper();
|
|
17
|
+
|
|
18
|
+
// Tests `process.chdir(projectDir)` for jsii-rosetta. If a test leaves cwd inside a projectDir that tmp()'s afterAll later removes, the worker's next test load sees uv_cwd ENOENT.
|
|
19
|
+
const _originalCwd = process.cwd();
|
|
20
|
+
if (typeof afterEach === "function") {
|
|
21
|
+
afterEach(() => process.chdir(_originalCwd));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const includeSynthTests = Boolean(process.env.CI);
|
|
25
|
+
|
|
26
|
+
// Load pre-generated fixtures manifest from globalSetup
|
|
27
|
+
let _manifest: FixturesManifest | undefined;
|
|
28
|
+
function getManifest(): FixturesManifest {
|
|
29
|
+
if (_manifest) return _manifest;
|
|
30
|
+
const manifestPath = process.env.HCL2CDK_FIXTURES_MANIFEST;
|
|
31
|
+
if (!manifestPath) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"HCL2CDK_FIXTURES_MANIFEST env var not set. Ensure globalSetup ran successfully.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
_manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
37
|
+
return _manifest!;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum Synth {
|
|
41
|
+
yes_all_languages, // Synth and snapshot all languages
|
|
42
|
+
yes,
|
|
43
|
+
yes_but_only_typescript_right_now_because_it_breaks,
|
|
44
|
+
no_cant_resolve_construct,
|
|
45
|
+
no_missing_map_access, // See https://github.com/hashicorp/terraform-cdk/issues/2670
|
|
46
|
+
no_missing_type_coercion, // We don't type coerce numbers yet
|
|
47
|
+
never, // Some examples are built so that they will never synth but test a specific generation edge case
|
|
48
|
+
}
|
|
49
|
+
export enum Snapshot {
|
|
50
|
+
yes_all_languages, // Synth and snapshot all languages
|
|
51
|
+
yes,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type PathToCopy = string;
|
|
55
|
+
type ProviderFqn = string;
|
|
56
|
+
enum ProviderType {
|
|
57
|
+
provider,
|
|
58
|
+
module,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type ProviderDefinition = {
|
|
62
|
+
fqn: ProviderFqn;
|
|
63
|
+
type: ProviderType;
|
|
64
|
+
path: PathToCopy;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type SchemaFilter = {
|
|
68
|
+
resources?: string[];
|
|
69
|
+
dataSources?: string[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const cdktnBin = path.join(__dirname, "../../../../cdktn-cli/bin/cdktn");
|
|
73
|
+
const cdktnDist = path.join(__dirname, "../../../../../dist");
|
|
74
|
+
const tsxPkgJsonPath = require.resolve("tsx/package.json");
|
|
75
|
+
const tsxBin = path.join(
|
|
76
|
+
path.dirname(tsxPkgJsonPath),
|
|
77
|
+
fs.readJsonSync(tsxPkgJsonPath).bin,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
export const binding = {
|
|
81
|
+
aws: {
|
|
82
|
+
fqn: "hashicorp/aws@=5.11.0",
|
|
83
|
+
type: ProviderType.provider,
|
|
84
|
+
path: "providers/aws",
|
|
85
|
+
},
|
|
86
|
+
docker: {
|
|
87
|
+
fqn: "kreuzwerker/docker@=3.0.1",
|
|
88
|
+
type: ProviderType.provider,
|
|
89
|
+
path: "providers/docker",
|
|
90
|
+
},
|
|
91
|
+
null: {
|
|
92
|
+
fqn: "hashicorp/null@=3.2.1",
|
|
93
|
+
type: ProviderType.provider,
|
|
94
|
+
path: "providers/null",
|
|
95
|
+
},
|
|
96
|
+
google: {
|
|
97
|
+
fqn: "hashicorp/google@=4.55.0",
|
|
98
|
+
type: ProviderType.provider,
|
|
99
|
+
path: "providers/google",
|
|
100
|
+
},
|
|
101
|
+
azuread: {
|
|
102
|
+
fqn: "hashicorp/azuread@=2.36.0",
|
|
103
|
+
type: ProviderType.provider,
|
|
104
|
+
path: "providers/azuread",
|
|
105
|
+
},
|
|
106
|
+
local: {
|
|
107
|
+
fqn: "hashicorp/local@=2.3.0",
|
|
108
|
+
type: ProviderType.provider,
|
|
109
|
+
path: "providers/local",
|
|
110
|
+
},
|
|
111
|
+
auth0: {
|
|
112
|
+
fqn: "alexkappa/auth0@=0.26.2",
|
|
113
|
+
type: ProviderType.provider,
|
|
114
|
+
path: "providers/auth0",
|
|
115
|
+
},
|
|
116
|
+
datadog: {
|
|
117
|
+
fqn: "DataDog/datadog@=3.21.0",
|
|
118
|
+
type: ProviderType.provider,
|
|
119
|
+
path: "providers/datadog",
|
|
120
|
+
},
|
|
121
|
+
kubernetes: {
|
|
122
|
+
fqn: "hashicorp/kubernetes@=2.18.0",
|
|
123
|
+
type: ProviderType.provider,
|
|
124
|
+
path: "providers/kubernetes",
|
|
125
|
+
},
|
|
126
|
+
scaleway: {
|
|
127
|
+
fqn: "scaleway/scaleway@ ~>2.10.0",
|
|
128
|
+
type: ProviderType.provider,
|
|
129
|
+
path: "providers/scaleway",
|
|
130
|
+
},
|
|
131
|
+
external: {
|
|
132
|
+
fqn: "hashicorp/external@=2.3.1",
|
|
133
|
+
type: ProviderType.provider,
|
|
134
|
+
path: "providers/external",
|
|
135
|
+
},
|
|
136
|
+
awsVpc: {
|
|
137
|
+
fqn: "terraform-aws-modules/vpc/aws@=3.19.0",
|
|
138
|
+
type: ProviderType.module,
|
|
139
|
+
path: "modules/terraform-aws-modules/aws",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
type AbsolutePath = string;
|
|
144
|
+
|
|
145
|
+
async function copyBindingsForProvider(
|
|
146
|
+
binding: ProviderDefinition,
|
|
147
|
+
targetDirectory: AbsolutePath,
|
|
148
|
+
) {
|
|
149
|
+
const manifest = getManifest();
|
|
150
|
+
const absoluteBindingPath = manifest.providerBindings[binding.fqn];
|
|
151
|
+
if (!absoluteBindingPath) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`No pre-generated binding found for ${binding.fqn}. Ensure globalSetup generates it.`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const target = path.resolve(targetDirectory, ".gen", binding.path);
|
|
158
|
+
await fs.mkdirp(target);
|
|
159
|
+
await fs.copy(absoluteBindingPath, target);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Provisions `projectDir` from `baseDir`.
|
|
164
|
+
*
|
|
165
|
+
* Copies everything except `node_modules`, then drops a directory symlink in its place. `node_modules` is the heavy
|
|
166
|
+
* bit of the base project (hundreds of MB) and the test runner only reads from it — sharing it across tests avoids
|
|
167
|
+
* per-test copy cost. The base project must therefore be treated as read-only for the lifetime of the test suite.
|
|
168
|
+
*
|
|
169
|
+
* @param baseDir Source project (output of `cdktn init`) created once in globalSetup.
|
|
170
|
+
* @param projectDir Destination for this test's project.
|
|
171
|
+
*/
|
|
172
|
+
async function copyBaseProject(baseDir: string, projectDir: string) {
|
|
173
|
+
await fs.copy(baseDir, projectDir, {
|
|
174
|
+
filter: (src) => path.basename(src) !== "node_modules",
|
|
175
|
+
});
|
|
176
|
+
await fs.symlink(
|
|
177
|
+
path.join(baseDir, "node_modules"),
|
|
178
|
+
path.join(projectDir, "node_modules"),
|
|
179
|
+
"dir",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const fileEndings: Record<string, string> = {
|
|
184
|
+
typescript: ".ts",
|
|
185
|
+
python: ".py",
|
|
186
|
+
csharp: ".cs",
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const getFileContent: Record<
|
|
190
|
+
string,
|
|
191
|
+
(
|
|
192
|
+
code: { all: string; code: string; imports: string },
|
|
193
|
+
stackName: string,
|
|
194
|
+
) => string
|
|
195
|
+
> = {
|
|
196
|
+
typescript: ({ all }, stackName) => `
|
|
197
|
+
${all}
|
|
198
|
+
import { App } from "cdktn";
|
|
199
|
+
const app = new App();
|
|
200
|
+
new MyConvertedCode(app, "${stackName}");
|
|
201
|
+
app.synth();`,
|
|
202
|
+
python: ({ all }, stackName) => `
|
|
203
|
+
${all}
|
|
204
|
+
|
|
205
|
+
app = App()
|
|
206
|
+
MyConvertedCode(app, "${stackName}")
|
|
207
|
+
app.synth()
|
|
208
|
+
`,
|
|
209
|
+
|
|
210
|
+
csharp: ({ all }, stackName) => {
|
|
211
|
+
const endOfStack = all.lastIndexOf("}");
|
|
212
|
+
const stack = all.substring(0, endOfStack);
|
|
213
|
+
return `
|
|
214
|
+
${stack}
|
|
215
|
+
public static void Main(string[] args)
|
|
216
|
+
{
|
|
217
|
+
App app = new App();
|
|
218
|
+
new MyConvertedCode(app, "${stackName}");
|
|
219
|
+
app.Synth();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const getAppCommand: Record<string, (stackName: string) => string> = {
|
|
227
|
+
typescript: (stackName) => `${tsxBin} ${stackName}.ts`,
|
|
228
|
+
python: (stackName) => `pipenv run python ${stackName}.py`,
|
|
229
|
+
csharp: (stackName) => `dotnet run --project ${stackName}.csproj`,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const preSynth: Record<
|
|
233
|
+
string,
|
|
234
|
+
(
|
|
235
|
+
stackName: string,
|
|
236
|
+
projectDir: string,
|
|
237
|
+
providers: ProviderDefinition[],
|
|
238
|
+
) => Promise<void> | undefined
|
|
239
|
+
> = {
|
|
240
|
+
csharp: async (stackName, projectDir, providers) => {
|
|
241
|
+
await fs.writeFile(
|
|
242
|
+
path.join(projectDir, `${stackName}.csproj`),
|
|
243
|
+
`<Project Sdk="Microsoft.NET.Sdk">
|
|
244
|
+
|
|
245
|
+
<PropertyGroup>
|
|
246
|
+
<OutputType>Exe</OutputType>
|
|
247
|
+
<TargetFramework>net6.0</TargetFramework>
|
|
248
|
+
</PropertyGroup>
|
|
249
|
+
|
|
250
|
+
<ItemGroup>
|
|
251
|
+
<PackageReference Include="HashiCorp.Cdktf" Version="0.21.0" />
|
|
252
|
+
</ItemGroup>
|
|
253
|
+
|
|
254
|
+
<ItemGroup>
|
|
255
|
+
<PackageReference Include="Io.Cdktn" Version="0.0.0" />
|
|
256
|
+
</ItemGroup>
|
|
257
|
+
|
|
258
|
+
<ItemGroup>
|
|
259
|
+
${providers
|
|
260
|
+
.map((provider) => {
|
|
261
|
+
const [, name] = provider.fqn.split("@")[0].split("/");
|
|
262
|
+
return `<ProjectReference Include=".gen\\${
|
|
263
|
+
provider.type === ProviderType.provider ? "providers" : "modules"
|
|
264
|
+
}\\${name}.csproj" />`;
|
|
265
|
+
})
|
|
266
|
+
.join("\n")}
|
|
267
|
+
</ItemGroup>
|
|
268
|
+
|
|
269
|
+
</Project>`,
|
|
270
|
+
"utf8",
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
await fs.writeFile(
|
|
274
|
+
path.join(projectDir, "NuGet.Config"),
|
|
275
|
+
`<?xml version="1.0" encoding="utf-8"?>
|
|
276
|
+
<configuration>
|
|
277
|
+
<packageSources>
|
|
278
|
+
<add key="Locally Distributed Packages" value="${cdktnDist}/dotnet/" />
|
|
279
|
+
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
|
|
280
|
+
</packageSources>
|
|
281
|
+
</configuration>
|
|
282
|
+
`,
|
|
283
|
+
"utf8",
|
|
284
|
+
);
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Writes the converted code to a project directory and spawns `cdktn synth`
|
|
290
|
+
* against it, asserting that synth reports success on stdout.
|
|
291
|
+
*
|
|
292
|
+
* @param language Target language for the synth (typescript, python, csharp).
|
|
293
|
+
* @param name Human-readable test-case name.
|
|
294
|
+
* @param code Output of `convert()` for the HCL input
|
|
295
|
+
* @param providers Provider bindings the converted code imports.
|
|
296
|
+
* @param projectDir An existing project dir to reuse.
|
|
297
|
+
*/
|
|
298
|
+
async function synthForLanguage(
|
|
299
|
+
language: string,
|
|
300
|
+
name: string,
|
|
301
|
+
code: { all: string; code: string; imports: string },
|
|
302
|
+
providers: ProviderDefinition[] = [],
|
|
303
|
+
projectDir?: string,
|
|
304
|
+
) {
|
|
305
|
+
const stackName = name.replace(/\s/g, "-");
|
|
306
|
+
if (!projectDir) {
|
|
307
|
+
projectDir = await getProjectDirectory(language, providers);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Have a before all somewhere above bootstrap a TS project
|
|
311
|
+
// __dirname should be replaceed by the bootstrapped directory
|
|
312
|
+
const pathToThisProjectsFile = path.join(
|
|
313
|
+
projectDir,
|
|
314
|
+
stackName + fileEndings[language],
|
|
315
|
+
);
|
|
316
|
+
const fileContent = getFileContent[language](code, stackName);
|
|
317
|
+
|
|
318
|
+
fs.writeFileSync(pathToThisProjectsFile, fileContent, "utf8");
|
|
319
|
+
|
|
320
|
+
const runBeforeSynth = preSynth[language];
|
|
321
|
+
if (runBeforeSynth) {
|
|
322
|
+
await runBeforeSynth(stackName, projectDir, providers);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const { all } = await execa(
|
|
326
|
+
cdktnBin,
|
|
327
|
+
[
|
|
328
|
+
"synth",
|
|
329
|
+
"-a",
|
|
330
|
+
`'${getAppCommand[language](stackName)}'`,
|
|
331
|
+
"-o",
|
|
332
|
+
`./${stackName}-output`,
|
|
333
|
+
],
|
|
334
|
+
{ cwd: projectDir, all: true, shell: true },
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
expect(all!).toEqual(
|
|
338
|
+
expect.stringContaining(`Generated Terraform code for the stacks`),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// getProviderSchema(Object.values(binding));
|
|
343
|
+
|
|
344
|
+
async function getProjectDirectory(
|
|
345
|
+
language: string,
|
|
346
|
+
providers: ProviderDefinition[],
|
|
347
|
+
) {
|
|
348
|
+
const manifest = getManifest();
|
|
349
|
+
const baseDir = manifest.baseProjects[language];
|
|
350
|
+
if (!baseDir) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Unsupported language used to synthesize code: ${language}`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const projectDir = tmp("cdktf-convert-test-");
|
|
356
|
+
|
|
357
|
+
await Promise.all([
|
|
358
|
+
copyBaseProject(baseDir, projectDir),
|
|
359
|
+
...providers.map((provider) =>
|
|
360
|
+
copyBindingsForProvider(provider, projectDir),
|
|
361
|
+
),
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
// We only copy the TS bindings, but we need to run cdktn get for the language specific ones
|
|
365
|
+
if (language !== "typescript") {
|
|
366
|
+
await fs.writeFile(
|
|
367
|
+
path.resolve(projectDir, "cdktf.json"),
|
|
368
|
+
JSON.stringify(
|
|
369
|
+
{
|
|
370
|
+
language,
|
|
371
|
+
app: "echo 'app command should be overwritten'",
|
|
372
|
+
terraformProviders: providers
|
|
373
|
+
.filter((binding) => binding.type === ProviderType.provider)
|
|
374
|
+
.map((binding) => binding.fqn),
|
|
375
|
+
terraformModules: providers
|
|
376
|
+
.filter((binding) => binding.type === ProviderType.module)
|
|
377
|
+
.map((binding) => binding.fqn),
|
|
378
|
+
},
|
|
379
|
+
null,
|
|
380
|
+
2,
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
await execa(cdktnBin, ["get", "--force"], { cwd: projectDir });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return projectDir;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function getProviderSchema(providers: ProviderDefinition[]) {
|
|
390
|
+
const constraints = providers.map(
|
|
391
|
+
(provider) =>
|
|
392
|
+
ProviderType.provider === provider.type
|
|
393
|
+
? new TerraformProviderConstraint(provider.fqn)
|
|
394
|
+
: new TerraformModuleConstraint(provider.fqn),
|
|
395
|
+
LANGUAGES[0],
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const schemaCacheDir =
|
|
399
|
+
process.env.CDKTF_EXPERIMENTAL_PROVIDER_SCHEMA_CACHE_PATH ||
|
|
400
|
+
getManifest().schemaCacheDir;
|
|
401
|
+
|
|
402
|
+
return await readSchema(constraints, schemaCacheDir);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function filterSchema(
|
|
406
|
+
providerSchema: any,
|
|
407
|
+
schemaFilter: SchemaFilter | undefined,
|
|
408
|
+
) {
|
|
409
|
+
if (!schemaFilter) return providerSchema;
|
|
410
|
+
|
|
411
|
+
const { resources, dataSources } = schemaFilter;
|
|
412
|
+
|
|
413
|
+
const providerSchemaKey = Object.keys(providerSchema.provider_schemas)[0];
|
|
414
|
+
const actualSchema = providerSchema.provider_schemas[providerSchemaKey];
|
|
415
|
+
|
|
416
|
+
let filteredDataSourceSchemas = {};
|
|
417
|
+
let filteredResourceSchemas = {};
|
|
418
|
+
|
|
419
|
+
if (resources && resources.length > 0) {
|
|
420
|
+
filteredResourceSchemas = Object.fromEntries(
|
|
421
|
+
Object.entries(actualSchema.resource_schemas).filter(([resourceName]) =>
|
|
422
|
+
resources?.includes(resourceName),
|
|
423
|
+
),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (dataSources && dataSources.length > 0) {
|
|
428
|
+
filteredDataSourceSchemas = Object.fromEntries(
|
|
429
|
+
Object.entries(actualSchema.data_source_schemas).filter(
|
|
430
|
+
([dataSourceName]) => dataSources?.includes(dataSourceName),
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
provider_schemas: {
|
|
437
|
+
[providerSchemaKey]: {
|
|
438
|
+
provider: providerSchema.provider_schemas[providerSchemaKey].provider,
|
|
439
|
+
resource_schemas: filteredResourceSchemas,
|
|
440
|
+
data_source_schemas: filteredDataSourceSchemas,
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const createTestCase =
|
|
447
|
+
(opts: { skip?: true; only?: true }) =>
|
|
448
|
+
(
|
|
449
|
+
name: string,
|
|
450
|
+
hcl: string,
|
|
451
|
+
providers: ProviderDefinition[],
|
|
452
|
+
shouldSnapshot: Snapshot,
|
|
453
|
+
shouldSynth: Synth,
|
|
454
|
+
schemaFilter?: SchemaFilter,
|
|
455
|
+
) => {
|
|
456
|
+
if (opts.skip) {
|
|
457
|
+
describe.skip(name, () => {});
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function runConvert(language: string) {
|
|
462
|
+
let { providerSchema } = await getProviderSchema(providers);
|
|
463
|
+
if (schemaFilter) {
|
|
464
|
+
// TODO: Re-enable once we can trick Terraform CLI Checksums
|
|
465
|
+
providerSchema = filterSchema(providerSchema, undefined);
|
|
466
|
+
}
|
|
467
|
+
return await convert(hcl, {
|
|
468
|
+
language: language as any,
|
|
469
|
+
providerSchema: providerSchema || {
|
|
470
|
+
format_version: "0.1",
|
|
471
|
+
},
|
|
472
|
+
codeContainer: "cdktn.TerraformStack",
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const testBody = () => {
|
|
477
|
+
describe("snapshot", () => {
|
|
478
|
+
it.each(
|
|
479
|
+
shouldSnapshot === Snapshot.yes_all_languages
|
|
480
|
+
? ["typescript", "python", "csharp", "java", "go"]
|
|
481
|
+
: ["typescript"],
|
|
482
|
+
)(
|
|
483
|
+
"%s",
|
|
484
|
+
async (language) => {
|
|
485
|
+
const projectDir = await getProjectDirectory(
|
|
486
|
+
// We need the typescript project directory to start the convert so JSII has the right types
|
|
487
|
+
"typescript",
|
|
488
|
+
providers,
|
|
489
|
+
);
|
|
490
|
+
process.chdir(projectDir); // JSII rosetta needs to be run in the project directory with bindings included
|
|
491
|
+
const convertResult = await runConvert(language);
|
|
492
|
+
expect(convertResult.all).toMatchSnapshot();
|
|
493
|
+
},
|
|
494
|
+
500_000,
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (
|
|
499
|
+
includeSynthTests &&
|
|
500
|
+
[Synth.yes_all_languages, Synth.yes].includes(shouldSynth)
|
|
501
|
+
) {
|
|
502
|
+
describe("synth", () => {
|
|
503
|
+
it.each(
|
|
504
|
+
shouldSynth === Synth.yes_all_languages
|
|
505
|
+
? ["typescript", "python", "csharp"]
|
|
506
|
+
: ["typescript"],
|
|
507
|
+
)(
|
|
508
|
+
"%s",
|
|
509
|
+
async (language) => {
|
|
510
|
+
const projectDir = await getProjectDirectory(
|
|
511
|
+
"typescript",
|
|
512
|
+
providers,
|
|
513
|
+
);
|
|
514
|
+
process.chdir(projectDir); // JSII rosetta needs to be run in the project directory with bindings included
|
|
515
|
+
const convertResult = await runConvert(language);
|
|
516
|
+
await synthForLanguage(
|
|
517
|
+
language,
|
|
518
|
+
name,
|
|
519
|
+
convertResult,
|
|
520
|
+
providers,
|
|
521
|
+
projectDir,
|
|
522
|
+
);
|
|
523
|
+
},
|
|
524
|
+
500_000,
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
} else {
|
|
528
|
+
describe.skip("synth", () => {});
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
if (opts.only) {
|
|
533
|
+
describe.only(name, testBody);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
describe(name, testBody);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
export const testCase = {
|
|
540
|
+
test: createTestCase({}),
|
|
541
|
+
skip: createTestCase({ skip: true }),
|
|
542
|
+
only: createTestCase({ only: true }),
|
|
543
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a per-test-file temp-dir helper.
|
|
9
|
+
*
|
|
10
|
+
* Returns a `tmp(prefix)` function that creates a uniquely-named directory
|
|
11
|
+
* under `os.tmpdir()` and tracks it. An `afterAll` hook is registered on
|
|
12
|
+
* the calling test file's scope; when the file finishes running, every
|
|
13
|
+
* tracked directory is removed (recursively, force).
|
|
14
|
+
*/
|
|
15
|
+
export function createTmpHelper(): (prefix: string) => string {
|
|
16
|
+
const dirs: string[] = [];
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
for (const d of dirs) fs.rmSync(d, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
return (prefix: string) => {
|
|
21
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
22
|
+
dirs.push(d);
|
|
23
|
+
return d;
|
|
24
|
+
};
|
|
25
|
+
}
|