diff --git a/package/lib/compileFunctions.js b/package/lib/compileFunctions.js index caec35b..304443e 100644 --- a/package/lib/compileFunctions.js +++ b/package/lib/compileFunctions.js @@ -49,6 +49,8 @@ module.exports = { _.get(funcObject, 'timeout') || _.get(this, 'serverless.service.provider.timeout') || '60s'; funcTemplate.properties.environmentVariables = this.provider.getConfiguredEnvironment(funcObject); + funcTemplate.properties.secretEnvironmentVariables = + this.provider.getConfiguredSecrets(funcObject); if (!funcTemplate.properties.serviceAccountEmail) { delete funcTemplate.properties.serviceAccountEmail; @@ -80,6 +82,9 @@ module.exports = { if (!_.size(funcTemplate.properties.environmentVariables)) { delete funcTemplate.properties.environmentVariables; } + if (!_.size(funcTemplate.properties.secretEnvironmentVariables)) { + delete funcTemplate.properties.secretEnvironmentVariables; + } funcTemplate.properties.labels = _.assign( {}, diff --git a/package/lib/compileFunctions.test.js b/package/lib/compileFunctions.test.js index c7ce612..6b8819c 100644 --- a/package/lib/compileFunctions.test.js +++ b/package/lib/compileFunctions.test.js @@ -507,6 +507,108 @@ describe('CompileFunctions', () => { }); }); + it('should set the secret environment variables based on the function configuration', () => { + googlePackage.serverless.service.functions = { + func1: { + handler: 'func1', + secrets: { + TEST_SECRET: { + secret: 'secret', + version: 'latest', + }, + }, + events: [{ http: 'foo' }], + }, + }; + + const compiledResources = [ + { + type: 'gcp-types/cloudfunctions-v1:projects.locations.functions', + name: 'my-service-dev-func1', + properties: { + parent: 'projects/myProject/locations/us-central1', + runtime: 'nodejs10', + function: 'my-service-dev-func1', + entryPoint: 'func1', + availableMemoryMb: 256, + secretEnvironmentVariables: [ + { + key: 'TEST_SECRET', + secret: 'secret', + version: 'latest', + }, + ], + timeout: '60s', + sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip', + httpsTrigger: { + url: 'foo', + }, + labels: {}, + }, + }, + ]; + + return googlePackage.compileFunctions().then(() => { + expect(consoleLogStub.calledOnce).toEqual(true); + expect( + googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources + ).toEqual(compiledResources); + }); + }); + + it('should merge the secret environment variables on the provider configuration and function definition', () => { + googlePackage.serverless.service.functions = { + func1: { + handler: 'func1', + secrets: { + TEST_SECRET: { secret: 'secret1', version: 'latest' }, + TEST_SECRET2: { secret: 'secret2', version: 'latest' }, + }, + events: [{ http: 'foo' }], + }, + }; + googlePackage.serverless.service.provider.secrets = { + TEST_SECRET: { secret: 'secretbase', version: 'latest' }, + TEST_SECRET_PROVIDER: { secret: 'secretprovider', version: 'latest' }, + }; + + const compiledResources = [ + { + type: 'gcp-types/cloudfunctions-v1:projects.locations.functions', + name: 'my-service-dev-func1', + properties: { + parent: 'projects/myProject/locations/us-central1', + runtime: 'nodejs10', + function: 'my-service-dev-func1', + entryPoint: 'func1', + availableMemoryMb: 256, + secretEnvironmentVariables: [ + { key: 'TEST_SECRET', secret: 'secret1', version: 'latest' }, + { key: 'TEST_SECRET2', secret: 'secret2', version: 'latest' }, + { key: 'TEST_SECRET_PROVIDER', secret: 'secretprovider', version: 'latest' }, + ], + timeout: '60s', + sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip', + httpsTrigger: { + url: 'foo', + }, + labels: {}, + }, + }, + ]; + + return googlePackage.compileFunctions().then(() => { + expect(consoleLogStub.calledOnce).toEqual(true); + expect( + googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources + ).toEqual(compiledResources); + expect(googlePackage.serverless.service.provider.secrets).toEqual({ + TEST_SECRET: { secret: 'secretbase', version: 'latest' }, + TEST_SECRET_PROVIDER: { secret: 'secretprovider', version: 'latest' }, + }); + }); + }); + it('should compile "http" events properly', () => { googlePackage.serverless.service.functions = { func1: { diff --git a/provider/googleProvider.js b/provider/googleProvider.js index 71c30f6..00bbd85 100644 --- a/provider/googleProvider.js +++ b/provider/googleProvider.js @@ -92,6 +92,29 @@ class GoogleProvider { }, additionalProperties: false, }, + cloudFunctionSecretEnvironmentVariables: { + type: 'object', + patternProperties: { + '^[a-zA-Z0-9_]+$': { + type: 'object', + properties: { + projectId: { + type: 'string', + minLength: 1, + }, + secret: { + type: 'string', + pattern: '^[a-zA-Z0-9_-]+$', + }, + version: { + type: 'string', + pattern: '^(latest|[0-9.]+)$', + }, + }, + required: ['secret', 'version'], + }, + }, + }, cloudFunctionVpcEgress: { enum: ['ALL', 'ALL_TRAFFIC', 'PRIVATE', 'PRIVATE_RANGES_ONLY'], }, @@ -119,6 +142,7 @@ class GoogleProvider { memorySize: { $ref: '#/definitions/cloudFunctionMemory' }, // Can be overridden by function configuration timeout: { type: 'string' }, // Can be overridden by function configuration environment: { $ref: '#/definitions/cloudFunctionEnvironmentVariables' }, // Can be overridden by function configuration + secrets: { $ref: '#/definitions/cloudFunctionSecretEnvironmentVariables' }, // Can be overridden by function configuration vpc: { type: 'string' }, // Can be overridden by function configuration vpcEgress: { $ref: '#/definitions/cloudFunctionVpcEgress' }, // Can be overridden by function configuration labels: { $ref: '#/definitions/resourceManagerLabels' }, // Can be overridden by function configuration @@ -133,6 +157,7 @@ class GoogleProvider { timeout: { type: 'string' }, // Override provider configuration minInstances: { type: 'number' }, environment: { $ref: '#/definitions/cloudFunctionEnvironmentVariables' }, // Override provider configuration + secrets: { $ref: '#/definitions/cloudFunctionSecretEnvironmentVariables' }, // Can be overridden by function configuration vpc: { type: 'string' }, // Override provider configuration vpcEgress: { $ref: '#/definitions/cloudFunctionVpcEgress' }, // Can be overridden by function configuration labels: { $ref: '#/definitions/resourceManagerLabels' }, // Override provider configuration @@ -279,6 +304,19 @@ class GoogleProvider { ); } + getConfiguredSecrets(funcObject) { + const providerSecrets = _.get(this, 'serverless.service.provider.secrets', {}); + const secrets = _.merge({}, providerSecrets, funcObject.secrets); + + const keys = Object.keys(secrets).sort(); + return keys.map((key) => { + return { + key, + ...secrets[key], + }; + }); + } + getConfiguredEnvironment(funcObject) { return _.merge( {},