Add util function for parsing durations and many more tests (#69)
This commit is contained in:
parent
1e9245c68a
commit
057960bb62
110
dist/main/index.js
vendored
110
dist/main/index.js
vendored
@ -610,7 +610,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.trimmedString = exports.fromBase64 = exports.toBase64 = exports.explodeStrings = exports.removeExportedCredentials = exports.writeSecureFile = void 0;
|
||||
exports.parseDuration = exports.trimmedString = exports.fromBase64 = exports.toBase64 = exports.explodeStrings = exports.removeExportedCredentials = exports.writeSecureFile = void 0;
|
||||
const fs_1 = __webpack_require__(747);
|
||||
const crypto_1 = __importDefault(__webpack_require__(417));
|
||||
const path_1 = __importDefault(__webpack_require__(622));
|
||||
@ -673,17 +673,38 @@ exports.removeExportedCredentials = removeExportedCredentials;
|
||||
* of trimmed strings.
|
||||
*/
|
||||
function explodeStrings(input) {
|
||||
if (input == null || input.length === 0) {
|
||||
if (!input || input.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
const list = new Array();
|
||||
for (const line of input.split(`\n`)) {
|
||||
for (const piece of line.split(',')) {
|
||||
const entry = piece.trim();
|
||||
if (entry !== '') {
|
||||
list.push(entry);
|
||||
}
|
||||
let curr = '';
|
||||
let escaped = false;
|
||||
for (const ch of input) {
|
||||
if (escaped) {
|
||||
curr += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
escaped = true;
|
||||
continue;
|
||||
case ',':
|
||||
case '\n': {
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
curr += ch;
|
||||
}
|
||||
}
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@ -705,7 +726,7 @@ exports.toBase64 = toBase64;
|
||||
*/
|
||||
function fromBase64(s) {
|
||||
const str = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (str.length % 4)
|
||||
while (s.length % 4)
|
||||
s += '=';
|
||||
return Buffer.from(str, 'base64').toString('utf8');
|
||||
}
|
||||
@ -718,6 +739,65 @@ function trimmedString(s) {
|
||||
return s ? s.trim() : '';
|
||||
}
|
||||
exports.trimmedString = trimmedString;
|
||||
/**
|
||||
* parseDuration parses a user-supplied string duration with optional suffix and
|
||||
* returns a number representing the number of seconds. It returns 0 when given
|
||||
* the empty string.
|
||||
*
|
||||
* @param str Duration string
|
||||
*/
|
||||
function parseDuration(str) {
|
||||
const given = (str || '').trim();
|
||||
if (!given) {
|
||||
return 0;
|
||||
}
|
||||
let total = 0;
|
||||
let curr = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
continue;
|
||||
case ',':
|
||||
continue;
|
||||
case 's': {
|
||||
total += +curr;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
total += +curr * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
total += +curr * 60 * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
curr += ch;
|
||||
break;
|
||||
default:
|
||||
throw new SyntaxError(`Unsupported character "${ch}" at position ${i}`);
|
||||
}
|
||||
}
|
||||
// Anything left over is seconds
|
||||
if (curr) {
|
||||
total += +curr;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
exports.parseDuration = parseDuration;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -1901,8 +1981,8 @@ class CredentialsJSONClient {
|
||||
const signature = signer.sign(__classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['private_key']);
|
||||
return message + '.' + (0, utils_1.toBase64)(signature);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Failed to sign auth token: ${e}`);
|
||||
catch (err) {
|
||||
throw new Error(`Failed to sign auth token using ${this.getServiceAccount()}: ${err}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -2190,8 +2270,8 @@ class BaseClient {
|
||||
expiration: parsed['expireTime'],
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${e}`);
|
||||
catch (err) {
|
||||
throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${err}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -2267,8 +2347,8 @@ class WorkloadIdentityClient {
|
||||
return project;
|
||||
}
|
||||
/**
|
||||
* getAuthToken generates a Google Cloud federated token using the provided OIDC
|
||||
* token and Workload Identity Provider.
|
||||
* getAuthToken generates a Google Cloud federated token using the provided
|
||||
* OIDC token and Workload Identity Provider.
|
||||
*/
|
||||
getAuthToken() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
|
98
dist/post/index.js
vendored
98
dist/post/index.js
vendored
@ -449,7 +449,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.trimmedString = exports.fromBase64 = exports.toBase64 = exports.explodeStrings = exports.removeExportedCredentials = exports.writeSecureFile = void 0;
|
||||
exports.parseDuration = exports.trimmedString = exports.fromBase64 = exports.toBase64 = exports.explodeStrings = exports.removeExportedCredentials = exports.writeSecureFile = void 0;
|
||||
const fs_1 = __webpack_require__(747);
|
||||
const crypto_1 = __importDefault(__webpack_require__(417));
|
||||
const path_1 = __importDefault(__webpack_require__(622));
|
||||
@ -512,17 +512,38 @@ exports.removeExportedCredentials = removeExportedCredentials;
|
||||
* of trimmed strings.
|
||||
*/
|
||||
function explodeStrings(input) {
|
||||
if (input == null || input.length === 0) {
|
||||
if (!input || input.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
const list = new Array();
|
||||
for (const line of input.split(`\n`)) {
|
||||
for (const piece of line.split(',')) {
|
||||
const entry = piece.trim();
|
||||
if (entry !== '') {
|
||||
list.push(entry);
|
||||
}
|
||||
let curr = '';
|
||||
let escaped = false;
|
||||
for (const ch of input) {
|
||||
if (escaped) {
|
||||
curr += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
escaped = true;
|
||||
continue;
|
||||
case ',':
|
||||
case '\n': {
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
curr += ch;
|
||||
}
|
||||
}
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@ -544,7 +565,7 @@ exports.toBase64 = toBase64;
|
||||
*/
|
||||
function fromBase64(s) {
|
||||
const str = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (str.length % 4)
|
||||
while (s.length % 4)
|
||||
s += '=';
|
||||
return Buffer.from(str, 'base64').toString('utf8');
|
||||
}
|
||||
@ -557,6 +578,65 @@ function trimmedString(s) {
|
||||
return s ? s.trim() : '';
|
||||
}
|
||||
exports.trimmedString = trimmedString;
|
||||
/**
|
||||
* parseDuration parses a user-supplied string duration with optional suffix and
|
||||
* returns a number representing the number of seconds. It returns 0 when given
|
||||
* the empty string.
|
||||
*
|
||||
* @param str Duration string
|
||||
*/
|
||||
function parseDuration(str) {
|
||||
const given = (str || '').trim();
|
||||
if (!given) {
|
||||
return 0;
|
||||
}
|
||||
let total = 0;
|
||||
let curr = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
continue;
|
||||
case ',':
|
||||
continue;
|
||||
case 's': {
|
||||
total += +curr;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
total += +curr * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
total += +curr * 60 * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
curr += ch;
|
||||
break;
|
||||
default:
|
||||
throw new SyntaxError(`Unsupported character "${ch}" at position ${i}`);
|
||||
}
|
||||
}
|
||||
// Anything left over is seconds
|
||||
if (curr) {
|
||||
total += +curr;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
exports.parseDuration = parseDuration;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
@ -138,8 +138,8 @@ export class BaseClient {
|
||||
accessToken: parsed['accessToken'],
|
||||
expiration: parsed['expireTime'],
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${e}`);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +96,8 @@ export class CredentialsJSONClient implements AuthClient {
|
||||
|
||||
const signature = signer.sign(this.#credentials['private_key']);
|
||||
return message + '.' + toBase64(signature);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to sign auth token: ${e}`);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to sign auth token using ${this.getServiceAccount()}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,8 +71,8 @@ export class WorkloadIdentityClient implements AuthClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* getAuthToken generates a Google Cloud federated token using the provided OIDC
|
||||
* token and Workload Identity Provider.
|
||||
* getAuthToken generates a Google Cloud federated token using the provided
|
||||
* OIDC token and Workload Identity Provider.
|
||||
*/
|
||||
async getAuthToken(): Promise<string> {
|
||||
const stsURL = new URL('https://sts.googleapis.com/v1/token');
|
||||
|
103
src/utils.ts
103
src/utils.ts
@ -62,19 +62,43 @@ export async function removeExportedCredentials(): Promise<string> {
|
||||
* of trimmed strings.
|
||||
*/
|
||||
export function explodeStrings(input: string): Array<string> {
|
||||
if (input == null || input.length === 0) {
|
||||
if (!input || input.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const list = new Array<string>();
|
||||
for (const line of input.split(`\n`)) {
|
||||
for (const piece of line.split(',')) {
|
||||
const entry = piece.trim();
|
||||
if (entry !== '') {
|
||||
list.push(entry);
|
||||
let curr = '';
|
||||
let escaped = false;
|
||||
for (const ch of input) {
|
||||
if (escaped) {
|
||||
curr += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
escaped = true;
|
||||
continue;
|
||||
case ',':
|
||||
case '\n': {
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
curr += ch;
|
||||
}
|
||||
}
|
||||
|
||||
const val = curr.trim();
|
||||
if (val) {
|
||||
list.push(val);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -95,7 +119,7 @@ export function toBase64(s: string | Buffer): string {
|
||||
*/
|
||||
export function fromBase64(s: string): string {
|
||||
const str = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (str.length % 4) s += '=';
|
||||
while (s.length % 4) s += '=';
|
||||
return Buffer.from(str, 'base64').toString('utf8');
|
||||
}
|
||||
|
||||
@ -103,6 +127,69 @@ export function fromBase64(s: string): string {
|
||||
* trimmedString returns a string trimmed of whitespace. If the input string is
|
||||
* null, then it returns the empty string.
|
||||
*/
|
||||
export function trimmedString(s: string): string {
|
||||
export function trimmedString(s: string | undefined | null): string {
|
||||
return s ? s.trim() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* parseDuration parses a user-supplied string duration with optional suffix and
|
||||
* returns a number representing the number of seconds. It returns 0 when given
|
||||
* the empty string.
|
||||
*
|
||||
* @param str Duration string
|
||||
*/
|
||||
export function parseDuration(str: string): number {
|
||||
const given = (str || '').trim();
|
||||
if (!given) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
let curr = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
continue;
|
||||
case ',':
|
||||
continue;
|
||||
case 's': {
|
||||
total += +curr;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
total += +curr * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
case 'h': {
|
||||
total += +curr * 60 * 60;
|
||||
curr = '';
|
||||
break;
|
||||
}
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
curr += ch;
|
||||
break;
|
||||
default:
|
||||
throw new SyntaxError(`Unsupported character "${ch}" at position ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Anything left over is seconds
|
||||
if (curr) {
|
||||
total += +curr;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
@ -4,12 +4,36 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { existsSync } from 'fs';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
|
||||
import { removeExportedCredentials } from '../src/utils';
|
||||
import { writeSecureFile } from '../src/utils';
|
||||
import {
|
||||
explodeStrings,
|
||||
fromBase64,
|
||||
parseDuration,
|
||||
removeExportedCredentials,
|
||||
toBase64,
|
||||
trimmedString,
|
||||
writeSecureFile,
|
||||
} from '../src/utils';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('#writeSecureFile', () => {
|
||||
it('writes data to the file', async () => {
|
||||
const tmp = tmpdir();
|
||||
const filePath = await writeSecureFile(tmp, 'hi');
|
||||
expect(existsSync(filePath)).to.be.true;
|
||||
expect(readFileSync(filePath).toString('utf8')).to.eq('hi');
|
||||
});
|
||||
|
||||
it('generates a random name', async () => {
|
||||
const tmp = tmpdir();
|
||||
const filePath1 = await writeSecureFile(tmp, 'hi');
|
||||
const filePath2 = await writeSecureFile(tmp, 'bye');
|
||||
expect(filePath1).to.not.eq(filePath2);
|
||||
expect(filePath1).to.not.eq(filePath2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('post', () => {
|
||||
describe('#removeExportedCredentials', () => {
|
||||
it('does nothing when GOOGLE_GHA_CREDS_PATH is unset', async () => {
|
||||
delete process.env.GOOGLE_GHA_CREDS_PATH;
|
||||
@ -32,4 +56,196 @@ describe('post', () => {
|
||||
expect(pth).to.eq('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#explodeStrings', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'padded empty string',
|
||||
input: ' ',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'comma-separated',
|
||||
input: 'hello , world , and goodbye',
|
||||
expected: ['hello', 'world', 'and goodbye'],
|
||||
},
|
||||
{
|
||||
name: 'newline-separated',
|
||||
input: `
|
||||
hello
|
||||
world
|
||||
and goodbye`,
|
||||
expected: ['hello', 'world', 'and goodbye'],
|
||||
},
|
||||
{
|
||||
name: 'comma and newline-separated',
|
||||
input: `
|
||||
hello,
|
||||
world,
|
||||
and goodbye,`,
|
||||
expected: ['hello', 'world', 'and goodbye'],
|
||||
},
|
||||
{
|
||||
name: 'comma-escaped',
|
||||
input: 'hello , world , and\\, goodbye',
|
||||
expected: ['hello', 'world', 'and, goodbye'],
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
expect(explodeStrings(tc.input)).to.eql(tc.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toBase64', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
name: 'empty buffer',
|
||||
input: Buffer.from(''),
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
name: 'encodes string',
|
||||
input: 'hello',
|
||||
expected: 'aGVsbG8',
|
||||
},
|
||||
{
|
||||
name: 'encodes buffer',
|
||||
input: Buffer.from('hello'),
|
||||
expected: 'aGVsbG8',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
expect(toBase64(tc.input)).to.eq(tc.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fromBase64', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'decodes',
|
||||
input: 'aGVsbG8',
|
||||
expected: 'hello',
|
||||
},
|
||||
{
|
||||
name: 'decodes padded',
|
||||
input: 'aGVsbG8==',
|
||||
expected: 'hello',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
expect(fromBase64(tc.input)).to.eq(tc.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trimmedString', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'null',
|
||||
input: null,
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
name: 'undefined',
|
||||
input: undefined,
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
name: 'trims',
|
||||
input: ' hello world ',
|
||||
expected: 'hello world',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
expect(trimmedString(tc.input)).to.eq(tc.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parseDuration', () => {
|
||||
const cases = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: 'unitless',
|
||||
input: '149585',
|
||||
expected: 149585,
|
||||
},
|
||||
{
|
||||
name: 'with commas',
|
||||
input: '149,585',
|
||||
expected: 149585,
|
||||
},
|
||||
{
|
||||
name: 'suffix seconds',
|
||||
input: '149585s',
|
||||
expected: 149585,
|
||||
},
|
||||
{
|
||||
name: 'suffix minutes',
|
||||
input: '25m',
|
||||
expected: 1500,
|
||||
},
|
||||
{
|
||||
name: 'suffix hours',
|
||||
input: '12h',
|
||||
expected: 43200,
|
||||
},
|
||||
{
|
||||
name: 'suffix hours minutes seconds',
|
||||
input: '12h10m55s',
|
||||
expected: 43855,
|
||||
},
|
||||
{
|
||||
name: 'commas and spaces',
|
||||
input: '12h, 10m 55s',
|
||||
expected: 43855,
|
||||
},
|
||||
{
|
||||
name: 'invalid',
|
||||
input: '12h blueberries',
|
||||
error: 'Unsupported character "b" at position 4',
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach((tc) => {
|
||||
it(tc.name, async () => {
|
||||
if (tc.expected) {
|
||||
expect(parseDuration(tc.input)).to.eq(tc.expected);
|
||||
} else if (tc.error) {
|
||||
expect(() => {
|
||||
parseDuration(tc.input);
|
||||
}).to.throw(tc.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user