Module: OpsWalrus::HostDSL

Included in:
Host
Defined in:
lib/opswalrus/host.rb

Instance Method Summary collapse

Instance Method Details

#autoretry(delay: 5, timeout: 300, limit: 3, &block) ⇒ Object



163
164
165
166
167
168
169
170
171
172
# File 'lib/opswalrus/host.rb', line 163

def autoretry(delay: 5, timeout: 300, limit: 3, &block)
  attempts ||= 0
  attempts += 1
  block.call
rescue RetriableRemoteInvocationError => e
  if attempts <= limit
    reconnected = reconnect(delay, timeout)
    retry if reconnected
  end
end

#debug(msg) ⇒ Object



382
383
384
# File 'lib/opswalrus/host.rb', line 382

def debug(msg)
  puts msg.mustache(2) if App.instance.debug? || App.instance.trace?    # we use two here, because one stack frame accounts for the call from the ops script into HostProxy#desc
end

#desc(msg) ⇒ Object



374
375
376
# File 'lib/opswalrus/host.rb', line 374

def desc(msg)
  puts Style.green(msg.mustache(2))    # we use two here, because one stack frame accounts for the call from the ops script into HostProxy#desc
end

#env(*args, **kwargs) ⇒ Object



386
387
388
# File 'lib/opswalrus/host.rb', line 386

def env(*args, **kwargs)
  @ops_file_script.env(*args, **kwargs)
end

#host_prop(name) ⇒ Object



394
395
396
# File 'lib/opswalrus/host.rb', line 394

def host_prop(name)
  @props[name] || @default_props[name]
end

#params(*args, **kwargs) ⇒ Object



390
391
392
# File 'lib/opswalrus/host.rb', line 390

def params(*args, **kwargs)
  @ops_file_script.params(*args, **kwargs)
end

#parse_stdout_and_script_return_value(command_output) ⇒ Object



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

