Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lib): Make default property in Fn.lookup optional and introduce Fn.lookupNested #2672

Merged
merged 5 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions examples/csharp/documentation/FunctionsOther.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0


using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Constructs;
using HashiCorp.Cdktf;
using aws.Provider;
using aws.DataAwsAvailabilityZones;

namespace Examples
{
class FunctionsOtherStack : TerraformStack
{
public FunctionsOtherStack(Construct scope, string name) : base(scope, name)
{
new AwsProvider(this, "aws", new AwsProviderConfig
{
Region = "eu-central-1"
});


// DOCS_BLOCK_START:functions-raw
DataAwsAvailabilityZones zones = new DataAwsAvailabilityZones(this, "zones", new DataAwsAvailabilityZonesConfig
{
State = "available"
});

new TerraformOutput(this, "half-of-the-zone", new TerraformOutputConfig
{
Value = $"${{length({zones.Fqn}.names) / 2}}"
});
// DOCS_BLOCK_END:functions-raw

// DOCS_BLOCK_START:functions-lookup
TerraformVariable v = new TerraformVariable(this, "complex_object", new TerraformVariableConfig
{
Type = "object({users: list(object({name: string}))})",
});
new TerraformOutput(this, "users", new TerraformOutputConfig
{
Value = Fn.Lookup(v.Value, "users")
});
new TerraformOutput(this, "first-user-name", new TerraformOutputConfig
{
Value = Fn.LookupNested(v.Value, new[] { "users", "0", "name" })
});
// DOCS_BLOCK_END:functions-lookup

// DOCS_BLOCK_START:functions-raw-string
new TerraformOutput(this, "quotes", new TerraformOutputConfig
{
Value = Fn.RawString("\"b\"")
});
new TerraformOutput(this, "template", new TerraformOutputConfig
{
Value = Fn.RawString("${TEMPLATE}")
});
// DOCS_BLOCK_END:functions-raw-string
}
}
}
39 changes: 0 additions & 39 deletions examples/csharp/documentation/FunctionsRawStack.cs

This file was deleted.

2 changes: 1 addition & 1 deletion examples/csharp/documentation/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static void Main(string[] args)
new Examples.Producer(app, "cdktf-producer");
new Examples.Consumer(app, "cdktf-consumer");
new Examples.OperatorsStack(app, "operators");
new Examples.FunctionsRawStack(app, "functions-raw");
new Examples.FunctionsOtherStack(app, "functions-other");

TerraformStack stack = new TerraformStack(app, "stack-escape-hatches");
// DOCS_BLOCK_START:stack-escape-hatches
Expand Down
37 changes: 37 additions & 0 deletions examples/go/documentation/functions-other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package main

import (
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
"github.com/hashicorp/terraform-cdk-go/cdktf"
)

func NewFunctionsOtherStack(scope constructs.Construct, name string) cdktf.TerraformStack {
stack := cdktf.NewTerraformStack(scope, &name)

// DOCS_BLOCK_START:functions-lookup
v := cdktf.NewTerraformVariable(stack, jsii.String("complex-object"), &cdktf.TerraformVariableConfig{
Type: jsii.String("object({users: list(object({name: string}))})"),
})
cdktf.NewTerraformOutput(stack, jsii.String("users"), &cdktf.TerraformOutputConfig{
Value: cdktf.Fn_Lookup(v.Value(), jsii.String("users"), nil),
})
cdktf.NewTerraformOutput(stack, jsii.String("first-user-name"), &cdktf.TerraformOutputConfig{
Value: cdktf.Fn_LookupNested(v.Value(), &[]interface{}{"users", 0, "name"}),
})
// DOCS_BLOCK_END:functions-lookup

// DOCS_BLOCK_START:functions-raw-string
cdktf.NewTerraformOutput(stack, jsii.String("quotes"), &cdktf.TerraformOutputConfig{
Value: cdktf.Fn_RawString(jsii.String("\"b\"")),
})
cdktf.NewTerraformOutput(stack, jsii.String("template"), &cdktf.TerraformOutputConfig{
Value: cdktf.Fn_RawString(jsii.String("${TEMPLATE}")),
})
// DOCS_BLOCK_END:functions-raw-string

return stack
}
1 change: 1 addition & 0 deletions examples/go/documentation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func main() {
NewAspectsStack(app, "aspects")
NewPrefixAspectsStack(app, "aspects-validation")
NewFunctionsStack(app, "functions")
NewFunctionsOtherStack(app, "functions-other")
NewOperatorsAndFunctionsRawStack(app, "operators-functions-raw")
NewHclInteropStack(app, "hcl-interop")
NewProvidersStack(app, "providers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@

import software.constructs.Construct;

import java.util.Arrays;

// DOCS_BLOCK_START:functions-usage-example
import com.hashicorp.cdktf.Fn;
import com.hashicorp.cdktf.TerraformVariable;
import com.hashicorp.cdktf.TerraformVariableConfig;
import com.hashicorp.cdktf.TerraformOutput;
import com.hashicorp.cdktf.TerraformOutputConfig;
import imports.aws.data_aws_availability_zones.DataAwsAvailabilityZones;
Expand Down Expand Up @@ -41,5 +45,26 @@ public MainFunction(Construct scope, String id) {
.build());
// DOCS_BLOCK_END:functions-usage-example

// DOCS_BLOCK_START:functions-lookup
TerraformVariable v = new TerraformVariable(this, "complex_object", TerraformVariableConfig.builder()
.type("object({users: list(object({name: string}))})")
.build());
new TerraformOutput(this, "users", TerraformOutputConfig.builder()
.value(Fn.lookup(v.getValue(), "users"))
.build());
new TerraformOutput(this, "first-user-name", TerraformOutputConfig.builder()
.value(Fn.lookupNested(v.getValue(), Arrays.asList("users", "0", "name")))
.build());
// DOCS_BLOCK_END:functions-lookup

// DOCS_BLOCK_START:functions-raw-string
new TerraformOutput(this, "quotes", TerraformOutputConfig.builder()
.value(Fn.rawString("\"b\""))
.build());
new TerraformOutput(this, "template", TerraformOutputConfig.builder()
.value(Fn.rawString("${TEMPLATE}"))
.build());
// DOCS_BLOCK_END:functions-raw-string

}
}
25 changes: 24 additions & 1 deletion examples/python/documentation/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: MPL-2.0

