Skip to content

Commit 726b4f7

Browse files
authored
Parse timeouts (#215)
1 parent b863bde commit 726b4f7

File tree

6 files changed

+154
-4
lines changed

6 files changed

+154
-4
lines changed

.github/workflows/integration.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ jobs:
224224
build_environment_variables_file: './tests/env-var-files/test.good.yaml'
225225
min_instances: 2
226226
max_instances: 5
227+
timeout: 300
227228

228229
# Auth as the main account for integration and cleanup
229230
- uses: 'google-github-actions/auth@main'

action.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ inputs:
106106
timeout:
107107
description: |-
108108
The function execution timeout.
109+
default: '60s'
109110
required: false
110111

111112
min_instances:

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { CloudFunctionsClient, CloudFunction } from './client';
2727
import {
2828
errorMessage,
2929
isServiceAccountKey,
30+
parseDuration,
3031
parseKVString,
3132
parseKVStringAndFile,
3233
parseServiceAccountKeyJSON,
@@ -54,7 +55,7 @@ async function run(): Promise<void> {
5455
);
5556
const ingressSettings = presence(getInput('ingress_settings'));
5657
const serviceAccountEmail = presence(getInput('service_account_email'));
57-
const timeout = presence(getInput('timeout'));
58+
const timeout = parseDuration(getInput('timeout'));
5859
const maxInstances = presence(getInput('max_instances'));
5960
const minInstances = presence(getInput('min_instances'));
6061
const eventTriggerType = presence(getInput('event_trigger_type'));
@@ -118,6 +119,11 @@ async function run(): Promise<void> {
118119
);
119120
}
120121
}
122+
if (timeout <= 0) {
123+
throw new Error(
124+
`The 'timeout' parameter must be > 0 seconds (got ${timeout})`,
125+
);
126+
}
121127

