smart_proxy_remote_execution_ssh 0.9.0 → 0.10.0
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/lib/smart_proxy_remote_execution_ssh/actions/pull_script.rb +5 -32
- data/lib/smart_proxy_remote_execution_ssh/api.rb +1 -0
- data/lib/smart_proxy_remote_execution_ssh/mqtt/dispatcher.rb +169 -0
- data/lib/smart_proxy_remote_execution_ssh/mqtt.rb +23 -0
- data/lib/smart_proxy_remote_execution_ssh/plugin.rb +8 -1
- data/lib/smart_proxy_remote_execution_ssh/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '048f8694430967060fae6bd7790fe56930b5c2bac37f3bc26e7b13db69081da6'
|
4
|
+
data.tar.gz: 5a3b1e344a47aa4c97dcce97f43dc9e424d398f79c67a6f1e066d670758c99f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4bc90b3dda6d190c8f94aeb6731a3a6679788ea4f950e1bb4196c1d5da4630b170fe152e9c96a5e4cb7dcd25ba1d6371292e52b4059773759c1fbe8461bd347
|
7
|
+
data.tar.gz: f4849b32f7529cbb29d127ae1c4c9fd9642b0700c834b1e3eebf59b5d9cfca282fca674188a734a37b36d45ff3e423096aa3512ceae118aef7ff831cb1600a38
|
@@ -6,7 +6,6 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
6
6
|
class PullScript < Proxy::Dynflow::Action::Runner
|
7
7
|
JobDelivered = Class.new
|
8
8
|
PickupTimeout = Class.new
|
9
|
-
ResendNotification = Class.new
|
10
9
|
|
11
10
|
execution_plan_hooks.use :cleanup, :on => :stopped
|
12
11
|
|
@@ -21,12 +20,6 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
21
20
|
suspend
|
22
21
|
elsif event == PickupTimeout
|
23
22
|
process_pickup_timeout
|
24
|
-
elsif event == ResendNotification
|
25
|
-
if input[:with_mqtt] && %w(ready_for_pickup notified).include?(output[:state])
|
26
|
-
schedule_mqtt_resend
|
27
|
-
mqtt_start(::Proxy::Dynflow::OtpManager.passwords[execution_plan_id])
|
28
|
-
end
|
29
|
-
suspend
|
30
23
|
else
|
31
24
|
super
|
32
25
|
end
|
@@ -45,16 +38,15 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
45
38
|
input[:job_uuid] = job_storage.store_job(host_name, execution_plan_id, run_step_id, input[:script].tr("\r", ''), effective_user: input[:effective_user])
|
46
39
|
output[:state] = :ready_for_pickup
|
47
40
|
output[:result] = []
|
48
|
-
|
49
|
-
|
50
|
-
mqtt_start(otp_password)
|
51
|
-
end
|
41
|
+
|
42
|
+
mqtt_start(otp_password) if input[:with_mqtt]
|
52
43
|
suspend
|
53
44
|
end
|
54
45
|
|
55
46
|
def cleanup(_plan = nil)
|
56
47
|
job_storage.drop_job(execution_plan_id, run_step_id)
|
57
48
|
Proxy::Dynflow::OtpManager.passwords.delete(execution_plan_id)
|
49
|
+
Proxy::RemoteExecution::Ssh::MQTT::Dispatcher.instance.done(input[:job_uuid])
|
58
50
|
end
|
59
51
|
|
60
52
|
def process_external_event(event)
|
@@ -125,7 +117,7 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
125
117
|
'effective_user': input[:effective_user]
|
126
118
|
},
|
127
119
|
)
|
128
|
-
|
120
|
+
Proxy::RemoteExecution::Ssh::MQTT::Dispatcher.instance.new(input[:job_uuid], mqtt_topic, payload)
|
129
121
|
output[:state] = :notified
|
130
122
|
end
|
131
123
|
|
@@ -141,18 +133,7 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
141
133
|
end
|
142
134
|
|
143
135
|
def mqtt_notify(payload)
|
144
|
-
|
145
|
-
c.publish(mqtt_topic, JSON.dump(payload), false, 1)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def with_mqtt_client(&block)
|
150
|
-
MQTT::Client.connect(settings.mqtt_broker, settings.mqtt_port,
|
151
|
-
:ssl => settings.mqtt_tls,
|
152
|
-
:cert_file => ::Proxy::SETTINGS.foreman_ssl_cert || ::Proxy::SETTINGS.ssl_certificate,
|
153
|
-
:key_file => ::Proxy::SETTINGS.foreman_ssl_key || ::Proxy::SETTINGS.ssl_private_key,
|
154
|
-
:ca_file => ::Proxy::SETTINGS.foreman_ssl_ca || ::Proxy::SETTINGS.ssl_ca_file,
|
155
|
-
&block)
|
136
|
+
Proxy::RemoteExecution::Ssh::MQTT.publish(mqtt_topic, JSON.dump(payload))
|
156
137
|
end
|
157
138
|
|
158
139
|
def host_name
|
@@ -167,10 +148,6 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
167
148
|
"yggdrasil/#{host_name}/data/in"
|
168
149
|
end
|
169
150
|
|
170
|
-
def settings
|
171
|
-
Proxy::RemoteExecution::Ssh::Plugin.settings
|
172
|
-
end
|
173
|
-
|
174
151
|
def job_storage
|
175
152
|
Proxy::RemoteExecution::Ssh.job_storage
|
176
153
|
end
|
@@ -192,9 +169,5 @@ module Proxy::RemoteExecution::Ssh::Actions
|
|
192
169
|
suspend
|
193
170
|
end
|
194
171
|
end
|
195
|
-
|
196
|
-
def schedule_mqtt_resend
|
197
|
-
plan_event(ResendNotification, settings[:mqtt_resend_interval], optional: true)
|
198
|
-
end
|
199
172
|
end
|
200
173
|
end
|
@@ -64,6 +64,7 @@ module Proxy::RemoteExecution
|
|
64
64
|
do_authorize_with_ssl_client
|
65
65
|
|
66
66
|
with_authorized_job(job_uuid) do |job_record|
|
67
|
+
Proxy::RemoteExecution::Ssh::MQTT::Dispatcher.instance.running(job_record[:uuid])
|
67
68
|
notify_job(job_record, Actions::PullScript::JobDelivered)
|
68
69
|
response.headers['X-Foreman-Effective-User'] = job_record[:effective_user]
|
69
70
|
job_record[:job]
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
require 'mqtt'
|
3
|
+
|
4
|
+
class Proxy::RemoteExecution::Ssh::MQTT
|
5
|
+
class Dispatcher
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_reader :reference
|
9
|
+
def initialize
|
10
|
+
limit = Proxy::RemoteExecution::Ssh::Plugin.settings[:mqtt_rate_limit]
|
11
|
+
@reference = DispatcherActor.spawn('MQTT dispatcher',
|
12
|
+
Proxy::Dynflow::Core.world.clock,
|
13
|
+
limit)
|
14
|
+
end
|
15
|
+
|
16
|
+
def new(uuid, topic, payload)
|
17
|
+
reference.tell([:new, uuid, topic, payload])
|
18
|
+
end
|
19
|
+
|
20
|
+
def running(uuid)
|
21
|
+
reference.tell([:running, uuid])
|
22
|
+
end
|
23
|
+
|
24
|
+
def resend(uuid)
|
25
|
+
reference.tell([:resend, uuid])
|
26
|
+
end
|
27
|
+
|
28
|
+
def done(uuid)
|
29
|
+
reference.tell([:done, uuid])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class DispatcherActor < Concurrent::Actor::RestartingContext
|
34
|
+
JobDefinition = Struct.new :uuid, :topic, :payload
|
35
|
+
|
36
|
+
class Tracker
|
37
|
+
def initialize(limit, clock)
|
38
|
+
@clock = clock
|
39
|
+
@limit = limit
|
40
|
+
@jobs = {}
|
41
|
+
@pending = []
|
42
|
+
@running = Set.new
|
43
|
+
@hot = Set.new
|
44
|
+
@cold = Set.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def new(uuid, topic, payload)
|
48
|
+
@jobs[uuid] = JobDefinition.new(uuid, topic, payload)
|
49
|
+
@pending << uuid
|
50
|
+
dispatch_pending
|
51
|
+
end
|
52
|
+
|
53
|
+
def running(uuid)
|
54
|
+
[@pending, @hot, @cold].each { |source| source.delete(uuid) }
|
55
|
+
@running << uuid
|
56
|
+
end
|
57
|
+
|
58
|
+
def resend(uuid)
|
59
|
+
return unless @jobs[uuid]
|
60
|
+
|
61
|
+
@pending << uuid
|
62
|
+
dispatch_pending
|
63
|
+
end
|
64
|
+
|
65
|
+
def done(uuid)
|
66
|
+
@jobs.delete(uuid)
|
67
|
+
[@pending, @running, @hot, @cold].each do |source|
|
68
|
+
source.delete(uuid)
|
69
|
+
end
|
70
|
+
dispatch_pending
|
71
|
+
end
|
72
|
+
|
73
|
+
def needs_processing?
|
74
|
+
pending_count.positive? || @hot.any? || @cold.any?
|
75
|
+
end
|
76
|
+
|
77
|
+
def pending_count
|
78
|
+
pending = @pending.count
|
79
|
+
return pending if @limit.nil?
|
80
|
+
|
81
|
+
running = [@running, @hot, @cold].map(&:count).sum
|
82
|
+
capacity = @limit - running
|
83
|
+
pending > capacity ? capacity : pending
|
84
|
+
end
|
85
|
+
|
86
|
+
def dispatch_pending
|
87
|
+
pending_count.times do
|
88
|
+
mqtt_notify(@pending.first)
|
89
|
+
@hot << @pending.shift
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def process
|
94
|
+
@cold.each { |uuid| schedule_resend(uuid) }
|
95
|
+
@cold = @hot
|
96
|
+
@hot = Set.new
|
97
|
+
|
98
|
+
dispatch_pending
|
99
|
+
end
|
100
|
+
|
101
|
+
def mqtt_notify(uuid)
|
102
|
+
job = @jobs[uuid]
|
103
|
+
return if job.nil?
|
104
|
+
|
105
|
+
Proxy::RemoteExecution::Ssh::MQTT.publish(job.topic, JSON.dump(job.payload))
|
106
|
+
end
|
107
|
+
|
108
|
+
def settings
|
109
|
+
Proxy::RemoteExecution::Ssh::Plugin.settings
|
110
|
+
end
|
111
|
+
|
112
|
+
def schedule_resend(uuid)
|
113
|
+
@clock.ping(Proxy::RemoteExecution::Ssh::MQTT::Dispatcher.instance, resend_interval, uuid, :resend)
|
114
|
+
end
|
115
|
+
|
116
|
+
def resend_interval
|
117
|
+
settings[:mqtt_resend_interval]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize(clock, limit = nil)
|
122
|
+
@tracker = Tracker.new(limit, clock)
|
123
|
+
|
124
|
+
interval = Proxy::RemoteExecution::Ssh::Plugin.settings[:mqtt_ttl]
|
125
|
+
@timer = Concurrent::TimerTask.new(execution_interval: interval) do
|
126
|
+
reference.tell(:tick)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def on_message(message)
|
131
|
+
action, arg = message
|
132
|
+
# Enable the timer just in case anything in tracker raises an exception so
|
133
|
+
# we can continue
|
134
|
+
timer_on
|
135
|
+
case action
|
136
|
+
when :new
|
137
|
+
_, uuid, topic, payload = message
|
138
|
+
@tracker.new(uuid, topic, payload)
|
139
|
+
when :resend
|
140
|
+
@tracker.resend(arg)
|
141
|
+
when :running
|
142
|
+
@tracker.running(arg)
|
143
|
+
when :done
|
144
|
+
@tracker.done(arg)
|
145
|
+
when :tick
|
146
|
+
@tracker.process
|
147
|
+
end
|
148
|
+
timer_set(@tracker.needs_processing?)
|
149
|
+
end
|
150
|
+
|
151
|
+
def timer_set(on)
|
152
|
+
on ? timer_on : timer_off
|
153
|
+
end
|
154
|
+
|
155
|
+
def timer_on
|
156
|
+
@timer.execute
|
157
|
+
end
|
158
|
+
|
159
|
+
def timer_off
|
160
|
+
@timer.shutdown
|
161
|
+
end
|
162
|
+
|
163
|
+
# In case an exception is raised during processing, instruct concurrent-ruby
|
164
|
+
# to keep going without losing state
|
165
|
+
def behaviour_definition
|
166
|
+
Concurrent::Actor::Behaviour.restarting_behaviour_definition(:resume!)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Proxy::RemoteExecution::Ssh
|
2
|
+
class MQTT
|
3
|
+
require 'smart_proxy_remote_execution_ssh/mqtt/dispatcher'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def publish(topic, payload, retain: false, qos: 1)
|
7
|
+
with_mqtt_client do |c|
|
8
|
+
c.publish(topic, payload, retain, qos)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_mqtt_client(&block)
|
13
|
+
::MQTT::Client.connect(Plugin.settings.mqtt_broker,
|
14
|
+
Plugin.settings.mqtt_port,
|
15
|
+
:ssl => Plugin.settings.mqtt_tls,
|
16
|
+
:cert_file => ::Proxy::SETTINGS.foreman_ssl_cert || ::Proxy::SETTINGS.ssl_certificate,
|
17
|
+
:key_file => ::Proxy::SETTINGS.foreman_ssl_key || ::Proxy::SETTINGS.ssl_private_key,
|
18
|
+
:ca_file => ::Proxy::SETTINGS.foreman_ssl_ca || ::Proxy::SETTINGS.ssl_ca_file,
|
19
|
+
&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -25,8 +25,10 @@ module Proxy::RemoteExecution::Ssh
|
|
25
25
|
# :mqtt_broker => nil,
|
26
26
|
# :mqtt_port => nil,
|
27
27
|
# :mqtt_tls => nil,
|
28
|
+
# :mqtt_rate_limit => nil
|
28
29
|
:mode => :ssh,
|
29
|
-
:mqtt_resend_interval => 900
|
30
|
+
:mqtt_resend_interval => 900,
|
31
|
+
:mqtt_ttl => 5
|
30
32
|
|
31
33
|
capability(proc { 'cockpit' if settings.cockpit_integration })
|
32
34
|
|
@@ -46,6 +48,11 @@ module Proxy::RemoteExecution::Ssh
|
|
46
48
|
Proxy::RemoteExecution::Ssh.validate!
|
47
49
|
|
48
50
|
Proxy::Dynflow::TaskLauncherRegistry.register('ssh', Proxy::Dynflow::TaskLauncher::Batch)
|
51
|
+
if settings.mode == :'pull-mqtt'
|
52
|
+
require 'smart_proxy_remote_execution_ssh/mqtt'
|
53
|
+
# Force initialization
|
54
|
+
Proxy::RemoteExecution::Ssh::MQTT::Dispatcher.instance
|
55
|
+
end
|
49
56
|
end
|
50
57
|
|
51
58
|
def self.simulate?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_remote_execution_ssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Nečas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -175,6 +175,8 @@ files:
|
|
175
175
|
- lib/smart_proxy_remote_execution_ssh/http_config.ru
|
176
176
|
- lib/smart_proxy_remote_execution_ssh/job_storage.rb
|
177
177
|
- lib/smart_proxy_remote_execution_ssh/log_filter.rb
|
178
|
+
- lib/smart_proxy_remote_execution_ssh/mqtt.rb
|
179
|
+
- lib/smart_proxy_remote_execution_ssh/mqtt/dispatcher.rb
|
178
180
|
- lib/smart_proxy_remote_execution_ssh/multiplexed_ssh_connection.rb
|
179
181
|
- lib/smart_proxy_remote_execution_ssh/net_ssh_compat.rb
|
180
182
|
- lib/smart_proxy_remote_execution_ssh/plugin.rb
|