from constructs import Construct
from cdktf import TerraformStack, App
from cdktf import TerraformStack, App, TerraformVariable, Token
# DOCS_BLOCK_START:functions-usage-example
from cdktf import Fn, TerraformOutput
from imports.aws.provider import AwsProvider
Expand All @@ -28,3 +28,26 @@ def __init__(self, scope: Construct, id: str):

# DOCS_BLOCK_END:functions-usage-example

# INTERNAL NOTE: Due to an JSII bug, we have to pass a default value for lookup in Python
# We can remove it, once https://github.com/aws/jsii/pull/4209 is released
# DOCS_BLOCK_START:functions-lookup
v = TerraformVariable(self, "complex-object",
type = 'object({users: list(object({name: string}))})',
)
TerraformOutput(self, 'users',
value=Fn.lookup(v.value, "users", "default")
)
TerraformOutput(self, 'first_user_name',
value=Fn.lookup_nested(v.value, ["users", 0, "name"])
)
# DOCS_BLOCK_END:functions-lookup

# DOCS_BLOCK_START:functions-raw-string
TerraformOutput(self, 'quotes',
value=Fn.raw_string('"b"')
)
TerraformOutput(self, 'template',
value=Fn.raw_string('${TEMPLATE}')
)
# DOCS_BLOCK_END:functions-raw-string

21 changes: 20 additions & 1 deletion examples/typescript/documentation/functions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0
// DOCS_BLOCK_START:functions
import { TerraformStack } from "cdktf";
import { TerraformStack, TerraformVariable } from "cdktf";
import { Construct } from "constructs";
import { AwsProvider } from "@cdktf/provider-aws/lib/aws-provider";
// DOCS_BLOCK_END:functions
Expand Down Expand Up @@ -37,6 +37,25 @@ export class FunctionsStack extends TerraformStack {
});
// DOCS_BLOCK_END:functions

// DOCS_BLOCK_START:functions-lookup
const v = new TerraformVariable(this, "complex_object", {
type: "object({users: list(object({name: string}))})",
});
new TerraformOutput(this, "users", { value: Fn.lookup(v.value, "users") });
new TerraformOutput(this, "first_user_name", {
value: Fn.lookupNested(v.value, ["users", 0, "name"]),
});
// DOCS_BLOCK_END:functions-lookup

// DOCS_BLOCK_START:functions-raw-string
new TerraformOutput(this, "quotes", {
value: Fn.rawString(`"b"`),
});
new TerraformOutput(this, "template", {
value: Fn.rawString("${TEMPLATE}"),
});
// DOCS_BLOCK_END:functions-raw-string

// DOCS_BLOCK_START:operators

// ...
Expand Down
21 changes: 16 additions & 5 deletions packages/cdktf/lib/terraform-functions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) HashiCorp, Inc
// SPDX-License-Identifier: MPL-2.0
import { rawString, Token } from ".";
import { propertyAccess, rawString, Token } from ".";
import { asAny } from "./functions/helpers";
import { FnGenerated } from "./functions/terraform-functions.generated";

