From e4c4cac40d9d65e638afadb128199e710260d035 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Sun, 9 Jun 2024 11:35:08 -0500 Subject: [PATCH] Add scheduler client --- lib/clients/scheduler.doc.js | 60 +++++++++++++++ lib/clients/scheduler.js | 138 ++++++++++++++++++++++++++++++++++ lib/clients/scheduler.spec.js | 72 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 lib/clients/scheduler.doc.js create mode 100644 lib/clients/scheduler.js create mode 100644 lib/clients/scheduler.spec.js diff --git a/lib/clients/scheduler.doc.js b/lib/clients/scheduler.doc.js new file mode 100644 index 0000000..57b4233 --- /dev/null +++ b/lib/clients/scheduler.doc.js @@ -0,0 +1,60 @@ +/** + * AWS SchedulerClient client. + * @class SchedulerClient + * @see {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-eventbridge/index.html|@aws-sdk/client-scheduler} + * @param {Object} parameters + * @param {string} [parameters.name=scheduler] Client name. + * @param {string} [parameters.reqId=] Request id. + * @param {Object} [parameters.log=] Pino compatible logger. + * @param {Constructor} [parameters.AwsSdkSchedulerClient=SchedulerClient] + * Constructor for a SchedulerClient from the AWS SDK. + * @param {Object} [parameters.params={}] + * Additional params to pass to the AwsSdkSchedulerClient constructor. + */ + +/** + * Get a schedule. + * @async + * @function getSchedule + * @memberof SchedulerClient + * @instance + * @param {Object[]} [name] Name of the schedule to get. + * @param {Object} [params=[]] Additional params to pass to the GetScheduleCommand. + * @return {Promise} Response normalized to camel case. + */ + +/** + * Create a schedule. + * @async + * @function createSchedule + * @memberof SchedulerClient + * @instance + * @param {Object[]} [name] Name of the schedule to create. + * @param {Object} [params=[]] Additional params to pass to the CreateScheduleCommand. + * @return {Promise} Response normalized to camel case. + */ + +/** + * Delete a schedule. + * @async + * @function deleteSchedule + * @memberof SchedulerClient + * @instance + * @param {Object[]} [name] Name of the schedule to delete. + * @param {Object} [params=[]] Additional params to pass to the DeleteScheduleCommand. + * @return {Promise} Response normalized to camel case. + */ + +/** + * Update a schedule. + * AWS uses a replace all attributes strategy when updating schedules. + * This method simplifies updates by getting the existing schedule first and merging all existing top level keys with the new parameter values. + * AWS schedules + * @async + * @function deleteSchedule + * @memberof SchedulerClient + * @instance + * @param {Object[]} [name] Name of the schedule to update. + * @param {Object} [params=[]] Additional params to pass to the UpdateScheduleCommand. + * @return {Promise} Response normalized to camel case. + */ diff --git a/lib/clients/scheduler.js b/lib/clients/scheduler.js new file mode 100644 index 0000000..b0873f3 --- /dev/null +++ b/lib/clients/scheduler.js @@ -0,0 +1,138 @@ +import { + SchedulerClient as AwsSdkSchedulerClient, + CreateScheduleCommand, + DeleteScheduleCommand, + GetScheduleCommand, + UpdateScheduleCommand +} from '@aws-sdk/client-scheduler' +import { v4 as uuidv4 } from 'uuid' +import { createLogger } from '@meltwater/mlabs-logger' + +import { createCache } from '../cache.js' +import { keysToCamelCase, keysToPascalCase } from '../case.js' + +const createClient = createCache() + +export class SchedulerClient { + #client + #reqId + #log + + constructor({ + name = 'scheduler', + reqId = uuidv4(), + log = createLogger(), + AwsSchedulerClient = AwsSdkSchedulerClient, + params = {} + }) { + this.#client = createClient(name, () => new AwsSchedulerClient(params)) + this.#reqId = reqId + this.#log = log.child({ + params, + client: name, + class: SchedulerClient.name, + reqId + }) + } + + async getSchedule(scheduleName, params = {}) { + const log = this.#log.child({ + scheduleName, + meta: params, + method: SchedulerClient.prototype.getSchedule.name + }) + try { + log.info('start') + const req = formatReq({ ...params, name: scheduleName }) + const command = new GetScheduleCommand(req) + + const res = await this.#client.send(command) + + const data = formatRes(res) + + log.debug({ data }, 'data') + log.info('end') + return data + } catch (err) { + log.error({ err }, 'fail') + throw err + } + } + + async createSchedule(scheduleName, params = {}) { + const log = this.#log.child({ + scheduleName, + meta: params, + method: SchedulerClient.prototype.createSchedule.name + }) + try { + log.info('start') + const req = formatReq({ ...params, name: scheduleName }) + const command = new CreateScheduleCommand(req) + + const res = await this.#client.send(command) + + const data = formatRes(res) + + log.debug({ data }, 'data') + log.info('end') + return data + } catch (err) { + log.error({ err }, 'fail') + throw err + } + } + + async deleteSchedule(scheduleName, params = {}) { + const log = this.#log.child({ + scheduleName, + meta: params, + method: SchedulerClient.prototype.deleteSchedule.name + }) + try { + log.info('start') + const req = formatReq({ name: scheduleName }) + const command = new DeleteScheduleCommand(req) + + const res = await this.#client.send(command) + + const data = formatRes(res) + + log.debug({ data }, 'data') + log.info('end') + return data + } catch (err) { + log.error({ err }, 'fail') + throw err + } + } + + async updateSchedule(scheduleName, params = {}) { + const log = this.#log.child({ + scheduleName, + meta: params, + method: SchedulerClient.prototype.updateSchedule.name + }) + try { + log.info('start') + const schedule = await this.getSchedule(scheduleName) + + const req = formatReq({ ...schedule, ...params, name: scheduleName }) + const command = new UpdateScheduleCommand(req) + + const res = await this.#client.send(command) + + const data = formatRes(res) + + log.debug({ data }, 'data') + log.info('end') + return data + } catch (err) { + log.error({ err }, 'fail') + throw err + } + } +} + +const formatReq = keysToPascalCase +const formatRes = keysToCamelCase diff --git a/lib/clients/scheduler.spec.js b/lib/clients/scheduler.spec.js new file mode 100644 index 0000000..e401fc2 --- /dev/null +++ b/lib/clients/scheduler.spec.js @@ -0,0 +1,72 @@ +import test from 'ava' +import * as td from 'testdouble' +import { v4 as uuidv4 } from 'uuid' +import { createLogger } from '@meltwater/mlabs-logger' +import { + CreateScheduleCommand, + DeleteScheduleCommand, + GetScheduleCommand, + UpdateScheduleCommand +} from '@aws-sdk/client-scheduler' + +import { registerTestdoubleMatchers } from '../../testdouble-matchers.js' + +import { SchedulerClient } from './scheduler.js' + +test.before(() => { + registerTestdoubleMatchers(td) +}) + +test.beforeEach((t) => { + t.context.AwsSchedulerClient = td.constructor(['send']) + + t.context.createClient = (t, options) => { + const client = new SchedulerClient({ + name: uuidv4(), + AwsSchedulerClient: t.context.AwsSchedulerClient, + reqId, + log: createLogger({ t }), + ...options + }) + + return client + } +}) + +test('constructor: passes params to AWS SchedulerClient', (t) => { + const { AwsSchedulerClient } = t.context + const params = { foo: 'bar' } + const client = new SchedulerClient({ + name: uuidv4(), + AwsSchedulerClient, + params, + log: createLogger({ t }) + }) + td.verify(new AwsSchedulerClient(params)) + t.truthy(client) +}) + +test('getSchedule: returns response', async (t) => { + const { AwsSchedulerClient, createClient } = t.context + const client = createClient(t) + + td.when( + AwsSchedulerClient.prototype.send( + td.matchers.isAwsSdkCommand( + new GetScheduleCommand({ Name: scheduleName }) + ) + ) + ).thenResolve(getScheduleResponse) + + const data = await client.getSchedule(scheduleName) + + t.deepEqual(data, getScheduleResponseFormatted) +}) + +const reqId = 'mock-req-id' + +const scheduleName = 'mock-schedule-name' + +const getScheduleResponse = { ScheduleArn: 'mock-schedule-arn' } + +const getScheduleResponseFormatted = { scheduleArn: 'mock-schedule-arn' }