Class: RemoteCommandHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/help/remote_command_handler.rb

Overview

Provides methods to be executed via ssh to remote instances.

Direct Known Subclasses

DmCryptHelper

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRemoteCommandHandler

Returns a new instance of RemoteCommandHandler.



9
10
11
12
# File 'lib/help/remote_command_handler.rb', line 9

def initialize()
  @logger = Logger.new(STDOUT)
  @use_sudo = false
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



8
9
10
# File 'lib/help/remote_command_handler.rb', line 8

def logger
  @logger
end

#ssh_sessionObject

Returns the value of attribute ssh_session.



8
9
10
# File 'lib/help/remote_command_handler.rb', line 8

def ssh_session
  @ssh_session
end

#use_sudoObject

Returns the value of attribute use_sudo.



8
9
10
# File 'lib/help/remote_command_handler.rb', line 8

def use_sudo
  @use_sudo
end

Instance Method Details

#connect(ip, user, key_data, timeout = 30) ⇒ Object

Connect to the machine as root using keydata from a keyfile. Params:

  • ip: ip address of the machine to connect to

  • user: user name

  • key_data: key_data to be used for authentication

NB: set paranoid to false in order to avoid server key verification, thus avoiding probleme when IP are reused



47
48
49
50
51
# File 'lib/help/remote_command_handler.rb', line 47

def connect(ip, user, key_data, timeout = 30)
  @use_sudo = false
  @ssh_session = Net::SSH.start(ip, user, :key_data => [key_data], :timeout => timeout, :paranoid => false, :verbose => :warn)
  @use_sudo = true unless user.strip == 'root'
end

#connect_with_keyfile(ip, user_name, keyfile, timeout = 30) ⇒ Object

Connect to the machine as root using a keyfile. Params:

  • ip: ip address of the machine to connect to

  • keyfile: path of the keyfile to be used for authentication



35
36
37
38
39
# File 'lib/help/remote_command_handler.rb', line 35

def connect_with_keyfile(ip, user_name, keyfile, timeout = 30)
  @use_sudo = false
  @ssh_session = Net::SSH.start(ip, user_name, :keys => [keyfile], :timeout => timeout, :verbose => :warn)
  @use_sudo = true unless user_name.strip == 'root'
end

#create_filesystem(fs_type, volume) ⇒ Object



168
169
170
171
172
# File 'lib/help/remote_command_handler.rb', line 168

def create_filesystem(fs_type, volume)
  e = "mkfs -t #{fs_type} #{volume}"
  #remote_execute(e, "y") #TODO: quiet mode?
  remote_execute(e, "y", false)
end

#disable_sudoers_requirettyObject

Disable ‘Defaults requiretty’ option in sudoers file



243
244
245
246
247
248
249
250
# File 'lib/help/remote_command_handler.rb', line 243

def disable_sudoers_requiretty()
  e = "sed -r -e \'s/^(Defaults[[:blank:]]+requiretty)$/# \\1/\' -i /etc/sudoers"
  @logger.debug "going to execute '#{e}'"
  status = remote_exec_helper(e, nil, nil, true)
  if status != true
    raise Exception.new("disabling 'requiretty' from sudoers failed with status: #{status}")
  end
end

#disconnectObject

Disconnect the current handler



54
55
56
# File 'lib/help/remote_command_handler.rb', line 54

def disconnect
  @ssh_session.close
end

#drive_mounted?(path) ⇒ Boolean

Checks if the drive on path is mounted

Returns:

  • (Boolean)


185
186
187
188
189
190
191
192
193
194
# File 'lib/help/remote_command_handler.rb', line 185

def drive_mounted?(path)
  #check if drive mounted
  drive_found = stdout_contains?("mount", "on #{path} type")
  if drive_found
    return file_exists?(path)
  else
    @logger.debug "not mounted (since #{path} non-existing)"
    false
  end
end

#drive_mounted_as?(device, path) ⇒ Boolean

Checks if the drive on path is mounted with the specific device

Returns:

  • (Boolean)


197
198
199
200
# File 'lib/help/remote_command_handler.rb', line 197

def drive_mounted_as?(device, path)
  #check if drive mounted
  stdout_contains?("mount", "#{device} on #{path} type")
