ruby-eql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ === 0.0.1 / 2008-05-28
2
+
3
+ * Initial release
4
+
5
+ * Birthday!
6
+ * Test release.
7
+
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ Todo.txt
6
+ examples/ssh_show_members_with_volumes.rb
7
+ examples/telnet_options_file_show_version.rb
8
+ lib/eql.rb
9
+ lib/eql/cli.rb
10
+ lib/netsshtelnet.rb
11
+ test/test_eql.rb
data/README.txt ADDED
@@ -0,0 +1,56 @@
1
+ = ruby-eql
2
+
3
+ * http://ruby-eql.rubyforge.org
4
+ * mailto:matt@bravenet.com
5
+
6
+ == DESCRIPTION:
7
+
8
+ Ruby module to make interacting with the Equallogic PSxxxE series command line
9
+ interface a breeze.
10
+
11
+ Initial development and testing done with firmware V3.3.1 and PS100E/PS300E
12
+ series arrays.
13
+
14
+ == FEATURES/PROBLEMS:
15
+
16
+ * No testing with the latest PS5000x arrays, if you have one, let me know.
17
+
18
+ == SYNOPSIS:
19
+
20
+ FIX (code sample of usage)
21
+
22
+ == REQUIREMENTS:
23
+
24
+ * net-ssh
25
+
26
+ == INSTALL:
27
+
28
+ * sudo gem install ruby-eql
29
+
30
+ == LICENSE:
31
+
32
+ This product is in no way sponsored, endorsed or associated with Dell Inc. or
33
+ EqualLogic Inc.
34
+
35
+ (The MIT License)
36
+
37
+ Copyright (c) 2008 Matthew Kent, Bravenet Web Services Inc.
38
+
39
+ Permission is hereby granted, free of charge, to any person obtaining
40
+ a copy of this software and associated documentation files (the
41
+ 'Software'), to deal in the Software without restriction, including
42
+ without limitation the rights to use, copy, modify, merge, publish,
43
+ distribute, sublicense, and/or sell copies of the Software, and to
44
+ permit persons to whom the Software is furnished to do so, subject to
45
+ the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be
48
+ included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
51
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
52
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
53
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
54
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
55
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
56
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ $:.unshift(File.dirname(__FILE__) + "/lib")
6
+ require 'eql'
7
+
8
+ Hoe.new('ruby-eql', Eql::VERSION) do |p|
9
+ p.developer('Matthew Kent', 'matt@bravenet.com')
10
+ p.extra_deps << ['net-ssh', '>= 2.0.1']
11
+ p.remote_rdoc_dir = ''
12
+ end
13
+
14
+ # vim: syntax=Ruby
data/Todo.txt ADDED
@@ -0,0 +1,5 @@
1
+ = to do
2
+
3
+ == 0.1.0
4
+ * proper documentation.
5
+ * basic test suite.
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/ruby
2
+
3
+ $: << File.dirname(__FILE__) + "/../lib"
4
+
5
+ require 'eql'
6
+
7
+ san = Eql::GroupManager.open(
8
+ :method => 'ssh',
9
+ :host => 'eql.foo.com',
10
+ :username => 'grpadmin',
11
+ :password => 'pass',
12
+ :timeout => 3,
13
+ # nb: In telnet mode will print your user/password into the log, though
14
+ # in ssh it won't.
15
+ :session_log => "debug.log"
16
+ )
17
+
18
+ begin
19
+
20
+ puts "= Group Volumes ="
21
+ san.volumes.each do |v|
22
+ puts "* volume #{v.name}"
23
+ san.snapshots(v.name).each do |s|
24
+ puts "** snapshot #{s.name}"
25
+ end
26
+ end
27
+
28
+ puts ""
29
+
30
+ puts "= Group Members ="
31
+ san.members.each do |m|
32
+ puts "* member #{m.name}"
33
+
34
+ Eql::CliTable.parse(san.raw("member select #{m.name} show volumes")).each do |v|
35
+ puts "** volume #{v.volume_name} (contribution #{v.contribution})"
36
+ end
37
+ end
38
+
39
+ puts ""
40
+
41
+ san.raw("member select thisshouldfail")
42
+
43
+ rescue Eql::GroupManager::GroupManagerError => e
44
+ puts "Error, GroupManager says '#{e}' :("
45
+ end
46
+
47
+ san.close
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/ruby
2
+
3
+ $: << File.dirname(__FILE__) + "/../lib"
4
+
5
+ require 'eql'
6
+
7
+ san = Eql::GroupManager.open(
8
+ :method => 'telnet',
9
+ :host => 'eql.foo.com',
10
+ :timeout => 5,
11
+ # This file can contain any of the options here, in this case it contains
12
+ # just our secret user/password, this file is set chmod 400 and doesn't
13
+ # have to live in source control.
14
+ :options_file => ENV['HOME'] + '/.eql_secrets.yaml'
15
+ )
16
+
17
+ begin
18
+
19
+ member = san.members[0]
20
+ puts "#{member.name} version is #{member.version}."
21
+
22
+ rescue Eql::GroupManager::GroupManagerError => e
23
+ puts "Error, GroupManager says '#{e}' :("
24
+ end
25
+
26
+ san.close
data/lib/eql/cli.rb ADDED
@@ -0,0 +1,227 @@
1
+ require 'ostruct'
2
+
3
+ module Eql
4
+ class CliTable
5
+ EQL_CLI_COLUMN_SEP = '-'
6
+ EQL_CLI_WRAPPED_INDENT = ' '
7
+
8
+ # Utility class to handle the unwrapping and interpreting of the Equallogic
9
+ # tables
10
+ #
11
+ # eg :
12
+ # eql2-Mirror> show volume
13
+ # Name Size SnapShots Status Permission Connections TP
14
+ # --------------- ---------- --------- -------------- ---------- ----------- -
15
+ # shared 750GB 6 online read-write 1 N
16
+ # shared.1 1000GB 0 online read-write 0 Y
17
+ # backups.1 2000GB 1 online read-write 1 Y
18
+ # eql2-Mirror>
19
+ #
20
+ # this is made even more difficult by how they wrap
21
+ #
22
+ # eql2-Mirror> show member
23
+ # Name Status Version Disks Capacity FreeSpace Connections
24
+ # ---------- ------- ---------- ----- ---------- ---------- -----------
25
+ # eql2 online V3.3.1 (R6 14 6695.86GB 2908.3GB 3
26
+ # 8703)
27
+ # eql2-Mirror>
28
+
29
+ class<<self
30
+ # take string or array of raw cli output which includes a typical
31
+ # equallogic table, process it and return an array of openstruct objects or yield each one
32
+ # eg:
33
+ #
34
+ # CliTable.parse(raw("show volume")
35
+ #
36
+ # input is pretty sensitive to format changes
37
+ def parse(raw_output)
38
+ table = raw_output
39
+
40
+ if raw_output.kind_of?(String)
41
+ table = raw_output.split("\n")
42
+ end
43
+
44
+ result = []
45
+
46
+ # Drop truely empty lines because some commands output them
47
+ # eg: volume select <vol> show access
48
+ table.each do |line|
49
+ if line.strip.length == 0
50
+ table.delete(line)
51
+ end
52
+ end
53
+
54
+ # Drop command
55
+ table.shift
56
+ # and prompt
57
+ table.pop
58
+
59
+ table = unwrap(table)
60
+
61
+ # Grab headers
62
+ # No multi line headers from what I see
63
+ headers = table.shift
64
+
65
+ # Convert them to simple symbols
66
+ attributes = []
67
+ headers.each do |h|
68
+ # cleanup whitespace
69
+ prep = h
70
+ prep.strip!
71
+ prep.downcase!
72
+ prep.gsub!(%r{\s}, '_')
73
+ attributes << prep.to_sym
74
+ end
75
+
76
+ # drop the header separator line
77
+ table.shift
78
+
79
+ # Now each row of output gets converted to its own object
80
+ tmp = {}
81
+ table.each do |line|
82
+ i = 0
83
+ line.each do |column|
84
+ tmp[attributes[i]] = column.strip
85
+ i += 1
86
+ end
87
+
88
+ row = OpenStruct.new(tmp)
89
+ if block_given?
90
+ yield row
91
+ else
92
+ result << row
93
+ end
94
+ end
95
+
96
+ result
97
+ end
98
+
99
+ # Returns a Regexp object matching column widths determined by examining our
100
+ # column seperator line
101
+ def get_filter(lines = [], columnsep = EQL_CLI_COLUMN_SEP) #:nodoc:
102
+ last = filter = nil
103
+
104
+ lines.each do |line|
105
+ # assume just whitespace and columnsep means our column headers were on the
106
+ # previous line
107
+ if line =~ %r{^[#{columnsep} ]+$}
108
+
109
+ # okay this is fun
110
+ tmp = String.new
111
+ column_width = 0
112
+
113
+ # walk through the line
114
+ line.length.times do |pos|
115
+ # collecting column width based on seperator
116
+ if line[pos].chr == columnsep
117
+ column_width += 1
118
+ # end of column, but!
119
+ elsif line[pos].chr == ' '
120
+ # check our headers above us, sometimes the column header is
121
+ # larger than the field seperator as in 'show volume' eg:
122
+ #
123
+ # Name Size SnapShots Status Permission Connections TP
124
+ # --------------- ---------- --------- -------------- ---------- ----------- -
125
+ # shared 750GB 5 online read-write 1 N
126
+ mark = pos
127
+ # enlarge the filter to capture the header as well, makes for a
128
+ # proper attribute name later when we form the object
129
+ while mark+1 <= last.length and last[mark].chr != ' '
130
+ column_width += 1
131
+ mark += 1
132
+ end
133
+ tmp << "(.{#{column_width}}) "
134
+ column_width = 0
135
+ end
136
+ end
137
+ tmp.chop!
138
+ # hooray!
139
+ filter = %r{#{tmp}}
140
+ break
141
+ end
142
+
143
+ last = line
144
+ end
145
+
146
+ filter
147
+ end
148
+
149
+ # break table into arrays of unwrapped data, yield array in block or
150
+ # return all arrays
151
+ def unwrap(lines = [], filter = nil, indented = EQL_CLI_WRAPPED_INDENT) #:nodoc:
152
+ previous_columns = nil
153
+ rows = []
154
+
155
+ if filter.nil?
156
+ filter = get_filter(lines)
157
+ end
158
+
159
+ collecting = true
160
+ # reversed seemed easier to parse
161
+ lines.reverse.each do |line|
162
+ # get our columns and drop the 'match all' entry
163
+ columns = filter.match(line).to_a
164
+ columns.shift
165
+
166
+ # step through the columns
167
+ i = 0
168
+ columns.each do |field|
169
+ # wrapped entries begin with indentation, its the only way to tell
170
+ # they are wrapped.
171
+ if field =~ %r{^#{indented}}
172
+ # we are building a line
173
+ collecting = true
174
+ else
175
+ # assume its done
176
+ collecting = false
177
+ end
178
+ if previous_columns.nil? == false and previous_columns[i].nil? == false
179
+ # append the last lines data to this new one, drop the indendation
180
+ columns[i] << previous_columns[i][(indented.length)..(previous_columns[i].length)]
181
+ end
182
+ i += 1
183
+ end
184
+
185
+ # collect finished lines and reset
186
+ if collecting == false
187
+ rows << columns
188
+ previous_columns = nil
189
+ else
190
+ previous_columns = columns
191
+ end
192
+ end
193
+
194
+ rows.reverse!
195
+
196
+ if block_given?
197
+ rows.each do |row|
198
+ yield row
199
+ end
200
+ else
201
+ rows
202
+ end
203
+ end
204
+
205
+ def get_headers(lines = [], filter = nil, columnsep = EQL_CLI_COLUMN_SEP) #:nodoc:
206
+ last = nil
207
+
208
+ if filter.nil?
209
+ filter = get_filter(lines)
210
+ end
211
+
212
+ lines.each do |line|
213
+ # assume just whitespace and columnsep means our column headers were on the
214
+ # previous line
215
+ if line =~ %r{^[#{columnsep} ]+$}
216
+ ret = filter.match(last).to_a
217
+ ret.shift
218
+ return ret
219
+ end
220
+
221
+ last = line
222
+ end
223
+ end
224
+
225
+ end # self
226
+ end # class
227
+ end # module
data/lib/eql.rb ADDED
@@ -0,0 +1,164 @@
1
+ # FIXME: proper rdoc summary here
2
+
3
+ require 'net/telnet'
4
+ require 'yaml'
5
+
6
+ require 'netsshtelnet'
7
+ require 'eql/cli'
8
+
9
+ module Eql
10
+ VERSION = '0.0.1'
11
+ COMPAT_VERSION = 'V3.3.1 (R68703)'
12
+
13
+ class GroupManager
14
+ class GroupManagerError < Exception; end
15
+
16
+ attr_reader :ops
17
+
18
+ # Safer!
19
+ DEFAULT_METHOD = 'ssh'
20
+
21
+ DEFAULT_HOST = '127.0.0.1'
22
+ DEFAULT_SSH_PORT = 22
23
+ DEFAULT_TELNET_PORT = 23
24
+ DEFAULT_TIMEOUT = 10
25
+
26
+ DEFAULT_USERNAME = 'grpadmin'
27
+ DEFAULT_PASSWORD = 'grpadmin'
28
+
29
+ # Typical command prompt
30
+ PROMPT = %r{^\S+>\s.*$}
31
+ # Eql tty doesn't translate \n to \r\n
32
+ TERMINATOR = "\r"
33
+
34
+ # An options_file can be specified as well to keep user/passwords secret or
35
+ # abstract the config from code. It should be a yaml hash consisting of any
36
+ # of the 'args[]' below in the following format
37
+ #
38
+ # ---
39
+ # :username: <user>
40
+ # :password: <pass>
41
+ # :method: [ssh|telnet]
42
+ # :host: <etc..>
43
+ #
44
+ # See examples/
45
+ def initialize(args = {})
46
+
47
+ # Pecking order:
48
+ # args > options_file > Defaults
49
+ if args[:options_file]
50
+ tmp = YAML.load_file(args[:options_file])
51
+ args = tmp.merge(args)
52
+ end
53
+
54
+ # Setup some standard, and non standard net::ssh/net::telnet options for
55
+ # our connection
56
+ @ops = {
57
+ # nb: method isn't used by ssh/telnet, just here for consistency
58
+ "Method" => args[:method] || DEFAULT_METHOD,
59
+ "Host" => args[:host] || DEFAULT_HOST,
60
+ "Port" => args[:port] || nil,
61
+ "Timeout" => args[:timeout] || DEFAULT_TIMEOUT,
62
+ # nb: Telnet doesn't use user/pass in this hash, but net::ssh does
63
+ "Username" => args[:username] || DEFAULT_USERNAME,
64
+ "Password" => args[:password] || DEFAULT_PASSWORD,
65
+ "Dump_log" => args[:session_log] || nil,
66
+ "Prompt" => PROMPT,
67
+ # nb: Just net::ssh, net::telnet is autodetected
68
+ "Terminator" => TERMINATOR,
69
+ }
70
+
71
+ # Don't pass it at all if undefined, they will try to open it
72
+ @ops.delete("Dump_log") if @ops["Dump_log"].nil?
73
+
74
+ if @ops["Method"] == "ssh"
75
+ @ops["Port"] = DEFAULT_SSH_PORT if @ops["Port"].nil?
76
+
77
+ @connection = Net::SSH::Telnet::new(@ops)
78
+ elsif @ops["Method"] == "telnet"
79
+ @ops["Port"] = DEFAULT_TELNET_PORT if @ops["Port"].nil?
80
+
81
+ @connection = Net::Telnet::new(@ops)
82
+ @connection.login(@ops["Username"], @ops["Password"])
83
+ end
84
+
85
+ # Wish these were per session.. they could be read and restored on close
86
+ # but the that would likely break down quickly.
87
+ #
88
+ # Make output easier to parse
89
+ raw("cli-settings paging off")
90
+ raw("cli-settings confirmation off")
91
+ raw("cli-settings formatoutput off")
92
+ # Keep spurious info off of terminal
93
+ raw("cli-settings events off")
94
+
95
+ # Finally set the display as wide as possible, this IS per session
96
+ # thankfully
97
+ raw("stty rows 256")
98
+ raw("stty columns 255")
99
+ raw("stty hardwrap off")
100
+ end
101
+
102
+ def self.open(args = {})
103
+ new(args)
104
+ end
105
+
106
+ def close
107
+ @connection.close
108
+ end
109
+
110
+ # Run an arbitrary command on the cli. Returning a String or yielding on
111
+ # newlines in a block.
112
+ def raw(args)
113
+ timeout = @ops["Timeout"]
114
+
115
+ if args.kind_of?(Hash)
116
+ cmd = args[:cmd]
117
+ timeout = args[:timeout] || timeout
118
+ else
119
+ cmd = args
120
+ end
121
+
122
+ data = @connection.cmd("String" => cmd, "Timeout" => timeout)
123
+
124
+ # Errors seem to be in these formats:
125
+ #
126
+ # Error: Too many parameters
127
+ # or
128
+ # % Error - Member xxx does not exist.
129
+ if data =~ %r{Error(:| -) (.*)\s?$}
130
+ raise GroupManagerError.new($2)
131
+ end
132
+
133
+ if block_given?
134
+ data.split("\n").each do |l|
135
+ yield l
136
+ end
137
+ else
138
+ data
139
+ end
140
+ end
141
+
142
+ # A few helper methods
143
+ #
144
+ # Next level of abstraction would be to create volume/member etc obj which
145
+ # would be do all the data conversion and be returned
146
+
147
+ # Get all available group volumes, returning the resulting Array
148
+ def volumes
149
+ CliTable.parse(raw("show volume"))
150
+ end
151
+
152
+ # Get all available group members, returning the resulting Array
153
+ def members
154
+ CliTable.parse(raw("show member"))
155
+ end
156
+
157
+ # Get all available snapshots on a given volume, returning the resulting
158
+ # Array
159
+ def snapshots(volume)
160
+ CliTable.parse(raw("volume select #{volume} show snapshots"))
161
+ end
162
+
163
+ end # end class
164
+ end # end module
@@ -0,0 +1,433 @@
1
+ # Based on code in net/telnet.rb by Wakou Aoyama <wakou@ruby-lang.org>
2
+ # Modified to work with Net::SSH by Brian Candler <b.candler@pobox.com>
3
+
4
+ require 'rubygems'
5
+ require 'net/ssh'
6
+
7
+ module Net
8
+ module SSH
9
+
10
+ # == Net::SSH::Telnet
11
+ #
12
+ # Provides a simple send/expect interface with an API almost
13
+ # identical to Net::Telnet. Please see Net::Telnet for main documentation.
14
+ # Only the differences are documented here.
15
+
16
+ class Telnet
17
+
18
+ CR = "\015"
19
+ LF = "\012"
20
+ EOL = CR + LF
21
+ REVISION = '$Id$'
22
+
23
+ # Wrapper to emulate the behaviour of Net::Telnet "Proxy" option, where
24
+ # the user passes in an already-open socket
25
+
26
+ class TinyFactory
27
+ def initialize(sock)
28
+ @sock = sock
29
+ end
30
+ def open(host, port)
31
+ s = @sock
32
+ @sock = nil
33
+ s
34
+ end
35
+ end
36
+
37
+ # Creates a new Net::SSH::Telnet object.
38
+ #
39
+ # The API is similar to Net::Telnet, although you will need to pass in
40
+ # either an existing Net::SSH::Session object or a Username and Password,
41
+ # as shown below.
42
+ #
43
+ # Note that unlike Net::Telnet there is no preprocess method automatically
44
+ # setting up options related to proper character translations, so if your
45
+ # remote ptty is configured differently than the typical linux one you may
46
+ # need to pass in a different terminator or call 'stty' remotely to set it
47
+ # into an expected mode. This is better explained by the author of perl's
48
+ # Net::SSH::Expect here:
49
+ #
50
+ # http://search.cpan.org/~bnegrao/Net-SSH-Expect-1.04/lib/Net/SSH/Expect.pod
51
+ # #IMPORTANT_NOTES_ABOUT_DEALING_WITH_SSH_AND_PSEUDO-TERMINALS
52
+ #
53
+ # though for most installs the default LF should be fine. See example 5
54
+ # below.
55
+ #
56
+ # A new option is added to correct a misfeature of Net::Telnet. If you
57
+ # pass "FailEOF" => true, then if the remote end disconnects while you
58
+ # are still waiting for your match pattern then an EOFError is raised.
59
+ # Otherwise, it reverts to the same behaviour as Net::Telnet, which is
60
+ # just to return whatever data was sent so far, or nil if no data was
61
+ # returned so far. (This is a poor design because you can't tell whether
62
+ # the expected pattern was successfully matched or the remote end
63
+ # disconnected unexpectedly, unless you perform a second match on the
64
+ # return string). See
65
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/11373
66
+ # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/11380
67
+ #
68
+ # Example 1 - pass existing Net::SSH::Session object
69
+ #
70
+ # ssh = Net::SSH.start("127.0.0.1",
71
+ # :username=>"test123",
72
+ # :password=>"pass456"
73
+ # )
74
+ # s = Net::SSH::Telnet.new(
75
+ # "Dump_log" => "/dev/stdout",
76
+ # "Session" => ssh
77
+ # )
78
+ # puts "Logged in"
79
+ # p s.cmd("echo hello")
80
+ #
81
+ # This is the most flexible way as it allows you to set up the SSH
82
+ # session using whatever authentication system you like. When done this
83
+ # way, calling Net::SSH::Telnet.new multiple times will create
84
+ # multiple channels, and #close will only close one channel.
85
+ #
86
+ # In all later examples, calling #close will close the entire
87
+ # Net::SSH::Session object (and therefore drop the TCP connection)
88
+ #
89
+ # Example 2 - pass host, username and password
90
+ #
91
+ # s = Net::SSH::Telnet.new(
92
+ # "Dump_log" => "/dev/stdout",
93
+ # "Host" => "127.0.0.1",
94
+ # "Username" => "test123",
95
+ # "Password" => "pass456"
96
+ # )
97
+ # puts "Logged in"
98
+ # puts s.cmd("echo hello")
99
+ #
100
+ # Example 3 - pass open IO object, username and password (this is really
101
+ # just for compatibility with Net::Telnet Proxy feature)
102
+ #
103
+ # require 'socket'
104
+ # sock = TCPSocket.open("127.0.0.1",22)
105
+ # s = Net::SSH::Telnet.new(
106
+ # "Dump_log" => "/dev/stdout",
107
+ # "Proxy" => sock,
108
+ # "Username" => "test123",
109
+ # "Password" => "pass456"
110
+ # )
111
+ # puts "Logged in"
112
+ # puts s.cmd("echo hello")
113
+ #
114
+ # Example 4 - pass a connection factory, host, username and password;
115
+ # Net::SSH will call #open(host,port) on this object. Included just
116
+ # because it was easy :-)
117
+ #
118
+ # require 'socket'
119
+ # s = Net::SSH::Telnet.new(
120
+ # "Dump_log" => "/dev/stdout",
121
+ # "Factory" => TCPSocket,
122
+ # "Host" => "127.0.0.1",
123
+ # "Username" => "test123",
124
+ # "Password" => "pass456"
125
+ # )
126
+ # puts "Logged in"
127
+ # puts s.cmd("echo hello")
128
+ #
129
+ # Example 5 - connection to a SAN device running a customized NetBSD with
130
+ # different ptty defaults, it doesn't convert LF -> CR+LF (see the man
131
+ # page for 'stty')
132
+ #
133
+ # require 'socket'
134
+ # s = Net::SSH::Telnet.new(
135
+ # "Dump_log" => "/dev/stdout",
136
+ # "Factory" => TCPSocket,
137
+ # "Host" => "192.168.1.1",
138
+ # "Username" => "test123",
139
+ # "Password" => "pass456",
140
+ # "Terminator" => "\r"
141
+ # )
142
+ # puts "Logged in"
143
+ # puts s.cmd("show alerts")
144
+
145
+ def initialize(options, &blk) # :yield: mesg
146
+ @options = options
147
+ @options["Host"] = "localhost" unless @options.has_key?("Host")
148
+ @options["Port"] = 22 unless @options.has_key?("Port")
149
+ @options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt")
150
+ @options["Timeout"] = 10 unless @options.has_key?("Timeout")
151
+ @options["Waittime"] = 0 unless @options.has_key?("Waittime")
152
+ @options["Terminator"] = LF unless @options.has_key?("Terminator")
153
+
154
+ unless @options.has_key?("Binmode")
155
+ @options["Binmode"] = false
156
+ else
157
+ unless (true == @options["Binmode"] or false == @options["Binmode"])
158
+ raise ArgumentError, "Binmode option must be true or false"
159
+ end
160
+ end
161
+
162
+ if @options.has_key?("Output_log")
163
+ @log = File.open(@options["Output_log"], 'a+')
164
+ @log.sync = true
165
+ @log.binmode
166
+ end
167
+
168
+ if @options.has_key?("Dump_log")
169
+ @dumplog = File.open(@options["Dump_log"], 'a+')
170
+ @dumplog.sync = true
171
+ @dumplog.binmode
172
+ def @dumplog.log_dump(dir, x) # :nodoc:
173
+ len = x.length
174
+ addr = 0
175
+ offset = 0
176
+ while 0 < len
177
+ if len < 16
178
+ line = x[offset, len]
179
+ else
180
+ line = x[offset, 16]
181
+ end
182
+ hexvals = line.unpack('H*')[0]
183
+ hexvals += ' ' * (32 - hexvals.length)
184
+ hexvals = format("%s %s %s %s " * 4, *hexvals.unpack('a2' * 16))
185
+ line = line.gsub(/[\000-\037\177-\377]/n, '.')
186
+ printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line
187
+ addr += 16
188
+ offset += 16
189
+ len -= 16
190
+ end
191
+ print "\n"
192
+ end
193
+ end
194
+
195
+ if @options.has_key?("Session")
196
+ @ssh = @options["Session"]
197
+ @close_all = false
198
+ elsif @options.has_key?("Proxy")
199
+ @ssh = Net::SSH.start(@options["Host"], # ignored
200
+ :port => @options["Port"], # ignored
201
+ :username => @options["Username"],
202
+ :password => @options["Password"],
203
+ :timeout => @options["Timeout"],
204
+ :proxy => TinyFactory.new(@options["Proxy"]),
205
+ :log => (@log ? @log : STDERR)
206
+ )
207
+ @close_all = true
208
+ else
209
+ message = "Trying " + @options["Host"] + "...\n"
210
+ yield(message) if block_given?
211
+ @log.write(message) if @options.has_key?("Output_log")
212
+ @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
213
+
214
+ begin
215
+ @ssh = Net::SSH.start(@options["Host"],
216
+ :port => @options["Port"],
217
+ :username => @options["Username"],
218
+ :password => @options["Password"],
219
+ :timeout => @options["Timeout"],
220
+ :proxy => @options["Factory"],
221
+ :log => (@log ? @log : STDERR)
222
+ )
223
+ @close_all = true
224
+ rescue TimeoutError
225
+ raise TimeoutError, "timed out while opening a connection to the host"
226
+ rescue
227
+ @log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log")
228
+ @dumplog.log_dump('#', $ERROR_INFO.to_s + "\n") if @options.has_key?("Dump_log")
229
+ raise
230
+ end
231
+
232
+ message = "Connected to " + @options["Host"] + ".\n"
233
+ yield(message) if block_given?
234
+ @log.write(message) if @options.has_key?("Output_log")
235
+ @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
236
+ end
237
+
238
+ @buf = ""
239
+ @eof = false
240
+ @channel = nil
241
+ state = nil
242
+ @ssh.open_channel do |channel|
243
+ channel.on_success { |ch|
244
+ case state
245
+ when :pty
246
+ state = :shell
247
+ channel.send_request "shell", nil, true
248
+ when :shell
249
+ state = :prompt
250
+ @channel = ch
251
+ waitfor(@options['Prompt'], &blk)
252
+ return
253
+ end
254
+ }
255
+ channel.on_failure { |ch|
256
+ case state
257
+ when :pty
258
+ state = :shell
259
+ channel.send_request "shell", nil, true
260
+ else
261
+ ch.close
262
+ raise "Failed to open ssh #{state}"
263
+ end
264
+ }
265
+ channel.on_data { |ch,data| @buf << data }
266
+ channel.on_extended_data { |ch,type,data| @buf << data if type == 1 }
267
+ channel.on_close { @eof = true }
268
+ state = :pty
269
+ channel.request_pty(:want_reply => true)
270
+ end
271
+ @ssh.loop
272
+ end # initialize
273
+
274
+ # Close the ssh channel, and also the entire ssh session if we
275
+ # opened it.
276
+
277
+ def close
278
+ @channel.close if @channel
279
+ @channel = nil
280
+ @ssh.close if @close_all and @ssh
281
+ end
282
+
283
+ # The ssh session and channel we are using.
284
+ attr_reader :ssh, :channel
285
+
286
+ # Turn newline conversion on (+mode+ == false) or off (+mode+ == true),
287
+ # or return the current value (+mode+ is not specified).
288
+ def binmode(mode = nil)
289
+ case mode
290
+ when nil
291
+ @options["Binmode"]
292
+ when true, false
293
+ @options["Binmode"] = mode
294
+ else
295
+ raise ArgumentError, "argument must be true or false"
296
+ end
297
+ end
298
+
299
+ # Turn newline conversion on (false) or off (true).
300
+ def binmode=(mode)
301
+ if (true == mode or false == mode)
302
+ @options["Binmode"] = mode
303
+ else
304
+ raise ArgumentError, "argument must be true or false"
305
+ end
306
+ end
307
+
308
+ # Read data from the host until a certain sequence is matched.
309
+
310
+ def waitfor(options) # :yield: recvdata
311
+ time_out = @options["Timeout"]
312
+ waittime = @options["Waittime"]
313
+ fail_eof = @options["FailEOF"]
314
+
315
+ if options.kind_of?(Hash)
316
+ prompt = if options.has_key?("Match")
317
+ options["Match"]
318
+ elsif options.has_key?("Prompt")
319
+ options["Prompt"]
320
+ elsif options.has_key?("String")
321
+ Regexp.new( Regexp.quote(options["String"]) )
322
+ end
323
+ time_out = options["Timeout"] if options.has_key?("Timeout")
324
+ waittime = options["Waittime"] if options.has_key?("Waittime")
325
+ fail_eof = options["FailEOF"] if options.has_key?("FailEOF")
326
+ else
327
+ prompt = options
328
+ end
329
+
330
+ if time_out == false
331
+ time_out = nil
332
+ end
333
+
334
+ line = ''
335
+ buf = ''
336
+ rest = ''
337
+ # We want to use #read_ready?(maxwait) but it's not available
338
+ sock = @ssh.connection.instance_variable_get(:@session).instance_variable_get(:@socket)
339
+
340
+ until prompt === line and ((@eof and @buf == "") or not IO::select([sock], nil, nil, waittime))
341
+ while @buf == "" and !@eof
342
+ unless IO::select([sock], nil, nil, time_out)
343
+ raise TimeoutError, "timed out while waiting for more data"
344
+ end
345
+ # Note: this could hang if partial message received. Should we
346
+ # wrap with timeout { ... } instead?
347
+ @channel.connection.process
348
+ end
349
+ if @buf != ""
350
+ c = @buf; @buf = ""
351
+ @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
352
+ buf = c
353
+ buf.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"]
354
+ rest = ''
355
+ @log.print(buf) if @options.has_key?("Output_log")
356
+ line += buf
357
+ yield buf if block_given?
358
+ elsif @eof # End of file reached
359
+ break if prompt === line
360
+ raise EOFError if fail_eof
361
+ if line == ''
362
+ line = nil
363
+ yield nil if block_given?
364
+ end
365
+ break
366
+ end
367
+ end
368
+ line
369
+ end
370
+
371
+ # Write +string+ to the host.
372
+ #
373
+ # Does not perform any conversions on +string+. Will log +string+ to the
374
+ # dumplog, if the Dump_log option is set.
375
+ def write(string)
376
+ @dumplog.log_dump('>', string) if @options.has_key?("Dump_log")
377
+ @channel.send_data string
378
+ @channel.connection.process true
379
+ end
380
+
381
+ # Sends a string to the host.
382
+ #
383
+ # This does _not_ automatically append a newline to the string. Embedded
384
+ # newlines may be converted depending upon the values of binmode or
385
+ # terminator.
386
+ def print(string)
387
+ terminator = @options["Terminator"]
388
+
389
+ if @options["Binmode"]
390
+ self.write(string)
391
+ else
392
+ self.write(string.gsub(/\n/n, terminator))
393
+ end
394
+ end
395
+
396
+ # Sends a string to the host.
397
+ #
398
+ # Same as #print(), but appends a newline to the string.
399
+ def puts(string)
400
+ self.print(string + "\n")
401
+ end
402
+
403
+ # Send a command to the host.
404
+ #
405
+ # More exactly, sends a string to the host, and reads in all received
406
+ # data until is sees the prompt or other matched sequence.
407
+ #
408
+ # The command or other string will have the newline sequence appended
409
+ # to it.
410
+
411
+ def cmd(options) # :yield: recvdata
412
+ match = @options["Prompt"]
413
+ time_out = @options["Timeout"]
414
+
415
+ if options.kind_of?(Hash)
416
+ string = options["String"]
417
+ match = options["Match"] if options.has_key?("Match")
418
+ time_out = options["Timeout"] if options.has_key?("Timeout")
419
+ else
420
+ string = options
421
+ end
422
+
423
+ self.puts(string)
424
+ if block_given?
425
+ waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c }
426
+ else
427
+ waitfor({"Prompt" => match, "Timeout" => time_out})
428
+ end
429
+ end
430
+
431
+ end # class Telnet
432
+ end # module SSH
433
+ end # module Net
data/test/test_eql.rb ADDED
File without changes
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: ruby-eql
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2008-05-30 00:00:00 -07:00
8
+ summary: Ruby module to make interacting with the Equallogic PSxxxE series command line interface a breeze
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ - matt@bravenet.com
13
+ homepage: http://ruby-eql.rubyforge.org
14
+ rubyforge_project: ruby-eql
15
+ description: Ruby module to make interacting with the Equallogic PSxxxE series command line interface a breeze. Initial development and testing done with firmware V3.3.1 and PS100E/PS300E series arrays.
16
+ autorequire:
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: true
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ - - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ - |
30
+ -----BEGIN CERTIFICATE-----
31
+ MIIDMDCCAhigAwIBAgIBADANBgkqhkiG9w0BAQUFADA+MQ0wCwYDVQQDDARtYXR0
32
+ MRgwFgYKCZImiZPyLGQBGRYIYnJhdmVuZXQxEzARBgoJkiaJk/IsZAEZFgNjb20w
33
+ HhcNMDgwNTI5MjM1MDUwWhcNMDkwNTI5MjM1MDUwWjA+MQ0wCwYDVQQDDARtYXR0
34
+ MRgwFgYKCZImiZPyLGQBGRYIYnJhdmVuZXQxEzARBgoJkiaJk/IsZAEZFgNjb20w
35
+ ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDGVrJnpk6/hCO5lg6T7As
36
+ unEksqdmCly6GkRpX0cVxusWG/wbjrZw4Ka5x9pSS2m3GfefEpu5CuWH9wnGB/q0
37
+ LwbWhrkpQb+uNVD6hEbQcngDEiQbiitaSWZKSvrIymbM5Tl92rIScmvx7Pd7l8tz
38
+ BLndL/DmrBHrHWQ47xLxOmBAA4NJy0gk7HdsdAFfJwrEoC14AAXqcI3X0qU3KGrr
39
+ 9Fs0jVeIKktx3fthwGVPrpYrMIw3xgiUqOqn7M+V4soD013dopaN7orewmCm9dQL
40
+ 4N06FMnGNjwFaS6+MV612nk8gkVpJwgcwTHE1GNt2NItR5zeK6j+AZ01DijYdjH9
41
+ AgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQ6jcdN
42
+ QqNNsx2XxigfTWE0Owy0XzANBgkqhkiG9w0BAQUFAAOCAQEAvWBkYaD7eKP+KHxJ
43
+ dtQuaebQTZvBX4vjk/Omjn7hj7u8X8aTvmCKyopnmywiApFH1zgDa8NRc1KTsvZt
44
+ zhSRWIuI7fFB+JFZUatE7Nius56id9UchYO98569t6bNU284XwBtcj4MN5Andfcp
45
+ LKdUNuuT0yui1InJG4fD0uKnOS1qcXEm+mN2s1uqPGVltEHorG7L/bPNAw8xV+uq
46
+ NPc7hhQTkbi7HB2Uwxr9uK9IGHEE5tDVnsPWPnJJ4jSOc7Bd1eHZuSMkEPfyREDf
47
+ h9ljoX3AGXW8qYohFzGjflamaWZ4xYaKZSNqS2i0Kc+M76Sy+r9FldDRNn4FGUBE
48
+ JqYgHg==
49
+ -----END CERTIFICATE-----
50
+
51
+ post_install_message:
52
+ authors:
53
+ - Matthew Kent
54
+ files:
55
+ - History.txt
56
+ - Manifest.txt
57
+ - README.txt
58
+ - Rakefile
59
+ - Todo.txt
60
+ - examples/ssh_show_members_with_volumes.rb
61
+ - examples/telnet_options_file_show_version.rb
62
+ - lib/eql.rb
63
+ - lib/eql/cli.rb
64
+ - lib/netsshtelnet.rb
65
+ - test/test_eql.rb
66
+ test_files:
67
+ - test/test_eql.rb
68
+ rdoc_options:
69
+ - --main
70
+ - README.txt
71
+ extra_rdoc_files:
72
+ - History.txt
73
+ - Manifest.txt
74
+ - README.txt
75
+ - Todo.txt
76
+ executables: []
77
+
78
+ extensions: []
79
+
80
+ requirements: []
81
+
82
+ dependencies:
83
+ - !ruby/object:Gem::Dependency
84
+ name: net-ssh
85
+ version_requirement:
86
+ version_requirements: !ruby/object:Gem::Version::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.0.1
91
+ version:
92
+ - !ruby/object:Gem::Dependency
93
+ name: hoe
94
+ version_requirement:
95
+ version_requirements: !ruby/object:Gem::Version::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 1.5.3
100
+ version:
metadata.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ 綛��� ޴X�V7x�ނ5��u�K!�Z8�c����|��a�����l�%x���!��A;���
2
+ .|�yF����1=s�DCN��>���_���n�VǞ�҃w���X�]��a�MO �L�HJ;ԟ��P�s�3�s����MY!��'� ���"�\�w�d�+����_����;�\��f�E�P ��%��l�{-�p�Z���i��t�#7=�u%42I�zu�!��aUM�l�3����3�QA