From 8efd01b4372d6637a9a05e1660468f2232d4f43d Mon Sep 17 00:00:00 2001 From: Nikolay Matrosov Date: Sun, 27 Jul 2025 19:05:50 +0200 Subject: [PATCH 1/3] feat(lib): add StringMapMap class Signed-off-by: OrfeasZ --- .../__snapshots__/types.test.ts.snap | 91 +++++++++++++++++++ .../map-of-map-of-string.test.fixture.json | 27 ++++++ .../lib/get/__tests__/generator/types.test.ts | 25 +++++ .../generator/models/attribute-type-model.ts | 4 + packages/cdktn/lib/complex-computed-list.ts | 26 ++++++ 5 files changed, 173 insertions(+) create mode 100644 packages/@cdktn/provider-generator/lib/get/__tests__/generator/fixtures/map-of-map-of-string.test.fixture.json diff --git a/packages/@cdktn/provider-generator/lib/get/__tests__/generator/__snapshots__/types.test.ts.snap b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/__snapshots__/types.test.ts.snap index c0537d300..69d780ba5 100644 --- a/packages/@cdktn/provider-generator/lib/get/__tests__/generator/__snapshots__/types.test.ts.snap +++ b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/__snapshots__/types.test.ts.snap @@ -3967,6 +3967,97 @@ export class ListOfStringMap extends cdktn.TerraformResource { " `; +exports[`map of map of string attribute 1`] = ` +"// https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/map_of_map_of_string +// generated from terraform resource schema + +import { Construct } from 'constructs'; +import * as cdktn from 'cdktn'; + +// Configuration + +export interface MapOfMapOfStringConfig extends cdktn.TerraformMetaArguments { +} + +/** +* Represents a {@link https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/map_of_map_of_string aws_map_of_map_of_string} +*/ +export class MapOfMapOfString extends cdktn.TerraformResource { + + // ================= + // STATIC PROPERTIES + // ================= + public static readonly tfResourceType = "aws_map_of_map_of_string"; + + // ============== + // STATIC Methods + // ============== + /** + * Generates CDKTN code for importing a MapOfMapOfString resource upon running "cdktn plan " + * @param scope The scope in which to define this construct + * @param importToId The construct id used in the generated config for the MapOfMapOfString to import + * @param importFromId The id of the existing MapOfMapOfString that should be imported. Refer to the {@link https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/map_of_map_of_string#import import section} in the documentation of this resource for the id to use + * @param provider? Optional instance of the provider where the MapOfMapOfString to import is found + */ + public static generateConfigForImport(scope: Construct, importToId: string, importFromId: string, provider?: cdktn.TerraformProvider) { + return new cdktn.ImportableResource(scope, importToId, { terraformResourceType: "aws_map_of_map_of_string", importId: importFromId, provider }); + } + + // =========== + // INITIALIZER + // =========== + + /** + * Create a new {@link https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/map_of_map_of_string aws_map_of_map_of_string} Resource + * + * @param scope The scope in which to define this construct + * @param id The scoped construct ID. Must be unique amongst siblings in the same scope + * @param options MapOfMapOfStringConfig = {} + */ + public constructor(scope: Construct, id: string, config: MapOfMapOfStringConfig = {}) { + super(scope, id, { + terraformResourceType: 'aws_map_of_map_of_string', + terraformGeneratorMetadata: { + providerName: 'aws' + }, + provider: config.provider, + dependsOn: config.dependsOn, + count: config.count, + lifecycle: config.lifecycle, + provisioners: config.provisioners, + connection: config.connection, + forEach: config.forEach + }); + } + + // ========== + // ATTRIBUTES + // ========== + + // labels - computed: true, optional: false, required: false + private _labels = new cdktn.StringMapMap(this, "labels"); + public get labels() { + return this._labels; + } + + // ========= + // SYNTHESIS + // ========= + + protected synthesizeAttributes(): { [name: string]: any } { + return { + }; + } + + protected synthesizeHclAttributes(): { [name: string]: any } { + const attrs = { + }; + return attrs; + } +} +" +`; + exports[`map of string list attribute 1`] = ` "// https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/map_of_string_list // generated from terraform resource schema diff --git a/packages/@cdktn/provider-generator/lib/get/__tests__/generator/fixtures/map-of-map-of-string.test.fixture.json b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/fixtures/map-of-map-of-string.test.fixture.json new file mode 100644 index 000000000..2a2de5d54 --- /dev/null +++ b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/fixtures/map-of-map-of-string.test.fixture.json @@ -0,0 +1,27 @@ +{ + "provider_schemas": { + "registry.terraform.io/hashicorp/aws": { + "resource_schemas": { + "aws_map_of_map_of_string": { + "version": 1, + "block": { + "attributes": { + "labels": { + "type": [ + "map", + [ + "map", + "string" + ] + ], + "description": "Map of map of string.", + "computed": true + } + } + }, + "block_types": {} + } + } + } + } +} \ No newline at end of file diff --git a/packages/@cdktn/provider-generator/lib/get/__tests__/generator/types.test.ts b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/types.test.ts index 8be8e86dd..270c0c758 100644 --- a/packages/@cdktn/provider-generator/lib/get/__tests__/generator/types.test.ts +++ b/packages/@cdktn/provider-generator/lib/get/__tests__/generator/types.test.ts @@ -571,3 +571,28 @@ test("list of list of strings", async () => { ); expect(datasourceOutput).toMatchSnapshot(); }); + +test("map of map of string attribute", async () => { + const code = new CodeMaker(); + const workdir = fs.mkdtempSync( + path.join(os.tmpdir(), "map-of-map-of-string.test"), + ); + const spec = JSON.parse( + fs.readFileSync( + path.join( + __dirname, + "fixtures", + "map-of-map-of-string.test.fixture.json", + ), + "utf-8", + ), + ); + new TerraformProviderGenerator(code, spec).generateAll(); + await code.save(workdir); + + const output = fs.readFileSync( + path.join(workdir, "providers/aws/map-of-map-of-string/index.ts"), + "utf-8", + ); + expect(output).toMatchSnapshot(); +}); diff --git a/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts b/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts index 85e407189..06f071492 100644 --- a/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts +++ b/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts @@ -393,6 +393,10 @@ export class MapAttributeTypeModel implements CollectionAttributeTypeModel { } get storedClassType() { + // Special case for map of map of string + if (this.elementType.storedClassType === "StringMap") { + return "StringMapMap"; + } return `${this.elementType.storedClassType}Map`; } diff --git a/packages/cdktn/lib/complex-computed-list.ts b/packages/cdktn/lib/complex-computed-list.ts index 62eee0707..05d43f271 100644 --- a/packages/cdktn/lib/complex-computed-list.ts +++ b/packages/cdktn/lib/complex-computed-list.ts @@ -211,6 +211,32 @@ export class AnyMap extends ComplexResolvable implements ITerraformAddressable { } } +// eslint-disable-next-line jsdoc/require-jsdoc +export class StringMapMap + extends ComplexResolvable + implements ITerraformAddressable +{ + constructor( + protected terraformResource: IInterpolatingParent, + protected terraformAttribute: string, + ) { + super(terraformResource, terraformAttribute); + } + + public lookup(key: string): StringMap { + return new StringMap( + this.terraformResource, + `${this.terraformAttribute}["${key}"]`, + ); + } + + computeFqn(): string { + return Token.asString( + this.terraformResource.interpolationForAttribute(this.terraformAttribute), + ); + } +} + /** * @deprecated Going to be replaced by Array of ComplexListItem * and will be removed in the future From 49527a0375b72948366229a8a09544e14db4aa8f Mon Sep 17 00:00:00 2001 From: OrfeasZ Date: Sun, 19 Apr 2026 12:11:42 +0000 Subject: [PATCH 2/3] chore: Add string map map lookup tests --- .../__snapshots__/data-source.test.ts.snap | 64 +++++++++++++++++++ packages/cdktn/test/data-source.test.ts | 30 +++++++++ packages/cdktn/test/helper/data-source.ts | 5 ++ 3 files changed, 99 insertions(+) diff --git a/packages/cdktn/test/__snapshots__/data-source.test.ts.snap b/packages/cdktn/test/__snapshots__/data-source.test.ts.snap index dbffe504e..515fa5bd9 100644 --- a/packages/cdktn/test/__snapshots__/data-source.test.ts.snap +++ b/packages/cdktn/test/__snapshots__/data-source.test.ts.snap @@ -219,3 +219,67 @@ exports[`with string map 1`] = ` } }" `; + +exports[`with string map map - lookup returns StringMap reference 1`] = ` +"{ + "data": { + "test_data_source": { + "test": { + "name": "foo" + } + } + }, + "provider": { + "test": [ + { + } + ] + }, + "resource": { + "test_resource": { + "test-resource": { + "name": "\${data.test_data_source.test.string_map_map[\\"outer_key\\"]}" + } + } + }, + "terraform": { + "required_providers": { + "test": { + "version": "~> 2.0" + } + } + } +}" +`; + +exports[`with string map map - nested lookup produces correct reference 1`] = ` +"{ + "data": { + "test_data_source": { + "test": { + "name": "foo" + } + } + }, + "provider": { + "test": [ + { + } + ] + }, + "resource": { + "test_resource": { + "test-resource": { + "name": "\${data.test_data_source.test.string_map_map[\\"outer_key\\"][\\"inner_key\\"]}" + } + } + }, + "terraform": { + "required_providers": { + "test": { + "version": "~> 2.0" + } + } + } +}" +`; diff --git a/packages/cdktn/test/data-source.test.ts b/packages/cdktn/test/data-source.test.ts index e948723eb..0dbef26ec 100644 --- a/packages/cdktn/test/data-source.test.ts +++ b/packages/cdktn/test/data-source.test.ts @@ -91,6 +91,36 @@ test("with any map", () => { ).toMatchSnapshot(); }); +test("with string map map - lookup returns StringMap reference", () => { + expect( + Testing.synthScope((stack) => { + new TestProvider(stack, "provider", {}); + + const dataSource = new TestDataSource(stack, "test", { + name: "foo", + }); + new TestResource(stack, "test-resource", { + name: Token.asString(dataSource.stringMapMap.lookup("outer_key")), + }); + }), + ).toMatchSnapshot(); +}); + +test("with string map map - nested lookup produces correct reference", () => { + expect( + Testing.synthScope((stack) => { + new TestProvider(stack, "provider", {}); + + const dataSource = new TestDataSource(stack, "test", { + name: "foo", + }); + new TestResource(stack, "test-resource", { + name: dataSource.stringMapMap.lookup("outer_key").lookup("inner_key"), + }); + }), + ).toMatchSnapshot(); +}); + test("dependent data source", () => { expect( Testing.synthScope((stack) => { diff --git a/packages/cdktn/test/helper/data-source.ts b/packages/cdktn/test/helper/data-source.ts index c4282c208..ec631b762 100644 --- a/packages/cdktn/test/helper/data-source.ts +++ b/packages/cdktn/test/helper/data-source.ts @@ -5,6 +5,7 @@ import { TerraformDataSource, TerraformMetaArguments, StringMap, + StringMapMap, NumberMap, BooleanMap, AnyMap, @@ -62,6 +63,10 @@ export class TestDataSource extends TerraformDataSource { return new AnyMap(this, "any_map").lookup(key); } + public get stringMapMap() { + return new StringMapMap(this, "string_map_map"); + } + public get listValue() { return this.getListAttribute("list_value"); } From 9fe2163273467bc0015ace1ff7884e8ad52d0676 Mon Sep 17 00:00:00 2001 From: OrfeasZ Date: Sun, 19 Apr 2026 12:13:15 +0000 Subject: [PATCH 3/3] chore: Remove redundant check --- .../lib/get/generator/models/attribute-type-model.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts b/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts index 06f071492..85e407189 100644 --- a/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts +++ b/packages/@cdktn/provider-generator/lib/get/generator/models/attribute-type-model.ts @@ -393,10 +393,6 @@ export class MapAttributeTypeModel implements CollectionAttributeTypeModel { } get storedClassType() { - // Special case for map of map of string - if (this.elementType.storedClassType === "StringMap") { - return "StringMapMap"; - } return `${this.elementType.storedClassType}Map`; }