channelizer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.travis.yml +9 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +1 -0
- data/Rakefile +10 -0
- data/channelizer.gemspec +22 -0
- data/lib/channelizer.rb +7 -0
- data/lib/channelizer/channels/base.rb +130 -0
- data/lib/channelizer/channels/ssh.rb +107 -0
- data/lib/channelizer/channels/winrm.rb +121 -0
- data/lib/channelizer/exceptions/exceptions.rb +13 -0
- data/lib/channelizer/factory.rb +23 -0
- data/lib/channelizer/util/retryable.rb +25 -0
- data/lib/channelizer/version.rb +3 -0
- data/spec/channel_helper.rb +19 -0
- data/spec/test_data/test_file.txt +1 -0
- data/spec/winrm_form_spec.rb +16 -0
- data/spec/winrm_functional_spec.rb +111 -0
- data/spec/winrm_options_spec.rb +38 -0
- metadata +156 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Paul Morton
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Test
|
data/Rakefile
ADDED
data/channelizer.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/channelizer/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Paul Morton"]
|
6
|
+
gem.email = ["pmorton@biaprotect.com"]
|
7
|
+
gem.description = %q{A gem that abstracts shell and upload channels}
|
8
|
+
gem.summary = File.read('README.md')
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "channelizer"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Channelizer::VERSION
|
17
|
+
gem.add_runtime_dependency "winrm", "~> 1.1.2"
|
18
|
+
gem.add_runtime_dependency "net-ssh", "~> 2.5.2"
|
19
|
+
gem.add_runtime_dependency "net-scp", "~> 1.0.4"
|
20
|
+
gem.add_runtime_dependency 'rspec'
|
21
|
+
gem.add_runtime_dependency 'rake'
|
22
|
+
end
|
data/lib/channelizer.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
module Channelizer
|
2
|
+
module Channels
|
3
|
+
class Base
|
4
|
+
include Channelizer::Exceptions
|
5
|
+
include Channelizer::Util::Retryable
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
self.class.validate_options(options)
|
9
|
+
end
|
10
|
+
# Executes a command on the channel
|
11
|
+
#
|
12
|
+
# @param [String] command the command to execute on the channel
|
13
|
+
# @param [Hash] options the options string to be used in executing the command
|
14
|
+
# @option options [Array] :exit_codes ([0]) the valid exit codes for the command
|
15
|
+
# @option options [TrueClass,FalseClass] :check_exit_code (true) make that the command returned a valid exit code
|
16
|
+
# @return [Fixnum] the exit code of the command
|
17
|
+
# @raise [BadExitCode] The command exited with a bad exitcode
|
18
|
+
def shell_execute(command, options = {})
|
19
|
+
defaults = {:exit_codes => [0], :check_exit_code => true, :sudo => false}
|
20
|
+
options = defaults.merge(options)
|
21
|
+
|
22
|
+
new_command = options[:sudo] ? sudo_command(command) : command
|
23
|
+
|
24
|
+
exit_code = execute(new_command, options)
|
25
|
+
|
26
|
+
if not options[:exit_codes].include? exit_code and options[:check_exit_code]
|
27
|
+
raise BadExitCode, "Exit Code: #{exit_code}"
|
28
|
+
end
|
29
|
+
|
30
|
+
exit_code.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a command wrapped in a sudo context
|
34
|
+
#
|
35
|
+
# @param [String] command the command to wrap in a sudo context
|
36
|
+
# @return [String] the command that has been wrapped in a sudo context
|
37
|
+
# @raise [NotImplementedError] this method has not been implemented
|
38
|
+
def sudo_command(command)
|
39
|
+
raise NotImplementedError, "sudo command not implemented"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Executes a command on the channel
|
43
|
+
#
|
44
|
+
# @param command the command to execute on the channel
|
45
|
+
# @return [Fixnum]
|
46
|
+
# @raise [NotImplementedError] this method has not been implemented
|
47
|
+
def execute(command, options = {})
|
48
|
+
raise NotImplementedError, "Execute not implemented"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Uploads a file over the channel
|
52
|
+
#
|
53
|
+
# @param source the file to upload
|
54
|
+
# @param destination where to place the file on the remote host
|
55
|
+
# @return [Fixnum]
|
56
|
+
# @raise [NotImplementedError] this method has not been implemented
|
57
|
+
def upload(source, destination, options = {} )
|
58
|
+
raise NotImplementedError, "Upload not implemented"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Checks if the channel is ready for action
|
62
|
+
#
|
63
|
+
# @return [TrueClass,FalseClass]
|
64
|
+
def ready?
|
65
|
+
begin
|
66
|
+
Timeout.timeout(60) do
|
67
|
+
execute "hostname"
|
68
|
+
end
|
69
|
+
return true
|
70
|
+
rescue Timeout::Error => e
|
71
|
+
return false
|
72
|
+
rescue Errno::ECONNREFUSED => e
|
73
|
+
return false
|
74
|
+
rescue HTTPClient::KeepAliveDisconnected => e
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class <<self
|
80
|
+
# Validates the option set for the channel
|
81
|
+
#
|
82
|
+
# @param [Hash] options the options hash to validate
|
83
|
+
# @return [True]
|
84
|
+
def validate_options(options)
|
85
|
+
@required_options.each do |o|
|
86
|
+
if o.is_a? Array
|
87
|
+
# At least one of the items in an array must be set, if the value is
|
88
|
+
# an array it is treated as a group of arguments that are required.
|
89
|
+
condition_met = false
|
90
|
+
o.each do |o_set|
|
91
|
+
if o_set.is_a? Array
|
92
|
+
included_options = []
|
93
|
+
o_set.each do |o_req_set|
|
94
|
+
included_options << o_req_set if options[o_req_set]
|
95
|
+
end
|
96
|
+
condition_met = true if included_options.length.eql?(o_set.length)
|
97
|
+
else
|
98
|
+
condition_met = true if options[o_set]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
raise ArgumentError, "You must specify one of #{o.inspect}" unless condition_met
|
102
|
+
else
|
103
|
+
raise ArgumentError, "#{o} is a required option, but was not provided" unless options[o]
|
104
|
+
|
105
|
+
if options[o].respond_to? :empty?
|
106
|
+
raise ArgumentError, "#{o} cannot be empty" if options[o].empty?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
113
|
+
# Register a required option
|
114
|
+
#
|
115
|
+
# @param [Symbol] value the option name that is required
|
116
|
+
def required_option(value)
|
117
|
+
@required_options ||= []
|
118
|
+
if value.respond_to? :to_sym
|
119
|
+
@required_options << value.to_sym
|
120
|
+
elsif value.is_a? Array
|
121
|
+
@required_options << value
|
122
|
+
else
|
123
|
+
raise ArgumentError, "#{value.inspect} cannot be added to the validation list"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/scp'
|
3
|
+
module Channelizer
|
4
|
+
module Channels
|
5
|
+
class Ssh < Channelizer::Channels::Base
|
6
|
+
|
7
|
+
required_option :host
|
8
|
+
required_option :port
|
9
|
+
required_option [[:username,:password], :keys]
|
10
|
+
|
11
|
+
# Creates a SSH Session
|
12
|
+
#
|
13
|
+
# @option options [String] :username the username
|
14
|
+
# @option options [String] :password the password
|
15
|
+
# @option options [String] :host the remote host
|
16
|
+
# @option options [String] :port (5985) the port to connect on
|
17
|
+
# @option options [String] :keys the keys to try logging in with
|
18
|
+
def initialize(options)
|
19
|
+
defaults = { :port => 22, :paranoid => false }
|
20
|
+
options = defaults.merge(options)
|
21
|
+
|
22
|
+
# Options need to be in a final state before we call the parent initializer
|
23
|
+
super(options)
|
24
|
+
|
25
|
+
@connection_options = options
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def session
|
30
|
+
options = @connection_options.clone
|
31
|
+
host = options.delete(:host)
|
32
|
+
username = options.delete(:username)
|
33
|
+
Net::SSH.start(host,username,options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Executes a command on the channel
|
37
|
+
#
|
38
|
+
# @param command the command to execute on the channel
|
39
|
+
# @return [Fixnum] the exit code of the command
|
40
|
+
# @raise [StandardException]
|
41
|
+
def execute(command, options = { })
|
42
|
+
defaults = { :out_console => true }
|
43
|
+
options = defaults.merge(options)
|
44
|
+
# open a new channel and configure a minimal set of callbacks, then run
|
45
|
+
# the event loop until the channel finishes (closes)
|
46
|
+
last_exit = -1
|
47
|
+
channel = session.open_channel do |ch|
|
48
|
+
|
49
|
+
#request pty for sudo stuff and so
|
50
|
+
ch.request_pty do |ch, success|
|
51
|
+
raise "Error requesting pty" unless success
|
52
|
+
end
|
53
|
+
|
54
|
+
ch.exec "#{command}" do |ch, success|
|
55
|
+
raise "could not execute command" unless success
|
56
|
+
|
57
|
+
|
58
|
+
# "on_data" is called when the process writes something to stdout
|
59
|
+
ch.on_data do |c, data|
|
60
|
+
STDOUT.print data if options[:out_console]
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# "on_extended_data" is called when the process writes something to stderr
|
65
|
+
ch.on_extended_data do |c, type, data|
|
66
|
+
STDOUT.print data if options[:out_console]
|
67
|
+
end
|
68
|
+
|
69
|
+
channel.on_request("exit-signal") do |ch, data|
|
70
|
+
last_exit = data.read_long
|
71
|
+
end
|
72
|
+
|
73
|
+
channel.on_request("exit-status") do |ch,data|
|
74
|
+
last_exit = data.read_long
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
channel.wait
|
80
|
+
last_exit
|
81
|
+
end
|
82
|
+
|
83
|
+
# Echos the command (Windows does not support sudo)
|
84
|
+
#
|
85
|
+
# @param [String] command the command to wrap in a sudo context
|
86
|
+
# @return [String] the command that has been wrapped in a sudo context
|
87
|
+
def sudo_command(command)
|
88
|
+
"sudo #{command}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Uploads a file over WinRM
|
92
|
+
#
|
93
|
+
# @param source the file to upload
|
94
|
+
# @param destination where to place the file on the remote host
|
95
|
+
# @return [Fixnum]
|
96
|
+
def upload(source, destination, options = {} )
|
97
|
+
options = @connection_options.clone
|
98
|
+
host = options.delete(:host)
|
99
|
+
username = options.delete(:username)
|
100
|
+
channel = session.scp.upload(source,destination)
|
101
|
+
channel.wait
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
Channelizer::Factory.register(:ssh, Channelizer::Channels::Ssh)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'winrm'
|
2
|
+
module Channelizer
|
3
|
+
module Channels
|
4
|
+
class WinRM < Channelizer::Channels::Base
|
5
|
+
|
6
|
+
# @return [WinRM::WinRMWebService] the winrm management session
|
7
|
+
attr_reader :session
|
8
|
+
|
9
|
+
required_option :host
|
10
|
+
required_option :port
|
11
|
+
required_option [[:username,:password], :realm]
|
12
|
+
|
13
|
+
# Creates a WinRM Channel
|
14
|
+
#
|
15
|
+
# @option options [String] :username the username
|
16
|
+
# @option options [String] :password the password
|
17
|
+
# @option options [String] :host the remote host
|
18
|
+
# @option options [String] :port (5985) the port to connect on
|
19
|
+
# @option options [String] :realm the realm to use when using kerberos authentication
|
20
|
+
def initialize(options)
|
21
|
+
defaults = { :port => 5985, :basic_auth_only => true }
|
22
|
+
options = defaults.merge(options)
|
23
|
+
options[:pass] = options[:password] if options[:password]
|
24
|
+
options[:user] = options[:username] if options[:username]
|
25
|
+
|
26
|
+
# Options need to be in a final state before we call the parent initializer
|
27
|
+
super(options)
|
28
|
+
|
29
|
+
endpoint = "http://#{options[:host]}:#{options[:port]}/wsman"
|
30
|
+
|
31
|
+
if options[:realm]
|
32
|
+
@session = ::WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => options[:realm])
|
33
|
+
else
|
34
|
+
@session = ::WinRM::WinRMWebService.new(endpoint, :plaintext, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# Executes a command on the channel
|
40
|
+
#
|
41
|
+
# @param command the command to execute on the channel
|
42
|
+
# @return [Fixnum] the exit code of the command
|
43
|
+
# @raise [StandardException]
|
44
|
+
def execute(command, options = { })
|
45
|
+
defaults = { :shell => :cmd, :out_console => true }
|
46
|
+
options = defaults.merge(options)
|
47
|
+
|
48
|
+
run_proc = Proc.new do |stdout, stderr|
|
49
|
+
STDOUT.print stdout
|
50
|
+
STDERR.print stderr
|
51
|
+
end
|
52
|
+
|
53
|
+
shell = options.delete(:shell)
|
54
|
+
case shell
|
55
|
+
when :cmd
|
56
|
+
if options[:out_console]
|
57
|
+
status = session.cmd(command,&run_proc)
|
58
|
+
else
|
59
|
+
status = session.cmd(command)
|
60
|
+
end
|
61
|
+
when :powershell
|
62
|
+
if options[:out_console]
|
63
|
+
status = session.powershell(command, &run_proc)
|
64
|
+
else
|
65
|
+
status = session.powershell(command)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise StandardError, "Invalid shell #{options[:shell]}"
|
69
|
+
end
|
70
|
+
status[:exitcode]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Echos the command (Windows does not support sudo)
|
74
|
+
#
|
75
|
+
# @param [String] command the command to wrap in a sudo context
|
76
|
+
# @return [String] the command that has been wrapped in a sudo context
|
77
|
+
def sudo_command(command)
|
78
|
+
command
|
79
|
+
end
|
80
|
+
|
81
|
+
# Uploads a file over WinRM
|
82
|
+
#
|
83
|
+
# @param source the file to upload
|
84
|
+
# @param destination where to place the file on the remote host
|
85
|
+
# @return [Fixnum]
|
86
|
+
def upload(source, destination, options = {} )
|
87
|
+
file = "winrm-upload-#{rand()}"
|
88
|
+
puts "File: " + file
|
89
|
+
file_name = (session.cmd("echo %TEMP%\\#{file}"))[:data][0][:stdout].chomp
|
90
|
+
puts "File Name: " + file_name
|
91
|
+
puts session.powershell <<-EOH
|
92
|
+
if(Test-Path #{destination})
|
93
|
+
{
|
94
|
+
rm #{destination}
|
95
|
+
}
|
96
|
+
EOH
|
97
|
+
|
98
|
+
Base64.encode64(IO.binread(source)).gsub("\n",'').chars.to_a.each_slice(8000-file_name.size) do |chunk|
|
99
|
+
out = session.cmd( "echo #{chunk.join} >> \"#{file_name}\"" )
|
100
|
+
puts out
|
101
|
+
end
|
102
|
+
|
103
|
+
puts session.powershell <<-EOH
|
104
|
+
$dir = [System.IO.Path]::GetDirectoryName(\"#{destination}\")
|
105
|
+
Write-host $dir
|
106
|
+
New-Item $dir -type directory -ea SilentlyContinue
|
107
|
+
EOH
|
108
|
+
|
109
|
+
puts session.powershell <<-EOH
|
110
|
+
$base64_string = Get-Content \"#{file_name}\"
|
111
|
+
$bytes = [System.Convert]::FromBase64String($base64_string)
|
112
|
+
$new_file = [System.IO.Path]::GetFullPath(\"#{destination}\")
|
113
|
+
[System.IO.File]::WriteAllBytes($new_file,$bytes)
|
114
|
+
EOH
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Channelizer::Factory.register(:winrm, Channelizer::Channels::WinRM)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Channelizer
|
2
|
+
module Exceptions
|
3
|
+
# Generic Channel Error
|
4
|
+
class ChannelError < StandardError ; end
|
5
|
+
|
6
|
+
# Invalid Exit Code
|
7
|
+
class BadExitCode < StandardError ; end
|
8
|
+
|
9
|
+
# Invalid Channel Type Code
|
10
|
+
class InvalidChannelTypeError < StandardError ; end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Channelizer
|
2
|
+
class Factory
|
3
|
+
include Channelizer::Exceptions
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def register(name,klass)
|
8
|
+
@registered_channels ||= {}
|
9
|
+
@registered_channels[name.to_sym] = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_channel(type,options)
|
13
|
+
raise InvalidChannelTypeError, ":#{type} is not a registered channel" unless @registered_channels[type.to_sym]
|
14
|
+
@registered_channels[type.to_sym].new(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def channels
|
18
|
+
@registered_channels ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Channelizer
|
2
|
+
module Util
|
3
|
+
module Retryable
|
4
|
+
# Retries a given block a specified number of times in the
|
5
|
+
# event the specified exception is raised. If the retries
|
6
|
+
# run out, the final exception is raised.
|
7
|
+
#
|
8
|
+
# This code is adapted slightly from the following blog post:
|
9
|
+
# http://blog.codefront.net/2008/01/14/retrying-code-blocks-in-ruby-on-exceptions-whatever/
|
10
|
+
def retryable(opts=nil)
|
11
|
+
opts = { :tries => 1, :on => Exception }.merge(opts || {})
|
12
|
+
|
13
|
+
begin
|
14
|
+
return yield
|
15
|
+
rescue *opts[:on]
|
16
|
+
if (opts[:tries] -= 1) > 0
|
17
|
+
sleep opts[:sleep].to_f if opts[:sleep]
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '../lib/'
|
2
|
+
$: << File.dirname(__FILE__)
|
3
|
+
require 'channelizer'
|
4
|
+
module ChannelHelper
|
5
|
+
|
6
|
+
def new_channel(type)
|
7
|
+
case type
|
8
|
+
when :winrm
|
9
|
+
Channelizer::Factory.build_channel(type, :username => 'vagrant', :password => 'vagrant', :host => 'localhost', :port => 5985)
|
10
|
+
when :ssh
|
11
|
+
Channelizer::Factory.build_channel(type, :username => 'vagrant', :keys => ['/Users/pmorton/.ssh/deploy'],:password => 'vagrant', :host => 'localhost', :port => 2200)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.include(ChannelHelper)
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
This is a test upload
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'channel_helper.rb'
|
2
|
+
describe "winrm object form" do
|
3
|
+
it 'should expose a session' do
|
4
|
+
c = Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :username => 'vagrant', :password => 'vagrant')
|
5
|
+
c.session.should be_a_kind_of(WinRM::WinRMWebService)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should respond to instance methods of the base interface' do
|
9
|
+
c = Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :username => 'vagrant', :password => 'vagrant')
|
10
|
+
c.should respond_to(:execute,:upload,:shell_execute,:sudo_command, :ready?)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should respond to class methods of the base interface' do
|
14
|
+
Channelizer::Channels::WinRM.should respond_to(:validate_options)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'channel_helper.rb'
|
2
|
+
|
3
|
+
Channelizer::Factory.channels.each do |channel,klass|
|
4
|
+
puts "CLASS #{channel}"
|
5
|
+
describe "#{channel} command execution" do
|
6
|
+
before(:all) do
|
7
|
+
@channel = new_channel(channel.to_sym)
|
8
|
+
puts @channel
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should echo back a sudo command' do
|
12
|
+
@channel.sudo_command('test_command').should == "sudo test_command" unless channel.to_sym.eql? :winrm
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should execute a command sucessfully' do
|
16
|
+
@channel.execute('hostname').should == 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#{channel} shell execution" do
|
21
|
+
before(:all) do
|
22
|
+
@channel = new_channel(:winrm)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should except when non-zero exit code is specified' do
|
26
|
+
expect {
|
27
|
+
@channel.shell_execute('exit 1')
|
28
|
+
}.to raise_error(Channelizer::Exceptions::BadExitCode, /1/)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should NOT except when non-zero exit code is specified and it has been told not to' do
|
32
|
+
@channel.shell_execute('exit 1',:check_exit_code => false)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should except based on custom error codes' do
|
36
|
+
expect {
|
37
|
+
@channel.shell_execute('exit 0', :exit_codes => [1,2])
|
38
|
+
}.to raise_error(Channelizer::Exceptions::BadExitCode, /0/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return the exit code of the command' do
|
42
|
+
@channel.shell_execute('exit 1',:check_exit_code => false).should == 1
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should output stuff to the console' do
|
46
|
+
STDOUT.should_receive(:print).with("test message\r\n")
|
47
|
+
@channel.shell_execute('echo test message')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should NOT output stuff to the console' do
|
51
|
+
STDOUT.should_not_receive(:print).with("test message\r\n")
|
52
|
+
@channel.shell_execute('echo test message', :out_console => false)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#{channel} upload" do
|
57
|
+
before(:all) do
|
58
|
+
@channel = new_channel(channel.to_sym)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#{channel} interogatory methods" do
|
65
|
+
before(:all) do
|
66
|
+
@channel = new_channel(channel.to_sym)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should be ready' do
|
70
|
+
@channel.ready?.should == true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'winrm specifc functionality' do
|
76
|
+
before(:all) do
|
77
|
+
@channel = new_channel(:winrm)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should echo back a sudo command' do
|
81
|
+
@channel.sudo_command('test_command').should == "test_command"
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should execute in powershell when requested' do
|
85
|
+
@channel.execute('dir ENV:\\', :shell => :powershell).should == 0
|
86
|
+
@channel.execute('dir ENV:\\').should == 1
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should upload a file' do
|
90
|
+
@channel.upload('./spec/test_data/test_file.txt', 'C:\test_file.txt')
|
91
|
+
STDOUT.should_receive(:print).with("This is a test upload")
|
92
|
+
(@channel.execute "type test_file.txt") == 0
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'ssh specifc functionality' do
|
99
|
+
before(:all) do
|
100
|
+
@channel = new_channel(:ssh)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should upload a file' do
|
104
|
+
@channel.upload('./spec/test_data/test_file.txt', '/tmp/test_file.txt')
|
105
|
+
STDOUT.should_receive(:print).with("This is a test upload")
|
106
|
+
(@channel.execute "cat /tmp/test_file.txt") == 0
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'channel_helper.rb'
|
2
|
+
|
3
|
+
describe "winrm option parsing" do
|
4
|
+
it 'should require a host' do
|
5
|
+
expect {
|
6
|
+
Channelizer::Factory.build_channel(:winrm, :username => 'vagrant', :password => 'vagrant', :port => 5985)
|
7
|
+
}.to raise_error(ArgumentError, /host is a required option/)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should use the default port' do
|
11
|
+
Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :username => 'vagrant', :password => 'vagrant')
|
12
|
+
end
|
13
|
+
|
14
|
+
# Requires a wokring kerberos setup
|
15
|
+
#it 'should not require username and password if kerberos is specified' do
|
16
|
+
# Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :realm => 'test')
|
17
|
+
#end
|
18
|
+
|
19
|
+
it 'should require a username and password' do
|
20
|
+
expect {
|
21
|
+
Channelizer::Factory.build_channel(:winrm, :host => 'localhost')
|
22
|
+
}.to raise_error(ArgumentError, /You must specify one of \[\[\:username, \:password\]/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should require a username even if a password is given' do
|
26
|
+
expect {
|
27
|
+
Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :password => 'vagrant')
|
28
|
+
}.to raise_error(ArgumentError, /You must specify one of \[\[\:username, \:password\]/)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should require a password even if a username is given' do
|
32
|
+
expect {
|
33
|
+
Channelizer::Factory.build_channel(:winrm, :host => 'localhost', :username => 'vagrant')
|
34
|
+
}.to raise_error(ArgumentError, /You must specify one of \[\[\:username, \:password\]/)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: channelizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul Morton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: winrm
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.1.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: net-ssh
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.5.2
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.5.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: net-scp
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.4
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: A gem that abstracts shell and upload channels
|
95
|
+
email:
|
96
|
+
- pmorton@biaprotect.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .travis.yml
|
103
|
+
- Gemfile
|
104
|
+
- LICENSE
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- channelizer.gemspec
|
108
|
+
- lib/channelizer.rb
|
109
|
+
- lib/channelizer/channels/base.rb
|
110
|
+
- lib/channelizer/channels/ssh.rb
|
111
|
+
- lib/channelizer/channels/winrm.rb
|
112
|
+
- lib/channelizer/exceptions/exceptions.rb
|
113
|
+
- lib/channelizer/factory.rb
|
114
|
+
- lib/channelizer/util/retryable.rb
|
115
|
+
- lib/channelizer/version.rb
|
116
|
+
- spec/channel_helper.rb
|
117
|
+
- spec/test_data/test_file.txt
|
118
|
+
- spec/winrm_form_spec.rb
|
119
|
+
- spec/winrm_functional_spec.rb
|
120
|
+
- spec/winrm_options_spec.rb
|
121
|
+
homepage: ''
|
122
|
+
licenses: []
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
hash: -721956935773256320
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
segments:
|
143
|
+
- 0
|
144
|
+
hash: -721956935773256320
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 1.8.24
|
148
|
+
signing_key:
|
149
|
+
specification_version: 3
|
150
|
+
summary: Test
|
151
|
+
test_files:
|
152
|
+
- spec/channel_helper.rb
|
153
|
+
- spec/test_data/test_file.txt
|
154
|
+
- spec/winrm_form_spec.rb
|
155
|
+
- spec/winrm_functional_spec.rb
|
156
|
+
- spec/winrm_options_spec.rb
|