ruby-eql 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/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