Skip to content

Commit 7c884b3

Browse files
feat(lib): allow chaining of TerraformIterator created resources
1 parent 47c8ec5 commit 7c884b3

File tree

17 files changed

+5355
-274
lines changed

17 files changed

+5355
-274
lines changed

examples/csharp/documentation/IteratorStack.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,51 @@ public IteratorStack(Construct scope, string name) : base(scope, name)
116116
}
117117
});
118118
// DOCS_BLOCK_END:iterators-count
119+
120+
// DOCS_BLOCK_START:iterators-chain
121+
// We need a local to be able to pass the list to the iterator
122+
TerraformLocal configuration = new TerraformLocal(this, "configuration", new Dictionary<string, object> {
123+
{
124+
"website ",
125+
new Dictionary<string, object> {
126+
{ "name", "website-static-files" },
127+
{ "tags", new Dictionary<string, string> {
128+
{ "app", "website" }
129+
}}
130+
}
131+
},
132+
{
133+
"images",
134+
new Dictionary<string, object> {
135+
{ "name", "images" },
136+
{ "tags", new Dictionary<string, string> {
137+
{ "app", "image-converter" }
138+
}}
139+
}
140+
}
141+
});
142+
MapTerraformIterator s3BucketConfigurationIterator = MapTerraformIterator.FromMap(configuration.AsAnyMap);
143+
S3Bucket s3Buckets = new S3Bucket(this, "complex-iterator-buckets", new S3BucketConfig
144+
{
145+
ForEach = s3BucketConfigurationIterator,
146+
Bucket = s3BucketConfigurationIterator.GetString("name"),
147+
Tags = s3BucketConfigurationIterator.GetMap("tags")
148+
});
149+
150+
// This would be TerraformIterator.fromDataSources for data_sources
151+
TerraformIterator s3BucketsIterator = TerraformIterator.FromResources(s3Buckets);
152+
TerraformAsset helpFile = new TerraformAsset(this, "help", new TerraformAssetConfig
153+
{
154+
Path = "./help"
155+
});
156+
new S3BucketObject(this, "object", new S3BucketObjectConfig
157+
{
158+
ForEach = s3BucketsIterator,
159+
Bucket = s3BucketsIterator.GetString("id"),
160+
Key = "help",
161+
Source = helpFile.Path
162+
});
163+
// // DOCS_BLOCK_END:iterators-chain
119164
}
120165
}
121166
}

examples/go/documentation/help

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Help file

examples/go/documentation/iterators.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/hashicorp/aws/instance"
1111
aws "github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/hashicorp/aws/provider"
1212
"github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/hashicorp/aws/s3bucket"
13+
"github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/hashicorp/aws/s3bucketobject"
1314
"github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/integrations/github/datagithuborganization"
1415
github "github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/integrations/github/provider"
1516
"github.com/hashicorp/terraform-cdk/examples/go/documentation/generated/integrations/github/team"
@@ -98,5 +99,36 @@ func NewIteratorsStack(scope constructs.Construct, name string) cdktf.TerraformS
9899
})
99100
// DOCS_BLOCK_END:iterators-count
100101

102+
// DOCS_BLOCK_START:iterators-chain
103+
config := cdktf.NewTerraformLocal(stack, jsii.String("config-local"), []map[string]interface{}{
104+
{
105+
"name": "website-static-files",
106+
"tags": map[string]string{"app": "website"},
107+
},
108+
{
109+
"name": "images",
110+
"tags": map[string]string{"app": "image-converter"},
111+
},
112+
})
113+
114+
s3BucketConfigurationIterator := cdktf.TerraformIterator_FromList(config.Expression())
115+
s3Buckets := s3bucket.NewS3Bucket(stack, jsii.String("complex-iterator-buckets"), &s3bucket.S3BucketConfig{
116+
ForEach: s3BucketConfigurationIterator,
117+
Bucket: s3BucketConfigurationIterator.GetString(jsii.String("name")),
118+
Tags: s3BucketConfigurationIterator.GetStringMap(jsii.String("tags")),
119+
})
120+
121+
s3BucketsIterator := cdktf.TerraformIterator_FromResources(s3Buckets)
122+
helpFile := cdktf.NewTerraformAsset(stack, jsii.String("help"), &cdktf.TerraformAssetConfig{
123+
Path: jsii.String("./help"),
124+
})
125+
s3bucketobject.NewS3BucketObject(stack, jsii.String("object"), &s3bucketobject.S3BucketObjectConfig{
126+
ForEach: s3BucketsIterator,
127+
Bucket: s3BucketsIterator.GetString(jsii.String("id")),
128+
Key: jsii.String("help"),
129+
Source: helpFile.Path(),
130+
})
131+
// DOCS_BLOCK_END:iterators-chain
132+
101133
return stack
102134
}

