Skip to content

Commit d785b78

Browse files
authored
feat: Support CloudFormation Join & Sub function in environment variable (#1842)
1 parent c35b39e commit d785b78

File tree

3 files changed

+117
-2
lines changed

3 files changed

+117
-2
lines changed

src/lambda/LambdaFunction.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { setTimeout } from "node:timers/promises"
77
import { emptyDir, ensureDir, remove } from "fs-extra"
88
import jszip from "jszip"
99
import { log } from "../utils/log.js"
10+
import renderIntrinsicFunction from "../utils/renderIntrinsicFunction.js"
1011
import HandlerRunner from "./handler-runner/index.js"
1112
import LambdaContext from "./LambdaContext.js"
1213
import {
@@ -114,8 +115,8 @@ export default class LambdaFunction {
114115
entries(process.env).filter(([key]) => key.startsWith("AWS_")),
115116
)),
116117
...this.#getAwsEnvVars(),
117-
...provider.environment,
118-
...functionDefinition.environment,
118+
...renderIntrinsicFunction(provider.environment),
119+
...renderIntrinsicFunction(functionDefinition.environment),
119120
IS_OFFLINE: "true",
120121
}
121122

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import assert from "node:assert"
2+
import renderIntrinsicFunction from "../renderIntrinsicFunction.js"
3+
4+
describe("renderIntrinsicFunction", () => {
5+
it("should process Fn::Join correctly", () => {
6+
const input = { "Fn::Join": [":", ["a", "b", "c"]] }
7+
const result = renderIntrinsicFunction(input)
8+
assert.strictEqual(result, "a:b:c")
9+
})
10+
11+
it("should process !Join correctly", () => {
12+
const input = { "!Join": [":", ["a", "b", "c"]] }
13+
const result = renderIntrinsicFunction(input)
14+
assert.strictEqual(result, "a:b:c")
15+
})
16+
17+
it("should process Fn::Sub correctly", () => {
18+
const input = {
19+
"Fn::Sub": ["The name is ${name}", { name: "CloudFormation" }], // eslint-disable-line no-template-curly-in-string
20+
}
21+
const result = renderIntrinsicFunction(input)
22+
assert.strictEqual(result, "The name is CloudFormation")
23+
})
24+
25+
it("should process !Sub correctly", () => {
26+
const input = { "!Sub": ["Hello ${name}", { name: "World" }] } // eslint-disable-line no-template-curly-in-string
27+
const result = renderIntrinsicFunction(input)
28+
assert.strictEqual(result, "Hello World")
29+
})
30+
31+
it("should process nested Join correctly", () => {
32+
const input = {
33+
"Fn::Join": ["-", [{ "!Join": [":", ["a", "b", "c"]] }, "d"]],
34+
}
35+
const result = renderIntrinsicFunction(input)
36+
assert.strictEqual(result, "a:b:c-d")
37+
})
38+
39+
it("should process plain strings without modification", () => {
40+
const input = "This is a plain string"
41+
const result = renderIntrinsicFunction(input)
42+
assert.strictEqual(result, "This is a plain string")
43+
})
44+
45+
it("should process numbers without modification", () => {
46+
const input = 42
47+
const result = renderIntrinsicFunction(input)
48+
assert.strictEqual(result, 42)
49+
})
50+
51+
it("should process arrays without intrinsic functions", () => {
52+
const input = ["a", "b", "c"]
53+
const result = renderIntrinsicFunction(input)
54+
assert.deepStrictEqual(result, ["a", "b", "c"])
55+
})
56+
57+
it("should process nested intrinsic functions in arrays", () => {
58+
const input = ["a", { "Fn::Join": [":", ["b", "c"]] }, "d"]
59+
const result = renderIntrinsicFunction(input)
60+
assert.deepStrictEqual(result, ["a", "b:c", "d"])
61+
})
62+
63+
it("should throw an error for unsupported intrinsic functions", () => {
64+
const input = { "Fn::UnsupportedFunction": "Value" }
65+
const result = renderIntrinsicFunction(input)
66+
assert.deepStrictEqual(result, { "Fn::UnsupportedFunction": "Value" })
67+
})
68+
69+
it("should throw an error for unsupported shorthand intrinsic functions", () => {
70+
const input = { "!UnsupportedFunction": "Value" }
71+
const result = renderIntrinsicFunction(input)
72+
assert.deepStrictEqual(result, { "!UnsupportedFunction": "Value" })
73+
})
74+
75+
it("should process nested arrays with intrinsic functions", () => {
76+
const input = ["a", ["b", { "!Join": [":", ["c", "d"]] }]]
77+
const result = renderIntrinsicFunction(input)
78+
assert.deepStrictEqual(result, ["a", ["b", "c:d"]])
79+
})
80+
})
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default function renderIntrinsicFunction(input) {
2+
if (input === null || input === undefined) {
3+
return input
4+
}
5+
6+
if (typeof input === "string") {
7+
return input
8+
}
9+
10+
if (Array.isArray(input)) {
11+
return input.map(renderIntrinsicFunction)
12+
}
13+
14+
if (typeof input === "object") {
15+
const result = {}
16+
for (const [key, value] of Object.entries(input)) {
17+
if (key === "Fn::Join" || key === "!Join") {
18+
const [delimiter, list] = value
19+
return list.map(renderIntrinsicFunction).join(delimiter)
20+
}
21+
if (key === "Fn::Sub" || key === "!Sub") {
22+
const [template, variables] = value
23+
result[key] = template.replaceAll(/\${(.*?)}/g, (match, variable) => {
24+
return variable in variables ? variables[variable] : match
25+
})
26+
return result[key]
27+
}
28+
result[key] = renderIntrinsicFunction(value)
29+
}
30+
return result
31+
}
32+
33+
return input
34+
}

0 commit comments

Comments
 (0)