From cacf19c2aa8d30dde906842c1c531a0ad508d1cd Mon Sep 17 00:00:00 2001 From: Egor Miasnikov Date: Fri, 28 Aug 2020 09:26:24 +0300 Subject: [PATCH 1/6] Add support for service account in `functions.runWith` --- spec/function-builder.spec.ts | 13 +++++++++++++ src/cloud-functions.ts | 5 +++++ src/function-builder.ts | 1 + src/function-configuration.ts | 5 +++++ 4 files changed, 24 insertions(+) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index eec531fdf..e588c36a5 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -238,4 +238,17 @@ describe('FunctionBuilder', () => { )}` ); }); + + it('should allow a serviceAccountEmail to be set', () => { + const serviceAccountEmail = + 'test-service-account@test.iam.gserviceaccount.com'; + const fn = functions + .runWith({ + serviceAccountEmail, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccountEmail); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 109a1ad05..b2054b50b 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -272,6 +272,7 @@ export interface TriggerAnnotated { timeout?: string; vpcConnector?: string; vpcConnectorEgressSettings?: string; + serviceAccountEmail?: string; }; } @@ -525,5 +526,9 @@ export function optionsToTrigger(options: DeploymentOptions) { trigger.vpcConnectorEgressSettings = options.vpcConnectorEgressSettings; } + if (options.serviceAccountEmail) { + trigger.serviceAccountEmail = options.serviceAccountEmail; + } + return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 919f9f09b..765ce0fa6 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -142,6 +142,7 @@ export function region( * 4. vpcConnector: id of a VPC connector in same project and region * 5. vpcConnectorEgressSettings: when a vpcConnector is set, control which * egress traffic is sent through the vpcConnector. + * 6. serviceAccountEmail: Specific service account for the function * * Value must not be null. */ diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 558895148..b2371565d 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -110,6 +110,11 @@ export interface RuntimeOptions { * Egress settings for VPC connector */ vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; + + /** + * Specific service account + */ + serviceAccountEmail?: string; } export interface DeploymentOptions extends RuntimeOptions { From 7e0284b1c138dc4b66b644749af6db3003740b8e Mon Sep 17 00:00:00 2001 From: Egor Miasnikov Date: Thu, 3 Sep 2020 12:14:46 +0300 Subject: [PATCH 2/6] Add email validation and email generation by project id --- spec/function-builder.spec.ts | 17 ++++++++++++++++- src/cloud-functions.ts | 9 ++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index e588c36a5..0b160e092 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -239,7 +239,7 @@ describe('FunctionBuilder', () => { ); }); - it('should allow a serviceAccountEmail to be set', () => { + it('should allow a serviceAccountEmail to be set as-is', () => { const serviceAccountEmail = 'test-service-account@test.iam.gserviceaccount.com'; const fn = functions @@ -251,4 +251,19 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccountEmail); }); + + it('should allow a serviceAccountEmail to be set with generated service account email', () => { + const serviceAccountEmail = 'test-service-account'; + const projectId = process.env.GCLOUD_PROJECT; + const fn = functions + .runWith({ + serviceAccountEmail, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.equal( + `${serviceAccountEmail}@${projectId}.iam.gserviceaccount.com` + ); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index b2054b50b..e3cd191a9 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -527,7 +527,14 @@ export function optionsToTrigger(options: DeploymentOptions) { } if (options.serviceAccountEmail) { - trigger.serviceAccountEmail = options.serviceAccountEmail; + if (options.serviceAccountEmail.includes('@')) { + trigger.serviceAccountEmail = options.serviceAccountEmail; + } else { + if (!process.env.GCLOUD_PROJECT) { + throw new Error('process.env.GCLOUD_PROJECT is not set.'); + } + trigger.serviceAccountEmail = `${options.serviceAccountEmail}@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + } } return trigger; From dd660a724a354a394e89fd56f539cf77d92af201 Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Wed, 2 Dec 2020 13:22:01 -0800 Subject: [PATCH 3/6] Changing to serviceAccount, and adding default as an option --- spec/function-builder.spec.ts | 17 ++++++++--------- src/cloud-functions.ts | 14 +++++++++----- src/function-builder.ts | 6 ++++-- src/function-configuration.ts | 8 ++++---- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 0b160e092..6ea619348 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -239,31 +239,30 @@ describe('FunctionBuilder', () => { ); }); - it('should allow a serviceAccountEmail to be set as-is', () => { - const serviceAccountEmail = - 'test-service-account@test.iam.gserviceaccount.com'; + it('should allow a serviceAccount to be set as-is', () => { + const serviceAccount = 'test-service-account@test.iam.gserviceaccount.com'; const fn = functions .runWith({ - serviceAccountEmail, + serviceAccount, }) .auth.user() .onCreate((user) => user); - expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccountEmail); + expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccount); }); - it('should allow a serviceAccountEmail to be set with generated service account email', () => { - const serviceAccountEmail = 'test-service-account'; + it('should allow a serviceAccount to be set with generated service account email', () => { + const serviceAccount = 'test-service-account'; const projectId = process.env.GCLOUD_PROJECT; const fn = functions .runWith({ - serviceAccountEmail, + serviceAccount, }) .auth.user() .onCreate((user) => user); expect(fn.__trigger.serviceAccountEmail).to.equal( - `${serviceAccountEmail}@${projectId}.iam.gserviceaccount.com` + `${serviceAccount}@${projectId}.iam.gserviceaccount.com` ); }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index e3cd191a9..d8cb1f669 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -526,14 +526,18 @@ export function optionsToTrigger(options: DeploymentOptions) { trigger.vpcConnectorEgressSettings = options.vpcConnectorEgressSettings; } - if (options.serviceAccountEmail) { - if (options.serviceAccountEmail.includes('@')) { - trigger.serviceAccountEmail = options.serviceAccountEmail; + if (options.serviceAccount) { + if (options.serviceAccount === 'default') { + // Do nothing, since this is equivalent to not setting serviceAccount. + } else if (options.serviceAccount.includes('@')) { + trigger.serviceAccountEmail = options.serviceAccount; } else { if (!process.env.GCLOUD_PROJECT) { - throw new Error('process.env.GCLOUD_PROJECT is not set.'); + throw new Error( + `Unable to determine email for service account '${options.serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` + ); } - trigger.serviceAccountEmail = `${options.serviceAccountEmail}@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + trigger.serviceAccountEmail = `${options.serviceAccount}@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; } } diff --git a/src/function-builder.ts b/src/function-builder.ts index 0cee53833..d4d2a08aa 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -139,10 +139,12 @@ export function region( * 0 to 540. * 3. `failurePolicy`: failure policy of the function, with boolean `true` being * equivalent to providing an empty retry object. - * 4. `vpcConnector`: id of a VPC connector in same project and region + * 4. `vpcConnector`: id of a VPC connector in same project and region. * 5. `vpcConnectorEgressSettings`: when a vpcConnector is set, control which * egress traffic is sent through the vpcConnector. - * 6. `serviceAccountEmail`: Specific service account for the function + * 6. `serviceAccount`: Specific service account for the function. + * 7. `ingressSettings`: ingress settings for the function, which control where a HTTPS + * function can be called from. * * Value must not be null. */ diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 0c7e3fe61..f5a325b41 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -123,11 +123,11 @@ export interface RuntimeOptions { vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; /** - * Specific service account + * Specific service account for the function to run as */ - serviceAccountEmail?: string; - - /** + serviceAccount?: 'default' | string; + + /** * Ingress settings */ ingressSettings?: typeof INGRESS_SETTINGS_OPTIONS[number]; From f1dea445d58726199b29333e3123800eee7586cf Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Wed, 2 Dec 2020 13:42:42 -0800 Subject: [PATCH 4/6] adds a test case for default --- spec/function-builder.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 6ea619348..47b91d647 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -265,4 +265,16 @@ describe('FunctionBuilder', () => { `${serviceAccount}@${projectId}.iam.gserviceaccount.com` ); }); + + it('should not set a serviceAccountEmail if service account is set to `default`', () => { + const serviceAccount = 'default'; + const fn = functions + .runWith({ + serviceAccount, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.be.undefined; + }); }); From 4c008641404869b57beabbfbf7d38a95d56378fc Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Fri, 4 Dec 2020 11:52:08 -0800 Subject: [PATCH 5/6] refactoring to checxk for @ at the end of service account, and throw erros earlier when service account is set to something invalid --- spec/function-builder.spec.ts | 11 ++++++++++- src/cloud-functions.ts | 10 +++++++--- src/function-builder.ts | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 47b91d647..acc9f54fe 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -252,7 +252,7 @@ describe('FunctionBuilder', () => { }); it('should allow a serviceAccount to be set with generated service account email', () => { - const serviceAccount = 'test-service-account'; + const serviceAccount = 'test-service-account@'; const projectId = process.env.GCLOUD_PROJECT; const fn = functions .runWith({ @@ -277,4 +277,13 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.serviceAccountEmail).to.be.undefined; }); + + it('should throw an error if serviceAccount is set to an invalid value', () => { + const serviceAccount = 'test-service-account'; + expect(() => { + functions.runWith({ + serviceAccount, + }); + }).to.throw(); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index d8cb1f669..4a1c557eb 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -529,15 +529,19 @@ export function optionsToTrigger(options: DeploymentOptions) { if (options.serviceAccount) { if (options.serviceAccount === 'default') { // Do nothing, since this is equivalent to not setting serviceAccount. - } else if (options.serviceAccount.includes('@')) { - trigger.serviceAccountEmail = options.serviceAccount; - } else { + } else if (options.serviceAccount.endsWith('@')) { if (!process.env.GCLOUD_PROJECT) { throw new Error( `Unable to determine email for service account '${options.serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` ); } trigger.serviceAccountEmail = `${options.serviceAccount}@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + } else if (options.serviceAccount.includes('@')) { + trigger.serviceAccountEmail = options.serviceAccount; + } else { + throw new Error( + `Invalid option for serviceAccount: '${options.serviceAccount}'. Valid options are 'default', a service account email, or '{serviceAccountName}@'` + ); } } diff --git a/src/function-builder.ts b/src/function-builder.ts index d4d2a08aa..7411f72e2 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -99,6 +99,16 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { } } } + + if ( + runtimeOptions.serviceAccount && + runtimeOptions.serviceAccount !== 'default' && + !_.includes(runtimeOptions.serviceAccount, '@') + ) { + throw new Error( + `serviceAccount must be set to 'default', a service account email, or '{serviceAccountName}@'` + ); + } return true; } From 30fd2097042ef01e830aac0a54c41b0f23409dcf Mon Sep 17 00:00:00 2001 From: Joe Hanley Date: Fri, 4 Dec 2020 12:04:00 -0800 Subject: [PATCH 6/6] gets rid of repeated @ for generated service account emails --- spec/function-builder.spec.ts | 2 +- src/cloud-functions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index acc9f54fe..c31a82d4a 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -262,7 +262,7 @@ describe('FunctionBuilder', () => { .onCreate((user) => user); expect(fn.__trigger.serviceAccountEmail).to.equal( - `${serviceAccount}@${projectId}.iam.gserviceaccount.com` + `test-service-account@${projectId}.iam.gserviceaccount.com` ); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 4a1c557eb..50d674022 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -535,7 +535,7 @@ export function optionsToTrigger(options: DeploymentOptions) { `Unable to determine email for service account '${options.serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` ); } - trigger.serviceAccountEmail = `${options.serviceAccount}@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + trigger.serviceAccountEmail = `${options.serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; } else if (options.serviceAccount.includes('@')) { trigger.serviceAccountEmail = options.serviceAccount; } else {