Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit e02f227

Browse files
committed
Add option to disable CORS support on endpoints
1 parent bba1548 commit e02f227

File tree

6 files changed

+238
-1
lines changed

6 files changed

+238
-1
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,16 @@ This feature comes with the following restrictions:
837837
- *Path parameters are only supported when `resp` is configured as`http`.*
838838
- *Individual path parameter values are not included as separate event parameters. Users have to manually parse values from the full `__ow_path` value.*
839839

840+
### CORS Support
841+
842+
API Gateway endpoints automatically include CORS headers for all endpoints under the service base path. This property can be disabled by manually configuring the `resources.apigw.cors` property.
843+
844+
```yaml
845+
resources:
846+
apigw:
847+
cors: false
848+
```
849+
840850
## Exporting Web Actions
841851

842852
Functions can be turned into "*web actions*" which return HTTP content without use of an API Gateway. This feature is enabled by setting an annotation (`web-export`) in the configuration file.

compile/apigw/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ class OpenWhiskCompileHttpEvents {
9292
return options;
9393
}
9494

95+
/**
96+
parseOptionalConfigParams (httpEvent) {
97+
const options = {}
98+
if (httpEvent.hasOwnProperty('cors')) {
99+
options.cors = httpEvent.cors
100+
}
101+
102+
return options
103+
}
104+
*/
105+
95106
parseHttpEvent(httpEvent) {
96107
if (httpEvent.path && httpEvent.method) {
97108
return { relpath: httpEvent.path, operation: httpEvent.method, responsetype: httpEvent.resp || 'json' };

compile/apigw/tests/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,19 @@ describe('OpenWhiskCompileHttpEvents', () => {
189189
return expect(result).to.deep.equal({basepath: '/my-service', relpath: '/api/foo/bar', operation: 'GET', action: '/sample_ns/my-service_action-name', responsetype: 'http'});
190190
});
191191

192+
/**
193+
it('should define http events with optional API GW parameters', () => {
194+
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
195+
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
196+
const http = {path: "/api/foo/bar", method: "GET", cors: true}
197+
let result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {}, http);
198+
expect(result.options).to.deep.equal({cors: true});
199+
http.cors = false
200+
result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {}, http);
201+
expect(result.options).to.deep.equal({cors: false});
202+
});
203+
*/
204+
192205
it('should throw if http event value invalid', () => {
193206
expect(() => openwhiskCompileHttpEvents.compileHttpEvent('', {}, 'OPERATION'))
194207
.to.throw(Error, /Incorrect HTTP event/);

deploy/lib/deployApiGw.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,35 @@ module.exports = {
2121
});
2222
},
2323

24+
// should only do this if config exists....
25+
updateRoutesConfig (basepath, config) {
26+
return this.provider.client().then(ow => {
27+
if (this.options.verbose) {
28+
this.serverless.cli.log(`Retrieving API Gateway Route Config: ${basepath} ${JSON.stringify(config)}`);
29+
}
30+
return ow.routes.get({basepath: basepath })
31+
.then(response => {
32+
const route = response.apis[0]
33+
if (!route) return BbPromise.resolve()
34+
35+
const swagger = route.value.apidoc
36+
if (this.options.verbose) {
37+
this.serverless.cli.log(`Retrieved API Gateway Route Config: ${JSON.stringify(swagger)}`);
38+
}
39+
const updated_swagger = this.configRouteSwagger(swagger, config)
40+
41+
if (this.options.verbose) {
42+
this.serverless.cli.log(`Updating API Gateway Route Config: ${JSON.stringify(updated_swagger)}`);
43+
}
44+
return ow.routes.create({swagger: updated_swagger})
45+
}).catch(err => {
46+
throw new this.serverless.classes.Error(
47+
`Failed to update API Gateway route config (${basepath}) due to error: ${err.message}`
48+
);
49+
})
50+
});
51+
},
52+
2453
unbindAllRoutes() {
2554
return new Promise((resolve) => {
2655
this.provider.client()
@@ -30,6 +59,31 @@ module.exports = {
3059
})
3160
},
3261

62+
configRouteSwagger(swagger, options) {
63+
const merged = Object.assign({}, swagger)
64+
65+
if (!merged.hasOwnProperty('x-ibm-configuration')) {
66+
merged['x-ibm-configuration'] = {}
67+
}
68+
69+
if (options.hasOwnProperty('cors')) {
70+
merged['x-ibm-configuration'].cors = { enabled: options.cors }
71+
}
72+
73+
return merged
74+
},
75+
76+
deployOptionalRoutesConfig() {
77+
const resources = this.serverless.service.resources || {}
78+
if (resources.hasOwnProperty('apigw')) {
79+
this.serverless.cli.log('Configuring API Gateway options...');
80+
const basepath = `/${this.serverless.service.service}`
81+
return this.updateRoutesConfig(basepath, resources.apigw)
82+
}
83+
84+
return BbPromise.resolve();
85+
},
86+
3387
deploySequentialRoutes(routes) {
3488
return BbPromise.mapSeries(routes, r => this.deployRoute(r))
3589
},
@@ -44,5 +98,6 @@ module.exports = {
4498
this.serverless.cli.log('Deploying API Gateway definitions...');
4599
return this.unbindAllRoutes()
46100
.then(() => this.deploySequentialRoutes(apigw))
101+
.then(() => this.deployOptionalRoutesConfig(apigw))
47102
}
48103
};

deploy/tests/deployApiGw.js

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const expect = require('chai').expect;
33
const OpenWhiskDeploy = require('../index');
44
const sinon = require('sinon');
55
const chaiAsPromised = require('chai-as-promised');
6+
const fs = require('fs');
67

78
require('chai').use(chaiAsPromised);
89

@@ -34,6 +35,32 @@ describe('deployHttpEvents', () => {
3435
sandbox.restore();
3536
});
3637

38+
describe('#deployOptionalRoutesConfig()', () => {
39+
it('should not call API GW unless config present', () => {
40+
sandbox.stub(openwhiskDeploy.provider, 'client', () => Promise.reject('No config present!'))
41+
return expect(openwhiskDeploy.deployOptionalRoutesConfig())
42+
.to.eventually.be.fulfilled;
43+
})
44+
45+
it('should call API GW when config present', () => {
46+
let called = false
47+
sandbox.stub(openwhiskDeploy, 'updateRoutesConfig', (basepath, config) => {
48+
called = true
49+
expect(basepath).to.be.equal('/my-service')
50+
expect(config).to.be.deep.equal(openwhiskDeploy.serverless.service.resources.apigw)
51+
return Promise.resolve({})
52+
});
53+
54+
openwhiskDeploy.serverless.service.resources = {
55+
apigw: { cors: true }
56+
}
57+
const result = openwhiskDeploy.deployOptionalRoutesConfig()
58+
59+
result.then(() => expect(called).to.be.equal(true))
60+
return expect(result).to.eventually.be.fulfilled;
61+
})
62+
})
63+
3764
describe('#unbindAllRoutes()', () => {
3865
it('should deploy api gw route handler to openwhisk', () => {
3966
sandbox.stub(openwhiskDeploy.provider, 'client', () => {
@@ -60,7 +87,62 @@ describe('deployHttpEvents', () => {
6087
});
6188

6289
});
63-
90+
91+
describe('#configRouteSwagger()', () => {
92+
it('should update swagger with CORS config parameter', () => {
93+
const source = fs.readFileSync('./deploy/tests/resources/swagger.json', 'utf-8')
94+
const swagger = JSON.parse(source)
95+
const options = { cors: false }
96+
let result = openwhiskDeploy.configRouteSwagger(swagger, options)
97+
expect(result['x-ibm-configuration'].cors.enabled).to.be.equal(false)
98+
99+
swagger['x-ibm-configuration'].cors.enabled = false
100+
options.cors = true
101+
result = openwhiskDeploy.configRouteSwagger(swagger, options)
102+
expect(result['x-ibm-configuration'].cors.enabled).to.be.equal(true)
103+
})
104+
105+
it('should maintain existing swagger config parameters', () => {
106+
const source = fs.readFileSync('./deploy/tests/resources/swagger.json', 'utf-8')
107+
const swagger = JSON.parse(source)
108+
swagger['x-ibm-configuration'].test = 'value'
109+
const options = { cors: false }
110+
let result = openwhiskDeploy.configRouteSwagger(swagger, options)
111+
expect(result['x-ibm-configuration'].test).to.be.equal('value')
112+
})
113+
114+
it('should leave swagger the same without config parameters', () => {
115+
const source = fs.readFileSync('./deploy/tests/resources/swagger.json', 'utf-8')
116+
const swagger = JSON.parse(source)
117+
const options = { }
118+
const result = openwhiskDeploy.configRouteSwagger(swagger, options)
119+
expect(result).to.be.deep.equal(swagger)
120+
})
121+
})
122+
123+
describe('#updateRouteConfig()', () => {
124+
it('should retrieve and deploy updated api gw route swagger to openwhisk', () => {
125+
const source = fs.readFileSync('./deploy/tests/resources/swagger.json', 'utf-8')
126+
const swagger = JSON.parse(source)
127+
128+
sandbox.stub(openwhiskDeploy.provider, 'client', () => {
129+
const get = params => {
130+
expect(params).to.be.deep.equal({basepath: '/my-service'});
131+
return Promise.resolve({ apis: [{value: {apidoc:swagger}}]});
132+
};
133+
134+
const create = params => {
135+
expect(params.swagger).to.be.deep.equal(swagger);
136+
return Promise.resolve({});
137+
};
138+
139+
return Promise.resolve({ routes: { get: get, create: create } });
140+
});
141+
return expect(openwhiskDeploy.updateRoutesConfig('/my-service', {random: false}))
142+
.to.eventually.be.fulfilled;
143+
});
144+
});
145+
64146
describe('#deploySequentialRoutes()', () => {
65147
it('should deploy each route in sequential order', () => {
66148
let inflight = 0

deploy/tests/resources/swagger.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"swagger": "2.0",
3+
"basePath": "/testing",
4+
"info": {
5+
"title": "testing",
6+
"version": "1.0"
7+
},
8+
"paths": {
9+
"/testing": {
10+
"get": {
11+
"operationId": "getTesting",
12+
"responses": {
13+
"200": {
14+
"description": "A successful invocation response"
15+
}
16+
},
17+
"x-openwhisk": {
18+
"action": "hello",
19+
"namespace": "[email protected]_dev",
20+
"package": "default",
21+
"url": "https://openwhisk/api/v1/web/[email protected]_dev/default/hello.json"
22+
}
23+
}
24+
}
25+
},
26+
"x-ibm-configuration": {
27+
"assembly": {
28+
"execute": [
29+
{
30+
"operation-switch": {
31+
"case": [
32+
{
33+
"execute": [
34+
{
35+
"set-variable": {
36+
"actions": [
37+
{
38+
"set": "message.headers.X-Require-Whisk-Auth",
39+
"value": "3b08f67e-c7fa-4998-9096-ffa355932a3d"
40+
}
41+
]
42+
}
43+
},
44+
{
45+
"invoke": {
46+
"target-url": "https://openwhisk/api/v1/web/[email protected]_dev/default/hello.json",
47+
"verb": "keep"
48+
}
49+
}
50+
],
51+
"operations": [
52+
"getTesting"
53+
]
54+
}
55+
],
56+
"otherwise": [],
57+
"title": "whisk-invoke"
58+
}
59+
}
60+
]
61+
},
62+
"cors": {
63+
"enabled": true
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)