Skip to content

Commit 6513a6e

Browse files
authored
feat: add env var file support (#34)
* feat: add env var file support * update readme * review feedback
1 parent c2e3bc3 commit 6513a6e

File tree

11 files changed

+127
-2
lines changed

11 files changed

+127
-2
lines changed

.github/workflows/deploy-cf-it.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ jobs:
140140
credentials: ${{ secrets.DEPLOY_CF_SA_KEY_JSON }}
141141
event_trigger_type: providers/cloud.pubsub/eventTypes/topic.publish
142142
event_trigger_resource: ${{ secrets.DEPLOY_CF_EVENT_PUBSUB_TOPIC }}
143+
env_vars_file: './tests/env-var-files/test.good.yaml'
143144
- uses: actions/setup-node@master
144145
with:
145146
node-version: 12.x

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ steps:
6565
`KEY1=VALUE1,KEY2=VALUE2`. All existing environment variables will be
6666
removed, even if this parameter is not passed.
6767

68+
- `env_vars_file`: (Optional) Path to a local YAML file with definitions for all environment variables. An example env_vars_file can be found [here](tests/env-var-files/test.good.yaml). Only one of env_vars or env_vars_file can be specified.
69+
6870
- `source_dir`: (Optional) Source directory for the function. Defaults to current directory.
6971

7072
- `project_id`: (Optional) ID of the Google Cloud project. If provided, this

action.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ inputs:
5353

5454
env_vars:
5555
description: |-
56-
List of key-value pairs to set as environment variables in the form KEY1=VALUE1,KEY2=VALUE2.
56+
List of key-value pairs to set as environment variables in the form KEY1=VALUE1,KEY2=VALUE2. Only one of env_vars or env_vars_file can be specified.
57+
required: false
58+
59+
env_vars_file:
60+
description: |-
61+
Path to a local YAML file with definitions for all environment variables. Only one of env_vars or env_vars_file can be specified.
5762
required: false
5863

5964
entry_point:

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
"archiver": "^5.0.0",
3737
"fs": "0.0.1-security",
3838
"gaxios": "^3.1.0",
39+
"google-auth-library": "^6.0.6",
3940
"googleapis": "^58.0.0",
40-
"google-auth-library": "^6.0.6"
41+
"yaml": "^1.10.0"
4142
},
4243
"devDependencies": {
4344
"@types/chai": "^4.2.12",

src/cloudFunction.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616

1717
import { cloudfunctions_v1 } from 'googleapis';
18+
import fs from 'fs';
19+
import YAML from 'yaml';
1820

1921
export type EnvVar = {
2022
[key: string]: string;
@@ -45,6 +47,7 @@ export type CloudFunctionOptions = {
4547
description?: string;
4648
sourceDir?: string;
4749
envVars?: string;
50+
envVarsFile?: string;
4851
entryPoint?: string;
4952
runtime: string;
5053
availableMemoryMb?: number;
@@ -108,12 +111,23 @@ export class CloudFunction {
108111
? opts.availableMemoryMb
109112
: null;
110113

114+
// Only one of envVars and envVarsFile should be set
115+
if (opts?.envVars && opts?.envVarsFile) {
116+
throw new Error(
117+
'Only one of env_vars or env_vars_file can be specified.',
118+
);
119+
}
120+
111121
// Parse env vars
112122
let envVars;
113123
if (opts?.envVars) {
114124
envVars = this.parseEnvVars(opts.envVars);
115125
request.environmentVariables = envVars;
116126
}
127+
if (opts?.envVarsFile) {
128+
envVars = this.parseEnvVarsFile(opts.envVarsFile);
129+
request.environmentVariables = envVars;
130+
}
117131

118132
this.request = request;
119133
this.name = opts.name;
@@ -149,4 +163,23 @@ export class CloudFunction {
149163
});
150164
return envVars;
151165
}
166+
167+
/**
168+
* Read and parse an env var file.
169+
*
170+
* @param envVarsFile env var file path.
171+
* @returns map of type {KEY1:VALUE1}
172+
*/
173+
protected parseEnvVarsFile(envVarFilePath: string): EnvVar {
174+
const content = fs.readFileSync(envVarFilePath, 'utf-8');
175+
const yamlContent = YAML.parse(content) as EnvVar;
176+
for (const [key, val] of Object.entries(yamlContent)) {
177+
if (typeof key !== 'string' || typeof val !== 'string') {
178+
throw new Error(
179+
`env_vars_file yaml must contain only key/value pair of strings. Error parsing key ${key} of type ${typeof key} with value ${val} of type ${typeof val}`,
180+
);
181+
}
182+
}
183+
return yamlContent;
184+
}
152185
}

src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ async function run(): Promise<void> {
2828
const availableMemoryMb = core.getInput('memory_mb');
2929
const region = core.getInput('region') || 'us-central1';
3030
const envVars = core.getInput('env_vars');
31+
const envVarsFile = core.getInput('env_vars_file');
3132
const entryPoint = core.getInput('entry_point');
3233
const sourceDir = core.getInput('source_dir');
3334
const vpcConnector = core.getInput('vpc_connector');
@@ -50,6 +51,7 @@ async function run(): Promise<void> {
5051
availableMemoryMb: +availableMemoryMb,
5152
entryPoint,
5253
envVars,
54+
envVarsFile,
5355
timeout,
5456
maxInstances: +maxInstances,
5557
eventTriggerType,

tests/cloudFunction.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,41 @@ describe('CloudFunction', function () {
7878
);
7979
});
8080

81+
it('creates a http function with envVarsFile', function () {
82+
const envVarsFile = 'tests/env-var-files/test.good.yaml';
83+
const cf = new CloudFunction({ name, runtime, parent, envVarsFile });
84+
expect(cf.request.name).equal(`${parent}/functions/${name}`);
85+
expect(cf.request.environmentVariables?.KEY1).equal('VALUE1');
86+
expect(cf.request.environmentVariables?.KEY2).equal('VALUE2');
87+
expect(cf.request.environmentVariables?.JSONKEY).equal('{"bar":"baz"}');
88+
});
89+
90+
it('throws an error with bad envVarsFile', function () {
91+
const envVarsFile = 'tests/env-var-files/test.bad.yaml';
92+
expect(function () {
93+
new CloudFunction({ name, runtime, parent, envVarsFile });
94+
}).to.throw(
95+
'env_vars_file yaml must contain only key/value pair of strings. Error parsing key KEY2 of type string with value VALUE2,VALUE3 of type object',
96+
);
97+
});
98+
99+
it('throws an error with nonexistent envVarsFile', function () {
100+
const envVarsFile = 'tests/env-var-files/test.nonexistent.yaml';
101+
expect(function () {
102+
new CloudFunction({ name, runtime, parent, envVarsFile });
103+
}).to.throw(
104+
"ENOENT: no such file or directory, open 'tests/env-var-files/test.nonexistent.yaml",
105+
);
106+
});
107+
108+
it('throws an error with both envVarsFile and envVars specified', function () {
109+
const envVarsFile = 'tests/env-var-files/test.good.yaml';
110+
const envVars = 'KEY1=VALUE1,KEY2=VALUE2';
111+
expect(function () {
112+
new CloudFunction({ name, runtime, parent, envVarsFile, envVars });
113+
}).to.throw('Only one of env_vars or env_vars_file can be specified.');
114+
});
115+
81116
it('creates an event function', function () {
82117
const eventTriggerType = 'fooType';
83118
const eventTriggerResource = 'barResource';

tests/cloudFunctionClient.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,37 @@ describe('CloudFunction', function () {
141141
const deleteFunc = await client.delete(updatedHttpFunc.functionPath);
142142
expect(deleteFunc.done).to.eq(true);
143143
});
144+
145+
it('can deploy CF with envVarFile', async function () {
146+
if (!credentials) {
147+
this.skip();
148+
}
149+
const client = new CloudFunctionClient(region, {
150+
credentials: credentials,
151+
projectId: project,
152+
});
153+
const newHttpFunc = new CloudFunction({
154+
name: `http-envfile-${name}`,
155+
parent: client.parent,
156+
sourceDir: testNodeFuncDir,
157+
runtime: 'nodejs10',
158+
envVarsFile: 'tests/env-var-files/test.good.yaml',
159+
entryPoint: 'helloWorld',
160+
});
161+
const result = await client.deploy(newHttpFunc);
162+
// expect function to be deployed without error
163+
expect(result).to.not.eql(null);
164+
expect(result.done).to.eq(true);
165+
expect(result).to.not.have.property('error');
166+
expect(result.response?.httpsTrigger.url).to.not.be.null;
167+
// expect to have the correct env vars from env var file
168+
expect(result.response?.environmentVariables.KEY1).to.eq('VALUE1');
169+
expect(result.response?.environmentVariables.KEY2).to.eq('VALUE2');
170+
expect(result.response?.environmentVariables.JSONKEY).to.eq(
171+
'{"bar":"baz"}',
172+
);
173+
// expect function to be deleted without error
174+
const deleteFunc = await client.delete(newHttpFunc.functionPath);
175+
expect(deleteFunc.done).to.eq(true);
176+
});
144177
});

tests/env-var-files/test.bad.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
KEY1: VALUE1
2+
KEY2:
3+
- VALUE2
4+
- VALUE3

0 commit comments

Comments
 (0)