Class: CemWinSpec::WinExec::Exec

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/cem_win_spec/win_exec/exec.rb

Overview

Class for executing PowerShell commands over WinRM

Constant Summary collapse

HALTING_ERRORS =
[
  IapTunnelStartError,
].freeze

Constants included from Logging

Logging::LEVEL_MAP

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

#current_log_format, current_log_format, current_log_level, #current_log_level, included, log_setup!, #log_setup!, logger, #logger, new_log_formatter, #new_log_formatter, new_log_level, #new_log_level

Constructor Details

#initialize(title: 'Command', l_cmd: nil, r_cmd: nil, iap_tunnel: nil, ma_builder: nil, r_test_cmds: nil, &block) ⇒ Exec

Returns a new instance of Exec.



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/cem_win_spec/win_exec/exec.rb', line 18

def initialize(title: 'Command', l_cmd: nil, r_cmd: nil, iap_tunnel: nil, ma_builder: nil, r_test_cmds: nil, &block)
  add_title(title)
  add_local_cmd(l_cmd) unless l_cmd.nil?
  add_remote_cmd(r_cmd) unless r_cmd.nil?
  add_iap_tunnel(iap_tunnel) unless iap_tunnel.nil?
  add_ma_builder(ma_builder) unless ma_builder.nil?
  add_rspec_test_cmds(r_test_cmds) unless r_test_cmds.nil?
  add_command_block(&block) unless block.nil?
  @reuse_tunnel = true
  @ignore_exitcode = false
  @result = nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, **kwargs, &block) ⇒ Object

Proxy method calls for methods that don’t exist here to various other objects. This allows doing things like: ‘win_exec.remote_exec(’Get-Process’)‘ and calling the `exec` method on the `winrm_exec` object. This is done by checking method name prefixes and calling the corresponding method on the appropriate object. The prefix is removed from the method name before calling the method on the object. The supported prefixes are:

local_ - calls the method on the @local_cmd object
remote_ - calls the method on the @remote_cmd object
rspec_ - calls the method on the @rspec_test_cmds object
module_archive_ - calls the method on the @ma_builder object


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/cem_win_spec/win_exec/exec.rb', line 108

def method_missing(method, *args, **kwargs, &block)
  if method.to_s.start_with?('local_') # proxy to local_exec
    method = method.to_s.sub('local_', '').to_sym
    @local_cmd.send(method, *args, **kwargs, &block)
  elsif method.to_s.start_with?('remote_') # proxy to remote_exec
    method = method.to_s.sub('remote_', '').to_sym
    @remote_cmd.send(method, *args, **kwargs, &block)
  elsif method.to_s.start_with?('rspec_') # proxy to rspec_test_cmds
    method = method.to_s.sub('rspec_', '').to_sym
    @rspec_test_cmds.send(method, *args, **kwargs, &block)
  elsif method.to_s.start_with?('module_archive_') # proxy to ma_builder
    method = method.to_s.sub('module_archive_', '').to_sym
    @ma_builder.send(method, *args, **kwargs, &block)
  else
    super
  end
end

Instance Attribute Details

#ignore_exitcodeObject Also known as: ignore_exitcode?

Returns the value of attribute ignore_exitcode.



16
17
18
# File 'lib/cem_win_spec/win_exec/exec.rb', line 16

def ignore_exitcode
  @ignore_exitcode
end

#resultObject (readonly)

Returns the value of attribute result.



16
17
18
# File 'lib/cem_win_spec/win_exec/exec.rb', line 16

def result
  @result
end

#reuse_tunnelObject Also known as: reuse_tunnel?

Returns the value of attribute reuse_tunnel.



16
17
18
# File 'lib/cem_win_spec/win_exec/exec.rb', line 16

def reuse_tunnel
  @reuse_tunnel
end

#titleObject (readonly)

Returns the value of attribute title.



16
17
18
# File 'lib/cem_win_spec/win_exec/exec.rb', line 16

def title
  @title
end

Instance Method Details

#add_command_block(&block) ⇒ Object

Raises:

  • (ArgumentError)


72
73
74
75
76
# File 'lib/cem_win_spec/win_exec/exec.rb', line 72

def add_command_block(&block)
  raise ArgumentError, 'block must be a Proc' unless block.is_a?(Proc)

  @block = block
end

#add_iap_tunnel(value) ⇒ Object



49
50
51
52
53
54
55
# File 'lib/cem_win_spec/win_exec/exec.rb', line 49

