@cdktf/hcl2cdk
Version:
Transform HCL into CDK
693 lines (692 loc) • 127 kB
JavaScript
"use strict";
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
const expressions_1 = require("../expressions");
const testHelpers_1 = require("./testHelpers");
const awsProviderSchema = {
format_version: "1.0",
provider_schemas: {
"registry.terraform.io/hashicorp/aws": {
provider: {},
resource_schemas: {
aws_s3_bucket: {
block: {
attributes: {
foo: {
type: ["map", "string"],
description_kind: "plain",
},
},
},
},
},
data_source_schemas: {
aws_availability_zones: {
version: 0,
block: {
attributes: {
all_availability_zones: {
type: "bool",
description_kind: "plain",
optional: true,
},
exclude_names: {
type: ["set", "string"],
description_kind: "plain",
optional: true,
},
exclude_zone_ids: {
type: ["set", "string"],
description_kind: "plain",
optional: true,
},
group_names: {
type: ["set", "string"],
description_kind: "plain",
computed: true,
},
id: {
type: "string",
description_kind: "plain",
optional: true,
computed: true,
},
names: {
type: ["list", "string"],
description_kind: "plain",
computed: true,
},
state: {
type: "string",
description_kind: "plain",
optional: true,
},
zone_ids: {
type: ["list", "string"],
description_kind: "plain",
computed: true,
},
testing_map: {
type: ["map", "string"],
description_kind: "plain",
computed: true,
},
},
block_types: {
filter: {
nesting_mode: "set",
block: {
attributes: {
name: {
type: "string",
description_kind: "plain",
required: true,
},
values: {
type: ["set", "string"],
description_kind: "plain",
required: true,
},
},
description_kind: "plain",
},
},
timeouts: {
nesting_mode: "single",
block: {
attributes: {
read: {
type: "string",
description_kind: "plain",
optional: true,
},
},
description_kind: "plain",
},
},
},
description_kind: "plain",
},
},
},
},
},
};
const externalProviderSchema = {
format_version: "1.0",
provider_schemas: {
"registry.terraform.io/hashicorp/external": {
provider: {},
resource_schemas: {},
data_source_schemas: {
external: {
version: 0,
block: {
attributes: {
id: {
type: "string",
description: "The id of the data source. This will always be set to `-`",
description_kind: "plain",
computed: true,
},
program: {
type: ["list", "string"],
description: "A list of strings, whose first element is the program to run and whose subsequent elements are optional command line arguments to the program. Terraform does not execute the program through a shell, so it is not necessary to escape shell metacharacters nor add quotes around arguments containing spaces.",
description_kind: "plain",
required: true,
},
query: {
type: ["map", "string"],
description: "A map of string values to pass to the external program as the query arguments. If not supplied, the program will receive an empty object as its input.",
description_kind: "plain",
optional: true,
},
result: {
type: ["map", "string"],
description: "A map of string values returned from the external program.",
description_kind: "plain",
computed: true,
},
working_dir: {
type: "string",
description: "Working directory of the program. If not supplied, the program will run in the current directory.",
description_kind: "plain",
optional: true,
},
},
description: 'The `external` data source allows an external program implementing a specific protocol (defined below) to act as a data source, exposing arbitrary data for use elsewhere in the Terraform configuration.\n\n**Warning** This mechanism is provided as an "escape hatch" for exceptional situations where a first-class Terraform provider is not more appropriate. Its capabilities are limited in comparison to a true data source, and implementing a data source via an external program is likely to hurt the portability of your Terraform configuration by creating dependencies on external programs and libraries that may not be available (or may need to be used differently) on different operating systems.\n\n**Warning** Terraform Enterprise does not guarantee availability of any particular language runtimes or external programs beyond standard shell utilities, so it is not recommended to use this data source within configurations that are applied within Terraform Enterprise.',
description_kind: "plain",
},
},
},
},
},
};
function getScope({ provider, resources, data, variables, locals, forEachIteratorName, scopedVariables, withinOverrideExpression = false, } = {}) {
const scopeVariables = {};
resources === null || resources === void 0 ? void 0 : resources.forEach((varName) => {
let [resourceType, resourceName] = varName.split(".");
if (!resourceName) {
resourceName = varName;
resourceType = "aws_s3_bucket";
}
scopeVariables[varName] = {
resource: resourceType,
variableName: resourceName,
};
});
data === null || data === void 0 ? void 0 : data.forEach((varName) => {
scopeVariables[varName] = {
resource: "data.aws_s3_bucket",
variableName: varName,
};
});
variables === null || variables === void 0 ? void 0 : variables.forEach((varName) => {
scopeVariables[varName] = {
resource: "var",
variableName: varName,
};
});
locals === null || locals === void 0 ? void 0 : locals.forEach((varName) => {
scopeVariables[varName] = {
resource: "local",
variableName: varName,
};
});
const scope = {
providerSchema: provider !== null && provider !== void 0 ? provider : {},
providerGenerator: {},
constructs: new Set(),
variables: scopeVariables,
hasTokenBasedTypeCoercion: true,
scopedVariables: scopedVariables || {},
forEachIteratorName,
withinOverrideExpression,
nodeIds: [],
importables: [],
topLevelConfig: {},
};
return scope;
}
const getType = () => "string";
describe("expressionToTs", () => {
test("converts a simple string to a string", async () => {
const expression = "hello";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""hello""`);
});
test("converts a simple expression with a template", async () => {
const expression = "${22}";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(22)"`);
});
test("converts a variable reference", async () => {
const expression = "${var.foo}";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"foo.stringValue"`);
});
test("convert a variable reference with snake case", async () => {
const expression = "${var.foo_bar}";
const scope = getScope({ variables: ["foo_bar"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(fooBar.value)"`);
});
test("convert a function call", async () => {
const expression = `\${replace("hello", "l", "w")}`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.replace("hello", "l", "w"))"`);
});
test("converts a function call with an expression", async () => {
const expression = `\${replace("hello-\${22+22}", "44", "world")}`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.replace("hello-" + Token.asString(Op.add(22, 22)), "44", "world"))"`);
});
test("converts string concatenation of iterator key", async () => {
const expression = '${"dynamic-ingress-${ingress.key}"}';
const scope = getScope({
scopedVariables: {
ingress: "dynamic_iterator0",
},
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""dynamic-ingress-\${" + Token.asString(dynamic_iterator0.key) + "}""`);
});
test("convert a variable reference", async () => {
const expression = `\${var.foo_bar}`;
const scope = getScope({ variables: ["foo_bar"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
// TODO: This seems broken
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(fooBar.value)"`);
});
test("convert a resource reference", async () => {
const expression = `"simple-\${aws_s3_bucket.foo.id}"`;
const scope = getScope({ resources: ["aws_s3_bucket.foo"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""simple-\${" + foo.id + "}""`);
});
test("convert a resource reference with nested properties", async () => {
const expression = `"simple-\${aws_s3_bucket.foo.prop.test}"`;
const scope = getScope({ resources: ["aws_s3_bucket.foo"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""simple-\${" + foo.prop.test + "}""`);
});
test("convert a local reference", async () => {
const expression = `"simple-\${local.foo}"`;
const scope = getScope({ locals: ["foo"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""simple-\${" + foo + "}""`);
});
test("plain resource references in arithmetics", async () => {
const expression = "${aws_s3_bucket.examplebucket.count + aws_s3_bucket.otherbucket.count }";
const scope = getScope({
resources: ["aws_s3_bucket.examplebucket", "aws_s3_bucket.otherbucket"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Op.add(examplebucket.count, otherbucket.count))"`);
});
test("use fqn for splat reference", async () => {
const expression = `\${aws_s3_bucket.foo.*.id}`;
const scope = getScope({ resources: ["aws_s3_bucket.foo"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(foo, ["*", "id"]))"`);
});
test("use fqn if property is present on numeric access using dot notation", async () => {
const expression = "${aws_s3_bucket.examplebucket.network_interface.0.access_config.0.assigned_nat_ip}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(examplebucket.networkInterface, ["0", "access_config", "0", "assigned_nat_ip"]))"`);
});
test("use fqn if property is present on numeric access", async () => {
const expression = "${aws_s3_bucket.examplebucket.network_interface[0].access_config[0].assigned_nat_ip}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(examplebucket.networkInterface, ["0", "access_config", "0", "assigned_nat_ip"]))"`);
});
test("use no fqn if property is present on numeric access using []", async () => {
const expression = "${aws_s3_bucket.examplebucket.network_interface[0].access_config[0].assigned_nat_ip}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(examplebucket.networkInterface, ["0", "access_config", "0", "assigned_nat_ip"]))"`);
});
test("detects splat reference within function", async () => {
const expression = "${toset(aws_s3_bucket.examplebucket.*)}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.toset(Fn.lookupNested(examplebucket, ["*"])))"`);
});
test("convert conditional", async () => {
const expression = "${aws_kms_key.key.deletion_window_in_days > 3 ? aws_s3_bucket.examplebucket.id : []}";
const scope = getScope({ resources: ["aws_kms_key.key", "examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(conditional(Op.gt(key.deletionWindowInDays, 3), examplebucket.id, []))"`);
});
test("converts a function with references", async () => {
const expression = "${element(aws_s3_bucket.examplebucket, 0).id}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(Fn.element(examplebucket, 0), ["id"]))"`);
});
test("converts a function with splat reference", async () => {
const expression = "${element(aws_s3_bucket.examplebucket.*.id, 0)}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.element(Fn.lookupNested(examplebucket, ["*", "id"]), 0))"`);
});
test("convert for loops", async () => {
const expression = "${{ for name, user in var.users : user.role => name...}}";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${{ for name, user in \${" + users.value + "} : user.role => name...}}""`);
});
test("for expressions", async () => {
const expression = "{for s in var.list : s => upper(s)}";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${{ for s in \${" + list.value + "} : s => upper(s)}}""`);
});
test("for list expression ", async () => {
const expression = "[for s in var.list : upper(s)]";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${[ for s in \${" + list.value + "} : upper(s)]}""`);
});
test("for list with conditional", async () => {
const expression = '[for s in var.list : upper(s) if s != ""]';
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${[ for s in \${" + list.value + "} : upper(s) if s != \\"\\"]}""`);
});
test("convert property access resources", async () => {
const expression = "${aws_s3_bucket.examplebucket[0].id}";
const scope = getScope({ resources: ["aws_s3_bucket.examplebucket"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(examplebucket, ["0", "id"]))"`);
});
test("convert unary operators", async () => {
const expression = `\${!var.enabled}`;
const scope = getScope({ variables: ["enabled"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => "bool");
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asBoolean(Op.not(enabled.value))"`);
});
test("convert function with arrays and comments", async () => {
const expression = `\${compact([
# The example "bucket"
aws_s3_bucket.examplebucket,
# The "Learn" single page application. This is not configured in all environments.
var.input,
])}`;
const scope = getScope({
resources: ["aws_s3_bucket.examplebucket"],
variables: ["input"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
// TODO: See if we have a way to preserve comments
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.compact(Token.asList([examplebucket, input.value])))"`);
});
test("convert references for same reference", async () => {
const expression = `\${var.input == "test" ? "azure-ad-int" : "azure-ad-\${var.input}"}`;
const scope = getScope({ variables: ["input"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(conditional(Op.eq(input.value, "test"), "azure-ad-int", "azure-ad-\${" + input.value + "}"))"`);
});
test("nested variable access", async () => {
const expression = `\${element(var.test2["val1"], 0)}`;
const scope = getScope({ variables: ["test2"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.element(Fn.lookupNested(test2.value, ["\\"val1\\""]), 0))"`);
});
test("complicated nested local value", async () => {
const expression = "${flatten(var.vnets[*].subnets[*].name)}";
const scope = getScope({ variables: ["vnets"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.flatten(Fn.lookupNested(vnets.value, ["*", "subnets", "*", "name"])))"`);
});
// TOIMPROVE: I don't think we can handle this case yet, since the variable
// needs to be wrapped in an iterator. I'm leaving this in, but it's broken
test("complicated nested variable access with map", async () => {
const expression = `{
for vnet in var.vnets[*]:
(vnet.vnet_name) => vnet.subnets[*].name
}`;
const scope = getScope({ variables: ["vnets"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${{ for vnet in \${" + Fn.lookupNested(vnets.value, ["*"]) + "} : (vnet.vnet_name) => vnet.subnets[*].name}}""`);
});
test("complicated nested variable access with list", async () => {
const expression = `\${flatten([
for k, v in var.route : [
for n, s in v : [
{
key = k,
name = n,
svc_url = s
}
]
]
])}`;
const scope = getScope({ variables: ["route"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.flatten("\${[ for k, v in \${" + route.value + "} : [\\n for n, s in v : [\\n {\\n key = k,\\n name = n,\\n svc_url = s\\n }\\n ]\\n ]]}"))"`);
});
test("simple local variable access", async () => {
const expression = "${!local.enabled}";
const scope = getScope({ locals: ["enabled"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Op.not(enabled))"`);
});
test("multi local access", async () => {
const expression = '"${local.service_name},${local.owner},${local.is_it_great},${local.how_many}"';
const scope = getScope({
locals: ["service_name", "owner", "is_it_great", "how_many"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${" + serviceName + "},\${" + owner + "},\${" + isItGreat + "},\${" + howMany + "}""`);
});
test("binary operations within literals and variables", async () => {
const expression = '"${var.test} + 1"';
const scope = getScope({ variables: ["test"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${" + test.value + "} + 1""`);
});
test("local variable access", async () => {
const expression = '"${local.enabled}"';
const scope = getScope({ locals: ["enabled"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"enabled"`);
});
test("converts traversals that cannot be references", async () => {
const expression = "${self.path}";
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(TerraformSelf.getAny("path"))"`);
});
test("converts join function with variables", async () => {
const expression = '${join("-", [var.tags.app, var.tags.env])}';
const scope = getScope({ variables: ["tags"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.join("-", Token.asList([Fn.lookupNested(tags.value, ["app"]), Fn.lookupNested(tags.value, ["env"])])))"`);
});
test("converts join function with many arguments variables", async () => {
const expression = '${join("-", var.tags.app, var.tags.env, var.tags.other)}';
const scope = getScope({ variables: ["tags"] });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.join("-", Token.asList(Fn.concat([Fn.lookupNested(tags.value, ["app"]), Fn.lookupNested(tags.value, ["env"]), Fn.lookupNested(tags.value, ["other"])]))))"`);
});
test("doesn't wrap any extra templates", async () => {
const expression = `"app-\${terraform.workspace}"`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""app-\${terraform.workspace}""`);
});
test("converts a for expression", async () => {
const expression = `[for record in aws_route53_record.example : record.fqdn]`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${[ for record in \${" + example.fqn + "} : record.fqdn]}""`);
});
test("converts a data source", async () => {
const expression = `"\${data.aws_route53_zone.example.zone_id}"`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(example.zoneId)"`);
});
test("convert an each expression", async () => {
const expression = `"\${each.value.name}"`;
const scope = getScope({ forEachIteratorName: "myIterator" });
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(myIterator.value, ["name"]))"`);
});
test("converts a property of a function containing a resource", async () => {
const expression = `"\${element(aws_s3_bucket.examplebucket, 0).id}"`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(Fn.element(examplebucket, 0), ["id"]))"`);
});
test("convert a function with references with splats", async () => {
const expression = `"\${concat(var.private_subnets.*.id, var.public_subnets.*.id)}"`;
const scope = getScope({
variables: ["private_subnets", "public_subnets"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.concat([Fn.lookupNested(privateSubnets.value, ["*", "id"]), Fn.lookupNested(publicSubnets.value, ["*", "id"])]))"`);
});
test("convert a iterator variable", async () => {
const expression = `"\${each.key}"`;
const scope = getScope({
variables: ["private_subnets", "public_subnets"],
forEachIteratorName: "myIterator",
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(myIterator.key)"`);
});
test("convert a self reference", async () => {
const expression = `"\${self.subnet.id}"`;
const scope = getScope({
variables: ["private_subnets", "public_subnets"],
forEachIteratorName: "myIterator",
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(TerraformSelf.getAny("subnet.id"))"`);
});
test("convert heredocs", async () => {
const expression = `<<EOF
[{
"Condition": {
"KeyPrefixEquals": "docs/"
},
"Redirect": {
"ReplaceKeyPrefixWith": "documents/"
}
}]
EOF
`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""[{\\n \\"Condition\\": {\\n \\"KeyPrefixEquals\\": \\"docs/\\"\\n },\\n \\"Redirect\\": {\\n \\"ReplaceKeyPrefixWith\\": \\"documents/\\"\\n }\\n}]\\n""`);
});
test("convert heredocs without a trailing newline", async () => {
const expression = `<<EOF
hello world
EOF`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""hello world\\n""`);
});
test("convert indented heredocs", async () => {
const expression = `<<-EOF
hello world
EOF`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""hello world\\n""`);
});
test("convert override expressions", async () => {
const expression = '"${required_resource_access.value["resource_access"]}"';
const scope = getScope({
withinOverrideExpression: true,
scopedVariables: {
required_resource_access: "dynamic-block",
},
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`""\${required_resource_access.value[\\"resource_access\\"]}""`);
});
test("convert using operations while containing substring", async () => {
const expression =
// prettier-ignore
'"${length(var.image_id) > 4 && substr(var.image_id, 0, 4) == \"ami-\"}"';
const scope = getScope({});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Op.and(Op.gt(Fn.lengthOf(imageId.value), 4), Op.eq(Fn.substr(imageId.stringValue, 0, 4), "ami-")))"`);
});
test("convert a data source with numeric access", async () => {
const expression = `"\${data.aws_availability_zones.changeme_az_list_ebs_snapshot.names[0]}"`;
const scope = getScope({
provider: awsProviderSchema,
data: ["aws_subnet_ids"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(changemeAzListEbsSnapshot.names, ["0"]))"`);
});
test("convert a reference to a map access", async () => {
const expression = `"\${data.aws_availability_zones.changeme_az_list_ebs_snapshot.testing_map.foo}"`;
const scope = getScope({
provider: awsProviderSchema,
data: ["aws_subnet_ids"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(changemeAzListEbsSnapshot, ["testing_map", "foo"]))"`);
});
test("convert attribute up to a map and not within attribute reference", async () => {
const expression = `"\${data.aws_availability_zones.changeme_az_list_ebs_snapshot.testing_map}"`;
const scope = getScope({
provider: awsProviderSchema,
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => ["map", "string"]);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asStringMap(changemeAzListEbsSnapshot.testingMap)"`);
});
test("don't convert external unknown fields", async () => {
const expression = `"\${data.external.changeme_external_thumbprint_data.result.thumbprint}"`;
const scope = getScope({
provider: externalProviderSchema,
data: ["aws_subnet_ids"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => ["map", "string"]);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asStringMap(Fn.lookupNested(changemeExternalThumbprintData, ["result", "thumbprint"]))"`);
});
test("convert resource reference with map attribute", async () => {
const expression = `"\${aws_s3_bucket.examplebucket.foo.bar}"`;
const scope = getScope({
provider: awsProviderSchema,
resources: ["aws_s3_bucket"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => "string");
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(examplebucket, ["foo", "bar"]))"`);
});
test("convert complex variable reference in template", async () => {
const expression = `"\${var.default_tags.project}-client-tg"`;
const scope = getScope({
provider: awsProviderSchema,
resources: ["aws_s3_bucket"],
variables: ["default_tags"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => "string");
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(defaultTags.value, ["project"])) + "-client-tg""`);
});
test("convert boolean values", async () => {
const expression = `"\${false}"`;
const scope = getScope({
provider: awsProviderSchema,
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, () => "bool");
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"false"`);
});
test("convert a data source with count", async () => {
const expression = `"\${data.aws_availability_zones.available.names[count.index]}"`;
const scope = getScope({
provider: awsProviderSchema,
data: ["aws_availability_zones"],
});
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.lookupNested(available.names, ["\${count.index}"]))"`);
});
test("accept escaped quotes within string", async () => {
const expression = `"\${jsonencode({
"Statement" = [{
"Action" = "s3:*",
"Effect" = "Allow",
}],
})}"`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`
"Token.asString(Fn.jsonencode({
"Statement": [{
"Action": "s3:*",
"Effect": "Allow"
}]
}))"
`);
});
test("support variadic parameters in any position", async () => {
const expression = `"\${cidrsubnets("fd00:fd12:3456:7890::/56", 16, 16, 16, 32)}"`;
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"Token.asString(Fn.cidrsubnets("fd00:fd12:3456:7890::/56", [16, 16, 16, 32]))"`);
});
test("converts template expression with escaped ${} expression", async () => {
const expression = '["$${path:name.givenName}"]'; // from aws_ssoadmin_instance_access_control_attributes example
const scope = getScope();
const result = await (0, expressions_1.convertTerraformExpressionToTs)(scope, expression, getType);
expect((0, testHelpers_1.astToCode)(result)).toMatchInlineSnapshot(`"["$\${path:name.givenName}"]"`);
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwcmVzc2lvblRvVHMudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImV4cHJlc3Npb25Ub1RzLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7R0FHRzs7QUFFSCxnREFBZ0U7QUFHaEUsK0NBQWtEO0FBRWxELE1BQU0saUJBQWlCLEdBQUc7SUFDeEIsY0FBYyxFQUFFLEtBQUs7SUFDckIsZ0JBQWdCLEVBQUU7UUFDaEIscUNBQXFDLEVBQUU7WUFDckMsUUFBUSxFQUFFLEVBQUU7WUFDWixnQkFBZ0IsRUFBRTtnQkFDaEIsYUFBYSxFQUFFO29CQUNiLEtBQUssRUFBRTt3QkFDTCxVQUFVLEVBQUU7NEJBQ1YsR0FBRyxFQUFFO2dDQUNILElBQUksRUFBRSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUM7Z0NBQ3ZCLGdCQUFnQixFQUFFLE9BQU87NkJBQzFCO3lCQUNGO3FCQUNGO2lCQUNGO2FBQ0Y7WUFDRCxtQkFBbUIsRUFBRTtnQkFDbkIsc0JBQXNCLEVBQUU7b0JBQ3RCLE9BQU8sRUFBRSxDQUFDO29CQUNWLEtBQUssRUFBRTt3QkFDTCxVQUFVLEVBQUU7NEJBQ1Ysc0JBQXNCLEVBQUU7Z0NBQ3RCLElBQUksRUFBRSxNQUFNO2dDQUNaLGdCQUFnQixFQUFFLE9BQU87Z0NBQ3pCLFFBQVEsRUFBRSxJQUFJOzZCQUNmOzRCQUNELGFBQWEsRUFBRTtnQ0FDYixJQUFJLEVBQUUsQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDO2dDQUN2QixnQkFBZ0IsRUFBRSxPQUFPO2dDQUN6QixRQUFRLEVBQUUsSUFBSTs2QkFDZjs0QkFDRCxnQkFBZ0IsRUFBRTtnQ0FDaEIsSUFBSSxFQUFFLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQztnQ0FDdkIsZ0JBQWdCLEVBQUUsT0FBTztnQ0FDekIsUUFBUSxFQUFFLElBQUk7NkJBQ2Y7NEJBQ0QsV0FBVyxFQUFFO2dDQUNYLElBQUksRUFBRSxDQUFDLEtBQUssRUFBRSxRQUFRLENBQUM7Z0NB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