examples/java/documentation/src/main/java/com/mycompany/app/MainIterator2.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
import com.hashicorp.cdktf.*;
99
import imports.aws.s3_bucket.S3Bucket;
1010
import imports.aws.s3_bucket.S3BucketConfig;
11+
import imports.aws.s3_bucket.S3BucketObject;
12+
import imports.aws.s3_bucket.S3BucketObjectConfig;
1113
import software.constructs.Construct;
1214
import imports.aws.provider.AwsProvider;
1315
import imports.aws.provider.AwsProviderConfig;
1416

17+
import java.util.Arrays;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
1522
public class MainIterator2 extends TerraformStack {
1623
public MainIterator2(Construct scope, String id) {
1724
super(scope, id);
@@ -32,5 +39,54 @@ public MainIterator2(Construct scope, String id) {
3239
.bucket(Token.asString(terraformIterator.getString("")))
3340
.build());
3441
// DOCS_BLOCK_END:iterators-define-iterators
42+
43+
// DOCS_BLOCK_START:iterators-chain
44+
TerraformLocal myComplexLocal = new TerraformLocal(this, "my-map", new HashMap() {
45+
{
46+
put("website", new HashMap() {
47+
{
48+
put("name", "website-static-files");
49+
put("tags", new HashMap<String, String>() {
50+
{
51+
put("app", "website");
52+
}
53+
});
54+
}
55+
});
56+
put("images", new HashMap() {
57+
{
58+
put("name", "images");
59+
put("tags", new HashMap<String, String>() {
60+
{
61+
put("app", "image-converter");
62+
}
63+
});
64+
}
65+
});
66+
}
67+
});
68+
69+
TerraformIterator s3BucketConfigurationIterator = TerraformIterator.fromMap(myComplexLocal.getAsAnyMap());
70+
S3Bucket s3Bucket = new S3Bucket(this, "bucket", S3BucketConfig.builder()
71+
.forEach(s3BucketConfigurationIterator)
72+
.bucket(s3BucketConfigurationIterator.getString("name"))
73+
.tags(s3BucketConfigurationIterator.getStringMap("tags"))
74+
.build());
75+
76+
TerraformAsset asset = new TerraformAsset(this, "help", TerraformAssetConfig.builder()
77+
.path(Paths.get(System.getProperty("user.dir"), "help").toString())
78+
.build()
79+
);
80+
81+
// This would be TerraformIterator.fromDataSources for data_sources
82+
TerraformIterator s3BucketIterator = TerraformIterator.fromResources(s3Bucket);
83+
new S3BucketObject(this, "object", S3BucketObjectConfig.builder()
84+
.forEach(s3BucketIterator)
85+
.bucket(s3BucketIterator.getString("id"))
86+
.key("help")
87+
.source(asset.getPath())
88+
.build()
89+
);
90+
// DOCS_BLOCK_END:iterators-chain
3591
}
3692
}

examples/python/documentation/iterators.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# Copyright (c) HashiCorp, Inc.
22
# SPDX-License-Identifier: MPL-2.0
33

4-
from cdktf import TerraformStack, Token, App, TerraformCount
4+
from cdktf import TerraformStack, Token, TerraformCount, TerraformAsset
55
from constructs import Construct
66
from imports.aws.provider import AwsProvider
77
from imports.aws.instance import Instance
88
from imports.github.data_github_organization import DataGithubOrganization
99
from imports.github.provider import GithubProvider
1010
from imports.github.team import Team
1111
from imports.github.team_members import TeamMembers
12+
from imports.aws.s3_bucket_object import S3BucketObject
13+
1214

