@cdktn/provider-generator 0.24.0-pre.9 → 0.24.0-pre.91
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/.spec.swcrc +22 -0
- package/LICENSE +355 -0
- package/README.md +1 -1
- package/build/__tests__/edge-provider-schema/builder.d.ts +9 -3
- package/build/__tests__/edge-provider-schema/builder.js +6 -3
- package/build/__tests__/edge-provider-schema/cli.js +8 -3
- package/build/__tests__/edge-provider-schema/index.js +119 -1
- package/build/__tests__/provider.test.js +4 -1
- package/build/get/__tests__/generator/ephemeral-resources.test.d.ts +2 -0
- package/build/get/__tests__/generator/ephemeral-resources.test.js +49 -0
- package/build/get/__tests__/generator/module-generator.test.js +12 -12
- package/build/get/__tests__/generator/provider-functions.test.d.ts +2 -0
- package/build/get/__tests__/generator/provider-functions.test.js +176 -0
- package/build/get/__tests__/generator/provider-source-matching.test.d.ts +2 -0
- package/build/get/__tests__/generator/provider-source-matching.test.js +45 -0
- package/build/get/__tests__/generator/provider.test.js +9 -1
- package/build/get/__tests__/generator/supported-stored-classes.test.d.ts +2 -0
- package/build/get/__tests__/generator/supported-stored-classes.test.js +137 -0
- package/build/get/__tests__/generator/types.test.js +44 -1
- package/build/get/__tests__/generator/write-only.test.d.ts +2 -0
- package/build/get/__tests__/generator/write-only.test.js +65 -0
- package/build/get/__tests__/target-versions.test.d.ts +2 -0
- package/build/get/__tests__/target-versions.test.js +171 -0
- package/build/get/constructs-maker.d.ts +8 -1
- package/build/get/constructs-maker.js +46 -10
- package/build/get/generator/emitter/attributes-emitter.d.ts +13 -2
- package/build/get/generator/emitter/attributes-emitter.js +71 -5
- package/build/get/generator/emitter/index.d.ts +1 -0
- package/build/get/generator/emitter/index.js +2 -1
- package/build/get/generator/emitter/provider-functions-emitter.d.ts +11 -0
- package/build/get/generator/emitter/provider-functions-emitter.js +94 -0
- package/build/get/generator/emitter/resource-emitter.d.ts +5 -1
- package/build/get/generator/emitter/resource-emitter.js +83 -7
- package/build/get/generator/emitter/struct-emitter.js +10 -10
- package/build/get/generator/models/attribute-model.d.ts +4 -0
- package/build/get/generator/models/attribute-model.js +50 -4
- package/build/get/generator/models/attribute-type-model.d.ts +24 -0
- package/build/get/generator/models/attribute-type-model.js +91 -5
- package/build/get/generator/models/index.d.ts +1 -0
- package/build/get/generator/models/index.js +2 -1
- package/build/get/generator/models/provider-function-model.d.ts +102 -0
- package/build/get/generator/models/provider-function-model.js +667 -0
- package/build/get/generator/models/resource-model.d.ts +3 -0
- package/build/get/generator/models/resource-model.js +13 -4
- package/build/get/generator/models/struct.d.ts +2 -0
- package/build/get/generator/models/struct.js +6 -2
- package/build/get/generator/models/supported-stored-classes.d.ts +5 -0
- package/build/get/generator/models/supported-stored-classes.js +86 -0
- package/build/get/generator/module-generator.js +3 -3
- package/build/get/generator/provider-generator.d.ts +2 -0
- package/build/get/generator/provider-generator.js +41 -6
- package/build/get/generator/resource-parser.js +3 -1
- package/jest.config.js +16 -9
- package/package.json +28 -27
- package/package.sh +1 -1
- package/src/__tests__/__snapshots__/edge-provider-schema.test.ts.snap +4 -0
- package/src/__tests__/__snapshots__/provider.test.ts.snap +2828 -2828
- package/src/__tests__/edge-provider-schema/builder.ts +201 -0
- package/src/__tests__/edge-provider-schema/cli.ts +51 -0
- package/src/__tests__/edge-provider-schema/index.ts +284 -0
- package/src/__tests__/edge-provider-schema.test.ts +24 -0
- package/src/__tests__/provider.test.ts +183 -0
- package/src/get/__tests__/constructs-maker.test.ts +118 -0
- package/src/get/__tests__/generator/__snapshots__/complex-computed-types.test.ts.snap +5 -5
- package/src/get/__tests__/generator/__snapshots__/ephemeral-resources.test.ts.snap +524 -0
- package/src/get/__tests__/generator/__snapshots__/export-sharding.test.ts.snap +3310 -3310
- package/src/get/__tests__/generator/__snapshots__/module-generator.test.ts.snap +355 -355
- package/src/get/__tests__/generator/__snapshots__/nested-types.test.ts.snap +8 -8
- package/src/get/__tests__/generator/__snapshots__/provider-functions.test.ts.snap +444 -0
- package/src/get/__tests__/generator/__snapshots__/provider.test.ts.snap +8 -8
- package/src/get/__tests__/generator/__snapshots__/resource-types.test.ts.snap +126 -126
- package/src/get/__tests__/generator/__snapshots__/skipped-attributes.test.ts.snap +17 -17
- package/src/get/__tests__/generator/__snapshots__/types.test.ts.snap +201 -52
- package/src/get/__tests__/generator/__snapshots__/write-only.test.ts.snap +262 -0
- package/src/get/__tests__/generator/complex-computed-types.test.ts +28 -0
- package/src/get/__tests__/generator/deep-nested-attributes.test.ts +56 -0
- package/src/get/__tests__/generator/description-escaping.test.ts +84 -0
- package/src/get/__tests__/generator/empty-provider-resources.test.ts +26 -0
- package/src/get/__tests__/generator/ephemeral-resources.test.ts +46 -0
- package/src/get/__tests__/generator/export-sharding.test.ts +169 -0
- package/src/get/__tests__/generator/fixtures/deeply-nested-collections.test.fixture.json +129 -0
- package/src/get/__tests__/generator/fixtures/ephemeral-resources.test.fixture.json +160 -0
- package/src/get/__tests__/generator/fixtures/provider-functions-collision.test.fixture.json +31 -0
- package/src/get/__tests__/generator/fixtures/provider-functions-synthetic.test.fixture.json +192 -0
- package/src/get/__tests__/generator/fixtures/provider-functions.test.fixture.json +170 -0
- package/src/get/__tests__/generator/fixtures/write-only.test.fixture.json +58 -0
- package/src/get/__tests__/generator/import-style.test.ts +129 -0
- package/src/get/__tests__/generator/module-generator.test.ts +528 -0
- package/src/get/__tests__/generator/nested-types.test.ts +54 -0
- package/src/get/__tests__/generator/provider-functions.test.ts +262 -0
- package/src/get/__tests__/generator/provider-source-matching.test.ts +84 -0
- package/src/get/__tests__/generator/provider.test.ts +59 -0
- package/src/get/__tests__/generator/resource-types.test.ts +118 -0
- package/src/get/__tests__/generator/skipped-attributes.test.ts +72 -0
- package/src/get/__tests__/generator/supported-stored-classes.test.ts +147 -0
- package/src/get/__tests__/generator/types.test.ts +686 -0
- package/src/get/__tests__/generator/versions-file.test.ts +72 -0
- package/src/get/__tests__/generator/write-only.test.ts +71 -0
- package/src/get/__tests__/target-versions.test.ts +206 -0
- package/src/get/__tests__/util.ts +75 -0
- package/src/get/constructs-maker.ts +885 -0
- package/src/get/generator/custom-defaults.ts +493 -0
- package/src/get/generator/emitter/attributes-emitter.ts +316 -0
- package/src/get/generator/emitter/index.ts +6 -0
- package/src/get/generator/emitter/provider-functions-emitter.ts +117 -0
- package/src/get/generator/emitter/resource-emitter.ts +330 -0
- package/src/get/generator/emitter/struct-emitter.ts +683 -0
- package/src/get/generator/loop-detection.ts +81 -0
- package/src/get/generator/models/attribute-model.ts +273 -0
- package/src/get/generator/models/attribute-type-model.ts +560 -0
- package/src/get/generator/models/index.ts +8 -0
- package/src/get/generator/models/provider-function-model.ts +872 -0
- package/src/get/generator/models/resource-model.ts +183 -0
- package/src/get/generator/models/scope.ts +54 -0
- package/src/get/generator/models/struct.ts +124 -0
- package/src/get/generator/models/supported-stored-classes.ts +88 -0
- package/src/get/generator/module-generator.ts +234 -0
- package/src/get/generator/provider-generator.ts +428 -0
- package/src/get/generator/resource-parser.ts +764 -0
- package/src/get/generator/sanitized-comments.ts +49 -0
- package/src/get/generator/skipped-attributes.ts +27 -0
- package/src/index.ts +40 -0
- package/src/util.ts +26 -0
- package/tsconfig.json +1 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { toSnakeCase } from "codemaker";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { FQPN, parseFQPN, ProviderName } from "@cdktn/provider-schema";
|
|
6
|
+
import { AttributeModel } from "./attribute-model";
|
|
7
|
+
import { Struct, ConfigStruct } from "./struct";
|
|
8
|
+
import { Schema } from "@cdktn/commons";
|
|
9
|
+
import { ProviderFunctionsModel } from "./provider-function-model";
|
|
10
|
+
|
|
11
|
+
// Limit is 1200 to prevent stack size error.
|
|
12
|
+
// Could increase now that calculation is more accurate;
|
|
13
|
+
// however, probably better to have smaller
|
|
14
|
+
export const STRUCT_SHARDING_THRESHOLD = 400;
|
|
15
|
+
|
|
16
|
+
interface ResourceModelOptions {
|
|
17
|
+
terraformType: string;
|
|
18
|
+
className: string;
|
|
19
|
+
baseName: string;
|
|
20
|
+
fileName: string;
|
|
21
|
+
filePath: string;
|
|
22
|
+
attributes: AttributeModel[];
|
|
23
|
+
structs: Struct[];
|
|
24
|
+
fqpn: FQPN;
|
|
25
|
+
schema: Schema;
|
|
26
|
+
terraformSchemaType: string;
|
|
27
|
+
configStructName: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type DefinitionPath = string;
|
|
31
|
+
export class ResourceModel {
|
|
32
|
+
public className: string;
|
|
33
|
+
public filePath: string;
|
|
34
|
+
public terraformType: string;
|
|
35
|
+
public baseName: string;
|
|
36
|
+
public provider: ProviderName;
|
|
37
|
+
public fqpn: FQPN;
|
|
38
|
+
public providerVersionConstraint?: string;
|
|
39
|
+
public providerVersion?: string;
|
|
40
|
+
public terraformProviderSource?: string;
|
|
41
|
+
/*
|
|
42
|
+
* usually same as provider, but can be overridden if user picks a different name
|
|
43
|
+
* to be able to use two providers with the same name
|
|
44
|
+
*/
|
|
45
|
+
public terraformProviderName: string;
|
|
46
|
+
/*
|
|
47
|
+
* Only set (by provider-generator.ts) when isProvider is true and the
|
|
48
|
+
* provider schema declares provider-defined functions - drives whether
|
|
49
|
+
* ResourceEmitter emits the memoized `functions` getter and its
|
|
50
|
+
* cross-directory import of the sibling provider-functions/index.ts file.
|
|
51
|
+
*/
|
|
52
|
+
public providerFunctionsModel?: ProviderFunctionsModel;
|
|
53
|
+
public fileName: string;
|
|
54
|
+
public attributes: AttributeModel[];
|
|
55
|
+
public schema: Schema;
|
|
56
|
+
private _structs: Struct[];
|
|
57
|
+
private dependencies: string[];
|
|
58
|
+
private terraformSchemaType: string;
|
|
59
|
+
private configStructName: string;
|
|
60
|
+
public readonly structsRequireSharding: boolean;
|
|
61
|
+
public recursiveAttributePaths: { [usagePath: string]: DefinitionPath } = {};
|
|
62
|
+
|
|
63
|
+
constructor(options: ResourceModelOptions) {
|
|
64
|
+
this.className = options.className;
|
|
65
|
+
this.filePath = options.filePath;
|
|
66
|
+
this.terraformType = options.terraformType;
|
|
67
|
+
this.baseName = options.baseName;
|
|
68
|
+
this.attributes = options.attributes;
|
|
69
|
+
this.schema = options.schema;
|
|
70
|
+
this.fqpn = options.fqpn;
|
|
71
|
+
this.provider = parseFQPN(options.fqpn).name;
|
|
72
|
+
this.terraformProviderName = this.provider;
|
|
73
|
+
this.fileName = options.fileName;
|
|
74
|
+
this._structs = options.structs;
|
|
75
|
+
this.terraformSchemaType = options.terraformSchemaType;
|
|
76
|
+
this.configStructName = options.configStructName;
|
|
77
|
+
this.dependencies = [
|
|
78
|
+
`import { Construct } from 'constructs';`,
|
|
79
|
+
`import * as cdktn from 'cdktn';`,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
this.structsRequireSharding =
|
|
83
|
+
this._structs.reduce((partialSum, s) => partialSum + s.exportCount, 0) >
|
|
84
|
+
STRUCT_SHARDING_THRESHOLD;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public get structs(): Struct[] {
|
|
88
|
+
return [this.configStruct, ...this._structs];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public get configStruct() {
|
|
92
|
+
return new ConfigStruct(
|
|
93
|
+
this.configStructName,
|
|
94
|
+
this.attributes,
|
|
95
|
+
this.isEphemeralResource ? "TerraformEphemeralMetaArguments" : undefined,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public get synthesizableAttributes(): AttributeModel[] {
|
|
100
|
+
return this.configStruct.assignableAttributes;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public get importStatements(): string[] {
|
|
104
|
+
return this.dependencies;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public get schemaAsJson(): string {
|
|
108
|
+
return this.escapeSchema(JSON.stringify(this.schema, undefined, 2));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public get linkToDocs(): string {
|
|
112
|
+
const { hostname, namespace, name } = parseFQPN(this.fqpn);
|
|
113
|
+
const version = this.providerVersion || "latest";
|
|
114
|
+
const base = `https://${hostname}/providers/${namespace}/${name}/${version}/docs`;
|
|
115
|
+
if (this.isProvider) return base;
|
|
116
|
+
if (this.isDataSource)
|
|
117
|
+
return `${base}/data-sources/${this.terraformDocName}`;
|
|
118
|
+
if (this.isEphemeralResource)
|
|
119
|
+
return `${base}/ephemeral-resources/${this.terraformDocName}`;
|
|
120
|
+
return `${base}/resources/${this.terraformDocName}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public get isProvider(): boolean {
|
|
124
|
+
return this.terraformSchemaType === "provider";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public get isDataSource(): boolean {
|
|
128
|
+
return this.terraformSchemaType === "data_source";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public get isEphemeralResource(): boolean {
|
|
132
|
+
return this.terraformSchemaType === "ephemeral_resource";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public get parentClassName(): string {
|
|
136
|
+
return this.isProvider
|
|
137
|
+
? "TerraformProvider"
|
|
138
|
+
: this.isDataSource
|
|
139
|
+
? "TerraformDataSource"
|
|
140
|
+
: this.isEphemeralResource
|
|
141
|
+
? "TerraformEphemeralResource"
|
|
142
|
+
: "TerraformResource";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public get terraformResourceType(): string {
|
|
146
|
+
return this.isProvider
|
|
147
|
+
? this.terraformProviderName
|
|
148
|
+
: this.isDataSource
|
|
149
|
+
? this.terraformType.replace(/^data_/, "")
|
|
150
|
+
: this.isEphemeralResource
|
|
151
|
+
? this.terraformType.replace(/^ephemeral_/, "")
|
|
152
|
+
: this.terraformType;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public get terraformDocName(): string {
|
|
156
|
+
return toSnakeCase(
|
|
157
|
+
this.terraformResourceType.replace(
|
|
158
|
+
new RegExp(`^${this.provider}_`, "i"),
|
|
159
|
+
"",
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public get referencedTypes(): string[] {
|
|
165
|
+
return this.configStruct.referencedTypes;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public get structsFolderName(): string {
|
|
169
|
+
return `${path.basename(this.fileName, ".ts")}-structs`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public get structsFolderPath(): string {
|
|
173
|
+
return path.join(this.namespaceFolderPath, this.structsFolderName);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public get namespaceFolderPath(): string {
|
|
177
|
+
return this.filePath.split("/").slice(0, -1).join("/");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private escapeSchema(schema: string): string {
|
|
181
|
+
return schema.replace(/(\*\/)/gi, `*\\/`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
export interface ScopeProps {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly isProvider: boolean;
|
|
6
|
+
readonly isComputed?: boolean;
|
|
7
|
+
readonly isOptional?: boolean;
|
|
8
|
+
readonly isRequired?: boolean;
|
|
9
|
+
readonly inBlockType?: boolean;
|
|
10
|
+
readonly isNestedType?: boolean;
|
|
11
|
+
readonly parent?: Scope;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Scope {
|
|
15
|
+
public readonly name: string;
|
|
16
|
+
public readonly provider?: string;
|
|
17
|
+
public readonly parent?: Scope;
|
|
18
|
+
public readonly isProvider: boolean;
|
|
19
|
+
public isComputed: boolean;
|
|
20
|
+
public isOptional: boolean;
|
|
21
|
+
public isRequired: boolean;
|
|
22
|
+
public inBlockType: boolean;
|
|
23
|
+
public isNestedType: boolean;
|
|
24
|
+
|
|
25
|
+
constructor(props: ScopeProps) {
|
|
26
|
+
this.name = props.name;
|
|
27
|
+
this.parent = props.parent;
|
|
28
|
+
this.isProvider = props.isProvider;
|
|
29
|
+
this.isComputed = props.isComputed ?? false;
|
|
30
|
+
this.isOptional = props.isOptional ?? true;
|
|
31
|
+
this.isRequired = props.isRequired ?? false;
|
|
32
|
+
this.inBlockType = props.inBlockType ?? false;
|
|
33
|
+
this.isNestedType = props.isNestedType ?? false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public fullName(attributeName: string) {
|
|
37
|
+
return `${this.baseName}.${attributeName}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public get baseName() {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
42
|
+
let scope: Scope | undefined = this;
|
|
43
|
+
let name = "";
|
|
44
|
+
while (scope) {
|
|
45
|
+
if (name === "") {
|
|
46
|
+
name = scope.name;
|
|
47
|
+
} else {
|
|
48
|
+
name = `${scope.name}.${name}`;
|
|
49
|
+
}
|
|
50
|
+
scope = scope.parent;
|
|
51
|
+
}
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { AttributeModel } from "./attribute-model";
|
|
4
|
+
import { downcaseFirst } from "../../../util";
|
|
5
|
+
export class Struct {
|
|
6
|
+
constructor(
|
|
7
|
+
public readonly name: string,
|
|
8
|
+
public readonly attributes: AttributeModel[],
|
|
9
|
+
public readonly isClass = false,
|
|
10
|
+
public readonly isAnonymous = false,
|
|
11
|
+
public isSingleItem = false,
|
|
12
|
+
public readonly nestingMode: string = "",
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
public get assignableAttributes(): AttributeModel[] {
|
|
16
|
+
return this.attributes.filter((attribute) => attribute.isAssignable);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public get optionalAttributes(): AttributeModel[] {
|
|
20
|
+
return this.attributes.filter((attribute) => attribute.optional);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public get allOptional(): boolean {
|
|
24
|
+
return (
|
|
25
|
+
this.attributes.filter(
|
|
26
|
+
(attribute) => !attribute.optional && !attribute.computed,
|
|
27
|
+
).length == 0
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public get attributeType() {
|
|
32
|
+
return `${this.name}${this.allOptional ? " = {}" : ""}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public get assignable() {
|
|
36
|
+
return !this.isClass || this.assignableAttributes.length > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public get extends(): string {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public get mapperName(): string {
|
|
44
|
+
return `${downcaseFirst(this.name)}ToTerraform`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public get hclMapperName(): string {
|
|
48
|
+
return `${downcaseFirst(this.name)}ToHclTerraform`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public get outputReferenceName(): string {
|
|
52
|
+
return `${this.name}OutputReference`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public get listName(): string {
|
|
56
|
+
return `${this.name}List`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public get mapName(): string {
|
|
60
|
+
return `${this.name}Map`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public get mapListName(): string {
|
|
64
|
+
return `${this.name}MapList`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public get listMapName(): string {
|
|
68
|
+
return `${this.name}ListMap`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public get listListName(): string {
|
|
72
|
+
return `${this.name}ListList`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public get isProvider(): boolean {
|
|
76
|
+
return this.attributes.some((att) => att.isProvider);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public get referencedTypes(): string[] {
|
|
80
|
+
const types: string[] = [];
|
|
81
|
+
|
|
82
|
+
this.attributes.forEach((att) => {
|
|
83
|
+
const attReferences = att.getReferencedTypes(false); // This may be a config struct, but still need the references in this context
|
|
84
|
+
if (attReferences) {
|
|
85
|
+
types.push(...attReferences);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return types;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public get exportCount(): number {
|
|
93
|
+
let count = 1; // self
|
|
94
|
+
count += 1; // toTerraform function
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
this.nestingMode === "list" ||
|
|
98
|
+
this.nestingMode === "set" ||
|
|
99
|
+
this.nestingMode === "map"
|
|
100
|
+
) {
|
|
101
|
+
count += 1; // output reference
|
|
102
|
+
|
|
103
|
+
if (!this.isSingleItem) {
|
|
104
|
+
count += 1; // complex collection
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return count;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class ConfigStruct extends Struct {
|
|
113
|
+
constructor(
|
|
114
|
+
name: string,
|
|
115
|
+
attributes: AttributeModel[],
|
|
116
|
+
private readonly extendsClass: string = "TerraformMetaArguments",
|
|
117
|
+
) {
|
|
118
|
+
super(name, attributes);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public get extends(): string {
|
|
122
|
+
return ` extends cdktn.${this.extendsClass}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { uppercaseFirst } from "../../../util";
|
|
4
|
+
|
|
5
|
+
// The core cdktn package (packages/cdktn/src/complex-computed-list.ts) only hand-writes
|
|
6
|
+
// this fixed set of wrapper classes for computed, non-complex collection attributes.
|
|
7
|
+
// Arbitrarily deep nestings of list/set/map (e.g. map(map(list(string)))) compose a
|
|
8
|
+
// storedClassType (e.g. "stringListMapMap") that has no corresponding class. Rather than
|
|
9
|
+
// falling back straight to a plain, untyped getter, resolveStoredClassName below first
|
|
10
|
+
// looks for the nearest typed "Any"-wrapper that still exists in this set, collapsing the
|
|
11
|
+
// unsupported inner shape into an untyped boundary while preserving typed navigation for
|
|
12
|
+
// the outer layers (see https://github.com/open-constructs/cdk-terrain/issues/11).
|
|
13
|
+
const SUPPORTED_STORED_CLASSES = new Set([
|
|
14
|
+
"StringMap",
|
|
15
|
+
"NumberMap",
|
|
16
|
+
"BooleanMap",
|
|
17
|
+
"AnyMap",
|
|
18
|
+
"StringMapMap",
|
|
19
|
+
"NumberMapMap",
|
|
20
|
+
"BooleanMapMap",
|
|
21
|
+
"AnyMapMap",
|
|
22
|
+
"BooleanList",
|
|
23
|
+
"StringListList",
|
|
24
|
+
"NumberListList",
|
|
25
|
+
"BooleanListList",
|
|
26
|
+
"AnyListList",
|
|
27
|
+
"StringMapList",
|
|
28
|
+
"NumberMapList",
|
|
29
|
+
"BooleanMapList",
|
|
30
|
+
"AnyMapList",
|
|
31
|
+
"StringListMap",
|
|
32
|
+
"NumberListMap",
|
|
33
|
+
"BooleanListMap",
|
|
34
|
+
"AnyListMap",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Resolves the composed stored class name to use for a collection attribute (list/set/map)
|
|
38
|
+
// whose element is itself a non-complex collection (or simple type).
|
|
39
|
+
//
|
|
40
|
+
// `ownStoredClassType` is this type's own composed storedClassType (e.g. "stringListMapMap"
|
|
41
|
+
// for map(map(list(string)))) and `elementTypeModelType` is the element's typeModelType
|
|
42
|
+
// ("list" | "set" | "map" | "simple" | ...).
|
|
43
|
+
//
|
|
44
|
+
// Resolution order:
|
|
45
|
+
// 1. The own composed name (e.g. "StringListMapMap") - used as-is when it refers to a
|
|
46
|
+
// class that is hand-written in the core package (not collapsed).
|
|
47
|
+
// 2. Otherwise, the nearest typed "Any"-wrapper that still encodes the outer two layers
|
|
48
|
+
// (this type's own layer and its element's layer), e.g. "AnyMapMap" - collapsing the
|
|
49
|
+
// unsupported inner shape into an untyped boundary while preserving typed navigation
|
|
50
|
+
// for the outer layers.
|
|
51
|
+
// 3. Otherwise (defensively; every depth should resolve via 1 or 2), undefined - the
|
|
52
|
+
// caller must fall back to a plain, untyped getter.
|
|
53
|
+
export function resolveStoredClassName(
|
|
54
|
+
ownStoredClassType: string,
|
|
55
|
+
elementTypeModelType: string,
|
|
56
|
+
): { name: string; collapsed: boolean } | undefined {
|
|
57
|
+
const candidate1 = uppercaseFirst(ownStoredClassType);
|
|
58
|
+
if (SUPPORTED_STORED_CLASSES.has(candidate1)) {
|
|
59
|
+
return { name: candidate1, collapsed: false };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let ownLayer: "List" | "Map" | undefined;
|
|
63
|
+
if (ownStoredClassType.endsWith("List")) {
|
|
64
|
+
ownLayer = "List";
|
|
65
|
+
} else if (ownStoredClassType.endsWith("Map")) {
|
|
66
|
+
ownLayer = "Map";
|
|
67
|
+
}
|
|
68
|
+
if (!ownLayer) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let innerLayer: "List" | "Map" | undefined;
|
|
73
|
+
if (elementTypeModelType === "list" || elementTypeModelType === "set") {
|
|
74
|
+
innerLayer = "List";
|
|
75
|
+
} else if (elementTypeModelType === "map") {
|
|
76
|
+
innerLayer = "Map";
|
|
77
|
+
}
|
|
78
|
+
if (!innerLayer) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const candidate2 = `Any${innerLayer}${ownLayer}`;
|
|
83
|
+
if (SUPPORTED_STORED_CLASSES.has(candidate2)) {
|
|
84
|
+
return { name: candidate2, collapsed: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Copyright (c) HashiCorp, Inc
|
|
2
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
3
|
+
import { CodeMaker, toCamelCase } from "codemaker";
|
|
4
|
+
import { AttributeModel } from "./models";
|
|
5
|
+
import { sanitizedComment } from "./sanitized-comments";
|
|
6
|
+
import { ConstructsMakerModuleTarget } from "@cdktn/commons";
|
|
7
|
+
|
|
8
|
+
export class ModuleGenerator {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly code: CodeMaker,
|
|
11
|
+
private readonly targets: ConstructsMakerModuleTarget[],
|
|
12
|
+
) {
|
|
13
|
+
this.code.indentation = 2;
|
|
14
|
+
|
|
15
|
+
for (const target of this.targets) {
|
|
16
|
+
this.emitSubmodule(target);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private emitSubmodule(target: ConstructsMakerModuleTarget) {
|
|
21
|
+
const spec = target.spec;
|
|
22
|
+
|
|
23
|
+
if (!spec) {
|
|
24
|
+
throw new Error(`missing spec for ${target.fqn}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.code.openFile(target.fileName);
|
|
28
|
+
|
|
29
|
+
this.code.line(`// generated by cdktn get`);
|
|
30
|
+
this.code.line(`// ${target.source}`);
|
|
31
|
+
|
|
32
|
+
this.code.line(
|
|
33
|
+
`import { TerraformModule, type TerraformModuleUserConfig } from 'cdktn';`,
|
|
34
|
+
);
|
|
35
|
+
this.code.line(`import { Construct } from 'constructs';`);
|
|
36
|
+
|
|
37
|
+
const baseName = this.code.toPascalCase(target.name.replace(/[-/.]/g, "_"));
|
|
38
|
+
const configType = `${baseName}Config`;
|
|
39
|
+
|
|
40
|
+
this.code.openBlock(
|
|
41
|
+
`export interface ${configType} extends TerraformModuleUserConfig`,
|
|
42
|
+
);
|
|
43
|
+
for (const input of spec.inputs) {
|
|
44
|
+
const optional = input.required && input.default === undefined ? "" : "?";
|
|
45
|
+
|
|
46
|
+
const comment = sanitizedComment(this.code);
|
|
47
|
+
if (input.description) {
|
|
48
|
+
comment.line(input.description);
|
|
49
|
+
}
|
|
50
|
+
if (input.default) {
|
|
51
|
+
comment.line(input.default);
|
|
52
|
+
}
|
|
53
|
+
if (input.type.includes("map(")) {
|
|
54
|
+
comment.line(
|
|
55
|
+
`The property type contains a map, they have special handling, please see {@link cdk.tf/module-map-inputs the docs}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
comment.end();
|
|
59
|
+
|
|
60
|
+
this.code.line(
|
|
61
|
+
`readonly ${AttributeModel.escapeName(
|
|
62
|
+
toCamelCase(input.name),
|
|
63
|
+
)}${optional}: ${parseType(input.type)};`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
this.code.closeBlock();
|
|
67
|
+
|
|
68
|
+
// Add a link to the Terraform Registry if it is sourced from there
|
|
69
|
+
// https://developer.hashicorp.com/terraform/language/modules/sources
|
|
70
|
+
// Registry modules are referred to as <NAMESPACE>/<NAME>/<PROVIDER>
|
|
71
|
+
// Other sources contain dots, e.g.
|
|
72
|
+
// app.terraform.io/example-corp/k8s-cluster/azurerm (private registries)
|
|
73
|
+
// github.com/hashicorp/example (Github)
|
|
74
|
+
// git@github.com:hashicorp/example.git
|
|
75
|
+
// bitbucket.org/hashicorp/terraform-consul-aws (Bitbucket)
|
|
76
|
+
// ... and more
|
|
77
|
+
// ../module and ./module (local paths)
|
|
78
|
+
const isNonRegistryModule = target.source.includes(".");
|
|
79
|
+
let registryPath;
|
|
80
|
+
// Submodules also exist (e.g. terraform-aws-modules/vpc/aws//modules/vpc-endpoints)
|
|
81
|
+
// And linking directly to them in the Registry requires including the version
|
|
82
|
+
// like e.g. https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
|
|
83
|
+
if (target.source.includes("//")) {
|
|
84
|
+
registryPath = target.source.replace(
|
|
85
|
+
"//modules",
|
|
86
|
+
`/${target.version || "latest"}/submodules`,
|
|
87
|
+
);
|
|
88
|
+
// terraform-aws-modules/vpc/aws//modules/vpc-endpoints
|
|
89
|
+
// ->
|
|
90
|
+
// terraform-aws-modules/vpc/aws/latest/submodules/vpc-endpoints
|
|
91
|
+
} else {
|
|
92
|
+
// not submodule specified, just append the version
|
|
93
|
+
registryPath = `${target.source}/${target.version || "latest"}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const comment = sanitizedComment(this.code);
|
|
97
|
+
comment.line(`Defines an ${baseName} based on a Terraform module`);
|
|
98
|
+
comment.line(``);
|
|
99
|
+
comment.line(
|
|
100
|
+
isNonRegistryModule
|
|
101
|
+
? `Source at ${target.source}`
|
|
102
|
+
: `Docs at Terraform Registry: {@link https://registry.terraform.io/modules/${registryPath} ${target.source}}`,
|
|
103
|
+
);
|
|
104
|
+
comment.end();
|
|
105
|
+
this.code.openBlock(`export class ${baseName} extends TerraformModule`);
|
|
106
|
+
|
|
107
|
+
this.code.line(`private readonly inputs: { [name: string]: any } = { };`);
|
|
108
|
+
|
|
109
|
+
const allOptional = spec.inputs.find((x) => x.required) ? "" : " = {}";
|
|
110
|
+
|
|
111
|
+
this.code.open(
|
|
112
|
+
`public constructor(scope: Construct, id: string, config: ${configType}${allOptional}) {`,
|
|
113
|
+
);
|
|
114
|
+
this.code.open(`super(scope, id, {`);
|
|
115
|
+
this.code.line("...config,");
|
|
116
|
+
this.code.line(`source: '${target.source}',`);
|
|
117
|
+
if (target.version) {
|
|
118
|
+
this.code.line(`version: '${target.version}',`);
|
|
119
|
+
}
|
|
120
|
+
this.code.close(`});`);
|
|
121
|
+
|
|
122
|
+
for (const input of spec.inputs) {
|
|
123
|
+
const inputName = AttributeModel.escapeName(toCamelCase(input.name));
|
|
124
|
+
this.code.line(`this.${inputName} = config.${inputName};`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.code.close(`}`); // ctor
|
|
128
|
+
|
|
129
|
+
for (const input of spec.inputs) {
|
|
130
|
+
const inputName = AttributeModel.escapeName(toCamelCase(input.name));
|
|
131
|
+
const inputType =
|
|
132
|
+
parseType(input.type) +
|
|
133
|
+
(input.required && input.default === undefined ? "" : " | undefined");
|
|
134
|
+
this.code.openBlock(`public get ${inputName}(): ${inputType}`);
|
|
135
|
+
this.code.line(`return this.inputs['${input.name}'] as ${inputType};`);
|
|
136
|
+
this.code.closeBlock();
|
|
137
|
+
|
|
138
|
+
this.code.openBlock(`public set ${inputName}(value: ${inputType})`);
|
|
139
|
+
this.code.line(`this.inputs['${input.name}'] = value;`);
|
|
140
|
+
this.code.closeBlock();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const output of spec.outputs) {
|
|
144
|
+
const outputName = toCamelCase(output.name);
|
|
145
|
+
this.code.openBlock(`public get ${outputName}Output()`);
|
|
146
|
+
this.code.line(`return this.getString('${output.name}');`);
|
|
147
|
+
this.code.closeBlock();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.code.openBlock(`protected synthesizeAttributes()`);
|
|
151
|
+
this.code.line(`return this.inputs;`);
|
|
152
|
+
this.code.closeBlock();
|
|
153
|
+
|
|
154
|
+
this.code.openBlock(
|
|
155
|
+
`protected synthesizeHclAttributes(): { [name: string]: any }`,
|
|
156
|
+
);
|
|
157
|
+
this.code.line(`return Object.fromEntries(`);
|
|
158
|
+
this.code.line(` Object.entries(this.inputs)`);
|
|
159
|
+
this.code.line(` .filter(([, val]) => val !== undefined)`);
|
|
160
|
+
this.code.line(` .map(([key, val]) => {`);
|
|
161
|
+
this.code.line(` return [`);
|
|
162
|
+
this.code.line(` key,`);
|
|
163
|
+
this.code.line(` {`);
|
|
164
|
+
this.code.line(` value: val,`);
|
|
165
|
+
this.code.line(` type: "any",`);
|
|
166
|
+
this.code.line(` },`);
|
|
167
|
+
this.code.line(` ];`);
|
|
168
|
+
this.code.line(` })`);
|
|
169
|
+
this.code.line(`);`);
|
|
170
|
+
this.code.closeBlock();
|
|
171
|
+
|
|
172
|
+
this.code.closeBlock(); // class
|
|
173
|
+
this.code.closeFile(target.fileName);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseType(type: string) {
|
|
178
|
+
if (type === "string") {
|
|
179
|
+
return "string";
|
|
180
|
+
}
|
|
181
|
+
if (type === "number") {
|
|
182
|
+
return "number";
|
|
183
|
+
}
|
|
184
|
+
if (type === "bool") {
|
|
185
|
+
return "boolean";
|
|
186
|
+
}
|
|
187
|
+
if (type === "list") {
|
|
188
|
+
return "string[]";
|
|
189
|
+
}
|
|
190
|
+
if (type === "map") {
|
|
191
|
+
return "{ [key: string]: string }";
|
|
192
|
+
}
|
|
193
|
+
if (type === "tuple") {
|
|
194
|
+
return "string[]";
|
|
195
|
+
}
|
|
196
|
+
if (type === "any") {
|
|
197
|
+
return "any";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const complexType = parseComplexType(type);
|
|
201
|
+
if (complexType) {
|
|
202
|
+
return complexType;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
throw new Error(`unknown type ${type}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function parseComplexType(type: string): string | undefined {
|
|
209
|
+
const complex = /^(object|list|map|set|tuple)\(([\s\S]+)\)/;
|
|
210
|
+
const match = complex.exec(type);
|
|
211
|
+
if (!match) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const [, kind, innerType] = match;
|
|
216
|
+
|
|
217
|
+
if (kind === "object") {
|
|
218
|
+
return `any`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (kind === "list" || kind === "set") {
|
|
222
|
+
return `${parseType(innerType)}[]`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (kind === "tuple") {
|
|
226
|
+
return `any[]`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (kind === "map") {
|
|
230
|
+
return `{ [key: string]: ${parseType(innerType)} }`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
throw new Error(`unexpected kind ${kind}`);
|
|
234
|
+
}
|