122128
// Create Cloud Functions client
123129
const client = new CloudFunctionsClient({
@@ -151,7 +157,7 @@ async function run(): Promise<void> {
151157
// network: network, // TODO: add support
152158
serviceAccountEmail: serviceAccountEmail,
153159
// sourceToken: sourceToken, // TODO: add support
154-
timeout: timeout,
160+
timeout: `${timeout}s`,
155161
vpcConnector: vpcConnector,
156162
vpcConnectorEgressSettings: vpcConnectorEgressSettings,
157163
};

src/util.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export function getGcloudIgnores(dir: string): string[] {
9393
* removeFile removes the file at the given path. If the file does not exist, it
9494
* does nothing.
9595
*
96+
* TODO(sethvargo): Candidate for centralization.
97+
*
9698
* @param filePath Path of the file on disk to delete.
9799
* @returns Path of the file that was removed.
98100
*/
@@ -114,6 +116,9 @@ export function removeFile(filePath: string): string {
114116
/**
115117
* fromBase64 base64 decodes the result, taking into account URL and standard
116118
* encoding with and without padding.
119+
*
120+
* TODO(sethvargo): Candidate for centralization.
121+
*
117122
*/
118123
export function fromBase64(s: string): string {
119124
let str = s.replace(/-/g, '+').replace(/_/g, '/');
@@ -137,6 +142,8 @@ export type ServiceAccountKey = {
137142
/**
138143
* parseServiceAccountKeyJSON attempts to parse the given string as a service
139144
* account key JSON. It handles if the string is base64-encoded.
145+
*
146+
* TODO(sethvargo): Candidate for centralization.
140147
*/
141148
export function parseServiceAccountKeyJSON(
142149
str: string,
@@ -185,6 +192,8 @@ type KVPair = Record<string, string>;
185192
/**
186193
* Parses a string of the format `KEY1=VALUE1,KEY2=VALUE2`.
187194
*
195+
* TODO(sethvargo): Candidate for centralization.
196+
*
188197
* @param str String with key/value pairs to parse.
189198
*/
190199
export function parseKVString(str: string): KVPair {
@@ -294,6 +303,8 @@ export function parseKVStringAndFile(
294303
* presence takes the given string and converts it to undefined iff it's null,
295304
* undefined, or the empty string. Otherwise, it returns the trimmed string.
296305
*
306+
* TODO(sethvargo): Candidate for centralization.
307+
*
297308
* @param str The string to check
298309
*/
299310
export function presence(str: string | null | undefined): string | undefined {
@@ -307,6 +318,9 @@ export function presence(str: string | null | undefined): string | undefined {
307318

308319
/**
309320
* errorMessage extracts the error message from the given error.
321+
*
322+
* TODO(sethvargo): Candidate for centralization.
323+
*
310324
*/
311325
export function errorMessage(err: unknown): string {
312326
if (!err) {
@@ -325,3 +339,68 @@ export function errorMessage(err: unknown): string {
325339
msg = msg[0].toLowerCase() + msg.slice(1);
326340
return msg;
327341
}
342+
343+
/**
344+
* parseDuration parses a user-supplied string duration with optional suffix and
345+
* returns a number representing the number of seconds. It returns 0 when given
346+
* the empty string.
347+
*
348+
* TODO(sethvargo): Candidate for centralization.
349+
*
350+
* @param str Duration string
351+
*/
352+
export function parseDuration(str: string): number {
353+
const given = (str || '').trim();
354+
if (!given) {
355+
return 0;
356+
}
357+
358+
let total = 0;
359+
let curr = '';
360+
for (let i = 0; i < str.length; i++) {
361+
const ch = str[i];
362+
switch (ch) {
363+
case ' ':
364+
continue;
365+
case ',':
366+
continue;
367+
case 's': {
368+
total += +curr;
369+
curr = '';
370+
break;
371+
}
372+
case 'm': {
373+
total += +curr * 60;
374+
curr = '';
375+
break;
376+
}
377+
case 'h': {
378+
total += +curr * 60 * 60;
379+
curr = '';
380+
break;
381+
}
382+
383+
case '0':
384+
case '1':
385+
case '2':
386+
case '3':
387+
case '4':
388+
case '5':
389+
case '6':
390+
case '7':
391+
case '8':
392+
case '9':
393+
curr += ch;
394+
break;
395+
default:
396+
throw new SyntaxError(`Unsupported character "${ch}" at position ${i}`);
397+
}
398+
}
399+
400+
// Anything left over is seconds
401+
if (curr) {
402+
total += +curr;
403+
}
404+
405+
return total;
406+
}

tests/util.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import 'mocha';
66
import * as path from 'path';
77
import {
88
errorMessage,
9-
parseServiceAccountKeyJSON,
9+
parseDuration,
1010
parseKVFile,
1111
parseKVString,
1212
parseKVStringAndFile,
1313
parseKVYAML,
14+
parseServiceAccountKeyJSON,
1415
presence,
1516
zipDir,
1617
} from '../src/util';
@@ -338,6 +339,68 @@ describe('Util', () => {
338339
});
339340
});
340341
});
342+
343+
describe('#parseDuration', () => {
344+
const cases = [
345+
{
346+
name: 'empty string',
347+
input: '',
348+
expected: 0,
349+
},
350+
{
351+
name: 'unitless',
352+
input: '149585',
353+
expected: 149585,
354+
},
355+
{
356+
name: 'with commas',
357+
input: '149,585',
358+
expected: 149585,
359+
},
360+
{
361+
name: 'suffix seconds',
362+
input: '149585s',
363+
expected: 149585,
364+
},
365+
{
366+
name: 'suffix minutes',
367+
input: '25m',
368+
expected: 1500,
369+
},
370+
{
371+
name: 'suffix hours',
372+
input: '12h',
373+
expected: 43200,
374+
},
375+
{
376+
name: 'suffix hours minutes seconds',
377+
input: '12h10m55s',
378+
expected: 43855,
379+
},
380+
{
381+
name: 'commas and spaces',
382+
input: '12h, 10m 55s',
383+
expected: 43855,
384+
},
385+
{
386+
name: 'invalid',
387+
input: '12h blueberries',
388+
error: 'Unsupported character "b" at position 4',
389+
},
390+
];
391+
392+
cases.forEach((tc) => {
393+
it(tc.name, async () => {
394+
if (tc.expected) {
395+
expect(parseDuration(tc.input)).to.eq(tc.expected);
396+
} else if (tc.error) {
397+
expect(() => {
398+
parseDuration(tc.input);
399+
}).to.throw(tc.error);
400+
}
401+
});
402+
});
403+
});
341404
});
342405

343406
describe('Zip', function () {

0 commit comments

Comments
 (0)