Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bin/configure-fastly.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,14 @@ async.auto({
});
}]
}, (err, results) => {
if (err) throw new Error(err);
if (err) throw (err instanceof Error ? err : new Error(err));
if (process.env.FASTLY_ACTIVATE_CHANGES) {
fastly.activateVersion(results.version, (e, resp) => {
if (e) throw new Error(e);
if (e) throw (e instanceof Error ? e : new Error(e));
process.stdout.write(`Successfully configured and activated version ${resp.number}\n`);
// purge static-assets using surrogate key
fastly.purgeKey(FASTLY_SERVICE_ID, 'static-assets', error => {
if (error) throw new Error(error);
if (error) throw (error instanceof Error ? error : new Error(error));
process.stdout.write('Purged static assets.\n');
});
});
Expand Down
118 changes: 97 additions & 21 deletions bin/lib/fastly-extended.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
const Fastly = require('fastly');
const FASTLY_API_BASE = 'https://api.fastly.com';

const stringifyBody = body => {
if (typeof body === 'undefined') return;
return JSON.stringify(body);
};

const parseBody = async response => {
const text = await response.text();
if (!text) return null;
try {
return JSON.parse(text);
} catch {
return text;
}
};

const createError = (method, url, statusCode, responseBody) => {
const details = responseBody ? `: ${responseBody}` : '';
const error = new Error(`Fastly API ${method} ${url} failed with status ${statusCode}${details}`);
error.statusCode = statusCode;
error.responseBody = responseBody;
return error;
};

const requestJson = async (apiKey, method, url, body) => {
const options = {
method,
headers: {
'Accept': 'application/json',
'Fastly-Key': apiKey
}
};
if (typeof body !== 'undefined') {
options.body = stringifyBody(body);
options.headers['Content-Type'] = 'application/json';
}
const response = await fetch(`${FASTLY_API_BASE}${url}`, options);
const responseBody = await parseBody(response);
if (!response.ok) {
throw createError(method, url, response.status, typeof responseBody === 'string' ?
responseBody : JSON.stringify(responseBody));
}
return responseBody;
};

/*
* Fastly library extended to allow configuration for a particular service
Expand All @@ -8,8 +52,9 @@ const Fastly = require('fastly');
* @param {string} Service id
*/
module.exports = function (apiKey, serviceId) {
const fastly = Fastly(apiKey);
fastly.serviceId = serviceId;
const fastly = {
serviceId
};

/*
* Helper method for constructing Fastly API urls
Expand All @@ -23,27 +68,45 @@ module.exports = function (apiKey, serviceId) {
return `/service/${encodeURIComponent(servId)}/version/${version}`;
};

/*
* Request helper used by the Fastly service mutators below.
*
* @param {string} Method
* @param {string} URL
* @param {object|Function} Body or callback
* @param {Function} Callback
*/
fastly.request = function (method, url, body, cb) {
if (typeof body === 'function') {
cb = body;
body = void 0;
}
requestJson(apiKey, method, url, body)
.then(response => cb(null, response))
.catch(error => cb(error));
};

/*
* getLatestActiveVersion: Get the most recent version for the configured service
*
* @param {callback} Callback with signature *err, latestVersion)
*/
fastly.getLatestActiveVersion = function (cb) {
if (!this.serviceId) {
return cb('Failed to get latest version. No serviceId configured');
return cb(new Error('Failed to get latest version. No serviceId configured'));
}
const url = `/service/${encodeURIComponent(this.serviceId)}/version`;
this.request('GET', url, (err, versions) => {
if (err) {
return cb(`Failed to fetch versions: ${err}`);
return cb(new Error(`Failed to fetch versions: ${err.message}`));
}
const latestVersion = versions.reduce((latestActiveSoFar, cur) => {
// if one of [latestActiveSoFar, cur] is active and the other isn't,
// If one of [latestActiveSoFar, cur] is active and the other isn't,
// return whichever is active. If both are not active, return
// latestActiveSoFar.
if (!cur || !cur.active) return latestActiveSoFar;
if (!latestActiveSoFar || !latestActiveSoFar.active) return cur;
// when both are active, prefer whichever has a higher version number.
// When both are active, prefer whichever has a higher version number.
return (cur.number > latestActiveSoFar.number) ? cur : latestActiveSoFar;
}, null);
return cb(null, latestVersion);
Expand All @@ -60,7 +123,7 @@ module.exports = function (apiKey, serviceId) {
*/
fastly.setCondition = function (version, condition, cb) {
if (!this.serviceId) {
return cb('Failed to set condition. No serviceId configured');
return cb(new Error('Failed to set condition. No serviceId configured'));
}
const name = condition.name;
const putUrl = `${this.getFastlyAPIPrefix(this.serviceId, version)}/condition/${encodeURIComponent(name)}`;
Expand All @@ -69,14 +132,14 @@ module.exports = function (apiKey, serviceId) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, condition, (e, resp) => {
if (e) {
return cb(`Failed while inserting condition "${condition.statement}": ${e}`);
return cb(new Error(`Failed while inserting condition "${condition.statement}": ${e.message}`));
}
return cb(null, resp);
});
return;
}
if (err) {
return cb(`Failed to update condition "${condition.statement}": ${err}`);
return cb(new Error(`Failed to update condition "${condition.statement}": ${err.message}`));
}
return cb(null, response);
});
Expand All @@ -92,7 +155,7 @@ module.exports = function (apiKey, serviceId) {
*/
fastly.setFastlyHeader = function (version, header, cb) {
if (!this.serviceId) {
cb('Failed to set header. No serviceId configured');
return cb(new Error('Failed to set header. No serviceId configured'));
}
const name = header.name;
const putUrl = `${this.getFastlyAPIPrefix(this.serviceId, version)}/header/${encodeURIComponent(name)}`;
Expand All @@ -101,14 +164,14 @@ module.exports = function (apiKey, serviceId) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, header, (e, resp) => {
if (e) {
return cb(`Failed to insert header: ${e}`);
return cb(new Error(`Failed to insert header: ${e.message}`));
}
return cb(null, resp);
});
return;
}
if (err) {
return cb(`Failed to update header: ${err}`);
return cb(new Error(`Failed to update header: ${err.message}`));
}
return cb(null, response);
});
Expand All @@ -124,7 +187,7 @@ module.exports = function (apiKey, serviceId) {
*/
fastly.setResponseObject = function (version, responseObj, cb) {
if (!this.serviceId) {
cb('Failed to set response object. No serviceId configured');
return cb(new Error('Failed to set response object. No serviceId configured'));
}
const name = responseObj.name;
const putUrl =
Expand All @@ -134,14 +197,14 @@ module.exports = function (apiKey, serviceId) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, responseObj, (e, resp) => {
if (e) {
return cb(`Failed to insert response object: ${e}`);
return cb(new Error(`Failed to insert response object: ${e.message}`));
}
return cb(null, resp);
});
return;
}
if (err) {
return cb(`Failed to update response object: ${err}`);
return cb(new Error(`Failed to update response object: ${err.message}`));
}
return cb(null, response);
});
Expand All @@ -154,7 +217,7 @@ module.exports = function (apiKey, serviceId) {
* @param {callback} Callback for fastly.request
*/
fastly.cloneVersion = function (version, cb) {
if (!this.serviceId) return cb('Failed to clone version. No serviceId configured.');
if (!this.serviceId) return cb(new Error('Failed to clone version. No serviceId configured.'));
const url = `${this.getFastlyAPIPrefix(this.serviceId, version)}/clone`;
this.request('PUT', url, cb);
};
Expand All @@ -166,7 +229,7 @@ module.exports = function (apiKey, serviceId) {
* @param {callback} Callback for fastly.request
*/
fastly.activateVersion = function (version, cb) {
if (!this.serviceId) return cb('Failed to activate version. No serviceId configured.');
if (!this.serviceId) return cb(new Error('Failed to activate version. No serviceId configured.'));
const url = `${this.getFastlyAPIPrefix(this.serviceId, version)}/activate`;
this.request('PUT', url, cb);
};
Expand All @@ -182,7 +245,7 @@ module.exports = function (apiKey, serviceId) {
*/
fastly.setCustomVCL = function (version, name, vcl, cb) {
if (!this.serviceId) {
return cb('Failed to set response object. No serviceId configured');
return cb(new Error('Failed to set response object. No serviceId configured'));
}

const url = `${this.getFastlyAPIPrefix(this.serviceId, version)}/vcl/${name}`;
Expand All @@ -193,18 +256,31 @@ module.exports = function (apiKey, serviceId) {
content.name = name;
this.request('POST', postUrl, content, (e, resp) => {
if (e) {
return cb(`Failed while adding custom vcl "${name}": ${e}`);
return cb(new Error(`Failed while adding custom vcl "${name}": ${e.message}`));
}
return cb(null, resp);
});
return;
}
if (err) {
return cb(`Failed to update custom vcl "${name}": ${err}`);
return cb(new Error(`Failed to update custom vcl "${name}": ${err.message}`));
}
return cb(null, response);
});
};

/*
* Purge all content associated with a surrogate key.
*
* @param {string} serviceId service id
* @param {string} key surrogate key
* @param {Function} cb callback
*/
fastly.purgeKey = function (targetServiceId, key, cb) {
if (!targetServiceId) return cb(new Error('Failed to purge key. No serviceId configured.'));
const url = `/service/${encodeURIComponent(targetServiceId)}/purge/${encodeURIComponent(key)}`;
this.request('POST', url, cb);
};

return fastly;
};
Loading