end

#echo(data, file) ⇒ Object



315
316
317
318
319
320
321
322
# File 'lib/help/remote_command_handler.rb', line 315

def echo(data, file)
  exec = "echo #{data} > #{file}"
  @logger.debug "going to execute #{exec}"
  remote_execute(exec, nil, true)
  if !file_exists?(file)
    raise Exception.new("file #{file} could not be created")
  end
end

#enable_sudoers_requirettyObject

Enable ‘Defaults requiretty’ option in sudoers file



253
254
255
256
257
258
259
260
# File 'lib/help/remote_command_handler.rb', line 253

def enable_sudoers_requiretty()
  e = "sed -r -e \'s/^#[[:blank:]]*(Defaults[[:blank:]]+requiretty)$/\\1/\' -i /etc/sudoers"
  @logger.debug "going to execute '#{e}'"
  status = remote_exec_helper(e, nil, nil, true)
  if status != true
    raise Exception.new("enabling 'requiretty' from sudoers failed with status: #{status}")
  end
end

#file_exists?(path) ⇒ Boolean

Check if the path/file specified exists

Returns:

  • (Boolean)


59
60
61
# File 'lib/help/remote_command_handler.rb', line 59

def file_exists?(path)
  remote_execute("ls #{path}")
end

#file_size(file) ⇒ Object



63
64
65
# File 'lib/help/remote_command_handler.rb', line 63

def file_size(file)
  get_output("ls -lh #{file} | cut -d ' ' -f 5").strip
end

#get_device_label(device) ⇒ Object

Get device label



108
109
110
# File 'lib/help/remote_command_handler.rb', line 108

def get_device_label(device)
  get_output("e2label #{device}").strip
end

#get_device_label_ext(device, fs_type) ⇒ Object

Get device label



113
114
115
116
117
118
119
120
# File 'lib/help/remote_command_handler.rb', line 113

def get_device_label_ext(device, fs_type)
  if fs_type.eql?("xfs")
    cmd = "xfs_admin -l #{device} | sed -r -e 's/^label[[:blank:]]*=[[:blank:]]*\"(.*)\"$/\\1/'"
  else
    cmd = "e2label #{device}"
  end
  get_output(cmd).strip
end

#get_device_partition(device) ⇒ Object

Return all the partitions of a device



78
79
80
# File 'lib/help/remote_command_handler.rb', line 78

def get_device_partition(device)
  get_output("ls #{device}*").strip
end

#get_output(exec_string, push_data = nil, stdout = [], stderr = []) ⇒ Object

Executes the specified #exec_string on a remote session specified as #ssh_session. When #push_data is specified, the data will be used as input for the command and thus allows to respond in advance to commands that ask the user something. It returns stdout. When #stdout or #stderr is specified as arrays, the respective output is also written into those arrays.



367
368
369
370
371
372
373
# File 'lib/help/remote_command_handler.rb', line 367

def get_output(exec_string, push_data = nil, stdout = [], stderr = [])
  exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
  stdout = []
  stderr = []
  remote_exec_helper(exec_string, stdout, stderr, true)
  stdout.join()
end

#get_partition_device(part) ⇒ Object

Get the device of a specific partition



102
103
104
105
# File 'lib/help/remote_command_handler.rb', line 102

def get_partition_device(part)
  #get_output("cat /etc/mtab | grep -E '[[:blank:]]+" + "#{part}" + "[[:blank:]]+' | cut -d ' ' -f 1").strip
  get_output("mount | grep -E '[[:blank:]]+" + "#{part}" + "[[:blank:]]+' | cut -d ' ' -f 1").strip
end

#get_partition_fs_type(part) ⇒ Object

Get filesystem type



143
144
145
# File 'lib/help/remote_command_handler.rb', line 143

def get_partition_fs_type(part)
  get_output("cat /etc/mtab | grep -E '[[:blank:]]+" + "#{part}" + "[[:blank:]]+' | cut -d ' ' -f 3").strip
end

#get_partition_table(device) ⇒ Object

Get the partition table of a device



83
84
85
# File 'lib/help/remote_command_handler.rb', line 83

