Skip to content

Commit 8a2c826

Browse files
authored
Add RESET_VALUE configuration value as sentinel value to reset a config option. (#1250)
Instead of allowing `null`s to reset a configuration value, we are introducing a new sentinel value, `RESET_VALUE` to make it clear the intention of resetting the config value to platform default: ```js import * as functions from "firebase-functions/v1"; import { onRequest } from "firebase-functions/v2/https"; import { RESET_VALUE } from "firebase-functions/v2/options"; export const v1Req = functions .runWith({ minInstances: functions.RESET_VALUE, memory: functions.RESET_VALUE, serviceAccount: RESET_VALUE }).https.onRequest( (req, res) => { res.sendStatus(200) }, ); export const v2Req = onRequest( { memory: RESET_VALUE, minInstances: RESET_VALUE, }, (req, res) => { res.sendStatus(200) }, ); ``` Majority of this PR concerns fixing/updating typings on various configuration options. Recall that in v2, we inlined/repeat all function options in each respective providers to make reference doc more clear. This unfortunately bloated up this PR since I had to repeat the changes in each and every provider.
1 parent 0cd8b9d commit 8a2c826

19 files changed

+428
-279
lines changed

spec/runtime/manifest.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { expect } from "chai";
22
import { stackToWire, ManifestStack } from "../../src/runtime/manifest";
33
import * as params from "../../src/params";
4+
import * as optsv2 from "../../src/v2/options";
5+
import * as v1 from "../../src/v1";
46

57
describe("stackToWire", () => {
68
afterEach(() => {
79
params.clearParams();
810
});
911

10-
it("converts stack with null values values", () => {
12+
it("converts stack with null values", () => {
1113
const stack: ManifestStack = {
1214
endpoints: {
1315
v2http: {
@@ -37,6 +39,50 @@ describe("stackToWire", () => {
3739
expect(stackToWire(stack)).to.deep.equal(expected);
3840
});
3941

42+
it("converts stack with RESET_VALUES", () => {
43+
const stack: ManifestStack = {
44+
endpoints: {
45+
v1http: {
46+
platform: "gcfv1",
47+
entryPoint: "v1http",
48+
labels: {},
49+
httpsTrigger: {},
50+
maxInstances: v1.RESET_VALUE,
51+
},
52+
v2http: {
53+
platform: "gcfv2",
54+
entryPoint: "v2http",
55+
labels: {},
56+
httpsTrigger: {},
57+
maxInstances: optsv2.RESET_VALUE,
58+
},
59+
},
60+
requiredAPIs: [],
61+
specVersion: "v1alpha1",
62+
};
63+
const expected = {
64+
endpoints: {
65+
v1http: {
66+
platform: "gcfv1",
67+
entryPoint: "v1http",
68+
labels: {},
69+
httpsTrigger: {},
70+
maxInstances: null,
71+
},
72+
v2http: {
73+
platform: "gcfv2",
74+
entryPoint: "v2http",
75+
labels: {},
76+
httpsTrigger: {},
77+
maxInstances: null,
78+
},
79+
},
80+
requiredAPIs: [],
81+
specVersion: "v1alpha1",
82+
};
83+
expect(stackToWire(stack)).to.deep.equal(expected);
84+
});
85+
4086
it("converts Expression types in endpoint options to CEL", () => {
4187
const intParam = params.defineInt("foo", { default: 11 });
4288
const stringParam = params.defineString("bar", {

src/common/options.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2022 Firebase
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
/**
24+
* Special configuration type to reset configuration to platform default.
25+
*
26+
* @alpha
27+
*/
28+
export class ResetValue {
29+
toJSON(): null {
30+
return null;
31+
}
32+
// eslint-disable-next-line @typescript-eslint/no-empty-function
33+
private constructor() {}
34+
public static getInstance() {
35+
return new ResetValue();
36+
}
37+
}
38+
39+
/**
40+
* Special configuration value to reset configuration to platform default.
41+
*/
42+
export const RESET_VALUE = ResetValue.getInstance();

src/common/providers/tasks.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,38 +26,39 @@ import { DecodedIdToken } from "firebase-admin/auth";
2626
import * as logger from "../../logger";
2727
import * as https from "./https";
2828
import { Expression } from "../../params";
29+
import { ResetValue } from "../options";
2930

3031
/** How a task should be retried in the event of a non-2xx return. */
3132
export interface RetryConfig {
3233
/**
3334
* Maximum number of times a request should be attempted.
3435
* If left unspecified, will default to 3.
3536
*/
36-
maxAttempts?: number | Expression<number> | null;
37+
maxAttempts?: number | Expression<number> | ResetValue;
3738

3839
/**
3940
* Maximum amount of time for retrying failed task.
4041
* If left unspecified will retry indefinitely.
4142
*/
42-
maxRetrySeconds?: number | Expression<number> | null;
43+
maxRetrySeconds?: number | Expression<number> | ResetValue;
4344

4445
/**
4546
* The maximum amount of time to wait between attempts.
4647
* If left unspecified will default to 1hr.
4748
*/
48-
maxBackoffSeconds?: number | Expression<number> | null;
49+
maxBackoffSeconds?: number | Expression<number> | ResetValue;
4950

5051
/**
5152
* The maximum number of times to double the backoff between
5253
* retries. If left unspecified will default to 16.
5354
*/
54-
maxDoublings?: number | Expression<number> | null;
55+
maxDoublings?: number | Expression<number> | ResetValue;
5556

5657
/**
5758
* The minimum time to wait between attempts. If left unspecified
5859
* will default to 100ms.
5960
*/
60-
minBackoffSeconds?: number | Expression<number> | null;
61+
minBackoffSeconds?: number | Expression<number> | ResetValue;
6162
}
6263

6364
/** How congestion control should be applied to the function. */
@@ -66,13 +67,13 @@ export interface RateLimits {
6667
* The maximum number of requests that can be outstanding at a time.
6768
* If left unspecified, will default to 1000.
6869
*/
69-
maxConcurrentDispatches?: number | Expression<number> | null;
70+
maxConcurrentDispatches?: number | Expression<number> | ResetValue;
7071

7172
/**
7273
* The maximum number of requests that can be invoked per second.
7374
* If left unspecified, will default to 500.
7475
*/
75-
maxDispatchesPerSecond?: number | Expression<number> | null;
76+
maxDispatchesPerSecond?: number | Expression<number> | ResetValue;
7677
}
7778

7879
/** Metadata about the authorization used to invoke a function. */

src/runtime/manifest.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// SOFTWARE.
2222

23+
import { ResetValue } from "../common/options";
2324
import { Expression } from "../params";
2425
import { WireParamSpec } from "../params/types";
2526

@@ -30,19 +31,19 @@ export interface ManifestEndpoint {
3031
entryPoint?: string;
3132
region?: string[];
3233
platform?: string;
33-
availableMemoryMb?: number | Expression<number>;
34-
maxInstances?: number | Expression<number>;
35-
minInstances?: number | Expression<number>;
36-
concurrency?: number | Expression<number>;
37-
serviceAccountEmail?: string;
38-
timeoutSeconds?: number | Expression<number>;
39-
cpu?: number | "gcf_gen1";
34+
availableMemoryMb?: number | Expression<number> | ResetValue;
35+
maxInstances?: number | Expression<number> | ResetValue;
36+
minInstances?: number | Expression<number> | ResetValue;
37+
concurrency?: number | Expression<number> | ResetValue;
38+
timeoutSeconds?: number | Expression<number> | ResetValue;
4039
vpc?: {
41-
connector: string | Expression<string>;
42-
egressSettings?: string;
40+
connector: string | Expression<string> | ResetValue;
41+
egressSettings?: string | Expression<string> | ResetValue;
4342
};
43+
serviceAccountEmail?: string | Expression<string> | ResetValue;
44+
cpu?: number | "gcf_gen1";
4445
labels?: Record<string, string>;
45-
ingressSettings?: string;
46+
ingressSettings?: string | Expression<string> | ResetValue;
4647
environmentVariables?: Record<string, string>;
4748
secretEnvironmentVariables?: Array<{ key: string; secret?: string }>;
4849

@@ -57,20 +58,38 @@ export interface ManifestEndpoint {
5758
eventFilterPathPatterns?: Record<string, string | Expression<string>>;
5859
channel?: string;
5960
eventType: string;
60-
retry: boolean | Expression<boolean>;
61+
retry: boolean | Expression<boolean> | ResetValue;
6162
region?: string;
62-
serviceAccountEmail?: string;
63+
serviceAccountEmail?: string | ResetValue;
64+
};
65+
66+
taskQueueTrigger?: {
67+
retryConfig?: {
68+
maxAttempts?: number | Expression<number> | ResetValue;
69+
maxRetrySeconds?: number | Expression<number> | ResetValue;
70+
maxBackoffSeconds?: number | Expression<number> | ResetValue;
71+
maxDoublings?: number | Expression<number> | ResetValue;
72+
minBackoffSeconds?: number | Expression<number> | ResetValue;
73+
};
74+
rateLimits?: {
75+
maxConcurrentDispatches?: number | Expression<number> | ResetValue;
76+
maxDispatchesPerSecond?: number | Expression<number> | ResetValue;
77+
};
6378
};
6479

6580
scheduleTrigger?: {
66-
schedule?: string | Expression<string>;
67-
timeZone?: string | Expression<string>;
81+
schedule: string | Expression<string>;
82+
timeZone?: string | Expression<string> | ResetValue;
6883
retryConfig?: {
69-
retryCount?: number | Expression<number>;
70-
maxRetrySeconds?: string | Expression<string>;
71-
minBackoffSeconds?: string | Expression<string>;
72-
maxBackoffSeconds?: string | Expression<string>;
73-
maxDoublings?: number | Expression<number>;
84+
retryCount?: number | Expression<number> | ResetValue;
85+
maxRetrySeconds?: string | Expression<string> | ResetValue;
86+
minBackoffSeconds?: string | Expression<string> | ResetValue;
87+
maxBackoffSeconds?: string | Expression<string> | ResetValue;
88+
maxDoublings?: number | Expression<number> | ResetValue;
89+
// Note: v1 schedule functions use *Duration instead of *Seconds
90+
maxRetryDuration?: string | Expression<string> | ResetValue;
91+
minBackoffDuration?: string | Expression<string> | ResetValue;
92+
maxBackoffDuration?: string | Expression<string> | ResetValue;
7493
};
7594
};
7695

@@ -107,6 +126,8 @@ export function stackToWire(stack: ManifestStack): Record<string, unknown> {
107126
for (const [key, val] of Object.entries(obj)) {
108127
if (val instanceof Expression) {
109128
obj[key] = val.toCEL();
129+
} else if (val instanceof ResetValue) {
130+
obj[key] = val.toJSON();
110131
} else if (typeof val === "object" && val !== null) {
111132
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
112133
traverse(val as any);

src/v1/cloud-functions.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222

2323
import { Request, Response } from "express";
2424
import { warn } from "../logger";
25-
import { DeploymentOptions } from "./function-configuration";
25+
import { DeploymentOptions, RESET_VALUE } from "./function-configuration";
2626
export { Request, Response };
2727
import { convertIfPresent, copyIfPresent } from "../common/encoding";
2828
import { ManifestEndpoint, ManifestRequiredAPI } from "../runtime/manifest";
29+
import { ResetValue } from "../common/options";
2930

3031
export { Change } from "../common/change";
3132

@@ -372,7 +373,7 @@ export function makeCloudFunction<EventData>({
372373
};
373374

374375
if (options.schedule) {
375-
endpoint.scheduleTrigger = options.schedule;
376+
endpoint.scheduleTrigger = options.schedule as any;
376377
} else {
377378
endpoint.eventTrigger = {
378379
eventType: legacyEventType || provider + "." + eventType,
@@ -471,9 +472,14 @@ export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint
471472
convertIfPresent(endpoint, options, "secretEnvironmentVariables", "secrets", (secrets) =>
472473
secrets.map((secret) => ({ key: secret }))
473474
);
474-
if (options?.vpcConnector) {
475-
endpoint.vpc = { connector: options.vpcConnector };
476-
convertIfPresent(endpoint.vpc, options, "egressSettings", "vpcConnectorEgressSettings");
475+
if (options?.vpcConnector !== undefined) {
476+
if (options.vpcConnector === null || options.vpcConnector instanceof ResetValue) {
477+
endpoint.vpc = { connector: RESET_VALUE, egressSettings: RESET_VALUE };
478+
} else {
479+
const vpc: ManifestEndpoint["vpc"] = { connector: options.vpcConnector };
480+
convertIfPresent(vpc, options, "egressSettings", "vpcConnectorEgressSettings");
481+
endpoint.vpc = vpc;
482+
}
477483
}
478484
convertIfPresent(endpoint, options, "availableMemoryMb", "memory", (mem) => {
479485
const memoryLookup = {
@@ -485,7 +491,7 @@ export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint
485491
"4GB": 4096,
486492
"8GB": 8192,
487493
};
488-
return memoryLookup[mem];
494+
return typeof mem === "object" ? mem : memoryLookup[mem];
489495
});
490496
return endpoint;
491497
}

src/v1/function-builder.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import * as express from "express";
2424

25+
import { ResetValue } from "../common/options";
2526
import { EventContext } from "./cloud-functions";
2627
import {
2728
DeploymentOptions,
@@ -50,7 +51,8 @@ import * as testLab from "./providers/testLab";
5051
* @throws { Error } Memory and TimeoutSeconds values must be valid.
5152
*/
5253
function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
53-
if (runtimeOptions.memory && !VALID_MEMORY_OPTIONS.includes(runtimeOptions.memory)) {
54+
const mem = runtimeOptions.memory;
55+
if (mem && !(mem instanceof ResetValue) && !VALID_MEMORY_OPTIONS.includes(mem)) {
5456
throw new Error(
5557
`The only valid memory allocation values are: ${VALID_MEMORY_OPTIONS.join(", ")}`
5658
);
@@ -61,6 +63,7 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
6163

6264
if (
6365
runtimeOptions.ingressSettings &&
66+
!(runtimeOptions.ingressSettings instanceof ResetValue) &&
6467
!INGRESS_SETTINGS_OPTIONS.includes(runtimeOptions.ingressSettings)
6568
) {
6669
throw new Error(
@@ -70,6 +73,7 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
7073

7174
if (
7275
runtimeOptions.vpcConnectorEgressSettings &&
76+
!(runtimeOptions.vpcConnectorEgressSettings instanceof ResetValue) &&
7377
!VPC_EGRESS_SETTINGS_OPTIONS.includes(runtimeOptions.vpcConnectorEgressSettings)
7478
) {
7579
throw new Error(
@@ -80,10 +84,12 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
8084
}
8185

8286
validateFailurePolicy(runtimeOptions.failurePolicy);
87+
const serviceAccount = runtimeOptions.serviceAccount;
8388
if (
84-
runtimeOptions.serviceAccount &&
85-
runtimeOptions.serviceAccount !== "default" &&
86-
!runtimeOptions.serviceAccount.includes("@")
89+
serviceAccount &&
90+
serviceAccount !== "default" &&
91+
!(serviceAccount instanceof ResetValue) &&
92+
!serviceAccount.includes("@")
8793
) {
8894
throw new Error(
8995
`serviceAccount must be set to 'default', a service account email, or '{serviceAccountName}@'`

0 commit comments

Comments
 (0)