foreman_remote_execution 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ca71ec8c1f3b330d327966298dea09c81a21378
4
- data.tar.gz: a86edc45eaebca0fa3acfeecf38115fa6596d199
3
+ metadata.gz: 4120a1934a7887f21bd50779f5d6f9f76b3046c4
4
+ data.tar.gz: 1e4c6270f33ef933afe21173fb9e3e0e1a6478b2
5
5
  SHA512:
6
- metadata.gz: db8e9e87cf16680859499d1f910f318b3ffba7e47bd180e30b5e43a25bebe04ef08a44b4225b519c73b312c4033d3b93dfababb80999ea04c667a0dbdd527d6f
7
- data.tar.gz: 0d4b6974e4172fff1feee3a861cad6a14fb10788d1fb1a1dc8486328e94029681f3fbc9070394f706a434f6047dc2840f947f09422853d9faa551404698bd3ed
6
+ metadata.gz: d986544207cce24d406123b2567df7216755234528151da383d6bce25e22612e80a99a920fcfd2188435a2706ff991bf98096d27e46d2f30dd2a8e8528be3929
7
+ data.tar.gz: d8fb4cfa5e43be5144895a706806348dc358c832946a0bd983744cb7ac4b3ea77a6c174846e6e85e2152574cbd5232f9f89c605f50b2717cc67d44d48d6217a3
@@ -14,7 +14,12 @@ Gem::Specification.new do |s|
14
14
  s.description = 'A plugin bringing remote execution to the Foreman, completing the config ' +
15
15
  'management functionality with remote management functionality.'
16
16
 
17
- s.files = `git ls-files`.split("\n").reject { |f| f =~ /^scripts/ }
17
+ s.files = `git ls-files`.split("\n").reject do |file|
18
+ file =~ /^scripts/ ||
19
+ file.start_with?('lib/foreman_remote_execution_core') ||
20
+ file == 'foreman_remote_execution_core.gemspec'
21
+ end
22
+
18
23
  s.test_files = `git ls-files test`.split("\n")
19
24
  s.extra_rdoc_files = `git ls-files doc`.split("\n") + Dir['README*', 'LICENSE']
20
25
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '1.2.0'
2
+ VERSION = '1.2.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
@@ -351,14 +351,9 @@ files:
351
351
  - doc/source/static/js/jquery.tocify.min.js
352
352
  - doc/source/static/js/scroll.js
353
353
  - foreman_remote_execution.gemspec
354
- - foreman_remote_execution_core.gemspec
355
354
  - lib/foreman_remote_execution.rb
356
355
  - lib/foreman_remote_execution/engine.rb
357
356
  - lib/foreman_remote_execution/version.rb
358
- - lib/foreman_remote_execution_core.rb
359
- - lib/foreman_remote_execution_core/actions.rb
360
- - lib/foreman_remote_execution_core/script_runner.rb
361
- - lib/foreman_remote_execution_core/version.rb
362
357
  - lib/tasks/foreman_remote_execution_tasks.rake
363
358
  - locale/Makefile
364
359
  - locale/action_names.rb