def get_partition_table(device)
  get_output("sfdisk -d #{device}")
end

#get_root_deviceObject

Get root partition label NB: add ‘/dev’ detection in order to avoid entries containing ‘rootfs’



95
96
97
98
99
# File 'lib/help/remote_command_handler.rb', line 95

def get_root_device()
  #get_output("cat /etc/mtab | grep -E '[[:blank:]]+\/[[:blank:]]+' | cut -d ' ' -f 1").strip
  #get_output("mount | grep -E '[[:blank:]]+\/[[:blank:]]+' | cut -d ' ' -f 1").strip
  get_output("mount | grep -E '[[:blank:]]+\/[[:blank:]]+' | grep -E '\/dev' | cut -d ' ' -f 1").strip
end

#get_root_fs_typeObject

Get filesystem type



138
139
140
# File 'lib/help/remote_command_handler.rb', line 138

def get_root_fs_type()
  get_output("cat /etc/mtab | grep -E '[[:blank:]]+\/[[:blank:]]+' | cut -d ' ' -f 3").strip
end

#install(software_package) ⇒ Object

Installs the software package specified.



148
149
150
151
152
153
154
155
156
157
# File 'lib/help/remote_command_handler.rb', line 148

def install(software_package)
  e = "yum -yq install #{software_package}"
  yum = remote_execute(e)
  if !yum
    @logger.info("yum installation failed; try apt-get")
    e = "apt-get -yq install #{software_package}"
    apt = remote_execute(e)
    @logger.info("apt=get installation? #{apt}")
  end
end

#is_port_open?(ip, port) ⇒ Boolean

Checks for a given IP/port if there’s a response on that port.

Returns:

  • (Boolean)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/help/remote_command_handler.rb', line 15

def is_port_open?(ip, port)
  begin
    Timeout::timeout(5) do
      begin
        s = TCPSocket.new(ip, port)
        s.close
        return true
      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
        return false
      end
    end
  rescue Timeout::Error
    return false
  end
end

#local_decompress_and_dump(source_filename, target_device) ⇒ Object

dump a file to a device locally



288
289
290
291
292
293
# File 'lib/help/remote_command_handler.rb', line 288

def local_decompress_and_dump(source_filename, target_device)
  #e = "sh -c 'gunzip -c #{source_filename} | dd of=#{target_device}'"
  e = "sh -c 'gunzip -1 -c #{source_filename} | dd of=#{target_device} bs=512k'"
  @logger.debug "going to execute #{e}" 
  status = remote_exec_helper(e, nil, nil, true)
end

#local_dump(source_device, target_filename) ⇒ Object

dump a device in a local file or a dump local file to a device



296
297
298
299
300
# File 'lib/help/remote_command_handler.rb', line 296

def local_dump(source_device, target_filename)
  e = "sh -c 'dd if=#{source_device} of=#{target_filename} bs=1M'"
  @logger.debug "going to execute #{e}" 
  status = remote_exec_helper(e, nil, nil, true)
end

#local_dump_and_compress(source_device, target_filename) ⇒ Object

dump and compress a device in a file locally



280
281
282
283
284
285
# File 'lib/help/remote_command_handler.rb', line 280

def local_dump_and_compress(source_device, target_filename)
  #e = "sh -c 'dd if=#{source_device} | gzip > #{target_filename}'"
  e = "sh -c 'dd if=#{source_device} bs=512k | gzip -1 > #{target_filename}'"
  @logger.debug "going to execute #{e}" 
  status = remote_exec_helper(e, nil, nil, true)
end

#local_rcopy(source_path, dest_path, exclude_path = nil) ⇒ Object

Copy directory using basic cp exclude_path: a space separated list of directory



211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/help/remote_command_handler.rb', line 211

def local_rcopy(source_path, dest_path, exclude_path = nil)
  e = ""
  if exclude_path.nil? || exclude_path.empty? 
    e = "cp -Rpv #{source_path} #{dest_path}"
  else
    # only one level of exclusion
    exclusion_regexp = exclude_path.gsub(' ', '|')
    e = "for dir in $(ls -d #{source_path}* | grep -E -v '#{exclusion_regexp}'); do cp -Rpv $dir #{dest_path}; done;"
  end
  @logger.debug "going to execute #{e}"
  remote_exec_helper(e, nil, nil, false)