// eslint-disable-next-line jsdoc/require-jsdoc
Expand All @@ -21,12 +22,22 @@ export class Fn extends FnGenerated {
* {@link /terraform/docs/language/functions/lookup.html lookup} retrieves the value of a single element from a map, given its key. If the given key does not exist, the given default value is returned instead.
* @param {any} inputMap
* @param {string} key
* @param {Array<any>} defaultValue
* @param {any} [defaultValue]
*/
static lookup(inputMap: any, key: string, defaultValue: any) {
static lookup(inputMap: any, key: string, defaultValue?: any) {
// overwritten because lookup() uses a variadic argument for its optional defaultValue
// we don't model it as optional since not passing it is deprecated in favor of the native Terraform expression "inputMap[key]"
return Fn._lookup(inputMap, key, [defaultValue]);
if (defaultValue) return Fn._lookup(inputMap, key, [defaultValue]);
return asAny(propertyAccess(inputMap, [key])); // -> renders inputMap[key] (which is recommended if no default value is given)
}

/**
* returns a property access expression that accesses the property at the given path in the given inputMap.
* For example lookupNested(x, ["a", "b", "c"]) will return a Terraform expression like x["a"]["b"]["c"]
* @param {any} inputMap
* @param {Array<any>} path
*/
static lookupNested(inputMap: any, path: any[]) {
return asAny(propertyAccess(inputMap, path));
}

/**
Expand Down
10 changes: 5 additions & 5 deletions packages/cdktf/test/fqn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ test("works when escape is mid-way", () => {
name: "${test_resource.first-resource}-second",
tags: {
firstResourceName:
'simple-test-${lookup(test_resource.other-resource, "name", "")}-${test_resource.first-resource.name}',
"simple-test-${test_resource.other-resource.name}-${test_resource.first-resource.name}",
},
};

Expand Down Expand Up @@ -147,7 +147,7 @@ test("after escape, reverts to normal", () => {
name: "${test_resource.first-resource}-second",
tags: {
firstResourceName:
'simple-test-${test_resource.first-resource.name}-${lookup(test_resource.other-resource, "name", "")}',
"simple-test-${test_resource.first-resource.name}-${test_resource.other-resource.name}",
},
};

Expand Down Expand Up @@ -186,7 +186,7 @@ test("can have multiple escapes", () => {
name: "${test_resource.first-resource}-second",
tags: {
firstResourceName:
'simple-test-${test_resource.first-resource.name}-${lookup(test_resource.other-resource, "name", "")}',
"simple-test-${test_resource.first-resource.name}-${test_resource.other-resource.name}",
},
};

Expand Down Expand Up @@ -291,7 +291,7 @@ test("works with functions", () => {
name: "${test_resource.first-resource}-second",
tags: {
firstResourceName:
'simple-test-${test_resource.first-resource.name}-${lookup(test_resource.other-resource, "name", "")}',
"simple-test-${test_resource.first-resource.name}-${test_resource.other-resource.name}",
},
};

Expand Down Expand Up @@ -416,7 +416,7 @@ test("allows functions within functions", () => {
name: "bar",
tags: {
firstResourceName:
'${lookup(test_resource.other-resource, "name", upper(lookup(test_resource.other-resource, "name", "")))}',
'${lookup(test_resource.other-resource, "name", upper(test_resource.other-resource.name))}',
},
};

Expand Down
40 changes: 40 additions & 0 deletions packages/cdktf/test/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,43 @@ it("errors mentioning function name and argument", () => {
`"Argument 1 of replace failed the validation: Error: 'this one " not' can not be used as value directly since it has unescaped double quotes in it. To safely use the value please use Fn.rawString on your string."`
);
});

test("Property access using lookup and lookupNested functions", () => {
const app = Testing.app();
const stack = new TerraformStack(app, "test");

const variable = new TerraformVariable(stack, "test-var", {
type: `object({a = object({b = string}), z = string})`,
});

new TerraformOutput(stack, "lookup", {
value: Fn.lookup(variable.value, "z", "defaultzzzz"),
});
new TerraformOutput(stack, "native-access", {
value: Fn.lookup(variable.value, "z"),
});
new TerraformOutput(stack, "native-access-nested", {
value: Fn.lookupNested(variable.value, ["a", "b"]),
});

expect(Testing.synth(stack)).toMatchInlineSnapshot(`
"{
"output": {
"lookup": {
"value": "\${lookup(var.test-var, \\"z\\", \\"defaultzzzz\\")}"
},
"native-access": {
"value": "\${var.test-var.z}"
},
"native-access-nested": {
"value": "\${var.test-var.a.b}"
}
},
"variable": {
"test-var": {
"type": "object({a = object({b = string}), z = string})"
}
}
}"
`);
});
Loading