1315
# DOCS_BLOCK_START:iterators-define-iterators,iterators-iterators-complex-types
1416
from imports.aws.s3_bucket import S3Bucket
@@ -105,3 +107,36 @@ def __init__(self, scope: Construct, id: str):
105107
})
106108
)
107109
# DOCS_BLOCK_END:iterators-list-attributes
110+
111+
# DOCS_BLOCK_START:iterators-chain
112+
map = TerraformLocal(self, "my-map", {
113+
"website": {
114+
"name": "website-static-files",
115+
"tags": {"app": "website"}
116+
},
117+
"images": {
118+
"name": "images",
119+
"tags": {"app": "image-converter"}
120+
}
121+
})
122+
s3_bucket_configuration_iterator = TerraformIterator.from_map(
123+
map=map.as_any_map
124+
)
125+
s3_buckets = S3Bucket(self, "complex-iterator-buckets",
126+
for_each=s3_bucket_configuration_iterator,
127+
bucket=s3_bucket_configuration_iterator.get_string("name"),
128+
tags=s3_bucket_configuration_iterator.get_map("tags")
129+
)
130+
131+
# This would be TerraformIterator.from_data_sources for data_sources
132+
s3_buckets_iterator = TerraformIterator.from_resources(s3_buckets)
133+
help_file = TerraformAsset(self, "help",
134+
path="./help"
135+
)
136+
S3BucketObject(self, "object",
137+
for_each=s3_buckets_iterator,
138+
bucket=s3_buckets_iterator.get_string("id"),
139+
key="help",
140+
source=help_file.path
141+
)
142+
# DOCS_BLOCK_END:iterators-chain

examples/typescript/documentation/iterators.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { Construct } from "constructs";
1010
import { AwsProvider } from "@cdktf/provider-aws/lib/aws-provider";
1111
import { S3Bucket } from "@cdktf/provider-aws/lib/s3-bucket";
1212

13+
// DOCS_BLOCK_END:iterators,iterators-complex-types
14+
import { TerraformAsset } from "cdktf";
15+
import { S3BucketObject } from "@cdktf/provider-aws/lib/s3-bucket-object";
16+
// DOCS_BLOCK_START:iterators,iterators-complex-types
1317
export class IteratorsStack extends TerraformStack {
1418
constructor(scope: Construct, id: string) {
1519
super(scope, id);
@@ -78,7 +82,38 @@ export class IteratorsStack extends TerraformStack {
7882
});
7983
// DOCS_BLOCK_END:iterators-complex-types
8084

81-
// DOCS_BLOCK_START:iterators,iterators-complex-types
85+
// DOCS_BLOCK_START:iterators-chain
86+
const s3BucketConfigurationIterator = TerraformIterator.fromMap({
87+
website: {
88+
name: "website-static-files",
89+
tags: { app: "website" },
90+
},
91+
images: {
92+
name: "images",
93+
tags: { app: "image-converter" },
94+
},
95+
});
96+
97+
const s3Buckets = new S3Bucket(this, "complex-iterator-buckets", {
98+
forEach: s3BucketConfigurationIterator,
99+
bucket: s3BucketConfigurationIterator.getString("name"),
100+
tags: s3BucketConfigurationIterator.getStringMap("tags"),
101+
});
102+
103+
// This would be TerraformIterator.fromDataSources for data_sources
104+
const s3BucketsIterator = TerraformIterator.fromResources(s3Buckets);
105+
const helpFile = new TerraformAsset(this, "help", {
106+
path: "./help",
107+
});
108+
new S3BucketObject(this, "object", {
109+
forEach: s3BucketsIterator,
110+
bucket: s3BucketsIterator.getString("id"),
111+
key: "help",
112+
source: helpFile.path,
113+
});
114+
// DOCS_BLOCK_END:iterators-chain
115+
116+
// DOCS_BLOCK_START:iterators,iterators-complex-types,iterators-chain
82117
}
83118
}
84-
// DOCS_BLOCK_END:iterators,iterators-complex-types
119+
// DOCS_BLOCK_END:iterators,iterators-complex-types,iterators-chain

