Skip to content

Instantly share code, notes, and snippets.

@jkcgs
Created February 25, 2019 20:17
Show Gist options
  • Save jkcgs/a7ffde95f4b4fa8b4259eca796ecde29 to your computer and use it in GitHub Desktop.
Save jkcgs/a7ffde95f4b4fa8b4259eca796ecde29 to your computer and use it in GitHub Desktop.
AWS Lambda function to redirect to latest release assets from a GitHub repository.
'use strict';
const https = require('https');
/**
* This parses arguments from an URL in the following form and
* magically redirects to an asset of a GitHub project release.
*
* https://api.owo.cl/grr/<owner>/<repo>/[version]/[file]
*
* owner: User or org that owns a repo
* repo: The repo name
* version: (optional) Version tag of the release, defaults to latest
* An underscore "_" will mean "latest".
* file: (optional) file to retrieve, defaults to first asset, allows
* wildcards like *.zip, and it will fetch the first asset that
* matches that wildcard.
*
* Examples:
* - https://api.owo.cl/grr/d0k3/SafeB9SInstaller/v0.0.6/*.zip
* - https://api.owo.cl/grr/d0k3/SafeB9SInstaller
* - https://api.owo.cl/grr/d0k3/SafeB9SInstaller/_/*.zip
**/
exports.handler = (event, context, callback) => {
console.log('Event: ', event);
// If it's LAMBDA_PROXY handled, parameters will be in pathParameters.
let params = 'pathParameters' in event ? event.pathParameters : event;
// Verify required parameters
if (!('username' in params) || !('project' in params)) {
callback(new Error(`Missing required parameters.`));
return;
}
// Store parameters
let username = params.username;
let project = params.project;
let version = params.version || '_';
let filename = params.filename || '';
// Validate owner's username
if (!/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(username)) {
callback(new Error('Invalid username'));
return;
}
// Validate project name
let rx = /^[a-zA-Z0-9_\-\.]+$/;
if(!rx.test(project)) {
callback(new Error('Invalid project name'));
return;
}
// Validate and transform version parameter
if (!version || version === '_' || version === 'latest') {
version = 'releases/latest';
} else if(!rx.test(version)) {
callback(new Error('Invalid version'));
return;
} else {
// Only tag versions are supported
version = 'releases/tags/' + version;
}
// Generate request options
const releasesURL = `https://github.com/${username}/${project}/releases/latest`;
const options = {
hostname: 'api.github.com',
port: 443,
path: `/repos/${username}/${project}/${version}`,
method: 'GET',
headers: {
'User-Agent': 'GithubReleasesRedirect/0.0.1 (github.com/jkcgs)'
}
};
console.log('Options:', options);
// Load data from GitHub
const req = https.get(options, (res) => {
let body = '';
console.log('Status:', res.statusCode);
console.log('Headers:', JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk);
// Process data received from GitHub
res.on('end', () => {
console.log('Successfully processed HTTPS response');
// We should always receive a JSON response
if (!res.headers['content-type'].startsWith('application/json')) {
callback(new Error('Unexpected answer received: ' + res.headers['content-type']));
return;
}
body = JSON.parse(body);
// No assets (files) found, redirect to latest release page
if (!('assets' in body) || body.assets.length === 0) {
callback(null, redirect(releasesURL));
return;
}
let asset = null;
// Search by filename if passed
if (filename !== '') {
// Wildcard search
if (filename.includes('*')) {
let pat = wildcard(filename);
asset = body.assets.find(rs => pat.test(rs.name));
} else {
// Exact name search
asset = body.assets.find(rs => rs.name === filename);
}
} else {
// No filename passed = use first asset
asset = body.assets[0];
}
if (!asset) {
// No asset found = 404
callback(null, { statusCode: 404 });
} else {
// Send redirect to the asset's direct link
console.log('Found asset:', asset);
callback(null, redirect(asset.browser_download_url));
}
});
});
req.on('error', callback);
};
function redirect(url) {
return {
statusCode: 301,
headers: {
Location: url
}
};
}
// https://gist.github.com/donmccurdy/6d073ce2c6f3951312dfa45da14a420f
function wildcard(s) {
return new RegExp('^' + s.split(/\*+/).map(
x => x.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
).join('.*') + '$');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment