@@ -4,6 +4,8 @@ import { logLabeledSuccess } from "../utils";
4
4
import { FirebaseError } from "../error" ;
5
5
import { Client } from "../apiv2" ;
6
6
import { secretManagerOrigin } from "../api" ;
7
+ import * as ensureApiEnabled from "../ensureApiEnabled" ;
8
+ import { needProjectId } from "../projectUtils" ;
7
9
8
10
// Matches projects/{PROJECT}/secrets/{SECRET}
9
11
const SECRET_NAME_REGEX = new RegExp (
@@ -25,11 +27,30 @@ export interface Secret {
25
27
name : string ;
26
28
// This is either projectID or number
27
29
projectId : string ;
28
- labels ?: Record < string , string > ;
30
+ labels : Record < string , string > ;
31
+ replication : Replication ;
32
+ }
33
+
34
+ export interface WireSecret {
35
+ name : string ;
36
+ labels : Record < string , string > ;
37
+ replication : Replication ;
29
38
}
30
39
31
40
type SecretVersionState = "STATE_UNSPECIFIED" | "ENABLED" | "DISABLED" | "DESTROYED" ;
32
41
42
+ export interface Replication {
43
+ automatic ?: { } ;
44
+ userManaged ?: {
45
+ replicas : Array < {
46
+ location : string ;
47
+ customerManagedEncryption ?: {
48
+ kmsKeyName : string ;
49
+ } ;
50
+ } > ;
51
+ } ;
52
+ }
53
+
33
54
export interface SecretVersion {
34
55
secret : Secret ;
35
56
versionId : string ;
@@ -40,7 +61,7 @@ export interface SecretVersion {
40
61
41
62
interface CreateSecretRequest {
42
63
name : string ;
43
- replication : { automatic : { } } ;
64
+ replication : Replication ;
44
65
labels : Record < string , string > ;
45
66
}
46
67
@@ -68,17 +89,18 @@ const client = new Client({ urlPrefix: secretManagerOrigin(), apiVersion: API_VE
68
89
* Returns secret resource of given name in the project.
69
90
*/
70
91
export async function getSecret ( projectId : string , name : string ) : Promise < Secret > {
71
- const getRes = await client . get < Secret > ( `projects/${ projectId } /secrets/${ name } ` ) ;
92
+ const getRes = await client . get < WireSecret > ( `projects/${ projectId } /secrets/${ name } ` ) ;
72
93
const secret = parseSecretResourceName ( getRes . body . name ) ;
73
94
secret . labels = getRes . body . labels ?? { } ;
95
+ secret . replication = getRes . body . replication ?? { } ;
74
96
return secret ;
75
97
}
76
98
77
99
/**
78
100
* Lists all secret resources associated with a project.
79
101
*/
80
102
export async function listSecrets ( projectId : string , filter ?: string ) : Promise < Secret [ ] > {
81
- type Response = { secrets : Secret [ ] ; nextPageToken ?: string } ;
103
+ type Response = { secrets : WireSecret [ ] ; nextPageToken ?: string } ;
82
104
const secrets : Secret [ ] = [ ] ;
83
105
const path = `projects/${ projectId } /secrets` ;
84
106
const baseOpts = filter ? { queryParams : { filter } } : { } ;
@@ -95,6 +117,7 @@ export async function listSecrets(projectId: string, filter?: string): Promise<S
95
117
secrets . push ( {
96
118
...parseSecretResourceName ( s . name ) ,
97
119
labels : s . labels ?? { } ,
120
+ replication : s . replication ?? { } ,
98
121
} ) ;
99
122
}
100
123
@@ -238,6 +261,8 @@ export function parseSecretResourceName(resourceName: string): Secret {
238
261
return {
239
262
projectId : match . groups . project ,
240
263
name : match . groups . secret ,
264
+ labels : { } ,
265
+ replication : { } ,
241
266
} ;
242
267
}
243
268
@@ -253,6 +278,8 @@ export function parseSecretVersionResourceName(resourceName: string): SecretVers
253
278
secret : {
254
279
projectId : match . groups . project ,
255
280
name : match . groups . secret ,
281
+ labels : { } ,
282
+ replication : { } ,
256
283
} ,
257
284
versionId : match . groups . version ,
258
285
} ;
@@ -272,21 +299,36 @@ export async function createSecret(
272
299
projectId : string ,
273
300
name : string ,
274
301
labels : Record < string , string > ,
302
+ location ?: string ,
275
303
) : Promise < Secret > {
304
+ let replication : CreateSecretRequest [ "replication" ] ;
305
+ if ( location ) {
306
+ replication = {
307
+ userManaged : {
308
+ replicas : [
309
+ {
310
+ location,
311
+ } ,
312
+ ] ,
313
+ } ,
314
+ } ;
315
+ } else {
316
+ replication = { automatic : { } } ;
317
+ }
318
+
276
319
const createRes = await client . post < CreateSecretRequest , Secret > (
277
320
`projects/${ projectId } /secrets` ,
278
321
{
279
322
name,
280
- replication : {
281
- automatic : { } ,
282
- } ,
323
+ replication,
283
324
labels,
284
325
} ,
285
326
{ queryParams : { secretId : name } } ,
286
327
) ;
287
328
return {
288
329
...parseSecretResourceName ( createRes . body . name ) ,
289
330
labels,
331
+ replication,
290
332
} ;
291
333
}
292
334
@@ -299,12 +341,16 @@ export async function patchSecret(
299
341
labels : Record < string , string > ,
300
342
) : Promise < Secret > {
301
343
const fullName = `projects/${ projectId } /secrets/${ name } ` ;
302
- const res = await client . patch < Omit < Secret , "projectId " > , Secret > (
344
+ const res = await client . patch < Omit < WireSecret , "replication " > , WireSecret > (
303
345
fullName ,
304
346
{ name : fullName , labels } ,
305
347
{ queryParams : { updateMask : "labels" } } , // Only allow patching labels for now.
306
348
) ;
307
- return parseSecretResourceName ( res . body . name ) ;
349
+ return {
350
+ ...parseSecretResourceName ( res . body . name ) ,
351
+ labels : res . body . labels ,
352
+ replication : res . body . replication ,
353
+ } ;
308
354
}
309
355
310
356
/**
@@ -340,7 +386,9 @@ export async function addVersion(
340
386
/**
341
387
* Returns IAM policy of a secret resource.
342
388
*/
343
- export async function getIamPolicy ( secret : Secret ) : Promise < iam . Policy > {
389
+ export async function getIamPolicy (
390
+ secret : Pick < Secret , "projectId" | "name" > ,
391
+ ) : Promise < iam . Policy > {
344
392
const res = await client . get < iam . Policy > (
345
393
`projects/${ secret . projectId } /secrets/${ secret . name } :getIamPolicy` ,
346
394
) ;
@@ -350,7 +398,10 @@ export async function getIamPolicy(secret: Secret): Promise<iam.Policy> {
350
398
/**
351
399
* Sets IAM policy on a secret resource.
352
400
*/
353
- export async function setIamPolicy ( secret : Secret , bindings : iam . Binding [ ] ) : Promise < void > {
401
+ export async function setIamPolicy (
402
+ secret : Pick < Secret , "projectId" | "name" > ,
403
+ bindings : iam . Binding [ ] ,
404
+ ) : Promise < void > {
354
405
await client . post < { policy : Partial < iam . Policy > ; updateMask : string } , iam . Policy > (
355
406
`projects/${ secret . projectId } /secrets/${ secret . name } :setIamPolicy` ,
356
407
{
@@ -366,7 +417,7 @@ export async function setIamPolicy(secret: Secret, bindings: iam.Binding[]): Pro
366
417
* Ensure that given service agents have the given IAM role on the secret resource.
367
418
*/
368
419
export async function ensureServiceAgentRole (
369
- secret : Secret ,
420
+ secret : Pick < Secret , "projectId" | "name" > ,
370
421
serviceAccountEmails : string [ ] ,
371
422
role : string ,
372
423
) : Promise < void > {
@@ -399,3 +450,40 @@ export async function ensureServiceAgentRole(
399
450
} to ${ serviceAccountEmails . join ( ", " ) } `,
400
451
) ;
401
452
}
453
+
454
+ export const FIREBASE_MANAGED = "firebase-managed" ;
455
+
456
+ /**
457
+ * Returns true if secret is managed by Cloud Functions for Firebase.
458
+ * This used to be firebase-managed: true, but was later changed to firebase-managed: functions to
459
+ * improve readability.
460
+ */
461
+ export function isFunctionsManaged ( secret : Secret ) : boolean {
462
+ return (
463
+ secret . labels [ FIREBASE_MANAGED ] === "true" || secret . labels [ FIREBASE_MANAGED ] === "functions"
464
+ ) ;
465
+ }
466
+
467
+ /**
468
+ * Returns true if secret is managed by Firebase App Hosting.
469
+ */
470
+ export function isAppHostingManaged ( secret : Secret ) : boolean {
471
+ return secret . labels [ FIREBASE_MANAGED ] === "apphosting" ;
472
+ }
473
+
474
+ /**
475
+ * Utility used in the "before" command annotation to enable the API.
476
+ */
477
+
478
+ export function ensureApi ( options : any ) : Promise < void > {
479
+ const projectId = needProjectId ( options ) ;
480
+ return ensureApiEnabled . ensure ( projectId , secretManagerOrigin ( ) , "secretmanager" , true ) ;
481
+ }
482
+ /**
483
+ * Return labels to mark secret as managed by Firebase.
484
+ * @internal
485
+ */
486
+
487
+ export function labels ( product : "functions" | "apphosting" = "functions" ) : Record < string , string > {
488
+ return { [ FIREBASE_MANAGED ] : product } ;
489
+ }
0 commit comments