@@ -1,23 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- require File.expand_path('../lib/foreman_remote_execution_core/version', __FILE__)
3
- require 'date'
4
-
5
- Gem::Specification.new do |s|
6
- s.name = 'foreman_remote_execution_core'
7
- s.version = ForemanRemoteExecutionCore::VERSION
8
- s.authors = ['Ivan Nečas']
9
- s.email = ['inecas@redhat.com']
10
- s.homepage = 'https://github.com/theforeman/foreman_remote_execution'
11
- s.summary = 'Foreman remote execution - core bits'
12
- s.description = <<DESC
13
- Ssh remote execution provider code sharable between Foreman and Foreman-Proxy
14
- DESC
15
- s.licenses = ['GPL-3']
16
-
17
- s.files = Dir['lib/foreman_remote_execution_core/**/*'] +
18
- ['lib/foreman_remote_execution_core.rb']
19
-
20
- s.add_runtime_dependency('foreman-tasks-core', '~> 0.1.0')
21
- s.add_runtime_dependency('net-ssh')
22
- s.add_runtime_dependency('net-scp')
23
- end
@@ -1,18 +0,0 @@
1
- require 'foreman_tasks_core'
2
-
3
- module ForemanRemoteExecutionCore
4
- extend ForemanTasksCore::SettingsLoader
5
- register_settings([:remote_execution_ssh, :smart_proxy_remote_execution_ssh_core],
6
- :ssh_identity_key_file => '~/.ssh/id_rsa_foreman_proxy',
7
- :ssh_user => 'root',
8
- :remote_working_dir => '/var/tmp',
9
- :local_working_dir => '/var/tmp')
10
-
11
- if ForemanTasksCore.dynflow_present?
12
- require 'foreman_tasks_core/runner'
13
- require 'foreman_remote_execution_core/script_runner'
14
- require 'foreman_remote_execution_core/actions'
15
- end
16
-
17
- require 'foreman_remote_execution_core/version'
18
- end
@@ -1,11 +0,0 @@
1
- require 'foreman_tasks_core/shareable_action'
2
-
3
- module ForemanRemoteExecutionCore
4
- module Actions
5
- class RunScript < ForemanTasksCore::Runner::Action
6
- def initiate_runner
7
- ScriptRunner.new(input)
8
- end
9
- end
10
- end
11
- end
@@ -1,266 +0,0 @@
1
- require 'net/ssh'
2
- require 'net/scp'
3
-
4
- module ForemanRemoteExecutionCore
5
- class ScriptRunner < ForemanTasksCore::Runner::Base
6
- EXPECTED_POWER_ACTION_MESSAGES = ["restart host", "shutdown host"]
7
-
8
- def initialize(options)
9
- super()
10
- @host = options.fetch(:hostname)
11
- @script = options.fetch(:script)
12
- @ssh_user = options.fetch(:ssh_user, 'root')
13
- @ssh_port = options.fetch(:ssh_port, 22)
14
- @effective_user = options.fetch(:effective_user, nil)
15
- @effective_user_method = options.fetch(:effective_user_method, 'sudo')
16
- @host_public_key = options.fetch(:host_public_key, nil)
17
- @verify_host = options.fetch(:verify_host, nil)
18
-
19
- @client_private_key_file = settings.fetch(:ssh_identity_key_file)
20
- @local_working_dir = options.fetch(:local_working_dir, settings.fetch(:local_working_dir))
21
- @remote_working_dir = options.fetch(:remote_working_dir, settings.fetch(:remote_working_dir))
22
- end
23
-
24
- def start
25
- remote_script = cp_script_to_remote
26
- output_path = File.join(File.dirname(remote_script), 'output')
27
-
28
- # pipe the output to tee while capturing the exit code
29
- script = <<-SCRIPT
30
- exec 4>&1
31
- exit_code=`((#{su_prefix}#{remote_script}; echo $?>&3 ) | /usr/bin/tee #{output_path} ) 3>&1 >&4`
32
- exec 4>&-
33
- exit $exit_code
34
- SCRIPT
35
- logger.debug("executing script:\n#{script.lines.map { |line| " | #{line}" }.join}")
36
- run_async(script)
37
- rescue => e
38
- logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
39
- publish_exception("Error initializing command", e)
40
- end
41
-
42
- def refresh
43
- return if @session.nil?
44
- with_retries do
45
- with_disconnect_handling do
46
- @session.process(0)
47
- end
48
- end
49
- ensure
50
- check_expecting_disconnect
51
- end
52
-
53
- def kill
54
- if @session
55
- run_sync("pkill -f #{remote_command_file('script')}")
56
- else
57
- logger.debug("connection closed")
58
- end
59
- rescue => e
60
- publish_exception("Unexpected error", e, false)
61
- end
62
-
63
- def with_retries
64
- tries = 0
65
- begin
66
- yield
67
- rescue => e
68
- logger.error("Unexpected error: #{e.class} #{e.message}\n #{e.backtrace.join("\n")}")
69
- tries += 1
70
- if tries <= MAX_PROCESS_RETRIES
71
- logger.error('Retrying')
72
- retry
73
- else
74
- publish_exception("Unexpected error", e)
75
- end
76
- end
77
- end
78
-
79
- def with_disconnect_handling
80
- yield
81
- rescue Net::SSH::Disconnect => e
82
- @session.shutdown!
83
- check_expecting_disconnect
84
- if @expecting_disconnect
85
- publish_exit_status(0)
86
- else
87
- publish_exception("Unexpected disconnect", e)
88
- end
89
- end
90
-
91
- def close
92
- @session.close if @session && !@session.closed?
93
- end
94
-
95
- private
96
-
97
- def session
98
- @session ||= begin
99
- @logger.debug("opening session to #{@ssh_user}@#{@host}")
100
- Net::SSH.start(@host, @ssh_user, ssh_options)
101
- end
102
- end
103
-
104
- def ssh_options
105
- ssh_options = {}
106
- ssh_options[:port] = @ssh_port if @ssh_port
107
- ssh_options[:keys] = [@client_private_key_file] if @client_private_key_file
108
- ssh_options[:user_known_hosts_file] = @known_hosts_file if @known_hosts_file
109
- ssh_options[:keys_only] = true
110
- # if the host public key is contained in the known_hosts_file,
111
- # verify it, otherwise, if missing, import it and continue
112
- ssh_options[:paranoid] = true
113
- ssh_options[:auth_methods] = ["publickey"]
114
- ssh_options[:user_known_hosts_file] = prepare_known_hosts if @host_public_key
115
- return ssh_options
116
- end
117
-
118
- def settings
119
- ForemanRemoteExecutionCore.settings
120
- end
121
-
122
- # Initiates run of the remote command and yields the data when
123
- # available. The yielding doesn't happen automatically, but as
124
- # part of calling the `refresh` method.
125
- def run_async(command)
126
- raise "Async command already in progress" if @started
127
- @started = false
128
- session.open_channel do |channel|
129
- channel.request_pty
130
- channel.on_data { |ch, data| publish_data(data, 'stdout') }
131
- channel.on_extended_data { |ch, type, data| publish_data(data, 'stderr') }
132
- # standard exit of the command
133
- channel.on_request("exit-status") { |ch, data| publish_exit_status(data.read_long) }
134
- # on signal: sending the signal value (such as 'TERM')
135
- channel.on_request("exit-signal") do |ch, data|
136
- publish_exit_status(data.read_string)
137
- ch.close
138
- # wait for the channel to finish so that we know at the end
139
- # that the session is inactive
140
- ch.wait
141
- end
142
- channel.exec(command) do |ch, success|
143
- @started = true
144
- raise("Error initializing command") unless success
145
- end
146
- end
147
- session.process(0) until @started
148
- return true
149
- end
150
-
151
- def run_sync(command)
152
- output = ""
153
- exit_status = nil
154
- channel = session.open_channel do |ch|
155
- ch.on_data { |data| output.concat(data) }
156
- ch.on_extended_data { |_, _, data| output.concat(data) }
157
- ch.on_request("exit-status") { |_, data| exit_status = data.read_long }
158
- # on signal: sending the signal value (such as 'TERM')
159
- ch.on_request("exit-signal") do |_, data|
160
- exit_status = data.read_string
161
- ch.close
162
- ch.wait
163
- end
164
- ch.exec command do |_, success|
165
- raise "could not execute command" unless success
166
- end
167
- end
168
- channel.wait
169
- return exit_status, output
170
- end
171
-
172
- def su_prefix
173
- return if @effective_user.nil? || @effective_user == @ssh_user
174
- case @effective_user_method
175
- when 'sudo'
176
- "sudo -n -u #{@effective_user} "
177
- when 'su'
178
- "su - #{@effective_user} -c "
179
- else
180
- raise "effective_user_method ''#{@effective_user_method}'' not supported"
181
- end
182
- end
183
-
184
- def prepare_known_hosts
185
- path = local_command_file('known_hosts')
186
- if @host_public_key
187
- write_command_file_locally('known_hosts', "#{@host} #{@host_public_key}")
188
- end
189
- return path
190
- end
191
-
192
- def local_command_dir
193
- File.join(@local_working_dir, 'foreman-proxy', "foreman-ssh-cmd-#{@id}")
194
- end
195
-
196
- def local_command_file(filename)
197
- File.join(local_command_dir, filename)
198
- end
199
-
200
- def remote_command_dir
201
- File.join(@remote_working_dir, "foreman-ssh-cmd-#{id}")
202
- end
203
-
204
- def remote_command_file(filename)
205
- File.join(remote_command_dir, filename)
206
- end
207
-
208
- def ensure_local_directory(path)
209
- if File.exist?(path)
210
- raise "#{path} expected to be a directory" unless File.directory?(path)
211
- else
212
- FileUtils.mkdir_p(path)
213
- end
214
- return path
215
- end
216
-
217
- def cp_script_to_remote
218
- local_script_file = write_command_file_locally('script', sanitize_script(@script))
219
- File.chmod(0555, local_script_file)
220
- remote_script_file = remote_command_file('script')
221
- upload_file(local_script_file, remote_script_file)
222
- return remote_script_file
223
- end
224
-
225
- def upload_file(local_path, remote_path)
226
- ensure_remote_directory(File.dirname(remote_path))
227
- scp = Net::SCP.new(session)
228
- upload_channel = scp.upload(local_path, remote_path)
229
- upload_channel.wait
230
- ensure
231
- if upload_channel
232
- upload_channel.close
233
- upload_channel.wait
234
- end
235
- end
236
-
237
- def ensure_remote_directory(path)
238
- exit_code, output = run_sync("mkdir -p #{path}")
239
- if exit_code != 0
240
- raise "Unable to create directory on remote system #{path}: exit code: #{exit_code}\n #{output}"
241
- end
242
- end
243
-
244
- def sanitize_script(script)
245
- script.tr("\r", '')
246
- end
247
-
248
- def write_command_file_locally(filename, content)
249
- path = local_command_file(filename)
250
- ensure_local_directory(File.dirname(path))
251
- File.write(path, content)
252
- return path
253
- end
254
-
255
- # when a remote server disconnects, it's hard to tell if it was on purpose (when calling reboot)
256
- # or it's an error. When it's expected, we expect the script to produce 'restart host' as
257
- # its last command output
258
- def check_expecting_disconnect
259
- last_output = @continuous_output.raw_outputs.find { |d| d['output_type'] == 'stdout' }
260
- return unless last_output
261
- if EXPECTED_POWER_ACTION_MESSAGES.any? { |message| last_output['output'] =~ /^#{message}/ }
262
- @expecting_disconnect = true
263
- end
264
- end
265
- end
266
- end
@@ -1,3 +0,0 @@
1
- module ForemanRemoteExecutionCore
2
- VERSION = '1.0.0'
3
- end