@backstage/plugin-events-backend-module-google-pubsub 0.0.0-nightly-20250423023843
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +42 -0
- package/config.d.ts +96 -0
- package/dist/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.cjs.js +143 -0
- package/dist/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.cjs.js.map +1 -0
- package/dist/GooglePubSubConsumingEventPublisher/config.cjs.js +88 -0
- package/dist/GooglePubSubConsumingEventPublisher/config.cjs.js.map +1 -0
- package/dist/GooglePubSubConsumingEventPublisher/module.cjs.js +31 -0
- package/dist/GooglePubSubConsumingEventPublisher/module.cjs.js.map +1 -0
- package/dist/index.cjs.js +10 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/util/createPatternResolver.cjs.js +57 -0
- package/dist/util/createPatternResolver.cjs.js.map +1 -0
- package/package.json +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @backstage/plugin-events-backend-module-google-pubsub
|
|
2
|
+
|
|
3
|
+
## 0.0.0-nightly-20250423023843
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @backstage/backend-plugin-api@0.0.0-nightly-20250423023843
|
|
13
|
+
- @backstage/plugin-events-node@0.0.0-nightly-20250423023843
|
|
14
|
+
- @backstage/config@1.3.2
|
|
15
|
+
- @backstage/errors@1.2.7
|
|
16
|
+
- @backstage/types@1.2.1
|
|
17
|
+
|
|
18
|
+
## 0.1.0-next.0
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies
|
|
27
|
+
- @backstage/backend-plugin-api@1.3.1-next.0
|
|
28
|
+
- @backstage/plugin-events-node@0.4.11-next.0
|
|
29
|
+
- @backstage/config@1.3.2
|
|
30
|
+
- @backstage/errors@1.2.7
|
|
31
|
+
- @backstage/types@1.2.1
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @backstage/plugin-events-backend-module-google-pubsub
|
|
2
|
+
|
|
3
|
+
This package is a module for the `events-backend` backend plugin
|
|
4
|
+
and extends the events system with Google Pub/Sub related capabilities.
|
|
5
|
+
|
|
6
|
+
## Configuration
|
|
7
|
+
|
|
8
|
+
The following configuration enables the transfer of messages from a Google
|
|
9
|
+
Pub/Sub subscription into a topic on the Backstage events system.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
events:
|
|
13
|
+
modules:
|
|
14
|
+
googlePubSub:
|
|
15
|
+
googlePubSubConsumingEventPublisher:
|
|
16
|
+
subscriptions:
|
|
17
|
+
# A unique key for your subscription, to be used in logging and metrics
|
|
18
|
+
mySubscription:
|
|
19
|
+
# The fully qualified name of the subscription
|
|
20
|
+
subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events'
|
|
21
|
+
# The event system topic to transfer to. This can also be just a plain string
|
|
22
|
+
targetTopic:
|
|
23
|
+
# This example picks the topic name from a message attribute + a prefix
|
|
24
|
+
fromMessageAttribute:
|
|
25
|
+
attributeName: 'x-github-event'
|
|
26
|
+
withPrefix: 'github.'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
1. Install this module
|
|
32
|
+
2. Add your configuration.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# From your Backstage root directory
|
|
36
|
+
yarn --cwd packages/backend add @backstage/plugin-events-backend-module-google-pubsub
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// packages/backend/src/index.ts
|
|
41
|
+
backend.add(import('@backstage/plugin-events-backend-module-google-pubsub'));
|
|
42
|
+
```
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 The Backstage Authors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface Config {
|
|
18
|
+
events?: {
|
|
19
|
+
modules?: {
|
|
20
|
+
/**
|
|
21
|
+
* events-backend-module-google-pubsub configuration.
|
|
22
|
+
*/
|
|
23
|
+
googlePubSub?: {
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for `GooglePubSubConsumingEventPublisher`, which
|
|
26
|
+
* consumes messages from a Google Pub/Sub subscription and forwards
|
|
27
|
+
* them into the Backstage events system.
|
|
28
|
+
*/
|
|
29
|
+
googlePubSubConsumingEventPublisher?: {
|
|
30
|
+
/**
|
|
31
|
+
* Generally contains a record per subscription to consume.
|
|
32
|
+
*/
|
|
33
|
+
subscriptions: {
|
|
34
|
+
/**
|
|
35
|
+
* The name can be anything, but it is recommended to use only
|
|
36
|
+
* letters, numbers, and hyphens for this identifier since it will
|
|
37
|
+
* appear in logs and metric names etc.
|
|
38
|
+
*/
|
|
39
|
+
[name: string]: {
|
|
40
|
+
/**
|
|
41
|
+
* The complete name of the Pub/Sub subscription to be used, on the
|
|
42
|
+
* form
|
|
43
|
+
* `projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID`.
|
|
44
|
+
*/
|
|
45
|
+
subscriptionName: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The name of the events backend topic to which the messages are
|
|
49
|
+
* to be forwarded.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
*
|
|
53
|
+
* The value can contain placeholders on the form `{{ message.attributes.foo }}`,
|
|
54
|
+
* to mirror attribute `foo` as the whole or part of the topic name.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
*
|
|
58
|
+
* This example expects the Pub/Sub topic to contain GitHub
|
|
59
|
+
* webhook events where the HTTP headers were mapped into
|
|
60
|
+
* message attributes. The outcome should be that messages
|
|
61
|
+
* end up on event topics such as `github.push`,
|
|
62
|
+
* `github.repository` etc which matches the [`@backstage/plugin-events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github) structure.
|
|
63
|
+
*
|
|
64
|
+
* ```yaml
|
|
65
|
+
* targetTopic: 'github.{{ message.attributes.x-github-event }}'
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
targetTopic: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pub/Sub message attributes are by default copied to the event
|
|
72
|
+
* metadata field. This setting allows you to override or amend
|
|
73
|
+
* that metadata.
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
*
|
|
77
|
+
* The values can contain placeholders on the form `{{ message.attributes.foo }}`,
|
|
78
|
+
* to mirror attribute `foo` as the whole or part of a metadata value.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
*
|
|
82
|
+
* ```yaml
|
|
83
|
+
* eventMetadata:
|
|
84
|
+
* x-gitHub-event: '{{ message.attributes.event }}'
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
eventMetadata?: {
|
|
88
|
+
[key: string]: string;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var pubsub = require('@google-cloud/pubsub');
|
|
4
|
+
var api = require('@opentelemetry/api');
|
|
5
|
+
var config = require('./config.cjs.js');
|
|
6
|
+
|
|
7
|
+
class GooglePubSubConsumingEventPublisher {
|
|
8
|
+
#logger;
|
|
9
|
+
#events;
|
|
10
|
+
#tasks;
|
|
11
|
+
#pubSubFactory;
|
|
12
|
+
#metrics;
|
|
13
|
+
#activeClientsByProjectId;
|
|
14
|
+
#activeSubscriptions;
|
|
15
|
+
static create(options) {
|
|
16
|
+
const publisher = new GooglePubSubConsumingEventPublisher({
|
|
17
|
+
logger: options.logger,
|
|
18
|
+
events: options.events,
|
|
19
|
+
tasks: config.readSubscriptionTasksFromConfig(options.config),
|
|
20
|
+
pubSubFactory: (projectId) => new pubsub.PubSub({ projectId })
|
|
21
|
+
});
|
|
22
|
+
options.rootLifecycle.addStartupHook(async () => {
|
|
23
|
+
await publisher.start();
|
|
24
|
+
});
|
|
25
|
+
options.rootLifecycle.addBeforeShutdownHook(async () => {
|
|
26
|
+
await publisher.stop();
|
|
27
|
+
});
|
|
28
|
+
return publisher;
|
|
29
|
+
}
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#logger = options.logger;
|
|
32
|
+
this.#events = options.events;
|
|
33
|
+
this.#tasks = options.tasks;
|
|
34
|
+
this.#pubSubFactory = options.pubSubFactory;
|
|
35
|
+
const meter = api.metrics.getMeter("default");
|
|
36
|
+
this.#metrics = {
|
|
37
|
+
messages: meter.createCounter(
|
|
38
|
+
"events.google.pubsub.consumer.messages.total",
|
|
39
|
+
{
|
|
40
|
+
description: "Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher",
|
|
41
|
+
unit: "short"
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
};
|
|
45
|
+
this.#activeClientsByProjectId = /* @__PURE__ */ new Map();
|
|
46
|
+
this.#activeSubscriptions = [];
|
|
47
|
+
}
|
|
48
|
+
async start() {
|
|
49
|
+
for (const task of this.#tasks) {
|
|
50
|
+
this.#logger.info(
|
|
51
|
+
`Starting subscription: id=${task.id} project=${task.project} subscription=${task.subscription}`
|
|
52
|
+
);
|
|
53
|
+
let pubsub = this.#activeClientsByProjectId.get(task.project);
|
|
54
|
+
if (!pubsub) {
|
|
55
|
+
pubsub = this.#pubSubFactory(task.project);
|
|
56
|
+
this.#activeClientsByProjectId.set(task.project, pubsub);
|
|
57
|
+
}
|
|
58
|
+
const subscription = pubsub.subscription(task.subscription, {
|
|
59
|
+
flowControl: {
|
|
60
|
+
maxMessages: 5,
|
|
61
|
+
allowExcessMessages: false
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
this.#activeSubscriptions.push(subscription);
|
|
65
|
+
subscription.on("error", (error) => {
|
|
66
|
+
this.#logger.error(
|
|
67
|
+
`Error reading Google Pub/Sub subscription: ${task.id}`,
|
|
68
|
+
error
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
subscription.on("message", async (message) => {
|
|
72
|
+
let event;
|
|
73
|
+
try {
|
|
74
|
+
event = this.#messageToEvent(message, task);
|
|
75
|
+
if (!event) {
|
|
76
|
+
this.#metrics.messages.add(1, {
|
|
77
|
+
subscription: task.id,
|
|
78
|
+
status: "ignored"
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.#logger.error("Error processing Google Pub/Sub message", error);
|
|
84
|
+
this.#metrics.messages.add(1, {
|
|
85
|
+
subscription: task.id,
|
|
86
|
+
status: "failed"
|
|
87
|
+
});
|
|
88
|
+
message.ack();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await this.#events.publish(event);
|
|
93
|
+
this.#metrics.messages.add(1, {
|
|
94
|
+
subscription: task.id,
|
|
95
|
+
status: "success"
|
|
96
|
+
});
|
|
97
|
+
message.ack();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.#logger.error("Error processing Google Pub/Sub message", error);
|
|
100
|
+
this.#metrics.messages.add(1, {
|
|
101
|
+
subscription: task.id,
|
|
102
|
+
status: "failed"
|
|
103
|
+
});
|
|
104
|
+
message.nack();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async stop() {
|
|
110
|
+
const subscriptions = this.#activeSubscriptions;
|
|
111
|
+
const clients = Array.from(this.#activeClientsByProjectId.values());
|
|
112
|
+
this.#activeSubscriptions = [];
|
|
113
|
+
this.#activeClientsByProjectId = /* @__PURE__ */ new Map();
|
|
114
|
+
await Promise.allSettled(
|
|
115
|
+
subscriptions.map(async (subscription) => {
|
|
116
|
+
this.#logger.info(
|
|
117
|
+
`Closing Google Pub/Sub subscription: ${subscription.name}`
|
|
118
|
+
);
|
|
119
|
+
await subscription.close();
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
await Promise.allSettled(
|
|
123
|
+
clients.map(async (client) => {
|
|
124
|
+
this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);
|
|
125
|
+
await client.close();
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
#messageToEvent(message, task) {
|
|
130
|
+
const topic = task.mapToTopic(message);
|
|
131
|
+
if (!topic) {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
topic,
|
|
136
|
+
eventPayload: JSON.parse(message.data.toString()),
|
|
137
|
+
metadata: task.mapToMetadata(message)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
exports.GooglePubSubConsumingEventPublisher = GooglePubSubConsumingEventPublisher;
|
|
143
|
+
//# sourceMappingURL=GooglePubSubConsumingEventPublisher.cjs.js.map
|
package/dist/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.cjs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GooglePubSubConsumingEventPublisher.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LoggerService,\n RootConfigService,\n RootLifecycleService,\n} from '@backstage/backend-plugin-api';\nimport { EventParams, EventsService } from '@backstage/plugin-events-node';\nimport { Message, PubSub, Subscription } from '@google-cloud/pubsub';\nimport { Counter, metrics } from '@opentelemetry/api';\nimport { readSubscriptionTasksFromConfig } from './config';\nimport { SubscriptionTask } from './types';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n */\nexport class GooglePubSubConsumingEventPublisher {\n readonly #logger: LoggerService;\n readonly #events: EventsService;\n readonly #tasks: SubscriptionTask[];\n readonly #pubSubFactory: (projectId: string) => PubSub;\n readonly #metrics: { messages: Counter };\n #activeClientsByProjectId: Map<string, PubSub>;\n #activeSubscriptions: Subscription[];\n\n static create(options: {\n config: RootConfigService;\n logger: LoggerService;\n rootLifecycle: RootLifecycleService;\n events: EventsService;\n }) {\n const publisher = new GooglePubSubConsumingEventPublisher({\n logger: options.logger,\n events: options.events,\n tasks: readSubscriptionTasksFromConfig(options.config),\n pubSubFactory: projectId => new PubSub({ projectId }),\n });\n\n options.rootLifecycle.addStartupHook(async () => {\n await publisher.start();\n });\n\n options.rootLifecycle.addBeforeShutdownHook(async () => {\n await publisher.stop();\n });\n\n return publisher;\n }\n\n constructor(options: {\n logger: LoggerService;\n events: EventsService;\n tasks: SubscriptionTask[];\n pubSubFactory: (projectId: string) => PubSub;\n }) {\n this.#logger = options.logger;\n this.#events = options.events;\n this.#tasks = options.tasks;\n this.#pubSubFactory = options.pubSubFactory;\n\n const meter = metrics.getMeter('default');\n this.#metrics = {\n messages: meter.createCounter(\n 'events.google.pubsub.consumer.messages.total',\n {\n description:\n 'Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher',\n unit: 'short',\n },\n ),\n };\n\n this.#activeClientsByProjectId = new Map();\n this.#activeSubscriptions = [];\n }\n\n async start() {\n for (const task of this.#tasks) {\n this.#logger.info(\n `Starting subscription: id=${task.id} project=${task.project} subscription=${task.subscription}`,\n );\n\n let pubsub = this.#activeClientsByProjectId.get(task.project);\n if (!pubsub) {\n pubsub = this.#pubSubFactory(task.project);\n this.#activeClientsByProjectId.set(task.project, pubsub);\n }\n\n // You cannot control the actual batch size delivered to the client from\n // pubsub, so these settings actually instead control the rate at which\n // messages are released to our event handlers by the pubsub library. This\n // means that there may be significantly more than maxMessages messages\n // pending in memory before we see them. Thus, the settings here are rather\n // chosen so as to limit the concurrency of hammering consumers (the catalog\n // etc).\n const subscription = pubsub.subscription(task.subscription, {\n flowControl: {\n maxMessages: 5,\n allowExcessMessages: false,\n },\n });\n\n this.#activeSubscriptions.push(subscription);\n\n subscription.on('error', error => {\n this.#logger.error(\n `Error reading Google Pub/Sub subscription: ${task.id}`,\n error,\n );\n });\n\n subscription.on('message', async message => {\n let event: EventParams;\n try {\n event = this.#messageToEvent(message, task)!;\n if (!event) {\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'ignored',\n });\n return;\n }\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We unconditionally ACK the message in this case, because if it's\n // broken, it will still be broken next time around, so there is no\n // point in re-delivering it.\n message.ack();\n return;\n }\n\n try {\n await this.#events.publish(event);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'success',\n });\n message.ack();\n } catch (error) {\n this.#logger.error('Error processing Google Pub/Sub message', error);\n this.#metrics.messages.add(1, {\n subscription: task.id,\n status: 'failed',\n });\n // We fast-NACK the message in this case because this may be a\n // transient problem with the events backend.\n message.nack();\n }\n });\n }\n }\n\n async stop() {\n const subscriptions = this.#activeSubscriptions;\n const clients = Array.from(this.#activeClientsByProjectId.values());\n\n this.#activeSubscriptions = [];\n this.#activeClientsByProjectId = new Map();\n\n await Promise.allSettled(\n subscriptions.map(async subscription => {\n this.#logger.info(\n `Closing Google Pub/Sub subscription: ${subscription.name}`,\n );\n await subscription.close();\n }),\n );\n\n await Promise.allSettled(\n clients.map(async client => {\n this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);\n await client.close();\n }),\n );\n }\n\n #messageToEvent(\n message: Message,\n task: SubscriptionTask,\n ): EventParams | undefined {\n const topic = task.mapToTopic(message);\n if (!topic) {\n return undefined;\n }\n return {\n topic,\n eventPayload: JSON.parse(message.data.toString()),\n metadata: task.mapToMetadata(message),\n };\n }\n}\n"],"names":["readSubscriptionTasksFromConfig","PubSub","metrics"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var createPatternResolver = require('../util/createPatternResolver.cjs.js');
|
|
5
|
+
|
|
6
|
+
function readSubscriptionTasksFromConfig(rootConfig) {
|
|
7
|
+
const subscriptionsConfig = rootConfig.getOptionalConfig(
|
|
8
|
+
"events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions"
|
|
9
|
+
);
|
|
10
|
+
if (!subscriptionsConfig) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return subscriptionsConfig.keys().map((subscriptionId) => {
|
|
14
|
+
if (!subscriptionId.match(/^[-_\w]+$/)) {
|
|
15
|
+
throw new errors.InputError(
|
|
16
|
+
`Expected Googoe Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const config = subscriptionsConfig.getConfig(subscriptionId);
|
|
20
|
+
const { project, subscription } = readSubscriptionName(config);
|
|
21
|
+
const mapToTopic = readTopicMapper(config);
|
|
22
|
+
const mapToMetadata = readMetadataMapper(config);
|
|
23
|
+
return {
|
|
24
|
+
id: subscriptionId,
|
|
25
|
+
project,
|
|
26
|
+
subscription,
|
|
27
|
+
mapToTopic,
|
|
28
|
+
mapToMetadata
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function readSubscriptionName(config) {
|
|
33
|
+
const subscriptionName = config.getString("subscriptionName");
|
|
34
|
+
const parts = subscriptionName.match(
|
|
35
|
+
/^projects\/([^/]+)\/subscriptions\/(.+)$/
|
|
36
|
+
);
|
|
37
|
+
if (!parts) {
|
|
38
|
+
throw new errors.InputError(
|
|
39
|
+
`Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
project: parts[1],
|
|
44
|
+
subscription: parts[2]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function readTopicMapper(config) {
|
|
48
|
+
const targetTopicPattern = config.getString("targetTopic");
|
|
49
|
+
const patternResolver = createPatternResolver.createPatternResolver(targetTopicPattern);
|
|
50
|
+
return (message) => {
|
|
51
|
+
try {
|
|
52
|
+
return patternResolver({ message });
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function readMetadataMapper(config) {
|
|
59
|
+
const setters = new Array();
|
|
60
|
+
const eventMetadata = config.getOptionalConfig("eventMetadata");
|
|
61
|
+
if (eventMetadata) {
|
|
62
|
+
for (const key of eventMetadata?.keys() ?? []) {
|
|
63
|
+
const valuePattern = eventMetadata.getString(key);
|
|
64
|
+
const patternResolver = createPatternResolver.createPatternResolver(valuePattern);
|
|
65
|
+
setters.push(({ message, metadata }) => {
|
|
66
|
+
try {
|
|
67
|
+
const value = patternResolver({ message });
|
|
68
|
+
if (value) {
|
|
69
|
+
metadata[key] = value;
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return (message) => {
|
|
77
|
+
const result = {
|
|
78
|
+
...message.attributes
|
|
79
|
+
};
|
|
80
|
+
for (const setter of setters) {
|
|
81
|
+
setter({ message, metadata: result });
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
exports.readSubscriptionTasksFromConfig = readSubscriptionTasksFromConfig;
|
|
88
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/config.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\nimport { Message } from '@google-cloud/pubsub';\nimport { createPatternResolver } from '../util/createPatternResolver';\nimport { SubscriptionTask } from './types';\n\nexport function readSubscriptionTasksFromConfig(\n rootConfig: RootConfigService,\n): SubscriptionTask[] {\n const subscriptionsConfig = rootConfig.getOptionalConfig(\n 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',\n );\n if (!subscriptionsConfig) {\n return [];\n }\n\n return subscriptionsConfig.keys().map(subscriptionId => {\n if (!subscriptionId.match(/^[-_\\w]+$/)) {\n throw new InputError(\n `Expected Googoe Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,\n );\n }\n\n const config = subscriptionsConfig.getConfig(subscriptionId);\n const { project, subscription } = readSubscriptionName(config);\n const mapToTopic = readTopicMapper(config);\n const mapToMetadata = readMetadataMapper(config);\n\n return {\n id: subscriptionId,\n project,\n subscription,\n mapToTopic,\n mapToMetadata,\n };\n });\n}\n\nfunction readSubscriptionName(config: Config): {\n project: string;\n subscription: string;\n} {\n const subscriptionName = config.getString('subscriptionName');\n const parts = subscriptionName.match(\n /^projects\\/([^/]+)\\/subscriptions\\/(.+)$/,\n );\n if (!parts) {\n throw new InputError(\n `Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`,\n );\n }\n return {\n project: parts[1],\n subscription: parts[2],\n };\n}\n\n/**\n * Handles the `targetTopic` configuration field.\n */\nfunction readTopicMapper(\n config: Config,\n): (message: Message) => string | undefined {\n const targetTopicPattern = config.getString('targetTopic');\n const patternResolver = createPatternResolver(targetTopicPattern);\n return message => {\n try {\n return patternResolver({ message });\n } catch {\n // could not map to a topic\n return undefined;\n }\n };\n}\n\n/**\n * Handles the `eventMetadata` configuration field.\n */\nfunction readMetadataMapper(\n config: Config,\n): (message: Message) => Record<string, string> {\n const setters = new Array<\n (options: { message: Message; metadata: Record<string, string> }) => void\n >();\n\n const eventMetadata = config.getOptionalConfig('eventMetadata');\n if (eventMetadata) {\n for (const key of eventMetadata?.keys() ?? []) {\n const valuePattern = eventMetadata.getString(key);\n const patternResolver = createPatternResolver(valuePattern);\n setters.push(({ message, metadata }) => {\n try {\n const value = patternResolver({ message });\n if (value) {\n metadata[key] = value;\n }\n } catch {\n // ignore silently, keep original\n }\n });\n }\n }\n\n return message => {\n const result: Record<string, string> = {\n ...message.attributes,\n };\n for (const setter of setters) {\n setter({ message, metadata: result });\n }\n return result;\n };\n}\n"],"names":["InputError","createPatternResolver"],"mappings":";;;;;AAuBO,SAAS,gCACd,UACoB,EAAA;AACpB,EAAA,MAAM,sBAAsB,UAAW,CAAA,iBAAA;AAAA,IACrC;AAAA,GACF;AACA,EAAA,IAAI,CAAC,mBAAqB,EAAA;AACxB,IAAA,OAAO,EAAC;AAAA;AAGV,EAAA,OAAO,mBAAoB,CAAA,IAAA,EAAO,CAAA,GAAA,CAAI,CAAkB,cAAA,KAAA;AACtD,IAAA,IAAI,CAAC,cAAA,CAAe,KAAM,CAAA,WAAW,CAAG,EAAA;AACtC,MAAA,MAAM,IAAIA,iBAAA;AAAA,QACR,4GAA4G,cAAc,CAAA,CAAA;AAAA,OAC5H;AAAA;AAGF,IAAM,MAAA,MAAA,GAAS,mBAAoB,CAAA,SAAA,CAAU,cAAc,CAAA;AAC3D,IAAA,MAAM,EAAE,OAAA,EAAS,YAAa,EAAA,GAAI,qBAAqB,MAAM,CAAA;AAC7D,IAAM,MAAA,UAAA,GAAa,gBAAgB,MAAM,CAAA;AACzC,IAAM,MAAA,aAAA,GAAgB,mBAAmB,MAAM,CAAA;AAE/C,IAAO,OAAA;AAAA,MACL,EAAI,EAAA,cAAA;AAAA,MACJ,OAAA;AAAA,MACA,YAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACF;AAAA,GACD,CAAA;AACH;AAEA,SAAS,qBAAqB,MAG5B,EAAA;AACA,EAAM,MAAA,gBAAA,GAAmB,MAAO,CAAA,SAAA,CAAU,kBAAkB,CAAA;AAC5D,EAAA,MAAM,QAAQ,gBAAiB,CAAA,KAAA;AAAA,IAC7B;AAAA,GACF;AACA,EAAA,IAAI,CAAC,KAAO,EAAA;AACV,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,6HAA6H,gBAAgB,CAAA,CAAA;AAAA,KAC/I;AAAA;AAEF,EAAO,OAAA;AAAA,IACL,OAAA,EAAS,MAAM,CAAC,CAAA;AAAA,IAChB,YAAA,EAAc,MAAM,CAAC;AAAA,GACvB;AACF;AAKA,SAAS,gBACP,MAC0C,EAAA;AAC1C,EAAM,MAAA,kBAAA,GAAqB,MAAO,CAAA,SAAA,CAAU,aAAa,CAAA;AACzD,EAAM,MAAA,eAAA,GAAkBC,4CAAsB,kBAAkB,CAAA;AAChE,EAAA,OAAO,CAAW,OAAA,KAAA;AAChB,IAAI,IAAA;AACF,MAAO,OAAA,eAAA,CAAgB,EAAE,OAAA,EAAS,CAAA;AAAA,KAC5B,CAAA,MAAA;AAEN,MAAO,OAAA,KAAA,CAAA;AAAA;AACT,GACF;AACF;AAKA,SAAS,mBACP,MAC8C,EAAA;AAC9C,EAAM,MAAA,OAAA,GAAU,IAAI,KAElB,EAAA;AAEF,EAAM,MAAA,aAAA,GAAgB,MAAO,CAAA,iBAAA,CAAkB,eAAe,CAAA;AAC9D,EAAA,IAAI,aAAe,EAAA;AACjB,IAAA,KAAA,MAAW,GAAO,IAAA,aAAA,EAAe,IAAK,EAAA,IAAK,EAAI,EAAA;AAC7C,MAAM,MAAA,YAAA,GAAe,aAAc,CAAA,SAAA,CAAU,GAAG,CAAA;AAChD,MAAM,MAAA,eAAA,GAAkBA,4CAAsB,YAAY,CAAA;AAC1D,MAAA,OAAA,CAAQ,IAAK,CAAA,CAAC,EAAE,OAAA,EAAS,UAAe,KAAA;AACtC,QAAI,IAAA;AACF,UAAA,MAAM,KAAQ,GAAA,eAAA,CAAgB,EAAE,OAAA,EAAS,CAAA;AACzC,UAAA,IAAI,KAAO,EAAA;AACT,YAAA,QAAA,CAAS,GAAG,CAAI,GAAA,KAAA;AAAA;AAClB,SACM,CAAA,MAAA;AAAA;AAER,OACD,CAAA;AAAA;AACH;AAGF,EAAA,OAAO,CAAW,OAAA,KAAA;AAChB,IAAA,MAAM,MAAiC,GAAA;AAAA,MACrC,GAAG,OAAQ,CAAA;AAAA,KACb;AACA,IAAA,KAAA,MAAW,UAAU,OAAS,EAAA;AAC5B,MAAA,MAAA,CAAO,EAAE,OAAA,EAAS,QAAU,EAAA,MAAA,EAAQ,CAAA;AAAA;AAEtC,IAAO,OAAA,MAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var pluginEventsNode = require('@backstage/plugin-events-node');
|
|
5
|
+
var GooglePubSubConsumingEventPublisher = require('./GooglePubSubConsumingEventPublisher.cjs.js');
|
|
6
|
+
|
|
7
|
+
const eventsModuleGooglePubsubConsumingEventPublisher = backendPluginApi.createBackendModule({
|
|
8
|
+
pluginId: "events",
|
|
9
|
+
moduleId: "google-pubsub-consuming-event-publisher",
|
|
10
|
+
register(reg) {
|
|
11
|
+
reg.registerInit({
|
|
12
|
+
deps: {
|
|
13
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
14
|
+
logger: backendPluginApi.coreServices.logger,
|
|
15
|
+
rootLifecycle: backendPluginApi.coreServices.rootLifecycle,
|
|
16
|
+
events: pluginEventsNode.eventsServiceRef
|
|
17
|
+
},
|
|
18
|
+
async init({ config, logger, rootLifecycle, events }) {
|
|
19
|
+
GooglePubSubConsumingEventPublisher.GooglePubSubConsumingEventPublisher.create({
|
|
20
|
+
config,
|
|
21
|
+
logger,
|
|
22
|
+
rootLifecycle,
|
|
23
|
+
events
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
exports.eventsModuleGooglePubsubConsumingEventPublisher = eventsModuleGooglePubsubConsumingEventPublisher;
|
|
31
|
+
//# sourceMappingURL=module.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../../src/GooglePubSubConsumingEventPublisher/module.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { eventsServiceRef } from '@backstage/plugin-events-node';\nimport { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher';\n\n/**\n * Reads messages off of Google Pub/Sub subscriptions and forwards them into the\n * Backstage events system.\n *\n * @public\n */\nexport const eventsModuleGooglePubsubConsumingEventPublisher =\n createBackendModule({\n pluginId: 'events',\n moduleId: 'google-pubsub-consuming-event-publisher',\n register(reg) {\n reg.registerInit({\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n rootLifecycle: coreServices.rootLifecycle,\n events: eventsServiceRef,\n },\n async init({ config, logger, rootLifecycle, events }) {\n GooglePubSubConsumingEventPublisher.create({\n config,\n logger,\n rootLifecycle,\n events,\n });\n },\n });\n },\n });\n"],"names":["createBackendModule","coreServices","eventsServiceRef","GooglePubSubConsumingEventPublisher"],"mappings":";;;;;;AA6BO,MAAM,kDACXA,oCAAoB,CAAA;AAAA,EAClB,QAAU,EAAA,QAAA;AAAA,EACV,QAAU,EAAA,yCAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,eAAeA,6BAAa,CAAA,aAAA;AAAA,QAC5B,MAAQ,EAAAC;AAAA,OACV;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,QAAQ,MAAQ,EAAA,aAAA,EAAe,QAAU,EAAA;AACpD,QAAAC,uEAAA,CAAoC,MAAO,CAAA;AAAA,UACzC,MAAA;AAAA,UACA,MAAA;AAAA,UACA,aAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var module$1 = require('./GooglePubSubConsumingEventPublisher/module.cjs.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
exports.default = module$1.eventsModuleGooglePubsubConsumingEventPublisher;
|
|
10
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads messages off of Google Pub/Sub subscriptions and forwards them into the
|
|
5
|
+
* Backstage events system.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare const eventsModuleGooglePubsubConsumingEventPublisher: _backstage_backend_plugin_api.BackendFeature;
|
|
10
|
+
|
|
11
|
+
export { eventsModuleGooglePubsubConsumingEventPublisher as default };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
function createPatternResolver(pattern) {
|
|
6
|
+
const patternParts = pattern.split(/{{\s*([\w\[\]'"_.-]*)\s*}}/g);
|
|
7
|
+
const resolvers = new Array();
|
|
8
|
+
for (let i = 0; i < patternParts.length; i += 2) {
|
|
9
|
+
const staticPart = patternParts[i];
|
|
10
|
+
const placeholderPart = patternParts[i + 1];
|
|
11
|
+
if (staticPart) {
|
|
12
|
+
resolvers.push(() => staticPart);
|
|
13
|
+
}
|
|
14
|
+
if (placeholderPart) {
|
|
15
|
+
const getter = createGetter(placeholderPart);
|
|
16
|
+
resolvers.push((context) => {
|
|
17
|
+
const value = getter(context);
|
|
18
|
+
if (typeof value === "string" || Number.isFinite(value)) {
|
|
19
|
+
return String(value);
|
|
20
|
+
} else if (!value) {
|
|
21
|
+
throw new errors.InputError(`No value for selector '${placeholderPart}'`);
|
|
22
|
+
} else {
|
|
23
|
+
throw new errors.InputError(
|
|
24
|
+
`Expected string or number value for selector '${placeholderPart}', got ${typeof value}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return (context) => resolvers.map((resolver) => resolver(context)).join("");
|
|
31
|
+
}
|
|
32
|
+
function createGetter(path) {
|
|
33
|
+
const parts = path.split(/\.|\[(?:(\d+)|'([^']+)'|"([^"]+)")\]\.?/g).filter(Boolean);
|
|
34
|
+
return (context) => {
|
|
35
|
+
let current = context;
|
|
36
|
+
for (const part of parts) {
|
|
37
|
+
if (typeof current !== "object" || !current) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(current)) {
|
|
41
|
+
if (!part.match(/^\d+$/)) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
current = current[Number(part)];
|
|
45
|
+
} else {
|
|
46
|
+
if (!Object.hasOwn(current, part)) {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
current = current[part];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return current;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.createPatternResolver = createPatternResolver;
|
|
57
|
+
//# sourceMappingURL=createPatternResolver.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPatternResolver.cjs.js","sources":["../../src/util/createPatternResolver.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { InputError } from '@backstage/errors';\n\n/**\n * Takes a pattern string that may contain `{{ path.to.value }}` placeholders,\n * and returns a function that accepts a context object and returns strings that\n * have had its placeholders filled in by following the dot separated path of\n * properties accordingly on the context.\n */\nexport function createPatternResolver<TContext extends object = object>(\n pattern: string,\n): (context: TContext) => string {\n // This split results in an array where even elements are static strings\n // between placeholders, and odd elements are the contents inside\n // placeholders.\n //\n // For example, the pattern:\n // \"{{ foo }}-{{bar}}{{baz}}.\"\n // will result in:\n // ['', 'foo', '-', 'bar', '', 'baz', '.']\n const patternParts = pattern.split(/{{\\s*([\\w\\[\\]'\"_.-]*)\\s*}}/g);\n\n const resolvers = new Array<(context: TContext) => string>();\n\n for (let i = 0; i < patternParts.length; i += 2) {\n const staticPart = patternParts[i];\n const placeholderPart = patternParts[i + 1];\n\n if (staticPart) {\n resolvers.push(() => staticPart);\n }\n\n if (placeholderPart) {\n const getter = createGetter<TContext>(placeholderPart);\n resolvers.push(context => {\n const value = getter(context);\n if (typeof value === 'string' || Number.isFinite(value)) {\n return String(value);\n } else if (!value) {\n throw new InputError(`No value for selector '${placeholderPart}'`);\n } else {\n throw new InputError(\n `Expected string or number value for selector '${placeholderPart}', got ${typeof value}`,\n );\n }\n });\n }\n }\n\n return context => resolvers.map(resolver => resolver(context)).join('');\n}\n\nfunction createGetter<TContext extends object = object>(\n path: string,\n): (context: TContext) => unknown | undefined {\n // The resulti of the split contains quads:\n //\n // - any \"regular\" part\n // - pure digits that were within brackets, if applicable\n // - contents of a single quoted string that was within brackets, if applicable\n // - contents of a double quoted string that was within brackets, if applicable\n //\n // For example, the path:\n // foo.bar[0].baz[\"qux.e\"]a\n // will result in:\n // [\n // 'foo', undefined, undefined, undefined,\n // 'bar', '0', undefined, undefined,\n // 'baz', undefined, 'qux.e', undefined,\n // 'a'\n // ]\n // and then the empty elements are stripped away\n const parts = path\n .split(/\\.|\\[(?:(\\d+)|'([^']+)'|\"([^\"]+)\")\\]\\.?/g)\n .filter(Boolean);\n\n return (context: TContext): unknown | undefined => {\n let current = context;\n for (const part of parts) {\n if (typeof current !== 'object' || !current) {\n return undefined;\n }\n\n if (Array.isArray(current)) {\n if (!part.match(/^\\d+$/)) {\n return undefined;\n }\n current = (current as any[])[Number(part)];\n } else {\n if (!Object.hasOwn(current, part)) {\n return undefined;\n }\n current = (current as any)[part];\n }\n }\n\n return current;\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAwBO,SAAS,sBACd,OAC+B,EAAA;AAS/B,EAAM,MAAA,YAAA,GAAe,OAAQ,CAAA,KAAA,CAAM,6BAA6B,CAAA;AAEhE,EAAM,MAAA,SAAA,GAAY,IAAI,KAAqC,EAAA;AAE3D,EAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,YAAa,CAAA,MAAA,EAAQ,KAAK,CAAG,EAAA;AAC/C,IAAM,MAAA,UAAA,GAAa,aAAa,CAAC,CAAA;AACjC,IAAM,MAAA,eAAA,GAAkB,YAAa,CAAA,CAAA,GAAI,CAAC,CAAA;AAE1C,IAAA,IAAI,UAAY,EAAA;AACd,MAAU,SAAA,CAAA,IAAA,CAAK,MAAM,UAAU,CAAA;AAAA;AAGjC,IAAA,IAAI,eAAiB,EAAA;AACnB,MAAM,MAAA,MAAA,GAAS,aAAuB,eAAe,CAAA;AACrD,MAAA,SAAA,CAAU,KAAK,CAAW,OAAA,KAAA;AACxB,QAAM,MAAA,KAAA,GAAQ,OAAO,OAAO,CAAA;AAC5B,QAAA,IAAI,OAAO,KAAU,KAAA,QAAA,IAAY,MAAO,CAAA,QAAA,CAAS,KAAK,CAAG,EAAA;AACvD,UAAA,OAAO,OAAO,KAAK,CAAA;AAAA,SACrB,MAAA,IAAW,CAAC,KAAO,EAAA;AACjB,UAAA,MAAM,IAAIA,iBAAA,CAAW,CAA0B,uBAAA,EAAA,eAAe,CAAG,CAAA,CAAA,CAAA;AAAA,SAC5D,MAAA;AACL,UAAA,MAAM,IAAIA,iBAAA;AAAA,YACR,CAAiD,8CAAA,EAAA,eAAe,CAAU,OAAA,EAAA,OAAO,KAAK,CAAA;AAAA,WACxF;AAAA;AACF,OACD,CAAA;AAAA;AACH;AAGF,EAAO,OAAA,CAAA,OAAA,KAAW,UAAU,GAAI,CAAA,CAAA,QAAA,KAAY,SAAS,OAAO,CAAC,CAAE,CAAA,IAAA,CAAK,EAAE,CAAA;AACxE;AAEA,SAAS,aACP,IAC4C,EAAA;AAkB5C,EAAA,MAAM,QAAQ,IACX,CAAA,KAAA,CAAM,0CAA0C,CAAA,CAChD,OAAO,OAAO,CAAA;AAEjB,EAAA,OAAO,CAAC,OAA2C,KAAA;AACjD,IAAA,IAAI,OAAU,GAAA,OAAA;AACd,IAAA,KAAA,MAAW,QAAQ,KAAO,EAAA;AACxB,MAAA,IAAI,OAAO,OAAA,KAAY,QAAY,IAAA,CAAC,OAAS,EAAA;AAC3C,QAAO,OAAA,KAAA,CAAA;AAAA;AAGT,MAAI,IAAA,KAAA,CAAM,OAAQ,CAAA,OAAO,CAAG,EAAA;AAC1B,QAAA,IAAI,CAAC,IAAA,CAAK,KAAM,CAAA,OAAO,CAAG,EAAA;AACxB,UAAO,OAAA,KAAA,CAAA;AAAA;AAET,QAAW,OAAA,GAAA,OAAA,CAAkB,MAAO,CAAA,IAAI,CAAC,CAAA;AAAA,OACpC,MAAA;AACL,QAAA,IAAI,CAAC,MAAA,CAAO,MAAO,CAAA,OAAA,EAAS,IAAI,CAAG,EAAA;AACjC,UAAO,OAAA,KAAA,CAAA;AAAA;AAET,QAAA,OAAA,GAAW,QAAgB,IAAI,CAAA;AAAA;AACjC;AAGF,IAAO,OAAA,OAAA;AAAA,GACT;AACF;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backstage/plugin-events-backend-module-google-pubsub",
|
|
3
|
+
"version": "0.0.0-nightly-20250423023843",
|
|
4
|
+
"description": "The google-pubsub backend module for the events plugin.",
|
|
5
|
+
"backstage": {
|
|
6
|
+
"role": "backend-plugin-module",
|
|
7
|
+
"pluginId": "events",
|
|
8
|
+
"pluginPackage": "@backstage/plugin-events-backend",
|
|
9
|
+
"features": {
|
|
10
|
+
".": "@backstage/BackendFeature"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"main": "dist/index.cjs.js",
|
|
16
|
+
"types": "dist/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/backstage/backstage",
|
|
21
|
+
"directory": "plugins/events-backend-module-google-pubsub"
|
|
22
|
+
},
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"main": "dist/index.cjs.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"files": [
|
|
27
|
+
"config.d.ts",
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "backstage-cli package build",
|
|
32
|
+
"clean": "backstage-cli package clean",
|
|
33
|
+
"lint": "backstage-cli package lint",
|
|
34
|
+
"prepack": "backstage-cli package prepack",
|
|
35
|
+
"postpack": "backstage-cli package postpack",
|
|
36
|
+
"start": "backstage-cli package start",
|
|
37
|
+
"test": "backstage-cli package test"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@backstage/backend-plugin-api": "0.0.0-nightly-20250423023843",
|
|
41
|
+
"@backstage/config": "1.3.2",
|
|
42
|
+
"@backstage/errors": "1.2.7",
|
|
43
|
+
"@backstage/plugin-events-node": "0.0.0-nightly-20250423023843",
|
|
44
|
+
"@backstage/types": "1.2.1",
|
|
45
|
+
"@google-cloud/pubsub": "^4.10.0",
|
|
46
|
+
"@opentelemetry/api": "^1.9.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@backstage/backend-defaults": "0.0.0-nightly-20250423023843",
|
|
50
|
+
"@backstage/backend-test-utils": "0.0.0-nightly-20250423023843",
|
|
51
|
+
"@backstage/cli": "0.0.0-nightly-20250423023843",
|
|
52
|
+
"@backstage/plugin-events-backend": "0.0.0-nightly-20250423023843",
|
|
53
|
+
"wait-for-expect": "^3.0.2"
|
|
54
|
+
},
|
|
55
|
+
"configSchema": "config.d.ts",
|
|
56
|
+
"typesVersions": {
|
|
57
|
+
"*": {
|
|
58
|
+
"package.json": [
|
|
59
|
+
"package.json"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|