Class: Chef::Knife::Winrm

Inherits:
Chef::Knife show all
Includes:
WinrmBase
Defined in:
lib/chef/knife/winrm.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from WinrmBase

included

Instance Attribute Details

#password=(value) ⇒ Object (writeonly)

Sets the attribute password

Parameters:

  • value

    the value to set the attribute password to.



34
35
36
# File 'lib/chef/knife/winrm.rb', line 34

def password=(value)
  @password = value
end

Instance Method Details

#check_for_errors!(exit_codes) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/chef/knife/winrm.rb', line 257

def check_for_errors!(exit_codes)

  exit_codes.each do |host, value|
		  Chef::Log.debug("Exit code found: #{value}")
    unless success_return_codes.include? value.to_i
      @exit_code = value.to_i
      ui.error "Failed to execute command on #{host} return code #{value}"
    end
  end

end

#configure_sessionObject



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/chef/knife/winrm.rb', line 103

def configure_session

  list = case config[:manual]
         when true
           @name_args[0].split(" ")
         when false
           r = Array.new
           q = Chef::Search::Query.new
           @action_nodes = q.search(:node, @name_args[0])[0]
           @action_nodes.each do |item|
             i = extract_nested_value(item, config[:attribute])
             r.push(i) unless i.nil?
           end
           r
         end
  if list.length == 0
    if @action_nodes.length == 0
      ui.fatal("No nodes returned from search!")
    else
      ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
               "but does not have the required attribute (#{config[:attribute]}) to establish the connection. " +
               "Try setting another attribute to open the connection using --attribute.")
    end
    exit 10
  end
  session_from_list(list)
end

#extract_nested_value(data, nested_value_spec) ⇒ Object

TODO: Copied from Knife::Core:GenericPresenter. Should be extracted



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/chef/knife/winrm.rb', line 84

def extract_nested_value(data, nested_value_spec)
  nested_value_spec.split(".").each do |attr|
    if data.nil?
      nil # don't get no method error on nil
    elsif data.respond_to?(attr.to_sym)
      data = data.send(attr.to_sym)
    elsif data.respond_to?(:[])
      data = data[attr]
    else
      data = begin
               data.send(attr.to_sym)
             rescue NoMethodError
               nil
             end
    end
  end
  ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
end

#get_passwordObject



198
199
200
# File 'lib/chef/knife/winrm.rb', line 198

def get_password
  @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
end

#interactiveObject



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/chef/knife/winrm.rb', line 227

def interactive
  puts "Connected to #{ui.list(session.servers.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}"
  puts
  puts "To run a command on a list of servers, do:"
  puts "  on SERVER1 SERVER2 SERVER3; COMMAND"
  puts "  Example: on latte foamy; echo foobar"
  puts
  puts "To exit interactive mode, use 'quit!'"
  puts
  while 1
    command = read_line
    case command
    when 'quit!'
      puts 'Bye!'
      session.close
      break
    when /^on (.+?); (.+)$/
      raw_list = $1.split(" ")
      server_list = Array.new
      session.servers.each do |session_server|
        server_list << session_server if raw_list.include?(session_server.host)
      end
      command = $2
      winrm_command(command, session.on(*server_list))
    else
      winrm_command(command)
    end
  end
end


182
183
184
185
186
187
188
189
190
191
# File 'lib/chef/knife/winrm.rb', line 182

def print_data(host, data, color = :cyan)
  if data =~ /\n/
    data.split(/\n/).each { |d| print_data(host, d, color) }
  else
    padding = @longest - host.length
    print ui.color(host, color)
    padding.downto(0) { print " " }
    puts data.chomp
  end
end

#read_lineObject

Present the prompt and read a single line from the console. It also detects ^D and returns “exit” in that case. Adds the input to the history, unless the input is empty. Loops repeatedly until a non-empty line is input.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/chef/knife/winrm.rb', line 206

def read_line
  loop do
    command = reader.readline("#{ui.color('knife-winrm>', :bold)} ", true)

    if command.nil?
      command = "exit"
      puts(command)
    else
      command.strip!
    end

    unless command.empty?
      return command
    end
  end
end

#readerObject



223
224
225
# File 'lib/chef/knife/winrm.rb', line 223

def reader
  Readline
end

#runObject



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
# File 'lib/chef/knife/winrm.rb', line 269

