diff --git a/README b/README new file mode 100644 index 0000000..90991d7 --- /dev/null +++ b/README @@ -0,0 +1,26 @@ +'winrm' can be used just like 'run'. For instance: + +task :ipconfig, :roles => :winrm do + winrm 'ipconfig' +end + +This will run ipconfig across all hosts in the :winrm role. + +'winrm' will also accept a block exactly like 'run': + +task :ipconfig, :roles => :winrm do + host_data = {} + winrm 'ipconfig' do |channel, stream, data| + host_data[channel[:host]] = "" unless host_data[channel[:host]].is_a?(String) + host_data[channel[:host]] << data + end + + host_data.each_pair do |host,data| + puts "HOST: #{host}" + puts "---------------------------------------" + puts data + puts "---------------------------------------" + end +end + + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b14d2f7 --- /dev/null +++ b/Rakefile @@ -0,0 +1,66 @@ +require 'rubygems' +require 'rake/clean' +require 'rake/gempackagetask' +require 'date' + +CLEAN.include("pkg") +CLEAN.include("doc") + +GEMSPEC = Gem::Specification.new do |gem| + gem.name = "capistrano_winrm" + gem.version = File.open('VERSION').readline.chomp + gem.date = Date.today.to_s + gem.platform = Gem::Platform::RUBY + gem.rubyforge_project = nil + + gem.author = "Dan Wanek" + gem.email = "dan.wanek@gmail.com" + gem.homepage = "http://github.com/zenchild/capistrano_winrm" + + gem.summary = 'WinRM extensions for Capistrano' + gem.description = <<-EOF + WinRM extensions for Capistrano + EOF + + gem.files = %w{ lib/capistrano_winrm.rb lib/capistrano_winrm/command.rb lib/capistrano_winrm/configuration/connections.rb lib/winrm_connection.rb } + gem.require_path = "lib" + gem.rdoc_options = %w(-x test/ -x examples/) + gem.extra_rdoc_files = %w(README) + + gem.required_ruby_version = '>= 1.8.7' + gem.add_runtime_dependency 'capistrano' + gem.add_runtime_dependency 'winrm', '>=0.0.4' +end + +Rake::GemPackageTask.new(GEMSPEC) do |pkg| + pkg.need_tar = true +end + +task :default => [:buildgem] + +desc "Build the gem without a version change" +task :buildgem => [:clean, :repackage] + +desc "Build the gem, but increment the version first" +task :newrelease => [:versionup, :clean, :repackage] + + +desc "Increment the version by 1 minor release" +task :versionup do + ver = up_min_version + puts "New version: #{ver}" +end + + +def up_min_version + f = File.open('VERSION', 'r+') + ver = f.readline.chomp + v_arr = ver.split(/\./).map do |v| + v.to_i + end + v_arr[2] += 1 + ver = v_arr.join('.') + f.rewind + f.write(ver) + ver +end diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/lib/capistrano_winrm.rb b/lib/capistrano_winrm.rb new file mode 100644 index 0000000..57b05b4 --- /dev/null +++ b/lib/capistrano_winrm.rb @@ -0,0 +1,20 @@ +module CapistranoWinRM + def self.included(base) + base.send(:alias_method, :winrm, :winrm_run) + end + + def winrm_run(cmd, options={}, &block) + set :winrm_running, true + options[:shell] = false + block ||= self.class.default_io_proc + tree = Capistrano::Command::Tree.new(self) { |t| t.else(cmd, &block) } + run_tree(tree, options) + set :winrm_running, false + end +end # CapistranoWinRM + +Capistrano::Configuration.send(:include, CapistranoWinRM) + +require 'winrm_connection' +require 'capistrano_winrm/command' +require 'capistrano_winrm/configuration/connections' diff --git a/lib/capistrano_winrm/command.rb b/lib/capistrano_winrm/command.rb new file mode 100644 index 0000000..caf20b5 --- /dev/null +++ b/lib/capistrano_winrm/command.rb @@ -0,0 +1,33 @@ +module Capistrano + class Command + # Processes the command in parallel on all specified hosts. If the command + # fails (non-zero return code) on any of the hosts, this will raise a + # Capistrano::CommandError. + def process! + if(@tree.configuration.variables[:winrm_running]) + @channels.each do |ch| + ch.process_data do |c, stream, data| + c[:branch].callback[c, stream, data] + end + end + else + loop do + break unless process_iteration { @channels.any? { |ch| !ch[:closed] } } + end + end + + logger.trace "command finished" if logger + + if (failed = @channels.select { |ch| ch[:status] != 0 }).any? + commands = failed.inject({}) { |map, ch| (map[ch[:command]] ||= []) << ch[:server]; map } + message = commands.map { |command, list| "#{command.inspect} on #{list.join(',')}" }.join("; ") + error = CommandError.new("failed: #{message}") + error.hosts = commands.values.flatten + raise error + end + + self + end + + end # end Command +end # end Capistrano diff --git a/lib/capistrano_winrm/configuration/connections.rb b/lib/capistrano_winrm/configuration/connections.rb new file mode 100644 index 0000000..76e8294 --- /dev/null +++ b/lib/capistrano_winrm/configuration/connections.rb @@ -0,0 +1,35 @@ +module Capistrano + class Configuration + module Connections + class WinRMConnectionFactory #:nodoc: + def initialize(options) + @options = options + @winrm = WINRM.new(options[:winrm_user], options[:winrm_password], nil, options[:winrm_ssl_ca_store]) + end + + def connect_to(server) + @winrm.setup_connection(server, @options) + @winrm + end + end + + # Returns the object responsible for establishing new SSH connections. + # The factory will respond to #connect_to, which can be used to + # establish connections to servers defined via ServerDefinition objects. + def connection_factory + @connection_factory ||= begin + if exists?(:gateway) + logger.debug "establishing connection to gateway `#{fetch(:gateway)}'" + GatewayConnectionFactory.new(fetch(:gateway), self) + elsif(exists?(:winrm_running)) + logger.debug "establishing connection to WinRM" + WinRMConnectionFactory.new(self) + else + DefaultConnectionFactory.new(self) + end + end + end + + end # Connections + end # Configuration +end # Capistrano diff --git a/lib/winrm_connection.rb b/lib/winrm_connection.rb new file mode 100644 index 0000000..99b0b79 --- /dev/null +++ b/lib/winrm_connection.rb @@ -0,0 +1,57 @@ +require 'winrm' + +class WINRM + attr_reader :server + alias :xserver :server + + def initialize(user, pass, endpoint = nil, ssl_ca_store = nil) + @user = user + @pass = pass + @endpoint = endpoint + @ssl_ca_store = ssl_ca_store + @int_hash = {} + end + + def [](key) + @int_hash[key.to_sym] + end + + def []=(key, value) + @int_hash[key.to_sym] = value + end + + def setup_connection(server, options) + @server = server + @int_hash[:options] = options + @int_hash[:server] = server + end + + def open_channel + yield self + end + + def exec(cmd) + http_method = ( server.port.to_s=~/(443|5986)/ ? 'https' : 'http' ) + endpoint = @endpoint ? @endpoint : "#{http_method}://#{server}/wsman" + WinRM::WinRM.endpoint = endpoint + WinRM::WinRM.set_auth(@user, @pass) + WinRM::WinRM.set_ca_trust_path(@ssl_ca_store) unless @ssl_ca_store.nil? + inst = WinRM::WinRM.instance + @ios = inst.cmd(cmd) + end + + def process_data + @ios[:data].each do |ds| + key = ds.keys.first + stream = (key == :stdout) ? :out : :err + yield self, stream, ds[key] + end + self[:status] = @ios[:exitcode] + end + + def on_data; self; end + def on_extended_data; self; end + def on_request(req_type); self; end + def on_close; self; end + +end