@@ -18,6 +18,7 @@ import {
18
18
CODEBASE_LABEL ,
19
19
HASH_LABEL ,
20
20
} from "../functions/constants" ;
21
+ import { RequireKeys } from "../metaprogramming" ;
21
22
22
23
export const API_VERSION = "v2" ;
23
24
@@ -31,17 +32,6 @@ export type VpcConnectorEgressSettings = "PRIVATE_RANGES_ONLY" | "ALL_TRAFFIC";
31
32
export type IngressSettings = "ALLOW_ALL" | "ALLOW_INTERNAL_ONLY" | "ALLOW_INTERNAL_AND_GCLB" ;
32
33
export type FunctionState = "ACTIVE" | "FAILED" | "DEPLOYING" | "DELETING" | "UNKONWN" ;
33
34
34
- // The GCFv2 funtion type has many inner types which themselves have output-only fields:
35
- // eventTrigger.trigger
36
- // buildConfig.config
37
- // buildConfig.workerPool
38
- // serviceConfig.service
39
- // serviceConfig.uri
40
- //
41
- // Because Omit<> doesn't work with nested property addresses, we're making those fields optional.
42
- // An alternative would be to name the types OutputCloudFunction/CloudFunction or CloudFunction/InputCloudFunction.
43
- export type OutputOnlyFields = "state" | "updateTime" ;
44
-
45
35
// Values allowed for the operator field in EventFilter
46
36
export type EventFilterOperator = "match-path-pattern" ;
47
37
@@ -153,17 +143,26 @@ export interface EventTrigger {
153
143
channel ?: string ;
154
144
}
155
145
156
- export interface CloudFunction {
146
+ interface CloudFunctionBase {
157
147
name : string ;
158
148
description ?: string ;
159
149
buildConfig : BuildConfig ;
160
- serviceConfig : ServiceConfig ;
150
+ serviceConfig ? : ServiceConfig ;
161
151
eventTrigger ?: EventTrigger ;
162
- state : FunctionState ;
163
- updateTime : Date ;
164
152
labels ?: Record < string , string > | null ;
165
153
}
166
154
155
+ export type OutputCloudFunction = CloudFunctionBase & {
156
+ state : FunctionState ;
157
+ updateTime : Date ;
158
+ serviceConfig ?: RequireKeys < ServiceConfig , "service" | "uri" > ;
159
+ } ;
160
+
161
+ export type InputCloudFunction = CloudFunctionBase & {
162
+ // serviceConfig is required.
163
+ serviceConfig : ServiceConfig ;
164
+ } ;
165
+
167
166
export interface OperationMetadata {
168
167
createTime : string ;
169
168
endTime : string ;
@@ -181,13 +180,13 @@ export interface Operation {
181
180
metadata ?: OperationMetadata ;
182
181
done : boolean ;
183
182
error ?: { code : number ; message : string ; details : unknown } ;
184
- response ?: CloudFunction ;
183
+ response ?: OutputCloudFunction ;
185
184
}
186
185
187
186
// Private API interface for ListFunctionsResponse. listFunctions returns
188
187
// a CloudFunction[]
189
188
interface ListFunctionsResponse {
190
- functions : CloudFunction [ ] ;
189
+ functions : OutputCloudFunction [ ] ;
191
190
unreachable : string [ ] ;
192
191
}
193
192
@@ -292,9 +291,7 @@ export async function generateUploadUrl(
292
291
/**
293
292
* Creates a new Cloud Function.
294
293
*/
295
- export async function createFunction (
296
- cloudFunction : Omit < CloudFunction , OutputOnlyFields >
297
- ) : Promise < Operation > {
294
+ export async function createFunction ( cloudFunction : InputCloudFunction ) : Promise < Operation > {
298
295
// the API is a POST to the collection that owns the function name.
299
296
const components = cloudFunction . name . split ( "/" ) ;
300
297
const functionId = components . splice ( - 1 , 1 ) [ 0 ] ;
@@ -325,17 +322,20 @@ export async function getFunction(
325
322
projectId : string ,
326
323
location : string ,
327
324
functionId : string
328
- ) : Promise < CloudFunction > {
325
+ ) : Promise < OutputCloudFunction > {
329
326
const name = `projects/${ projectId } /locations/${ location } /functions/${ functionId } ` ;
330
- const res = await client . get < CloudFunction > ( name ) ;
327
+ const res = await client . get < OutputCloudFunction > ( name ) ;
331
328
return res . body ;
332
329
}
333
330
334
331
/**
335
332
* List all functions in a region.
336
333
* Customers should generally use backend.existingBackend.
337
334
*/
338
- export async function listFunctions ( projectId : string , region : string ) : Promise < CloudFunction [ ] > {
335
+ export async function listFunctions (
336
+ projectId : string ,
337
+ region : string
338
+ ) : Promise < OutputCloudFunction [ ] > {
339
339
const res = await listFunctionsInternal ( projectId , region ) ;
340
340
if ( res . unreachable . includes ( region ) ) {
341
341
throw new FirebaseError ( `Cloud Functions region ${ region } is unavailable` ) ;
@@ -356,7 +356,7 @@ async function listFunctionsInternal(
356
356
region : string
357
357
) : Promise < ListFunctionsResponse > {
358
358
type Response = ListFunctionsResponse & { nextPageToken ?: string } ;
359
- const functions : CloudFunction [ ] = [ ] ;
359
+ const functions : OutputCloudFunction [ ] = [ ] ;
360
360
const unreacahble = new Set < string > ( ) ;
361
361
let pageToken = "" ;
362
362
while ( true ) {
@@ -386,9 +386,7 @@ async function listFunctionsInternal(
386
386
* Updates a Cloud Function.
387
387
* Customers can force a field to be deleted by setting that field to `undefined`
388
388
*/
389
- export async function updateFunction (
390
- cloudFunction : Omit < CloudFunction , OutputOnlyFields >
391
- ) : Promise < Operation > {
389
+ export async function updateFunction ( cloudFunction : InputCloudFunction ) : Promise < Operation > {
392
390
// Keys in labels and environmentVariables and secretEnvironmentVariables are user defined, so we don't recurse
393
391
// for field masks.
394
392
const fieldMasks = proto . fieldMasks (
@@ -439,7 +437,7 @@ export async function deleteFunction(cloudFunction: string): Promise<Operation>
439
437
export function functionFromEndpoint (
440
438
endpoint : backend . Endpoint ,
441
439
source : StorageSource
442
- ) : Omit < CloudFunction , OutputOnlyFields > {
440
+ ) : InputCloudFunction {
443
441
if ( endpoint . platform !== "gcfv2" ) {
444
442
throw new FirebaseError (
445
443
"Trying to create a v2 CloudFunction with v1 API. This should never happen"
@@ -453,7 +451,7 @@ export function functionFromEndpoint(
453
451
) ;
454
452
}
455
453
456
- const gcfFunction : Omit < CloudFunction , OutputOnlyFields > = {
454
+ const gcfFunction : InputCloudFunction = {
457
455
name : backend . functionName ( endpoint ) ,
458
456
buildConfig : {
459
457
runtime : endpoint . runtime ,
@@ -601,7 +599,7 @@ export function functionFromEndpoint(
601
599
/**
602
600
* Generate a versionless Endpoint object from a v2 Cloud Function API object.
603
601
*/
604
- export function endpointFromFunction ( gcfFunction : CloudFunction ) : backend . Endpoint {
602
+ export function endpointFromFunction ( gcfFunction : OutputCloudFunction ) : backend . Endpoint {
605
603
const [ , project , , region , , id ] = gcfFunction . name . split ( "/" ) ;
606
604
let trigger : backend . Triggered ;
607
605
if ( gcfFunction . labels ?. [ "deployment-scheduled" ] === "true" ) {
@@ -671,63 +669,78 @@ export function endpointFromFunction(gcfFunction: CloudFunction): backend.Endpoi
671
669
...trigger ,
672
670
entryPoint : gcfFunction . buildConfig . entryPoint ,
673
671
runtime : gcfFunction . buildConfig . runtime ,
674
- uri : gcfFunction . serviceConfig . uri ,
675
672
} ;
676
- proto . copyIfPresent (
677
- endpoint ,
678
- gcfFunction . serviceConfig ,
679
- "ingressSettings" ,
680
- "environmentVariables" ,
681
- "secretEnvironmentVariables" ,
682
- "timeoutSeconds"
683
- ) ;
684
- proto . renameIfPresent (
685
- endpoint ,
686
- gcfFunction . serviceConfig ,
687
- "serviceAccount" ,
688
- "serviceAccountEmail"
689
- ) ;
690
- proto . convertIfPresent (
691
- endpoint ,
692
- gcfFunction . serviceConfig ,
693
- "availableMemoryMb" ,
694
- "availableMemory" ,
695
- ( prod ) => {
696
- if ( prod === null ) {
697
- logger . debug ( "Prod should always return a valid memory amount" ) ;
698
- return prod as never ;
673
+ if ( gcfFunction . serviceConfig ) {
674
+ proto . copyIfPresent (
675
+ endpoint ,
676
+ gcfFunction . serviceConfig ,
677
+ "ingressSettings" ,
678
+ "environmentVariables" ,
679
+ "secretEnvironmentVariables" ,
680
+ "timeoutSeconds" ,
681
+ "uri"
682
+ ) ;
683
+ proto . renameIfPresent (
684
+ endpoint ,
685
+ gcfFunction . serviceConfig ,
686
+ "serviceAccount" ,
687
+ "serviceAccountEmail"
688
+ ) ;
689
+ proto . convertIfPresent (
690
+ endpoint ,
691
+ gcfFunction . serviceConfig ,
692
+ "availableMemoryMb" ,
693
+ "availableMemory" ,
694
+ ( prod ) => {
695
+ if ( prod === null ) {
696
+ logger . debug ( "Prod should always return a valid memory amount" ) ;
697
+ return prod as never ;
698
+ }
699
+ const mem = mebibytes ( prod ) ;
700
+ if ( ! backend . isValidMemoryOption ( mem ) ) {
701
+ logger . warn ( "Converting a function to an endpoint with an invalid memory option" , mem ) ;
702
+ }
703
+ return mem as backend . MemoryOptions ;
699
704
}
700
- const mem = mebibytes ( prod ) ;
701
- if ( ! backend . isValidMemoryOption ( mem ) ) {
702
- logger . warn ( "Converting a function to an endpoint with an invalid memory option" , mem ) ;
705
+ ) ;
706
+ proto . convertIfPresent ( endpoint , gcfFunction . serviceConfig , "cpu" , "availableCpu" , ( cpu ) => {
707
+ let cpuVal : number | null = Number ( cpu ) ;
708
+ if ( Number . isNaN ( cpuVal ) ) {
709
+ cpuVal = null ;
703
710
}
704
- return mem as backend . MemoryOptions ;
705
- }
706
- ) ;
707
- proto . renameIfPresent ( endpoint , gcfFunction . serviceConfig , "minInstances" , "minInstanceCount" ) ;
708
- proto . renameIfPresent ( endpoint , gcfFunction . serviceConfig , "maxInstances" , "maxInstanceCount" ) ;
709
- proto . copyIfPresent ( endpoint , gcfFunction , "labels" ) ;
710
- if ( gcfFunction . serviceConfig . vpcConnector ) {
711
- endpoint . vpc = { connector : gcfFunction . serviceConfig . vpcConnector } ;
711
+ return cpuVal ;
712
+ } ) ;
713
+ proto . renameIfPresent ( endpoint , gcfFunction . serviceConfig , "minInstances" , "minInstanceCount" ) ;
714
+ proto . renameIfPresent ( endpoint , gcfFunction . serviceConfig , "maxInstances" , "maxInstanceCount" ) ;
712
715
proto . renameIfPresent (
713
- endpoint . vpc ,
716
+ endpoint ,
714
717
gcfFunction . serviceConfig ,
715
- "egressSettings " ,
716
- "vpcConnectorEgressSettings "
718
+ "concurrency " ,
719
+ "maxInstanceRequestConcurrency "
717
720
) ;
721
+ proto . copyIfPresent ( endpoint , gcfFunction , "labels" ) ;
722
+ if ( gcfFunction . serviceConfig . vpcConnector ) {
723
+ endpoint . vpc = { connector : gcfFunction . serviceConfig . vpcConnector } ;
724
+ proto . renameIfPresent (
725
+ endpoint . vpc ,
726
+ gcfFunction . serviceConfig ,
727
+ "egressSettings" ,
728
+ "vpcConnectorEgressSettings"
729
+ ) ;
730
+ }
731
+ const serviceName = gcfFunction . serviceConfig . service ;
732
+ if ( ! serviceName ) {
733
+ logger . debug (
734
+ "Got a v2 function without a service name." +
735
+ "Maybe we've migrated to using the v2 API everywhere and missed this code"
736
+ ) ;
737
+ } else {
738
+ endpoint . runServiceId = utils . last ( serviceName . split ( "/" ) ) ;
739
+ }
718
740
}
719
741
endpoint . codebase = gcfFunction . labels ?. [ CODEBASE_LABEL ] || projectConfig . DEFAULT_CODEBASE ;
720
742
if ( gcfFunction . labels ?. [ HASH_LABEL ] ) {
721
743
endpoint . hash = gcfFunction . labels [ HASH_LABEL ] ;
722
744
}
723
- const serviceName = gcfFunction . serviceConfig . service ;
724
- if ( ! serviceName ) {
725
- logger . debug (
726
- "Got a v2 function without a service name." +
727
- "Maybe we've migrated to using the v2 API everywhere and missed this code"
728
- ) ;
729
- } else {
730
- endpoint . runServiceId = utils . last ( serviceName . split ( "/" ) ) ;
731
- }
732
745
return endpoint ;
733
746
}
0 commit comments