def parse_stdout_and_script_return_value(command_output)
  output_sections = command_output.split(/#{::OpsWalrus::App::SCRIPT_RESULT_HEADER}/)
  case output_sections.count
  when 1
    stdout, ops_script_retval = output_sections.first, nil
    # puts "found it1!: #{output_sections.inspect}"
  when 2
    stdout, ops_script_retval = *output_sections
    # puts "found it2!: #{ops_script_retval}"
  else
    # this is unexpected
    ops_script_retval = output_sections.pop
    stdout = output_sections.join(::OpsWalrus::App::SCRIPT_RESULT_HEADER)
    # puts "found it3!: #{ops_script_retval}"
  end
  [stdout, ops_script_retval]
end

#reboot(delay: 1, sync: true, timeout: 300) ⇒ Object

delay: integer? # default: 1 - 1 second delay before reboot sync: boolean? # default: true - wait for the remote host to become available again before returning success/failure timeout: integer? # default: 300 - 300 seconds (5 minutes)



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/opswalrus/host.rb', line 145

def reboot(delay: 1, sync: true, timeout: 300)
  delay = 1 if delay < 1

  desc "Rebooting #{to_s} (alias=#{self.alias})"
  reboot_success = sh? 'sudo /bin/sh -c "(sleep {{ delay }} && reboot) &"'.mustache
  puts reboot_success

  reconnect_time = reconnect(delay, timeout) if sync
  reconnect_success = !!reconnect_time

  {
    success: reboot_success && (sync == reconnect_success),
    rebooted: reboot_success,
    reconnected: reconnect_success,
    reboot_duration: reconnect_time
  }
end

#reconnect(delay = 1, timeout = 300) ⇒ Object

returns an integer number of seconds if reconnected; nil otherwise



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/opswalrus/host.rb', line 175

def reconnect(delay = 1, timeout = 300)
  delay = 1 if delay < 1

  desc "Waiting for #{to_s} (alias=#{self.alias}) to become available. Reconnecting."
  initial_reconnect_delay = delay + 10
  sleep initial_reconnect_delay

  reconnected = false
  give_up = false
  t1 = Time.now
  until reconnected || give_up
    begin
      reconnected = sh?('true')
      # while trying to reconnect, we expect the following exceptions:
      # 1. Net::SSH::Disconnect < Net::SSH::Exception with message: "connection closed by remote host"
      # 2. Errno::ECONNRESET < SystemCallError with message: "Connection reset by peer"
      # 3. Errno::ECONNREFUSED < SystemCallError with message: "Connection refused - connect(2) for 192.168.56.10:22"
    rescue Net::SSH::Disconnect, Net::SSH::ConnectionTimeout, Errno::ECONNRESET, Errno::ECONNREFUSED => e
      # noop; we expect these while we're trying to reconnect
    rescue => e
      puts "#{e.class} < #{e.class.superclass}"
      puts e.message
      puts e.backtrace.take(5).join("\n")
    end

    wait_time_elapsed_in_seconds = Time.now - t1
    give_up = wait_time_elapsed_in_seconds > timeout
    sleep 5
  end

  if reconnected
    initial_reconnect_delay + (Time.now - t1)

    # initialize session again
    _initialize_session
  end
end

#run_ops(ops_command, ops_command_options = nil, command_arguments, in_bundle_root_dir: true, ops_prompt_for_sudo_password: false) ⇒ Object

runs the specified ops command with the specified command arguments returns [stdout, stderr, exit_status]



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/opswalrus/host.rb', line 350

def run_ops(ops_command, ops_command_options = nil, command_arguments, in_bundle_root_dir: true, ops_prompt_for_sudo_password: false)
  local_hostname_for_remote_host = if self.alias
    "#{host} (#{self.alias})"
  else
    host
  end

  # cmd = "OPS_GEM=\"#{OPS_GEM}\" OPSWALRUS_LOCAL_HOSTNAME='#{local_hostname_for_remote_host}'; $OPS_GEM exec --conservative -g opswalrus ops"
  cmd = "OPSWALRUS_LOCAL_HOSTNAME='#{local_hostname_for_remote_host}' eval #{OPS_CMD}"
  if App.instance.trace?
    cmd << " --loudest"
  elsif App.instance.debug?
    cmd << " --louder"
  elsif App.instance.info?
    cmd << " --loud"
  end
  cmd << " #{ops_command.to_s}"
  cmd << " #{ops_command_options.to_s}" if ops_command_options
  cmd << " #{@tmp_bundle_root_dir}" if in_bundle_root_dir
  cmd << " #{command_arguments}" unless command_arguments.empty?

  shell!(cmd, ops_prompt_for_sudo_password: ops_prompt_for_sudo_password)
end

#sh(desc_or_cmd = nil, cmd = nil, input: nil, &block) ⇒ Object

runs the given command returns the stdout from the command



215
216
217
218
# File 'lib/opswalrus/host.rb', line 215

def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
  out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
  out
end

#sh?(desc_or_cmd = nil, cmd = nil, input: nil, &block) ⇒ Boolean

runs the given command returns true if the exit status was success; false otherwise

Returns:

  • (Boolean)


222
223
224
225
# File 'lib/opswalrus/host.rb', line 222

def sh?(desc_or_cmd = nil, cmd = nil, input: nil, &block)
  out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
  status == 0
end

#shell(desc_or_cmd = nil, cmd = nil, input: nil, &block) ⇒ Object

returns the tuple: [stdout, stderr, exit_status]



228
229
230
# File 'lib/opswalrus/host.rb', line 228

def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
  shell!(desc_or_cmd, cmd, block, input: input)
end

#shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil, ops_prompt_for_sudo_password: false) ⇒ Object

returns the tuple: [stdout, stderr, exit_status]



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/opswalrus/host.rb', line 233

def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil, ops_prompt_for_sudo_password: false)
  # description = nil

  return ["", "", 0] if !desc_or_cmd && !cmd && !block    # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully

  description = desc_or_cmd if cmd || block
  description = WalrusLang.render(description, block.binding) if description && block
  cmd = block.call if block
  cmd ||= desc_or_cmd

  cmd = if cmd =~ /{{.*}}/
    if block
      WalrusLang.render(cmd, block.binding)
    else
      offset = 3    # 3, because 1 references the stack frame corresponding to the caller of WalrusLang.eval,
                    # 2 references the stack frame corresponding to the caller of shell!,
                    # and 3 references the stack frame corresponding to the caller of either sh/sh?/shell
      WalrusLang.eval(cmd, offset)
    end
  else
    cmd
  end
  # cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/

  #cmd = Shellwords.escape(cmd)

  cmd_id = Random.uuid.split('-').first
  output_block = StringIO.open do |io|
    if App.instance.info?   # this is true if log_level is trace, debug, info
      io.print Style.blue(host)
      io.print " (#{Style.blue(self.alias)})" if self.alias
      io.print " | #{Style.green(description)}" if description
      io.puts
      io.print Style.yellow(cmd_id)
      io.print Style.green.bold(" > ")
      io.puts Style.yellow(cmd)
    elsif App.instance.warn? && description
      io.print Style.blue(host)
      io.print " (#{Style.blue(self.alias)})" if self.alias
      io.print " | #{Style.green(description)}" if description
      io.puts
    end
    io.string
  end
  puts output_block unless output_block.empty?

  return unless cmd && !cmd.strip.empty?

  t1 = Time.now
  output, stderr, exit_status = if App.instance.dry_run?
    ["", "", 0]
  else
    sshkit_cmd = execute_cmd(cmd, input_mapping: input, ops_prompt_for_sudo_password: ops_prompt_for_sudo_password)
    [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
  end
  t2 = Time.now
  seconds = t2 - t1

  stdout, remote_ops_script_retval = parse_stdout_and_script_return_value(output)

  output_block = StringIO.open do |io|
    if App.instance.info?   # this is true if log_level is trace, debug, info
      if App.instance.trace?
        io.puts Style.cyan(stdout)
        io.puts Style.red(stderr)
      elsif App.instance.debug?
        io.puts Style.cyan(stdout)
        io.puts Style.red(stderr)
      elsif App.instance.info?
        io.puts Style.cyan(stdout)
        io.puts Style.red(stderr)
      end
      io.print Style.yellow(cmd_id)
      io.print Style.blue(" | Finished in #{seconds} seconds with exit status ")
      if exit_status == 0
        io.puts Style.green("#{exit_status} (success)")
      else
        io.puts Style.red("#{exit_status} (failure)")
      end
      io.puts Style.green("*" * 80)
    elsif App.instance.warn? && description
      io.print Style.blue("Finished in #{seconds} seconds with exit status ")
      if exit_status == 0
        io.puts Style.green("#{exit_status} (success)")
      else
        io.puts Style.red("#{exit_status} (failure)")
      end
      io.puts Style.green("*" * 80)
    end
    io.string
  end
  puts output_block unless output_block.empty?

  out = remote_ops_script_retval || stdout
  [out, stderr, exit_status]
end

#ssh_sessionObject



398
399
400
# File 'lib/opswalrus/host.rb', line 398

def ssh_session
  @sshkit_backend
end

#warn(msg) ⇒ Object



378
379
380
# File 'lib/opswalrus/host.rb', line 378

def warn(msg)
  puts Style.yellow(msg.mustache(2))    # we use two here, because one stack frame accounts for the call from the ops script into HostProxy#desc
end