lib/ridley/host_connector/winrm.rb in ridley-0.12.4 vs lib/ridley/host_connector/winrm.rb in ridley-1.0.0.rc1

- old
+ new

@@ -1,56 +1,181 @@ +require 'active_support/core_ext/kernel/reporting' +# Silencing warnings because not all versions of GSSAPI support all of the GSSAPI methods +# the gssapi gem attempts to attach to and these warnings are dumped to STDERR. +silence_warnings do + require 'winrm' +end + module Ridley module HostConnector # @author Kyle Allan <kallan@riotgames.com> - class WinRM + class WinRM < HostConnector::Base require_relative 'winrm/command_uploader' - require_relative 'winrm/worker' - class << self - # @param [Ridley::NodeResource, Array<Ridley::NodeResource>] nodes - # @param [Hash] options - def start(nodes, options = {}, &block) - runner = new(nodes, options) - result = yield runner - runner.terminate + DEFAULT_PORT = 5985 + EMBEDDED_RUBY_PATH = 'C:\opscode\chef\embedded\bin\ruby'.freeze - result - ensure - runner.terminate if runner && runner.alive? + # Execute a shell command on a node + # + # @param [String] host + # the host to perform the action on + # @param [String] command + # + # @option options [Hash] :winrm + # * :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) + # + # @return [HostConnector::Response] + def run(host, command, options = {}) + options = options.reverse_merge(winrm: Hash.new) + 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)) + + HostConnector::Response.new(host).tap do |response| + command_uploaders << command_uploader = CommandUploader.new(connection) + command = get_command(command, command_uploader) + + begin + log.info "Running WinRM Command: '#{command}' on: '#{host}' as: '#{user}'" + + output = connection.run_cmd(command) do |stdout, stderr| + if stdout + response.stdout += stdout + log.info "[#{host}](WinRM) #{stdout}" + end + + if stderr + response.stderr += stderr unless stderr.nil? + log.info "[#{host}](WinRM) #{stdout}" + end + end + + response.exit_code = output[:exitcode] + rescue ::WinRM::WinRMHTTPTransportError => ex + response.exit_code = -1 + response.stderr = ex.message + return response + end + + case response.exit_code + when 0 + log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}'" + else + log.info "Successfully ran WinRM command on: '#{host}' as: '#{user}', but it failed" + end end + ensure + command_uploaders.map(&:cleanup) end - include Celluloid - include Celluloid::Logger + # Returns the command if it does not break the WinRM command length + # limit. Otherwise, we return an execution of the command as a batch file. + # + # @param command [String] + # + # @return [String] + def get_command(command, command_uploader) + if command.length < CommandUploader::CHUNK_LIMIT + command + else + log.debug "Detected a command that was longer than #{CommandUploader::CHUNK_LIMIT} characters. " + + "Uploading command as a file to the host." + command_uploader.upload(command) + command_uploader.command + end + end - attr_reader :nodes - attr_reader :options + # Bootstrap a node + # + # @param [String] host + # the host to perform the action on + # + # @option options [Hash] :winrm + # * :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) + # + # @return [HostConnector::Response] + def bootstrap(host, options = {}) + context = BootstrapContext::Windows.new(options) - # @param [Ridley::NodeResource, Array<Ridley::NodeResource>] nodes - # @param [Hash] options - def initialize(nodes, options = {}) - @nodes = Array(nodes) - @options = options + log.info "Bootstrapping host: #{host}" + run(host, context.boot_command, options) end - # @param [String] command + # Perform a chef client run on a node # - # @return [Array] - def run(command) - workers = Array.new - futures = self.nodes.collect do |node| - workers << worker = Worker.new(node.public_hostname, self.options.freeze) - worker.future.run(command) - end + # @param [String] host + # the host to perform the action on + # + # @option options [Hash] :winrm + # * :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) + # + # @return [HostConnector::Response] + def chef_client(host, options = {}) + run(host, "chef-client", options) + end - Ridley::HostConnector::ResponseSet.new.tap do |response_set| - futures.each do |future| - status, response = future.value - response_set.add_response(response) - end - end - ensure - workers.map(&:terminate) + # Write your encrypted data bag secret on a node + # + # @param [String] host + # the host to perform the action on + # @param [String] secret + # your organization's encrypted data bag secret + # + # @option options [Hash] :winrm + # * :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) + # + # @return [HostConnector::Response] + def put_secret(host, secret, options = {}) + command = "echo #{secret} > C:\\chef\\encrypted_data_bag_secret" + run(host, command, options) end + + # Execute line(s) of Ruby code on a node using Chef's embedded Ruby + # + # @param [String] host + # the host to perform the action on + # @param [Array<String>] command_lines + # An Array of lines of the command to be executed + # + # @option options [Hash] :winrm + # * :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) + # + # @return [HostConnector::Response] + def ruby_script(host, command_lines, options = {}) + command = "#{EMBEDDED_RUBY_PATH} -e \"#{command_lines.join(';')}\"" + run(host, command, options) + end + + private + + # @param [String] host + # @param [Integer] port + # + # @option options [String] :user + # @option options [String] :password + # + # @return [WinRM::WinRMWebService] + def winrm(host, port, options = {}) + winrm_opts = { disable_sspi: true, basic_auth_only: true } + winrm_opts[:user] = options[:user] + winrm_opts[:pass] = options[:password] + client = ::WinRM::WinRMWebService.new("http://#{host}:#{port}/wsman", :plaintext, winrm_opts) + client.set_timeout(6000) + client + end end end end