end

#local_rsync(source_path, dest_path, exclude_path = nil) ⇒ Object

Copy directory using options -avHx



225
226
227
228
229
230
231
232
233
# File 'lib/help/remote_command_handler.rb', line 225

def local_rsync(source_path, dest_path, exclude_path = nil)
  exclude = ""
  if exclude_path != nil
    exclude = "--exclude #{exclude_path}"
  end
  e = "rsync -avHx #{exclude} #{source_path} #{dest_path}"
  @logger.debug "going to execute #{e}"
  remote_exec_helper(e, nil, nil, true) #TODO: handle output in stderr?
end

#mkdir(path) ⇒ Object



174
175
176
177
# File 'lib/help/remote_command_handler.rb', line 174

def mkdir(path)
  e = "mkdir #{path}"
  remote_execute(e, nil, true)
end

#mount(device, path) ⇒ Object



179
180
181
182
# File 'lib/help/remote_command_handler.rb', line 179

def mount(device, path)
  e = "mount #{device} #{path}"
  remote_execute(e, nil, true)
end

#mount_outputObject

return teh result of mount



73
74
75
# File 'lib/help/remote_command_handler.rb', line 73

def mount_output()
  get_output("mount").strip
end

#remote_execute(exec_string, push_data = nil, raise_exception = false) ⇒ Object

Executes the specified #exec_string on a remote session specified. When #push_data is specified, the data will be used as input for the command and thus allow to respond in advance to commands that ask the user something. The method will return true if nothing was written into stderr, otherwise false. When #raise_exception is set, an exception will be raised instead of returning false.

Raises:

  • (Exception)


331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/help/remote_command_handler.rb', line 331

def remote_execute(exec_string, push_data = nil, raise_exception = false)
  exec_string = "sh -c 'echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt'" unless push_data == nil
  stdout = []
  stderr = []
  result = remote_exec_helper(exec_string, stdout, stderr, true)
  #dump stdout in case of error
  if result == false
    em = "RemoteCommandHandler: #{exec_string} lead to stdout message: #{stdout.join().strip}"
    @logger.info(em) unless stdout.size == 0
  end
  em = "RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}"
  @logger.info(em) unless stderr.size == 0
  raise Exception.new(em) unless result == true || raise_exception == false
  result
end

#remote_rsync(keyfile, source_path, dest_ip, dest_user, dest_path) ⇒ Object



262
263
264
265
266
267
268
269
270
# File 'lib/help/remote_command_handler.rb', line 262

def remote_rsync(keyfile, source_path, dest_ip, dest_user, dest_path)
  e = "rsync -rlpgoDzq --rsh 'ssh -o stricthostkeychecking=no -i #{keyfile}' --rsync-path='sudo rsync'"+
        " #{source_path} #{dest_user}@#{dest_ip}:#{dest_path}"
  @logger.debug "going to execute #{e}"
  status = remote_exec_helper(e, nil, nil, true) #TODO: handle output in stderr?
  if status != true
    raise Exception.new("rsync bewteen source and target servers failed with status: #{status}")
  end
end

#remote_rsync_old(keyfile, source_path, dest_ip, dest_path) ⇒ Object

Rsync directory via an ssh-tunnel.



236
237
238
239
240
# File 'lib/help/remote_command_handler.rb', line 236

def remote_rsync_old(keyfile, source_path, dest_ip, dest_path)
  e = "rsync -rlpgoDzq -e "+'"'+"ssh -o stricthostkeychecking=no -i #{keyfile}"+'"'+" #{source_path} root@#{dest_ip}:#{dest_path}"
  @logger.debug "going to execute #{e}"
  remote_exec_helper(e, nil, nil, false) #TODO: handle output in stderr?
end

#retrieve_osObject

Returns the result of uname -a (Linux)



68
69
70
# File 'lib/help/remote_command_handler.rb', line 68

def retrieve_os()
  get_output("uname -r").strip
end

#scp(keyfile, source_path, dest_ip, dest_user, dest_path) ⇒ Object

