Skip to content

Commit 301a392

Browse files
authored
Merge pull request #3390 from natefoo/cloudfront-rewrites
Add CloudFront rewrites function, test, and deployment workflow
2 parents 1c612db + fd3adb2 commit 301a392

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

.github/workflows/rewrites.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Deploy CloudFront Rewrites Function
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
paths:
8+
- 'deploy/rewrites.js'
9+
pull_request:
10+
branches:
11+
- master
12+
paths:
13+
- 'deploy/rewrites.js'
14+
15+
jobs:
16+
test:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v5
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v5
25+
with:
26+
node-version: '24'
27+
28+
- name: Run tests
29+
run: node deploy/test-rewrites.js
30+
31+
deploy:
32+
runs-on: ubuntu-latest
33+
needs: test
34+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
35+
36+
steps:
37+
- name: Checkout code
38+
uses: actions/checkout@v5
39+
40+
- name: Configure AWS Credentials
41+
uses: aws-actions/configure-aws-credentials@v4
42+
with:
43+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
44+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
45+
aws-region: us-east-1
46+
47+
- name: Deploy CloudFront Rewrites Function
48+
uses: dhollerbach/actions.deploy-cloudfront-function@v1
49+
with:
50+
function-name: hub-rewrites
51+
comment: Deployed from GitHub Actions
52+
source-file: ./deploy/rewrites.js

deploy/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Galaxy Hub Deployment
2+
3+
The [rewrites.js](rewrites.js) function contains a [CloudFront Viewer Request function](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html) to intercept requests and perform rewrites (redirects). See the existing rewrites for syntax. The function is automatically deployed with the [rewrites.yml](../.github/workflows/rewrites.yml) workflow.

deploy/rewrites.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
function handler(event) {
2+
var request = event.request;
3+
var uri = request.uri;
4+
5+
var patterns = [
6+
{ pattern: "^/bushman/?$", target: "https://usegalaxy.org/bushman/" },
7+
{ pattern: "^/learn/api/?$", target: "/develop/api/" },
8+
{ pattern: "^/admin/api/?$", target: "/develop/api/" },
9+
];
10+
11+
for (var i = 0; i < patterns.length; i++) {
12+
var re = new RegExp(patterns[i].pattern);
13+
var m = uri.match(re);
14+
if (m) {
15+
var dest = patterns[i].target.replace("$1", m[1]);
16+
return {
17+
statusCode: 302,
18+
statusDescription: "Found",
19+
headers: { location: { value: dest } },
20+
};
21+
}
22+
}
23+
24+
return request;
25+
}

deploy/test-rewrites.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// deploy/test-rewrites.js
2+
const assert = require('assert');
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
// Load and evaluate the CloudFront function
7+
const functionCode = fs.readFileSync(path.join(__dirname, 'rewrites.js'), 'utf8');
8+
9+
// CloudFront functions don't use module.exports, so we need to eval it
10+
// and extract the handler function
11+
const handler = eval(`(function() { ${functionCode}; return handler; })()`);
12+
13+
// Helper to create test events
14+
function createEvent(uri) {
15+
return {
16+
request: {
17+
uri: uri,
18+
method: 'GET',
19+
querystring: {},
20+
headers: {}
21+
}
22+
};
23+
}
24+
25+
// Test suite
26+
console.log('Running CloudFront Rewrite Function Tests...\n');
27+
28+
let passed = 0;
29+
let failed = 0;
30+
31+
function test(description, testFn) {
32+
try {
33+
testFn();
34+
console.log(`✓ ${description}`);
35+
passed++;
36+
} catch (error) {
37+
console.log(`✗ ${description}`);
38+
console.log(` Error: ${error.message}`);
39+
failed++;
40+
}
41+
}
42+
43+
// Test 1: /bushman redirect
44+
test('Redirects /bushman to https://usegalaxy.org/bushman/', () => {
45+
const event = createEvent('/bushman');
46+
const result = handler(event);
47+
assert.strictEqual(result.statusCode, 302);
48+
assert.strictEqual(result.headers.location.value, 'https://usegalaxy.org/bushman/');
49+
});
50+
51+
// Test 2: /bushman/ redirect (with trailing slash)
52+
test('Redirects /bushman/ to https://usegalaxy.org/bushman/', () => {
53+
const event = createEvent('/bushman/');
54+
const result = handler(event);
55+
assert.strictEqual(result.statusCode, 302);
56+
assert.strictEqual(result.headers.location.value, 'https://usegalaxy.org/bushman/');
57+
});
58+
59+
// Test 3: /learn/api redirect
60+
test('Redirects /learn/api to /develop/api/', () => {
61+
const event = createEvent('/learn/api');
62+
const result = handler(event);
63+
assert.strictEqual(result.statusCode, 302);
64+
assert.strictEqual(result.headers.location.value, '/develop/api/');
65+
});
66+
67+
// Test 4: /learn/api/ redirect (with trailing slash)
68+
test('Redirects /learn/api/ to /develop/api/', () => {
69+
const event = createEvent('/learn/api/');
70+
const result = handler(event);
71+
assert.strictEqual(result.statusCode, 302);
72+
assert.strictEqual(result.headers.location.value, '/develop/api/');
73+
});
74+
75+
// Test 5: Non-matching URI passes through
76+
test('Passes through non-matching URI /other/path', () => {
77+
const event = createEvent('/other/path');
78+
const result = handler(event);
79+
assert.strictEqual(result.uri, '/other/path');
80+
assert.strictEqual(result.statusCode, undefined);
81+
});
82+
83+
// Test 6: Root path passes through
84+
test('Passes through root path /', () => {
85+
const event = createEvent('/');
86+
const result = handler(event);
87+
assert.strictEqual(result.uri, '/');
88+
assert.strictEqual(result.statusCode, undefined);
89+
});
90+
91+
// Test 7: /bushman with additional path should not match
92+
test('Does not redirect /bushman/extra', () => {
93+
const event = createEvent('/bushman/extra');
94+
const result = handler(event);
95+
assert.strictEqual(result.uri, '/bushman/extra');
96+
assert.strictEqual(result.statusCode, undefined);
97+
});
98+
99+
// Test 8: /learn/api with additional path should not match
100+
test('Does not redirect /learn/api/extra', () => {
101+
const event = createEvent('/learn/api/extra');
102+
const result = handler(event);
103+
assert.strictEqual(result.uri, '/learn/api/extra');
104+
assert.strictEqual(result.statusCode, undefined);
105+
});
106+
107+
// Summary
108+
console.log(`\n${passed} passed, ${failed} failed`);
109+
110+
if (failed > 0) {
111+
process.exit(1);
112+
}

0 commit comments

Comments
 (0)