Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VPC Naming and Egress Setting #250

Closed
wants to merge 7 commits into from
49 changes: 45 additions & 4 deletions package/lib/compileFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = {
validateHandlerProperty(funcObject, functionName);
validateEventsProperty(funcObject, functionName);
validateVpcConnectorProperty(funcObject, functionName);
validateVpcConnectorEgressProperty(funcObject, functionName);

const funcTemplate = getFunctionTemplate(
funcObject,
Expand Down Expand Up @@ -62,6 +63,19 @@ module.exports = {
});
}

if (funcObject.vpcEgress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I might be missing something here - if we're only checking for setting on funcObject, does that mean that setting on provider level will always be ineffective?

let egress =
_.get(funcObject, 'vpcEgress') || _.get(this, 'serverless.service.provider.vpcEgress');
if (egress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that check redundant? The whole if will be executed only if funcObject has vpcEgress set, which means that egress will always be set.

egress = egress.toUpperCase();
if (egress === 'ALL') egress = 'ALL_TRAFFIC';
if (egress === 'PRIVATE') egress = 'PRIVATE_RANGES_ONLY';
}
_.assign(funcTemplate.properties, {
vpcConnectorEgressSettings: egress,
});
}

if (funcObject.maxInstances) {
funcTemplate.properties.maxInstances = funcObject.maxInstances;
}
Expand Down Expand Up @@ -145,12 +159,39 @@ const validateEventsProperty = (funcObject, functionName) => {

const validateVpcConnectorProperty = (funcObject, functionName) => {
if (funcObject.vpc && typeof funcObject.vpc === 'string') {
const vpcNamePattern = /projects\/[\s\S]*\/locations\/[\s\S]*\/connectors\/[\s\S]*/i;
if (!vpcNamePattern.test(funcObject.vpc)) {
// vpcConnector argument can be one of two possible formats as described here:
// https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction
if (funcObject.vpc.indexOf('/') > -1) {
const vpcNamePattern = /projects\/[\s\S]*\/locations\/[\s\S]*\/connectors\/[\s\S]*/i;
if (!vpcNamePattern.test(funcObject.vpc)) {
const errorMessage = [
`The function "${functionName}" has invalid vpc connection name`,
' VPC Connector name should follow projects/{project_id}/locations/{region}/connectors/{connector_name}',
' or just {connector_name} if within the same project.',
' Please check the docs for more info at ',
' https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction',
].join('');
throw new Error(errorMessage);
}
}
}
};

const validateVpcConnectorEgressProperty = (funcObject, functionName) => {
if (funcObject.vpcEgress && typeof funcObject.vpcEgress === 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new property should be added to configuration schema in provider/googleProvider.js

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey! how are you? sorry, I was trying to make these changes since these would be of great help to me.
I can't find where to add that property in that file. Would you help me?

Thank you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello 👋 The schema for provider and function properties is here:

provider: {
properties: {
credentials: { type: 'string' },
project: { type: 'string' },
region: { $ref: '#/definitions/cloudFunctionRegion' },
runtime: { $ref: '#/definitions/cloudFunctionRuntime' }, // Can be overridden by function configuration
serviceAccountEmail: { type: 'string' }, // Can be overridden by function configuration
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
vpc: { type: 'string' }, // Can be overridden by function configuration
labels: { $ref: '#/definitions/resourceManagerLabels' }, // Can be overridden by function configuration
},
},
function: {
properties: {
handler: { type: 'string' },
runtime: { $ref: '#/definitions/cloudFunctionRuntime' }, // Override provider configuration
serviceAccountEmail: { type: 'string' }, // Override provider configuration
memorySize: { $ref: '#/definitions/cloudFunctionMemory' }, // Override provider configuration
timeout: { type: 'string' }, // Override provider configuration
environment: { $ref: '#/definitions/cloudFunctionEnvironmentVariables' }, // Override provider configuration
vpc: { type: 'string' }, // Override provider configuration
labels: { $ref: '#/definitions/resourceManagerLabels' }, // Override provider configuration
},
},
});

const egress = funcObject.vpcEgress.toUpperCase();
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check can be achieved by proper schema definition in for vpcEgress property

egress !== 'ALL' &&
egress !== 'ALL_TRAFFIC' &&
egress !== 'PRIVATE' &&
egress !== 'PRIVATE_RANGES_ONLY'
) {
const errorMessage = [
`The function "${functionName}" has invalid vpc connection name`,
' VPC Connector name should follow projects/{project_id}/locations/{region}/connectors/{connector_name}',
' Please check the docs for more info.',
' VPC Connector Egress Setting be either ALL_TRAFFIC or PRIVATE_RANGES_ONLY. ',
' You may shorten these to ALL or PRIVATE optionally.',
' Please check the docs for more info at',
' https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction',
].join('');
throw new Error(errorMessage);
}
Expand Down
124 changes: 124 additions & 0 deletions package/lib/compileFunctions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -767,4 +767,128 @@ describe('CompileFunctions', () => {
});
});
});

it('should allow vpc as short name', () => {
googlePackage.serverless.service.functions = {
func1: {
handler: 'func1',
memorySize: 128,
runtime: 'nodejs10',
vpc: 'my-vpc',
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: 128,
timeout: '60s',
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
httpsTrigger: {
url: 'foo',
},
labels: {},
vpcConnector: 'my-vpc',
},
},
];

return googlePackage.compileFunctions().then(() => {
expect(consoleLogStub.called).toEqual(true);
expect(
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
).toEqual(compiledResources);
});
});

it('should allow vpc egress all', () => {
googlePackage.serverless.service.functions = {
func1: {
handler: 'func1',
memorySize: 128,
runtime: 'nodejs10',
vpc: 'my-vpc',
vpcEgress: 'all',
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: 128,
timeout: '60s',
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
httpsTrigger: {
url: 'foo',
},
labels: {},
vpcConnector: 'my-vpc',
vpcConnectorEgressSettings: 'ALL_TRAFFIC',
},
},
];

return googlePackage.compileFunctions().then(() => {
expect(consoleLogStub.called).toEqual(true);
expect(
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
).toEqual(compiledResources);
});
});

it('should allow vpc egress private', () => {
googlePackage.serverless.service.functions = {
func1: {
handler: 'func1',
memorySize: 128,
runtime: 'nodejs10',
vpc: 'my-vpc',
vpcEgress: 'private',
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: 128,
timeout: '60s',
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
httpsTrigger: {
url: 'foo',
},
labels: {},
vpcConnector: 'my-vpc',
vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY',
},
},
];

return googlePackage.compileFunctions().then(() => {
expect(consoleLogStub.called).toEqual(true);
expect(
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
).toEqual(compiledResources);
});
});
});