Copy directory via an ssh-tunnel.



273
274
275
276
277
# File 'lib/help/remote_command_handler.rb', line 273

def scp(keyfile, source_path, dest_ip, dest_user, dest_path)
  e = "scp -Cpqr -o stricthostkeychecking=no -i #{keyfile} #{source_path} #{dest_user}@#{dest_ip}:#{dest_path}"
  @logger.debug "going to execute #{e}"
  remote_exec_helper(e, nil, nil, false) #TODO: handle output in stderr?
end

#set_device_label(device, label) ⇒ Object

Set device label



123
124
125
# File 'lib/help/remote_command_handler.rb', line 123

def set_device_label(device, label)
  remote_execute("e2label #{device} #{label}", nil, false)
end

#set_device_label_ext(device, label, fs_type) ⇒ Object

Set device label



128
129
130
131
132
133
134
135
# File 'lib/help/remote_command_handler.rb', line 128

def set_device_label_ext(device, label, fs_type)
  if fs_type.eql?("xfs")
    cmd = "xfs_admin -L #{label} #{device}"
  else
    cmd = "e2label #{device} #{label}"
  end
  remote_execute(cmd, nil, false)
end

#set_partition_table(device, partition_table) ⇒ Object

Set the partition table of a device



88
89
90
91
# File 'lib/help/remote_command_handler.rb', line 88

def set_partition_table(device, partition_table)
  push_data = "\"" + partition_table.gsub(/\/dev\/(s|xv)d[a-z]/, "#{device}") + "\""
  remote_execute("sfdisk -f #{device}", push_data, nil)
end

#stdout_contains?(exec_string, search_string = "", push_data = nil) ⇒ Boolean

Executes the specified #exec_string on a remote session specified as #ssh_session and logs the command-output into the specified #logger. When #push_data is specified, the data will be used as input for the command and thus allows to respond in advance to commands that ask the user something. If the output in stdout contains the specified #search_string, the method returns true otherwise false. Output to stderr will be logged.

Returns:

  • (Boolean)


353
354
355
356
357
358
359
360
# File 'lib/help/remote_command_handler.rb', line 353

def stdout_contains?(exec_string, search_string = "", push_data = nil)
  exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
  stdout = []
  stderr = []
  remote_exec_helper(exec_string, stdout, stderr)
  @logger.info("RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}") unless stderr.size == 0
  stdout.join().include?(search_string)
end

#tools_installed?(software_package) ⇒ Boolean

Checks if the software package specified is installed.

Returns:

  • (Boolean)


160
161
162
163
164
165
166
# File 'lib/help/remote_command_handler.rb', line 160

def tools_installed?(software_package)
  exec_string = "which #{software_package}"
  stdout = []
  stderr = []
  result = remote_exec_helper(exec_string, stdout, stderr)
  return result == true && stdout.size > 0
end

#umount(path) ⇒ Object

Unmount the specified path.



203
204
205
206
207
# File 'lib/help/remote_command_handler.rb', line 203

def umount(path)
  exec_string = "umount #{path}"
  remote_execute(exec_string)
  !drive_mounted?(path)
end

#upload(ip, user, key_data, local_file, destination_file, timeout = 60) ⇒ Object



375
376
377
378
379
380
381
# File 'lib/help/remote_command_handler.rb', line 375

def upload(ip, user, key_data, local_file, destination_file, timeout = 60)
  Timeout::timeout(timeout) {
    Net::SCP.start(ip, user, {:key_data => [key_data], :timeout => timeout, :paranoid => false, :verbose => :warn}) do |scp|
      scp.upload!(local_file, destination_file)
    end
  }
end

#zip(source_path, destination_file) ⇒ Object

Zip the complete contents of the source path into the destination file. Returns the an array with stderr output messages.



304
305
306
307
308
309
310
311
312
313
# File 'lib/help/remote_command_handler.rb', line 304

def zip(source_path, destination_file)
  begin
    exec = "cd #{source_path}; zip -ryq #{destination_file} *"
    stderr = []
    get_output(exec, nil, nil, stderr)
    return stderr
  rescue Exception => e
    raise Exception.new("zip failed due to #{e.message}")
  end
end