Skip to content

Commit

Permalink
Switch KV joining for gcloud to be a predictable string (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo committed May 17, 2024
1 parent 607383e commit c193fe5
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 60 deletions.
37 changes: 14 additions & 23 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/kv.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export declare function joinKVString(input: KVPair, separator?: string): string;
* string.
*
* @param input KVPair to serialize.
* @param rand String of random characters to use; override for testing.
* @param chars String of characters to use.
*/
export declare function joinKVStringForGCloud(input: KVPair, rand?: string): string;
export declare function joinKVStringForGCloud(input: KVPair, chars?: string): string;
/**
* parseKVString parses a string of the format "KEY1=VALUE1,KEY2=VALUE2" or
* "KEY1=VALUE1\nKEY2=VALUE2". Keys or values that contain a separator must be
Expand Down
44 changes: 16 additions & 28 deletions src/kv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import YAML from 'yaml';
import { readFileSync } from 'fs';
import { randomBytes } from 'crypto';

import { errorMessage } from './errors';
import { presence } from './validations';
Expand Down Expand Up @@ -46,46 +45,35 @@ export function joinKVString(input: KVPair, separator = ','): string {
* string.
*
* @param input KVPair to serialize.
* @param rand String of random characters to use; override for testing.
* @param chars String of characters to use.
*/
export function joinKVStringForGCloud(
input: KVPair,
rand = randomBytes(128).toString('hex'),
chars = ',.!@#$%&*()_=+~`[]{}|:;<>?🚀🍪🐼\u200B',
): string {
const initial = Object.entries(input).join('');

const initial = joinKVString(input, '');
if (initial === '') {
return '';
}

let delim = '';

// Build an increasingly longer delimiter.
for (let i = 0; i < rand.length; i++) {
const ch = rand[i];

// The first character in the delimiter cannot match the last character of
// any values because gcloud parses left-to-right:
//
// https://github.com/google-github-actions/deploy-cloudrun/issues/503
//
if (delim === '' && Object.values(input).some((v) => v.endsWith(ch))) {
continue;
}
const initialMap: Record<string, boolean> = {};
for (let i = 0; i < initial.length; i++) {
initialMap[initial[i]] = true;
}

delim += ch;
if (initial.includes(delim)) {
continue;
let delim = '';
for (let i = 0; i < chars.length; i++) {
const ch = chars[i];
if (!(ch in initialMap)) {
delim = ch;
break;
}

// If we got this far, then the delimiter is not found in the string.
break;
}

if (delim === '' || initial.includes(delim)) {
throw new Error(
`Something extremely probabilistically unlikely has occured - one of ` +
`the KV pairs exactly matched 128 randomly-generated bytes. Please ` +
`retry, or use smaller KV pairs with non-conflicting values.`,
`Something extremely probabilistically unlikely has occured - none of ` +
`the possible delimiters is viable for the input.`,
);
}

Expand Down
12 changes: 6 additions & 6 deletions tests/kv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,23 @@ describe('kv', { concurrency: true }, async () => {
{
name: 'single entry',
input: { FOO: 'bar' },
expected: '^d^FOO=bar',
expected: '^,^FOO=bar',
},
{
name: 'value ends with delim',
input: { HELLO: 'world', GOODBYE: 'mars' },
expected: '^e^HELLO=worldeGOODBYE=mars',
input: { HELLO: 'world,', GOODBYE: 'mars' },
expected: '^.^HELLO=world,.GOODBYE=mars',
},
{
name: 'iterates',
input: { abcdefghijklmnopqrstuvwxyz: '01234567890' },
expected: '^dea^abcdefghijklmnopqrstuvwxyz=01234567890',
input: { HELLO: ',.!@#$%&*()' },
expected: '^_^HELLO=,.!@#$%&*()',
},
];

for await (const tc of cases) {
await suite.test(tc.name, async () => {
const actual = joinKVStringForGCloud(tc.input as KVPair, 'deab');
const actual = joinKVStringForGCloud(tc.input as KVPair);
assert.deepStrictEqual(actual, tc.expected);
});
}
Expand Down

0 comments on commit c193fe5

Please sign in to comment.