packages/cdktf/lib/terraform-iterator.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "./complex-computed-list";
1515
import { TerraformDynamicExpression } from "./terraform-dynamic-expression";
1616
import { Fn } from "./terraform-functions";
17+
import { ITerraformResource } from "./terraform-resource";
1718
import {
1819
FOR_EXPRESSION_KEY,
1920
FOR_EXPRESSION_VALUE,
@@ -80,6 +81,26 @@ export abstract class TerraformIterator implements ITerraformIterator {
8081
return new MapTerraformIterator(map);
8182
}
8283

84+
/**
85+
* Creates a new iterator from a resource that
86+
* has been created with the `for_each` argument.
87+
*/
88+
public static fromResources(
89+
resource: ITerraformResource
90+
): ResourceTerraformIterator {
91+
return new ResourceTerraformIterator(resource);
92+
}
93+
94+
/**
95+
* Creates a new iterator from a data source that
96+
* has been created with the `for_each` argument.
97+
*/
98+
public static fromDataSources(
99+
resource: ITerraformResource
100+
): ResourceTerraformIterator {
101+
return new ResourceTerraformIterator(resource);
102+
}
103+
83104
/**
84105
* @param attribute name of the property to retrieve
85106
* @returns the given attribute of the current item iterated over as a string
@@ -284,3 +305,46 @@ export class MapTerraformIterator extends TerraformIterator {
284305
return this._getValue();
285306
}
286307
}
308+
309+
// eslint-disable-next-line jsdoc/require-jsdoc
310+
export class ResourceTerraformIterator extends TerraformIterator {
311+
constructor(private readonly element: ITerraformResource) {
312+
super();
313+
314+
if (element.count) {
315+
throw new Error(
316+
"Cannot create iterator from resource with count argument. Please use the same TerraformCount used in the resource passed here instead."
317+
);
318+
}
319+
320+
if (!element.forEach) {
321+
throw new Error(
322+
"Cannot create iterator from resource without for_each argument"
323+
);
324+
}
325+
}
326+
327+
/**
328+
* Returns the currenty entry in the list or set that is being iterated over.
329+
* For lists this is the same as `iterator.value`. If you need the index,
330+
* use count using the escape hatch:
331+
* https://developer.hashicorp.com/terraform/cdktf/concepts/resources#escape-hatch
332+
*/
333+
public get key(): any {
334+
return this._getKey();
335+
}
336+
337+
/**
338+
* Returns the value of the current item iterated over.
339+
*/
340+
public get value(): any {
341+
return this._getValue();
342+
}
343+
344+
/**
345+
* @internal used by TerraformResource to set the for_each expression
346+
*/
347+
public _getForEachExpression(): any {
348+
return this.element.fqn; // no wrapping necessary for resources
349+
}
350+
}

packages/cdktf/test/dynamic-block.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,29 @@ test("dynamic blocks are properly rendered for providers", () => {
8888
},
8989
]);
9090
});
91+
92+
test("chained iterators used in dynamic blocks", () => {
93+
const app = Testing.app();
94+
const stack = new TerraformStack(app, "test");
95+
const it = TerraformIterator.fromList(["a", "b", "c"]);
96+
const source = new TestResource(stack, "test", {
97+
forEach: it,
98+
name: "foo",
99+
});
100+
101+
const chainedIt = TerraformIterator.fromResources(source);
102+
new TestResource(stack, "chained", {
103+
name: "foo",
104+
listBlock: chainedIt.dynamic({ name: chainedIt.getString("string_value") }),
105+
});
106+
107+
const synth = JSON.parse(Testing.synth(stack));
108+
expect(synth).toHaveProperty(
109+
"resource.test_resource.chained.dynamic.list_block.for_each",
110+
"${test_resource.test}"
111+
);
112+
expect(synth).toHaveProperty(
113+
"resource.test_resource.chained.dynamic.list_block.content",
114+
{ name: "${each.value.string_value}" }
115+
);
116+
});

0 commit comments

Comments
 (0)