def add_iap_tunnel(value)
  unless value.respond_to?(:start) && value.respond_to?(:stop) && value.respond_to?(:with)
    raise ArgumentError, 'iap_tunnel must implement the #start, #stop, and #with methods'
  end

  @iap_tunnel = value
end

#add_local_cmd(value) ⇒ Object

Raises:

  • (ArgumentError)


37
38
39
40
41
# File 'lib/cem_win_spec/win_exec/exec.rb', line 37

def add_local_cmd(value)
  raise ArgumentError, 'local_exec must implement the #run method' unless value.respond_to?(:run)

  @local_cmd = value
end

#add_ma_builder(value) ⇒ Object

Raises:

  • (ArgumentError)


57
58
59
60
61
# File 'lib/cem_win_spec/win_exec/exec.rb', line 57

def add_ma_builder(value)
  raise ArgumentError, 'ma_builder must implement the #build method' unless value.respond_to?(:build)

  @ma_builder = value
end

#add_remote_cmd(value) ⇒ Object

Raises:

  • (ArgumentError)


43
44
45
46
47
# File 'lib/cem_win_spec/win_exec/exec.rb', line 43

def add_remote_cmd(value)
  raise ArgumentError, 'winrm_exec must implement the #run method' unless value.respond_to?(:run)

  @remote_cmd = value
end

#add_rspec_test_cmds(value) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/cem_win_spec/win_exec/exec.rb', line 63

def add_rspec_test_cmds(value)
  unless value.respond_to?(:cmd_standalone) && value.respond_to?(:cmd_parallel) &&
         value.respond_to?(:prep_cmd) && value.respond_to?(:cleanup_cmd)
    raise ArgumentError, 'rspec_test_cmds must implement the #cmd_standalone, #cmd_parallel, #prep_cmd, and #cleanup_cmd methods'
  end

  @rspec_test_cmds = value
end

#add_title(value) ⇒ Object

Raises:

  • (ArgumentError)


31
32
33
34
35
# File 'lib/cem_win_spec/win_exec/exec.rb', line 31

def add_title(value)
  raise ArgumentError, 'title must be a string' unless value.is_a?(String)

  @title = value
end

#respond_to_missing?(method, include_private = false) ⇒ Boolean

Proxy respond_to? for methods that don’t exist here to various other objects. This allows doing things like: ‘win_exec.respond_to?(:remote_exec)` and checking if the `exec` method exists on the `winrm_exec` object. This is done by checking method name prefixes and calling the corresponding method on the appropriate object. The prefix is removed from the method name before calling the method on the object. The supported prefixes are:

local_ - calls the method on the @local_cmd object
remote_ - calls the method on the @remote_cmd object
rspec_ - calls the method on the @rspec_test_cmds object
module_archive_ - calls the method on the @ma_builder object

Returns:

  • (Boolean)


136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/cem_win_spec/win_exec/exec.rb', line 136

def respond_to_missing?(method, include_private = false)
  if method.to_s.start_with?('local_')
    @local_cmd.respond_to?(method.to_s.sub('local_', '').to_sym, include_private)
  elsif method.to_s.start_with?('remote_')
    @remote_cmd.respond_to?(method.to_s.sub('remote_', '').to_sym, include_private)
  elsif method.to_s.start_with?('rspec_')
    @rspec_test_cmds.respond_to?(method.to_s.sub('rspec_', '').to_sym, include_private)
  elsif method.to_s.start_with?('module_archive_')
    @ma_builder.respond_to?(method.to_s.sub('module_archive_', '').to_sym, include_private)
  else
    super
  end
end

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



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/cem_win_spec/win_exec/exec.rb', line 150

def run(*args, **kwargs)
  validate_instance_variables
  logger.info "Running #{@title}"
  result = run_with_tunnel { run_in_current_scope(*args, **kwargs) }
  logger.debug "Command stdout: #{result.stdout}" if result.respond_to?(:stdout)
  logger.debug "Command stderr: #{result.stderr}" if result.respond_to?(:stderr)
  @result = Output.new(result)
  return if @result.pending_threaded?

  unless @result.success? || ignore_exitcode?
    raise "Command failed with exit code #{@result.exitcode}: #{@result.stdout}\n#{@result.stderr}"
  end
  @result
rescue StandardError => e
  raise if HALTING_ERRORS.include?(e.class)

  logger.error "Error running #{@title}: #{e.message[0..100]}"
  @result = Output.new(e)
  @result
end

#success?Boolean

Returns:

  • (Boolean)


92
93
94
95
96
# File 'lib/cem_win_spec/win_exec/exec.rb', line 92

def success?
  @result.success? if @result.is_a?(Output)

  false
end