Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add __trigger back #1271

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/common/encoding.ts
Expand Up @@ -22,6 +22,36 @@

// Copied from firebase-tools/src/gcp/proto

/**
* A type alias used to annotate interfaces as using a google.protobuf.Duration.
* This type is parsed/encoded as a string of seconds + the "s" prefix.
*/
export type Duration = string;

/** Get a google.protobuf.Duration for a number of seconds. */
export function durationFromSeconds(s: number): Duration {
return `${s}s`;
}

export function serviceAccountFromShorthand(serviceAccount: string): string | null {
if (serviceAccount === "default") {
return null;
} else if (serviceAccount.endsWith("@")) {
if (!process.env.GCLOUD_PROJECT) {
throw new Error(
`Unable to determine email for service account '${serviceAccount}' because process.env.GCLOUD_PROJECT is not set.`
);
}
return `${serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`;
} else if (serviceAccount.includes("@")) {
return serviceAccount;
} else {
throw new Error(
`Invalid option for serviceAccount: '${serviceAccount}'. Valid options are 'default', a service account email, or '{serviceAccountName}@'`
);
}
}

/**
* Utility function to help copy fields from type A to B.
* As a safety net, catches typos or fields that aren't named the same
Expand Down
125 changes: 123 additions & 2 deletions src/v1/cloud-functions.ts
Expand Up @@ -22,9 +22,21 @@

import { Request, Response } from "express";
import { warn } from "../logger";
import { DeploymentOptions, RESET_VALUE } from "./function-configuration";
import {
DEFAULT_FAILURE_POLICY,
DeploymentOptions,
FailurePolicy,
RESET_VALUE,
Schedule,
} from "./function-configuration";
export { Request, Response };
import { convertIfPresent, copyIfPresent } from "../common/encoding";
import {
convertIfPresent,
copyIfPresent,
Duration,
durationFromSeconds,
serviceAccountFromShorthand,
} from "../common/encoding";
import {
initV1Endpoint,
initV1ScheduleTrigger,
Expand Down Expand Up @@ -217,6 +229,36 @@ export interface Resource {
labels?: { [tag: string]: string };
}

/**
* TriggerAnnotion is used internally by the firebase CLI to understand what
* type of Cloud Function to deploy.
*/
interface TriggerAnnotation {
availableMemoryMb?: number;
blockingTrigger?: {
eventType: string;
options?: Record<string, unknown>;
};
eventTrigger?: {
eventType: string;
resource: string;
service: string;
};
failurePolicy?: FailurePolicy;
httpsTrigger?: {
invoker?: string[];
};
labels?: { [key: string]: string };
regions?: string[];
schedule?: Schedule;
timeout?: Duration;
vpcConnector?: string;
vpcConnectorEgressSettings?: string;
serviceAccountEmail?: string;
ingressSettings?: string;
secrets?: string[];
}

/**
* A Runnable has a `run` method which directly invokes the user-defined
* function - useful for unit testing.
Expand All @@ -239,6 +281,9 @@ export interface Runnable<T> {
export interface HttpsFunction {
(req: Request, resp: Response): void | Promise<void>;

/** @alpha */
__trigger: TriggerAnnotation;

/** @alpha */
__endpoint: ManifestEndpoint;

