Skip to content

Commit

Permalink
feat(lib): allow chaining of TerraformIterator created resources
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielMSchmidt committed Nov 20, 2023
1 parent 47c8ec5 commit a7bc1f7
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 1 deletion.
37 changes: 36 additions & 1 deletion examples/typescript/documentation/iterators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import { Team } from "@cdktf/provider-github/lib/team";
import { DataGithubOrganization } from "@cdktf/provider-github/lib/data-github-organization";
import { TeamMembers } from "@cdktf/provider-github/lib/team-members";
// DOCS_BLOCK_START:iterators,iterators-complex-types
import { TerraformIterator, TerraformStack, TerraformVariable } from "cdktf";
import {
TerraformAsset,
TerraformIterator,
TerraformStack,
TerraformVariable,
} from "cdktf";
import { Construct } from "constructs";
import { AwsProvider } from "@cdktf/provider-aws/lib/aws-provider";
import { S3Bucket } from "@cdktf/provider-aws/lib/s3-bucket";
import { S3BucketObject } from "@cdktf/provider-aws/lib/s3-bucket-object";

export class IteratorsStack extends TerraformStack {
constructor(scope: Construct, id: string) {
Expand Down Expand Up @@ -78,6 +84,35 @@ export class IteratorsStack extends TerraformStack {
});
// DOCS_BLOCK_END:iterators-complex-types

// TODO: write documentation for the following
const chainedIterators = TerraformIterator.fromMap({
website: {
name: "website-static-files",
tags: { app: "website" },
},
images: {
name: "images",
tags: { app: "image-converter" },
},
});

const s3Buckets = new S3Bucket(this, "complex-iterator-buckets", {
forEach: chainedIterators,
bucket: chainedIterators.getString("name"),
tags: chainedIterators.getStringMap("tags"),
});

const s3BucketsIterator = TerraformIterator.fromResources(s3Buckets);
const helpFile = new TerraformAsset(this, "help", {
path: "./help",
});
new S3BucketObject(this, "object", {
forEach: s3BucketsIterator,
bucket: s3BucketsIterator.getString("id"),
key: "help",
source: helpFile.path,
});

// DOCS_BLOCK_START:iterators,iterators-complex-types
}
}
Expand Down
64 changes: 64 additions & 0 deletions packages/cdktf/lib/terraform-iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "./complex-computed-list";
import { TerraformDynamicExpression } from "./terraform-dynamic-expression";
import { Fn } from "./terraform-functions";
import { ITerraformResource } from "./terraform-resource";
import {
FOR_EXPRESSION_KEY,
FOR_EXPRESSION_VALUE,
Expand Down Expand Up @@ -80,6 +81,26 @@ export abstract class TerraformIterator implements ITerraformIterator {
return new MapTerraformIterator(map);
}

/**
* Creates a new iterator from a resource that
* has been created with the `for_each` argument.
*/
public static fromResources(
resource: ITerraformResource
): ResourceTerraformIterator {
return new ResourceTerraformIterator(resource);
}

/**
* Creates a new iterator from a data source that
* has been created with the `for_each` argument.
*/
public static fromDataSources(
resource: ITerraformResource
): ResourceTerraformIterator {
return new ResourceTerraformIterator(resource);
}

/**
* @param attribute name of the property to retrieve
* @returns the given attribute of the current item iterated over as a string
Expand Down Expand Up @@ -284,3 +305,46 @@ export class MapTerraformIterator extends TerraformIterator {
return this._getValue();
}
}

// eslint-disable-next-line jsdoc/require-jsdoc
export class ResourceTerraformIterator extends TerraformIterator {
constructor(private readonly element: ITerraformResource) {
super();

if (element.count) {
throw new Error(
"Cannot create iterator from resource with count argument. Please use the same TerraformCount used in the resource passed here instead."
);
}

if (!element.forEach) {
throw new Error(
"Cannot create iterator from resource without for_each argument"
);
}
}

/**
* Returns the currenty entry in the list or set that is being iterated over.
* For lists this is the same as `iterator.value`. If you need the index,
* use count using the escape hatch:
* https://developer.hashicorp.com/terraform/cdktf/concepts/resources#escape-hatch
*/
public get key(): any {
return this._getKey();
}

/**
* Returns the value of the current item iterated over.
*/
public get value(): any {
return this._getValue();
}

/**
* @internal used by TerraformResource to set the for_each expression
*/
public _getForEachExpression(): any {
return this.element.fqn; // no wrapping necessary for resources
}
}
26 changes: 26 additions & 0 deletions packages/cdktf/test/dynamic-block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,29 @@ test("dynamic blocks are properly rendered for providers", () => {
},
]);
});

test("chained iterators used in dynamic blocks", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");
const it = TerraformIterator.fromList(["a", "b", "c"]);
const source = new TestResource(stack, "test", {
forEach: it,
name: "foo",
});

const chainedIt = TerraformIterator.fromResources(source);
new TestResource(stack, "chained", {
name: "foo",
listBlock: chainedIt.dynamic({ name: chainedIt.getString("string_value") }),
});

const synth = JSON.parse(Testing.synth(stack));
expect(synth).toHaveProperty(
"resource.test_resource.chained.dynamic.list_block.for_each",
"${test_resource.test}"
);
expect(synth).toHaveProperty(
"resource.test_resource.chained.dynamic.list_block.content",
{ name: "${each.value.string_value}" }
);
});
59 changes: 59 additions & 0 deletions packages/cdktf/test/iterator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,62 @@ test("count can count references", () => {
"data${count.index}"
);
});

test("chained iterators used in for_each", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");
const it = TerraformIterator.fromList(["a", "b", "c"]);
const source = new TestResource(stack, "test", {
forEach: it,
name: "foo",
});

const chainedIt = TerraformIterator.fromResources(source);
new TestResource(stack, "chained", {
forEach: chainedIt,
name: chainedIt.getString("string_value"),
});

const synth = JSON.parse(Testing.synth(stack));
expect(synth).toHaveProperty(
"resource.test_resource.chained.for_each",
"${test_resource.test}"
);
expect(synth).toHaveProperty(
"resource.test_resource.chained.name",
"${each.value.string_value}"
);
});

test("chained iterators from singular resources", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");
const source = new TestResource(stack, "test", {
name: "foo",
});

expect(() => {
TerraformIterator.fromResources(source);
}).toThrowErrorMatchingInlineSnapshot(
`"Cannot create iterator from resource without for_each argument"`
);
});

test("chained iterators used with count", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");

const resource = new TestResource(stack, "test", { name: "foo" });
const it = TerraformCount.of(resource.numericValue);

const datasFromCount = new TestDataSource(stack, "test_data", {
count: it,
name: `data${it.index}`,
});

expect(() => {
TerraformIterator.fromDataSources(datasFromCount);
}).toThrowErrorMatchingInlineSnapshot(
`"Cannot create iterator from resource with count argument. Please use the same TerraformCount used in the resource passed here instead."`
);
});

0 comments on commit a7bc1f7

Please sign in to comment.