From 72c66ef05289da624a4f7b13d76ca3bbeb530cd9 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Tue, 26 Oct 2021 22:33:50 -0700 Subject: [PATCH 01/24] WIP. --- spec/v1/cloud-functions.spec.ts | 107 +++++++++++++++++++++++++++++ spec/v1/providers/https.spec.ts | 26 ++++++- spec/v2/providers/helpers.ts | 17 +++++ spec/v2/providers/https.spec.ts | 59 ++++++++++++++-- spec/v2/providers/pubsub.spec.ts | 62 ++++++++++++++++- spec/v2/providers/storage.spec.ts | 109 ++++++++++++++++++++++++++++++ src/cloud-functions.ts | 82 +++++++++++++++++++++- src/common/manifest/v1alpha.ts | 65 ++++++++++++++++++ src/handler-builder.ts | 2 + src/providers/https.ts | 28 +++++++- src/v2/core.ts | 1 + src/v2/options.ts | 70 +++++++++++++++++++ src/v2/providers/https.ts | 60 +++++++++++++++- src/v2/providers/pubsub.ts | 36 +++++++++- src/v2/providers/storage.ts | 33 ++++++++- 15 files changed, 740 insertions(+), 17 deletions(-) create mode 100644 src/common/manifest/v1alpha.ts diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 05e80bd21..d4e335932 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -69,6 +69,113 @@ describe('makeCloudFunction', () => { }); }); + it('should put a __endpoint on the returned CloudFunction', () => { + const cf = makeCloudFunction({ + provider: 'mock.provider', + eventType: 'mock.event', + service: 'service', + triggerResource: () => 'resource', + handler: () => null, + }); + expect(cf.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'mock.provider.mock.event', + eventFilters: { + resource: 'resource', + }, + retry: false, + }, + }); + }); + + it('should have legacy event type in __endpoint if provided', () => { + const cf = makeCloudFunction(cloudFunctionArgs); + expect(cf.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'providers/provider/eventTypes/event', + eventFilters: { + resource: 'resource', + }, + retry: false, + }, + }); + }); + + it('should include converted options in __endpoint', () => { + const cf = makeCloudFunction({ + provider: 'mock.provider', + eventType: 'mock.event', + service: 'service', + triggerResource: () => 'resource', + handler: () => null, + options: { + timeoutSeconds: 10, + regions: ['us-central1'], + memory: '128MB', + serviceAccount: 'foo@google.com', + }, + }); + expect(cf.__endpoint).to.deep.equal({ + platform: 'gcfv1', + timeout: '10s', + region: ['us-central1'], + availableMemoryMb: 128, + serviceAccountEmail: 'foo@google.com', + eventTrigger: { + eventType: 'mock.provider.mock.event', + eventFilters: { + resource: 'resource', + }, + retry: false, + }, + }); + }); + + it('should set retry given failure policy in __endpoint', () => { + const cf = makeCloudFunction({ + provider: 'mock.provider', + eventType: 'mock.event', + service: 'service', + triggerResource: () => 'resource', + handler: () => null, + options: { failurePolicy: { retry: {} } }, + }); + expect(cf.__endpoint).to.deep.equal({ + platform: 'gcfv1', + eventTrigger: { + eventType: 'mock.provider.mock.event', + eventFilters: { + resource: 'resource', + }, + retry: true, + }, + }); + }); + + it('should setup scheduleTrigger in __endpoint given a schedule', () => { + const schedule = { + schedule: 'every 5 minutes', + retryConfig: { retryCount: 3 }, + timeZone: 'America/New_York', + }; + const cf = makeCloudFunction({ + provider: 'mock.provider', + eventType: 'mock.event', + service: 'service', + triggerResource: () => 'resource', + handler: () => null, + options: { + schedule, + }, + }); + expect(cf.__endpoint).to.deep.equal({ + platform: 'gcfv1', + scheduleTrigger: schedule, + }); + }); + it('should construct the right context for event', () => { const args: any = _.assign({}, cloudFunctionArgs, { handler: (data: any, context: EventContext) => context, diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 025c99a7a..57193cc37 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -99,6 +99,10 @@ describe('CloudHttpsBuilder', () => { resp.send(200); }); expect(result.__trigger).to.deep.equal({ httpsTrigger: {} }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv1', + httpsTrigger: {}, + }); }); it('should allow both region and runtime options to be set', () => { @@ -115,30 +119,37 @@ describe('CloudHttpsBuilder', () => { expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); expect(fn.__trigger.httpsTrigger.invoker).to.deep.equal(['private']); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(fn.__endpoint.timeout).to.deep.equal('90s'); + expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(['private']); }); }); }); describe('handler namespace', () => { describe('#onRequest', () => { - it('should return an empty trigger', () => { + it('should return empty trigger and endpoint', () => { const result = functions.handler.https.onRequest((req, res) => { res.send(200); }); expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.deep.equal({}); }); }); describe('#onCall', () => { - it('should return an empty trigger', () => { + it('should return empty trigger and endpoint', () => { const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); + expect(result.__endpoint).to.deep.equal({}); }); }); }); describe('#onCall', () => { - it('should return a Trigger with appropriate values', () => { + it('should return a Trigger and Endpoint with appropriate values', () => { const result = https.onCall((data) => { return 'response'; }); @@ -146,6 +157,11 @@ describe('#onCall', () => { httpsTrigger: {}, labels: { 'deployment-callable': 'true' }, }); + expect(result.__endpoint).to.deep.equal({ + platform: "gcfv1", + httpsTrigger: {}, + labels: { 'deployment-callable': 'true' }, + }); }); it('should allow both region and runtime options to be set', () => { @@ -160,6 +176,10 @@ describe('#onCall', () => { expect(fn.__trigger.regions).to.deep.equal(['us-east1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); + + expect(fn.__endpoint.region).to.deep.equal(['us-east1']); + expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); + expect(fn.__endpoint.timeout).to.deep.equal('90s'); }); it('has a .run method', () => { diff --git a/spec/v2/providers/helpers.ts b/spec/v2/providers/helpers.ts index a654a4944..91b386e1a 100644 --- a/spec/v2/providers/helpers.ts +++ b/spec/v2/providers/helpers.ts @@ -33,3 +33,20 @@ export const FULL_TRIGGER = { hello: 'world', }, }; + +export const FULL_ENDPOINT = { + platform: 'gcfv2', + region: ['us-west1'], + availableMemoryMb: 512, + timeout: '60s', + minInstances: 1, + maxInstances: 3, + concurrency: 20, + vpcConnector: 'aConnector', + vpcConnectorEgressSettings: 'ALL_TRAFFIC', + serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', + ingressSettings: 'ALLOW_ALL', + labels: { + hello: 'world', + }, +}; diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index a28cb1c9c..65426ab94 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -7,7 +7,7 @@ import { expectedResponseHeaders, MockRequest, } from '../../fixtures/mockrequest'; -import { FULL_OPTIONS, FULL_TRIGGER } from './helpers'; +import { FULL_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from './helpers'; /** * RunHandlerResult contains the data from an express.Response. @@ -82,7 +82,7 @@ describe('onRequest', () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger with appropriate values', () => { + it('should return a minimal trigger/endpoint with appropriate values', () => { const result = https.onRequest((req, res) => { res.send(200); }); @@ -94,9 +94,14 @@ describe('onRequest', () => { }, labels: {}, }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: {}, + labels: {}, + }); }); - it('should create a complex trigger with appropriate values', () => { + it('should create a complex trigger/endpoint with appropriate values', () => { const result = https.onRequest( { ...FULL_OPTIONS, @@ -115,6 +120,13 @@ describe('onRequest', () => { }, regions: ['us-west1', 'us-central1'], }); + expect(result.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + httpsTrigger: { + invoker: ['service-account1@', 'service-account2@'], + }, + region: ['us-west1', 'us-central1'], + }); }); it('should merge options and globalOptions', () => { @@ -148,6 +160,17 @@ describe('onRequest', () => { regions: ['us-west1', 'us-central1'], labels: {}, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: { + invoker: ['private'], + }, + concurrency: 20, + minInstances: 3, + region: ['us-west1', 'us-central1'], + labels: {}, + }); }); it('should be an express handler', async () => { @@ -209,7 +232,7 @@ describe('onCall', () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger with appropriate values', () => { + it('should return a minimal trigger/endpoint with appropriate values', () => { const result = https.onCall((request) => 42); expect(result.__trigger).to.deep.equal({ apiVersion: 2, @@ -221,9 +244,16 @@ describe('onCall', () => { 'deployment-callable': 'true', }, }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: {}, + labels: { + 'deployment-callable': 'true', + }, + }); }); - it('should create a complex trigger with appropriate values', () => { + it('should create a complex trigger/endpoint with appropriate values', () => { const result = https.onCall(FULL_OPTIONS, (request) => 42); expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, @@ -235,6 +265,14 @@ describe('onCall', () => { 'deployment-callable': 'true', }, }); + expect(result.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + httpsTrigger: {}, + labels: { + ...FULL_ENDPOINT.labels, + 'deployment-callable': 'true', + }, + }); }); it('should merge options and globalOptions', () => { @@ -265,6 +303,17 @@ describe('onCall', () => { 'deployment-callable': 'true', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + httpsTrigger: {}, + concurrency: 20, + minInstances: 3, + region: ['us-west1', 'us-central1'], + labels: { + 'deployment-callable': 'true', + }, + }); }); it('has a .run method', () => { diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index 4fc338614..46910baa3 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -3,13 +3,21 @@ import { expect } from 'chai'; import { CloudEvent } from '../../../src/v2/core'; import * as options from '../../../src/v2/options'; import * as pubsub from '../../../src/v2/providers/pubsub'; -import { FULL_OPTIONS, FULL_TRIGGER } from './helpers'; +import { FULL_ENDPOINT, FULL_OPTIONS, FULL_TRIGGER } from './helpers'; const EVENT_TRIGGER = { eventType: 'google.cloud.pubsub.topic.v1.messagePublished', resource: 'projects/aProject/topics/topic', }; +const ENDPOINT_EVENT_TRIGGER = { + eventType: 'google.cloud.pubsub.topic.v1.messagePublished', + eventFilters: { + resource: 'projects/aProject/topics/topic', + }, + retry: false, +}; + describe('onMessagePublished', () => { beforeEach(() => { options.setGlobalOptions({}); @@ -20,7 +28,7 @@ describe('onMessagePublished', () => { delete process.env.GCLOUD_PROJECT; }); - it('should return a minimal trigger with appropriate values', () => { + it('should return a minimal trigger/endpoint with appropriate values', () => { const result = pubsub.onMessagePublished('topic', () => 42); expect(result.__trigger).to.deep.equal({ apiVersion: 2, @@ -28,9 +36,14 @@ describe('onMessagePublished', () => { eventTrigger: EVENT_TRIGGER, labels: {}, }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + eventTrigger: ENDPOINT_EVENT_TRIGGER, + labels: {}, + }); }); - it('should create a complex trigger with appropriate values', () => { + it('should create a complex trigger/endpoint with appropriate values', () => { const result = pubsub.onMessagePublished( { ...FULL_OPTIONS, topic: 'topic' }, () => 42 @@ -39,6 +52,10 @@ describe('onMessagePublished', () => { ...FULL_TRIGGER, eventTrigger: EVENT_TRIGGER, }); + expect(result.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); }); it('should merge options and globalOptions', () => { @@ -66,6 +83,45 @@ describe('onMessagePublished', () => { labels: {}, eventTrigger: EVENT_TRIGGER, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + concurrency: 20, + minInstances: 3, + region: ['us-west1'], + labels: {}, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); + }); + + it('should convert retry option if appropriate', () => { + const result = pubsub.onMessagePublished( + { + topic: 'topic', + region: 'us-west1', + minInstances: 3, + retry: true, + }, + () => 42 + ); + + expect(result.__trigger).to.deep.equal({ + apiVersion: 2, + platform: 'gcfv2', + minInstances: 3, + regions: ['us-west1'], + labels: {}, + eventTrigger: EVENT_TRIGGER, + failurePolicy: { retry: true }, + }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + minInstances: 3, + region: ['us-west1'], + labels: {}, + eventTrigger: { ...ENDPOINT_EVENT_TRIGGER, retry: true }, + }); }); it('should have a .run method', () => { diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index 601b5c024..b3873e194 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -10,6 +10,14 @@ const EVENT_TRIGGER = { resource: 'some-bucket', }; +const ENDPOINT_EVENT_TRIGGER = { + eventType: 'event-type', + eventFilters: { + bucket: 'some-bucket', + }, + retry: false, +}; + describe('v2/storage', () => { describe('getOptsAndBucket', () => { it('should return the default bucket with empty opts', () => { @@ -76,6 +84,12 @@ describe('v2/storage', () => { labels: {}, eventTrigger: EVENT_TRIGGER, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); }); it('should create a minimal trigger with opts', () => { @@ -96,6 +110,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_EVENT_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + region: ['us-west1'], + }); }); it('should create a minimal trigger with bucket with opts and bucket', () => { @@ -110,6 +136,12 @@ describe('v2/storage', () => { labels: {}, eventTrigger: EVENT_TRIGGER, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); }); it('should create a complex trigger with appropriate values', () => { @@ -139,6 +171,24 @@ describe('v2/storage', () => { }, eventTrigger: EVENT_TRIGGER, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + region: ['us-west1'], + availableMemoryMb: 512, + timeout: '60s', + minInstances: 1, + maxInstances: 3, + concurrency: 20, + vpcConnector: 'aConnector', + vpcConnectorEgressSettings: 'ALL_TRAFFIC', + serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', + ingressSettings: 'ALLOW_ALL', + labels: { + hello: 'world', + }, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); }); it('should merge options and globalOptions', () => { @@ -166,6 +216,15 @@ describe('v2/storage', () => { labels: {}, eventTrigger: EVENT_TRIGGER, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + concurrency: 20, + minInstances: 3, + region: ['us-west1'], + labels: {}, + eventTrigger: ENDPOINT_EVENT_TRIGGER, + }); }); }); @@ -174,6 +233,10 @@ describe('v2/storage', () => { ...EVENT_TRIGGER, eventType: storage.archivedEvent, }; + const ENDPOINT_ARCHIVED_TRIGGER = { + ...ENDPOINT_EVENT_TRIGGER, + eventType: storage.archivedEvent, + }; let configStub: sinon.SinonStub; beforeEach(() => { @@ -197,6 +260,17 @@ describe('v2/storage', () => { resource: 'default-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_ARCHIVED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + }); }); it('should accept bucket and handler', () => { @@ -210,6 +284,17 @@ describe('v2/storage', () => { resource: 'my-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_ARCHIVED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + }); }); it('should accept opts and handler', () => { @@ -227,6 +312,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_ARCHIVED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + region: ['us-west1'], + }); }); it('should accept opts and handler, default bucket', () => { @@ -243,6 +340,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_ARCHIVED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + region: ['us-west1'], + }); }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 688c4eb78..5454aa374 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -37,6 +37,7 @@ import { durationFromSeconds, serviceAccountFromShorthand, } from './common/encoding'; +import { ManifestEndpoint } from './common/manifest/v1alpha'; /** @hidden */ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); @@ -283,6 +284,10 @@ export interface TriggerAnnotated { }; } +export interface TriggerEndpoint { + __endpoint: ManifestEndpoint; +} + /** * A Runnable has a `run` method which directly invokes the user-defined * function - useful for unit testing. @@ -301,6 +306,7 @@ export interface Runnable { * arguments. */ export type HttpsFunction = TriggerAnnotated & + TriggerEndpoint & ((req: Request, resp: Response) => void | Promise); /** @@ -312,6 +318,7 @@ export type HttpsFunction = TriggerAnnotated & */ export type CloudFunction = Runnable & TriggerAnnotated & + TriggerEndpoint & ((input: any, context?: any) => PromiseLike | any); /** @hidden */ @@ -324,7 +331,7 @@ export interface MakeCloudFunctionArgs { handler?: (data: EventData, context: EventContext) => PromiseLike | any; labels?: { [key: string]: any }; legacyEventType?: string; - options?: { [key: string]: any }; + options?: DeploymentOptions; /* * TODO: should remove `provider` and require a fully qualified `eventType` * once all providers have migrated to new format. @@ -432,6 +439,37 @@ export function makeCloudFunction({ }, }); + Object.defineProperty(cloudFunction, '__endpoint', { + get: () => { + if (triggerResource() == null) { + return {}; + } + + const endpoint: ManifestEndpoint = { + platform: 'gcfv1', + ...optionsToEndpoint(options), + }; + + if (options.schedule) { + endpoint.scheduleTrigger = options.schedule; + } else { + endpoint.eventTrigger = { + eventType: legacyEventType || provider + '.' + eventType, + eventFilters: { + resource: triggerResource(), + }, + retry: !!options.failurePolicy, + }; + } + + if (Object.keys(labels).length > 0) { + endpoint.labels = { ...endpoint.labels, ...labels }; + } + + return endpoint; + }, + }); + cloudFunction.run = handler || contextOnlyHandler; return cloudFunction; } @@ -545,3 +583,45 @@ export function optionsToTrigger(options: DeploymentOptions) { return trigger; } + +export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint { + const endpoint: ManifestEndpoint = {}; + copyIfPresent( + endpoint, + options, + 'minInstances', + 'maxInstances', + 'ingressSettings', + 'vpcConnectorEgressSettings', + 'vpcConnector', + 'labels' + ); + convertIfPresent( + endpoint, + options, + 'timeout', + 'timeoutSeconds', + durationFromSeconds + ); + convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); + convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { + const memoryLookup = { + '128MB': 128, + '256MB': 256, + '512MB': 512, + '1GB': 1024, + '2GB': 2048, + '4GB': 4096, + '8GB': 8192, + }; + return memoryLookup[mem]; + }); + convertIfPresent( + endpoint, + options, + 'serviceAccountEmail', + 'serviceAccount', + serviceAccountFromShorthand + ); + return endpoint; +} diff --git a/src/common/manifest/v1alpha.ts b/src/common/manifest/v1alpha.ts new file mode 100644 index 000000000..90d186970 --- /dev/null +++ b/src/common/manifest/v1alpha.ts @@ -0,0 +1,65 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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. +export interface ManifestEndpoint { + region?: string[]; + platform?: string; + entryPoint?: string; + availableMemoryMb?: number; + maxInstances?: number; + minInstances?: number; + concurrency?: number; + serviceAccountEmail?: string; + timeout?: string; + vpcConnector?: string; + vpcConnectorEgressSettings?: string; + labels?: Record; + ingressSettings?: string; + environmentVariables?: Record; + httpsTrigger?: { + invoker?: string[]; + }; + eventTrigger?: { + eventFilters: Record; + eventType: string; + retry: boolean; + region?: string; + serviceAccountEmail?: string; + }; + scheduleTrigger?: { + schedule?: string; + timezone?: string; + retryConfig?: { + retryCount?: number; + maxRetryDuration?: string; + minBackoffDuration?: string; + maxBackoffDuration?: string; + maxDoublings?: number; + }; + }; +} + +/** An definition of a function deployment as appears in the Manifest. **/ +export interface ManifestBackend { + specVersion: "v1alpha"; + requiredAPIs: Record; + endpoints: Record; +} diff --git a/src/handler-builder.ts b/src/handler-builder.ts index ad4cd1541..d620eee57 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -70,6 +70,7 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onRequestWithOptions(handler, {}); func.__trigger = {}; + func.__endpoint = {}; return func; }, onCall: ( @@ -80,6 +81,7 @@ export class HandlerBuilder { ): HttpsFunction => { const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; + func.__endpoint = {}; return func; }, }; diff --git a/src/providers/https.ts b/src/providers/https.ts index 8a48f41fe..f85a8db56 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -22,7 +22,12 @@ import * as express from 'express'; -import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; +import { + HttpsFunction, + optionsToEndpoint, + optionsToTrigger, + Runnable, +} from '../cloud-functions'; import { convertIfPresent, convertInvoker } from '../common/encoding'; import { CallableContext, @@ -77,6 +82,19 @@ export function _onRequestWithOptions( convertInvoker ); // TODO parse the options + + cloudFunction.__endpoint = { + platform: 'gcfv1', + ...optionsToEndpoint(options), + httpsTrigger: {}, + }; + convertIfPresent( + cloudFunction.__endpoint.httpsTrigger, + options, + 'invoker', + 'invoker', + convertInvoker + ); return cloudFunction; } @@ -105,6 +123,14 @@ export function _onCallWithOptions( }; func.__trigger.labels['deployment-callable'] = 'true'; + func.__endpoint = { + platform: 'gcfv1', + labels: {}, + ...optionsToEndpoint(options), + httpsTrigger: {}, + }; + func.__endpoint.labels['deployment-callable'] = 'true'; + func.run = handler; return func; diff --git a/src/v2/core.ts b/src/v2/core.ts index d471c107b..613bddee0 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -92,6 +92,7 @@ export interface CloudFunction { (raw: CloudEvent): any | Promise; __trigger: unknown; + __endpoint: unknown; run(event: CloudEvent): any | Promise; } diff --git a/src/v2/options.ts b/src/v2/options.ts index 076cb8532..e3621b21d 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -29,6 +29,7 @@ import * as logger from '../logger'; import { TriggerAnnotation } from './core'; import { declaredParams } from './params'; import { ParamSpec } from './params/types'; +import { ManifestEndpoint } from '../common/manifest/v1alpha'; /** * List of all regions supported by Cloud Functions v2 @@ -279,6 +280,75 @@ export function optionsToTriggerAnnotations( return annotation; } +/** + * Apply GlobalOptions to endpoint manifest. + * @internal + */ +export function optionsToManifestEndpoint( + opts: GlobalOptions | EventHandlerOptions +): ManifestEndpoint { + const endpoint: ManifestEndpoint = {}; + copyIfPresent( + endpoint, + opts, + 'concurrency', + 'minInstances', + 'maxInstances', + 'ingressSettings', + 'vpcConnectorEgressSettings', + 'vpcConnector', + 'labels' + ); + convertIfPresent( + endpoint, + opts, + 'timeout', + 'timeoutSeconds', + durationFromSeconds + ); + convertIfPresent(endpoint, opts, 'availableMemoryMb', 'memory', (mem) => { + const memoryLookup = { + '128MB': 128, + '256MB': 256, + '512MB': 512, + '1GB': 1024, + '2GB': 2048, + '4GB': 4096, + '8GB': 8192, + }; + return memoryLookup[mem]; + }); + convertIfPresent( + endpoint, + opts, + 'serviceAccountEmail', + 'serviceAccount', + serviceAccountFromShorthand + ); + convertIfPresent(endpoint, opts, 'region', 'region', (region) => { + if (typeof region === 'string') { + return [region]; + } + return region; + }); + convertIfPresent( + endpoint, + opts, + 'serviceAccountEmail', + 'serviceAccount', + serviceAccountFromShorthand + ); + convertIfPresent( + endpoint, + opts, + 'timeout', + 'timeoutSeconds', + durationFromSeconds + ); + + return endpoint; +} + /** * @hidden */ diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 9e4c466e4..20977e261 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -32,6 +32,7 @@ import { Request, } from '../../common/providers/https'; import * as options from '../options'; +import {ManifestEndpoint} from "../../common/manifest/v1alpha"; export { Request, CallableRequest, FunctionsErrorCode, HttpsError }; @@ -46,7 +47,7 @@ export interface HttpsOptions extends Omit { export type HttpsFunction = (( req: Request, res: express.Response -) => void | Promise) & { __trigger: unknown }; +) => void | Promise) & { __trigger: unknown } & { __endpoint: unknown }; export interface CallableFunction extends HttpsFunction { run(data: CallableRequest): Return; } @@ -95,6 +96,7 @@ export function onRequest( }); }; } + Object.defineProperty(handler, '__trigger', { get: () => { const baseOpts = options.optionsToTriggerAnnotations( @@ -130,6 +132,38 @@ export function onRequest( return trigger; }, }); + + Object.defineProperty(handler, '__endpoint', { + get: () => { + const baseOpts = options.optionsToManifestEndpoint( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToManifestEndpoint( + opts as options.GlobalOptions + ); + const endpoint: ManifestEndpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + httpsTrigger: {}, + }; + convertIfPresent( + endpoint.httpsTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker + ); + return endpoint; + }, + }); + return handler as HttpsFunction; } @@ -191,6 +225,30 @@ export function onCall>( }, }); + Object.defineProperty(func, '__endpoint', { + get: () => { + const baseOpts = options.optionsToManifestEndpoint( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToManifestEndpoint( + opts as options.GlobalOptions + ); + return { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + 'deployment-callable': 'true', + }, + httpsTrigger: {}, + }; + }, + }); + func.run = handler; return func; } diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index cca25b294..2d98f979b 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -1,5 +1,7 @@ import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; +import {copyIfPresent} from "../../common/encoding"; +import {ManifestEndpoint} from "../../common/manifest/v1alpha"; /** * Interface representing a Google Cloud Pub/Sub message. @@ -134,9 +136,10 @@ export function onMessagePublished( func.run = handler; // TypeScript doesn't recongize defineProperty as adding a property and complains - // that __trigger doesn't exist. We can either cast to any and lose all type safety - // or we can just assign a meaningless value before calling defineProperty. + // that __trigger/__endpoint doesn't exist. We can either cast to any and lose all + // type safety or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; + func.__endpoint = 'silence the transpiler'; Object.defineProperty(func, '__trigger', { get: () => { @@ -164,5 +167,34 @@ export function onMessagePublished( }, }); + Object.defineProperty(func, '__endpoint', { + get: () => { + const baseOpts = options.optionsToManifestEndpoint( + options.getGlobalOptions() + ); + const specificOpts = options.optionsToManifestEndpoint(opts); + + const endpoint: ManifestEndpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType: 'google.cloud.pubsub.topic.v1.messagePublished', + eventFilters: { + resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, + }, + retry: false, + } + }; + copyIfPresent(endpoint.eventTrigger, opts, "retry", "retry"); + + return endpoint; + }, + }); + return func; } diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 4240ae9a9..e61878e0a 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -23,6 +23,8 @@ import { firebaseConfig } from '../../config'; import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; +import { copyIfPresent } from '../../common/encoding'; +import { ManifestEndpoint } from '../../common/manifest/v1alpha'; /** * An object within Google Cloud Storage. @@ -314,9 +316,10 @@ export function onOperation( func.run = handler; // TypeScript doesn't recongize defineProperty as adding a property and complains - // that __trigger doesn't exist. We can either cast to any and lose all type safety + // that __trigger/__endpoint doesn't exist. We can either cast to any and lose all type safety // or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; + func.__endpoint = 'silence the transpiler'; Object.defineProperty(func, '__trigger', { get: () => { @@ -341,6 +344,34 @@ export function onOperation( }, }); + Object.defineProperty(func, '__endpoint', { + get: () => { + const baseOpts = options.optionsToManifestEndpoint( + options.getGlobalOptions() + ); + const specificOpts = options.optionsToManifestEndpoint(opts); + + const endpoint: ManifestEndpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType: eventType, + eventFilters: { + bucket, + }, + retry: false, + }, + }; + copyIfPresent(endpoint.eventTrigger, opts, 'retry', 'retry'); + return endpoint; + }, + }); + return func; } From fb5af884d790e8ac0d40f274067ada3bfa1895c0 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 27 Oct 2021 11:35:13 -0700 Subject: [PATCH 02/24] It's v1alpha1 --- src/common/manifest/{v1alpha.ts => v1alpha1.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/common/manifest/{v1alpha.ts => v1alpha1.ts} (98%) diff --git a/src/common/manifest/v1alpha.ts b/src/common/manifest/v1alpha1.ts similarity index 98% rename from src/common/manifest/v1alpha.ts rename to src/common/manifest/v1alpha1.ts index 90d186970..ca18a9618 100644 --- a/src/common/manifest/v1alpha.ts +++ b/src/common/manifest/v1alpha1.ts @@ -59,7 +59,7 @@ export interface ManifestEndpoint { /** An definition of a function deployment as appears in the Manifest. **/ export interface ManifestBackend { - specVersion: "v1alpha"; + specVersion: "v1alpha1"; requiredAPIs: Record; endpoints: Record; } From 3c58ff3014eb46a10c54bf2431de52589140043b Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 27 Oct 2021 13:10:09 -0700 Subject: [PATCH 03/24] Finish writing test. Cleanup. --- spec/v1/cloud-functions.spec.ts | 38 +++----- spec/v1/providers/https.spec.ts | 8 +- spec/v2/providers/https.spec.ts | 8 ++ spec/v2/providers/pubsub.spec.ts | 4 + spec/v2/providers/storage.spec.ts | 156 +++++++++++++++++++++++++++++- src/cloud-functions.ts | 6 +- src/v2/options.ts | 2 +- src/v2/providers/https.ts | 22 ++--- src/v2/providers/pubsub.ts | 12 +-- src/v2/providers/storage.ts | 4 +- 10 files changed, 208 insertions(+), 52 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index d4e335932..79f9666d3 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -41,7 +41,7 @@ describe('makeCloudFunction', () => { legacyEventType: 'providers/provider/eventTypes/event', }; - it('should put a __trigger on the returned CloudFunction', () => { + it('should put a __trigger/__endpoint on the returned CloudFunction', () => { const cf = makeCloudFunction({ provider: 'mock.provider', eventType: 'mock.event', @@ -49,34 +49,15 @@ describe('makeCloudFunction', () => { triggerResource: () => 'resource', handler: () => null, }); - expect(cf.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'mock.provider.mock.event', - resource: 'resource', - service: 'service', - }, - }); - }); - it('should have legacy event type in __trigger if provided', () => { - const cf = makeCloudFunction(cloudFunctionArgs); expect(cf.__trigger).to.deep.equal({ eventTrigger: { - eventType: 'providers/provider/eventTypes/event', + eventType: 'mock.provider.mock.event', resource: 'resource', service: 'service', }, }); - }); - it('should put a __endpoint on the returned CloudFunction', () => { - const cf = makeCloudFunction({ - provider: 'mock.provider', - eventType: 'mock.event', - service: 'service', - triggerResource: () => 'resource', - handler: () => null, - }); expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', eventTrigger: { @@ -89,8 +70,17 @@ describe('makeCloudFunction', () => { }); }); - it('should have legacy event type in __endpoint if provided', () => { + it('should have legacy event type in __trigger if provided', () => { const cf = makeCloudFunction(cloudFunctionArgs); + + expect(cf.__trigger).to.deep.equal({ + eventTrigger: { + eventType: 'providers/provider/eventTypes/event', + resource: 'resource', + service: 'service', + }, + }); + expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', eventTrigger: { @@ -117,6 +107,7 @@ describe('makeCloudFunction', () => { serviceAccount: 'foo@google.com', }, }); + expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', timeout: '10s', @@ -142,6 +133,7 @@ describe('makeCloudFunction', () => { handler: () => null, options: { failurePolicy: { retry: {} } }, }); + expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', eventTrigger: { @@ -154,7 +146,7 @@ describe('makeCloudFunction', () => { }); }); - it('should setup scheduleTrigger in __endpoint given a schedule', () => { + it('should setup a scheduleTrigger in __endpoint given a schedule', () => { const schedule = { schedule: 'every 5 minutes', retryConfig: { retryCount: 3 }, diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 57193cc37..b5da8e5b2 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; import * as express from 'express'; -import * as _ from 'lodash'; + import * as functions from '../../../src/index'; import * as https from '../../../src/providers/https'; import { @@ -94,7 +94,7 @@ function runHandler( describe('CloudHttpsBuilder', () => { describe('#onRequest', () => { - it('should return a Trigger with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const result = https.onRequest((req, resp) => { resp.send(200); }); @@ -149,7 +149,7 @@ describe('handler namespace', () => { }); describe('#onCall', () => { - it('should return a Trigger and Endpoint with appropriate values', () => { + it('should return a trigger/endpoint with appropriate values', () => { const result = https.onCall((data) => { return 'response'; }); @@ -158,7 +158,7 @@ describe('#onCall', () => { labels: { 'deployment-callable': 'true' }, }); expect(result.__endpoint).to.deep.equal({ - platform: "gcfv1", + platform: 'gcfv1', httpsTrigger: {}, labels: { 'deployment-callable': 'true' }, }); diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 65426ab94..4e8e131c6 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -86,6 +86,7 @@ describe('onRequest', () => { const result = https.onRequest((req, res) => { res.send(200); }); + expect(result.__trigger).to.deep.equal({ apiVersion: 2, platform: 'gcfv2', @@ -94,6 +95,7 @@ describe('onRequest', () => { }, labels: {}, }); + expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', httpsTrigger: {}, @@ -112,6 +114,7 @@ describe('onRequest', () => { res.send(200); } ); + expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, httpsTrigger: { @@ -120,6 +123,7 @@ describe('onRequest', () => { }, regions: ['us-west1', 'us-central1'], }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, httpsTrigger: { @@ -234,6 +238,7 @@ describe('onCall', () => { it('should return a minimal trigger/endpoint with appropriate values', () => { const result = https.onCall((request) => 42); + expect(result.__trigger).to.deep.equal({ apiVersion: 2, platform: 'gcfv2', @@ -244,6 +249,7 @@ describe('onCall', () => { 'deployment-callable': 'true', }, }); + expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', httpsTrigger: {}, @@ -255,6 +261,7 @@ describe('onCall', () => { it('should create a complex trigger/endpoint with appropriate values', () => { const result = https.onCall(FULL_OPTIONS, (request) => 42); + expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, httpsTrigger: { @@ -265,6 +272,7 @@ describe('onCall', () => { 'deployment-callable': 'true', }, }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, httpsTrigger: {}, diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index 46910baa3..12ca63a81 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -30,12 +30,14 @@ describe('onMessagePublished', () => { it('should return a minimal trigger/endpoint with appropriate values', () => { const result = pubsub.onMessagePublished('topic', () => 42); + expect(result.__trigger).to.deep.equal({ apiVersion: 2, platform: 'gcfv2', eventTrigger: EVENT_TRIGGER, labels: {}, }); + expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', eventTrigger: ENDPOINT_EVENT_TRIGGER, @@ -48,10 +50,12 @@ describe('onMessagePublished', () => { { ...FULL_OPTIONS, topic: 'topic' }, () => 42 ); + expect(result.__trigger).to.deep.equal({ ...FULL_TRIGGER, eventTrigger: EVENT_TRIGGER, }); + expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, eventTrigger: ENDPOINT_EVENT_TRIGGER, diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index b3873e194..313ed6a22 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -76,7 +76,7 @@ describe('v2/storage', () => { configStub.restore(); }); - it('should create a minimal trigger with bucket', () => { + it('should create a minimal trigger/endpoint with bucket', () => { const result = storage.onOperation('event-type', 'some-bucket', () => 42); expect(result.__trigger).to.deep.equal({ @@ -92,7 +92,7 @@ describe('v2/storage', () => { }); }); - it('should create a minimal trigger with opts', () => { + it('should create a minimal trigger/endpoint with opts', () => { configStub.returns({ storageBucket: 'default-bucket' }); const result = storage.onOperation( @@ -144,7 +144,7 @@ describe('v2/storage', () => { }); }); - it('should create a complex trigger with appropriate values', () => { + it('should create a complex trigger/endpoint with appropriate values', () => { const result = storage.onOperation( 'event-type', { @@ -360,6 +360,10 @@ describe('v2/storage', () => { ...EVENT_TRIGGER, eventType: storage.finalizedEvent, }; + const ENDPOINT_FINALIZED_TRIGGER = { + ...ENDPOINT_EVENT_TRIGGER, + eventType: storage.finalizedEvent, + }; let configStub: sinon.SinonStub; beforeEach(() => { @@ -383,6 +387,17 @@ describe('v2/storage', () => { resource: 'default-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_FINALIZED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + }); }); it('should accept bucket and handler', () => { @@ -396,6 +411,17 @@ describe('v2/storage', () => { resource: 'my-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_FINALIZED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + }); }); it('should accept opts and handler', () => { @@ -413,6 +439,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_FINALIZED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + region: ['us-west1'], + }); }); it('should accept opts and handler, default bucket', () => { @@ -432,6 +470,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_FINALIZED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + region: ['us-west1'], + }); }); }); @@ -440,6 +490,10 @@ describe('v2/storage', () => { ...EVENT_TRIGGER, eventType: storage.deletedEvent, }; + const ENDPOINT_DELETED_TRIGGER = { + ...ENDPOINT_EVENT_TRIGGER, + eventType: storage.deletedEvent, + }; let configStub: sinon.SinonStub; beforeEach(() => { @@ -464,6 +518,17 @@ describe('v2/storage', () => { }, }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_DELETED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + }); + configStub.restore(); }); @@ -478,6 +543,17 @@ describe('v2/storage', () => { resource: 'my-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_DELETED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + }); }); it('should accept opts and handler', () => { @@ -495,6 +571,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_DELETED_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + region: ['us-west1'], + }); }); it('should accept opts and handler, default bucket', () => { @@ -511,6 +599,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_DELETED_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + region: ['us-west1'], + }); }); }); @@ -519,6 +619,10 @@ describe('v2/storage', () => { ...EVENT_TRIGGER, eventType: storage.metadataUpdatedEvent, }; + const ENDPOINT_METADATA_TRIGGER = { + ...ENDPOINT_EVENT_TRIGGER, + eventType: storage.metadataUpdatedEvent, + }; let configStub: sinon.SinonStub; beforeEach(() => { @@ -543,6 +647,17 @@ describe('v2/storage', () => { }, }); + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_METADATA_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + }); + configStub.restore(); }); @@ -557,6 +672,17 @@ describe('v2/storage', () => { resource: 'my-bucket', }, }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_METADATA_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + }); }); it('should accept opts and handler', () => { @@ -574,6 +700,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_METADATA_TRIGGER, + eventFilters: { + bucket: 'my-bucket', + }, + }, + region: ['us-west1'], + }); }); it('should accept opts and handler, default bucket', () => { @@ -593,6 +731,18 @@ describe('v2/storage', () => { }, regions: ['us-west1'], }); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + ...ENDPOINT_METADATA_TRIGGER, + eventFilters: { + bucket: 'default-bucket', + }, + }, + region: ['us-west1'], + }); }); }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 5454aa374..bf232dd15 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -37,7 +37,7 @@ import { durationFromSeconds, serviceAccountFromShorthand, } from './common/encoding'; -import { ManifestEndpoint } from './common/manifest/v1alpha'; +import { ManifestEndpoint } from './common/manifest/v1alpha1'; /** @hidden */ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); @@ -584,7 +584,9 @@ export function optionsToTrigger(options: DeploymentOptions) { return trigger; } -export function optionsToEndpoint(options: DeploymentOptions): ManifestEndpoint { +export function optionsToEndpoint( + options: DeploymentOptions +): ManifestEndpoint { const endpoint: ManifestEndpoint = {}; copyIfPresent( endpoint, diff --git a/src/v2/options.ts b/src/v2/options.ts index e3621b21d..719cf8949 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -29,7 +29,7 @@ import * as logger from '../logger'; import { TriggerAnnotation } from './core'; import { declaredParams } from './params'; import { ParamSpec } from './params/types'; -import { ManifestEndpoint } from '../common/manifest/v1alpha'; +import { ManifestEndpoint } from '../common/manifest/v1alpha1'; /** * List of all regions supported by Cloud Functions v2 diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 20977e261..df51dc534 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -24,6 +24,7 @@ import * as cors from 'cors'; import * as express from 'express'; import { convertIfPresent, convertInvoker } from '../../common/encoding'; +import * as options from '../options'; import { CallableRequest, FunctionsErrorCode, @@ -31,8 +32,7 @@ import { onCallHandler, Request, } from '../../common/providers/https'; -import * as options from '../options'; -import {ManifestEndpoint} from "../../common/manifest/v1alpha"; +import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; export { Request, CallableRequest, FunctionsErrorCode, HttpsError }; @@ -136,12 +136,12 @@ export function onRequest( Object.defineProperty(handler, '__endpoint', { get: () => { const baseOpts = options.optionsToManifestEndpoint( - options.getGlobalOptions() + options.getGlobalOptions() ); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. const specificOpts = options.optionsToManifestEndpoint( - opts as options.GlobalOptions + opts as options.GlobalOptions ); const endpoint: ManifestEndpoint = { platform: 'gcfv2', @@ -154,11 +154,11 @@ export function onRequest( httpsTrigger: {}, }; convertIfPresent( - endpoint.httpsTrigger, - opts, - 'invoker', - 'invoker', - convertInvoker + endpoint.httpsTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker ); return endpoint; }, @@ -228,12 +228,12 @@ export function onCall>( Object.defineProperty(func, '__endpoint', { get: () => { const baseOpts = options.optionsToManifestEndpoint( - options.getGlobalOptions() + options.getGlobalOptions() ); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. const specificOpts = options.optionsToManifestEndpoint( - opts as options.GlobalOptions + opts as options.GlobalOptions ); return { platform: 'gcfv2', diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 2d98f979b..43177af52 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -1,7 +1,7 @@ -import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; -import {copyIfPresent} from "../../common/encoding"; -import {ManifestEndpoint} from "../../common/manifest/v1alpha"; +import { CloudEvent, CloudFunction } from '../core'; +import { copyIfPresent } from '../../common/encoding'; +import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; /** * Interface representing a Google Cloud Pub/Sub message. @@ -170,7 +170,7 @@ export function onMessagePublished( Object.defineProperty(func, '__endpoint', { get: () => { const baseOpts = options.optionsToManifestEndpoint( - options.getGlobalOptions() + options.getGlobalOptions() ); const specificOpts = options.optionsToManifestEndpoint(opts); @@ -188,9 +188,9 @@ export function onMessagePublished( resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, }, retry: false, - } + }, }; - copyIfPresent(endpoint.eventTrigger, opts, "retry", "retry"); + copyIfPresent(endpoint.eventTrigger, opts, 'retry', 'retry'); return endpoint; }, diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index e61878e0a..73c4b8a40 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -20,11 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import * as options from '../options'; import { firebaseConfig } from '../../config'; import { CloudEvent, CloudFunction } from '../core'; -import * as options from '../options'; import { copyIfPresent } from '../../common/encoding'; -import { ManifestEndpoint } from '../../common/manifest/v1alpha'; +import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; /** * An object within Google Cloud Storage. From 5b369f13bac7def6d070e8370b9c2b5f68b708b9 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 27 Oct 2021 14:58:41 -0700 Subject: [PATCH 04/24] Prettier. --- src/common/manifest/v1alpha1.ts | 72 ++++++++++++++++----------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/common/manifest/v1alpha1.ts b/src/common/manifest/v1alpha1.ts index ca18a9618..11d7125de 100644 --- a/src/common/manifest/v1alpha1.ts +++ b/src/common/manifest/v1alpha1.ts @@ -20,46 +20,46 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. export interface ManifestEndpoint { - region?: string[]; - platform?: string; - entryPoint?: string; - availableMemoryMb?: number; - maxInstances?: number; - minInstances?: number; - concurrency?: number; + region?: string[]; + platform?: string; + entryPoint?: string; + availableMemoryMb?: number; + maxInstances?: number; + minInstances?: number; + concurrency?: number; + serviceAccountEmail?: string; + timeout?: string; + vpcConnector?: string; + vpcConnectorEgressSettings?: string; + labels?: Record; + ingressSettings?: string; + environmentVariables?: Record; + httpsTrigger?: { + invoker?: string[]; + }; + eventTrigger?: { + eventFilters: Record; + eventType: string; + retry: boolean; + region?: string; serviceAccountEmail?: string; - timeout?: string; - vpcConnector?: string; - vpcConnectorEgressSettings?: string; - labels?: Record; - ingressSettings?: string; - environmentVariables?: Record; - httpsTrigger?: { - invoker?: string[]; - }; - eventTrigger?: { - eventFilters: Record; - eventType: string; - retry: boolean; - region?: string; - serviceAccountEmail?: string; - }; - scheduleTrigger?: { - schedule?: string; - timezone?: string; - retryConfig?: { - retryCount?: number; - maxRetryDuration?: string; - minBackoffDuration?: string; - maxBackoffDuration?: string; - maxDoublings?: number; - }; + }; + scheduleTrigger?: { + schedule?: string; + timezone?: string; + retryConfig?: { + retryCount?: number; + maxRetryDuration?: string; + minBackoffDuration?: string; + maxBackoffDuration?: string; + maxDoublings?: number; }; + }; } /** An definition of a function deployment as appears in the Manifest. **/ export interface ManifestBackend { - specVersion: "v1alpha1"; - requiredAPIs: Record; - endpoints: Record; + specVersion: 'v1alpha1'; + requiredAPIs: Record; + endpoints: Record; } From 2db4640d86553503715a1df18a242b3c443e48c4 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 27 Oct 2021 15:16:02 -0700 Subject: [PATCH 05/24] Nits. --- spec/v1/cloud-functions.spec.ts | 2 +- spec/v1/providers/https.spec.ts | 6 ++++-- src/cloud-functions.ts | 14 +++++++++----- src/common/manifest/v1alpha1.ts | 9 ++++++++- src/v2/providers/https.ts | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index 79f9666d3..e76f7c830 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -70,7 +70,7 @@ describe('makeCloudFunction', () => { }); }); - it('should have legacy event type in __trigger if provided', () => { + it('should have legacy event type in __trigger/__endpoint if provided', () => { const cf = makeCloudFunction(cloudFunctionArgs); expect(cf.__trigger).to.deep.equal({ diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index b5da8e5b2..415846356 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -130,7 +130,7 @@ describe('CloudHttpsBuilder', () => { describe('handler namespace', () => { describe('#onRequest', () => { - it('should return empty trigger and endpoint', () => { + it('should return an empty trigger and endpoint', () => { const result = functions.handler.https.onRequest((req, res) => { res.send(200); }); @@ -140,7 +140,7 @@ describe('handler namespace', () => { }); describe('#onCall', () => { - it('should return empty trigger and endpoint', () => { + it('should return an empty trigger and endpoint', () => { const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); expect(result.__endpoint).to.deep.equal({}); @@ -153,10 +153,12 @@ describe('#onCall', () => { const result = https.onCall((data) => { return 'response'; }); + expect(result.__trigger).to.deep.equal({ httpsTrigger: {}, labels: { 'deployment-callable': 'true' }, }); + expect(result.__endpoint).to.deep.equal({ platform: 'gcfv1', httpsTrigger: {}, diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index bf232dd15..8858665a2 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -284,7 +284,11 @@ export interface TriggerAnnotated { }; } -export interface TriggerEndpoint { +/** + * @hidden + * EndpointAnnotated is used to generate the manifest that conforms to the container contract. + */ +export interface EndpointAnnotated { __endpoint: ManifestEndpoint; } @@ -306,7 +310,7 @@ export interface Runnable { * arguments. */ export type HttpsFunction = TriggerAnnotated & - TriggerEndpoint & + EndpointAnnotated & ((req: Request, resp: Response) => void | Promise); /** @@ -318,7 +322,7 @@ export type HttpsFunction = TriggerAnnotated & */ export type CloudFunction = Runnable & TriggerAnnotated & - TriggerEndpoint & + EndpointAnnotated & ((input: any, context?: any) => PromiseLike | any); /** @hidden */ @@ -329,7 +333,7 @@ export interface MakeCloudFunctionArgs { dataConstructor?: (raw: Event) => EventData; eventType: string; handler?: (data: EventData, context: EventContext) => PromiseLike | any; - labels?: { [key: string]: any }; + labels?: Record; legacyEventType?: string; options?: DeploymentOptions; /* @@ -442,7 +446,7 @@ export function makeCloudFunction({ Object.defineProperty(cloudFunction, '__endpoint', { get: () => { if (triggerResource() == null) { - return {}; + return undefined; } const endpoint: ManifestEndpoint = { diff --git a/src/common/manifest/v1alpha1.ts b/src/common/manifest/v1alpha1.ts index 11d7125de..9f862128e 100644 --- a/src/common/manifest/v1alpha1.ts +++ b/src/common/manifest/v1alpha1.ts @@ -19,6 +19,10 @@ // 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. +/** + * @internal + * An definition of a function as appears in the Manifest. + */ export interface ManifestEndpoint { region?: string[]; platform?: string; @@ -57,7 +61,10 @@ export interface ManifestEndpoint { }; } -/** An definition of a function deployment as appears in the Manifest. **/ +/** + * @internal + * An definition of a function deployment as appears in the Manifest. +**/ export interface ManifestBackend { specVersion: 'v1alpha1'; requiredAPIs: Record; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index df51dc534..3f63bf8dc 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -231,7 +231,7 @@ export function onCall>( options.getGlobalOptions() ); // global options calls region a scalar and https allows it to be an array, - // but optionsToTriggerAnnotations handles both cases. + // but optionsToManifestEndpoint handles both cases. const specificOpts = options.optionsToManifestEndpoint( opts as options.GlobalOptions ); From 306c269d48130f1950cc99a1c15526ecab48df7b Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 27 Oct 2021 15:19:12 -0700 Subject: [PATCH 06/24] Prettier --- src/common/manifest/v1alpha1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/manifest/v1alpha1.ts b/src/common/manifest/v1alpha1.ts index 9f862128e..159da3440 100644 --- a/src/common/manifest/v1alpha1.ts +++ b/src/common/manifest/v1alpha1.ts @@ -64,7 +64,7 @@ export interface ManifestEndpoint { /** * @internal * An definition of a function deployment as appears in the Manifest. -**/ + **/ export interface ManifestBackend { specVersion: 'v1alpha1'; requiredAPIs: Record; From 584ff6dc48f7ac7cbbe065bf28a36bde6cdb732e Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Thu, 28 Oct 2021 20:49:47 -0700 Subject: [PATCH 07/24] Unroll v1alpha manifest out of dir. --- src/cloud-functions.ts | 2 +- src/common/{manifest/v1alpha1.ts => manifest.ts} | 0 src/v2/options.ts | 2 +- src/v2/providers/https.ts | 2 +- src/v2/providers/pubsub.ts | 2 +- src/v2/providers/storage.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/common/{manifest/v1alpha1.ts => manifest.ts} (100%) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 8858665a2..6906c7aa4 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -37,7 +37,7 @@ import { durationFromSeconds, serviceAccountFromShorthand, } from './common/encoding'; -import { ManifestEndpoint } from './common/manifest/v1alpha1'; +import { ManifestEndpoint } from './common/manifest'; /** @hidden */ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); diff --git a/src/common/manifest/v1alpha1.ts b/src/common/manifest.ts similarity index 100% rename from src/common/manifest/v1alpha1.ts rename to src/common/manifest.ts diff --git a/src/v2/options.ts b/src/v2/options.ts index 719cf8949..195fb0090 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -29,7 +29,7 @@ import * as logger from '../logger'; import { TriggerAnnotation } from './core'; import { declaredParams } from './params'; import { ParamSpec } from './params/types'; -import { ManifestEndpoint } from '../common/manifest/v1alpha1'; +import { ManifestEndpoint } from '../common/manifest'; /** * List of all regions supported by Cloud Functions v2 diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 3f63bf8dc..da7eaa03c 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -32,7 +32,7 @@ import { onCallHandler, Request, } from '../../common/providers/https'; -import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; +import { ManifestEndpoint } from '../../common/manifest'; export { Request, CallableRequest, FunctionsErrorCode, HttpsError }; diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 43177af52..286dd6968 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -1,7 +1,7 @@ import * as options from '../options'; import { CloudEvent, CloudFunction } from '../core'; import { copyIfPresent } from '../../common/encoding'; -import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; +import { ManifestEndpoint } from '../../common/manifest'; /** * Interface representing a Google Cloud Pub/Sub message. diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 73c4b8a40..aedba12ef 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -24,7 +24,7 @@ import * as options from '../options'; import { firebaseConfig } from '../../config'; import { CloudEvent, CloudFunction } from '../core'; import { copyIfPresent } from '../../common/encoding'; -import { ManifestEndpoint } from '../../common/manifest/v1alpha1'; +import { ManifestEndpoint } from '../../common/manifest'; /** * An object within Google Cloud Storage. From e1d90031d7f7878f549897d8302cca01a616e327 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 3 Nov 2021 12:34:28 -0700 Subject: [PATCH 08/24] Prefer timeoutSeconds over timeout. --- spec/v1/cloud-functions.spec.ts | 2 +- spec/v1/providers/https.spec.ts | 4 ++-- src/cloud-functions.ts | 10 ++-------- src/common/encoding.ts | 5 ----- src/common/manifest.ts | 16 ++++++++++++---- src/v2/options.ts | 27 ++++----------------------- 6 files changed, 21 insertions(+), 43 deletions(-) diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index e76f7c830..5c861f3d6 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -110,7 +110,7 @@ describe('makeCloudFunction', () => { expect(cf.__endpoint).to.deep.equal({ platform: 'gcfv1', - timeout: '10s', + timeoutSeconds: 10, region: ['us-central1'], availableMemoryMb: 128, serviceAccountEmail: 'foo@google.com', diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 415846356..f78c655cc 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -122,7 +122,7 @@ describe('CloudHttpsBuilder', () => { expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeout).to.deep.equal('90s'); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal('90s'); expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(['private']); }); }); @@ -181,7 +181,7 @@ describe('#onCall', () => { expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeout).to.deep.equal('90s'); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal('90s'); }); it('has a .run method', () => { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 6906c7aa4..8d10f6281 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -600,14 +600,8 @@ export function optionsToEndpoint( 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', - 'labels' - ); - convertIfPresent( - endpoint, - options, - 'timeout', - 'timeoutSeconds', - durationFromSeconds + 'labels', + 'timeoutSeconds' ); convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { diff --git a/src/common/encoding.ts b/src/common/encoding.ts index 0cbc2d50f..1810231d4 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -6,11 +6,6 @@ */ export type Duration = string; -/** Get a google.protobuf.Duration for a number of seconds. */ -export function durationFromSeconds(s: number): Duration { - return `${s}s`; -} - /** * 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 diff --git a/src/common/manifest.ts b/src/common/manifest.ts index 159da3440..201e3ffab 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -24,23 +24,30 @@ * An definition of a function as appears in the Manifest. */ export interface ManifestEndpoint { + id: string; region?: string[]; - platform?: string; + platform?: 'gcfv1' | 'gcfv2'; entryPoint?: string; availableMemoryMb?: number; maxInstances?: number; minInstances?: number; concurrency?: number; serviceAccountEmail?: string; - timeout?: string; - vpcConnector?: string; - vpcConnectorEgressSettings?: string; + timeoutSeconds?: number; + vpcConnector: { + id: string; + egressSettings: string; + }; labels?: Record; ingressSettings?: string; environmentVariables?: Record; + httpsTrigger?: { invoker?: string[]; }; + + callableTrigger?: {}; + eventTrigger?: { eventFilters: Record; eventType: string; @@ -48,6 +55,7 @@ export interface ManifestEndpoint { region?: string; serviceAccountEmail?: string; }; + scheduleTrigger?: { schedule?: string; timezone?: string; diff --git a/src/v2/options.ts b/src/v2/options.ts index 195fb0090..988c40589 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -21,10 +21,11 @@ // SOFTWARE. import { + convertIfPresent, + copyIfPresent, durationFromSeconds, serviceAccountFromShorthand, } from '../common/encoding'; -import { convertIfPresent, copyIfPresent } from '../common/encoding'; import * as logger from '../logger'; import { TriggerAnnotation } from './core'; import { declaredParams } from './params'; @@ -297,14 +298,8 @@ export function optionsToManifestEndpoint( 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', - 'labels' - ); - convertIfPresent( - endpoint, - opts, - 'timeout', - 'timeoutSeconds', - durationFromSeconds + 'labels', + 'timeoutSeconds' ); convertIfPresent(endpoint, opts, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { @@ -331,20 +326,6 @@ export function optionsToManifestEndpoint( } return region; }); - convertIfPresent( - endpoint, - opts, - 'serviceAccountEmail', - 'serviceAccount', - serviceAccountFromShorthand - ); - convertIfPresent( - endpoint, - opts, - 'timeout', - 'timeoutSeconds', - durationFromSeconds - ); return endpoint; } From 69a8846c14a5ba021f6803e040fe3fdf1c9f6d9c Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 3 Nov 2021 12:34:43 -0700 Subject: [PATCH 09/24] Prefer topic over generic resource. --- src/v2/providers/pubsub.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 286dd6968..9fe6c6e44 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -184,9 +184,7 @@ export function onMessagePublished( }, eventTrigger: { eventType: 'google.cloud.pubsub.topic.v1.messagePublished', - eventFilters: { - resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, - }, + eventFilters: { topic }, retry: false, }, }; From 8d7cbee0961d0b88674545cd38d7ee6404222b5c Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 3 Nov 2021 16:17:08 -0700 Subject: [PATCH 10/24] Whoops this shouldn't go. --- src/common/encoding.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/encoding.ts b/src/common/encoding.ts index 1810231d4..ac5500325 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -6,6 +6,11 @@ */ export type Duration = string; +/** Get a google.protobuf.Duration for a number of seconds. */ +export function durationFromSeconds(s: number): Duration { + return `${s}s`; +} + /** * 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 @@ -29,14 +34,14 @@ export function convertIfPresent( src: Src, destField: keyof Dest, srcField: keyof Src, - converter: (from: any) => any = (from: any) => { + converter: (from: any, to?: any) => any = (from: any) => { return from; } ) { if (!Object.prototype.hasOwnProperty.call(src, srcField)) { return; } - dest[destField] = converter(src[srcField]); + dest[destField] = converter(src[srcField], dest[destField]); } export function serviceAccountFromShorthand( From eb0a9b039c188605ff18f8407b50a0e8f2514425 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 3 Nov 2021 20:20:09 -0700 Subject: [PATCH 11/24] Refactor vpc option. --- src/cloud-functions.ts | 14 +++++++++----- src/common/manifest.ts | 6 +++--- src/v2/options.ts | 12 ++++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 8d10f6281..66072d81d 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -449,7 +449,7 @@ export function makeCloudFunction({ return undefined; } - const endpoint: ManifestEndpoint = { + const endpoint: Partial = { platform: 'gcfv1', ...optionsToEndpoint(options), }; @@ -590,20 +590,24 @@ export function optionsToTrigger(options: DeploymentOptions) { export function optionsToEndpoint( options: DeploymentOptions -): ManifestEndpoint { - const endpoint: ManifestEndpoint = {}; +): Partial { + const endpoint: Partial = {}; copyIfPresent( endpoint, options, 'minInstances', 'maxInstances', 'ingressSettings', - 'vpcConnectorEgressSettings', - 'vpcConnector', 'labels', 'timeoutSeconds' ); convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); + convertIfPresent(endpoint, options, 'vpc', 'vpcConnector', (connector) => { + return { connector }; + }); + convertIfPresent(endpoint, options, 'vpc', 'vpcConnectorEgressSettings', (egressSettings, vpc) => { + return { ...vpc, egressSettings }; + }); convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128, diff --git a/src/common/manifest.ts b/src/common/manifest.ts index 201e3ffab..9bde32df3 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -25,17 +25,17 @@ */ export interface ManifestEndpoint { id: string; + entryPoint: string; region?: string[]; platform?: 'gcfv1' | 'gcfv2'; - entryPoint?: string; availableMemoryMb?: number; maxInstances?: number; minInstances?: number; concurrency?: number; serviceAccountEmail?: string; timeoutSeconds?: number; - vpcConnector: { - id: string; + vpc: { + connector: string; egressSettings: string; }; labels?: Record; diff --git a/src/v2/options.ts b/src/v2/options.ts index 988c40589..91aa09ece 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -287,8 +287,8 @@ export function optionsToTriggerAnnotations( */ export function optionsToManifestEndpoint( opts: GlobalOptions | EventHandlerOptions -): ManifestEndpoint { - const endpoint: ManifestEndpoint = {}; +): Partial { + const endpoint: Partial = {}; copyIfPresent( endpoint, opts, @@ -296,11 +296,15 @@ export function optionsToManifestEndpoint( 'minInstances', 'maxInstances', 'ingressSettings', - 'vpcConnectorEgressSettings', - 'vpcConnector', 'labels', 'timeoutSeconds' ); + convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { + return { connector }; + }); + convertIfPresent(endpoint, opts, 'vpc', 'vpcConnectorEgressSettings', (egressSettings, vpc) => { + return { ...vpc, egressSettings }; + }); convertIfPresent(endpoint, opts, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128, From 105cc7ce94d91b15c85ee5e7f4c44e163325b8fe Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Wed, 3 Nov 2021 20:55:50 -0700 Subject: [PATCH 12/24] Finalize refactor. --- spec/v1/providers/https.spec.ts | 4 ++-- spec/v2/providers/helpers.ts | 8 +++++--- spec/v2/providers/pubsub.spec.ts | 2 +- spec/v2/providers/storage.spec.ts | 8 +++++--- src/cloud-functions.ts | 18 ++++++++++++------ src/common/manifest.ts | 19 ++++++++++++------- src/v2/options.ts | 16 +++++++++++----- src/v2/providers/https.ts | 2 +- 8 files changed, 49 insertions(+), 28 deletions(-) diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index f78c655cc..82233cd9c 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -122,7 +122,7 @@ describe('CloudHttpsBuilder', () => { expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal('90s'); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); expect(fn.__endpoint.httpsTrigger.invoker).to.deep.equal(['private']); }); }); @@ -181,7 +181,7 @@ describe('#onCall', () => { expect(fn.__endpoint.region).to.deep.equal(['us-east1']); expect(fn.__endpoint.availableMemoryMb).to.deep.equal(256); - expect(fn.__endpoint.timeoutSeconds).to.deep.equal('90s'); + expect(fn.__endpoint.timeoutSeconds).to.deep.equal(90); }); it('has a .run method', () => { diff --git a/spec/v2/providers/helpers.ts b/spec/v2/providers/helpers.ts index 91b386e1a..9cbd6d21b 100644 --- a/spec/v2/providers/helpers.ts +++ b/spec/v2/providers/helpers.ts @@ -38,12 +38,14 @@ export const FULL_ENDPOINT = { platform: 'gcfv2', region: ['us-west1'], availableMemoryMb: 512, - timeout: '60s', + timeoutSeconds: 60, minInstances: 1, maxInstances: 3, concurrency: 20, - vpcConnector: 'aConnector', - vpcConnectorEgressSettings: 'ALL_TRAFFIC', + vpc: { + connector: 'aConnector', + egressSettings: 'ALL_TRAFFIC', + }, serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', ingressSettings: 'ALLOW_ALL', labels: { diff --git a/spec/v2/providers/pubsub.spec.ts b/spec/v2/providers/pubsub.spec.ts index 12ca63a81..f48e10f72 100644 --- a/spec/v2/providers/pubsub.spec.ts +++ b/spec/v2/providers/pubsub.spec.ts @@ -13,7 +13,7 @@ const EVENT_TRIGGER = { const ENDPOINT_EVENT_TRIGGER = { eventType: 'google.cloud.pubsub.topic.v1.messagePublished', eventFilters: { - resource: 'projects/aProject/topics/topic', + topic: 'topic', }, retry: false, }; diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index 313ed6a22..c8c7ffc74 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -176,12 +176,14 @@ describe('v2/storage', () => { platform: 'gcfv2', region: ['us-west1'], availableMemoryMb: 512, - timeout: '60s', + timeoutSeconds: 60, minInstances: 1, maxInstances: 3, concurrency: 20, - vpcConnector: 'aConnector', - vpcConnectorEgressSettings: 'ALL_TRAFFIC', + vpc: { + connector: 'aConnector', + egressSettings: 'ALL_TRAFFIC', + }, serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', ingressSettings: 'ALLOW_ALL', labels: { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 66072d81d..88d4b64ff 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -449,7 +449,7 @@ export function makeCloudFunction({ return undefined; } - const endpoint: Partial = { + const endpoint: ManifestEndpoint = { platform: 'gcfv1', ...optionsToEndpoint(options), }; @@ -590,8 +590,8 @@ export function optionsToTrigger(options: DeploymentOptions) { export function optionsToEndpoint( options: DeploymentOptions -): Partial { - const endpoint: Partial = {}; +): ManifestEndpoint { + const endpoint: ManifestEndpoint = {}; copyIfPresent( endpoint, options, @@ -605,9 +605,15 @@ export function optionsToEndpoint( convertIfPresent(endpoint, options, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); - convertIfPresent(endpoint, options, 'vpc', 'vpcConnectorEgressSettings', (egressSettings, vpc) => { - return { ...vpc, egressSettings }; - }); + convertIfPresent( + endpoint, + options, + 'vpc', + 'vpcConnectorEgressSettings', + (egressSettings, vpc) => { + return { ...vpc, egressSettings }; + } + ); convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128, diff --git a/src/common/manifest.ts b/src/common/manifest.ts index 9bde32df3..c8ebdd0e7 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -24,17 +24,16 @@ * An definition of a function as appears in the Manifest. */ export interface ManifestEndpoint { - id: string; - entryPoint: string; + entryPoint?: string; region?: string[]; - platform?: 'gcfv1' | 'gcfv2'; + platform?: string; availableMemoryMb?: number; maxInstances?: number; minInstances?: number; concurrency?: number; serviceAccountEmail?: string; timeoutSeconds?: number; - vpc: { + vpc?: { connector: string; egressSettings: string; }; @@ -49,11 +48,17 @@ export interface ManifestEndpoint { callableTrigger?: {}; eventTrigger?: { - eventFilters: Record; + eventFilters: { + resource?: string; + topic?: string; + bucket?: string; + }; eventType: string; retry: boolean; - region?: string; - serviceAccountEmail?: string; + trigger?: { + region?: string; + serviceAccountEmail?: string; + }; }; scheduleTrigger?: { diff --git a/src/v2/options.ts b/src/v2/options.ts index 91aa09ece..ddf8ac156 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -287,8 +287,8 @@ export function optionsToTriggerAnnotations( */ export function optionsToManifestEndpoint( opts: GlobalOptions | EventHandlerOptions -): Partial { - const endpoint: Partial = {}; +): ManifestEndpoint { + const endpoint: ManifestEndpoint = {}; copyIfPresent( endpoint, opts, @@ -302,9 +302,15 @@ export function optionsToManifestEndpoint( convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); - convertIfPresent(endpoint, opts, 'vpc', 'vpcConnectorEgressSettings', (egressSettings, vpc) => { - return { ...vpc, egressSettings }; - }); + convertIfPresent( + endpoint, + opts, + 'vpc', + 'vpcConnectorEgressSettings', + (egressSettings, vpc) => { + return { ...vpc, egressSettings }; + } + ); convertIfPresent(endpoint, opts, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128, diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index da7eaa03c..852b8cc72 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -143,7 +143,7 @@ export function onRequest( const specificOpts = options.optionsToManifestEndpoint( opts as options.GlobalOptions ); - const endpoint: ManifestEndpoint = { + const endpoint: Partial = { platform: 'gcfv2', ...baseOpts, ...specificOpts, From 3de212b22d0d099aea43a57f0ce8495d043676ad Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 14:01:13 -0700 Subject: [PATCH 13/24] Don't transform SA, consistent fn name. --- src/cloud-functions.ts | 8 +------- src/v2/options.ts | 10 ++-------- src/v2/providers/https.ts | 8 ++++---- src/v2/providers/pubsub.ts | 4 ++-- src/v2/providers/storage.ts | 4 ++-- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 88d4b64ff..0a93696c3 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -602,6 +602,7 @@ export function optionsToEndpoint( 'timeoutSeconds' ); convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); + convertIfPresent(endpoint, options, 'serviceAccountEmail', 'serviceAccount', (sa) => sa); convertIfPresent(endpoint, options, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); @@ -626,12 +627,5 @@ export function optionsToEndpoint( }; return memoryLookup[mem]; }); - convertIfPresent( - endpoint, - options, - 'serviceAccountEmail', - 'serviceAccount', - serviceAccountFromShorthand - ); return endpoint; } diff --git a/src/v2/options.ts b/src/v2/options.ts index ddf8ac156..32e470b0c 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -285,7 +285,7 @@ export function optionsToTriggerAnnotations( * Apply GlobalOptions to endpoint manifest. * @internal */ -export function optionsToManifestEndpoint( +export function optionsToEndpoint( opts: GlobalOptions | EventHandlerOptions ): ManifestEndpoint { const endpoint: ManifestEndpoint = {}; @@ -299,6 +299,7 @@ export function optionsToManifestEndpoint( 'labels', 'timeoutSeconds' ); + convertIfPresent(endpoint, opts, "serviceAccountEmail", "serviceAccount", (sa) => sa); convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); @@ -323,13 +324,6 @@ export function optionsToManifestEndpoint( }; return memoryLookup[mem]; }); - convertIfPresent( - endpoint, - opts, - 'serviceAccountEmail', - 'serviceAccount', - serviceAccountFromShorthand - ); convertIfPresent(endpoint, opts, 'region', 'region', (region) => { if (typeof region === 'string') { return [region]; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 852b8cc72..53e5f049e 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -135,12 +135,12 @@ export function onRequest( Object.defineProperty(handler, '__endpoint', { get: () => { - const baseOpts = options.optionsToManifestEndpoint( + const baseOpts = options.optionsToEndpoint( options.getGlobalOptions() ); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. - const specificOpts = options.optionsToManifestEndpoint( + const specificOpts = options.optionsToEndpoint( opts as options.GlobalOptions ); const endpoint: Partial = { @@ -227,12 +227,12 @@ export function onCall>( Object.defineProperty(func, '__endpoint', { get: () => { - const baseOpts = options.optionsToManifestEndpoint( + const baseOpts = options.optionsToEndpoint( options.getGlobalOptions() ); // global options calls region a scalar and https allows it to be an array, // but optionsToManifestEndpoint handles both cases. - const specificOpts = options.optionsToManifestEndpoint( + const specificOpts = options.optionsToEndpoint( opts as options.GlobalOptions ); return { diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 9fe6c6e44..1255d3f51 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -169,10 +169,10 @@ export function onMessagePublished( Object.defineProperty(func, '__endpoint', { get: () => { - const baseOpts = options.optionsToManifestEndpoint( + const baseOpts = options.optionsToEndpoint( options.getGlobalOptions() ); - const specificOpts = options.optionsToManifestEndpoint(opts); + const specificOpts = options.optionsToEndpoint(opts); const endpoint: ManifestEndpoint = { platform: 'gcfv2', diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 790771eb2..0362df3c3 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -346,10 +346,10 @@ export function onOperation( Object.defineProperty(func, '__endpoint', { get: () => { - const baseOpts = options.optionsToManifestEndpoint( + const baseOpts = options.optionsToEndpoint( options.getGlobalOptions() ); - const specificOpts = options.optionsToManifestEndpoint(opts); + const specificOpts = options.optionsToEndpoint(opts); const endpoint: ManifestEndpoint = { platform: 'gcfv2', From c765db51c4082c1d7ee3f99887d5922539e6cd98 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 15:11:30 -0700 Subject: [PATCH 14/24] Unroll trigger options. --- src/common/manifest.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/common/manifest.ts b/src/common/manifest.ts index c8ebdd0e7..9c08a84d4 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -55,10 +55,8 @@ export interface ManifestEndpoint { }; eventType: string; retry: boolean; - trigger?: { - region?: string; - serviceAccountEmail?: string; - }; + region?: string; + serviceAccountEmail?: string; }; scheduleTrigger?: { From 08cde7ac74ed80e397ddd7546d4821444083e3a9 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 15:21:36 -0700 Subject: [PATCH 15/24] Make callable its own trigger, fix tests. --- spec/v1/providers/https.spec.ts | 4 ++-- spec/v2/providers/helpers.ts | 2 +- spec/v2/providers/https.spec.ts | 15 +++++---------- spec/v2/providers/storage.spec.ts | 2 +- src/providers/https.ts | 3 +-- src/v2/providers/https.ts | 3 +-- 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/spec/v1/providers/https.spec.ts b/spec/v1/providers/https.spec.ts index 82233cd9c..f820fe277 100644 --- a/spec/v1/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -161,8 +161,8 @@ describe('#onCall', () => { expect(result.__endpoint).to.deep.equal({ platform: 'gcfv1', - httpsTrigger: {}, - labels: { 'deployment-callable': 'true' }, + callableTrigger: {}, + labels: {}, }); }); diff --git a/spec/v2/providers/helpers.ts b/spec/v2/providers/helpers.ts index 9cbd6d21b..a499be69a 100644 --- a/spec/v2/providers/helpers.ts +++ b/spec/v2/providers/helpers.ts @@ -46,7 +46,7 @@ export const FULL_ENDPOINT = { connector: 'aConnector', egressSettings: 'ALL_TRAFFIC', }, - serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', + serviceAccountEmail: 'root@', ingressSettings: 'ALLOW_ALL', labels: { hello: 'world', diff --git a/spec/v2/providers/https.spec.ts b/spec/v2/providers/https.spec.ts index 4e8e131c6..e7c34dad0 100644 --- a/spec/v2/providers/https.spec.ts +++ b/spec/v2/providers/https.spec.ts @@ -252,10 +252,8 @@ describe('onCall', () => { expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', - httpsTrigger: {}, - labels: { - 'deployment-callable': 'true', - }, + labels: {}, + callableTrigger: {}, }); }); @@ -275,10 +273,9 @@ describe('onCall', () => { expect(result.__endpoint).to.deep.equal({ ...FULL_ENDPOINT, - httpsTrigger: {}, + callableTrigger: {}, labels: { ...FULL_ENDPOINT.labels, - 'deployment-callable': 'true', }, }); }); @@ -314,13 +311,11 @@ describe('onCall', () => { expect(result.__endpoint).to.deep.equal({ platform: 'gcfv2', - httpsTrigger: {}, + callableTrigger: {}, concurrency: 20, minInstances: 3, region: ['us-west1', 'us-central1'], - labels: { - 'deployment-callable': 'true', - }, + labels: {}, }); }); diff --git a/spec/v2/providers/storage.spec.ts b/spec/v2/providers/storage.spec.ts index c8c7ffc74..8c2e25576 100644 --- a/spec/v2/providers/storage.spec.ts +++ b/spec/v2/providers/storage.spec.ts @@ -184,7 +184,7 @@ describe('v2/storage', () => { connector: 'aConnector', egressSettings: 'ALL_TRAFFIC', }, - serviceAccountEmail: 'root@aProject.iam.gserviceaccount.com', + serviceAccountEmail: 'root@', ingressSettings: 'ALLOW_ALL', labels: { hello: 'world', diff --git a/src/providers/https.ts b/src/providers/https.ts index f85a8db56..842ebd84c 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -127,9 +127,8 @@ export function _onCallWithOptions( platform: 'gcfv1', labels: {}, ...optionsToEndpoint(options), - httpsTrigger: {}, + callableTrigger: {}, }; - func.__endpoint.labels['deployment-callable'] = 'true'; func.run = handler; diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 53e5f049e..848f9c82d 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -242,9 +242,8 @@ export function onCall>( labels: { ...baseOpts?.labels, ...specificOpts?.labels, - 'deployment-callable': 'true', }, - httpsTrigger: {}, + callableTrigger: {}, }; }, }); From fafcbd5ada73cdbf0f5f49bdda265e5378ea33ce Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 15:29:09 -0700 Subject: [PATCH 16/24] No need for dynamic props. --- src/v2/providers/https.ts | 56 ++++++++++++++++++-------------------- src/v2/providers/pubsub.ts | 46 ++++++++++++++----------------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 848f9c82d..a29433584 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -47,7 +47,7 @@ export interface HttpsOptions extends Omit { export type HttpsFunction = (( req: Request, res: express.Response -) => void | Promise) & { __trigger: unknown } & { __endpoint: unknown }; +) => void | Promise) & { __trigger?: unknown , __endpoint: ManifestEndpoint }; export interface CallableFunction extends HttpsFunction { run(data: CallableRequest): Return; } @@ -133,36 +133,32 @@ export function onRequest( }, }); - Object.defineProperty(handler, '__endpoint', { - get: () => { - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); - // global options calls region a scalar and https allows it to be an array, - // but optionsToTriggerAnnotations handles both cases. - const specificOpts = options.optionsToEndpoint( - opts as options.GlobalOptions - ); - const endpoint: Partial = { - platform: 'gcfv2', - ...baseOpts, - ...specificOpts, - labels: { - ...baseOpts?.labels, - ...specificOpts?.labels, - }, - httpsTrigger: {}, - }; - convertIfPresent( - endpoint.httpsTrigger, - opts, - 'invoker', - 'invoker', - convertInvoker - ); - return endpoint; + const baseOpts = options.optionsToEndpoint( + options.getGlobalOptions() + ); + // global options calls region a scalar and https allows it to be an array, + // but optionsToTriggerAnnotations handles both cases. + const specificOpts = options.optionsToEndpoint( + opts as options.GlobalOptions + ); + const endpoint: Partial = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, }, - }); + httpsTrigger: {}, + }; + convertIfPresent( + endpoint.httpsTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker + ); + (handler as HttpsFunction).__endpoint = endpoint; return handler as HttpsFunction; } diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 1255d3f51..2efd154a2 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -139,7 +139,6 @@ export function onMessagePublished( // that __trigger/__endpoint doesn't exist. We can either cast to any and lose all // type safety or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; - func.__endpoint = 'silence the transpiler'; Object.defineProperty(func, '__trigger', { get: () => { @@ -167,32 +166,27 @@ export function onMessagePublished( }, }); - Object.defineProperty(func, '__endpoint', { - get: () => { - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); - const specificOpts = options.optionsToEndpoint(opts); - - const endpoint: ManifestEndpoint = { - platform: 'gcfv2', - ...baseOpts, - ...specificOpts, - labels: { - ...baseOpts?.labels, - ...specificOpts?.labels, - }, - eventTrigger: { - eventType: 'google.cloud.pubsub.topic.v1.messagePublished', - eventFilters: { topic }, - retry: false, - }, - }; - copyIfPresent(endpoint.eventTrigger, opts, 'retry', 'retry'); - - return endpoint; + const baseOpts = options.optionsToEndpoint( + options.getGlobalOptions() + ); + const specificOpts = options.optionsToEndpoint(opts); + + const endpoint: ManifestEndpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, }, - }); + eventTrigger: { + eventType: 'google.cloud.pubsub.topic.v1.messagePublished', + eventFilters: { topic }, + retry: false, + }, + }; + copyIfPresent(endpoint.eventTrigger, opts, 'retry', 'retry'); + func.__endpoint = endpoint; return func; } From 7864635182498e9f45f6ee0e984947f517f2b472 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 15:42:42 -0700 Subject: [PATCH 17/24] Prettier. --- src/cloud-functions.ts | 8 +++++++- src/v2/options.ts | 8 +++++++- src/v2/providers/https.ts | 27 ++++++++++++--------------- src/v2/providers/pubsub.ts | 4 +--- src/v2/providers/storage.ts | 4 +--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 0a93696c3..57457211a 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -602,7 +602,13 @@ export function optionsToEndpoint( 'timeoutSeconds' ); convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); - convertIfPresent(endpoint, options, 'serviceAccountEmail', 'serviceAccount', (sa) => sa); + convertIfPresent( + endpoint, + options, + 'serviceAccountEmail', + 'serviceAccount', + (sa) => sa + ); convertIfPresent(endpoint, options, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); diff --git a/src/v2/options.ts b/src/v2/options.ts index 32e470b0c..b4cc64aec 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -299,7 +299,13 @@ export function optionsToEndpoint( 'labels', 'timeoutSeconds' ); - convertIfPresent(endpoint, opts, "serviceAccountEmail", "serviceAccount", (sa) => sa); + convertIfPresent( + endpoint, + opts, + 'serviceAccountEmail', + 'serviceAccount', + (sa) => sa + ); convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { return { connector }; }); diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index a29433584..6d0b7c1cf 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -47,7 +47,10 @@ export interface HttpsOptions extends Omit { export type HttpsFunction = (( req: Request, res: express.Response -) => void | Promise) & { __trigger?: unknown , __endpoint: ManifestEndpoint }; +) => void | Promise) & { + __trigger?: unknown; + __endpoint: ManifestEndpoint; +}; export interface CallableFunction extends HttpsFunction { run(data: CallableRequest): Return; } @@ -133,14 +136,10 @@ export function onRequest( }, }); - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToTriggerAnnotations handles both cases. - const specificOpts = options.optionsToEndpoint( - opts as options.GlobalOptions - ); + const specificOpts = options.optionsToEndpoint(opts as options.GlobalOptions); const endpoint: Partial = { platform: 'gcfv2', ...baseOpts, @@ -152,11 +151,11 @@ export function onRequest( httpsTrigger: {}, }; convertIfPresent( - endpoint.httpsTrigger, - opts, - 'invoker', - 'invoker', - convertInvoker + endpoint.httpsTrigger, + opts, + 'invoker', + 'invoker', + convertInvoker ); (handler as HttpsFunction).__endpoint = endpoint; @@ -223,9 +222,7 @@ export function onCall>( Object.defineProperty(func, '__endpoint', { get: () => { - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); // global options calls region a scalar and https allows it to be an array, // but optionsToManifestEndpoint handles both cases. const specificOpts = options.optionsToEndpoint( diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 2efd154a2..23e9848dd 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -166,9 +166,7 @@ export function onMessagePublished( }, }); - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); const specificOpts = options.optionsToEndpoint(opts); const endpoint: ManifestEndpoint = { diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 0362df3c3..e3a3c881c 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -346,9 +346,7 @@ export function onOperation( Object.defineProperty(func, '__endpoint', { get: () => { - const baseOpts = options.optionsToEndpoint( - options.getGlobalOptions() - ); + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); const specificOpts = options.optionsToEndpoint(opts); const endpoint: ManifestEndpoint = { From 4ca1d441a25759c02e177e99278ec1b25bb501d1 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 5 Nov 2021 20:52:55 -0700 Subject: [PATCH 18/24] Better types. --- src/v2/core.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/v2/core.ts b/src/v2/core.ts index 613bddee0..924c3ce1b 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -20,6 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { ManifestEndpoint } from '../common/manifest'; + /** @internal */ export interface TriggerAnnotation { concurrency?: number; @@ -92,7 +94,7 @@ export interface CloudFunction { (raw: CloudEvent): any | Promise; __trigger: unknown; - __endpoint: unknown; + __endpoint: ManifestEndpoint; run(event: CloudEvent): any | Promise; } From 3f47fdc8cfb44e33efa0c3241a7d4559ba025d04 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 8 Nov 2021 11:54:09 -0800 Subject: [PATCH 19/24] Few more fixes. --- src/v2/core.ts | 2 +- src/v2/providers/storage.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v2/core.ts b/src/v2/core.ts index 924c3ce1b..c790be7a8 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -93,7 +93,7 @@ export interface CloudEvent { export interface CloudFunction { (raw: CloudEvent): any | Promise; - __trigger: unknown; + __trigger?: unknown; __endpoint: ManifestEndpoint; run(event: CloudEvent): any | Promise; diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index e3a3c881c..9a4549839 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -315,11 +315,11 @@ export function onOperation( func.run = handler; - // TypeScript doesn't recongize defineProperty as adding a property and complains - // that __trigger/__endpoint doesn't exist. We can either cast to any and lose all type safety + // TypeScript doesn't recognize defineProperty as adding a property and complains + // that __endpoint doesn't exist. We can either cast to any and lose all type safety // or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; - func.__endpoint = 'silence the transpiler'; + func.__endpoint = ({} as ManifestEndpoint); Object.defineProperty(func, '__trigger', { get: () => { From 726b4eb6ebef3dcbd2a875bc844d1d9a65616548 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 8 Nov 2021 11:57:29 -0800 Subject: [PATCH 20/24] One mo nit. --- src/v2/providers/pubsub.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 23e9848dd..81c4bca68 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -135,9 +135,9 @@ export function onMessagePublished( func.run = handler; - // TypeScript doesn't recongize defineProperty as adding a property and complains - // that __trigger/__endpoint doesn't exist. We can either cast to any and lose all - // type safety or we can just assign a meaningless value before calling defineProperty. + // TypeScript doesn't recognize defineProperty as adding a property and complains + // that __trigger doesn't exist. We can either cast to any and lose all type safety + // or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; Object.defineProperty(func, '__trigger', { From 5eda9cd55298425df1a7e886190100636c0449b8 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 8 Nov 2021 12:06:50 -0800 Subject: [PATCH 21/24] No getters if unncessary. --- src/v2/providers/https.ts | 32 +++++++++++++------------------- src/v2/providers/storage.ts | 2 ++ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/v2/providers/https.ts b/src/v2/providers/https.ts index 6d0b7c1cf..b89a26ed4 100644 --- a/src/v2/providers/https.ts +++ b/src/v2/providers/https.ts @@ -220,26 +220,20 @@ export function onCall>( }, }); - Object.defineProperty(func, '__endpoint', { - get: () => { - const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); - // global options calls region a scalar and https allows it to be an array, - // but optionsToManifestEndpoint handles both cases. - const specificOpts = options.optionsToEndpoint( - opts as options.GlobalOptions - ); - return { - platform: 'gcfv2', - ...baseOpts, - ...specificOpts, - labels: { - ...baseOpts?.labels, - ...specificOpts?.labels, - }, - callableTrigger: {}, - }; + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); + // global options calls region a scalar and https allows it to be an array, + // but optionsToManifestEndpoint handles both cases. + const specificOpts = options.optionsToEndpoint(opts as options.GlobalOptions); + func.__endpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, }, - }); + callableTrigger: {}, + }; func.run = handler; return func; diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 9a4549839..bdfa65979 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -344,6 +344,8 @@ export function onOperation( }, }); + // SDK may attempt to read FIREBASE_CONFIG env var to fetch the default bucket name. + // To prevent runtime errors when FIREBASE_CONFIG env var is missing, we use getters. Object.defineProperty(func, '__endpoint', { get: () => { const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); From cec015fcd3d66fdcedc30625d6e22b1f4818d166 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Mon, 8 Nov 2021 12:08:10 -0800 Subject: [PATCH 22/24] Prettier. --- src/v2/providers/storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index bdfa65979..c092d25a2 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -319,7 +319,7 @@ export function onOperation( // that __endpoint doesn't exist. We can either cast to any and lose all type safety // or we can just assign a meaningless value before calling defineProperty. func.__trigger = 'silence the transpiler'; - func.__endpoint = ({} as ManifestEndpoint); + func.__endpoint = {} as ManifestEndpoint; Object.defineProperty(func, '__trigger', { get: () => { From 11f18e8b483f941a0a29145097a304ab4e409fd5 Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 12 Nov 2021 17:44:16 -0800 Subject: [PATCH 23/24] Rely on default converter function. --- src/cloud-functions.ts | 2 +- src/v2/options.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 57457211a..fa28a29a7 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -601,7 +601,7 @@ export function optionsToEndpoint( 'labels', 'timeoutSeconds' ); - convertIfPresent(endpoint, options, 'region', 'regions', (r) => r); + convertIfPresent(endpoint, options, 'region', 'regions'); convertIfPresent( endpoint, options, diff --git a/src/v2/options.ts b/src/v2/options.ts index b4cc64aec..712078092 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -304,7 +304,6 @@ export function optionsToEndpoint( opts, 'serviceAccountEmail', 'serviceAccount', - (sa) => sa ); convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { return { connector }; From fe43588490a5bb1c97e64c9dd9897d91f8c78c4d Mon Sep 17 00:00:00 2001 From: Daniel Young Lee Date: Fri, 12 Nov 2021 18:51:00 -0800 Subject: [PATCH 24/24] Simplify logic for copying vpc options. --- src/cloud-functions.ts | 22 ++++++++++------------ src/common/encoding.ts | 4 ++-- src/common/manifest.ts | 8 ++------ src/v2/options.ts | 24 ++++++------------------ 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index fa28a29a7..5f7823c2f 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -609,18 +609,16 @@ export function optionsToEndpoint( 'serviceAccount', (sa) => sa ); - convertIfPresent(endpoint, options, 'vpc', 'vpcConnector', (connector) => { - return { connector }; - }); - convertIfPresent( - endpoint, - options, - 'vpc', - 'vpcConnectorEgressSettings', - (egressSettings, vpc) => { - return { ...vpc, egressSettings }; - } - ); + if (options.vpcConnector) { + const vpc: ManifestEndpoint['vpc'] = { connector: options.vpcConnector }; + convertIfPresent( + vpc, + options, + 'egressSettings', + 'vpcConnectorEgressSettings' + ); + endpoint.vpc = vpc; + } convertIfPresent(endpoint, options, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128, diff --git a/src/common/encoding.ts b/src/common/encoding.ts index ac5500325..0cbc2d50f 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -34,14 +34,14 @@ export function convertIfPresent( src: Src, destField: keyof Dest, srcField: keyof Src, - converter: (from: any, to?: any) => any = (from: any) => { + converter: (from: any) => any = (from: any) => { return from; } ) { if (!Object.prototype.hasOwnProperty.call(src, srcField)) { return; } - dest[destField] = converter(src[srcField], dest[destField]); + dest[destField] = converter(src[srcField]); } export function serviceAccountFromShorthand( diff --git a/src/common/manifest.ts b/src/common/manifest.ts index 9c08a84d4..02784b68c 100644 --- a/src/common/manifest.ts +++ b/src/common/manifest.ts @@ -35,7 +35,7 @@ export interface ManifestEndpoint { timeoutSeconds?: number; vpc?: { connector: string; - egressSettings: string; + egressSettings?: string; }; labels?: Record; ingressSettings?: string; @@ -48,11 +48,7 @@ export interface ManifestEndpoint { callableTrigger?: {}; eventTrigger?: { - eventFilters: { - resource?: string; - topic?: string; - bucket?: string; - }; + eventFilters: Record; eventType: string; retry: boolean; region?: string; diff --git a/src/v2/options.ts b/src/v2/options.ts index 712078092..6591554ad 100644 --- a/src/v2/options.ts +++ b/src/v2/options.ts @@ -299,24 +299,12 @@ export function optionsToEndpoint( 'labels', 'timeoutSeconds' ); - convertIfPresent( - endpoint, - opts, - 'serviceAccountEmail', - 'serviceAccount', - ); - convertIfPresent(endpoint, opts, 'vpc', 'vpcConnector', (connector) => { - return { connector }; - }); - convertIfPresent( - endpoint, - opts, - 'vpc', - 'vpcConnectorEgressSettings', - (egressSettings, vpc) => { - return { ...vpc, egressSettings }; - } - ); + convertIfPresent(endpoint, opts, 'serviceAccountEmail', 'serviceAccount'); + if (opts.vpcConnector) { + const vpc: ManifestEndpoint['vpc'] = { connector: opts.vpcConnector }; + convertIfPresent(vpc, opts, 'egressSettings', 'vpcConnectorEgressSettings'); + endpoint.vpc = vpc; + } convertIfPresent(endpoint, opts, 'availableMemoryMb', 'memory', (mem) => { const memoryLookup = { '128MB': 128,