Skip to content

Commit 7b5ac4b

Browse files
authored
Add region support for europe-west2 and asia-east2 (firebase#435)
* 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
1 parent ecfb4ad commit 7b5ac4b

File tree

3 files changed

+155
-91
lines changed

3 files changed

+155
-91
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feature - Adds region support for europe-west2 and asia-east2

spec/function-builder.spec.ts

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('FunctionBuilder', () => {
3333
delete process.env.GCLOUD_PROJECT;
3434
});
3535

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

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

54-
it('should allow runtime options to be set', () => {
54+
it('should allow all supported regions to be set', () => {
55+
let fn = functions
56+
.region(
57+
'us-central1',
58+
'us-east1',
59+
'europe-west1',
60+
'europe-west2',
61+
'asia-east2',
62+
'asia-northeast1'
63+
)
64+
.auth.user()
65+
.onCreate(user => user);
66+
67+
expect(fn.__trigger.regions).to.deep.equal([
68+
'us-central1',
69+
'us-east1',
70+
'europe-west1',
71+
'europe-west2',
72+
'asia-east2',
73+
'asia-northeast1',
74+
]);
75+
});
76+
77+
it('should allow valid runtime options to be set', () => {
5578
let fn = functions
5679
.runWith({
5780
timeoutSeconds: 90,
@@ -64,95 +87,111 @@ describe('FunctionBuilder', () => {
6487
expect(fn.__trigger.timeout).to.deep.equal('90s');
6588
});
6689

67-
it('should allow both region and runtime options to be set', () => {
90+
it('should allow both supported region and valid runtime options to be set', () => {
6891
let fn = functions
69-
.region('us-east1')
92+
.region('europe-west2')
7093
.runWith({
7194
timeoutSeconds: 90,
7295
memory: '256MB',
7396
})
7497
.auth.user()
7598
.onCreate(user => user);
7699

77-
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
100+
expect(fn.__trigger.regions).to.deep.equal(['europe-west2']);
78101
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
79102
expect(fn.__trigger.timeout).to.deep.equal('90s');
80103
});
81104

82-
it('should allow both region and runtime options to be set (reverse order)', () => {
105+
it('should allow both valid runtime options and supported region to be set in reverse order', () => {
83106
let fn = functions
84107
.runWith({
85108
timeoutSeconds: 90,
86109
memory: '256MB',
87110
})
88-
.region('us-east1')
111+
.region('europe-west1')
89112
.auth.user()
90113
.onCreate(user => user);
91114

92-
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
115+
expect(fn.__trigger.regions).to.deep.equal(['europe-west1']);
93116
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
94117
expect(fn.__trigger.timeout).to.deep.equal('90s');
95118
});
96119

97-
it('should throw an error if user chooses an unsupported memory allocation', () => {
120+
it('should fail if valid runtime options but unsupported region are set (reverse order)', () => {
121+
expect(() => {
122+
functions
123+
.runWith({ timeoutSeconds: 90, memory: '256MB' })
124+
.region('unsupported');
125+
}).to.throw(Error, 'region');
126+
});
127+
128+
it('should fail if supported region but invalid runtime options are set (reverse order)', () => {
129+
expect(() => {
130+
functions
131+
.region('asia-northeast1')
132+
.runWith({ timeoutSeconds: 600, memory: '256MB' });
133+
}).to.throw(Error, 'TimeoutSeconds');
134+
});
135+
136+
it('should throw an error if user chooses an invalid memory allocation', () => {
98137
expect(() => {
99138
return functions.runWith({
100139
memory: 'unsupported',
101140
} as any);
102-
}).to.throw(Error);
141+
}).to.throw(Error, 'memory');
103142

104143
expect(() => {
105144
return functions.region('us-east1').runWith({
106145
memory: 'unsupported',
107146
} as any);
108-
}).to.throw(Error);
147+
}).to.throw(Error, 'memory');
109148
});
110149

111150
it('should throw an error if user chooses an invalid timeoutSeconds', () => {
112151
expect(() => {
113152
return functions.runWith({
114153
timeoutSeconds: 1000000,
115154
} as any);
116-
}).to.throw(Error);
155+
}).to.throw(Error, 'TimeoutSeconds');
117156

118157
expect(() => {
119-
return functions.region('us-east1').runWith({
158+
return functions.region('asia-east2').runWith({
120159
timeoutSeconds: 1000000,
121160
} as any);
122-
}).to.throw(Error);
161+
}).to.throw(Error, 'TimeoutSeconds');
123162
});
124163

125164
it('should throw an error if user chooses an invalid region', () => {
126165
expect(() => {
127166
return functions.region('unsupported');
128-
}).to.throw(Error);
167+
}).to.throw(Error, 'region');
129168

130169
expect(() => {
131170
return functions.region('unsupported').runWith({
132171
timeoutSeconds: 500,
133172
} as any);
134-
}).to.throw(Error);
173+
}).to.throw(Error, 'region');
135174

136175
expect(() => {
137176
return functions.region('unsupported', 'us-east1');
138-
}).to.throw(Error);
177+
}).to.throw(Error, 'region');
139178

140179
expect(() => {
141180
return functions.region('unsupported', 'us-east1').runWith({
142181
timeoutSeconds: 500,
143182
} as any);
144-
}).to.throw(Error);
183+
}).to.throw(Error, 'region');
145184
});
146185

