UNPKG

serverless-local-schedule

Version:

Allows defining CloudWatch schedules in local timezones(with DST!) in serverless events

165 lines (146 loc) 5.28 kB
const { localCrontabToUtcCrontabs } = require("local-crontab"); const { timezones } = require("timezone-enum/src/timezones.json") /** * Convert an AWS CloudWatch crontab to a standard crontab. * * Main differences are: * * A year field * * ? instead of * sometimes * * Some others.. implementation TBD * * The data that is removed is returned as well so that it can be used to * roundtrip back to an AWS CloudWatch crontab * */ const convertAwsToStandardCrontab = awsCrontab => { const crontabParts = awsCrontab.split(/\s+/); // standard crontabs don't have a year const year = crontabParts.pop(); // replace ? with *, but remember where they were const questionParts = []; for (const i in crontabParts) { if (crontabParts[i] === "?") { questionParts.push(i); crontabParts[i] = "*"; } } return { crontab: crontabParts.join(" "), awsSpecificDetails: { year, questionParts } }; }; const convertStandardCrontabToAws = ({ crontab, awsSpecificDetails }) => { const parts = crontab.split(/\s+/); for (const questionPart of awsSpecificDetails.questionParts) { parts[questionPart] = parts[questionPart].replace(/\*/, "?"); } parts.push(awsSpecificDetails.year); return parts.join(" "); }; const convertAwsLocalCrontabToAwsUtcCrontab = (localCrontab, timezone) => { const { crontab, awsSpecificDetails } = convertAwsToStandardCrontab( localCrontab ); const utcCrontabs = localCrontabToUtcCrontabs(crontab, timezone); return utcCrontabs.map(crontab => convertStandardCrontabToAws({ crontab, awsSpecificDetails }) ); }; function convertCrontabs() { this.serverless.cli.log("Converting local crontabs to UTC crontabs..."); const newCrontabsMap = {}; for (const funcName in this.serverless.service.functions) { for (const eventIndex in this.serverless.service.functions[funcName] .events) { const event = this.serverless.service.functions[funcName].events[ eventIndex ]; // only process events with a schedule & a timezone if ( event.hasOwnProperty("schedule") && event.schedule.hasOwnProperty("timezone") ) { const schedule = event.schedule; const ratesAreArray = Array.isArray(schedule.rate); const rates = ratesAreArray ? schedule.rate : [schedule.rate]; const matches = rates .map(rate => rate.match(/^cron\((.*)\)$/)) .filter(match => match && match[1]) if (!matches.length) // skip rate() schedules continue; // convert the local crontab to utc crontabs const newCrontabs = matches.flatMap(match => { const convertedCrontabs = convertAwsLocalCrontabToAwsUtcCrontab( match[1], schedule.timezone ); if (this.options.verbose || this.options.v) { this.serverless.cli.log(`Converted ${match[1]} ${schedule.timezone} to ${convertedCrontabs.join("\n ")}`); } return convertedCrontabs; }) // remove timezone from original schedule event delete schedule.timezone; // append new utc crontab schedule events newCrontabsMap[funcName] = newCrontabsMap[funcName] || { newCrontabs: [], removeIndexes: [] }; newCrontabsMap[funcName].removeIndexes.splice(0, 0, eventIndex); newCrontabsMap[funcName].newCrontabs.push( ...newCrontabs.map((crontab, i) => { // When the rates are an array, the version of Serverless being used // is one that doesn't support named schedules when there are multiple // schedules or rates. See https://github.com/serverless/serverless/issues/9867 for details. const addName = !ratesAreArray && schedule.name; return ({ schedule: Object.assign({}, schedule, { rate: ratesAreArray ? [`cron(${crontab})`] : `cron(${crontab})`, name: addName ? `${schedule.name}-${i}` : undefined, }) }); }) ); } } } // remove the original schedule events for (const funcName in newCrontabsMap) { newCrontabsMap[funcName].removeIndexes.forEach(eventIndex => { this.serverless.service.functions[funcName].events.splice(eventIndex, 1); }); } for (const funcName in newCrontabsMap) { this.serverless.service.functions[funcName].events.push( ...newCrontabsMap[funcName].newCrontabs ); } } class ServerlessLocalCrontabs { constructor(serverless, options) { this.serverless = serverless; if ( this.serverless.configSchemaHandler && this.serverless.configSchemaHandler.defineFunctionEventProperties ) { // Create schema for your properties. For reference use https://github.com/ajv-validator/ajv this.serverless.configSchemaHandler.defineFunctionEventProperties('aws', 'schedule', { properties: { timezone: { enum: timezones }, }, }); } this.options = options; this.hooks = { "before:package:initialize": convertCrontabs.bind(this) }; } } module.exports = ServerlessLocalCrontabs;