surfliner-metadata_consumer 0.1.0.pre.alpha.4 → 0.1.0.pre.alpha.5
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.
- checksums.yaml +4 -4
- data/CHANGES.md +34 -1
- data/Dockerfile +1 -1
- data/Gemfile.lock +17 -11
- data/README.md +60 -22
- data/bin/index-on-publish +33 -0
- data/bin/simulate-publish-event +30 -20
- data/lib/surfliner/metadata_consumer/consumer.rb +41 -17
- data/lib/surfliner/metadata_consumer/version.rb +1 -2
- data/lib/surfliner/metadata_consumer.rb +4 -1
- data/lib/surfliner/mq/connection.rb +127 -0
- data/lib/surfliner/mq/connection_config.rb +62 -0
- data/lib/surfliner/mq/queue_config.rb +49 -0
- data/lib/surfliner/mq/topic.rb +65 -0
- data/lib/surfliner/mq/topic_config.rb +37 -0
- data/lib/surfliner/mq.rb +4 -0
- data/lib/surfliner/util/assert.rb +21 -0
- data/lib/surfliner/util.rb +4 -0
- metadata +61 -27
- data/bin/daylight-index-listen +0 -23
- data/lib/surfliner/metadata_consumer/mq_config.rb +0 -79
- data/lib/surfliner/metadata_consumer/mq_connection.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fff0fe7a29854ecb3b75f91ed5aa1e5abdbf6fe7fc625b4b117a3e2c4e4805c
|
4
|
+
data.tar.gz: 534536637b909d309f90095aaba4e9cb51a75d4ca92a78531971d3550ea42bfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e20f325aa14042b5ed7e950803d8a045834df543da1f5c4a8f30297645015d97add22a5de38e01d15a6ab74adb856777bba4fccfb609d50bd622c9fae1f51d5
|
7
|
+
data.tar.gz: a69109802f8c408f077b2fc2d1c748c02a900eda042d6e40a4423ee3257b39d2dde892eecf9c77c690df06aa53d367136a8246f3d8bc69114d0e1278326b4531
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# 0.1.0.pre.alpha.5 (2025-03-25)
|
2
|
+
|
3
|
+
In order to support using one connection (and one underlying `Bunny::Session`)
|
4
|
+
with multiple topics, queues, and/or routing keys, as well as publishing to
|
5
|
+
a topic without creating or subscribing to a queue, this release introduces
|
6
|
+
the following **breaking changes**:
|
7
|
+
|
8
|
+
- RabbitMQ-related configuration and connection code (i.e. not specific to
|
9
|
+
the consumer/handler setup) has been moved from `Surfliner::MetadataConsumer`
|
10
|
+
to `Surfliner::Mq`. This may move to a separate gem in a future release.
|
11
|
+
- `MetadataConsumer::MqConnection` is now `Mq::Connection`
|
12
|
+
- `MetadataConsumer::MqConfig` is now `Mq::ConnectionConfig`
|
13
|
+
- `ConnectionConfig` no longer includes topic or queue configuration;
|
14
|
+
topics are configured with `TopicConfig` and queues with
|
15
|
+
`QueueConfig`.
|
16
|
+
- The `MqConnection#open` method, which yielded a `Bunny::Queue`, has been
|
17
|
+
replaced by the `Connection#with_topic` method, which yields an
|
18
|
+
`Mq::Topic` object wrapping a single topic; call `Mq::Topic#bind_queue`
|
19
|
+
to bind to a queue. Like the removed`#open`, `#with_topic` opens and
|
20
|
+
closes the connection implicitly and allows connecting only to a single
|
21
|
+
topic.
|
22
|
+
- The `Mq::Connection#topic_from(config)` method allows you to connect to
|
23
|
+
any number of topics, but you must open and close the connection
|
24
|
+
explicitly.
|
25
|
+
- The `MqConnection#publish` method has been moved to `Mq::Topic` and can
|
26
|
+
now take an explicit routing key, although if one is not provided it will
|
27
|
+
still default to `ENV["RABBITMQ_PLATFORM_ROUTING_KEY"]`.
|
28
|
+
- `MetadataConsumer::Consumer` now requires an explicit `Mq:Connection`
|
29
|
+
instead of creating one by default.
|
30
|
+
|
31
|
+
In addition, the `daylight-index-listen` script has been renamed to the more
|
32
|
+
generic `index-on-publish`.
|
33
|
+
|
1
34
|
# 0.1.0.pre.alpha.4 (2025-03-17)
|
2
35
|
|
3
36
|
- lower required Ruby version to 3.2
|
@@ -19,4 +52,4 @@
|
|
19
52
|
|
20
53
|
# 0.1.0.pre.alpha (2025-02-12)
|
21
54
|
|
22
|
-
- Initial release
|
55
|
+
- Initial release
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
surfliner-metadata_consumer (0.1.0.pre.alpha.
|
4
|
+
surfliner-metadata_consumer (0.1.0.pre.alpha.5)
|
5
5
|
bunny (~> 2.23)
|
6
6
|
opentelemetry-exporter-otlp (~> 0.26.3)
|
7
7
|
opentelemetry-instrumentation-all (~> 0.60.0)
|
@@ -14,11 +14,11 @@ GEM
|
|
14
14
|
addressable (2.8.7)
|
15
15
|
public_suffix (>= 2.0.2, < 7.0)
|
16
16
|
amq-protocol (2.3.3)
|
17
|
-
ast (2.4.
|
17
|
+
ast (2.4.3)
|
18
18
|
bigdecimal (3.1.9)
|
19
19
|
builder (3.3.0)
|
20
|
-
bunny (2.
|
21
|
-
amq-protocol (~> 2.3
|
20
|
+
bunny (2.24.0)
|
21
|
+
amq-protocol (~> 2.3)
|
22
22
|
sorted_set (~> 1, >= 1.0.2)
|
23
23
|
ci_reporter (2.1.0)
|
24
24
|
builder (>= 2.1.2)
|
@@ -34,7 +34,7 @@ GEM
|
|
34
34
|
debug (1.9.2)
|
35
35
|
irb (~> 1.10)
|
36
36
|
reline (>= 0.3.8)
|
37
|
-
diff-lcs (1.6.
|
37
|
+
diff-lcs (1.6.1)
|
38
38
|
docile (1.4.1)
|
39
39
|
dotenv (2.8.1)
|
40
40
|
faraday (2.12.2)
|
@@ -43,8 +43,9 @@ GEM
|
|
43
43
|
logger
|
44
44
|
faraday-net_http (3.4.0)
|
45
45
|
net-http (>= 0.5.0)
|
46
|
+
github-markup (5.0.1)
|
46
47
|
google-protobuf (3.25.6)
|
47
|
-
googleapis-common-protos-types (1.
|
48
|
+
googleapis-common-protos-types (1.19.0)
|
48
49
|
google-protobuf (>= 3.18, < 5.a)
|
49
50
|
hashdiff (1.1.2)
|
50
51
|
io-console (0.8.0)
|
@@ -255,12 +256,13 @@ GEM
|
|
255
256
|
opentelemetry-semantic_conventions (1.11.0)
|
256
257
|
opentelemetry-api (~> 1.0)
|
257
258
|
parallel (1.26.3)
|
258
|
-
parser (3.3.7.
|
259
|
+
parser (3.3.7.2)
|
259
260
|
ast (~> 2.4.1)
|
260
261
|
racc
|
261
262
|
pp (0.6.2)
|
262
263
|
prettyprint
|
263
264
|
prettyprint (0.2.0)
|
265
|
+
prism (1.4.0)
|
264
266
|
psych (5.2.3)
|
265
267
|
date
|
266
268
|
stringio
|
@@ -269,8 +271,9 @@ GEM
|
|
269
271
|
rainbow (3.1.1)
|
270
272
|
rake (13.2.1)
|
271
273
|
rbtree (0.4.6)
|
272
|
-
rdoc (6.
|
274
|
+
rdoc (6.13.0)
|
273
275
|
psych (>= 4.0.0)
|
276
|
+
redcarpet (3.6.1)
|
274
277
|
regexp_parser (2.10.0)
|
275
278
|
reline (0.6.0)
|
276
279
|
io-console (~> 0.5)
|
@@ -302,8 +305,9 @@ GEM
|
|
302
305
|
rubocop-ast (>= 1.38.0, < 2.0)
|
303
306
|
ruby-progressbar (~> 1.7)
|
304
307
|
unicode-display_width (>= 2.4.0, < 4.0)
|
305
|
-
rubocop-ast (1.
|
306
|
-
parser (>= 3.3.
|
308
|
+
rubocop-ast (1.43.0)
|
309
|
+
parser (>= 3.3.7.2)
|
310
|
+
prism (~> 1.4)
|
307
311
|
rubocop-performance (1.24.0)
|
308
312
|
lint_roller (~> 1.1)
|
309
313
|
rubocop (>= 1.72.1, < 2.0)
|
@@ -334,7 +338,7 @@ GEM
|
|
334
338
|
standard-performance (1.7.0)
|
335
339
|
lint_roller (~> 1.1)
|
336
340
|
rubocop-performance (~> 1.24.0)
|
337
|
-
stringio (3.1.
|
341
|
+
stringio (3.1.6)
|
338
342
|
unicode-display_width (3.1.4)
|
339
343
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
340
344
|
unicode-emoji (4.0.4)
|
@@ -354,7 +358,9 @@ DEPENDENCIES
|
|
354
358
|
colorize (~> 0.8)
|
355
359
|
debug (~> 1.9.2)
|
356
360
|
dotenv (~> 2.7)
|
361
|
+
github-markup (~> 5.0)
|
357
362
|
rake (~> 13.0)
|
363
|
+
redcarpet (~> 3.6)
|
358
364
|
rspec (~> 3.13)
|
359
365
|
simplecov (~> 0.22)
|
360
366
|
simplecov-cobertura (~> 2.1)
|
data/README.md
CHANGED
@@ -36,23 +36,59 @@ if prior steps succeed.
|
|
36
36
|
|
37
37
|
## Configuration
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
> Note: Sample values taken from Surfliner's development
|
40
|
+
> [daylight-listener.sh](https://gitlab.com/surfliner/surfliner/-/blob/trunk/docker-compose/env/daylight-listener.sh).)
|
41
|
+
|
42
|
+
An `Mq::Connection` object, suitable for listening or publishing, can be configured
|
43
|
+
explicitly with a `ConnectionConfig` object, or implicitly, reading a default
|
44
|
+
configuration with `ConnectionConfig#from_env`. `ConnectionConfig#from_env` expects
|
45
|
+
the following environment variables:
|
46
|
+
|
47
|
+
| Variable | Sample value | Description |
|
48
|
+
|-----------------------------|--------------|------------------------------|
|
49
|
+
| `RABBITMQ_HOST` | `rabbitmq` | Hostname of RabbitMQ server |
|
50
|
+
| `RABBITMQ_NODE_PORT_NUMBER` | `5672` | Port name of RabbitMQ server |
|
51
|
+
| `RABBITMQ_USERNAME` | `user` | RabbitMQ username |
|
52
|
+
| `RABBITMQ_PASSWORD` | `bitnami` | RabbitMQ password |
|
53
|
+
|
54
|
+
The `Mq::Connection#with_topic` and `Mq::Connection#topic_from` can either take an
|
55
|
+
explicit `TopicConfig` object, or implicitly read a default configuration with
|
56
|
+
`TopicConfig#from_env`. `TopicConfig#from_env` expects the following environment variables:
|
57
|
+
|
58
|
+
| Variable | Sample value | Description |
|
59
|
+
|------------------|----------------------|---------------------|
|
60
|
+
| `RABBITMQ_TOPIC` | `surfliner.metadata` | RabbitMQ topic name |
|
61
|
+
|
62
|
+
Note that `TopicConfig#from_env` also accepts keyword options, which are passed through
|
63
|
+
to [`Bunny::Channel#topic`](https://api.rubybunny.info/Bunny/Channel.html#topic-instance_method).
|
64
|
+
E.g.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
topic_config = TopicConfig.from_env(durable: true, auto_delete: true)
|
68
|
+
connection.with_topic(topic_config) do |topic|
|
69
|
+
# ...
|
70
|
+
end
|
71
|
+
```
|
42
72
|
|
43
|
-
|
73
|
+
Similarly, `Mq::Topic#bind_queue` can either take an explicit `QueueConfig`, or implicitly
|
74
|
+
read a default with `QueConfig#from_env`. `QueueConfig#from_env` expects the following environment
|
75
|
+
variables:
|
44
76
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
RABBITMQ_QUEUE=surfliner.metadata
|
50
|
-
RABBITMQ_PLATFORM_ROUTING_KEY=surfliner.metadata.daylight
|
51
|
-
RABBITMQ_TOPIC=surfliner.metadata
|
52
|
-
RABBITMQ_USERNAME=user
|
53
|
-
```
|
77
|
+
| Variable | Sample value | Description |
|
78
|
+
|---------------------------------|-------------------------------|----------------------|
|
79
|
+
| `RABBITMQ_QUEUE` | `surfliner.metadata` | RabbitMQ queue name |
|
80
|
+
| `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
|
54
81
|
|
55
|
-
|
82
|
+
And `QueueConfig#from_env` similarly accepts keyword options, which are forwarded to
|
83
|
+
[`Bunny::Channel#queue`](https://api.rubybunny.info/Bunny/Channel.html#queue-instance_method):
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
connection.with_topic(topic_config) do |topic|
|
87
|
+
queue_config = QueueConfig.from_env(exclusive: true, durable: true)
|
88
|
+
queue = topic.bind_queue(queue_config)
|
89
|
+
# ...
|
90
|
+
end
|
91
|
+
```
|
56
92
|
|
57
93
|
The Solr / Daylight handler implementation (see below) additionally expects
|
58
94
|
a configured `SOLR_URL`, e.g.:
|
@@ -74,17 +110,19 @@ The `Surfliner::MetadataConsumer::Solr` package contains a handler that retrieve
|
|
74
110
|
metadata for `:published`/`:updated` events, transforms results for indexing, and
|
75
111
|
updates a target Solr index.
|
76
112
|
|
77
|
-
|
113
|
+
The `bin/index-on-publish` script starts a consumer using this handler, and accepts
|
114
|
+
(but does not require) the following environment variables:
|
78
115
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
116
|
+
| Variable | Sample value | Default | Description |
|
117
|
+
|---------------------|-------------------------------|----------------|----------------------------------|
|
118
|
+
| `OTEL_SDK_DISABLED` | `true` | (not set) | Whether to disable OpenTelemetry |
|
119
|
+
| `OTEL_SERVICE_NAME` | `surfliner-daylight-consumer` | name of script | OpenTelemetry service name |
|
120
|
+
| `OTEL_TRACER_NAME` | `DaylightConsumerTracer` | name of script | OpenTelemetry tracer name |
|
83
121
|
|
84
122
|
### Running the service
|
85
123
|
|
86
124
|
<!--
|
87
|
-
TODO: Dockerfile shouldn't be
|
125
|
+
TODO: Dockerfile shouldn't be index-on-publish specific
|
88
126
|
-->
|
89
127
|
|
90
128
|
Build the listener Dockerfile and tag it with the name `solr-listener`:
|
@@ -101,5 +139,5 @@ docker run --env-file <env-file> solr-listener
|
|
101
139
|
|
102
140
|
## Utility scripts
|
103
141
|
|
104
|
-
- `bin/
|
105
|
-
- `bin/simulate-publish-event` posts a publish event to the queue configured with `
|
142
|
+
- `bin/index-on-publish` script starts a consumer using `MetadataConsumer::Solr::MessageHandler`.
|
143
|
+
- `bin/simulate-publish-event` posts a publish event to the queue configured with `Mq::Connection`
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "bundler/setup"
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
require "surfliner/metadata_consumer"
|
6
|
+
require "surfliner/mq"
|
7
|
+
|
8
|
+
$stdout.sync = true # don't buffer log output
|
9
|
+
logger = Logger.new($stdout).tap do |logger|
|
10
|
+
logger.level = ENV.fetch("LOG_LEVEL", Logger::INFO)
|
11
|
+
end
|
12
|
+
|
13
|
+
unless ENV["OTEL_SDK_DISABLED"] == "true"
|
14
|
+
require "opentelemetry/sdk"
|
15
|
+
require "opentelemetry-exporter-otlp"
|
16
|
+
|
17
|
+
otel_service_name = ENV.fetch('OTEL_SERVICE_NAME', File.basename(__FILE__))
|
18
|
+
otel_tracer_name = ENV.fetch('OTEL_TRACER_NAME', File.basename(__FILE__))
|
19
|
+
|
20
|
+
OpenTelemetry::SDK.configure do |c|
|
21
|
+
c.service_name = otel_service_name
|
22
|
+
c.use_all # enables auto instrumentation for Bunny, Net::HTTP, etc...
|
23
|
+
end
|
24
|
+
|
25
|
+
tracer = OpenTelemetry.tracer_provider.tracer(otel_tracer_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
consumer = Surfliner::MetadataConsumer::Consumer.new(
|
29
|
+
connection: Surfliner::Mq::Connection.new(logger: logger),
|
30
|
+
tracer: (tracer if defined?(tracer)),
|
31
|
+
handler: Surfliner::MetadataConsumer::Solr::MessageHandler
|
32
|
+
)
|
33
|
+
consumer.run(topic_config: Surfliner::Mq::TopicConfig.from_env(auto_delete: true))
|
data/bin/simulate-publish-event
CHANGED
@@ -4,24 +4,24 @@ require "json"
|
|
4
4
|
require "logger"
|
5
5
|
|
6
6
|
require "surfliner/metadata_consumer"
|
7
|
+
require "surfliner/mq"
|
7
8
|
|
8
9
|
module Surfliner
|
9
10
|
module MetadataConsumer
|
10
11
|
class Publisher
|
12
|
+
attr_reader :topic
|
13
|
+
attr_reader :api_base
|
14
|
+
attr_reader :routing_key
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def initialize(logger:, topic_opts: {}, queue_opts: {})
|
17
|
-
@logger = logger
|
18
|
-
@topic_opts = topic_opts
|
19
|
-
@queue_opts = queue_opts
|
16
|
+
def initialize(topic, api_base:, routing_key:)
|
17
|
+
@topic = topic
|
18
|
+
@api_base = api_base
|
19
|
+
@routing_key = routing_key
|
20
20
|
end
|
21
21
|
|
22
22
|
def publish(resource_id)
|
23
23
|
payload = payload_for(resource_id)
|
24
|
-
|
24
|
+
topic.publish(payload, routing_key:)
|
25
25
|
end
|
26
26
|
|
27
27
|
def payload_for(id)
|
@@ -30,27 +30,37 @@ module Surfliner
|
|
30
30
|
time_stamp: DateTime.now }.to_json
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@
|
33
|
+
class << self
|
34
|
+
# Connects with default (environment-configured) settings, yields a publisher
|
35
|
+
# for the specified topic, and closes the connection after the provided block
|
36
|
+
# completes.
|
37
|
+
#
|
38
|
+
# @param logger [Logger] the logger
|
39
|
+
# @param topic_opts [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
40
|
+
#
|
41
|
+
# @yieldparam publisher [Publisher] the publisher
|
42
|
+
def connect(logger:, **topic_opts)
|
43
|
+
topic_config = Mq::TopicConfig.from_env(**topic_opts)
|
44
|
+
Mq::Connection.new(logger:).with_topic(topic_config) do |topic|
|
45
|
+
yield Publisher.new(
|
46
|
+
topic,
|
47
|
+
api_base: ENV.fetch("METADATA_API_URL_BASE", "http://metadata.test/resources"),
|
48
|
+
routing_key: ENV.fetch("RABBITMQ_PLATFORM_ROUTING_KEY")
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
39
52
|
end
|
40
53
|
end
|
41
54
|
end
|
42
55
|
end
|
43
56
|
|
44
57
|
$stdout.sync = true # don't buffer log output
|
45
|
-
logger
|
58
|
+
logger = Logger.new($stdout).tap do |logger|
|
46
59
|
logger.level = ENV.fetch("LOG_LEVEL", Logger::INFO)
|
47
60
|
end
|
48
61
|
|
49
|
-
|
50
|
-
begin
|
62
|
+
Surfliner::MetadataConsumer::Publisher.connect(logger:, auto_delete: true) do |publisher|
|
51
63
|
ARGV.each do |resource_id|
|
52
64
|
publisher.publish(resource_id)
|
53
65
|
end
|
54
|
-
ensure
|
55
|
-
publisher.connection.close
|
56
66
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require "rsolr"
|
2
|
-
require "surfliner/
|
2
|
+
require "surfliner/util/assert"
|
3
|
+
require "surfliner/mq/connection"
|
4
|
+
require "surfliner/mq/topic_config"
|
5
|
+
require "surfliner/mq/queue_config"
|
3
6
|
require "surfliner/metadata_consumer/solr/message_handler"
|
4
7
|
|
5
8
|
module Surfliner
|
@@ -7,29 +10,40 @@ module Surfliner
|
|
7
10
|
# A metadata consumer that subscribes to a RabbitMQ queue and passes
|
8
11
|
# messages to the specified handler.
|
9
12
|
class Consumer
|
10
|
-
|
13
|
+
include Util::Assert
|
14
|
+
|
15
|
+
# @return [Mq::Connection] the connection
|
16
|
+
attr_reader :connection
|
17
|
+
|
18
|
+
# @return [OpenTelemetry::Trace::Tracer] OpenTelemetry tracer
|
19
|
+
attr_reader :tracer
|
20
|
+
|
21
|
+
# @return [#handle] an object accepting a JSON string
|
22
|
+
attr_reader :handler
|
11
23
|
|
12
24
|
# Initializes a new `Consumer`
|
25
|
+
#
|
26
|
+
# @param connection [Mq::Connection] the connection
|
27
|
+
# @param handler [#handle] an object accepting a JSON string
|
13
28
|
# @param tracer [OpenTelemetry::Trace::Tracer] OpenTelemetry tracer
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@connection = MqConnection.new(logger:)
|
18
|
-
@logger = logger
|
29
|
+
def initialize(connection:, handler:, tracer: nil)
|
30
|
+
@connection = not_nil!(connection, "connection")
|
31
|
+
@handler = not_nil!(handler, "handler")
|
19
32
|
@tracer = tracer
|
20
|
-
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Logger] log message destination
|
36
|
+
def logger
|
37
|
+
connection.logger
|
21
38
|
end
|
22
39
|
|
23
40
|
# Starts listening to the message queue and passing messages to the handler.
|
24
|
-
# @param
|
25
|
-
# @param
|
26
|
-
def run(
|
27
|
-
connection.
|
28
|
-
queue.
|
29
|
-
|
30
|
-
handle(payload_json)
|
31
|
-
end
|
32
|
-
end
|
41
|
+
# @param topic_config [TopicConfig] topic configuration
|
42
|
+
# @param queue_config [QueueConfig] queue configuration
|
43
|
+
def run(topic_config: Mq::TopicConfig.from_env, queue_config: Mq::QueueConfig.from_env)
|
44
|
+
connection.with_topic(topic_config) do |topic|
|
45
|
+
queue = topic.bind_queue(queue_config)
|
46
|
+
queue.subscribe(block: true, &method(:on_delivery))
|
33
47
|
end
|
34
48
|
end
|
35
49
|
|
@@ -43,6 +57,16 @@ module Surfliner
|
|
43
57
|
rescue => err
|
44
58
|
logger.error(" [!] failed to handle message: #{err.full_message}")
|
45
59
|
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def on_delivery(_delivery_info, _properties, payload_json)
|
64
|
+
return handle(payload_json) unless tracer
|
65
|
+
|
66
|
+
tracer.in_span("surfliner metadata consumer message") do |_span, _context|
|
67
|
+
handle(payload_json)
|
68
|
+
end
|
69
|
+
end
|
46
70
|
end
|
47
71
|
end
|
48
72
|
end
|
@@ -1 +1,4 @@
|
|
1
|
-
Dir.glob(File.expand_path("metadata_consumer/*.rb", __dir__)).
|
1
|
+
Dir.glob(File.expand_path("metadata_consumer/*.rb", __dir__)).each(&method(:require))
|
2
|
+
|
3
|
+
# Functionality specific to consuming RabbitMQ feeds
|
4
|
+
module Surfliner::MetadataConsumer; end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "bunny"
|
2
|
+
require "surfliner/util/assert"
|
3
|
+
|
4
|
+
module Surfliner
|
5
|
+
module Mq
|
6
|
+
# An object encapsulating a RabbitMQ connection.
|
7
|
+
class Connection
|
8
|
+
include Util::Assert
|
9
|
+
|
10
|
+
# @return [Logger] The logger
|
11
|
+
attr_reader :logger
|
12
|
+
|
13
|
+
# @return [Bunny::Session] The current RabbitMQ session
|
14
|
+
attr_reader :session
|
15
|
+
|
16
|
+
# @return [Bunny::Channel] The channel being listened to
|
17
|
+
attr_reader :channel
|
18
|
+
|
19
|
+
# @return [ConnectionConfig] The configuration
|
20
|
+
attr_reader :config
|
21
|
+
|
22
|
+
# Initializes a new `MqConnection`.
|
23
|
+
#
|
24
|
+
# @param logger [Logger] the logger
|
25
|
+
# @param config [ConnectionConfig] the configuration
|
26
|
+
def initialize(logger:, config: ConnectionConfig.from_env)
|
27
|
+
@logger = not_nil!(logger)
|
28
|
+
@config = not_nil!(config)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Opens a connection.
|
32
|
+
# @return [self]
|
33
|
+
# @raise [IOError] if already connected
|
34
|
+
def connect
|
35
|
+
raise IOError, "RabbitMQ session #{session} already open." if open?
|
36
|
+
|
37
|
+
logger.info("Rabbitmq message broker session url: #{config.redacted_url}")
|
38
|
+
@session = Bunny.new(config.session_url, logger: logger)
|
39
|
+
connect_on(session)
|
40
|
+
@channel = session.create_channel
|
41
|
+
|
42
|
+
self
|
43
|
+
rescue Bunny::TCPConnectionFailed => err
|
44
|
+
# TODO: realistically, this only happens in session.start, where we're eating it
|
45
|
+
logger.error("Connection to #{config.redacted_url} failed")
|
46
|
+
raise err
|
47
|
+
rescue Bunny::PossibleAuthenticationFailureError => err
|
48
|
+
# TODO: realistically, this only happens in session.start, where we're eating it
|
49
|
+
logger.error("Failed to authenticate to #{config.redacted_url}")
|
50
|
+
raise err
|
51
|
+
end
|
52
|
+
|
53
|
+
# Opens a session, yields a client for the specified topic, and closes the
|
54
|
+
# session after the provided block completes.
|
55
|
+
# @param config [TopicConfig] topic configuration
|
56
|
+
# @yield [MqTopic] A client for the topic
|
57
|
+
def with_topic(config = TopicConfig.from_env)
|
58
|
+
connect
|
59
|
+
|
60
|
+
yield topic_from(config)
|
61
|
+
ensure
|
62
|
+
close
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a client for the specified topic. Note that this does _not_ open
|
66
|
+
# or close the connection.
|
67
|
+
#
|
68
|
+
# @param config [TopicConfig] topic configuration
|
69
|
+
# @return [Topic] A client for the topic
|
70
|
+
# @raise [IOError] if the connection is not open
|
71
|
+
def topic_from(config)
|
72
|
+
raise IOError, "RabbitMQ session not open" unless open?
|
73
|
+
|
74
|
+
Topic.from_config(config, channel:, logger:)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Closes the session.
|
78
|
+
def close
|
79
|
+
return unless channel
|
80
|
+
return if channel.closed?
|
81
|
+
logger.info("closing channel")
|
82
|
+
channel.close
|
83
|
+
ensure
|
84
|
+
logger.info("closing session")
|
85
|
+
session&.close
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [true, false] True if the session is open, false otherwise
|
89
|
+
def open?
|
90
|
+
session&.status == :open
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Symbol, nil] The session status, or nil if there is no session
|
94
|
+
def status
|
95
|
+
session&.status
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [String] The RabbitMQ hostname
|
99
|
+
def host
|
100
|
+
config.host
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [String] The RabbitMQ port
|
104
|
+
def port
|
105
|
+
config.port
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def connect_on(session, timeout = 120)
|
111
|
+
timer = 0
|
112
|
+
logger.info "Trying to open queue session with timeout=#{timeout}"
|
113
|
+
while timer < timeout
|
114
|
+
begin
|
115
|
+
session.start
|
116
|
+
rescue
|
117
|
+
# TODO: do we actually want to rescue from everything?
|
118
|
+
end
|
119
|
+
return session if session.status == :open
|
120
|
+
sleep 1
|
121
|
+
timer += 1
|
122
|
+
end
|
123
|
+
raise "Failed to connect to queue."
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Surfliner
|
2
|
+
module Mq
|
3
|
+
# An object encapsulating RabbitMQ configuration.
|
4
|
+
class ConnectionConfig
|
5
|
+
# @return [String] The RabbitMQ hostname
|
6
|
+
attr_reader :host
|
7
|
+
|
8
|
+
# @return [String] The RabbitMQ AMQP port
|
9
|
+
attr_reader :port
|
10
|
+
|
11
|
+
# @return [String] The RabbitMQ username
|
12
|
+
attr_reader :username
|
13
|
+
|
14
|
+
# @return [String] The RabbitMQ passsword
|
15
|
+
attr_reader :password
|
16
|
+
|
17
|
+
# Initializes a new `MqConfig` object.
|
18
|
+
# @param host [String] RabbitMQ hostname
|
19
|
+
# @param port [String] RabbitMQ AMQP port
|
20
|
+
# @param username [String] RabbitMQ username
|
21
|
+
# @param password [String] RabbitMQ passsword
|
22
|
+
def initialize(host:, port:, username:, password:)
|
23
|
+
@host = host
|
24
|
+
@port = port
|
25
|
+
@username = username
|
26
|
+
@password = password
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
# Reads RabbitMQ configuration from environment variables and
|
31
|
+
# returns it as a new `ConnectionConfig` object.
|
32
|
+
#
|
33
|
+
# | Variable | Sample value | Description |
|
34
|
+
# |-----------------------------|--------------|------------------------------|
|
35
|
+
# | `RABBITMQ_HOST` | `rabbitmq` | Hostname of RabbitMQ server |
|
36
|
+
# | `RABBITMQ_NODE_PORT_NUMBER` | `5672` | Port name of RabbitMQ server |
|
37
|
+
# | `RABBITMQ_USERNAME` | `user` | RabbitMQ username |
|
38
|
+
# | `RABBITMQ_PASSWORD` | `bitnami` | RabbitMQ password |
|
39
|
+
#
|
40
|
+
# @return [ConnectionConfig] The configuration.
|
41
|
+
def from_env
|
42
|
+
ConnectionConfig.new(
|
43
|
+
host: ENV.fetch("RABBITMQ_HOST"),
|
44
|
+
port: ENV.fetch("RABBITMQ_NODE_PORT_NUMBER"),
|
45
|
+
username: ENV.fetch("RABBITMQ_USERNAME"),
|
46
|
+
password: ENV.fetch("RABBITMQ_PASSWORD")
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String] the connection URL as a string
|
52
|
+
def session_url
|
53
|
+
@session_url ||= "amqp://#{username}:#{password}@#{host}:#{port}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String] the connection URL as a string, without the password
|
57
|
+
def redacted_url
|
58
|
+
@redacted_url ||= session_url.sub(password, "REDACTED")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Surfliner
|
2
|
+
module Mq
|
3
|
+
# Encapsulates queue configuration
|
4
|
+
class QueueConfig
|
5
|
+
# @return [String] The queue to listen to
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# @return [String] platform routing key to listen to
|
9
|
+
attr_reader :routing_key
|
10
|
+
|
11
|
+
# @return [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# @param name [String] queue exchange to listen to
|
15
|
+
# @param routing_key [String] platform routing key to listen to
|
16
|
+
# @param options [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
17
|
+
def initialize(name:, routing_key:, options: {})
|
18
|
+
@name = name
|
19
|
+
@routing_key = routing_key
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Returns a default (environment-variable-based) configuration with the
|
25
|
+
# specified options.
|
26
|
+
#
|
27
|
+
# | Variable | Sample value | Description |
|
28
|
+
# |---------------------------------|-------------------------------|----------------------|
|
29
|
+
# | `RABBITMQ_QUEUE` | `surfliner.metadata` | RabbitMQ queue name |
|
30
|
+
# | `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
|
31
|
+
#
|
32
|
+
# @param options [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
33
|
+
# @return [QueueConfig] The configuration.
|
34
|
+
def from_env(**options)
|
35
|
+
QueueConfig.new(
|
36
|
+
name: ENV.fetch("RABBITMQ_QUEUE"),
|
37
|
+
routing_key: default_routing_key,
|
38
|
+
options:
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String] The default routing key from `ENV["RABBITMQ_PLATFORM_ROUTING_KEY"]`
|
43
|
+
def default_routing_key
|
44
|
+
ENV.fetch("RABBITMQ_PLATFORM_ROUTING_KEY")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "surfliner/util/assert"
|
2
|
+
|
3
|
+
module Surfliner
|
4
|
+
module Mq
|
5
|
+
# Encapsulates a RabbitMQ topic
|
6
|
+
class Topic
|
7
|
+
include Surfliner::Util::Assert
|
8
|
+
|
9
|
+
# @return [String] The name of the topic
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @return [Bunny::Channel] The channel being used
|
13
|
+
attr_reader :channel
|
14
|
+
|
15
|
+
# @return [Logger] The logger
|
16
|
+
attr_reader :logger
|
17
|
+
|
18
|
+
# @return [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
# @param name [String] The name of the topic
|
22
|
+
# @param channel [Bunny::Channel] The channel to use
|
23
|
+
# @param logger [Logger] the logger
|
24
|
+
# @param options [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
25
|
+
def initialize(name, channel:, logger:, options: {})
|
26
|
+
@name = not_nil!(name, "topic name")
|
27
|
+
@channel = not_nil!(channel, "channel")
|
28
|
+
@logger = not_nil!(logger, "logger")
|
29
|
+
@options = not_nil!(options, "options")
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Bunny::Exchange] The exchange for the topic
|
33
|
+
def exchange
|
34
|
+
@exchange ||= channel.topic(name, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Publishes the specified payload
|
38
|
+
# @param payload [String] the payload to publish
|
39
|
+
# @param routing_key [String] platform routing key to publish to
|
40
|
+
# @return [Bunny::Exchange] see #exchange
|
41
|
+
def publish(payload, routing_key: QueueConfig.default_routing_key)
|
42
|
+
logger.info "Publishing to #{routing_key} with payload: #{payload}"
|
43
|
+
exchange.publish(payload, routing_key:)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param config [QueueConfig] queue configuration
|
47
|
+
# @return [Bunny::Queue] the queue
|
48
|
+
def bind_queue(config = QueueConfig.from_env)
|
49
|
+
channel.queue(config.name, config.options).tap do |q|
|
50
|
+
q.bind(exchange, routing_key: config.routing_key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
# Creates a new `MqTopic` from the specified configuration
|
56
|
+
# @param config [TopicConfig] the configuration
|
57
|
+
# @param channel [Bunny::Channel] The channel to use
|
58
|
+
# @param logger [Logger] the logger
|
59
|
+
def from_config(config, channel:, logger:)
|
60
|
+
new(config.name, channel:, logger:, options: config.options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Surfliner
|
2
|
+
module Mq
|
3
|
+
# Encapsulates topic configuration
|
4
|
+
class TopicConfig
|
5
|
+
# @return [String] The topic exchange to listen to
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# @return [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
# @param name [String] topic exchange to listen to
|
12
|
+
# @param options [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
13
|
+
def initialize(name:, options: {})
|
14
|
+
@name = name
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Returns a default (environment-variable-based) configuration with the
|
20
|
+
# specified options.
|
21
|
+
#
|
22
|
+
# | Variable | Sample value | Description |
|
23
|
+
# |------------------|----------------------|---------------------|
|
24
|
+
# | `RABBITMQ_TOPIC` | `surfliner.metadata` | RabbitMQ topic name |
|
25
|
+
#
|
26
|
+
# @param options [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
27
|
+
# @return [TopicConfig] The configuration.
|
28
|
+
def from_env(**options)
|
29
|
+
TopicConfig.new(
|
30
|
+
name: ENV.fetch("RABBITMQ_TOPIC"),
|
31
|
+
options:
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/surfliner/mq.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Surfliner
|
2
|
+
# Miscellaneous utility module
|
3
|
+
module Util
|
4
|
+
# Helper methods for argument validation
|
5
|
+
module Assert
|
6
|
+
class << self
|
7
|
+
include Assert
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param arg_value [Object, nil] the argument value
|
11
|
+
# @param arg_name [String] the name of the argument, for error messages
|
12
|
+
# @return arg_value
|
13
|
+
# @raise [ArgumentError] if `arg_value` is `nil`
|
14
|
+
def not_nil!(arg_value, arg_name = "argument")
|
15
|
+
return arg_value unless arg_value.nil?
|
16
|
+
|
17
|
+
raise ArgumentError, "#{arg_name} must not be nil"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: surfliner-metadata_consumer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.pre.alpha.
|
4
|
+
version: 0.1.0.pre.alpha.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Project Surfliner
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-03-
|
10
|
+
date: 2025-03-25 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: bunny
|
@@ -86,103 +86,117 @@ dependencies:
|
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '3'
|
88
88
|
- !ruby/object:Gem::Dependency
|
89
|
-
name:
|
89
|
+
name: ci_reporter_rspec
|
90
90
|
requirement: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
92
|
- - "~>"
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version: 1.
|
94
|
+
version: '1.0'
|
95
95
|
type: :development
|
96
96
|
prerelease: false
|
97
97
|
version_requirements: !ruby/object:Gem::Requirement
|
98
98
|
requirements:
|
99
99
|
- - "~>"
|
100
100
|
- !ruby/object:Gem::Version
|
101
|
-
version: 1.
|
101
|
+
version: '1.0'
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
|
-
name:
|
103
|
+
name: colorize
|
104
104
|
requirement: !ruby/object:Gem::Requirement
|
105
105
|
requirements:
|
106
106
|
- - "~>"
|
107
107
|
- !ruby/object:Gem::Version
|
108
|
-
version: '
|
108
|
+
version: '0.8'
|
109
109
|
type: :development
|
110
110
|
prerelease: false
|
111
111
|
version_requirements: !ruby/object:Gem::Requirement
|
112
112
|
requirements:
|
113
113
|
- - "~>"
|
114
114
|
- !ruby/object:Gem::Version
|
115
|
-
version: '
|
115
|
+
version: '0.8'
|
116
116
|
- !ruby/object:Gem::Dependency
|
117
|
-
name:
|
117
|
+
name: debug
|
118
118
|
requirement: !ruby/object:Gem::Requirement
|
119
119
|
requirements:
|
120
120
|
- - "~>"
|
121
121
|
- !ruby/object:Gem::Version
|
122
|
-
version:
|
122
|
+
version: 1.9.2
|
123
123
|
type: :development
|
124
124
|
prerelease: false
|
125
125
|
version_requirements: !ruby/object:Gem::Requirement
|
126
126
|
requirements:
|
127
127
|
- - "~>"
|
128
128
|
- !ruby/object:Gem::Version
|
129
|
-
version:
|
129
|
+
version: 1.9.2
|
130
130
|
- !ruby/object:Gem::Dependency
|
131
|
-
name:
|
131
|
+
name: dotenv
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
134
|
- - "~>"
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
136
|
+
version: '2.7'
|
137
137
|
type: :development
|
138
138
|
prerelease: false
|
139
139
|
version_requirements: !ruby/object:Gem::Requirement
|
140
140
|
requirements:
|
141
141
|
- - "~>"
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: '
|
143
|
+
version: '2.7'
|
144
144
|
- !ruby/object:Gem::Dependency
|
145
|
-
name:
|
145
|
+
name: github-markup
|
146
146
|
requirement: !ruby/object:Gem::Requirement
|
147
147
|
requirements:
|
148
148
|
- - "~>"
|
149
149
|
- !ruby/object:Gem::Version
|
150
|
-
version: '0
|
150
|
+
version: '5.0'
|
151
151
|
type: :development
|
152
152
|
prerelease: false
|
153
153
|
version_requirements: !ruby/object:Gem::Requirement
|
154
154
|
requirements:
|
155
155
|
- - "~>"
|
156
156
|
- !ruby/object:Gem::Version
|
157
|
-
version: '0
|
157
|
+
version: '5.0'
|
158
158
|
- !ruby/object:Gem::Dependency
|
159
|
-
name:
|
159
|
+
name: rake
|
160
160
|
requirement: !ruby/object:Gem::Requirement
|
161
161
|
requirements:
|
162
162
|
- - "~>"
|
163
163
|
- !ruby/object:Gem::Version
|
164
|
-
version: '
|
164
|
+
version: '13.0'
|
165
165
|
type: :development
|
166
166
|
prerelease: false
|
167
167
|
version_requirements: !ruby/object:Gem::Requirement
|
168
168
|
requirements:
|
169
169
|
- - "~>"
|
170
170
|
- !ruby/object:Gem::Version
|
171
|
-
version: '
|
171
|
+
version: '13.0'
|
172
172
|
- !ruby/object:Gem::Dependency
|
173
|
-
name:
|
173
|
+
name: redcarpet
|
174
174
|
requirement: !ruby/object:Gem::Requirement
|
175
175
|
requirements:
|
176
176
|
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
|
-
version: '
|
178
|
+
version: '3.6'
|
179
179
|
type: :development
|
180
180
|
prerelease: false
|
181
181
|
version_requirements: !ruby/object:Gem::Requirement
|
182
182
|
requirements:
|
183
183
|
- - "~>"
|
184
184
|
- !ruby/object:Gem::Version
|
185
|
-
version: '
|
185
|
+
version: '3.6'
|
186
|
+
- !ruby/object:Gem::Dependency
|
187
|
+
name: rspec
|
188
|
+
requirement: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - "~>"
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '3.13'
|
193
|
+
type: :development
|
194
|
+
prerelease: false
|
195
|
+
version_requirements: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - "~>"
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '3.13'
|
186
200
|
- !ruby/object:Gem::Dependency
|
187
201
|
name: simplecov
|
188
202
|
requirement: !ruby/object:Gem::Requirement
|
@@ -211,6 +225,20 @@ dependencies:
|
|
211
225
|
- - "~>"
|
212
226
|
- !ruby/object:Gem::Version
|
213
227
|
version: '2.1'
|
228
|
+
- !ruby/object:Gem::Dependency
|
229
|
+
name: standard
|
230
|
+
requirement: !ruby/object:Gem::Requirement
|
231
|
+
requirements:
|
232
|
+
- - "~>"
|
233
|
+
- !ruby/object:Gem::Version
|
234
|
+
version: '1.31'
|
235
|
+
type: :development
|
236
|
+
prerelease: false
|
237
|
+
version_requirements: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - "~>"
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '1.31'
|
214
242
|
- !ruby/object:Gem::Dependency
|
215
243
|
name: webmock
|
216
244
|
requirement: !ruby/object:Gem::Requirement
|
@@ -240,7 +268,7 @@ dependencies:
|
|
240
268
|
- !ruby/object:Gem::Version
|
241
269
|
version: 0.9.37
|
242
270
|
executables:
|
243
|
-
-
|
271
|
+
- index-on-publish
|
244
272
|
- simulate-publish-event
|
245
273
|
extensions: []
|
246
274
|
extra_rdoc_files: []
|
@@ -252,12 +280,10 @@ files:
|
|
252
280
|
- LICENSE
|
253
281
|
- README.md
|
254
282
|
- Rakefile
|
255
|
-
- bin/
|
283
|
+
- bin/index-on-publish
|
256
284
|
- bin/simulate-publish-event
|
257
285
|
- lib/surfliner/metadata_consumer.rb
|
258
286
|
- lib/surfliner/metadata_consumer/consumer.rb
|
259
|
-
- lib/surfliner/metadata_consumer/mq_config.rb
|
260
|
-
- lib/surfliner/metadata_consumer/mq_connection.rb
|
261
287
|
- lib/surfliner/metadata_consumer/payload.rb
|
262
288
|
- lib/surfliner/metadata_consumer/solr.rb
|
263
289
|
- lib/surfliner/metadata_consumer/solr/delete_handler.rb
|
@@ -265,6 +291,14 @@ files:
|
|
265
291
|
- lib/surfliner/metadata_consumer/solr/message_handler.rb
|
266
292
|
- lib/surfliner/metadata_consumer/superskunk_client.rb
|
267
293
|
- lib/surfliner/metadata_consumer/version.rb
|
294
|
+
- lib/surfliner/mq.rb
|
295
|
+
- lib/surfliner/mq/connection.rb
|
296
|
+
- lib/surfliner/mq/connection_config.rb
|
297
|
+
- lib/surfliner/mq/queue_config.rb
|
298
|
+
- lib/surfliner/mq/topic.rb
|
299
|
+
- lib/surfliner/mq/topic_config.rb
|
300
|
+
- lib/surfliner/util.rb
|
301
|
+
- lib/surfliner/util/assert.rb
|
268
302
|
homepage: https://gitlab.com/surfliner/metadata_consumer
|
269
303
|
licenses:
|
270
304
|
- MIT
|
data/bin/daylight-index-listen
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require "bundler/setup"
|
3
|
-
require "logger"
|
4
|
-
require "opentelemetry/sdk"
|
5
|
-
|
6
|
-
require "surfliner/metadata_consumer"
|
7
|
-
|
8
|
-
unless ENV["OTEL_SDK_DISABLED"] == "true"
|
9
|
-
OpenTelemetry::SDK.configure do |c|
|
10
|
-
c.service_name = "surfliner-daylight-consumer"
|
11
|
-
c.use_all # enables auto instrumentation for Bunny, Net::HTTP, etc...
|
12
|
-
end
|
13
|
-
end
|
14
|
-
tracer = OpenTelemetry.tracer_provider.tracer("DaylightConsumerTracer")
|
15
|
-
|
16
|
-
$stdout.sync = true # don't buffer log output
|
17
|
-
logger = Logger.new($stdout).tap do |logger|
|
18
|
-
logger.level = ENV.fetch("LOG_LEVEL", Logger::INFO)
|
19
|
-
end
|
20
|
-
|
21
|
-
handler = Surfliner::MetadataConsumer::Solr::MessageHandler
|
22
|
-
consumer = Surfliner::MetadataConsumer::Consumer.new(tracer:, logger:, handler:)
|
23
|
-
consumer.run
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module Surfliner
|
2
|
-
module MetadataConsumer
|
3
|
-
# An object encapsulating RabbitMQ configuration.
|
4
|
-
class MqConfig
|
5
|
-
# @return [String] The RabbitMQ hostname
|
6
|
-
attr_reader :host
|
7
|
-
|
8
|
-
# @return [String] The RabbitMQ AMQP port
|
9
|
-
attr_reader :port
|
10
|
-
|
11
|
-
# @return [String] The RabbitMQ username
|
12
|
-
attr_reader :username
|
13
|
-
|
14
|
-
# @return [String] The RabbitMQ passsword
|
15
|
-
attr_reader :password
|
16
|
-
|
17
|
-
# @return [String] The topic exchange to listen to
|
18
|
-
attr_reader :topic
|
19
|
-
|
20
|
-
# @return [String] The name of the queue to listen to
|
21
|
-
attr_reader :queue_name
|
22
|
-
|
23
|
-
# @return [String] The platform routing key to listen to
|
24
|
-
attr_reader :routing_key
|
25
|
-
|
26
|
-
# Initializes a new `MqConfig` object.
|
27
|
-
# @param host [The] RabbitMQ hostname
|
28
|
-
# @param port [The] RabbitMQ AMQP port
|
29
|
-
# @param username [The] RabbitMQ username
|
30
|
-
# @param password [The] RabbitMQ passsword
|
31
|
-
# @param topic [The] topic exchange to listen to
|
32
|
-
# @param queue_name [The] name of the queue to listen to
|
33
|
-
# @param routing_key [The] platform routing key to listen to
|
34
|
-
def initialize(host:, port:, username:, password:, topic:, queue_name:, routing_key:)
|
35
|
-
@host = host
|
36
|
-
@port = port
|
37
|
-
@username = username
|
38
|
-
@password = password
|
39
|
-
@topic = topic
|
40
|
-
@queue_name = queue_name
|
41
|
-
@routing_key = routing_key
|
42
|
-
end
|
43
|
-
|
44
|
-
class << self
|
45
|
-
# Reads RabbitMQ configuration from environment variables and
|
46
|
-
# returns it as a new `MqConfig` object.
|
47
|
-
#
|
48
|
-
# - `RABBITMQ_HOST` → `host`
|
49
|
-
# - `RABBITMQ_NODE_PORT_NUMBER` → `port`
|
50
|
-
# - `RABBITMQ_USERNAME` → `username`
|
51
|
-
# - `RABBITMQ_PASSWORD` → `password`
|
52
|
-
# - `RABBITMQ_TOPIC` → `topic`
|
53
|
-
# - `RABBITMQ_QUEUE` → `queue_name`
|
54
|
-
# - `RABBITMQ_PLATFORM_ROUTING_KEY` → `routing_key`
|
55
|
-
def from_env
|
56
|
-
MqConfig.new(
|
57
|
-
host: ENV.fetch("RABBITMQ_HOST"),
|
58
|
-
port: ENV.fetch("RABBITMQ_NODE_PORT_NUMBER"),
|
59
|
-
username: ENV.fetch("RABBITMQ_USERNAME"),
|
60
|
-
password: ENV.fetch("RABBITMQ_PASSWORD"),
|
61
|
-
topic: ENV.fetch("RABBITMQ_TOPIC"),
|
62
|
-
queue_name: ENV.fetch("RABBITMQ_QUEUE"),
|
63
|
-
routing_key: ENV.fetch("RABBITMQ_PLATFORM_ROUTING_KEY")
|
64
|
-
)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# @return [String] the connection URL as a string
|
69
|
-
def connection_url
|
70
|
-
@connection_url ||= "amqp://#{username}:#{password}@#{host}:#{port}"
|
71
|
-
end
|
72
|
-
|
73
|
-
# @return [String] the connection URL as a string, without the password
|
74
|
-
def redacted_url
|
75
|
-
@redacted_url ||= connection_url.sub(password, "REDACTED")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,136 +0,0 @@
|
|
1
|
-
require "bunny"
|
2
|
-
|
3
|
-
module Surfliner
|
4
|
-
module MetadataConsumer
|
5
|
-
# An object encapsulating a RabbitMQ connection.
|
6
|
-
class MqConnection
|
7
|
-
# @return [Logger] The logger
|
8
|
-
attr_reader :logger
|
9
|
-
|
10
|
-
# @return [Bunny::Session] The current RabbitMQ session
|
11
|
-
attr_reader :connection
|
12
|
-
|
13
|
-
# @return [Bunny::Channel] The channel being listened to
|
14
|
-
attr_reader :channel
|
15
|
-
|
16
|
-
# @return [Bunny::Exchange] The exchange being listened to
|
17
|
-
attr_reader :exchange
|
18
|
-
|
19
|
-
# @return [Bunny::Queue] The queue being listened to
|
20
|
-
attr_reader :queue
|
21
|
-
|
22
|
-
# @return [MqConfig] The configuration
|
23
|
-
attr_reader :config
|
24
|
-
|
25
|
-
# Initializes a new `MqConnection`.
|
26
|
-
#
|
27
|
-
# @param logger [Logger] the logger
|
28
|
-
# @param config [MqConfig] the configuration
|
29
|
-
def initialize(logger:, config: MqConfig.from_env)
|
30
|
-
@logger = logger
|
31
|
-
@config = config
|
32
|
-
end
|
33
|
-
|
34
|
-
# Opens a connection.
|
35
|
-
# @param topic_opts [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
36
|
-
# @param queue_opts [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
37
|
-
# @return [self]
|
38
|
-
# @raise RuntimeError if already connected
|
39
|
-
def connect(topic_opts: {}, queue_opts: {})
|
40
|
-
raise "RabbitMQ connection #{connection} already open." if open?
|
41
|
-
|
42
|
-
logger.info("Rabbitmq message broker connection url: #{config.redacted_url}")
|
43
|
-
@connection = Bunny.new(config.connection_url, logger: logger)
|
44
|
-
connect_on(connection)
|
45
|
-
@channel = connection.create_channel
|
46
|
-
@exchange = channel.topic(config.topic, topic_opts)
|
47
|
-
@queue = channel.queue(config.queue_name, queue_opts)
|
48
|
-
queue.bind(exchange, routing_key: config.routing_key)
|
49
|
-
|
50
|
-
self
|
51
|
-
rescue Bunny::TCPConnectionFailed => err
|
52
|
-
# TODO: realistically, this only happens in connection.start, where we're eating it
|
53
|
-
logger.error("Connection to #{config.redacted_url} failed")
|
54
|
-
raise err
|
55
|
-
rescue Bunny::PossibleAuthenticationFailureError => err
|
56
|
-
# TODO: realistically, this only happens in connection.start, where we're eating it
|
57
|
-
logger.error("Failed to authenticate to #{config.redacted_url}")
|
58
|
-
raise err
|
59
|
-
end
|
60
|
-
|
61
|
-
# Opens a connection, yields the queue, and closes the connection after
|
62
|
-
# the provided block completes.
|
63
|
-
# @param topic_opts [Hash] RabbitMQ topic options. (See Bunny::Channel#topic)
|
64
|
-
# @param queue_opts [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
|
65
|
-
# @yield [Bunny::Queue] the queue
|
66
|
-
def open(topic_opts: {}, queue_opts: {})
|
67
|
-
connect(topic_opts:, queue_opts:)
|
68
|
-
yield queue
|
69
|
-
ensure
|
70
|
-
close
|
71
|
-
end
|
72
|
-
|
73
|
-
# Closes the connection.
|
74
|
-
def close
|
75
|
-
return unless channel
|
76
|
-
return if channel.closed?
|
77
|
-
logger.info("closing channel")
|
78
|
-
channel.close
|
79
|
-
ensure
|
80
|
-
logger.info("closing connection")
|
81
|
-
connection&.close
|
82
|
-
end
|
83
|
-
|
84
|
-
# @return [true, false] True if the connection is open, false otherwise
|
85
|
-
def open?
|
86
|
-
connection&.status == :open
|
87
|
-
end
|
88
|
-
|
89
|
-
# @return [Symbol, nil] The connection status, or nil if there is no connection
|
90
|
-
def status
|
91
|
-
connection&.status
|
92
|
-
end
|
93
|
-
|
94
|
-
# @return [String] The RabbitMQ hostname
|
95
|
-
def host
|
96
|
-
config.host
|
97
|
-
end
|
98
|
-
|
99
|
-
# @return [String] The RabbitMQ port
|
100
|
-
def port
|
101
|
-
config.port
|
102
|
-
end
|
103
|
-
|
104
|
-
# @return [String] The routing key
|
105
|
-
def routing_key
|
106
|
-
config.routing_key
|
107
|
-
end
|
108
|
-
|
109
|
-
# Publishes the specified payload
|
110
|
-
# @param payload [String] the payload to publish
|
111
|
-
# @return [Bunny::Exchange] see #exchange
|
112
|
-
def publish(payload)
|
113
|
-
logger.info "Publishing to #{routing_key} with payload: #{payload}"
|
114
|
-
exchange.publish(payload, routing_key:)
|
115
|
-
end
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
def connect_on(connection, timeout = 120)
|
120
|
-
timer = 0
|
121
|
-
logger.info "Trying to open queue connection with timeout=#{timeout}"
|
122
|
-
while timer < timeout
|
123
|
-
begin
|
124
|
-
connection.start
|
125
|
-
rescue
|
126
|
-
# TODO: do we actually want to rescue from everything?
|
127
|
-
end
|
128
|
-
return connection if connection.status == :open
|
129
|
-
sleep 1
|
130
|
-
timer += 1
|
131
|
-
end
|
132
|
-
raise "Failed to connect to queue."
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|