147186
it('should throw an error if user chooses no region when using .region()', () => {
148187
expect(() => {
149188
return functions.region();
150-
}).to.throw(Error);
189+
}).to.throw(Error, 'at least one region');
151190

152191
expect(() => {
153192
return functions.region().runWith({
154193
timeoutSeconds: 500,
155194
} as any);
156-
}).to.throw(Error);
195+
}).to.throw(Error, 'at least one region');
157196
});
158197
});

src/function-builder.ts

Lines changed: 94 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -32,72 +32,112 @@ import * as https from './providers/https';
3232
import * as pubsub from './providers/pubsub';
3333
import * as remoteConfig from './providers/remoteConfig';
3434
import * as storage from './providers/storage';
35-
import {
36-
CloudFunction,
37-
EventContext,
38-
Runnable,
39-
TriggerAnnotated,
40-
Schedule,
41-
} from './cloud-functions';
35+
import { CloudFunction, EventContext, Schedule } from './cloud-functions';
4236

4337
/**
44-
* Configure the regions that the function is deployed to.
45-
* @param regions One of more region strings.
46-
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
38+
* List of all regions supported by Cloud Functions.
4739
*/
48-
export function region(...regions: string[]) {
49-
if (!regions.length) {
50-
throw new Error('You must specify at least one region');
40+
const SUPPORTED_REGIONS = [
41+
'us-central1',
42+
'us-east1',
43+
'europe-west1',
44+
'europe-west2',
45+
'asia-east2',
46+
'asia-northeast1',
47+
];
48+
49+
/**
50+
* List of available memory options supported by Cloud Functions.
51+
*/
52+
const VALID_MEMORY_OPTS = ['128MB', '256MB', '512MB', '1GB', '2GB'];
53+
54+
// Adding this memory type here to error on compile for TS users.
55+
// Unfortunately I have not found a way to merge this with VALID_MEMORY_OPS
56+
// without it being super ugly. But here they are right next to each other at least.
57+
type Memory = '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
58+
59+
/**
60+
* Cloud Functions max timeout value.
61+
*/
62+
const MAX_TIMEOUT_SECONDS = 540;
63+
64+
/**
65+
* Assert that the runtime options passed in are valid.
66+
* @param runtimeOptions object containing memory and timeout information.
67+
* @throws { Error } Memory and TimeoutSeconds values must be valid.
68+
*/
69+
function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
70+
if (
71+
runtimeOptions.memory &&
72+
!_.includes(VALID_MEMORY_OPTS, runtimeOptions.memory)
73+
) {
74+
throw new Error(
75+
`The only valid memory allocation values are: ${VALID_MEMORY_OPTS.join(
76+
', '
77+
)}`
78+
);
5179
}
5280
if (
53-
_.difference(regions, [
54-
'us-central1',
55-
'us-east1',
56-
'europe-west1',
57-
'asia-northeast1',
58-
]).length
81+
runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS ||
82+
runtimeOptions.timeoutSeconds < 0
5983
) {
6084
throw new Error(
61-
"The only valid regions are 'us-central1', 'us-east1', 'europe-west1', and 'asia-northeast1'"
85+
`TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}`
6286
);
6387
}
64-
return new FunctionBuilder({ regions });
88+
return true;
6589
}
90+
91+
/**
92+
* Assert regions specified are valid.
93+
* @param regions list of regions.
94+
* @throws { Error } Regions must be in list of supported regions.
95+
*/
96+
function assertRegionsAreValid(regions: string[]): boolean {
97+
if (!regions.length) {
98+
throw new Error('You must specify at least one region');
99+
}
100+
if (_.difference(regions, SUPPORTED_REGIONS).length) {
101+
throw new Error(
102+
`The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}`
103+
);
104+
}
105+
return true;
106+
}
107+
108+
/**
109+
* Configure the regions that the function is deployed to.
110+
* @param regions One of more region strings.
111+
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
112+
*/
113+
export function region(...regions: string[]): FunctionBuilder {
114+
if (assertRegionsAreValid(regions)) {
115+
return new FunctionBuilder({ regions });
116+
}
117+
}
118+
66119
/**
67120
* Configure runtime options for the function.
68121
* @param runtimeOptions Object with 2 optional fields:
69122
* 1. `timeoutSeconds`: timeout for the function in seconds, possible values are 0 to 540
70123
* 2. `memory`: amount of memory to allocate to the function,
71124
* possible values are: '128MB', '256MB', '512MB', '1GB', and '2GB'.
72125
*/
73-
export function runWith(runtimeOptions: {
74-
timeoutSeconds?: number;
75-
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
76-
}) {
77-
if (
78-
runtimeOptions.memory &&
79-
!_.includes(
80-
['128MB', '256MB', '512MB', '1GB', '2GB'],
81-
runtimeOptions.memory
82-
)
83-
) {
84-
throw new Error(
85-
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
86-
);
87-
}
88-
if (
89-
runtimeOptions.timeoutSeconds > 540 ||
90-
runtimeOptions.timeoutSeconds < 0
91-
) {
92-
throw new Error('TimeoutSeconds must be between 0 and 540');
126+
export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
127+
if (assertRuntimeOptionsValid(runtimeOptions)) {
128+
return new FunctionBuilder(runtimeOptions);
93129
}
94-
return new FunctionBuilder(runtimeOptions);
130+
}
131+
132+
export interface RuntimeOptions {
133+
timeoutSeconds?: number;
134+
memory?: Memory;
95135
}
96136

97137
export interface DeploymentOptions {
98138
regions?: string[];
99139
timeoutSeconds?: number;
100-
memory?: string;
140+
memory?: Memory;
101141
schedule?: Schedule;
102142
}
103143

@@ -109,10 +149,12 @@ export class FunctionBuilder {
109149
* @param regions One or more region strings.
110150
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
111151
*/
112-
region = (...regions: string[]) => {
113-
this.options.regions = regions;
114-
return this;
115-
};
152+
region(...regions: string[]): FunctionBuilder {
153+
if (assertRegionsAreValid(regions)) {
154+
this.options.regions = regions;
155+
return this;
156+
}
157+
}
116158

117159
/**
118160
* Configure runtime options for the function.
@@ -121,30 +163,12 @@ export class FunctionBuilder {
121163
* 2. memory: amount of memory to allocate to the function, possible values are:
122164
* '128MB', '256MB', '512MB', '1GB', and '2GB'.
123165
*/
124-
runWith = (runtimeOptions: {
125-
timeoutSeconds?: number;
126-
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
127-
}) => {
128-
if (
129-
runtimeOptions.memory &&
130-
!_.includes(
131-
['128MB', '256MB', '512MB', '1GB', '2GB'],
132-
runtimeOptions.memory
133-
)
134-
) {
135-
throw new Error(
136-
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
137-
);
138-
}
139-
if (
140-
runtimeOptions.timeoutSeconds > 540 ||
141-
runtimeOptions.timeoutSeconds < 0
142-
) {
143-
throw new Error('TimeoutSeconds must be between 0 and 540');
166+
runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
167+
if (assertRuntimeOptionsValid(runtimeOptions)) {
168+
this.options = _.assign(this.options, runtimeOptions);
169+
return this;
144170
}
145-
this.options = _.assign(this.options, runtimeOptions);
146-
return this;
147-
};
171+
}
148172

149173
get https() {
150174
return {

0 commit comments

Comments
 (0)