-
Notifications
You must be signed in to change notification settings - Fork 273
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support latest in distribution build urls (#1569)
- Loading branch information
Showing
4 changed files
with
350 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,55 @@ | ||
import { CloudFrontRequest, CloudFrontRequestCallback, CloudFrontRequestEvent, Context } from 'aws-lambda'; | ||
import { httpsGet } from './https-get'; | ||
|
||
export const handle = (request: CloudFrontRequest) => { | ||
export async function handler(event: CloudFrontRequestEvent, context: Context, callback: CloudFrontRequestCallback) { | ||
const request = event.Records[0].cf.request; | ||
// Incoming URLs from ci.opensearch.org will have a '/ci/123/' prefix, remove the prefix path from requests into S3. | ||
request.uri = request.uri.replace(/^\/ci\/...\//, '\/'); | ||
|
||
if (request.uri.includes("/latest/")) { | ||
|
||
const indexUri = request.uri.replace(/\/latest\/.*/, '/index.json'); | ||
|
||
try { | ||
const data: any = await httpsGet('https://' + request.headers.host[0].value + indexUri); | ||
|
||
if (data && data.latest) { | ||
callback(null, redirectResponse(request, data.latest)); | ||
} else { | ||
callback(null, errorResponse()); | ||
} | ||
} catch (e) { | ||
console.log(e); | ||
callback(null, errorResponse()); | ||
} | ||
|
||
} else { | ||
callback(null, request); | ||
} | ||
} | ||
|
||
export async function handler(event: CloudFrontRequestEvent, context: Context, callback: CloudFrontRequestCallback) { | ||
const request = event.Records[0].cf.request; | ||
function redirectResponse(request: CloudFrontRequest, latestNumber: number) { | ||
return { | ||
status: '302', | ||
statusDescription: 'Moved temporarily', | ||
headers: { | ||
'location': [{ | ||
key: 'Location', | ||
value: request.uri.replace(/\/latest\//, '/' + latestNumber + '/'), | ||
}], | ||
'cache-control': [{ | ||
key: 'Cache-Control', | ||
value: "max-age=3600" | ||
}], | ||
}, | ||
}; | ||
|
||
handle(request); | ||
} | ||
|
||
callback(null, request); | ||
function errorResponse() { | ||
return { | ||
body: 'The page is not found!', | ||
status: '404', | ||
statusDescription: 'Not found', | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as https from 'https'; | ||
|
||
export async function httpsGet(url: string) { | ||
|
||
return new Promise((resolve, reject) => { | ||
|
||
const request = https.get(url, (res) => { | ||
|
||
let body = ""; | ||
|
||
res.on("data", (chunk) => { | ||
body += chunk; | ||
}); | ||
|
||
res.on("end", () => { | ||
try { | ||
let json = JSON.parse(body); | ||
resolve(json); | ||
} catch (e) { | ||
console.log(e); | ||
reject({ | ||
error: 'Failed to parse body!' | ||
}); | ||
}; | ||
}); | ||
}); | ||
|
||
request.on("error", (e) => { | ||
console.log(e); | ||
reject({ | ||
error: 'Request error!' | ||
}); | ||
}); | ||
}); | ||
} |
199 changes: 188 additions & 11 deletions
199
deployment/test/lambdas/cf-url-rewriter/cf-url-rewriter.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,197 @@ | ||
import { CloudFrontRequest } from 'aws-lambda'; | ||
import { handle } from "../../../lambdas/cf-url-rewriter/cf-url-rewriter"; | ||
import { CloudFrontEvent, CloudFrontHeaders, CloudFrontRequest, CloudFrontRequestCallback, CloudFrontRequestEvent, Context } from 'aws-lambda'; | ||
import { handler } from '../../../lambdas/cf-url-rewriter/cf-url-rewriter'; | ||
import { httpsGet } from '../../../lambdas/cf-url-rewriter/https-get'; | ||
jest.mock('../../../lambdas/cf-url-rewriter/https-get'); | ||
|
||
test('Lmabda handle uri with ci string', () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
test('handler with latest url and valid latest field', async () => { | ||
|
||
const event = createTestEvent('/bundle-build-dashboards/1.2.0/latest/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '123' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(httpsGet).toBeCalledWith('https://test.cloudfront.net/bundle-build-dashboards/1.2.0/index.json'); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"headers": { | ||
"cache-control": [{ "key": "Cache-Control", "value": "max-age=3600" }], | ||
"location": [{ "key": "Location", "value": "/bundle-build-dashboards/1.2.0/123/linux/x64/" }] | ||
}, | ||
"status": "302", | ||
"statusDescription": "Moved temporarily" | ||
} | ||
); | ||
}); | ||
|
||
test('handler with latest url and with ci keyword and valid latest field', async () => { | ||
|
||
const event = createTestEvent('/ci/dbc/bundle-build-dashboards/1.2.0/latest/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '123' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(httpsGet).toBeCalledWith('https://test.cloudfront.net/bundle-build-dashboards/1.2.0/index.json'); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"headers": { | ||
"cache-control": [{ "key": "Cache-Control", "value": "max-age=3600" }], | ||
"location": [{ "key": "Location", "value": "/bundle-build-dashboards/1.2.0/123/linux/x64/" }] | ||
}, | ||
"status": "302", | ||
"statusDescription": "Moved temporarily" | ||
} | ||
); | ||
}); | ||
|
||
test('handler with latest url and empty latest field', async () => { | ||
|
||
const event = createTestEvent('/bundle-build-dashboards/1.2.0/latest/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(httpsGet).toBeCalledWith('https://test.cloudfront.net/bundle-build-dashboards/1.2.0/index.json'); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"body": "The page is not found!", | ||
"status": "404", | ||
"statusDescription": "Not found" | ||
} | ||
); | ||
}); | ||
|
||
test('handler with latest url and exception when getting index.json', async () => { | ||
|
||
let request = { uri: '/ci/dbc/bundle-build-dashboards/1.2.0/428/linux/x64/' } as CloudFrontRequest; | ||
const event = createTestEvent('/bundle-build-dashboards/1.2.0/latest/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
handle(request); | ||
(httpsGet as unknown as jest.Mock).mockImplementation(() => { | ||
throw new Error('Error getting!'); | ||
}); | ||
|
||
expect(request.uri).toBe('/bundle-build-dashboards/1.2.0/428/linux/x64/'); | ||
await handler(event, context, callback); | ||
|
||
expect(httpsGet).toBeCalledWith('https://test.cloudfront.net/bundle-build-dashboards/1.2.0/index.json'); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"body": "The page is not found!", | ||
"status": "404", | ||
"statusDescription": "Not found" | ||
} | ||
); | ||
}); | ||
|
||
test('Lmabda handle uri without ci string', () => { | ||
test('handler without latest url and without ci keyword', async () => { | ||
|
||
const event = createTestEvent('/bundle-build-dashboards/1.2.0/456/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '123' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"headers": { "host": [{ "key": "Host", "value": "test.cloudfront.net" }] }, | ||
"uri": "/bundle-build-dashboards/1.2.0/456/linux/x64/" | ||
} | ||
); | ||
|
||
expect(httpsGet).not.toHaveBeenCalled(); | ||
}) | ||
|
||
test('handler without latest url and with ci keyword', async () => { | ||
|
||
const event = createTestEvent('/ci/dbc/bundle-build-dashboards/1.2.0/456/linux/x64/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '123' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"headers": { "host": [{ "key": "Host", "value": "test.cloudfront.net" }] }, | ||
"uri": "/bundle-build-dashboards/1.2.0/456/linux/x64/" | ||
} | ||
); | ||
|
||
expect(httpsGet).not.toHaveBeenCalled(); | ||
}) | ||
|
||
test('handler with /fool(latest)bar/ keyword', async () => { | ||
|
||
const event = createTestEvent('/bundle-build-dashboards/1.2.0/456/linux/x64/foollatestbar/'); | ||
const context = {} as Context; | ||
const callback = jest.fn() as CloudFrontRequestCallback; | ||
|
||
(httpsGet as unknown as jest.Mock).mockReturnValue({ latest: '123' }); | ||
|
||
await handler(event, context, callback); | ||
|
||
expect(callback).toHaveBeenCalledWith( | ||
null, | ||
{ | ||
"headers": { "host": [{ "key": "Host", "value": "test.cloudfront.net" }] }, | ||
"uri": "/bundle-build-dashboards/1.2.0/456/linux/x64/foollatestbar/" | ||
} | ||
); | ||
|
||
expect(httpsGet).not.toHaveBeenCalled(); | ||
}) | ||
|
||
function createTestEvent(uri: string): CloudFrontRequestEvent { | ||
const event = {} as CloudFrontRequestEvent; | ||
|
||
const headers = { | ||
"host": [ | ||
{ | ||
"key": "Host", | ||
"value": "test.cloudfront.net" | ||
} | ||
] | ||
} as CloudFrontHeaders; | ||
|
||
const request = { | ||
uri: uri, | ||
headers: headers | ||
|
||
} as CloudFrontRequest; | ||
|
||
let request = { uri: '/bundle-build-dashboards/1.2.0/428/linux/x64/' } as CloudFrontRequest; | ||
const cf: CloudFrontEvent & { | ||
request: CloudFrontRequest; | ||
} = { | ||
config: {} as CloudFrontEvent["config"], | ||
request: request | ||
}; | ||
|
||
handle(request); | ||
event.Records = [{ cf: cf }]; | ||
|
||
expect(request.uri).toBe('/bundle-build-dashboards/1.2.0/428/linux/x64/'); | ||
}); | ||
return event; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { ClientRequest } from 'http'; | ||
import * as https from 'https'; | ||
import { Stream } from 'stream'; | ||
import { httpsGet } from '../../../lambdas/cf-url-rewriter/https-get'; | ||
|
||
jest.mock('https'); | ||
|
||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
test('httpGet valid json', async () => { | ||
|
||
const stream = new Stream(); | ||
|
||
(https.get as unknown as jest.Mock) = jest.fn().mockImplementation((url, cb) => { | ||
cb(stream); | ||
stream.emit('data', JSON.stringify({ key: 'value' })); | ||
stream.emit('end'); | ||
}); | ||
|
||
const url = 'https://testurl.com'; | ||
const data = await httpsGet(url); | ||
|
||
expect(data).toStrictEqual({ key: 'value' }); | ||
}); | ||
|
||
test('httpGet invalid json', async () => { | ||
const stream = new Stream(); | ||
|
||
(https.get as unknown as jest.Mock) = jest.fn().mockImplementation((url, cb) => { | ||
cb(stream); | ||
stream.emit('data', 'random string'); | ||
stream.emit('end'); | ||
}); | ||
|
||
const url = 'https://testurl.com'; | ||
|
||
try { | ||
await httpsGet(url); | ||
} catch (e) { | ||
expect(e).toStrictEqual({ error: 'Failed to parse body!' }); | ||
} | ||
}); | ||
|
||
test('httpGet request on event error', async () => { | ||
const stream = new Stream(); | ||
const mockGet = {} as ClientRequest; | ||
|
||
mockGet.on = jest.fn().mockImplementation((event, cb) => { | ||
cb(stream); | ||
stream.emit('error', 'some error'); | ||
}); | ||
|
||
(https.get as unknown as jest.Mock).mockReturnValue(mockGet); | ||
|
||
const url = 'https://testurl.com'; | ||
|
||
try { | ||
await httpsGet(url); | ||
} catch (e) { | ||
expect(e).toStrictEqual({ error: 'Request error!' }); | ||
} | ||
|
||
}); | ||
|
||
test('httpGet request error', async () => { | ||
const error = new Error('Error getting!'); | ||
|
||
(https.get as unknown as jest.Mock).mockImplementation(() => { | ||
throw error; | ||
}); | ||
|
||
const url = 'https://testurl.com'; | ||
|
||
try { | ||
await httpsGet(url); | ||
} catch (e) { | ||
expect(e).toBe(error); | ||
} | ||
}); |