Skip to content

Commit 269df15

Browse files
authored
feat: Support provider.vpcEgress configuration (#271)
1 parent e6e0799 commit 269df15

File tree

3 files changed

+298
-8
lines changed

3 files changed

+298
-8
lines changed

package/lib/compileFunctions.js

+39-8
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ module.exports = {
2020
this.serverless.service.getAllFunctions().forEach((functionName) => {
2121
const funcObject = this.serverless.service.getFunction(functionName);
2222

23+
let vpcEgress = funcObject.vpcEgress || this.serverless.service.provider.vpcEgress;
24+
2325
this.serverless.cli.log(`Compiling function "${functionName}"...`);
2426

2527
validateHandlerProperty(funcObject, functionName);
2628
validateEventsProperty(funcObject, functionName);
2729
validateVpcConnectorProperty(funcObject, functionName);
30+
validateVpcConnectorEgressProperty(vpcEgress);
2831

2932
const funcTemplate = getFunctionTemplate(
3033
funcObject,
@@ -57,6 +60,15 @@ module.exports = {
5760
});
5861
}
5962

63+
if (vpcEgress) {
64+
vpcEgress = vpcEgress.toUpperCase();
65+
if (vpcEgress === 'ALL') vpcEgress = 'ALL_TRAFFIC';
66+
if (vpcEgress === 'PRIVATE') vpcEgress = 'PRIVATE_RANGES_ONLY';
67+
_.assign(funcTemplate.properties, {
68+
vpcConnectorEgressSettings: vpcEgress,
69+
});
70+
}
71+
6072
if (funcObject.maxInstances) {
6173
funcTemplate.properties.maxInstances = funcObject.maxInstances;
6274
}
@@ -116,18 +128,37 @@ const validateHandlerProperty = (funcObject, functionName) => {
116128

117129
const validateVpcConnectorProperty = (funcObject, functionName) => {
118130
if (funcObject.vpc && typeof funcObject.vpc === 'string') {
119-
const vpcNamePattern = /projects\/[\s\S]*\/locations\/[\s\S]*\/connectors\/[\s\S]*/i;
120-
if (!vpcNamePattern.test(funcObject.vpc)) {
121-
const errorMessage = [
122-
`The function "${functionName}" has invalid vpc connection name`,
123-
' VPC Connector name should follow projects/{project_id}/locations/{region}/connectors/{connector_name}',
124-
' Please check the docs for more info.',
125-
].join('');
126-
throw new Error(errorMessage);
131+
// vpcConnector argument can be one of two possible formats as described here:
132+
// https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction
133+
if (funcObject.vpc.indexOf('/') > -1) {
134+
const vpcNamePattern = /projects\/[\s\S]*\/locations\/[\s\S]*\/connectors\/[\s\S]*/i;
135+
if (!vpcNamePattern.test(funcObject.vpc)) {
136+
const errorMessage = [
137+
`The function "${functionName}" has invalid vpc connection name`,
138+
' VPC Connector name should follow projects/{project_id}/locations/{region}/connectors/{connector_name}',
139+
' or just {connector_name} if within the same project.',
140+
' Please check the docs for more info at ',
141+
' https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction',
142+
].join('');
143+
throw new Error(errorMessage);
144+
}
127145
}
128146
}
129147
};
130148

149+
const validateVpcConnectorEgressProperty = (vpcEgress) => {
150+
if (vpcEgress && typeof vpcEgress !== 'string') {
151+
const errorMessage = [
152+
'Your provider/function has invalid vpc connection name',
153+
' VPC Connector Egress Setting be either ALL_TRAFFIC or PRIVATE_RANGES_ONLY. ',
154+
' You may shorten these to ALL or PRIVATE optionally.',
155+
' Please check the docs for more info at',
156+
' https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#resource:-cloudfunction',
157+
].join('');
158+
throw new Error(errorMessage);
159+
}
160+
};
161+
131162
const getFunctionTemplate = (funcObject, projectName, region, sourceArchiveUrl) => {
132163
//eslint-disable-line
133164
return {

package/lib/compileFunctions.test.js

+254
Original file line numberDiff line numberDiff line change
@@ -767,4 +767,258 @@ describe('CompileFunctions', () => {
767767
});
768768
});
769769
});
770+
771+
it('should allow vpc as short name', () => {
772+
googlePackage.serverless.service.functions = {
773+
func1: {
774+
handler: 'func1',
775+
memorySize: 128,
776+
runtime: 'nodejs10',
777+
vpc: 'my-vpc',
778+
events: [{ http: 'foo' }],
779+
},
780+
};
781+
782+
const compiledResources = [
783+
{
784+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
785+
name: 'my-service-dev-func1',
786+
properties: {
787+
parent: 'projects/myProject/locations/us-central1',
788+
runtime: 'nodejs10',
789+
function: 'my-service-dev-func1',
790+
entryPoint: 'func1',
791+
availableMemoryMb: 128,
792+
timeout: '60s',
793+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
794+
httpsTrigger: {
795+
url: 'foo',
796+
},
797+
labels: {},
798+
vpcConnector: 'my-vpc',
799+
},
800+
},
801+
];
802+
803+
return googlePackage.compileFunctions().then(() => {
804+
expect(consoleLogStub.called).toEqual(true);
805+
expect(
806+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
807+
).toEqual(compiledResources);
808+
});
809+
});
810+
811+
it('should allow vpc egress all at function level', () => {
812+
googlePackage.serverless.service.functions = {
813+
func1: {
814+
handler: 'func1',
815+
memorySize: 128,
816+
runtime: 'nodejs10',
817+
vpc: 'my-vpc',
818+
vpcEgress: 'all',
819+
events: [{ http: 'foo' }],
820+
},
821+
};
822+
823+
const compiledResources = [
824+
{
825+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
826+
name: 'my-service-dev-func1',
827+
properties: {
828+
parent: 'projects/myProject/locations/us-central1',
829+
runtime: 'nodejs10',
830+
function: 'my-service-dev-func1',
831+
entryPoint: 'func1',
832+
availableMemoryMb: 128,
833+
timeout: '60s',
834+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
835+
httpsTrigger: {
836+
url: 'foo',
837+
},
838+
labels: {},
839+
vpcConnector: 'my-vpc',
840+
vpcConnectorEgressSettings: 'ALL_TRAFFIC',
841+
},
842+
},
843+
];
844+
845+
return googlePackage.compileFunctions().then(() => {
846+
expect(consoleLogStub.called).toEqual(true);
847+
expect(
848+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
849+
).toEqual(compiledResources);
850+
});
851+
});
852+
853+
it('should allow vpc egress private at function level', () => {
854+
googlePackage.serverless.service.functions = {
855+
func1: {
856+
handler: 'func1',
857+
memorySize: 128,
858+
runtime: 'nodejs10',
859+
vpc: 'my-vpc',
860+
vpcEgress: 'private',
861+
events: [{ http: 'foo' }],
862+
},
863+
};
864+
865+
const compiledResources = [
866+
{
867+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
868+
name: 'my-service-dev-func1',
869+
properties: {
870+
parent: 'projects/myProject/locations/us-central1',
871+
runtime: 'nodejs10',
872+
function: 'my-service-dev-func1',
873+
entryPoint: 'func1',
874+
availableMemoryMb: 128,
875+
timeout: '60s',
876+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
877+
httpsTrigger: {
878+
url: 'foo',
879+
},
880+
labels: {},
881+
vpcConnector: 'my-vpc',
882+
vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY',
883+
},
884+
},
885+
];
886+
887+
return googlePackage.compileFunctions().then(() => {
888+
expect(consoleLogStub.called).toEqual(true);
889+
expect(
890+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
891+
).toEqual(compiledResources);
892+
});
893+
});
894+
895+
it('should allow vpc egress all at provider level', () => {
896+
googlePackage.serverless.service.functions = {
897+
func1: {
898+
handler: 'func1',
899+
memorySize: 128,
900+
runtime: 'nodejs10',
901+
vpc: 'my-vpc',
902+
events: [{ http: 'foo' }],
903+
},
904+
};
905+
906+
googlePackage.serverless.service.provider.vpcEgress = 'all';
907+
908+
const compiledResources = [
909+
{
910+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
911+
name: 'my-service-dev-func1',
912+
properties: {
913+
parent: 'projects/myProject/locations/us-central1',
914+
runtime: 'nodejs10',
915+
function: 'my-service-dev-func1',
916+
entryPoint: 'func1',
917+
availableMemoryMb: 128,
918+
timeout: '60s',
919+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
920+
httpsTrigger: {
921+
url: 'foo',
922+
},
923+
labels: {},
924+
vpcConnector: 'my-vpc',
925+
vpcConnectorEgressSettings: 'ALL_TRAFFIC',
926+
},
927+
},
928+
];
929+
930+
return googlePackage.compileFunctions().then(() => {
931+
expect(consoleLogStub.called).toEqual(true);
932+
expect(
933+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
934+
).toEqual(compiledResources);
935+
});
936+
});
937+
938+
it('should allow vpc egress private at provider level', () => {
939+
googlePackage.serverless.service.functions = {
940+
func1: {
941+
handler: 'func1',
942+
memorySize: 128,
943+
runtime: 'nodejs10',
944+
vpc: 'my-vpc',
945+
events: [{ http: 'foo' }],
946+
},
947+
};
948+
949+
googlePackage.serverless.service.provider.vpcEgress = 'private';
950+
951+
const compiledResources = [
952+
{
953+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
954+
name: 'my-service-dev-func1',
955+
properties: {
956+
parent: 'projects/myProject/locations/us-central1',
957+
runtime: 'nodejs10',
958+
function: 'my-service-dev-func1',
959+
entryPoint: 'func1',
960+
availableMemoryMb: 128,
961+
timeout: '60s',
962+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
963+
httpsTrigger: {
964+
url: 'foo',
965+
},
966+
labels: {},
967+
vpcConnector: 'my-vpc',
968+
vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY',
969+
},
970+
},
971+
];
972+
973+
return googlePackage.compileFunctions().then(() => {
974+
expect(consoleLogStub.called).toEqual(true);
975+
expect(
976+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
977+
).toEqual(compiledResources);
978+
});
979+
});
980+
981+
it('should replace vpc egress private at provider level for a vpc egress all defined at function level', () => {
982+
googlePackage.serverless.service.functions = {
983+
func1: {
984+
handler: 'func1',
985+
memorySize: 128,
986+
runtime: 'nodejs10',
987+
vpc: 'my-vpc',
988+
events: [{ http: 'foo' }],
989+
vpcEgress: 'all',
990+
},
991+
};
992+
993+
googlePackage.serverless.service.provider.vpcEgress = 'private';
994+
995+
const compiledResources = [
996+
{
997+
type: 'gcp-types/cloudfunctions-v1:projects.locations.functions',
998+
name: 'my-service-dev-func1',
999+
properties: {
1000+
parent: 'projects/myProject/locations/us-central1',
1001+
runtime: 'nodejs10',
1002+
function: 'my-service-dev-func1',
1003+
entryPoint: 'func1',
1004+
availableMemoryMb: 128,
1005+
timeout: '60s',
1006+
sourceArchiveUrl: 'gs://sls-my-service-dev-12345678/some-path/artifact.zip',
1007+
httpsTrigger: {
1008+
url: 'foo',
1009+
},
1010+
labels: {},
1011+
vpcConnector: 'my-vpc',
1012+
vpcConnectorEgressSettings: 'ALL_TRAFFIC',
1013+
},
1014+
},
1015+
];
1016+
1017+
return googlePackage.compileFunctions().then(() => {
1018+
expect(consoleLogStub.called).toEqual(true);
1019+
expect(
1020+
googlePackage.serverless.service.provider.compiledConfigurationTemplate.resources
1021+
).toEqual(compiledResources);
1022+
});
1023+
});
7701024
});

provider/googleProvider.js

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class GoogleProvider {
8686
},
8787
additionalProperties: false,
8888
},
89+
cloudFunctionVpcEgress: {
90+
enum: ['ALL', 'ALL_TRAFFIC', 'PRIVATE', 'PRIVATE_RANGES_ONLY'],
91+
},
8992
resourceManagerLabels: {
9093
type: 'object',
9194
propertyNames: {
@@ -111,6 +114,7 @@ class GoogleProvider {
111114
timeout: { type: 'string' }, // Can be overridden by function configuration
112115
environment: { $ref: '#/definitions/cloudFunctionEnvironmentVariables' }, // Can be overridden by function configuration
113116
vpc: { type: 'string' }, // Can be overridden by function configuration
117+
vpcEgress: { $ref: '#/definitions/cloudFunctionVpcEgress' }, // Can be overridden by function configuration
114118
labels: { $ref: '#/definitions/resourceManagerLabels' }, // Can be overridden by function configuration
115119
},
116120
},
@@ -123,6 +127,7 @@ class GoogleProvider {
123127
timeout: { type: 'string' }, // Override provider configuration
124128
environment: { $ref: '#/definitions/cloudFunctionEnvironmentVariables' }, // Override provider configuration
125129
vpc: { type: 'string' }, // Override provider configuration
130+
vpcEgress: { $ref: '#/definitions/cloudFunctionVpcEgress' }, // Can be overridden by function configuration
126131
labels: { $ref: '#/definitions/resourceManagerLabels' }, // Override provider configuration
127132
},
128133
},

0 commit comments

Comments
 (0)