def run

  STDOUT.sync = STDERR.sync = true

  begin
    @longest = 0

    configure_session

    case @name_args[1]
    when "interactive"
      interactive
    else
      winrm_command(@name_args[1..-1].join(" "))

      if config[:returns]
        check_for_errors! session.exit_codes
      end

      session.close

      # Knife seems to ignore the return value of this method,
      # so we exit to force the process exit code for this
      # subcommand if returns is set
      exit @exit_code if @exit_code && @exit_code != 0
      @exit_code || 0
    end
  rescue WinRM::WinRMHTTPTransportError => e
    case e.message
    when /401/
      if ! config[:suppress_auth_failure]
        # Display errors if the caller hasn't opted to retry
        ui.error "Failed to authenticate to #{@name_args[0].split(" ")} as #{config[:winrm_user]}"
        ui.info "Response: #{e.message}"
        raise e
      end
      @exit_code = 401
    else
      raise e
    end
  end
end

#sessionObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/chef/knife/winrm.rb', line 57

def session
  session_opts = {}
  session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
  @session ||= begin
    s = EventMachine::WinRM::Session.new(session_opts)
    s.on_output do |host, data|
      print_data(host, data)
    end
    s.on_error do |host, err|
      print_data(host, err, :red)
    end
    s.on_command_complete do |host|
      host = host == :all ? 'All Servers' : host
      Chef::Log.debug("command complete on #{host}")
    end
    s
  end

end

#session_from_list(list) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/chef/knife/winrm.rb', line 131

def session_from_list(list)
  list.each do |item|
    Chef::Log.debug("Adding #{item}")
    session_opts = {}
    session_opts[:user] = config[:winrm_user] = Chef::Config[:knife][:winrm_user] || config[:winrm_user]
    session_opts[:password] = config[:winrm_password] = Chef::Config[:knife][:winrm_password] || config[:winrm_password]
    session_opts[:port] = Chef::Config[:knife][:winrm_port] || config[:winrm_port]
    session_opts[:keytab] = Chef::Config[:knife][:kerberos_keytab_file] if Chef::Config[:knife][:kerberos_keytab_file]
    session_opts[:realm] = Chef::Config[:knife][:kerberos_realm] if Chef::Config[:knife][:kerberos_realm]
    session_opts[:service] = Chef::Config[:knife][:kerberos_service] if Chef::Config[:knife][:kerberos_service]
    session_opts[:ca_trust_path] = Chef::Config[:knife][:ca_trust_file] if Chef::Config[:knife][:ca_trust_file]
    session_opts[:operation_timeout] = 1800 # 30 min OperationTimeout for long bootstraps fix for KNIFE_WINDOWS-8

    ## If you have a \\ in your name you need to use NTLM domain authentication
    username_contains_domain = session_opts[:user].split("\\").length.eql?(2)

    if username_contains_domain
      # We cannot use basic_auth for domain authentication
      session_opts[:basic_auth_only] = false
    else
      session_opts[:basic_auth_only] = true
    end

    if config.keys.any? {|k| k.to_s =~ /kerberos/ }
      session_opts[:transport] = :kerberos
      session_opts[:basic_auth_only] = false
    else
      session_opts[:transport] = (Chef::Config[:knife][:winrm_transport] || config[:winrm_transport]).to_sym

      if Chef::Platform.windows? && session_opts[:transport] == :plaintext && username_contains_domain
        ui.warn("Switching to Negotiate authentication, Basic does not support Domain Authentication")
        # windows - force only encrypted communication
        require 'winrm-s'
        session_opts[:transport] = :sspinegotiate
        session_opts[:disable_sspi] = false
      else
        session_opts[:disable_sspi] = true
      end
      if session_opts[:user] and
          (not session_opts[:password])
        session_opts[:password] = Chef::Config[:knife][:winrm_password] = config[:winrm_password] = get_password
      end
    end

    session.use(item, session_opts)

    @longest = item.length if item.length > @longest
  end
  session
end

#success_return_codesObject



77
78
79
80
81
# File 'lib/chef/knife/winrm.rb', line 77

def success_return_codes
  #Redundant if the CLI options parsing occurs
  return [0] unless config[:returns]
  return config[:returns].split(',').collect {|item| item.to_i}
end

#winrm_command(command, subsession = nil) ⇒ Object



193
194
195
196
# File 'lib/chef/knife/winrm.rb', line 193

def winrm_command(command, subsession=nil)
  subsession ||= session
  subsession.relay_command(command)
end