Class: Ridley::HostConnector::WinRM

Inherits:
Base
  • Object
show all
Defined in:
lib/ridley-connectors/host_connector/winrm.rb,
lib/ridley-connectors/host_connector/winrm/command_uploader.rb

Defined Under Namespace

Classes: CommandUploader

Constant Summary collapse

DEFAULT_PORT =
5985
EMBEDDED_RUBY_PATH =
'C:\opscode\chef\embedded\bin\ruby'.freeze
SESSION_TYPE_COMMAND_METHODS =
{
  powershell: :run_powershell_script,
  cmd: :run_cmd
}.freeze

Constants inherited from Base

Base::CONNECTOR_PORT_ERRORS, Base::PORT_CHECK_TIMEOUT, Base::RETRY_COUNT

Instance Method Summary collapse

Methods inherited from Base

#connectable?, #port_open?

Instance Method Details

#bootstrap(host, options = {}) ⇒ HostConnector::Response

Bootstrap a node

Parameters:

  • host (String)

    the host to perform the action on

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

Returns:



122
123
124
125
126
127
128
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 122

def bootstrap(host, options = {})
  context = BootstrapContext::Windows.new(options)

  log.info "Bootstrapping host: #{host}"
  log.filter_param(context.boot_command)
  run(host, context.boot_command, options)
end

#chef_client(host, options = {}) ⇒ HostConnector::Response

Perform a chef client run on a node

Parameters:

  • host (String)

    the host to perform the action on

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

Returns:



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 141

def chef_client(host, options = {})
  log_level = case log.level
    when 0
      "debug"
    when 1
      "info"
    when 2
      "warn"
    else
      "info"
    end
  run(host, "chef-client -l #{log_level}", options)
end

#connector_port_open?(host, options = {}) ⇒ Boolean

Checks to see if the given port is open for TCP connections on the given host.

Parameters:

  • host (String)

    the host to attempt to connect to

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :winrm (Hash)
    • port (Fixnum) the winrm port to connect to

  • :retries (Fixnum)

Returns:

  • (Boolean)


231
232
233
234
235
236
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 231

def connector_port_open?(host, options = {})
  options[:winrm]        ||= Hash.new
  options[:winrm][:port] ||= HostConnector::WinRM::DEFAULT_PORT

  port_open?(host, options[:winrm][:port], options[:retries])
end

#put_secret(host, secret, options = {}) ⇒ HostConnector::Response

Write your encrypted data bag secret on a node

Parameters:

  • host (String)

    the host to perform the action on

  • secret (String)

    your organization’s encrypted data bag secret

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

Returns:



168
169
170
171
172
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 168

def put_secret(host, secret, options = {})
  log.filter_param(secret)
  command = "echo #{secret} > C:\\chef\\encrypted_data_bag_secret"
  run(host, command, options)
end

#ruby_script(host, command_lines, options = {}) ⇒ HostConnector::Response

Execute line(s) of Ruby code on a node using Chef’s embedded Ruby. This will always run via a batch file due to WinRM having difficulty handling long ruby scripts being passed via ruby -e.

Parameters:

  • host (String)

    the host to perform the action on

  • command_lines (Array<String>)

    An Array of lines of the command to be executed

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

Returns:



190
191
192
193
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 190

def ruby_script(host, command_lines, options = {})
  command = "#{EMBEDDED_RUBY_PATH} -e \"#{command_lines.join(';')}\""
  run(host, command, options.merge(force_batch_file: true))
end

#run(host, command, options = {}) ⇒ HostConnector::Response

Execute a shell command on a node

Parameters:

  • host (String)

    the host to perform the action on

  • command (String)
  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :session_type (Symbol) — default: :cmd
    • :powershell - run the given command in a powershell session

    • :cmd - run the given command in a cmd session

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

  • :force_batch_file (TrueClass, FalseClass)

    Always use a batch file to run the command regardless of command length.

Returns:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 36

