Merge pull request from LLK/release/2.2.20

[Develop] Release 2.2.20
This commit is contained in:
Matthew Taylor 2017-04-26 22:20:16 -04:00 committed by GitHub
commit 5152a337f0
5 changed files with 314 additions and 155 deletions

View file

@ -1,18 +1,12 @@
var async = require('async');
var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');
var fastlyConfig = require('./lib/fastly-config-methods');
var route_json = require('../src/routes.json');
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
const PASS_REQUEST_CONDITION_NAME = 'Pass';
const NOT_PASS_REQUEST_CONDITION_NAME = '!(Pass)';
const PASS_CACHE_CONDITION_NAME = 'Cache Pass';
const BUCKET_NAME_HEADER_NAME = 'Bucket name';
var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY_SERVICE_ID);
var extraAppRoutes = [
@ -23,87 +17,8 @@ var extraAppRoutes = [
'/[^\/]*\.html$'
];
/*
* Given the relative path to the static directory, return an array of
* patterns matching the files and directories there.
*/
var getStaticPaths = function (pathToStatic) {
var staticPaths = glob.sync(path.resolve(__dirname, pathToStatic));
return staticPaths.filter(function (pathName) {
// Exclude view html, resolve everything else in the build
return path.extname(pathName) !== '.html';
}).map(function (pathName) {
// Reduce absolute path to relative paths like '/js'
var base = path.dirname(path.resolve(__dirname, pathToStatic));
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
});
};
/*
* Given a list of express routes, return a list of patterns to match
* the express route and a static view file associated with the route
*/
var getViewPaths = function (routes) {
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
}
return paths;
}, []);
};
/*
* Translate an express-style pattern e.g. /path/:arg/ to a regex
* all :arguments become .+?
*/
var expressPatternToRegex = function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?');
};
/*
* Given a list of patterns for paths, OR all of them together into one
* string suitable for a Fastly condition
*/
var pathsToCondition = function (paths) {
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + ')"';
};
/*
* Helper method to NOT a condition statement
*/
var negateConditionStatement = function (statement) {
return '!(' + statement + ')';
};
/*
* Combine static paths, routes, and any additional paths to a single
* fastly condition to match req.url
*/
var getAppRouteCondition = function (pathToStatic, routes, additionalPaths) {
var staticPaths = getStaticPaths(pathToStatic);
var viewPaths = getViewPaths(routes);
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
return pathsToCondition(allPaths);
};
var getConditionNameForRoute = function (route, type) {
return 'routes/' + route.pattern + ' (' + type + ')';
};
var getHeaderNameForRoute = function (route) {
if (route.name) return 'rewrites/' + route.name;
if (route.redirect) return 'redirects/' + route.pattern;
};
var getResponseNameForRoute = function (route) {
return 'redirects/' + route.pattern;
};
var routes = route_json.map(function (route) {
return defaults({}, {pattern: expressPatternToRegex(route.pattern)}, route);
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
async.auto({
@ -121,76 +36,43 @@ async.auto({
}
});
},
notPassRequestCondition: ['version', function (cb, results) {
var statement = getAppRouteCondition('../build/*', routes, extraAppRoutes);
var condition = {
name: NOT_PASS_REQUEST_CONDITION_NAME,
statement: statement,
type: 'REQUEST',
priority: 10
};
fastly.setCondition(results.version, condition, cb);
}],
setBucketNameHeader: ['version', 'notPassRequestCondition', function (cb, results) {
var header = {
name: BUCKET_NAME_HEADER_NAME,
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',
dst: 'http.host',
src: '"' + S3_BUCKET_NAME + '"',
request_condition: results.notPassRequestCondition.name,
priority: 1
};
fastly.setFastlyHeader(results.version, header, cb);
}],
passRequestCondition: ['version', 'notPassRequestCondition', function (cb, results) {
var condition = {
name: PASS_REQUEST_CONDITION_NAME,
statement: negateConditionStatement(results.notPassRequestCondition.statement),
type: 'REQUEST',
priority: 10
};
fastly.setCondition(results.version, condition, cb);
}],
passRequestSettingsCondition: ['version', 'passRequestCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/request_settings/Pass',
{request_condition: results.passRequestCondition.name},
recvCustomVCL: ['version', function (cb, results) {
// For all the routes in routes.json, construct a varnish-style regex that matches
// on any of those route conditions.
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
// For all the routes in routes.json, construct a varnish-style regex that matches
// only if NONE of those routes are matched.
var passStatement = fastlyConfig.negateConditionStatement(notPassStatement);
// For a non-pass condition, point backend at s3
var backendCondition = fastlyConfig.setBackend(
'F_s3',
S3_BUCKET_NAME,
notPassStatement
);
// For a pass condition, set forwarding headers
var forwardCondition = fastlyConfig.setForwardHeaders(passStatement);
fastly.setCustomVCL(
results.version,
'recv-condition',
backendCondition + forwardCondition,
cb
);
}],
backendCondition: ['version', 'notPassRequestCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/backend/s3',
{request_condition: results.notPassRequestCondition.name},
cb
);
}],
passCacheCondition: ['version', 'passRequestCondition', function (cb, results) {
var condition = {
name: PASS_CACHE_CONDITION_NAME,
type: 'CACHE',
statement: results.passRequestCondition.statement,
priority: results.passRequestCondition.priority
};
fastly.setCondition(results.version, condition, cb);
}],
passCacheSettingsCondition: ['version', 'passCacheCondition', function (cb, results) {
fastly.request(
'PUT',
fastly.getFastlyAPIPrefix(FASTLY_SERVICE_ID, results.version) + '/cache_settings/Pass',
{cache_condition: results.passCacheCondition.name},
cb
fetchCustomVCL: ['version', function (cb, results) {
var passStatement = fastlyConfig.negateConditionStatement(
fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname)
);
var ttlCondition = fastlyConfig.setResponseTTL(passStatement);
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
}],
appRouteRequestConditions: ['version', function (cb, results) {
var conditions = {};
async.forEachOf(routes, function (route, id, cb2) {
var condition = {
name: getConditionNameForRoute(route, 'request'),
name: fastlyConfig.getConditionNameForRoute(route, 'request'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'REQUEST',
// Priority needs to be > 1 to not interact with http->https redirect
@ -213,7 +95,7 @@ async.auto({
async.auto({
responseCondition: function (cb3) {
var condition = {
name: getConditionNameForRoute(route, 'response'),
name: fastlyConfig.getConditionNameForRoute(route, 'response'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'RESPONSE',
priority: id
@ -222,16 +104,16 @@ async.auto({
},
responseObject: function (cb3) {
var responseObject = {
name: getResponseNameForRoute(route),
name: fastlyConfig.getResponseNameForRoute(route),
status: 301,
response: 'Moved Permanently',
request_condition: getConditionNameForRoute(route, 'request')
request_condition: fastlyConfig.getConditionNameForRoute(route, 'request')
};
fastly.setResponseObject(results.version, responseObject, cb3);
},
redirectHeader: ['responseCondition', function (cb3, redirectResults) {
var header = {
name: getHeaderNameForRoute(route),
name: fastlyConfig.getHeaderNameForRoute(route),
action: 'set',
ignore_if_set: 0,
type: 'RESPONSE',
@ -248,7 +130,7 @@ async.auto({
});
} else {
var header = {
name: getHeaderNameForRoute(route, 'request'),
name: fastlyConfig.getHeaderNameForRoute(route, 'request'),
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',

View file

@ -0,0 +1,139 @@
var glob = require('glob');
var path = require('path');
var FastlyConfigMethods = {
/*
* Given the relative path to the static directory, return an array of
* patterns matching the files and directories there.
*/
getStaticPaths: function (rootDir, pathToStatic) {
var staticPaths = glob.sync(path.resolve(rootDir, pathToStatic));
return staticPaths.filter(function (pathName) {
// Exclude view html, resolve everything else in the build
return path.extname(pathName) !== '.html';
}).map(function (pathName) {
// Reduce absolute path to relative paths like '/js'
var base = path.dirname(path.resolve(rootDir, pathToStatic));
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
});
},
/*
* Given a list of express routes, return a list of patterns to match
* the express route and a static view file associated with the route
*/
getViewPaths: function (routes) {
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
}
return paths;
}, []);
},
/*
* Translate an express-style pattern e.g. /path/:arg/ to a regex
* all :arguments become .+?
*/
expressPatternToRegex: function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?');
},
/*
* Given a list of patterns for paths, OR all of them together into one
* string suitable for a Fastly condition
*/
pathsToCondition: function (paths) {
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + ')"';
},
/*
* Helper method to NOT a condition statement
*/
negateConditionStatement: function (statement) {
return '!(' + statement + ')';
},
/*
* Combine static paths, routes, and any additional paths to a single
* fastly condition to match req.url
*/
getAppRouteCondition: function (pathToStatic, routes, additionalPaths, rootDir) {
var staticPaths = FastlyConfigMethods.getStaticPaths(rootDir, pathToStatic);
var viewPaths = FastlyConfigMethods.getViewPaths(routes);
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
return FastlyConfigMethods.pathsToCondition(allPaths);
},
getConditionNameForRoute: function (route, type) {
return 'routes/' + route.pattern + ' (' + type + ')';
},
getHeaderNameForRoute: function (route) {
if (route.name) return 'rewrites/' + route.name;
if (route.redirect) return 'redirects/' + route.pattern;
},
getResponseNameForRoute: function (route) {
return 'redirects/' + route.pattern;
},
/**
* Returns custom vcl configuration as a string for setting the backend
* of a request to the given backend/host.
*
* @param {string} backend name of the backend declared in fastly
* @param {string} host name of the s3 bucket to be set as the host
* @param {string} condition condition under which backend should be set
*/
setBackend: function (backend, host, condition) {
return '' +
'if (' + condition + ') {\n' +
' set req.backend = ' + backend + ';\n' +
' set req.http.host = \"' + host + '\";\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string for headers that
* should be added for the condition in which a request is forwarded.
*
* @param {string} condition condition under which to set pass headers
*/
setForwardHeaders: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3.
*
* @param {string} condition condition under which the response should be set
*/
setResponseTTL: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
'}\n';
}
};
module.exports = FastlyConfigMethods;

View file

@ -166,5 +166,40 @@ module.exports = function (apiKey, serviceId) {
this.request('PUT', url, cb);
};
/**
* Upsert a custom vcl file. Attempts a PUT, and falls back
* to POST if not there already.
*
* @param {number} version current version number for fastly service
* @param {string} name name of the custom vcl file to be upserted
* @param {string} vcl stringified custom vcl to be uploaded
* @param {Function} cb function that takes in two args: err, response
*/
fastly.setCustomVCL = function (version, name, vcl, cb) {
if (!this.serviceId) {
return cb('Failed to set response object. No serviceId configured');
}
var url = this.getFastlyAPIPrefix(this.serviceId, version) + '/vcl/' + name;
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/vcl';
var content = {content: vcl};
return this.request('PUT', url, content, function (err, response) {
if (err && err.statusCode === 404) {
content.name = name;
this.request('POST', postUrl, content, function (err, response) {
if (err) {
return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
}
return cb(null, response);
});
return;
}
if (err) {
return cb('Failed to update custom vcl \"' + name + '\": ' + err);
}
return cb(null, response);
}.bind(this));
};
return fastly;
};

View file

@ -338,9 +338,9 @@ module.exports = {
formData.user.birth.month - 1,
1
);
if (((Date.now() - birthdate) / (24*3600*1000*365.25)) < 13) {
if (((Date.now() - birthdate) / (24*3600*1000*365.25)) < this.props.birthOffset) {
return invalidate({
'user.birth.month': this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'})
'user.birth.year': this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'})
});
}
return this.props.onNextStep(formData);

View file

@ -0,0 +1,103 @@
var defaults = require('lodash.defaults');
var fastlyConfig = require('../../bin/lib/fastly-config-methods');
var route_json = require('../../src/routes.json');
var tap = require('tap');
var testRoutes = [
{
name: 'less-traveled',
pattern: '^/?$',
routeAlias: '/?$',
view: 'less-traveled/less-traveled',
title: 'Robert Frost Goes Here'
},
{
name: 'more-traveled',
pattern: '^/more?$',
routeAlias: '/more?$',
view: 'more-traveled/more-traveled',
title: 'Robert Frost Does Not Go Here'
}
];
var routes = route_json.map(function (route) {
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
var extraAppRoutes = [
// Homepage with querystring.
// TODO: Should this be added for every route?
'/\\?',
// View html
'/[^\/]*\.html$'
];
tap.test('getStaticPaths', function (t) {
var staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
t.type(staticPaths, 'object');
t.end();
});
tap.test('getViewPaths', function (t) {
var viewPaths = fastlyConfig.getViewPaths(testRoutes);
t.type(viewPaths, 'object');
t.equal(viewPaths[0], '/?$');
t.equal(viewPaths[1], '/more?$');
t.end();
});
tap.test('pathsToCondition', function (t) {
var condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
t.type(condition, 'string');
t.equal(condition, 'req.url~"^(/?$|/more?$)"');
t.end();
});
tap.test('getAppRouteCondition', function (t) {
var condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
t.type(condition, 'string');
t.end();
});
tap.test('testSetBackend', function (t) {
var backend = fastlyConfig.setBackend('wemust', 'goback', 'marty');
t.equal(backend, '' +
'if (marty) {\n' +
' set req.backend = wemust;\n' +
' set req.http.host = \"goback\";\n' +
'}\n'
);
t.end();
});
tap.test('testSetForward', function (t) {
var forward = fastlyConfig.setForwardHeaders('alwaysforward');
t.equal(forward, '' +
'if (alwaysforward) {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n'
);
t.end();
});
tap.test('testSetTTL', function (t) {
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
t.equal(ttl, '' +
'if (itsactuallyttyl) {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
'}\n'
);
t.end();
});