diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..4601cb23b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Allow `JsonSecretParam` in function `secrets` option arrays. (#1788) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 8729f300b..0753fb0a2 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -31,6 +31,7 @@ import { RESET_VALUE, } from "../../src/v1"; import { MINIMAL_V1_ENDPOINT } from "../fixtures"; +import { defineJsonSecret, defineSecret } from "../../src/params"; describe("makeCloudFunction", () => { const cloudFunctionArgs: MakeCloudFunctionArgs = { @@ -161,6 +162,31 @@ describe("makeCloudFunction", () => { }); }); + it("should accept all valid secret types in secrets array (type test)", () => { + // This is a compile-time type test. If any of these types are not assignable + // to the secrets array, TypeScript will fail to compile this test file. + const jsonSecret = defineJsonSecret<{ key: string }>("JSON_SECRET"); + const stringSecret = defineSecret("STRING_SECRET"); + const plainSecret = "PLAIN_SECRET"; + + const cf = makeCloudFunction({ + provider: "mock.provider", + eventType: "mock.event", + service: "service", + triggerResource: () => "resource", + handler: () => null, + options: { + secrets: [plainSecret, stringSecret, jsonSecret], + }, + }); + + expect(cf.__endpoint.secretEnvironmentVariables).to.deep.equal([ + { key: "PLAIN_SECRET" }, + { key: "STRING_SECRET" }, + { key: "JSON_SECRET" }, + ]); + }); + it("should set retry given failure policy in __endpoint", () => { const cf = makeCloudFunction({ provider: "mock.provider", diff --git a/spec/v2/options.spec.ts b/spec/v2/options.spec.ts new file mode 100644 index 000000000..4105f9a1f --- /dev/null +++ b/spec/v2/options.spec.ts @@ -0,0 +1,41 @@ +// The MIT License (MIT) +// +// Copyright (c) 2025 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { expect } from "chai"; +import { defineJsonSecret, defineSecret } from "../../src/params"; +import { GlobalOptions } from "../../src/v2/options"; + +describe("GlobalOptions", () => { + it("should accept all valid secret types in secrets array (type test)", () => { + // This is a compile-time type test. If any of these types are not assignable + // to the secrets array, TypeScript will fail to compile this test file. + const jsonSecret = defineJsonSecret<{ key: string }>("JSON_SECRET"); + const stringSecret = defineSecret("STRING_SECRET"); + const plainSecret = "PLAIN_SECRET"; + + const opts: GlobalOptions = { + secrets: [plainSecret, stringSecret, jsonSecret], + }; + + expect(opts.secrets).to.have.length(3); + }); +}); diff --git a/src/params/types.ts b/src/params/types.ts index 14f7ce69d..0d9028b17 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -535,6 +535,12 @@ export class JsonSecretParam { } } +/** + * A union type representing all valid secret parameter types that can be used + * in a function's `secrets` configuration array. + */ +export type SupportedSecretParam = string | SecretParam | JsonSecretParam; + /** * A parametrized value of String type that will be read from .env files * if present, or prompted for by the CLI if missing. diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index ac0d87752..8e88cddee 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -43,7 +43,7 @@ import { ManifestRequiredAPI, } from "../runtime/manifest"; import { ResetValue } from "../common/options"; -import { SecretParam } from "../params/types"; +import { SupportedSecretParam } from "../params/types"; import { withInit } from "../common/onInit"; export { Change } from "../common/change"; @@ -638,8 +638,10 @@ export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint options, "secretEnvironmentVariables", "secrets", - (secrets: (string | SecretParam)[]) => - secrets.map((secret) => ({ key: secret instanceof SecretParam ? secret.name : secret })) + (secrets: SupportedSecretParam[]) => + secrets.map((secret) => ({ + key: typeof secret === "string" ? secret : secret.name, + })) ); if (options?.vpcConnector !== undefined) { if (options.vpcConnector === null || options.vpcConnector instanceof ResetValue) { diff --git a/src/v1/function-builder.ts b/src/v1/function-builder.ts index e70f26166..3e8286933 100644 --- a/src/v1/function-builder.ts +++ b/src/v1/function-builder.ts @@ -23,7 +23,7 @@ import * as express from "express"; import { ResetValue } from "../common/options"; -import { Expression, SecretParam } from "../params/types"; +import { Expression } from "../params/types"; import { EventContext } from "./cloud-functions"; import { DeploymentOptions, @@ -200,7 +200,7 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { if (runtimeOptions.secrets !== undefined) { const invalidSecrets = runtimeOptions.secrets.filter( - (s) => !/^[A-Za-z\d\-_]+$/.test(s instanceof SecretParam ? s.name : s) + (s) => !/^[A-Za-z\d\-_]+$/.test(typeof s === "string" ? s : s.name) ); if (invalidSecrets.length > 0) { throw new Error( diff --git a/src/v1/function-configuration.ts b/src/v1/function-configuration.ts index 90aa391fc..fa7b4d5c2 100644 --- a/src/v1/function-configuration.ts +++ b/src/v1/function-configuration.ts @@ -1,6 +1,6 @@ import { Expression } from "../params"; import { ResetValue } from "../common/options"; -import { SecretParam } from "../params/types"; +import { SupportedSecretParam } from "../params/types"; export { RESET_VALUE } from "../common/options"; @@ -235,7 +235,7 @@ export interface RuntimeOptions { /* * Secrets to bind to a function instance. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** * Determines whether Firebase AppCheck is enforced. diff --git a/src/v2/options.ts b/src/v2/options.ts index 608db1fa4..5b7e12aa9 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -35,7 +35,7 @@ import { RESET_VALUE, ResetValue } from "../common/options"; import { ManifestEndpoint } from "../runtime/manifest"; import { TriggerAnnotation } from "./core"; import { declaredParams, Expression } from "../params"; -import { ParamSpec, SecretParam } from "../params/types"; +import { ParamSpec, SupportedSecretParam } from "../params/types"; import { HttpsOptions } from "./providers/https"; import * as logger from "../logger"; @@ -210,7 +210,7 @@ export interface GlobalOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** * Determines whether Firebase App Check is enforced. Defaults to false. @@ -396,8 +396,10 @@ export function optionsToEndpoint( opts, "secretEnvironmentVariables", "secrets", - (secrets: (string | SecretParam)[]) => - secrets.map((secret) => ({ key: secret instanceof SecretParam ? secret.name : secret })) + (secrets: SupportedSecretParam[]) => + secrets.map((secret) => ({ + key: typeof secret === "string" ? secret : secret.name, + })) ); return endpoint; diff --git a/src/v2/providers/alerts/alerts.ts b/src/v2/providers/alerts/alerts.ts index e3b51c549..1cfacd706 100644 --- a/src/v2/providers/alerts/alerts.ts +++ b/src/v2/providers/alerts/alerts.ts @@ -26,7 +26,7 @@ import { CloudEvent, CloudFunction } from "../../core"; import { Expression } from "../../../params"; import { wrapTraceContext } from "../../trace"; import * as options from "../../options"; -import { SecretParam } from "../../../params/types"; +import { SupportedSecretParam } from "../../../params/types"; import { withInit } from "../../../common/onInit"; /** @@ -180,7 +180,7 @@ export interface FirebaseAlertOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/alerts/appDistribution.ts b/src/v2/providers/alerts/appDistribution.ts index 6aa54e733..a26c5e108 100644 --- a/src/v2/providers/alerts/appDistribution.ts +++ b/src/v2/providers/alerts/appDistribution.ts @@ -31,7 +31,7 @@ import { CloudEvent, CloudFunction } from "../../core"; import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; import * as options from "../../options"; -import { SecretParam } from "../../../params/types"; +import { SupportedSecretParam } from "../../../params/types"; import { withInit } from "../../../common/onInit"; /** @@ -191,7 +191,7 @@ export interface AppDistributionOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/alerts/crashlytics.ts b/src/v2/providers/alerts/crashlytics.ts index 9fd2b26bb..638b7f18e 100644 --- a/src/v2/providers/alerts/crashlytics.ts +++ b/src/v2/providers/alerts/crashlytics.ts @@ -31,7 +31,7 @@ import { CloudEvent, CloudFunction } from "../../core"; import { wrapTraceContext } from "../../trace"; import { convertAlertAndApp, FirebaseAlertData, getEndpointAnnotation } from "./alerts"; import * as options from "../../options"; -import { SecretParam } from "../../../params/types"; +import { SupportedSecretParam } from "../../../params/types"; import { withInit } from "../../../common/onInit"; /** Generic Crashlytics issue interface */ @@ -271,7 +271,7 @@ export interface CrashlyticsOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index c85f01172..a32786528 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -33,7 +33,7 @@ import { CloudEvent, CloudFunction } from "../core"; import { Expression } from "../../params"; import { wrapTraceContext } from "../trace"; import * as options from "../options"; -import { SecretParam } from "../../params/types"; +import { SupportedSecretParam } from "../../params/types"; import { withInit } from "../../common/onInit"; export { DataSnapshot }; @@ -192,7 +192,7 @@ export interface ReferenceOptions extends options.E /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/eventarc.ts b/src/v2/providers/eventarc.ts index 48f5974be..f00597bab 100644 --- a/src/v2/providers/eventarc.ts +++ b/src/v2/providers/eventarc.ts @@ -32,7 +32,7 @@ import { CloudEvent, CloudFunction } from "../core"; import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; -import { SecretParam } from "../../params/types"; +import { SupportedSecretParam } from "../../params/types"; import { withInit } from "../../common/onInit"; /** Options that can be set on an Eventarc trigger. */ @@ -156,7 +156,7 @@ export interface EventarcTriggerOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index b5ebc5b05..f38400029 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -44,7 +44,7 @@ import { import { initV2Endpoint, ManifestEndpoint } from "../../runtime/manifest"; import { GlobalOptions, SupportedRegion } from "../options"; import { Expression } from "../../params"; -import { SecretParam } from "../../params/types"; +import { SupportedSecretParam } from "../../params/types"; import * as options from "../options"; import { withInit } from "../../common/onInit"; import * as logger from "../../logger"; @@ -165,7 +165,7 @@ export interface HttpsOptions extends Omit | ResetValue; diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index e05187eb1..f619b814d 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -33,7 +33,7 @@ import { CloudEvent, CloudFunction } from "../core"; import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; -import { SecretParam } from "../../params/types"; +import { SupportedSecretParam } from "../../params/types"; import { withInit } from "../../common/onInit"; /** @@ -297,7 +297,7 @@ export interface StorageOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean | Expression | ResetValue; diff --git a/src/v2/providers/tasks.ts b/src/v2/providers/tasks.ts index e4e0ca127..90c2bf656 100644 --- a/src/v2/providers/tasks.ts +++ b/src/v2/providers/tasks.ts @@ -38,7 +38,7 @@ import * as options from "../options"; import { wrapTraceContext } from "../trace"; import { HttpsFunction } from "./https"; import { Expression } from "../../params"; -import { SecretParam } from "../../params/types"; +import { SupportedSecretParam } from "../../params/types"; import { initV2Endpoint, initTaskQueueTrigger } from "../../runtime/manifest"; import { withInit } from "../../common/onInit"; @@ -154,7 +154,7 @@ export interface TaskQueueOptions extends options.EventHandlerOptions { /* * Secrets to bind to a function. */ - secrets?: (string | SecretParam)[]; + secrets?: SupportedSecretParam[]; /** Whether failed executions should be delivered again. */ retry?: boolean;