def run(host, command, options = {})
  options = options.reverse_merge(winrm: Hash.new, session_type: :cmd)
  options[:winrm].reverse_merge!(port: DEFAULT_PORT)

  command_uploaders = Array.new
  user              = options[:winrm][:user]
  password          = options[:winrm][:password]
  port              = options[:winrm][:port]
  connection        = winrm(host, port, options[:winrm].slice(:user, :password))

  unless command_method = SESSION_TYPE_COMMAND_METHODS[options[:session_type]]
    raise RuntimeError, "unknown session type: #{options[:session_type]}. Known session types " +
      "are: #{SESSION_TYPE_COMMAND_METHODS.keys}"
  end

  HostConnector::Response.new(host).tap do |response|
    begin
      if options[:force_batch_file] || command.length > CommandUploader::CHUNK_LIMIT
        log.debug "Detected a command that was longer than #{CommandUploader::CHUNK_LIMIT} characters. " +
          "Uploading command as a file to the host."
        command_uploaders << command_uploader = CommandUploader.new(connection)
        command_uploader.upload(command)
        command = command_uploader.command
      end

      log.info "Running WinRM command: '#{command}' on: '#{host}' as: '#{user}'"

      defer {
        output = connection.send(command_method, command) do |stdout, stderr|
          if stdout && stdout.present?
            response.stdout += stdout
            log.info "[#{host}](WinRM) #{stdout}"
          end

          if stderr && stderr.present?
            response.stderr += stderr
            log.info "[#{host}](WinRM) #{stderr}"
          end
        end
        response.exit_code = output[:exitcode]
      }
    rescue ::WinRM::WinRMHTTPTransportError => ex
      response.exit_code = :transport_error
      response.stderr    = ex.message
    rescue SocketError, Errno::EHOSTUNREACH
      response.exit_code = -1
      response.stderr    = "Host unreachable"
    rescue Errno::ECONNREFUSED
      response.exit_code = -1
      response.stderr    = "Connection refused"
    rescue => ex
      response.exit_code = -1
      response.stderr    = "An unknown error occurred: #{ex.class} - #{ex.message}"
    end

    case response.exit_code
    when 0
      log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}'"
    when :transport_error
      log.info "A transport error occured while attempting to run a WinRM command on: '#{host}' as: '#{user}'"
    when -1
      log.info "Failed to run WinRM command on: '#{host}' as: '#{user}'"
    else
      log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}', but it failed"
    end
  end
ensure
  begin
    command_uploaders.map(&:cleanup)
  rescue ::WinRM::WinRMHTTPTransportError, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED => ex
    log.info "Error cleaning up leftover Powershell scripts on some hosts due to: " +
      "#{ex.class} - #{ex.message}"
  end
end

#uninstall_chef(host, options = {}) ⇒ HostConnector::Response

Uninstall Chef from a node

Parameters:

  • host (String)

    the host to perform the action on

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :skip_chef (Boolena) — default: false

    skip removal of the Chef package and the contents of the installation directory. Setting this to true will only remove any data and configurations generated by running Chef client.

  • :winrm (Hash)
    • :user (String) a user that will login to each node and perform the bootstrap command on

    • :password (String) the password for the user that will perform the bootstrap (required)

    • :port (Fixnum) the winrm port to connect on the node the bootstrap will be performed on (5985)

Returns:



210
211
212
213
214
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 210

def uninstall_chef(host, options = {})
  options[:session_type] = :powershell
  log.info "Uninstalling Chef from host: #{host}"
  run(host, CommandContext::WindowsUninstall.command(options), options)
end

#update_omnibus(host, options = {}) ⇒ Object



216
217
218
219
# File 'lib/ridley-connectors/host_connector/winrm.rb', line 216

def update_omnibus(host, options = {})
  log.info "Updating Omnibus installation on host: #{host}"
  run(host, CommandContext::WindowsUpdateOmnibus.command(options), options)
end