grunt.registerMultiTask('fastly', 'A Grunt plugin to purge cache from Fastly', function() { var done = this.async(); // Merge task-specific and/or target-specific options with these defaults. var options = this.options({ purgeAll: false, concurrentPurges: 10 }); if (typeof options.key === 'undefined') { grunt.fail.fatal('Fastly api key is required.'); } fastly = fastly(options.key); // Purge all cache from a service if (options.purgeAll) { if (typeof options.serviceId === 'undefined') { grunt.fail.fatal('A serviceId must be provided when purging all cache.'); } grunt.log.write('PurgeAll from "'+options.serviceId+'"...'); fastly.purgeAll(options.serviceId, function(err) { if (err) grunt.log.error(); else grunt.log.ok(); done(); }); return } // Purge only specific urls if (typeof options.host === 'undefined') { grunt.fail.fatal('If purging specific urls, a host must be provided.'); } if (typeof options.urls === 'undefined') { options.urls = []; } async.eachLimit(options.urls, options.concurrentPurges, function(uriPath, next) { var uri = url.format({host: options.host, pathname: uriPath}).substr(2); fastly.purge(options.host, uriPath, function(err) { if (err) grunt.log.error(); else grunt.log.writeln('Purged: '+uri); next(); }); }, done); });
module.exports = function (apiKey, serviceId) { var fastly = Fastly(apiKey); fastly.serviceId = serviceId; /* * Helper method for constructing Fastly API urls * * @param {string} Service id * @param {number} Version * * @return {string} */ fastly.getFastlyAPIPrefix = function (serviceId, version) { return '/service/' + encodeURIComponent(serviceId) + '/version/' + version; }; /* * getLatestVersion: Get the most recent version for the configured service * * @param {callback} Callback with signature *err, latestVersion) */ fastly.getLatestVersion = function (cb) { if (!this.serviceId) { return cb('Failed to get latest version. No serviceId configured'); } var url = '/service/'+ encodeURIComponent(this.serviceId) +'/version'; this.request('GET', url, function (err, versions) { if (err) { return cb('Failed to fetch versions: ' + err); } var latestVersion = versions.reduce(function (latestVersion, version) { if (!latestVersion) return version; if (version.number > latestVersion.number) return version; return latestVersion; }); return cb(null, latestVersion); }); }; /* * setCondition: Upsert a Fastly condition entry * Attempts to PUT and POSTs if the PUT request is a 404 * * @param {number} Version number * @param {object} Condition object sent to the API * @param {callback} Callback for fastly.request */ fastly.setCondition = function (version, condition, cb) { if (!this.serviceId) { return cb('Failed to set condition. No serviceId configured'); } var name = condition.name; var putUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition/' + encodeURIComponent(name); var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition'; return this.request('PUT', putUrl, condition, function (err, response) { if (err && err.statusCode === 404) { this.request('POST', postUrl, condition, function (err, response) { if (err) { return cb('Failed while inserting condition: ' + err); } return cb(null, response); }); return; } if (err) { return cb('Failed to update condition: ' + err); } return cb(null, response); }.bind(this)); }; /* * setFastlyHeader: Upsert a Fastly header entry * Attempts to PUT and POSTs if the PUT request is a 404 * * @param {number} Version number * @param {object} Header object sent to the API * @param {callback} Callback for fastly.request */ fastly.setFastlyHeader = function (version, header, cb) { if (!this.serviceId) { cb('Failed to set header. No serviceId configured'); } var name = header.name; var putUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header/' + encodeURIComponent(name); var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header'; return this.request('PUT', putUrl, header, function (err, response) { if (err && err.statusCode === 404) { this.request('POST', postUrl, header, function (err, response) { if (err) { return cb('Failed to insert header: ' + err); } return cb(null, response); }); return; } if (err) { return cb('Failed to update header: ' + err); } return cb(null, response); }.bind(this)); }; /* * setResponseObject: Upsert a Fastly response object * Attempts to PUT and POSTs if the PUT request is a 404 * * @param {number} Version number * @param {object} Response object sent to the API * @param {callback} Callback for fastly.request */ fastly.setResponseObject = function (version, responseObj, cb) { if (!this.serviceId) { cb('Failed to set response object. No serviceId configured'); } var name = responseObj.name; var putUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object/' + encodeURIComponent(name); var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object'; return this.request('PUT', putUrl, responseObj, function (err, response) { if (err && err.statusCode === 404) { this.request('POST', postUrl, responseObj, function (err, response) { if (err) { return cb('Failed to insert response object: ' + err); } return cb(null, response); }); return; } if (err) { return cb('Failed to update response object: ' + err); } return cb(null, response); }.bind(this)); }; /* * cloneVersion: Clone a version to create a new version * * @param {number} Version to clone * @param {callback} Callback for fastly.request */ fastly.cloneVersion = function (version, cb) { if (!this.serviceId) return cb('Failed to clone version. No serviceId configured.'); var url = this.getFastlyAPIPrefix(this.serviceId, version) + '/clone'; this.request('PUT', url, cb); }; /* * activateVersion: Activate a version * * @param {number} Version number * @param {callback} Callback for fastly.request */ fastly.activateVersion = function (version, cb) { if (!this.serviceId) return cb('Failed to activate version. No serviceId configured.'); var url = this.getFastlyAPIPrefix(this.serviceId, version) + '/activate'; this.request('PUT', url, cb); }; return fastly; };