From d3ecb2382debcfe384d958abfcdf929e00e8eb37 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Tue, 4 Oct 2022 16:50:24 +0200 Subject: [PATCH] feat(events/sqs): add a new AWS SQS event publisher This change introduces a new plugin `@backstage/plugin-events-backend-module-aws-sqs`. This plugin provides an event publisher which receives events from (an) AWS SQS queue(s) and publishes them to the event broker. The plugin supports the new backend-plugin-api and connects with the other plugins. Signed-off-by: Patrick Jungermann --- .changeset/modern-toys-yell.md | 12 + .github/CODEOWNERS | 1 + .../.eslintrc.js | 1 + .../events-backend-module-aws-sqs/README.md | 42 + .../api-report.md | 29 + .../events-backend-module-aws-sqs/config.d.ts | 78 ++ .../package.json | 48 + .../src/index.ts | 27 + .../AwsSqsConsumingEventPublisher.test.ts | 227 +++++ .../AwsSqsConsumingEventPublisher.ts | 191 ++++ .../src/publisher/config.test.ts | 222 +++++ .../src/publisher/config.ts | 117 +++ ...onsumingEventPublisherEventsModule.test.ts | 94 ++ ...sSqsConsumingEventPublisherEventsModule.ts | 55 ++ .../src/setupTests.ts | 17 + yarn.lock | 883 +++++++++++++++++- 16 files changed, 2041 insertions(+), 3 deletions(-) create mode 100644 .changeset/modern-toys-yell.md create mode 100644 plugins/events-backend-module-aws-sqs/.eslintrc.js create mode 100644 plugins/events-backend-module-aws-sqs/README.md create mode 100644 plugins/events-backend-module-aws-sqs/api-report.md create mode 100644 plugins/events-backend-module-aws-sqs/config.d.ts create mode 100644 plugins/events-backend-module-aws-sqs/package.json create mode 100644 plugins/events-backend-module-aws-sqs/src/index.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/publisher/config.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.test.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.ts create mode 100644 plugins/events-backend-module-aws-sqs/src/setupTests.ts diff --git a/.changeset/modern-toys-yell.md b/.changeset/modern-toys-yell.md new file mode 100644 index 0000000000..d867d2b3af --- /dev/null +++ b/.changeset/modern-toys-yell.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-events-backend-module-aws-sqs': minor +--- + +Adds a new module `aws-sqs` for plugin-events-backend. + +The module provides an event publisher `AwsSqsConsumingEventPublisher` +which will allow you to receive events from +an AWS SQS queue and will publish these to the used event broker. + +Please find more information at +https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dea361606a..84c8cd22d2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -37,6 +37,7 @@ yarn.lock @backstage/reviewers @backst /plugins/cost-insights @backstage/reviewers @backstage/silver-lining /plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining /plugins/events-backend @backstage/reviewers @pjungermann +/plugins/events-backend-module-aws-sqs @backstage/reviewers @pjungermann /plugins/events-backend-test-utils @backstage/reviewers @pjungermann /plugins/events-node @backstage/reviewers @pjungermann /plugins/explore @backstage/reviewers @backstage/sda-se-reviewers diff --git a/plugins/events-backend-module-aws-sqs/.eslintrc.js b/plugins/events-backend-module-aws-sqs/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/events-backend-module-aws-sqs/README.md b/plugins/events-backend-module-aws-sqs/README.md new file mode 100644 index 0000000000..cb3a5045c0 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/README.md @@ -0,0 +1,42 @@ +# events-backend-module-aws-sqs + +Welcome to the `events-backend-module-aws-sqs` backend plugin! + +This plugin is a module for the `events-backend` backend plugin +and extends it with an `AwsSqsConsumingEventPublisher`. + +This event publisher will allow you to receive events from +an AWS SQS queue and will publish these to the used event broker. + +## Configuration + +The polled AWS SQS queues depend on your configuration: + +```yaml +events: + modules: + awsSqs: + awsSqsConsumingEventPublisher: + topics: + topicName1: # replace with actual topic name as expected by subscribers + queue: + url: 'https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue' + region: us-east-2 + # visibilityTimeout - as HumanDuration; defaults to queue-based config + # waitTime - as HumanDuration; defaults to max of 20 seconds (long polling) + # timeout - as HumanDuration; timeout for the task execution + # waitTimeAfterEmptyReceive - as HumanDuration; time to wait before a retry when there was no message. + topicName2: + # [...] +``` + +## Installation + +1. Install the [`events-backend` plugin](../events-backend/README.md). +2. Install this module +3. Add your configuration. + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-events-backend-module-aws-sqs +``` diff --git a/plugins/events-backend-module-aws-sqs/api-report.md b/plugins/events-backend-module-aws-sqs/api-report.md new file mode 100644 index 0000000000..79a422c9f3 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/api-report.md @@ -0,0 +1,29 @@ +## API Report File for "@backstage/plugin-events-backend-module-aws-sqs" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { EventBroker } from '@backstage/plugin-events-node'; +import { EventPublisher } from '@backstage/plugin-events-node'; +import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; + +// @public +export class AwsSqsConsumingEventPublisher implements EventPublisher { + // (undocumented) + static fromConfig(env: { + config: Config; + logger: Logger; + scheduler: PluginTaskScheduler; + }): AwsSqsConsumingEventPublisher[]; + // (undocumented) + setEventBroker(eventBroker: EventBroker): Promise; +} + +// @alpha +export const awsSqsConsumingEventPublisherEventsModule: ( + options?: undefined, +) => BackendFeature; +``` diff --git a/plugins/events-backend-module-aws-sqs/config.d.ts b/plugins/events-backend-module-aws-sqs/config.d.ts new file mode 100644 index 0000000000..dfef35bf22 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/config.d.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HumanDuration } from '@backstage/types'; + +export interface Config { + events?: { + modules?: { + /** + * events-backend-module-aws-sqs plugin configuration. + */ + awsSqs?: { + /** + * Configuration for AwsSqsConsumingEventPublisher. + */ + awsSqsConsumingEventPublisher?: { + /** + * Contains a record per topic for which an AWS SQS queue + * should be used as source of events. + */ + topics: Record< + string, + { + /** + * (Required) Queue-related configuration. + */ + queue: { + /** + * (Required) The region of the AWS SQS queue. + */ + region: string; + /** + * (Required) The absolute URL for the AWS SQS queue to be used. + */ + url: string; + /** + * (Optional) Visibility timeout for messages in flight. + */ + visibilityTimeout: HumanDuration; + /** + * (Optional) Wait time when polling for available messages. + * Default: 20 seconds. + */ + waitTime: HumanDuration; + }; + /** + * (Optional) Timeout for the task execution which includes polling for messages + * and publishing the events to the event broker + * and the wait time after empty receives. + * + * Must be greater than `queue.waitTime` + `waitTimeAfterEmptyReceive`. + */ + timeout: HumanDuration; + /** + * (Optional) Wait time before polling again if no message was received. + * Default: 1 minute. + */ + waitTimeAfterEmptyReceive: HumanDuration; + } + >; + }; + }; + }; + }; +} diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json new file mode 100644 index 0000000000..ecce75d9a3 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -0,0 +1,48 @@ +{ + "name": "@backstage/plugin-events-backend-module-aws-sqs", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@aws-sdk/client-sqs": "^3.0.0", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/backend-tasks": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", + "@backstage/types": "workspace:^", + "luxon": "^3.0.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-events-backend-test-utils": "workspace:^", + "aws-sdk-client-mock": "^2.0.0" + }, + "files": [ + "alpha", + "config.d.ts", + "dist" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/events-backend-module-aws-sqs/src/index.ts b/plugins/events-backend-module-aws-sqs/src/index.ts new file mode 100644 index 0000000000..73b950856b --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The module "sqs" for the Backstage backend plugin "events" + * adding an AWS SQS-based publisher, + * receiving events from an AWS SQS queue and passing it to the + * internal event broker. + * + * @packageDocumentation + */ + +export { AwsSqsConsumingEventPublisher } from './publisher/AwsSqsConsumingEventPublisher'; +export { awsSqsConsumingEventPublisherEventsModule } from './service/AwsSqsConsumingEventPublisherEventsModule'; diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts new file mode 100644 index 0000000000..e32245b483 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts @@ -0,0 +1,227 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + DeleteMessageBatchCommand, + ReceiveMessageCommand, + SQSClient, +} from '@aws-sdk/client-sqs'; +import { getVoidLogger } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { ConfigReader } from '@backstage/config'; +import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils'; +import { mockClient } from 'aws-sdk-client-mock'; +import { AwsSqsConsumingEventPublisher } from './AwsSqsConsumingEventPublisher'; + +describe('AwsSqsConsumingEventPublisher', () => { + it('creates one publisher instance per configured topic', async () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + }, + }, + fake2: { + queue: { + region: 'us-east-1', + url: 'https://fake2.queue.url', + }, + }, + }, + }, + }, + }, + }, + }); + const logger = getVoidLogger(); + const scheduler = { + scheduleTask: jest.fn(), + } as unknown as PluginTaskScheduler; + + const publishers = AwsSqsConsumingEventPublisher.fromConfig({ + config, + logger, + scheduler, + }); + expect(publishers.length).toEqual(2); + }); + + it('polling will be scheduled after connecting to the EventBroker', async () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + }, + }, + }, + }, + }, + }, + }, + }); + const logger = getVoidLogger(); + const scheduler = { + scheduleTask: jest.fn(), + } as unknown as PluginTaskScheduler; + + const publishers = AwsSqsConsumingEventPublisher.fromConfig({ + config, + logger, + scheduler, + }); + expect(publishers.length).toEqual(1); + + const publisher = publishers[0]; + + const eventBroker = new TestEventBroker(); + await publisher.setEventBroker(eventBroker); + + // publisher.connect(..) was causing the polling for events to be scheduled + expect(scheduler.scheduleTask).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'events.awsSqs.publisher:fake1', + frequency: { seconds: 0 }, + timeout: { seconds: 260 }, + scope: 'local', + }), + ); + }); + + it('publishes events for received messages and deletes them in bulk', async () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + }, + waitTimeAfterEmptyReceive: { seconds: 1 }, + }, + }, + }, + }, + }, + }, + }); + const logger = getVoidLogger(); + let taskFn: (() => Promise) | undefined = undefined; + const scheduler = { + scheduleTask: (spec: { fn: () => Promise }) => { + taskFn = spec.fn; + }, + } as unknown as PluginTaskScheduler; + + // on the first attempt, we will return 1 message and 0 messages afterwards + const sqsMock = mockClient(SQSClient); + sqsMock + .on(ReceiveMessageCommand, { + MaxNumberOfMessages: 10, + QueueUrl: 'https://fake1.queue.url', + WaitTimeSeconds: 20, + }) + .resolvesOnce({ + Messages: [], + }) + .resolvesOnce({ + Messages: [ + { + Body: '{"event":"payload1"}', + ReceiptHandle: 'fake-handle1', + MessageAttributes: { + 'X-Custom-Attr': { + DataType: 'String', + StringValue: 'value', + }, + }, + }, + { + Body: '{"event":"payload2"}', + ReceiptHandle: 'fake-handle2', + }, + ], + }) + .on(DeleteMessageBatchCommand, { + Entries: [ + { + Id: 'message-0', + ReceiptHandle: 'fake-handle1', + }, + { + Id: 'message-1', + ReceiptHandle: 'fake-handle2', + }, + ], + QueueUrl: 'https://fake1.queue.url', + }) + .resolvesOnce({ + Failed: [ + { + Id: 'message-1', + Message: 'test failure', + SenderFault: true, + Code: '400', + }, + ], + Successful: [{ Id: 'message-0' }], + }); + + const publishers = AwsSqsConsumingEventPublisher.fromConfig({ + config, + logger, + scheduler, + }); + expect(publishers.length).toEqual(1); + const publisher = publishers[0]; + + const eventBroker = new TestEventBroker(); + await publisher.setEventBroker(eventBroker); + + await taskFn!(); + await taskFn!(); + await taskFn!(); + + expect(eventBroker.published.length).toEqual(2); + expect(eventBroker.published[0].topic).toEqual('fake1'); + expect(eventBroker.published[0].eventPayload).toEqual({ + event: 'payload1', + }); + expect(eventBroker.published[0].metadata).toEqual({ + 'X-Custom-Attr': 'value', + }); + + expect(eventBroker.published[1].topic).toEqual('fake1'); + expect(eventBroker.published[1].eventPayload).toEqual({ + event: 'payload2', + }); + expect(eventBroker.published[1].metadata).toEqual({}); + }); +}); diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts new file mode 100644 index 0000000000..4638769c45 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -0,0 +1,191 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + DeleteMessageBatchCommand, + Message, + ReceiveMessageCommand, + ReceiveMessageCommandInput, + SQSClient, +} from '@aws-sdk/client-sqs'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import { EventBroker, EventPublisher } from '@backstage/plugin-events-node'; +import { Logger } from 'winston'; +import { AwsSqsEventSourceConfig, readConfig } from './config'; + +/** + * Publishes events received from an AWS SQS queue. + * The message payload will be used as event payload and passed to registered subscribers. + * + * @public + */ +// TODO(pjungermann): add prom metrics? (see plugins/catalog-backend/src/util/metrics.ts, etc.) +export class AwsSqsConsumingEventPublisher implements EventPublisher { + private readonly topic: string; + private readonly receiveParams: ReceiveMessageCommandInput; + private readonly sqs: SQSClient; + private readonly queueUrl: string; + private readonly taskTimeoutSeconds: number; + private readonly waitTimeAfterEmptyReceiveMs; + private eventBroker?: EventBroker; + + static fromConfig(env: { + config: Config; + logger: Logger; + scheduler: PluginTaskScheduler; + }): AwsSqsConsumingEventPublisher[] { + return readConfig(env.config).map( + config => + new AwsSqsConsumingEventPublisher(env.logger, env.scheduler, config), + ); + } + + private constructor( + private readonly logger: Logger, + private readonly scheduler: PluginTaskScheduler, + config: AwsSqsEventSourceConfig, + ) { + this.topic = config.topic; + + this.receiveParams = { + MaxNumberOfMessages: 10, + MessageAttributeNames: ['All'], + QueueUrl: config.queueUrl, + VisibilityTimeout: config.visibilityTimeout?.as('seconds'), + WaitTimeSeconds: config.pollingWaitTime.as('seconds'), + }; + this.sqs = new SQSClient({ region: config.region }); + this.queueUrl = config.queueUrl; + + this.taskTimeoutSeconds = config.timeout.as('seconds'); + this.waitTimeAfterEmptyReceiveMs = + config.waitTimeAfterEmptyReceive.as('milliseconds'); + } + + async setEventBroker(eventBroker: EventBroker): Promise { + this.eventBroker = eventBroker; + return this.start(); + } + + private async start(): Promise { + const id = `events.awsSqs.publisher:${this.topic}`; + const logger = this.logger.child({ + class: AwsSqsConsumingEventPublisher.prototype.constructor.name, + taskId: id, + }); + + await this.scheduler.scheduleTask({ + id: id, + frequency: { seconds: 0 }, + timeout: { seconds: this.taskTimeoutSeconds }, + scope: 'local', + fn: async () => { + try { + const numMessages = await this.consumeMessages(); + if (numMessages === 0) { + await this.sleep(this.waitTimeAfterEmptyReceiveMs); + } + } catch (error) { + logger.error(error); + } + }, + }); + } + + private async deleteMessages(messages?: Message[]): Promise { + if (!messages) { + return; + } + + const deleteParams = { + QueueUrl: this.queueUrl, + Entries: messages.map((message, index) => { + return { + Id: message.MessageId ?? `message-${index}`, + ReceiptHandle: message.ReceiptHandle, + }; + }), + }; + + try { + const result = await this.sqs.send( + new DeleteMessageBatchCommand(deleteParams), + ); + if (result.Failed) { + this.logger.error( + `Failed to delete ${result.Failed!.length} of ${ + messages.length + } messages from AWS SQS ${this.queueUrl}. First: ${ + result.Failed[0].Message + }`, + ); + } + } catch (error) { + this.logger.error( + `Failed to delete message from AWS SQS ${this.queueUrl}`, + error, + ); + } + } + + private async consumeMessages(): Promise { + try { + const data = await this.sqs.send( + new ReceiveMessageCommand(this.receiveParams), + ); + + data.Messages?.forEach(message => { + const eventPayload = JSON.parse(message.Body!); + + const metadata: Record = {}; + Object.keys(message.MessageAttributes ?? {}).forEach(key => { + const attrValue = message.MessageAttributes![key]; + if ( + !attrValue || + !attrValue.DataType || + !['String', 'Number'].includes(attrValue.DataType) + ) { + return; + } + + const value = attrValue.StringListValues ?? attrValue.StringValue; + if (value !== undefined) { + metadata[key] = value; + } + }); + + this.eventBroker!.publish({ + topic: this.topic, + eventPayload, + metadata, + }); + }); + await this.deleteMessages(data.Messages); + return data.Messages?.length ?? 0; + } catch (error) { + this.logger.error( + `Failed to receive events from AWS SQS ${this.queueUrl}`, + error, + ); + return 0; + } + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts new file mode 100644 index 0000000000..1fa9df4ce3 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts @@ -0,0 +1,222 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { readConfig } from './config'; + +describe('readConfig', () => { + it('not configured', () => { + const config = new ConfigReader({}); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs.length).toBe(0); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + }, + }, + fake2: { + queue: { + region: 'us-east-1', + url: 'https://fake2.queue.url', + }, + }, + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs.length).toBe(2); + + expect(publisherConfigs[0].topic).toEqual('fake1'); + expect(publisherConfigs[0].region).toEqual('eu-west-1'); + expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url'); + expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(20); + expect(publisherConfigs[0].timeout.as('seconds')).toBe(260); + expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe( + 60, + ); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + visibilityTimeout: { minutes: 5 }, + waitTime: { seconds: 10 }, + }, + timeout: { minutes: 5 }, + waitTimeAfterEmptyReceive: { seconds: 30 }, + }, + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConfig(config); + + expect(publisherConfigs.length).toBe(1); + + expect(publisherConfigs[0].topic).toEqual('fake1'); + expect(publisherConfigs[0].region).toEqual('eu-west-1'); + expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url'); + expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(10); + expect(publisherConfigs[0].timeout.as('seconds')).toBe(300); + expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe( + 30, + ); + }); + + it('fail on negative queue.waitTime', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + visibilityTimeout: { minutes: 5 }, + waitTime: { seconds: -10 }, + }, + timeout: { minutes: 5 }, + waitTimeAfterEmptyReceive: { seconds: 30 }, + }, + }, + }, + }, + }, + }, + }); + + expect(() => readConfig(config)).toThrow( + 'events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime must be within 0..20 seconds', + ); + }); + + it('fail on too high queue.waitTime', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + visibilityTimeout: { minutes: 5 }, + waitTime: { seconds: 30 }, + }, + timeout: { minutes: 5 }, + waitTimeAfterEmptyReceive: { seconds: 30 }, + }, + }, + }, + }, + }, + }, + }); + + expect(() => readConfig(config)).toThrow( + 'events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime must be within 0..20 seconds', + ); + }); + + it('fail on too low timeout', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + visibilityTimeout: { minutes: 5 }, + waitTime: { seconds: 10 }, + }, + timeout: { seconds: 10 }, + waitTimeAfterEmptyReceive: { seconds: 30 }, + }, + }, + }, + }, + }, + }, + }); + + expect(() => readConfig(config)).toThrow( + 'The events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.timeout must be greater than events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.queue.waitTime', + ); + }); + + it('fail on negative waitTimeAfterEmptyReceive', () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + visibilityTimeout: { minutes: 5 }, + waitTime: { seconds: 10 }, + }, + timeout: { minutes: 5 }, + waitTimeAfterEmptyReceive: { seconds: -30 }, + }, + }, + }, + }, + }, + }, + }); + + expect(() => readConfig(config)).toThrow( + 'The events.modules.awsSqs.awsSqsConsumingEventPublisher.topics.fake1.waitTimeAfterEmptyReceive must not be negative', + ); + }); +}); diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts new file mode 100644 index 0000000000..c6b74ea8d3 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { HumanDuration, JsonObject } from '@backstage/types'; +import { Duration } from 'luxon'; + +const CONFIG_PREFIX_MODULE = 'events.modules.awsSqs.'; +const CONFIG_PREFIX_PUBLISHER = `${CONFIG_PREFIX_MODULE}awsSqsConsumingEventPublisher.`; +const DEFAULT_WAIT_TIME_AFTER_EMPTY_RECEIVE = { minutes: 1 }; +const MAX_WAIT_SECONDS = 20; + +export interface AwsSqsEventSourceConfig { + pollingWaitTime: Duration; + queueUrl: string; + region: string; + timeout: Duration; + topic: string; + visibilityTimeout?: Duration; + waitTimeAfterEmptyReceive: Duration; +} + +// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks +function readOptionalHumanDuration( + config: Config, + key: string, +): HumanDuration | undefined { + return config.getOptional(key) as HumanDuration; +} + +function readOptionalDuration( + config: Config, + key: string, +): Duration | undefined { + const duration = readOptionalHumanDuration(config, key); + return duration ? Duration.fromObject(duration) : undefined; +} + +export function readConfig(config: Config): AwsSqsEventSourceConfig[] { + const key = `${CONFIG_PREFIX_PUBLISHER}topics`; + const topics = config.getOptionalConfig(key); + + return ( + topics?.keys()?.map(topic => { + const topicConfig = topics.getConfig(topic); + const keyPrefix = `${key}.${topic}.`; + + // queue config: + const pollingWaitTime = Duration.fromObject( + readOptionalHumanDuration(topicConfig, 'queue.waitTime') ?? { + seconds: MAX_WAIT_SECONDS, + }, + ); + if ( + pollingWaitTime.valueOf() < 0 || + pollingWaitTime.as('seconds') > MAX_WAIT_SECONDS + ) { + throw new Error( + `${keyPrefix}queue.waitTime must be within 0..${MAX_WAIT_SECONDS} seconds.`, + ); + } + const queueUrl = topicConfig.getString('queue.url'); + const region = topicConfig.getString('queue.region'); + const visibilityTimeout = readOptionalDuration( + topicConfig, + 'queue.visibilityTimeout', + ); + + // task: + const waitTimeAfterEmptyReceive = Duration.fromObject( + readOptionalHumanDuration(topicConfig, 'waitTimeAfterEmptyReceive') ?? + DEFAULT_WAIT_TIME_AFTER_EMPTY_RECEIVE, + ); + if (waitTimeAfterEmptyReceive.valueOf() < 0) { + throw new Error( + `The ${keyPrefix}waitTimeAfterEmptyReceive must not be negative.`, + ); + } + const timeout = + readOptionalDuration(topicConfig, 'timeout') ?? + pollingWaitTime + .plus(waitTimeAfterEmptyReceive) + .plus(Duration.fromObject({ seconds: 180 })); + if ( + timeout.valueOf() <= + pollingWaitTime.valueOf() + waitTimeAfterEmptyReceive.valueOf() + ) { + throw new Error( + `The ${keyPrefix}timeout must be greater than ${keyPrefix}queue.waitTime + ${keyPrefix}waitTimeAfterEmptyReceive.`, + ); + } + + return { + pollingWaitTime, + queueUrl, + region, + timeout, + topic, + visibilityTimeout, + waitTimeAfterEmptyReceive, + }; + }) ?? [] + ); +} diff --git a/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.test.ts b/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.test.ts new file mode 100644 index 0000000000..584b62a2c6 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { eventsExtensionPoint } from '@backstage/plugin-events-node'; +import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils'; +import { awsSqsConsumingEventPublisherEventsModule } from './AwsSqsConsumingEventPublisherEventsModule'; +import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEventPublisher'; + +describe('awsSqsEventsModule', () => { + it('should be correctly wired and set up', async () => { + const config = new ConfigReader({ + events: { + modules: { + awsSqs: { + awsSqsConsumingEventPublisher: { + topics: { + fake1: { + queue: { + region: 'eu-west-1', + url: 'https://fake1.queue.url', + }, + }, + fake2: { + queue: { + region: 'us-east-1', + url: 'https://fake2.queue.url', + }, + }, + }, + }, + }, + }, + }, + }); + + let addedPublishers: AwsSqsConsumingEventPublisher[] | undefined; + const extensionPoint = { + addPublishers: (publishers: any) => { + addedPublishers = publishers; + }, + }; + + const scheduler = { + scheduleTask: jest.fn(), + }; + + await startTestBackend({ + extensionPoints: [[eventsExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [awsSqsConsumingEventPublisherEventsModule()], + }); + + expect(addedPublishers).not.toBeUndefined(); + expect(addedPublishers!.length).toEqual(2); + + const eventBroker = new TestEventBroker(); + await Promise.all( + addedPublishers!.map(publisher => publisher.setEventBroker(eventBroker)), + ); + + // publisher.connect(..) was causing the polling for events to be scheduled + expect(scheduler.scheduleTask).toHaveBeenCalledWith( + expect.objectContaining({ id: 'events.awsSqs.publisher:fake1' }), + ); + expect(scheduler.scheduleTask).toHaveBeenCalledWith( + expect.objectContaining({ id: 'events.awsSqs.publisher:fake2' }), + ); + }); +}); diff --git a/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.ts b/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.ts new file mode 100644 index 0000000000..7c33d7baae --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/service/AwsSqsConsumingEventPublisherEventsModule.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { eventsExtensionPoint } from '@backstage/plugin-events-node'; +import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEventPublisher'; + +/** + * AWS SQS module for the Events plugin. + * + * @alpha + */ +export const awsSqsConsumingEventPublisherEventsModule = createBackendModule({ + pluginId: 'events', + moduleId: 'awsSqsConsumingEventPublisherEventsModule', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + events: eventsExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, events, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const sqs = AwsSqsConsumingEventPublisher.fromConfig({ + config: config, + logger: winstonLogger, + scheduler: scheduler, + }); + + events.addPublishers(sqs); + }, + }); + }, +}); diff --git a/plugins/events-backend-module-aws-sqs/src/setupTests.ts b/plugins/events-backend-module-aws-sqs/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/yarn.lock b/yarn.lock index 535a0c483e..97ca3c01a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -330,6 +330,790 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/ie11-detection@npm:^2.0.0": + version: 2.0.2 + resolution: "@aws-crypto/ie11-detection@npm:2.0.2" + dependencies: + tslib: ^1.11.1 + checksum: 713293deea8eefd3ab43dc05e62228571d27754e7293f8ec2fd8a0c693fbbfc55213e6599387776e3cdbc951965dc62e24e92b9c4a853e4a50d00ae6a9f6b2bd + languageName: node + linkType: hard + +"@aws-crypto/sha256-browser@npm:2.0.0": + version: 2.0.0 + resolution: "@aws-crypto/sha256-browser@npm:2.0.0" + dependencies: + "@aws-crypto/ie11-detection": ^2.0.0 + "@aws-crypto/sha256-js": ^2.0.0 + "@aws-crypto/supports-web-crypto": ^2.0.0 + "@aws-crypto/util": ^2.0.0 + "@aws-sdk/types": ^3.1.0 + "@aws-sdk/util-locate-window": ^3.0.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: 7bc1ff042d0c53a46c0fc3824bd97fb3ed1df7dc030b8a995889471052860b8c8ade469c97866fafd8249a3144d0f48b0f1054f357e2b403606009381c4b8f0e + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:2.0.0": + version: 2.0.0 + resolution: "@aws-crypto/sha256-js@npm:2.0.0" + dependencies: + "@aws-crypto/util": ^2.0.0 + "@aws-sdk/types": ^3.1.0 + tslib: ^1.11.1 + checksum: e4abf9baec6bed19d380f92a999a41ac5bdd8890dfd45971d29054c298854c5b7087e7de633413f2e64618ef8238ccf4c0b75797c73063c74bbba3cb5d8b2581 + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:^2.0.0": + version: 2.0.2 + resolution: "@aws-crypto/sha256-js@npm:2.0.2" + dependencies: + "@aws-crypto/util": ^2.0.2 + "@aws-sdk/types": ^3.110.0 + tslib: ^1.11.1 + checksum: 9125ec65a2b05fce908ac2289ba97b995a299f2d717684804211df8e8bcffd8cd9b8861582240655b88f2255c46fcee34026f75c057ffb22f44b6a76cd43f65a + languageName: node + linkType: hard + +"@aws-crypto/supports-web-crypto@npm:^2.0.0": + version: 2.0.2 + resolution: "@aws-crypto/supports-web-crypto@npm:2.0.2" + dependencies: + tslib: ^1.11.1 + checksum: 03d04d29292dc1b76db9bc6becd05f52fa79adee0ec084f971b0767f7e73250dd0422bea57636015f8c27f38aefcd1d9c58800a4749cf35339296c8d670f3ccb + languageName: node + linkType: hard + +"@aws-crypto/util@npm:^2.0.0, @aws-crypto/util@npm:^2.0.2": + version: 2.0.2 + resolution: "@aws-crypto/util@npm:2.0.2" + dependencies: + "@aws-sdk/types": ^3.110.0 + "@aws-sdk/util-utf8-browser": ^3.0.0 + tslib: ^1.11.1 + checksum: 13cb33a39005b09c062398d361043c2224bc8ba42b1432bad52e15bc4bf9ffad4facdddc394b3cc71b3fb8d86a7ec325fd1afa107b5fde0dab84a7e32d311d7f + languageName: node + linkType: hard + +"@aws-sdk/abort-controller@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/abort-controller@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 28f5bc16b5aba414e354df603bc8becd010aa8c9b3c79e527130723a39f21d5a3df98c3bafb3cea733d415a5b042af90098ffa9dfa29d400114c2f6f5292524e + languageName: node + linkType: hard + +"@aws-sdk/client-sqs@npm:^3.0.0": + version: 3.183.0 + resolution: "@aws-sdk/client-sqs@npm:3.183.0" + dependencies: + "@aws-crypto/sha256-browser": 2.0.0 + "@aws-crypto/sha256-js": 2.0.0 + "@aws-sdk/client-sts": 3.183.0 + "@aws-sdk/config-resolver": 3.183.0 + "@aws-sdk/credential-provider-node": 3.183.0 + "@aws-sdk/fetch-http-handler": 3.183.0 + "@aws-sdk/hash-node": 3.183.0 + "@aws-sdk/invalid-dependency": 3.183.0 + "@aws-sdk/md5-js": 3.183.0 + "@aws-sdk/middleware-content-length": 3.183.0 + "@aws-sdk/middleware-host-header": 3.183.0 + "@aws-sdk/middleware-logger": 3.183.0 + "@aws-sdk/middleware-recursion-detection": 3.183.0 + "@aws-sdk/middleware-retry": 3.183.0 + "@aws-sdk/middleware-sdk-sqs": 3.183.0 + "@aws-sdk/middleware-serde": 3.183.0 + "@aws-sdk/middleware-signing": 3.183.0 + "@aws-sdk/middleware-stack": 3.183.0 + "@aws-sdk/middleware-user-agent": 3.183.0 + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/node-http-handler": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/smithy-client": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/url-parser": 3.183.0 + "@aws-sdk/util-base64-browser": 3.183.0 + "@aws-sdk/util-base64-node": 3.183.0 + "@aws-sdk/util-body-length-browser": 3.183.0 + "@aws-sdk/util-body-length-node": 3.183.0 + "@aws-sdk/util-defaults-mode-browser": 3.183.0 + "@aws-sdk/util-defaults-mode-node": 3.183.0 + "@aws-sdk/util-user-agent-browser": 3.183.0 + "@aws-sdk/util-user-agent-node": 3.183.0 + "@aws-sdk/util-utf8-browser": 3.183.0 + "@aws-sdk/util-utf8-node": 3.183.0 + entities: 2.2.0 + fast-xml-parser: 3.19.0 + tslib: ^2.3.1 + checksum: fe0a7ba53cac4d52c25a4a4f1a1bb16438d5011347fcfec037df1d73379d750cd47b437f7a45aea2d4e29bc92587b51dd7631a06f5d6881e3b67b2d4c0dd2633 + languageName: node + linkType: hard + +"@aws-sdk/client-sso@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/client-sso@npm:3.183.0" + dependencies: + "@aws-crypto/sha256-browser": 2.0.0 + "@aws-crypto/sha256-js": 2.0.0 + "@aws-sdk/config-resolver": 3.183.0 + "@aws-sdk/fetch-http-handler": 3.183.0 + "@aws-sdk/hash-node": 3.183.0 + "@aws-sdk/invalid-dependency": 3.183.0 + "@aws-sdk/middleware-content-length": 3.183.0 + "@aws-sdk/middleware-host-header": 3.183.0 + "@aws-sdk/middleware-logger": 3.183.0 + "@aws-sdk/middleware-recursion-detection": 3.183.0 + "@aws-sdk/middleware-retry": 3.183.0 + "@aws-sdk/middleware-serde": 3.183.0 + "@aws-sdk/middleware-stack": 3.183.0 + "@aws-sdk/middleware-user-agent": 3.183.0 + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/node-http-handler": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/smithy-client": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/url-parser": 3.183.0 + "@aws-sdk/util-base64-browser": 3.183.0 + "@aws-sdk/util-base64-node": 3.183.0 + "@aws-sdk/util-body-length-browser": 3.183.0 + "@aws-sdk/util-body-length-node": 3.183.0 + "@aws-sdk/util-defaults-mode-browser": 3.183.0 + "@aws-sdk/util-defaults-mode-node": 3.183.0 + "@aws-sdk/util-user-agent-browser": 3.183.0 + "@aws-sdk/util-user-agent-node": 3.183.0 + "@aws-sdk/util-utf8-browser": 3.183.0 + "@aws-sdk/util-utf8-node": 3.183.0 + tslib: ^2.3.1 + checksum: 6909329cf87c1a0c830fa9657e04c7e1ae496d7d79cc0a6813999503ae809f245f0b879af1d5564a5d97c2c69583c8a4df702d29ec7dc75934d98a0cdf017ad3 + languageName: node + linkType: hard + +"@aws-sdk/client-sts@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/client-sts@npm:3.183.0" + dependencies: + "@aws-crypto/sha256-browser": 2.0.0 + "@aws-crypto/sha256-js": 2.0.0 + "@aws-sdk/config-resolver": 3.183.0 + "@aws-sdk/credential-provider-node": 3.183.0 + "@aws-sdk/fetch-http-handler": 3.183.0 + "@aws-sdk/hash-node": 3.183.0 + "@aws-sdk/invalid-dependency": 3.183.0 + "@aws-sdk/middleware-content-length": 3.183.0 + "@aws-sdk/middleware-host-header": 3.183.0 + "@aws-sdk/middleware-logger": 3.183.0 + "@aws-sdk/middleware-recursion-detection": 3.183.0 + "@aws-sdk/middleware-retry": 3.183.0 + "@aws-sdk/middleware-sdk-sts": 3.183.0 + "@aws-sdk/middleware-serde": 3.183.0 + "@aws-sdk/middleware-signing": 3.183.0 + "@aws-sdk/middleware-stack": 3.183.0 + "@aws-sdk/middleware-user-agent": 3.183.0 + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/node-http-handler": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/smithy-client": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/url-parser": 3.183.0 + "@aws-sdk/util-base64-browser": 3.183.0 + "@aws-sdk/util-base64-node": 3.183.0 + "@aws-sdk/util-body-length-browser": 3.183.0 + "@aws-sdk/util-body-length-node": 3.183.0 + "@aws-sdk/util-defaults-mode-browser": 3.183.0 + "@aws-sdk/util-defaults-mode-node": 3.183.0 + "@aws-sdk/util-user-agent-browser": 3.183.0 + "@aws-sdk/util-user-agent-node": 3.183.0 + "@aws-sdk/util-utf8-browser": 3.183.0 + "@aws-sdk/util-utf8-node": 3.183.0 + entities: 2.2.0 + fast-xml-parser: 3.19.0 + tslib: ^2.3.1 + checksum: d4492e537803d64e5fc0c5db8ac3a3788204aadc14b21e4fb407188feb58b35689c4a6878b4f13123a5f35f7e9fa01d95d427be168aeec9072aea881f2a92fb7 + languageName: node + linkType: hard + +"@aws-sdk/config-resolver@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/config-resolver@npm:3.183.0" + dependencies: + "@aws-sdk/signature-v4": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-config-provider": 3.183.0 + "@aws-sdk/util-middleware": 3.183.0 + tslib: ^2.3.1 + checksum: 1be0ab79d5019f7502a7aee8c740f4e381ab7501ed639569526a4a7e1c70ad6bb36d642a8a2b18afe58c34497c6f2f8244dbf05211a0f6bc9a57e5e379c70b2b + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-env@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 4281e14c286cc5c57c69ba88c49fe1c1ff61f27923db5e8f852a84cce02b40fdc938f876ad08e72005deae9a28fab7400f747f63ca50f9c4d60e04e6c2705205 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-imds@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-imds@npm:3.183.0" + dependencies: + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/url-parser": 3.183.0 + tslib: ^2.3.1 + checksum: e9fe1007b22825dc0c033beb2057b9f759d8cafec6e65d08ba4915bccb9b7d737eeccb3d06e9768a084b6726d9ea10778172b1bec695353c60276c1a5c21370e + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-ini@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.183.0" + dependencies: + "@aws-sdk/credential-provider-env": 3.183.0 + "@aws-sdk/credential-provider-imds": 3.183.0 + "@aws-sdk/credential-provider-sso": 3.183.0 + "@aws-sdk/credential-provider-web-identity": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/shared-ini-file-loader": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: c7784a3674c863dacc1305a08dd63cb16d1e7374250b0ceafa9feee8344de4228d7c32e84fbaa9d963adab69eb3938cd75b99554756da120cd9beae4693eded9 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.183.0" + dependencies: + "@aws-sdk/credential-provider-env": 3.183.0 + "@aws-sdk/credential-provider-imds": 3.183.0 + "@aws-sdk/credential-provider-ini": 3.183.0 + "@aws-sdk/credential-provider-process": 3.183.0 + "@aws-sdk/credential-provider-sso": 3.183.0 + "@aws-sdk/credential-provider-web-identity": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/shared-ini-file-loader": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: f23e1148054a96d68632284501ba01c4285cce1a2f4562817ec4b7fa315ff56e2f6b97fae0d2ae26fd52fca8c9f000be5d72a3582cff6c32ba10010f02a30977 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-process@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/shared-ini-file-loader": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 3109e79ec2b4647f5e7f78c6dc2e10cd8a34a70147e7e90549153b7ff3ff12eda3499b3189d6fc1f3b99f461d5445e8017b44cd50029d30b0fc7437ad88a5f38 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-sso@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.183.0" + dependencies: + "@aws-sdk/client-sso": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/shared-ini-file-loader": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 8775894ef50499361a4b49d846417c2c1e1ea90383730a32428154628358e7847f6f29dddd3cd04d3a4e15c1343fe10b051a92863055989ac3c4cb241e68f63a + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-web-identity@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: c39576c8f35137284fc69db8d94adcf07fa89d51153ee9710481262e205d485ff089a796e8a253aa14a0434dde91eadb65602fc614167a4f14301201ee840c08 + languageName: node + linkType: hard + +"@aws-sdk/fetch-http-handler@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/fetch-http-handler@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/querystring-builder": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-base64-browser": 3.183.0 + tslib: ^2.3.1 + checksum: 87c51d1dad8c469e32b99f6ed3729e753097cd4a9f8f417ec8681cf01e18b9a5dc6571f3cd1220293216bd4527b8499bb2ea1f821a240fde6d9560817c120066 + languageName: node + linkType: hard + +"@aws-sdk/hash-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/hash-node@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-buffer-from": 3.183.0 + tslib: ^2.3.1 + checksum: fbed8757df633620807594535e3bb7d387576e0c2f89ad16dc7c98405a9d1d9743bea50148186de2f19cac67144b30f7f89e6c15317ddd7b8c0c8f61ca9556ef + languageName: node + linkType: hard + +"@aws-sdk/invalid-dependency@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/invalid-dependency@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: d928ca26d4390ad4f5e2c8198d16e14b675face6ad8c4a4eef699b54ce3f6cba584456659287b0e57e9f9019bc456b51dfa474671e11bf555fe04ed16174c10c + languageName: node + linkType: hard + +"@aws-sdk/is-array-buffer@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/is-array-buffer@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 717e508989821434a2ace4f690fc599f67b3f28ee9de5f06fce65a296fe11a68a5b97f30d66bfbf490d4a83259e4ffa1f0321231e4a54ce2440a9c6fdbacde24 + languageName: node + linkType: hard + +"@aws-sdk/md5-js@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/md5-js@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-utf8-browser": 3.183.0 + "@aws-sdk/util-utf8-node": 3.183.0 + tslib: ^2.3.1 + checksum: 5bd92465940ace6b3ca13342bd60eab42ffd5d74dfa65655c28e10bbf4aa0c32b2f9b4deb2050f3d092b8586e558bf248559af9c3cffb37810961169ad0797af + languageName: node + linkType: hard + +"@aws-sdk/middleware-content-length@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-content-length@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: bf3ec6872a925dbafd91e57334b6264e8446ca59eba598ee4bc7b37113c466300ac6b77ab314c4aa2859221f1f4e98beabbd365346e0815d591f276e642d1849 + languageName: node + linkType: hard + +"@aws-sdk/middleware-host-header@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: b4c4886976cffec614ff368daa995a69c765e737c8d366daa0033d781cf8e7483565bb3627ecd752698edc23dcf578de6657c9e8cb41c0fcd92631d17b7749b8 + languageName: node + linkType: hard + +"@aws-sdk/middleware-logger@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-logger@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 10ffb6af0f81b0144fef91af2bbeaeb680df2beff838f2a6ebcc260971fd6252a166e3bf3501c1fc00f6f8f203117ff6e2b4baebb7a6d8cd8504b7e1f392bd6d + languageName: node + linkType: hard + +"@aws-sdk/middleware-recursion-detection@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 461117e0dc8c38750942035a07163817bc8e89f377211f5fb52d4314acae4e7e6cb49be04193c508112833535b33814c1609196cb65e38cff1122043398ad89e + languageName: node + linkType: hard + +"@aws-sdk/middleware-retry@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-retry@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/service-error-classification": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-middleware": 3.183.0 + tslib: ^2.3.1 + uuid: ^8.3.2 + checksum: 76fa3f9fe5f1c67bbfa792611289edf0afa2d0271073039931ac9ea46b3f432d74692d2db55443f40b7e97014898454f6d4bd8d2f2f5714a4281428b7464efac + languageName: node + linkType: hard + +"@aws-sdk/middleware-sdk-sqs@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-sdk-sqs@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-hex-encoding": 3.183.0 + tslib: ^2.3.1 + checksum: 0bb914b2e7a5391f5b22a5262a5c299fa7e3eb22076d00c4cfffb449e97404b4830a77bb742e8f3110d66f15ae9aad81ea740238799cc35bb8d328eaf13f3fe2 + languageName: node + linkType: hard + +"@aws-sdk/middleware-sdk-sts@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-sdk-sts@npm:3.183.0" + dependencies: + "@aws-sdk/middleware-signing": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/signature-v4": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 1969d6128d24cc7d8ff199c2900d805281928668f4dc3ffd128065ccfe515f9f7467a265af276592009bd285847cb9298f8ba4b1dfbe33971c3e7de80986b422 + languageName: node + linkType: hard + +"@aws-sdk/middleware-serde@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-serde@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 7495bb2adceab2c686588697fd9dd50a859706a8d33d98d9907ffc8f9ef78af141f2654cdac8d7b28b1f0544dcf2e52f25d109f5da1348102234ab5b4e79b256 + languageName: node + linkType: hard + +"@aws-sdk/middleware-signing@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-signing@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/signature-v4": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-middleware": 3.183.0 + tslib: ^2.3.1 + checksum: 8025499acfefc072511adbc5084a1ed3cfe9cd9e514c58045425c87538af72ad3adfc134f7d0a059ff52832da392b4679b62d4ad28e477b866a20e78370f0758 + languageName: node + linkType: hard + +"@aws-sdk/middleware-stack@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-stack@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 03136847b96b0e4baba9b64f53a6c6b5738b1fa2c645edb3f47c1f85082791549b423ea791af33e9da3507cd56fbcbd052f1ba773a9f45ea42a3647277e85264 + languageName: node + linkType: hard + +"@aws-sdk/middleware-user-agent@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.183.0" + dependencies: + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 8d1001d6d30f0f56aa440c541f6dcee9aad9190837e7a9653aef35448ff13e644cb7a441373192134ac86b5741fa64a120add07202cb97674018bd44612acdf7 + languageName: node + linkType: hard + +"@aws-sdk/node-config-provider@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/node-config-provider@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/shared-ini-file-loader": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 673fb105de8d11ddd1c3e636c7ed01ef4fbe412a7d96543d9db69149dfad64d33adf5b96b106763c058190841ea497f220e1b75ca438dbda7b34729f00b9e823 + languageName: node + linkType: hard + +"@aws-sdk/node-http-handler@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/node-http-handler@npm:3.183.0" + dependencies: + "@aws-sdk/abort-controller": 3.183.0 + "@aws-sdk/protocol-http": 3.183.0 + "@aws-sdk/querystring-builder": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: a24ee8cc4e3b63fdec0ebb671caa1e634af488befa7c93a28bb733bb532934c221f6c47707db20264af91ca84336a50b98427b425329b89aeac2a6d2be9537ff + languageName: node + linkType: hard + +"@aws-sdk/property-provider@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/property-provider@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 6e8616e5f7efa9f46bdf62118dfc586e61dcdf0bc99f82305759020ce3d25fc1d2db71c62cfd4417a757a138b1a539cf305b584e6e7ade9bce9975fbe66fd326 + languageName: node + linkType: hard + +"@aws-sdk/protocol-http@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/protocol-http@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 13b255bcb37b70853d93374f972d743c80f33ac16188c41b512536c2dbec4d7beb55226bfac6d809981d63bbc87f6bf345334238f187da2faa9feaef5001e0a7 + languageName: node + linkType: hard + +"@aws-sdk/querystring-builder@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/querystring-builder@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-uri-escape": 3.183.0 + tslib: ^2.3.1 + checksum: a3de19863ccee4e8c9d5bbd719688ee1ffea02c3d90aad5e9c3eb04bed942ff8e5be0bfbc21ed03a968481abe2a2b1093e93b986b4111c468a9f3d3466b61f83 + languageName: node + linkType: hard + +"@aws-sdk/querystring-parser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/querystring-parser@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: f05a03c46317c97f1b775f7c5be19365cf464396edc6b23c93efd5289ff1a8658883f7c9dde2cc7b74bb42b57f257bc212abae5d62100d23cdec5409fbffe468 + languageName: node + linkType: hard + +"@aws-sdk/service-error-classification@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/service-error-classification@npm:3.183.0" + checksum: 8c015163aee9ec4d789daa8605e5b9424dfda81d3f609a2c34d800cb36ee78f4a3af02d1738c7e75f9a62cd1ef8ec4307f77457eecba3de4c6565a981484bca4 + languageName: node + linkType: hard + +"@aws-sdk/shared-ini-file-loader@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/shared-ini-file-loader@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 317fd0d8012559754f82b88fb386d2a01c19be1d3a0b6da3a6436f71701ed8cd8fbe3f5aa869fd7bbfb8dfb148cd9de02aa68e93001200c76ba9e0691e135773 + languageName: node + linkType: hard + +"@aws-sdk/signature-v4@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/signature-v4@npm:3.183.0" + dependencies: + "@aws-sdk/is-array-buffer": 3.183.0 + "@aws-sdk/types": 3.183.0 + "@aws-sdk/util-hex-encoding": 3.183.0 + "@aws-sdk/util-middleware": 3.183.0 + "@aws-sdk/util-uri-escape": 3.183.0 + tslib: ^2.3.1 + checksum: 6639a4925b194171907fc1cf61101470836f5a8fa363f3fcd7b70bbaa4c756f43e13ad93b575885218a68192c375295d359a7e91db370816bd8f39f9c158ba36 + languageName: node + linkType: hard + +"@aws-sdk/smithy-client@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/smithy-client@npm:3.183.0" + dependencies: + "@aws-sdk/middleware-stack": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 70866adfd91fb35721bcd8667d9a4638e306de6ddc22be37a5b3ea19dfc2a1404a7325992f7f56d635a3bfd3b0ffa159d32f17df35c47ada4724ad8d5bb8d1ce + languageName: node + linkType: hard + +"@aws-sdk/types@npm:3.183.0, @aws-sdk/types@npm:^3.1.0, @aws-sdk/types@npm:^3.110.0": + version: 3.183.0 + resolution: "@aws-sdk/types@npm:3.183.0" + checksum: ab6e888ef8f6d5f5c047dbe0899ac94ad4aa83b9ae1b1fd7a3b88eacec61ab912f215fda65c71f783418d569d1088641806f6b98c9a28ddae04cc70d0eeccea7 + languageName: node + linkType: hard + +"@aws-sdk/url-parser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/url-parser@npm:3.183.0" + dependencies: + "@aws-sdk/querystring-parser": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 54c99d47e927368db12b6aa1fe4331a27461502355e0c18e99d8e05d9a114529d0b15965ff885cae8e19e49692ad44bf3c323ffdc535d7129deac900f286d1d5 + languageName: node + linkType: hard + +"@aws-sdk/util-base64-browser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-base64-browser@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 2c3a4dc7e285b146285ac23de155de5e885d96aa46b29f9c830b6f12541032c6a42be7fd412ec019ddb83e6f155f561d04065b99159515d26d28c0968965dcf2 + languageName: node + linkType: hard + +"@aws-sdk/util-base64-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-base64-node@npm:3.183.0" + dependencies: + "@aws-sdk/util-buffer-from": 3.183.0 + tslib: ^2.3.1 + checksum: d8d0330aa768f816411a7b2343c500728501775d2f8d7c121d52d8484cbdd938102f3049e69e28574c838416ead0cb742494140609287b8d462bfb041218600d + languageName: node + linkType: hard + +"@aws-sdk/util-body-length-browser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-body-length-browser@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 294074e20e4eb46ea992fc9d50fac0b97875d1d09efa706d5298e34f4200226c90bb33bbe96c41c7e91a43fbe44e6cfc0654d09fcdb823fe579545ada65701d9 + languageName: node + linkType: hard + +"@aws-sdk/util-body-length-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-body-length-node@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 60697959c63731d6b4b334bed91d08c4e38e09c0ad95000509641370249c76c5df1ffa0a7cab7418d250a526f49e660f0c67cea28547e442ac47feb7623413b8 + languageName: node + linkType: hard + +"@aws-sdk/util-buffer-from@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-buffer-from@npm:3.183.0" + dependencies: + "@aws-sdk/is-array-buffer": 3.183.0 + tslib: ^2.3.1 + checksum: 5f16145cf68b0756f0930990cfba57ac343c83c6e9dc48bc8d4f488cd66548a9e186e702fa94ba33b69d852c2d019eedf4116ac676cf0dfb701081e39e0776dd + languageName: node + linkType: hard + +"@aws-sdk/util-config-provider@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-config-provider@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 8af6bf5ba31ec0a2fcf994f0a13bde5ff948bcdba02d153cdeea2105ea5eaf934368456bc92f35bfa42a7475cd4d2d2b9a644dfb8b4c2670ae2f103c045dc8b4 + languageName: node + linkType: hard + +"@aws-sdk/util-defaults-mode-browser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-defaults-mode-browser@npm:3.183.0" + dependencies: + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + bowser: ^2.11.0 + tslib: ^2.3.1 + checksum: 8ff54acf1684dd9d82ca13a522c5d2d7ee305511a2748f6e8ef7dc43e668798269e51455ac81b905a5b000a704fd5536f178c84425b8c431500e6a8dc1418135 + languageName: node + linkType: hard + +"@aws-sdk/util-defaults-mode-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-defaults-mode-node@npm:3.183.0" + dependencies: + "@aws-sdk/config-resolver": 3.183.0 + "@aws-sdk/credential-provider-imds": 3.183.0 + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/property-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + checksum: 40665d251844c4add23e358d9171add7180e0f1fcd4bd99601f4bdd03daa2259fed2567fa200152e14c4d7b1acc1126997f5b4e74c569ee1032f9fee16e6b582 + languageName: node + linkType: hard + +"@aws-sdk/util-hex-encoding@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-hex-encoding@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 1bf9577d96d01c13a3118e5d767c1e16b1f4f1c574168d78e128e0342edac30c87776340e9e14deb1e9adf539a6b2f953338adabc097fb143e870889698a6308 + languageName: node + linkType: hard + +"@aws-sdk/util-locate-window@npm:^3.0.0": + version: 3.183.0 + resolution: "@aws-sdk/util-locate-window@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: c29899b440c148aa44a5d9322e06e4cf21644e47bee10555cf8db104d9d76b3985fa4da6e8ca92c137b0b739f7504a7d106e9da49e1d189b84f51f45c21b2204 + languageName: node + linkType: hard + +"@aws-sdk/util-middleware@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-middleware@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: e1a433aebae5e74b365ecb4a57e3c583e7b89697f24b0c9edde93b8ba358e92256550329efb7c6924c0a5037e1bad2b7a1c8ed1b6d02e1aaaa35bca502923752 + languageName: node + linkType: hard + +"@aws-sdk/util-uri-escape@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-uri-escape@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: 9d5f30821cdad15ed6912f7406f83b5b5a45358b208a9374ea944313d9853da8f4ec9881aefa5d6fe78caeaefbb0d103c8485a32f7c25e8b2cae193ea2233bb8 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-browser@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.183.0" + dependencies: + "@aws-sdk/types": 3.183.0 + bowser: ^2.11.0 + tslib: ^2.3.1 + checksum: 21349881a4069a09fb0f8c0f5a6d69e6659255008861817c5d9dada61bf032f0bd7d5735f01dbddbc310d228ea843fbec6ada9f83a4197909efd6ebadb364f6b + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.183.0" + dependencies: + "@aws-sdk/node-config-provider": 3.183.0 + "@aws-sdk/types": 3.183.0 + tslib: ^2.3.1 + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 727d46842053f8f19789fee1550e235adff399559ef6c9b8dd0fe9edd7ac629a9b5c5849377553bfff37deba1ba9889d0c313f059f27cfc40a33895990c1f894 + languageName: node + linkType: hard + +"@aws-sdk/util-utf8-browser@npm:3.183.0, @aws-sdk/util-utf8-browser@npm:^3.0.0": + version: 3.183.0 + resolution: "@aws-sdk/util-utf8-browser@npm:3.183.0" + dependencies: + tslib: ^2.3.1 + checksum: f1c9cbafc3dcf5b04264c3708509117d5e6f6b373fb30712a0cd9d09966a2cdead0a91067a8e9513abac9b082971fd40519e421a2e046b3653ce1ede136197c2 + languageName: node + linkType: hard + +"@aws-sdk/util-utf8-node@npm:3.183.0": + version: 3.183.0 + resolution: "@aws-sdk/util-utf8-node@npm:3.183.0" + dependencies: + "@aws-sdk/util-buffer-from": 3.183.0 + tslib: ^2.3.1 + checksum: c37f2bd388783c33c981afcd5a6a80481cc4e019f42bc5ab10ba2dfb039a4684acb4fa30df579cb3a5b509f61b1261870b30cd966c179029adb2a42672c0a871 + languageName: node + linkType: hard + "@azure/abort-controller@npm:^1.0.0": version: 1.0.2 resolution: "@azure/abort-controller@npm:1.0.2" @@ -5532,6 +6316,26 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-events-backend-module-aws-sqs@workspace:plugins/events-backend-module-aws-sqs": + version: 0.0.0-use.local + resolution: "@backstage/plugin-events-backend-module-aws-sqs@workspace:plugins/events-backend-module-aws-sqs" + dependencies: + "@aws-sdk/client-sqs": ^3.0.0 + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-events-backend-test-utils": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" + "@backstage/types": "workspace:^" + aws-sdk-client-mock: ^2.0.0 + luxon: ^3.0.0 + winston: ^3.2.1 + languageName: unknown + linkType: soft + "@backstage/plugin-events-backend-test-utils@workspace:^, @backstage/plugin-events-backend-test-utils@workspace:plugins/events-backend-test-utils": version: 0.0.0-use.local resolution: "@backstage/plugin-events-backend-test-utils@workspace:plugins/events-backend-test-utils" @@ -12705,7 +13509,16 @@ __metadata: languageName: node linkType: hard -"@sinonjs/samsam@npm:^6.1.1": +"@sinonjs/fake-timers@npm:^7.1.2": + version: 7.1.2 + resolution: "@sinonjs/fake-timers@npm:7.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: c84773d7973edad5511a31d2cc75023447b5cf714a84de9bb50eda45dda88a0d3bd2c30bf6e6e936da50a048d5352e2151c694e13e59b97d187ba1f329e9a00c + languageName: node + linkType: hard + +"@sinonjs/samsam@npm:^6.0.2, @sinonjs/samsam@npm:^6.1.1": version: 6.1.1 resolution: "@sinonjs/samsam@npm:6.1.1" dependencies: @@ -14916,6 +15729,22 @@ __metadata: languageName: node linkType: hard +"@types/sinon@npm:^10.0.10": + version: 10.0.13 + resolution: "@types/sinon@npm:10.0.13" + dependencies: + "@types/sinonjs__fake-timers": "*" + checksum: 46a14c888db50f0098ec53d451877e0111d878ec4a653b9e9ed7f8e54de386d6beb0e528ddc3e95cd3361a8ab9ad54e4cca33cd88d45b9227b83e9fc8fb6688a + languageName: node + linkType: hard + +"@types/sinonjs__fake-timers@npm:*": + version: 8.1.2 + resolution: "@types/sinonjs__fake-timers@npm:8.1.2" + checksum: bbc73a5ab6c0ec974929392f3d6e1e8db4ebad97ec506d785301e1c3d8a4f98a35b1aa95b97035daef02886fd8efd7788a2fa3ced2ec7105988bfd8dce61eedd + languageName: node + linkType: hard + "@types/sinonjs__fake-timers@npm:8.1.1": version: 8.1.1 resolution: "@types/sinonjs__fake-timers@npm:8.1.1" @@ -16755,6 +17584,17 @@ __metadata: languageName: node linkType: hard +"aws-sdk-client-mock@npm:^2.0.0": + version: 2.0.0 + resolution: "aws-sdk-client-mock@npm:2.0.0" + dependencies: + "@types/sinon": ^10.0.10 + sinon: ^11.1.1 + tslib: ^2.1.0 + checksum: e6081ca6bb72f5c082dfcd93155bbb7e1c9e8d3d346914ed05d736c495a5ef99ab3d0253507aff2e07b5b995fae63671e53e094437ddfaf8649d69725628ff8f + languageName: node + linkType: hard + "aws-sdk-mock@npm:^5.2.1": version: 5.7.0 resolution: "aws-sdk-mock@npm:5.7.0" @@ -17411,6 +18251,13 @@ __metadata: languageName: node linkType: hard +"bowser@npm:^2.11.0": + version: 2.11.0 + resolution: "bowser@npm:2.11.0" + checksum: 29c3f01f22e703fa6644fc3b684307442df4240b6e10f6cfe1b61c6ca5721073189ca97cdeedb376081148c8518e33b1d818a57f781d70b0b70e1f31fb48814f + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -21021,6 +21868,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:2.2.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3 + languageName: node + linkType: hard + "entities@npm:^2.0.0, entities@npm:~2.1.0": version: 2.1.0 resolution: "entities@npm:2.1.0" @@ -22767,6 +23621,15 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:3.19.0": + version: 3.19.0 + resolution: "fast-xml-parser@npm:3.19.0" + bin: + xml2js: cli.js + checksum: d9da9145f73d90c05ee2746d80c78eca4da0249dea8c81ea8f1a6e1245e62988ed4a040dbd1c7229b1e0bdcbf69d33c882e0ac337d10c7eedb159a4dc9779327 + languageName: node + linkType: hard + "fastest-stable-stringify@npm:^2.0.2": version: 2.0.2 resolution: "fastest-stable-stringify@npm:2.0.2" @@ -30077,7 +30940,7 @@ __metadata: languageName: node linkType: hard -"nise@npm:^5.1.1": +"nise@npm:^5.1.0, nise@npm:^5.1.1": version: 5.1.1 resolution: "nise@npm:5.1.1" dependencies: @@ -35666,6 +36529,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^11.1.1": + version: 11.1.2 + resolution: "sinon@npm:11.1.2" + dependencies: + "@sinonjs/commons": ^1.8.3 + "@sinonjs/fake-timers": ^7.1.2 + "@sinonjs/samsam": ^6.0.2 + diff: ^5.0.0 + nise: ^5.1.0 + supports-color: ^7.2.0 + checksum: 1d01377e230c9ba976bf33f28b588bae7901b0b5a503d2f6b2a7914b0dbaa9f09823481926c6f2abed820123c7fa865519695af3ae2e9ba18d8b025616163501 + languageName: node + linkType: hard + "sinon@npm:^13.0.2": version: 13.0.2 resolution: "sinon@npm:13.0.2" @@ -37713,7 +38590,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:^1.10.0, tslib@npm:^1.11.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd