lex-node 0.3.6 → 0.3.8
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/CHANGELOG.md +15 -0
- data/README.md +31 -0
- data/lib/legion/extensions/node/actors/beat.rb +1 -1
- data/lib/legion/extensions/node/actors/cluster_control.rb +7 -0
- data/lib/legion/extensions/node/config.rb +90 -0
- data/lib/legion/extensions/node/control_auth.rb +186 -0
- data/lib/legion/extensions/node/runners/node.rb +18 -2
- data/lib/legion/extensions/node/transport/exchanges/cluster_control.rb +8 -4
- data/lib/legion/extensions/node/transport/messages/cluster_killswitch.rb +4 -2
- data/lib/legion/extensions/node/transport/messages/cluster_settings.rb +4 -2
- data/lib/legion/extensions/node/transport/queues/cluster_control.rb +32 -2
- data/lib/legion/extensions/node/version.rb +1 -1
- data/lib/legion/extensions/node.rb +5 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d243dbcf948208a7274558621a0653a8f35b209398a992dfe2cfa658af21c61
|
|
4
|
+
data.tar.gz: bfa04d660f833939a44d155703e5c6038e71d03bc3c511b4f3a0d66d1988a056
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64d3c06f36f78d07eb13ea50344e53cdff8526d84aa66652d349186b8f87965997a406714a0caccc731aa6cdd04a4a6f8caac821fd8ca87f5357ef090b0ae872
|
|
7
|
+
data.tar.gz: d1e66f3a081e57b5430b9f3faa9a89f420ba479f89444197a176e6f2b52ff96d5a90856e312e2b3b74f35f98c887c24d502c4e2a9edf954b07641dd4c67579e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.8] - 2026-04-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Configurable cluster-control payload signing via `extensions.node.cluster_control.auth.mode` (`auto`, `required`, `disabled`), with timestamp freshness and nonce format validation when signing is active.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- `ClusterControl` queues now read durability and retention settings from `extensions.node.cluster_control.queue`; defaults remain durable and non-auto-delete so per-node commands survive node restarts.
|
|
10
|
+
- Beat no longer runs immediately at actor construction, avoiding startup reconciliation before extension boot completes.
|
|
11
|
+
- `update_gem` now installs through `Legion::Extensions::GemSource` and calls extension-scoped reload instead of full daemon reload.
|
|
12
|
+
|
|
13
|
+
## [0.3.7] - 2026-03-31
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `ClusterControl` exchange now inherits from `Legion::Transport::Exchange` instead of being a bare PORO with constants, fixing `NOT_FOUND` error during autobuild queue binding
|
|
17
|
+
|
|
3
18
|
## [0.3.6] - 2026-03-31
|
|
4
19
|
|
|
5
20
|
### Added
|
data/README.md
CHANGED
|
@@ -68,6 +68,37 @@ Each node periodically calls `beat` to broadcast its presence. On startup, nodes
|
|
|
68
68
|
|
|
69
69
|
Dynamic config changes (`update_settings`) and gem upgrades (`update_gem`) can be pushed to individual nodes or broadcast to all nodes at runtime. Both operations publish an `UpdateResult` message to `node.<name>.update_result` so the outcome can be observed cluster-wide.
|
|
70
70
|
|
|
71
|
+
### Cluster Control Settings
|
|
72
|
+
|
|
73
|
+
Cluster-control broadcasts are configurable under `extensions.node.cluster_control`. By default, auth mode is `auto`: messages are signed and verified when a shared secret is configured, and unsigned messages are allowed when no secret exists for simpler local or homelab deployments.
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"extensions": {
|
|
78
|
+
"node": {
|
|
79
|
+
"cluster_control": {
|
|
80
|
+
"auth": {
|
|
81
|
+
"mode": "auto",
|
|
82
|
+
"timestamp_skew_seconds": 300,
|
|
83
|
+
"nonce_bytes": 16
|
|
84
|
+
},
|
|
85
|
+
"queue": {
|
|
86
|
+
"durable": true,
|
|
87
|
+
"exclusive": false,
|
|
88
|
+
"auto_delete": false,
|
|
89
|
+
"queue_type": "classic",
|
|
90
|
+
"expires_ms": 604800000,
|
|
91
|
+
"message_ttl_ms": 86400000,
|
|
92
|
+
"max_length": 1000
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Set `auth.mode` to `required` to force HMAC signatures, or `disabled` to run without cluster-control signatures even when a shared secret is present.
|
|
101
|
+
|
|
71
102
|
## Transport
|
|
72
103
|
|
|
73
104
|
- **Exchange**: `node` (topic exchange)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/extensions/node/control_auth'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Extensions
|
|
5
7
|
module Node
|
|
@@ -28,6 +30,11 @@ module Legion
|
|
|
28
30
|
def generate_task?
|
|
29
31
|
false
|
|
30
32
|
end
|
|
33
|
+
|
|
34
|
+
def process_message(message, metadata, delivery_info)
|
|
35
|
+
verified_message = Legion::Extensions::Node::ControlAuth.verify!(message)
|
|
36
|
+
super(verified_message, metadata, delivery_info)
|
|
37
|
+
end
|
|
31
38
|
end
|
|
32
39
|
end
|
|
33
40
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Node
|
|
6
|
+
module Config
|
|
7
|
+
DEFAULT_SETTINGS = {
|
|
8
|
+
cluster_control: {
|
|
9
|
+
auth: {
|
|
10
|
+
mode: 'auto',
|
|
11
|
+
timestamp_skew_seconds: 300,
|
|
12
|
+
nonce_bytes: 16
|
|
13
|
+
},
|
|
14
|
+
queue: {
|
|
15
|
+
durable: true,
|
|
16
|
+
exclusive: false,
|
|
17
|
+
auto_delete: false,
|
|
18
|
+
queue_type: 'classic',
|
|
19
|
+
expires_ms: 604_800_000,
|
|
20
|
+
message_ttl_ms: 86_400_000,
|
|
21
|
+
max_length: 1000
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def default_settings
|
|
29
|
+
deep_dup(DEFAULT_SETTINGS)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def cluster_control
|
|
33
|
+
deep_merge(default_settings[:cluster_control], extension_settings[:cluster_control] || {})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def control_auth
|
|
37
|
+
cluster_control[:auth] || {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def control_queue
|
|
41
|
+
cluster_control[:queue] || {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extension_settings
|
|
45
|
+
return {} unless defined?(Legion::Settings) && Legion::Settings.respond_to?(:dig)
|
|
46
|
+
|
|
47
|
+
settings = Legion::Settings.dig(:extensions, :node)
|
|
48
|
+
settings.is_a?(Hash) ? symbolize_keys(settings) : {}
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
log.debug("node settings lookup failed: #{e.message}")
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def deep_merge(base, override)
|
|
55
|
+
base_hash = symbolize_keys(base)
|
|
56
|
+
override_hash = symbolize_keys(override)
|
|
57
|
+
base_hash.merge(override_hash) do |_key, old_value, new_value|
|
|
58
|
+
old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value) : new_value
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def deep_dup(value)
|
|
63
|
+
case value
|
|
64
|
+
when Hash
|
|
65
|
+
value.each_with_object({}) { |(key, val), result| result[key] = deep_dup(val) }
|
|
66
|
+
when Array
|
|
67
|
+
value.map { |item| deep_dup(item) }
|
|
68
|
+
else
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def symbolize_keys(value)
|
|
74
|
+
case value
|
|
75
|
+
when Hash
|
|
76
|
+
value.each_with_object({}) { |(key, val), result| result[key.to_sym] = symbolize_keys(val) }
|
|
77
|
+
when Array
|
|
78
|
+
value.map { |item| symbolize_keys(item) }
|
|
79
|
+
else
|
|
80
|
+
value
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def log
|
|
85
|
+
Legion::Logging.respond_to?(:logger) ? Legion::Logging.logger : Legion::Logging
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'legion/extensions/node/config'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Node
|
|
11
|
+
module ControlAuth
|
|
12
|
+
AUTH_MODES = %w[auto required disabled].freeze
|
|
13
|
+
|
|
14
|
+
class UnauthorizedControlMessage < StandardError; end
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def sign(payload)
|
|
19
|
+
normalized = deep_symbolize(payload).dup
|
|
20
|
+
return normalized unless sign_control_messages?
|
|
21
|
+
|
|
22
|
+
control = {
|
|
23
|
+
sender: node_name,
|
|
24
|
+
timestamp: Time.now.utc.to_i,
|
|
25
|
+
nonce: SecureRandom.hex(nonce_bytes)
|
|
26
|
+
}
|
|
27
|
+
normalized[:control] = control.merge(signature: signature_for(normalized.merge(control: control)))
|
|
28
|
+
normalized
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def verify!(payload)
|
|
32
|
+
normalized = deep_symbolize(payload)
|
|
33
|
+
raise UnauthorizedControlMessage, 'missing control signature' unless normalized.is_a?(Hash)
|
|
34
|
+
return normalized unless verify_control_messages?
|
|
35
|
+
|
|
36
|
+
control = normalized[:control]
|
|
37
|
+
raise UnauthorizedControlMessage, 'missing control signature' unless control.is_a?(Hash)
|
|
38
|
+
|
|
39
|
+
signature = control[:signature].to_s
|
|
40
|
+
raise UnauthorizedControlMessage, 'missing control signature' if signature.empty?
|
|
41
|
+
raise UnauthorizedControlMessage, 'stale control message' unless fresh_timestamp?(control[:timestamp])
|
|
42
|
+
raise UnauthorizedControlMessage, 'invalid control nonce' unless valid_nonce?(control[:nonce])
|
|
43
|
+
|
|
44
|
+
unsigned_control = control.dup
|
|
45
|
+
unsigned_control.delete(:signature)
|
|
46
|
+
expected = signature_for(normalized.merge(control: unsigned_control))
|
|
47
|
+
raise UnauthorizedControlMessage, 'invalid control signature' unless secure_compare(signature, expected)
|
|
48
|
+
|
|
49
|
+
normalized
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sign_control_messages?
|
|
53
|
+
return false if auth_disabled?
|
|
54
|
+
return true if auth_required?
|
|
55
|
+
|
|
56
|
+
!secret.to_s.empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def verify_control_messages?
|
|
60
|
+
return false if auth_disabled?
|
|
61
|
+
return true if auth_required?
|
|
62
|
+
|
|
63
|
+
!secret.to_s.empty?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def auth_mode
|
|
67
|
+
configured = auth_settings[:mode] || auth_settings[:enabled]
|
|
68
|
+
mode = case configured
|
|
69
|
+
when true then 'required'
|
|
70
|
+
when false then 'disabled'
|
|
71
|
+
else configured.to_s
|
|
72
|
+
end
|
|
73
|
+
AUTH_MODES.include?(mode) ? mode : 'auto'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def auth_required?
|
|
77
|
+
auth_mode == 'required'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def auth_disabled?
|
|
81
|
+
auth_mode == 'disabled'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def auth_settings
|
|
85
|
+
Legion::Extensions::Node::Config.control_auth
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def secret
|
|
89
|
+
configured_secret || ENV.fetch('LEGION_NODE_CONTROL_SECRET', nil)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def configured_secret
|
|
93
|
+
if defined?(Legion::Settings) && Legion::Settings.respond_to?(:dig)
|
|
94
|
+
auth_settings[:secret] ||
|
|
95
|
+
Legion::Settings.dig(:cluster, :control_secret) ||
|
|
96
|
+
Legion::Settings.dig(:crypt, :cluster_secret)
|
|
97
|
+
end
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
log.debug("control secret lookup failed: #{e.message}")
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def signature_for(payload)
|
|
104
|
+
value = secret
|
|
105
|
+
raise UnauthorizedControlMessage, 'cluster control secret is not configured' if value.to_s.empty?
|
|
106
|
+
|
|
107
|
+
OpenSSL::HMAC.hexdigest('SHA256', value.to_s, canonical_json(payload))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def canonical_json(value)
|
|
111
|
+
::JSON.generate(canonicalize(value))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def canonicalize(value)
|
|
115
|
+
case value
|
|
116
|
+
when Hash
|
|
117
|
+
value.each_with_object({}) do |(key, val), result|
|
|
118
|
+
result[key.to_s] = canonicalize(val)
|
|
119
|
+
end.sort.to_h
|
|
120
|
+
when Array
|
|
121
|
+
value.map { |item| canonicalize(item) }
|
|
122
|
+
else
|
|
123
|
+
value
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def deep_symbolize(value)
|
|
128
|
+
case value
|
|
129
|
+
when Hash
|
|
130
|
+
value.each_with_object({}) do |(key, val), result|
|
|
131
|
+
result[key.to_sym] = deep_symbolize(val)
|
|
132
|
+
end
|
|
133
|
+
when Array
|
|
134
|
+
value.map { |item| deep_symbolize(item) }
|
|
135
|
+
else
|
|
136
|
+
value
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def fresh_timestamp?(timestamp)
|
|
141
|
+
ts = Integer(timestamp)
|
|
142
|
+
(Time.now.utc.to_i - ts).abs <= timestamp_skew_seconds
|
|
143
|
+
rescue ArgumentError, TypeError => e
|
|
144
|
+
log.debug("control timestamp validation failed: #{e.message}")
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def timestamp_skew_seconds
|
|
149
|
+
Integer(auth_settings[:timestamp_skew_seconds])
|
|
150
|
+
rescue ArgumentError, TypeError => e
|
|
151
|
+
log.debug("control timestamp skew setting invalid: #{e.message}")
|
|
152
|
+
300
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def nonce_bytes
|
|
156
|
+
bytes = Integer(auth_settings[:nonce_bytes])
|
|
157
|
+
bytes.positive? ? bytes : 16
|
|
158
|
+
rescue ArgumentError, TypeError => e
|
|
159
|
+
log.debug("control nonce byte setting invalid: #{e.message}")
|
|
160
|
+
16
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def valid_nonce?(nonce)
|
|
164
|
+
nonce.to_s.match?(/\A[0-9a-f]{#{nonce_bytes * 2}}\z/i)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def secure_compare(left, right)
|
|
168
|
+
return false unless left.bytesize == right.bytesize
|
|
169
|
+
|
|
170
|
+
left.bytes.zip(right.bytes).reduce(0) { |acc, (a, b)| acc | (a ^ b) }.zero?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def node_name
|
|
174
|
+
(Legion::Settings[:client]&.fetch(:name, nil) if defined?(Legion::Settings)) || 'unknown'
|
|
175
|
+
rescue StandardError => e
|
|
176
|
+
log.debug("node name lookup failed: #{e.message}")
|
|
177
|
+
'unknown'
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def log
|
|
181
|
+
Legion::Logging.respond_to?(:logger) ? Legion::Logging.logger : Legion::Logging
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -28,8 +28,10 @@ module Legion
|
|
|
28
28
|
gem_name = "lex-#{name}"
|
|
29
29
|
log.debug "update_gem: installing #{gem_name} #{version || 'latest'}"
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
install_result = install_extension_gem(gem_name, version)
|
|
32
|
+
raise Gem::InstallError, install_result[:output].to_s unless install_result[:success]
|
|
33
|
+
|
|
34
|
+
reload_extension(gem_name) if reload
|
|
33
35
|
|
|
34
36
|
publish_update_result(action: 'update_gem', status: 'success', detail: "#{gem_name} #{version || 'latest'}")
|
|
35
37
|
rescue StandardError => e
|
|
@@ -146,6 +148,20 @@ module Legion
|
|
|
146
148
|
|
|
147
149
|
private
|
|
148
150
|
|
|
151
|
+
def install_extension_gem(gem_name, version)
|
|
152
|
+
require 'legion/extensions/gem_source' unless defined?(Legion::Extensions::GemSource) # rubocop:disable Legion/HelperMigration/RequireDefinedGuard
|
|
153
|
+
|
|
154
|
+
Legion::Extensions::GemSource.install_gem(gem_name, version: version)
|
|
155
|
+
rescue LoadError => e
|
|
156
|
+
{ success: false, output: "GemSource unavailable: #{e.message}" }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def reload_extension(gem_name)
|
|
160
|
+
raise 'extension-scoped reload is unavailable' unless defined?(Legion::Extensions) && Legion::Extensions.respond_to?(:reload_extension)
|
|
161
|
+
|
|
162
|
+
Legion::Extensions.reload_extension(gem_name)
|
|
163
|
+
end
|
|
164
|
+
|
|
149
165
|
def publish_update_result(action:, status:, detail: nil, error: nil)
|
|
150
166
|
Legion::Extensions::Node::Transport::Messages::UpdateResult.new(
|
|
151
167
|
action: action,
|
|
@@ -5,10 +5,14 @@ module Legion
|
|
|
5
5
|
module Node
|
|
6
6
|
module Transport
|
|
7
7
|
module Exchanges
|
|
8
|
-
class ClusterControl
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
class ClusterControl < Legion::Transport::Exchange
|
|
9
|
+
def exchange_name
|
|
10
|
+
'legion.cluster.control'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def default_type
|
|
14
|
+
'topic'
|
|
15
|
+
end
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/extensions/node/control_auth'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Extensions
|
|
5
7
|
module Node
|
|
@@ -25,11 +27,11 @@ module Legion
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def message
|
|
28
|
-
|
|
30
|
+
Legion::Extensions::Node::ControlAuth.sign(
|
|
29
31
|
function: 'update_settings',
|
|
30
32
|
settings: { extensions: { blocked: [@options[:extension]] } },
|
|
31
33
|
restart: true
|
|
32
|
-
|
|
34
|
+
)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def validate
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/extensions/node/control_auth'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Extensions
|
|
5
7
|
module Node
|
|
@@ -23,11 +25,11 @@ module Legion
|
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def message
|
|
26
|
-
|
|
28
|
+
Legion::Extensions::Node::ControlAuth.sign(
|
|
27
29
|
function: 'update_settings',
|
|
28
30
|
settings: @options[:settings],
|
|
29
31
|
restart: @options.fetch(:restart, false)
|
|
30
|
-
|
|
32
|
+
)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def validate
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'legion/extensions/node/config'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Extensions
|
|
5
7
|
module Node
|
|
@@ -11,8 +13,36 @@ module Legion
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def queue_options
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
options = Legion::Extensions::Node::Config.control_queue
|
|
17
|
+
{ durable: truthy?(options[:durable], default: true),
|
|
18
|
+
exclusive: truthy?(options[:exclusive], default: false),
|
|
19
|
+
auto_delete: truthy?(options[:auto_delete], default: false),
|
|
20
|
+
arguments: queue_arguments(options) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def queue_arguments(options)
|
|
26
|
+
{
|
|
27
|
+
'x-queue-type': options[:queue_type],
|
|
28
|
+
'x-expires': positive_integer(options[:expires_ms]),
|
|
29
|
+
'x-message-ttl': positive_integer(options[:message_ttl_ms]),
|
|
30
|
+
'x-max-length': positive_integer(options[:max_length])
|
|
31
|
+
}.compact
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def positive_integer(value)
|
|
35
|
+
return value if value.is_a?(Integer) && value.positive?
|
|
36
|
+
return nil unless value.to_s.match?(/\A\d+\z/)
|
|
37
|
+
|
|
38
|
+
parsed = value.to_i
|
|
39
|
+
parsed.positive? ? parsed : nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def truthy?(value, default:)
|
|
43
|
+
return default if value.nil?
|
|
44
|
+
|
|
45
|
+
value == true || value.to_s == 'true'
|
|
16
46
|
end
|
|
17
47
|
end
|
|
18
48
|
end
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/node/version'
|
|
4
|
+
require 'legion/extensions/node/config'
|
|
4
5
|
require 'legion/extensions/node/helpers/rabbitmq'
|
|
5
6
|
|
|
6
7
|
module Legion
|
|
7
8
|
module Extensions
|
|
8
9
|
module Node
|
|
9
10
|
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
|
|
11
|
+
|
|
12
|
+
def self.default_settings
|
|
13
|
+
Config.default_settings
|
|
14
|
+
end
|
|
10
15
|
end
|
|
11
16
|
end
|
|
12
17
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-node
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -150,6 +150,8 @@ files:
|
|
|
150
150
|
- lib/legion/extensions/node/actors/push_key.rb
|
|
151
151
|
- lib/legion/extensions/node/actors/vault.rb
|
|
152
152
|
- lib/legion/extensions/node/actors/vault_token_request.rb
|
|
153
|
+
- lib/legion/extensions/node/config.rb
|
|
154
|
+
- lib/legion/extensions/node/control_auth.rb
|
|
153
155
|
- lib/legion/extensions/node/data_test/migrations/001_nodes_table.rb
|
|
154
156
|
- lib/legion/extensions/node/data_test/migrations/002_node_history_table.rb
|
|
155
157
|
- lib/legion/extensions/node/data_test/migrations/003_legion_version_colume.rb
|