Skip to content

Commit

Permalink
Add region support for europe-west2 and asia-east2 (#435)
Browse files Browse the repository at this point in the history
* Add new regions, fix bugs, refactor

* change from ...region to region array

* add test for specifying all regions, add memory type to catch compilcation errors

* capitalize memory type

* better check of error being thrown

* add changelog

* lower case feature in changelog
  • Loading branch information
thechenky committed May 9, 2019
1 parent ecfb4ad commit 7b5ac4b
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 91 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feature - Adds region support for europe-west2 and asia-east2
81 changes: 60 additions & 21 deletions spec/function-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('FunctionBuilder', () => {
delete process.env.GCLOUD_PROJECT;
});

it('should allow region to be set', () => {
it('should allow supported region to be set', () => {
let fn = functions
.region('us-east1')
.auth.user()
Expand All @@ -42,7 +42,7 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
});

it('should allow multiple regions to be set', () => {
it('should allow multiple supported regions to be set', () => {
let fn = functions
.region('us-east1', 'us-central1')
.auth.user()
Expand All @@ -51,7 +51,30 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']);
});

it('should allow runtime options to be set', () => {
it('should allow all supported regions to be set', () => {
let fn = functions
.region(
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1'
)
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal([
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1',
]);
});

it('should allow valid runtime options to be set', () => {
let fn = functions
.runWith({
timeoutSeconds: 90,
Expand All @@ -64,95 +87,111 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should allow both region and runtime options to be set', () => {
it('should allow both supported region and valid runtime options to be set', () => {
let fn = functions
.region('us-east1')
.region('europe-west2')
.runWith({
timeoutSeconds: 90,
memory: '256MB',
})
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
expect(fn.__trigger.regions).to.deep.equal(['europe-west2']);
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should allow both region and runtime options to be set (reverse order)', () => {
it('should allow both valid runtime options and supported region to be set in reverse order', () => {
let fn = functions
.runWith({
timeoutSeconds: 90,
memory: '256MB',
})
.region('us-east1')
.region('europe-west1')
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
expect(fn.__trigger.regions).to.deep.equal(['europe-west1']);
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should throw an error if user chooses an unsupported memory allocation', () => {
it('should fail if valid runtime options but unsupported region are set (reverse order)', () => {
expect(() => {
functions
.runWith({ timeoutSeconds: 90, memory: '256MB' })
.region('unsupported');
}).to.throw(Error, 'region');
});

it('should fail if supported region but invalid runtime options are set (reverse order)', () => {
expect(() => {
functions
.region('asia-northeast1')
.runWith({ timeoutSeconds: 600, memory: '256MB' });
}).to.throw(Error, 'TimeoutSeconds');
});

it('should throw an error if user chooses an invalid memory allocation', () => {
expect(() => {
return functions.runWith({
memory: 'unsupported',
} as any);
}).to.throw(Error);
}).to.throw(Error, 'memory');

expect(() => {
return functions.region('us-east1').runWith({
memory: 'unsupported',
} as any);
}).to.throw(Error);
}).to.throw(Error, 'memory');
});

it('should throw an error if user chooses an invalid timeoutSeconds', () => {
expect(() => {
return functions.runWith({
timeoutSeconds: 1000000,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'TimeoutSeconds');

expect(() => {
return functions.region('us-east1').runWith({
return functions.region('asia-east2').runWith({
timeoutSeconds: 1000000,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'TimeoutSeconds');
});

it('should throw an error if user chooses an invalid region', () => {
expect(() => {
return functions.region('unsupported');
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported').runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported', 'us-east1');
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported', 'us-east1').runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'region');
});

it('should throw an error if user chooses no region when using .region()', () => {
expect(() => {
return functions.region();
}).to.throw(Error);
}).to.throw(Error, 'at least one region');

expect(() => {
return functions.region().runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'at least one region');
});
});
164 changes: 94 additions & 70 deletions src/function-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,72 +32,112 @@ import * as https from './providers/https';
import * as pubsub from './providers/pubsub';
import * as remoteConfig from './providers/remoteConfig';
import * as storage from './providers/storage';
import {
CloudFunction,
EventContext,
Runnable,
TriggerAnnotated,
Schedule,
} from './cloud-functions';
import { CloudFunction, EventContext, Schedule } from './cloud-functions';

/**
* Configure the regions that the function is deployed to.
* @param regions One of more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
* List of all regions supported by Cloud Functions.
*/
export function region(...regions: string[]) {
if (!regions.length) {
throw new Error('You must specify at least one region');
const SUPPORTED_REGIONS = [
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1',
];

/**
* List of available memory options supported by Cloud Functions.
*/
const VALID_MEMORY_OPTS = ['128MB', '256MB', '512MB', '1GB', '2GB'];

// Adding this memory type here to error on compile for TS users.
// Unfortunately I have not found a way to merge this with VALID_MEMORY_OPS
// without it being super ugly. But here they are right next to each other at least.
type Memory = '128MB' | '256MB' | '512MB' | '1GB' | '2GB';

/**
* Cloud Functions max timeout value.
*/
const MAX_TIMEOUT_SECONDS = 540;

/**
* Assert that the runtime options passed in are valid.
* @param runtimeOptions object containing memory and timeout information.
* @throws { Error } Memory and TimeoutSeconds values must be valid.
*/
function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
if (
runtimeOptions.memory &&
!_.includes(VALID_MEMORY_OPTS, runtimeOptions.memory)
) {
throw new Error(
`The only valid memory allocation values are: ${VALID_MEMORY_OPTS.join(
', '
)}`
);
}
if (
_.difference(regions, [
'us-central1',
'us-east1',
'europe-west1',
'asia-northeast1',
]).length
runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error(
"The only valid regions are 'us-central1', 'us-east1', 'europe-west1', and 'asia-northeast1'"
`TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}`
);
}
return new FunctionBuilder({ regions });
return true;
}

/**
* Assert regions specified are valid.
* @param regions list of regions.
* @throws { Error } Regions must be in list of supported regions.
*/
function assertRegionsAreValid(regions: string[]): boolean {
if (!regions.length) {
throw new Error('You must specify at least one region');
}
if (_.difference(regions, SUPPORTED_REGIONS).length) {
throw new Error(
`The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}`
);
}
return true;
}

/**
* Configure the regions that the function is deployed to.
* @param regions One of more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
*/
export function region(...regions: string[]): FunctionBuilder {
if (assertRegionsAreValid(regions)) {
return new FunctionBuilder({ regions });
}
}

/**
* Configure runtime options for the function.
* @param runtimeOptions Object with 2 optional fields:
* 1. `timeoutSeconds`: timeout for the function in seconds, possible values are 0 to 540
* 2. `memory`: amount of memory to allocate to the function,
* possible values are: '128MB', '256MB', '512MB', '1GB', and '2GB'.
*/
export function runWith(runtimeOptions: {
timeoutSeconds?: number;
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
}) {
if (
runtimeOptions.memory &&
!_.includes(
['128MB', '256MB', '512MB', '1GB', '2GB'],
runtimeOptions.memory
)
) {
throw new Error(
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
);
}
if (
runtimeOptions.timeoutSeconds > 540 ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error('TimeoutSeconds must be between 0 and 540');
export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
if (assertRuntimeOptionsValid(runtimeOptions)) {
return new FunctionBuilder(runtimeOptions);
}
return new FunctionBuilder(runtimeOptions);
}

export interface RuntimeOptions {
timeoutSeconds?: number;
memory?: Memory;
}

export interface DeploymentOptions {
regions?: string[];
timeoutSeconds?: number;
memory?: string;
memory?: Memory;
schedule?: Schedule;
}

Expand All @@ -109,10 +149,12 @@ export class FunctionBuilder {
* @param regions One or more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
*/
region = (...regions: string[]) => {
this.options.regions = regions;
return this;
};
region(...regions: string[]): FunctionBuilder {
if (assertRegionsAreValid(regions)) {
this.options.regions = regions;
return this;
}
}

/**
* Configure runtime options for the function.
Expand All @@ -121,30 +163,12 @@ export class FunctionBuilder {
* 2. memory: amount of memory to allocate to the function, possible values are:
* '128MB', '256MB', '512MB', '1GB', and '2GB'.
*/
runWith = (runtimeOptions: {
timeoutSeconds?: number;
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
}) => {
if (
runtimeOptions.memory &&
!_.includes(
['128MB', '256MB', '512MB', '1GB', '2GB'],
runtimeOptions.memory
)
) {
throw new Error(
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
);
}
if (
runtimeOptions.timeoutSeconds > 540 ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error('TimeoutSeconds must be between 0 and 540');
runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
if (assertRuntimeOptionsValid(runtimeOptions)) {
this.options = _.assign(this.options, runtimeOptions);
return this;
}
this.options = _.assign(this.options, runtimeOptions);
return this;
};
}

get https() {
return {
Expand Down

0 comments on commit 7b5ac4b

Please sign in to comment.