Expand All @@ -259,6 +304,9 @@ export interface BlockingFunction {
/** @public */
(req: Request, resp: Response): void | Promise<void>;

/** @alpha */
__trigger: TriggerAnnotation;

/** @alpha */
__endpoint: ManifestEndpoint;

Expand All @@ -276,6 +324,9 @@ export interface BlockingFunction {
export interface CloudFunction<T> extends Runnable<T> {
(input: any, context?: any): PromiseLike<any> | any;

/** @alpha */
__trigger: TriggerAnnotation;

/** @alpha */
__endpoint: ManifestEndpoint;

Expand Down Expand Up @@ -367,6 +418,27 @@ export function makeCloudFunction<EventData>({
return Promise.resolve(promise);
};

Object.defineProperty(cloudFunction, "__trigger", {
get: () => {
if (triggerResource() == null) {
return {};
}

const trigger: any = {
...optionsToTrigger(options),
eventTrigger: {
resource: triggerResource(),
eventType: legacyEventType || provider + "." + eventType,
service,
},
};
if (labels) {
trigger.labels = { ...trigger.labels, ...labels };
}
return trigger;
},
});

Object.defineProperty(cloudFunction, "__endpoint", {
get: () => {
if (triggerResource() == null) {
Expand Down Expand Up @@ -472,6 +544,55 @@ function _detectAuthType(event: Event) {
return "UNAUTHENTICATED";
}

/** @internal */
export function optionsToTrigger(options: DeploymentOptions) {
const trigger: any = {};
copyIfPresent(
trigger,
options,
"regions",
"schedule",
"minInstances",
"maxInstances",
"ingressSettings",
"vpcConnectorEgressSettings",
"vpcConnector",
"labels",
"secrets"
);
convertIfPresent(trigger, options, "failurePolicy", "failurePolicy", (policy) => {
if (policy === false) {
return undefined;
} else if (policy === true) {
return DEFAULT_FAILURE_POLICY;
} else {
return policy;
}
});
convertIfPresent(trigger, options, "timeout", "timeoutSeconds", durationFromSeconds);
convertIfPresent(trigger, options, "availableMemoryMb", "memory", (mem) => {
const memoryLookup = {
"128MB": 128,
"256MB": 256,
"512MB": 512,
"1GB": 1024,
"2GB": 2048,
"4GB": 4096,
"8GB": 8192,
};
return memoryLookup[mem];
});
convertIfPresent(
trigger,
options,
"serviceAccountEmail",
"serviceAccount",
serviceAccountFromShorthand
);

return trigger;
}

/** @internal */
export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint {
const endpoint: ManifestEndpoint = {};
Expand Down
4 changes: 4 additions & 0 deletions src/v1/function-configuration.ts
Expand Up @@ -74,6 +74,10 @@ export const INGRESS_SETTINGS_OPTIONS = [
"ALLOW_INTERNAL_AND_GCLB",
] as const;

export const DEFAULT_FAILURE_POLICY: FailurePolicy = {
retry: {},
};

/**
* Scheduler retry options. Applies only to scheduled functions.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/v1/providers/auth.ts
Expand Up @@ -40,6 +40,7 @@ import {
EventContext,
makeCloudFunction,
optionsToEndpoint,
optionsToTrigger,
} from "../cloud-functions";
import { DeploymentOptions } from "../function-configuration";
import { initV1Endpoint } from "../../runtime/manifest";
Expand Down Expand Up @@ -213,6 +214,19 @@ export class UserBuilder {

const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`;

func.__trigger = {
labels: {},
...optionsToTrigger(this.options),
blockingTrigger: {
eventType: legacyEventType,
options: {
accessToken,
idToken,
refreshToken,
},
},
};

func.__endpoint = {
platform: "gcfv1",
labels: {},
Expand Down
20 changes: 19 additions & 1 deletion src/v1/providers/https.ts
Expand Up @@ -30,7 +30,7 @@ import {
onCallHandler,
Request,
} from "../../common/providers/https";
import { HttpsFunction, optionsToEndpoint, Runnable } from "../cloud-functions";
import { HttpsFunction, optionsToEndpoint, optionsToTrigger, Runnable } from "../cloud-functions";
import { DeploymentOptions } from "../function-configuration";
import { initV1Endpoint } from "../../runtime/manifest";

Expand Down Expand Up @@ -66,6 +66,17 @@ export function _onRequestWithOptions(
const cloudFunction: any = (req: Request, res: express.Response) => {
return handler(req, res);
};
cloudFunction.__trigger = {
...optionsToTrigger(options),
httpsTrigger: {},
};
convertIfPresent(
cloudFunction.__trigger.httpsTrigger,
options,
"invoker",
"invoker",
convertInvoker
);
// TODO parse the options

cloudFunction.__endpoint = {
Expand Down Expand Up @@ -101,6 +112,13 @@ export function _onCallWithOptions(
fixedLen
);

func.__trigger = {
labels: {},
...optionsToTrigger(options),
httpsTrigger: {},
};
func.__trigger.labels["deployment-callable"] = "true";

func.__endpoint = {
platform: "gcfv1",
labels: {},
Expand Down
19 changes: 18 additions & 1 deletion src/v1/providers/tasks.ts
Expand Up @@ -36,7 +36,7 @@ import {
ManifestEndpoint,
ManifestRequiredAPI,
} from "../../runtime/manifest";
import { optionsToEndpoint } from "../cloud-functions";
import { optionsToEndpoint, optionsToTrigger } from "../cloud-functions";
import { DeploymentOptions } from "../function-configuration";

export { RetryConfig, RateLimits, TaskContext };
Expand Down Expand Up @@ -65,6 +65,9 @@ export interface TaskQueueOptions {
export interface TaskQueueFunction {
(req: Request, res: express.Response): Promise<void>;

/** @alpha */
__trigger: unknown;

/** @alpha */
__endpoint: ManifestEndpoint;

Expand Down Expand Up @@ -106,6 +109,20 @@ export class TaskQueueBuilder {
const fixedLen = (data: any, context: TaskContext) => handler(data, context);
const func: any = onDispatchHandler(fixedLen);

func.__trigger = {
...optionsToTrigger(this.depOpts || {}),
taskQueueTrigger: {},
};
copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, "retryConfig");
copyIfPresent(func.__trigger.taskQueueTrigger, this.tqOpts, "rateLimits");
convertIfPresent(
func.__trigger.taskQueueTrigger,
this.tqOpts,
"invoker",
"invoker",
convertInvoker
);

func.__endpoint = {
platform: "gcfv1",
...initV1Endpoint(this.depOpts),
Expand Down
34 changes: 34 additions & 0 deletions src/v2/core.ts
Expand Up @@ -32,6 +32,37 @@ export { Change };

export { ParamsOf } from "../common/params";

/** @internal */
export interface TriggerAnnotation {
platform?: string;
concurrency?: number;
minInstances?: number;
maxInstances?: number;
availableMemoryMb?: number;
eventTrigger?: {
eventType: string;
resource: string;
service: string;
};
failurePolicy?: { retry: boolean };
httpsTrigger?: {
invoker?: string[];
};
labels?: { [key: string]: string };
regions?: string[];
timeout?: string;
vpcConnector?: string;
vpcConnectorEgressSettings?: string;
serviceAccountEmail?: string;
ingressSettings?: string;
secrets?: string[];
blockingTrigger?: {
eventType: string;
options?: Record<string, unknown>;
};
// TODO: schedule
}

/**
* A CloudEventBase is the base of a cross-platform format for encoding a serverless event.
* More information can be found in https://github.com/cloudevents/spec
Expand Down Expand Up @@ -70,6 +101,9 @@ export interface CloudEvent<T> {
export interface CloudFunction<EventType extends CloudEvent<unknown>> {
(raw: CloudEvent<unknown>): any | Promise<any>;

/** @alpha */
__trigger?: unknown;

/** @alpha */
__endpoint: ManifestEndpoint;

Expand Down