Skip to content

Commit

Permalink
support latest in distribution build urls (#1569)
Browse files Browse the repository at this point in the history
  • Loading branch information
tianleh authored Mar 1, 2022
1 parent 2064d4f commit a0e25d8
Show file tree
Hide file tree
Showing 4 changed files with 350 additions and 16 deletions.
51 changes: 46 additions & 5 deletions deployment/lambdas/cf-url-rewriter/cf-url-rewriter.ts
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',
};
}
35 changes: 35 additions & 0 deletions deployment/lambdas/cf-url-rewriter/https-get.ts
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 deployment/test/lambdas/cf-url-rewriter/cf-url-rewriter.test.ts
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;
}
81 changes: 81 additions & 0 deletions deployment/test/lambdas/cf-url-rewriter/https-get.test.ts
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);
}
});

0 comments on commit a0e25d8

Please sign in to comment.