channelizer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|