rabbitmq-graph 0.1.1
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 +7 -0
- data/README.md +81 -0
- data/bin/rabbitmq-graph +75 -0
- data/lib/rabbitmq-graph.rb +1 -0
- data/lib/rabbitmq-graph/discover.rb +136 -0
- data/lib/rabbitmq-graph/dot_format.rb +85 -0
- data/lib/rabbitmq-graph/markdown_table_format.rb +47 -0
- data/lib/rabbitmq-graph/route.rb +58 -0
- data/rabbitmq-graph.gemspec +18 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0c207d8f0935bddfd81f0e6600182134797df3a27f283a4b71ac172f8ea23549
|
4
|
+
data.tar.gz: 75690fe90096674172429863f3bb21b71a9b11cfa1014fbd1f84acf4d85fded0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f381610c65e306db2d734852f21d3cd23932747418cea7437d8ceffbc94a02feec3f8215ff4f95910e0b7209c1ff2bb4b2119ca8c9a8ee0d6c87707e7478668f
|
7
|
+
data.tar.gz: 9a2013484acbac11866864c1095b8a795210ac8e1d6c77478fd869ae5e535dd7a40a1ebdcc057c7208aa71e9ae4e3e800a8aa3a3ec06f168c542edfd1bf8cb48
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# rabbitmq-graph
|
2
|
+
|
3
|
+
[](https://gemnasium.com/github.com/sldblog/rabbitmq-graph)
|
4
|
+
[](https://circleci.com/gh/sldblog/rabbitmq-graph)
|
5
|
+
[](https://codeclimate.com/github/sldblog/rabbitmq-graph/maintainability)
|
6
|
+
[](https://codeclimate.com/github/sldblog/rabbitmq-graph/test_coverage)
|
7
|
+
|
8
|
+
Discover RabbitMQ topology.
|
9
|
+
|
10
|
+
## Assumptions
|
11
|
+
|
12
|
+
- Routing keys are segmented with dots (`.`).
|
13
|
+
|
14
|
+
| Segment name | Routing key | Extracted | Assumed to be |
|
15
|
+
| --- | --- | --- | --- |
|
16
|
+
| from\_app | **splitter**.experiment.something.assigned | splitted | The name of the publishing application. |
|
17
|
+
| entity | splitter.**experiment**.something.assigned | experiment | The entity that is participating in the action. |
|
18
|
+
| actions | splitter.experiment.**something.assigned** | something.assigned | The action(s) describing the event. |
|
19
|
+
|
20
|
+
- [Consumer tags][hutch-consumer-tag-pr] are configured to contain the name of the consuming application.
|
21
|
+
|
22
|
+
## How to run?
|
23
|
+
|
24
|
+
Without arguments `bin/rabbitmq-graph` will connect to `localhost:15672` with the default guest user.
|
25
|
+
|
26
|
+
### Configuration
|
27
|
+
|
28
|
+
| Setting | Configuration | Effect | Default |
|
29
|
+
| ------- | ------------- | ------ | ------- |
|
30
|
+
| RabbitMQ management URL | `-uURL`<br/>`--url=URL`<br/>or environment variable<br/>`RABBITMQ_API_URI` | Specifies the connection URL to RabbitMQ management API | http://guest:guest@localhost:15672/ |
|
31
|
+
| Save topology | `--save-topology=FILE` | After discovery save the topology to the given file. | disabled |
|
32
|
+
| Read topology | `--read-topology=FILE` | Skip discovery and use a stored topology file. | disabled |
|
33
|
+
| Choose format | `--format=FORMAT` | Choose an output format. `--help` will give a list of available options. | `DotFormat` |
|
34
|
+
|
35
|
+
#### Dot format specific options
|
36
|
+
|
37
|
+
| Setting | Configuration | Effect | Default |
|
38
|
+
| ------- | ------------- | ------ | ------- |
|
39
|
+
| Show only applications | `--dot-applications-only` | Creates a graph without entity nodes. | disabled |
|
40
|
+
| Label details | `--dot-label-detail=DETAILS` | Comma separated segment names to display on labels drawn between applications and/or entities. | `'actions'` |
|
41
|
+
|
42
|
+
### Show only applications
|
43
|
+
|
44
|
+
- **enabled**: will only show application to application relations.
|
45
|
+
- **disabled** (default): will show application to entity to application relations. The edge going into the entity and coming out of the entity will have the same label.
|
46
|
+
|
47
|
+
### Label details
|
48
|
+
|
49
|
+
Affects the labeling of edges:
|
50
|
+
|
51
|
+
- `'entity,actions'`: displays the entity name and the actions on the edge.
|
52
|
+
- `'entity'`: displays the entity name on the edge.
|
53
|
+
- `'actions'`: displays the actions on the edge.
|
54
|
+
- `''` (empty string): displays no labels.
|
55
|
+
|
56
|
+
Any combination and order of the above is allowed.
|
57
|
+
|
58
|
+
### Example
|
59
|
+
|
60
|
+
Running the discovery against a dockerised `rabbitmq:3.6-management`:
|
61
|
+
|
62
|
+
```
|
63
|
+
$ docker run --detach --publish 5672:5672 --publish 15672:15672 rabbitmq:3.6-management
|
64
|
+
|
65
|
+
$ RABBITMQ_API_URI=http://localhost:15672/ bin/rabbitmq-graph > test.dot
|
66
|
+
I, [2018-04-30T13:05:29.735060 #90042] INFO -- : connecting to rabbitmq HTTP API (http://guest@127.0.0.1:15672/)
|
67
|
+
Discovering bindings: |================================================================================================|
|
68
|
+
Discovering queues: |==================================================================================================|
|
69
|
+
|
70
|
+
$ fdp -O -Tpng test.dot # assumes "graphviz" is installed
|
71
|
+
$ open test.dot.png
|
72
|
+
```
|
73
|
+
|
74
|
+
## How to configure consumer tags?
|
75
|
+
|
76
|
+
### hutch
|
77
|
+
|
78
|
+
Hutch supports consumer tag prefixes since [0.24][hutch-0.24].
|
79
|
+
|
80
|
+
[hutch-consumer-tag-pr]: https://github.com/gocardless/hutch/pull/265
|
81
|
+
[hutch-0.24]: https://github.com/gocardless/hutch/blob/master/CHANGELOG.md#0240--february-1st-2017
|
data/bin/rabbitmq-graph
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'optparse'
|
6
|
+
require 'rabbitmq-graph/discover'
|
7
|
+
require 'rabbitmq-graph/dot_format'
|
8
|
+
require 'rabbitmq-graph/markdown_table_format'
|
9
|
+
require 'rabbitmq-graph/route'
|
10
|
+
|
11
|
+
def topology(options)
|
12
|
+
if (read_file = options[:discover][:read_topology_file])
|
13
|
+
JSON.parse(IO.read(read_file), symbolize_names: true).map { |route_hash| Route.new(route_hash) }
|
14
|
+
else
|
15
|
+
result = Discover.new(api_url: options[:discover][:api_url]).topology
|
16
|
+
if (save_file = options[:discover][:save_topology_file])
|
17
|
+
output = "[\n " + result.map { |e| JSON.generate(e.to_h) }.join(",\n ") + "\n]\n"
|
18
|
+
IO.write(save_file, output)
|
19
|
+
end
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_discovery_options(options, option_parser)
|
25
|
+
options[:discover] ||= {}
|
26
|
+
options[:discover][:api_url] = ENV['RABBITMQ_API_URI'] || 'http://guest:guest@localhost:15672/'
|
27
|
+
option_parser.on('-uURL', '--url=URL', 'RabbitMQ management API URL. ' \
|
28
|
+
'Defaults to "http://guest:guest@localhost:15672/". ' \
|
29
|
+
'Also configurable through the "RABBITMQ_API_URI" environment variable.') do |url|
|
30
|
+
options[:discover][:api_url] = url
|
31
|
+
end
|
32
|
+
option_parser.on('--read-topology=FILE', 'Skip discovery and use a stored topology file.') do |file|
|
33
|
+
options[:discover][:read_topology_file] = file
|
34
|
+
end
|
35
|
+
option_parser.on('--save-topology=FILE', 'After discovery save the topology to the given file.') do |file|
|
36
|
+
options[:discover][:save_topology_file] = file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_format_options(options, option_parser, formats:)
|
41
|
+
options[:format] = formats.first
|
42
|
+
option_parser.on('--format=FORMAT', formats.map(&:to_s), "Select format to use from #{formats.join(', ')}. " \
|
43
|
+
"Defaults to #{options[:format]}.") do |format|
|
44
|
+
options[:format] = Object.const_get(format)
|
45
|
+
end
|
46
|
+
|
47
|
+
formats.each { |format| options[format] = {} }
|
48
|
+
|
49
|
+
# DotFormat specific options
|
50
|
+
options[DotFormat][:show_entities] = true
|
51
|
+
option_parser.on('--dot-applications-only', 'Creates a graph without entity nodes.') do |apps_only|
|
52
|
+
options[DotFormat][:show_entities] = !apps_only
|
53
|
+
end
|
54
|
+
|
55
|
+
options[DotFormat][:label_detail] = %i[actions]
|
56
|
+
option_parser.on('--dot-label-detail=DETAILS', 'Specifies edge label format. ' \
|
57
|
+
'Comma separated list of "queue_name", "entity", "actions"') do |label_detail|
|
58
|
+
options[DotFormat][:label_detail] = label_detail.to_s.split(',').map(&:strip).reject(&:empty?).map(&:to_sym)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
options = {}
|
63
|
+
OptionParser.new do |option_parser|
|
64
|
+
option_parser.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
65
|
+
setup_discovery_options(options, option_parser)
|
66
|
+
setup_format_options(options, option_parser, formats: [DotFormat, MarkdownTableFormat])
|
67
|
+
option_parser.on('-h', '--help', 'Prints this help.') do
|
68
|
+
puts option_parser
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
end.parse!
|
72
|
+
|
73
|
+
chosen_format = options[:format]
|
74
|
+
format_options = options[chosen_format].merge(topology: topology(options))
|
75
|
+
puts chosen_format.new(format_options).present
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hutch'
|
4
|
+
require 'json'
|
5
|
+
require 'logger'
|
6
|
+
require 'ruby-progressbar'
|
7
|
+
require 'uri'
|
8
|
+
require 'rabbitmq-graph/route'
|
9
|
+
|
10
|
+
# Using RabbitMQ's HTTP management API, discovers the server's publisher/subscriber topology.
|
11
|
+
#
|
12
|
+
# Assumes that:
|
13
|
+
# - `consumer_tag`s are set on consumers to the consuming application's name
|
14
|
+
# - bound routing keys are in the format of `application_name.entity_name[.action]+`
|
15
|
+
class Discover
|
16
|
+
def initialize(api_url: ENV.fetch('RABBITMQ_API_URI', 'http://guest:guest@localhost:15672/'),
|
17
|
+
api_client: nil, output: $stderr)
|
18
|
+
@output_io = output
|
19
|
+
Hutch::Logging.logger = Logger.new(output_io)
|
20
|
+
configure_hutch_http_api(api_url)
|
21
|
+
configure_api_client(api_client)
|
22
|
+
end
|
23
|
+
|
24
|
+
def topology
|
25
|
+
all_publishers = discover_routing_keys
|
26
|
+
all_consumers = discover_queues_and_consumers
|
27
|
+
queue_names = all_publishers.keys | all_consumers.keys
|
28
|
+
|
29
|
+
queue_names.inject([]) do |result, queue_name|
|
30
|
+
publishers = all_publishers[queue_name] || []
|
31
|
+
publishers << {} if publishers.empty?
|
32
|
+
consumers = all_consumers[queue_name] || []
|
33
|
+
consumers << {} if consumers.empty?
|
34
|
+
|
35
|
+
routes = publishers
|
36
|
+
.flat_map { |route| consumers.map { |consumer| route.merge(consumer) } }
|
37
|
+
.map { |route| route.delete_if { |_key, value| value.nil? } }
|
38
|
+
.map { |route| Route.new(route.merge(queue_name: queue_name)) }
|
39
|
+
.uniq
|
40
|
+
result.concat(routes)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :client, :output_io
|
47
|
+
|
48
|
+
def configure_hutch_http_api(api_url)
|
49
|
+
parsed_uri = URI(api_url)
|
50
|
+
Hutch::Config.set(:mq_api_host, parsed_uri.host)
|
51
|
+
Hutch::Config.set(:mq_username, parsed_uri.user || 'guest')
|
52
|
+
Hutch::Config.set(:mq_password, parsed_uri.password || 'guest')
|
53
|
+
Hutch::Config.set(:mq_api_port, parsed_uri.port)
|
54
|
+
Hutch::Config.set(:mq_api_ssl, parsed_uri.scheme == 'https')
|
55
|
+
end
|
56
|
+
|
57
|
+
def configure_api_client(api_client)
|
58
|
+
return @client = api_client if api_client
|
59
|
+
|
60
|
+
broker = Hutch::Broker.new
|
61
|
+
broker.set_up_api_connection
|
62
|
+
@client = broker.api_client
|
63
|
+
end
|
64
|
+
|
65
|
+
def discover_routing_keys
|
66
|
+
items = {}
|
67
|
+
ProgressBar.create(title: 'Discovering bindings', total: client.bindings.size, output: output_io).tap do |progress|
|
68
|
+
bindings.each do |mq_binding|
|
69
|
+
queue_name = mq_binding[:queue_name]
|
70
|
+
items[queue_name] ||= []
|
71
|
+
items[queue_name] << { routing_key: mq_binding[:routing_key] }
|
72
|
+
progress.increment
|
73
|
+
end
|
74
|
+
progress.finish
|
75
|
+
end
|
76
|
+
items
|
77
|
+
end
|
78
|
+
|
79
|
+
def discover_queues_and_consumers
|
80
|
+
items = {}
|
81
|
+
ProgressBar.create(title: 'Discovering queues', total: client.queues.size, output: output_io).tap do |progress|
|
82
|
+
bound_queues.each do |queue|
|
83
|
+
bound_consumers(queue).each do |consumer|
|
84
|
+
queue_name = consumer[:queue_name]
|
85
|
+
items[queue_name] ||= []
|
86
|
+
items[queue_name] << { consumer_tag: consumer[:consumer] }
|
87
|
+
end
|
88
|
+
progress.increment
|
89
|
+
end
|
90
|
+
progress.finish
|
91
|
+
end
|
92
|
+
items
|
93
|
+
end
|
94
|
+
|
95
|
+
def bindings
|
96
|
+
client.bindings.lazy
|
97
|
+
.select { |binding| binding['destination_type'] == 'queue' }
|
98
|
+
.reject { |binding| binding['routing_key'].empty? }
|
99
|
+
.reject { |binding| binding['source'].empty? }
|
100
|
+
.map { |binding_data| extract_binding_data(binding_data) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def bound_queues
|
104
|
+
client.queues.lazy
|
105
|
+
.map { |queue| fetch_queue_data(queue['vhost'], queue['name']) }
|
106
|
+
.map { |queue| queue['consumer_details'] }
|
107
|
+
end
|
108
|
+
|
109
|
+
def bound_consumers(queue_data)
|
110
|
+
queue_data
|
111
|
+
.flatten
|
112
|
+
.reject(&:empty?)
|
113
|
+
.map { |consumer_data| extract_consumer_data(consumer_data) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def extract_binding_data(binding_data)
|
117
|
+
{
|
118
|
+
vhost: binding_data['vhost'],
|
119
|
+
queue_name: binding_data['destination'],
|
120
|
+
routing_key: binding_data['routing_key']
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def extract_consumer_data(consumer_data)
|
125
|
+
{
|
126
|
+
vhost: consumer_data['queue']['vhost'],
|
127
|
+
queue_name: consumer_data['queue']['name'],
|
128
|
+
consumer: consumer_data['consumer_tag']
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def fetch_queue_data(vhost, name)
|
133
|
+
escaped_vhost_path = URI.encode_www_form_component(vhost)
|
134
|
+
JSON.parse(client.query_api(path: "/queues/#{escaped_vhost_path}/#{name}").body)
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# Presents a RabbitMQ topology in graphviz's .dot format
|
6
|
+
class DotFormat
|
7
|
+
def initialize(topology:, show_entities: true, label_detail: %i[actions])
|
8
|
+
@topology = topology
|
9
|
+
@show_entities = show_entities
|
10
|
+
@label_detail = label_detail
|
11
|
+
end
|
12
|
+
|
13
|
+
def present
|
14
|
+
<<-GRAPH
|
15
|
+
digraph G {
|
16
|
+
#{render_application_subgraph}
|
17
|
+
#{render_entity_subgraph}
|
18
|
+
#{message_edges.join("\n")}
|
19
|
+
}
|
20
|
+
GRAPH
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :topology, :show_entities, :label_detail
|
26
|
+
|
27
|
+
def render_application_subgraph
|
28
|
+
<<-APPS
|
29
|
+
subgraph Apps {
|
30
|
+
node [shape=oval fillcolor=yellow style=filled]
|
31
|
+
#{application_nodes.join("\n")}
|
32
|
+
}
|
33
|
+
APPS
|
34
|
+
end
|
35
|
+
|
36
|
+
def render_entity_subgraph
|
37
|
+
return '' unless show_entities
|
38
|
+
<<-ENTITIES
|
39
|
+
subgraph Entities {
|
40
|
+
node [shape=box fillcolor=turquoise style=filled]
|
41
|
+
#{entity_nodes.join("\n")}
|
42
|
+
}
|
43
|
+
ENTITIES
|
44
|
+
end
|
45
|
+
|
46
|
+
def application_nodes
|
47
|
+
applications = {}
|
48
|
+
topology.each do |route|
|
49
|
+
applications[route.source_app] ||= Set.new
|
50
|
+
applications[route.source_app] << 'fillcolor="red"' if route.missing_source?
|
51
|
+
|
52
|
+
applications[route.target_app] ||= Set.new
|
53
|
+
applications[route.target_app] << 'fillcolor="red"' if route.missing_target?
|
54
|
+
applications[route.target_app] << 'fillcolor="orange"' if route.default_consumer_tag?
|
55
|
+
end
|
56
|
+
applications.map { |name, properties| %("#{name}" [#{properties.to_a.join(' ')}]) }.sort
|
57
|
+
end
|
58
|
+
|
59
|
+
def entity_nodes
|
60
|
+
entities = topology.map(&:entity).sort.uniq
|
61
|
+
entities.map { |entity| %("#{entity}") }
|
62
|
+
end
|
63
|
+
|
64
|
+
def message_edges
|
65
|
+
topology.map { |route| %(#{route_path(route)} [#{route_properties(route)}]) }.uniq
|
66
|
+
end
|
67
|
+
|
68
|
+
def route_path(route)
|
69
|
+
path = []
|
70
|
+
path << route.source_app
|
71
|
+
path << route.entity if show_entities
|
72
|
+
path << route.target_app
|
73
|
+
path.map { |text| %("#{text}") }.join('->')
|
74
|
+
end
|
75
|
+
|
76
|
+
def route_properties(route)
|
77
|
+
label = label_detail.select { |detail| route.respond_to?(detail) }
|
78
|
+
.map { |detail| route.public_send(detail) }
|
79
|
+
.flatten.join('.')
|
80
|
+
properties = []
|
81
|
+
properties << %(label="#{label}")
|
82
|
+
properties << %(color="red") if route.missing_source? || route.missing_target?
|
83
|
+
properties.join(' ')
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Presents a RabbitMQ topology in a GitHub-flavoured Markdown table
|
4
|
+
class MarkdownTableFormat
|
5
|
+
def initialize(topology:)
|
6
|
+
@topology = topology
|
7
|
+
end
|
8
|
+
|
9
|
+
def present
|
10
|
+
no_consumer_routes = topology.select(&:missing_target?)
|
11
|
+
no_binding_routes = topology.select(&:missing_source?)
|
12
|
+
default_tag_routes = topology.select(&:default_consumer_tag?)
|
13
|
+
connected_routes = topology.reject { |r| r.missing_source? || r.missing_target? || r.default_consumer_tag? }
|
14
|
+
|
15
|
+
lines = []
|
16
|
+
lines.concat(route_table('Routes without consumers', no_consumer_routes))
|
17
|
+
lines.concat(route_table('Routes without publisher bindings', no_binding_routes))
|
18
|
+
lines.concat(route_table('Routes with default consumer names', default_tag_routes))
|
19
|
+
lines.concat(route_table('Named, connected routes', connected_routes))
|
20
|
+
lines.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :topology
|
26
|
+
|
27
|
+
def route_table(title, routes)
|
28
|
+
return [] if routes.empty?
|
29
|
+
lines = []
|
30
|
+
lines << ''
|
31
|
+
lines << "# #{title}"
|
32
|
+
lines << ''
|
33
|
+
lines << '| Publisher application | Consumer application | Entity | Actions | Queue |'
|
34
|
+
lines << '| --- | --- | --- | --- | --- |'
|
35
|
+
lines.concat(routes.map { |route| route_line(route) }.uniq)
|
36
|
+
end
|
37
|
+
|
38
|
+
def route_line(route)
|
39
|
+
columns = []
|
40
|
+
columns << route.source_app
|
41
|
+
columns << route.target_app
|
42
|
+
columns << route.entity
|
43
|
+
columns << route.actions.join('.')
|
44
|
+
columns << route.queue_name
|
45
|
+
'| ' + columns.join(' | ') + ' |'
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extracts publisher/consumer application names and routing key fragments from routing data
|
4
|
+
class Route
|
5
|
+
DEFAULT_CONSUMER_TAG = 'default-consumer-tag'
|
6
|
+
MISSING_SOURCE_LABEL = 'no-routing-key-binding'
|
7
|
+
MISSING_TARGET_LABEL = 'no-consumers'
|
8
|
+
|
9
|
+
def initialize(queue_name:, routing_key: nil, consumer_tag: nil)
|
10
|
+
@queue_name = queue_name
|
11
|
+
@routing_fragments = routing_key.to_s.split('.')
|
12
|
+
@consumer_tag = consumer_tag
|
13
|
+
|
14
|
+
@source_app = routing_fragments[0] || MISSING_SOURCE_LABEL
|
15
|
+
@entity = routing_fragments[1] || ''
|
16
|
+
@actions = routing_fragments[2..-1] || []
|
17
|
+
@target_app = consumer_tag_to_application_name(consumer_tag) || MISSING_TARGET_LABEL
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :source_app, :target_app, :entity, :actions, :queue_name
|
21
|
+
|
22
|
+
def missing_source?
|
23
|
+
source_app == MISSING_SOURCE_LABEL
|
24
|
+
end
|
25
|
+
|
26
|
+
def missing_target?
|
27
|
+
target_app == MISSING_TARGET_LABEL
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_consumer_tag?
|
31
|
+
target_app == DEFAULT_CONSUMER_TAG
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
{ queue_name: queue_name, routing_key: routing_fragments.join('.'), consumer_tag: consumer_tag }
|
36
|
+
end
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
eql?(other)
|
40
|
+
end
|
41
|
+
|
42
|
+
def eql?(other)
|
43
|
+
[queue_name, routing_fragments, consumer_tag] == [other.queue_name, other.routing_fragments, other.consumer_tag]
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
attr_reader :routing_fragments, :consumer_tag
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def consumer_tag_to_application_name(tag)
|
53
|
+
return DEFAULT_CONSUMER_TAG if tag =~ /^bunny-/ || tag =~ /^hutch-/ || tag =~ /^amq\.ctag/
|
54
|
+
return tag.split('-')[0..-6].join('-') if tag =~ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
55
|
+
return tag.split('-')[0..-3].join('-') if tag =~ /[0-9]+-[0-9]+$/
|
56
|
+
tag
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'rabbitmq-graph'
|
5
|
+
s.version = '0.1.1'
|
6
|
+
s.summary = 'Discover RabbitMQ topology'
|
7
|
+
s.description = 'Map out RabbitMQ topology with the use of routing key conventions and consumer tags.'
|
8
|
+
s.authors = ['David Lantos']
|
9
|
+
s.email = 'david.lantos+rabbitmq-graph@gmail.com'
|
10
|
+
s.bindir = 'bin'
|
11
|
+
s.executables = ['rabbitmq-graph']
|
12
|
+
s.files = `git ls-files lib bin *.gemspec *.md`.split
|
13
|
+
s.homepage = 'https://github.com/sldblog/rabbitmq-graph'
|
14
|
+
s.metadata = { 'source_code_uri' => 'https://github.com/sldblog/rabbitmq-graph' }
|
15
|
+
|
16
|
+
s.add_runtime_dependency 'hutch', '~> 0.24'
|
17
|
+
s.add_runtime_dependency 'ruby-progressbar', '~> 1.9'
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabbitmq-graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Lantos
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hutch
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.24'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.24'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ruby-progressbar
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.9'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.9'
|
41
|
+
description: Map out RabbitMQ topology with the use of routing key conventions and
|
42
|
+
consumer tags.
|
43
|
+
email: david.lantos+rabbitmq-graph@gmail.com
|
44
|
+
executables:
|
45
|
+
- rabbitmq-graph
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- README.md
|
50
|
+
- bin/rabbitmq-graph
|
51
|
+
- lib/rabbitmq-graph.rb
|
52
|
+
- lib/rabbitmq-graph/discover.rb
|
53
|
+
- lib/rabbitmq-graph/dot_format.rb
|
54
|
+
- lib/rabbitmq-graph/markdown_table_format.rb
|
55
|
+
- lib/rabbitmq-graph/route.rb
|
56
|
+
- rabbitmq-graph.gemspec
|
57
|
+
homepage: https://github.com/sldblog/rabbitmq-graph
|
58
|
+
licenses: []
|
59
|
+
metadata:
|
60
|
+
source_code_uri: https://github.com/sldblog/rabbitmq-graph
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.7.6
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Discover RabbitMQ topology
|
81
|
+
test_files: []
|