Skip to content

Commit

Permalink
feat: add function labels support (#60) (#62)
Browse files Browse the repository at this point in the history
* feat: add function labels support (#60)

* feat: add function labels support

* fix: apply changes based on code review

* Document label input in readme

Co-authored-by: Alexey Blinov <nilcolor@gmail.com>
  • Loading branch information
bharathkkb and nilcolor committed May 26, 2021
1 parent 48ee7cd commit 9f988bd
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ steps:

- `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.

- `labels`: (Optional) List of key-value pairs to set as function labels in the form label1=VALUE1,label2=VALUE2.

- `source_dir`: (Optional) Source directory for the function. Defaults to current directory.

- `project_id`: (Optional) ID of the Google Cloud project. If provided, this
Expand Down
11 changes: 8 additions & 3 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ inputs:
formatted private key which can be exported from the Cloud Console. The
value can be raw or base64-encoded.
required: false

name:
description: |-
Name of the Cloud Function.
Expand Down Expand Up @@ -55,15 +55,20 @@ inputs:
description: |-
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.
required: false

env_vars_file:
description: |-
Path to a local YAML file with definitions for all environment variables. Only one of env_vars or env_vars_file can be specified.
required: false

labels:
description: |-
List of key-value pairs to set as function labels in the form label1=VALUE1,label2=VALUE2.
required: false

entry_point:
description: |-
Name of a function (as defined in source code) that will be executed. Defaults to the resource name suffix, if not specified.
Name of a function (as defined in source code) that will be executed. Defaults to the resource name suffix, if not specified.
required: false

runtime:
Expand Down
36 changes: 21 additions & 15 deletions src/cloudFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { cloudfunctions_v1 } from 'googleapis';
import fs from 'fs';
import YAML from 'yaml';

export type EnvVar = {
export type KVPair = {
[key: string]: string;
};

Expand All @@ -40,6 +40,7 @@ export type EnvVar = {
* @param eventTriggerType Specifies which action should trigger the function.
* @param eventTriggerResource Specifies which resource from eventTrigger is observed.
* @param eventTriggerService The hostname of the service that should be observed.
* @param labels List of key-value pairs to set as function labels.
*/

export type CloudFunctionOptions = {
Expand All @@ -59,6 +60,7 @@ export type CloudFunctionOptions = {
eventTriggerType?: string;
eventTriggerResource?: string;
eventTriggerService?: string;
labels?: string;
};

/**
Expand Down Expand Up @@ -121,14 +123,18 @@ export class CloudFunction {
// Parse env vars
let envVars;
if (opts?.envVars) {
envVars = this.parseEnvVars(opts.envVars);
envVars = this.parseKVPairs(opts.envVars);
request.environmentVariables = envVars;
}
if (opts?.envVarsFile) {
envVars = this.parseEnvVarsFile(opts.envVarsFile);
request.environmentVariables = envVars;
}

if (opts?.labels) {
request.labels = this.parseKVPairs(opts.labels);
}

this.request = request;
this.name = opts.name;
this.sourceDir = opts.sourceDir ? opts.sourceDir : './';
Expand All @@ -144,24 +150,24 @@ export class CloudFunction {
}

/**
* Parses a string of the format `KEY1=VALUE1`.
* Parses a string of the format `KEY1=VALUE1,KEY2=VALUE2`.
*
* @param envVarInput Env var string to parse.
* @param values String with key/value pairs to parse.
* @returns map of type {KEY1:VALUE1}
*/
protected parseEnvVars(envVarInput: string): EnvVar {
const envVarList = envVarInput.split(',');
const envVars: EnvVar = {};
envVarList.forEach((envVar) => {
if (!envVar.includes('=')) {
protected parseKVPairs(values: string): KVPair {
const valuePairs = values.split(',');
const kvPairs: KVPair = {};
valuePairs.forEach((pair) => {
if (!pair.includes('=')) {
throw new TypeError(
`Env Vars must be in "KEY1=VALUE1,KEY2=VALUE2" format, received ${envVar}`,
`The expected data format should be "KEY1=VALUE1", got "${pair}" while parsing "${values}"`,
);
}
const keyValue = envVar.split('=');
envVars[keyValue[0]] = keyValue[1];
const keyValue = pair.split('=');
kvPairs[keyValue[0]] = keyValue[1];
});
return envVars;
return kvPairs;
}

/**
Expand All @@ -170,9 +176,9 @@ export class CloudFunction {
* @param envVarsFile env var file path.
* @returns map of type {KEY1:VALUE1}
*/
protected parseEnvVarsFile(envVarFilePath: string): EnvVar {
protected parseEnvVarsFile(envVarFilePath: string): KVPair {
const content = fs.readFileSync(envVarFilePath, 'utf-8');
const yamlContent = YAML.parse(content) as EnvVar;
const yamlContent = YAML.parse(content) as KVPair;
for (const [key, val] of Object.entries(yamlContent)) {
if (typeof key !== 'string' || typeof val !== 'string') {
throw new Error(
Expand Down
1 change: 1 addition & 0 deletions src/cloudFunctionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export class CloudFunctionClient {
'eventTrigger.eventType',
'eventTrigger.resource',
'eventTrigger.service',
'labels',
];
const updateFunctionRequest: cloudfunctions_v1.Params$Resource$Projects$Locations$Functions$Patch = {
updateMask: updateMasks.join(','),
Expand Down
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ async function run(): Promise<void> {
const eventTriggerType = core.getInput('event_trigger_type');
const eventTriggerResource = core.getInput('event_trigger_resource');
const eventTriggerService = core.getInput('event_trigger_service');
const labels = core.getInput('labels');

// Create Cloud Functions client
const client = new CloudFunctionClient(region, { projectId, credentials });
Expand All @@ -59,6 +60,7 @@ async function run(): Promise<void> {
eventTriggerService,
vpcConnector,
serviceAccountEmail,
labels,
});

// Deploy function
Expand Down
33 changes: 32 additions & 1 deletion tests/cloudFunction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,28 @@ describe('CloudFunction', function () {
expect(cf.request.environmentVariables?.KEY1).equal('VALUE1');
});

it('creates an http function with one label', function () {
const labels = 'label1=value1';
const cf = new CloudFunction({ name, runtime, parent, labels });
expect(cf.request.name).equal(`${parent}/functions/${name}`);
expect(cf.request.runtime).equal(runtime);
expect(cf.request.httpsTrigger).not.to.be.null;
expect(cf.request.labels?.label1).equal('value1');
});

it('creates an http function with two labels', function () {
const labels = 'label1=value1,label2=value2';
const cf = new CloudFunction({ name, runtime, parent, labels });
expect(cf.request.name).equal(`${parent}/functions/${name}`);
expect(cf.request.runtime).equal(runtime);
expect(cf.request.httpsTrigger).not.to.be.null;
expect(cf.request.labels?.label1).equal('value1');
expect(cf.request.labels?.label2).equal('value2');
});

it('creates a http function with optionals', function () {
const envVars = 'KEY1=VALUE1';
const labels = 'label1=value1';
const funcOptions = {
name: name,
description: 'foo',
Expand All @@ -40,6 +60,7 @@ describe('CloudFunction', function () {
timeout: '500',
maxInstances: 10,
availableMemoryMb: 512,
labels: labels,
};
const cf = new CloudFunction(funcOptions);
expect(cf.request.name).equal(`${parent}/functions/${name}`);
Expand All @@ -56,6 +77,7 @@ describe('CloudFunction', function () {
expect(cf.request.availableMemoryMb).equal(funcOptions.availableMemoryMb);
expect(cf.request.httpsTrigger).not.to.be.null;
expect(cf.request.environmentVariables?.KEY1).equal('VALUE1');
expect(cf.request.labels?.label1).equal('value1');
});

it('creates a http function with three envVars', function () {
Expand All @@ -74,7 +96,16 @@ describe('CloudFunction', function () {
expect(function () {
new CloudFunction({ name, runtime, parent, envVars });
}).to.throw(
'Env Vars must be in "KEY1=VALUE1,KEY2=VALUE2" format, received KEY1',
'The expected data format should be "KEY1=VALUE1", got "KEY1" while parsing "KEY1,VALUE1"',
);
});

it('throws an error with bad labels', function () {
const envVars = 'label1=value1,label2';
expect(function () {
new CloudFunction({ name, runtime, parent, envVars });
}).to.throw(
'The expected data format should be "KEY1=VALUE1", got "label2" while parsing "label1=value1,label2"',
);
});

Expand Down

0 comments on commit 9f988bd

Please sign in to comment.