tsm-command 1.2

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/README ADDED
@@ -0,0 +1,33 @@
1
+ == Description
2
+ A wrapper for TSM dsmadmc and dsmc commands
3
+
4
+ == Prerequisites
5
+ * Ruby 1.8.4 or later
6
+
7
+ == Usage
8
+ t = TSM::Dsmadmc(id,pass)
9
+ t.open { |d|
10
+ d.command('q pr')
11
+ d.command('select count(*) from volumes')
12
+ }
13
+
14
+ TSM::Dsmadmc.command(id,pass,'q pr',{:se => :testserver})
15
+
16
+
17
+ class TestObserver
18
+ def update(msg)
19
+ p msg
20
+ end
21
+ end
22
+
23
+ TSM::Dsmadmc(id,pass) { |t|
24
+ t.add_observer(TestObserver.new())
25
+ t.command('q pr')
26
+ }
27
+
28
+ == Installation
29
+ === Manual Installation
30
+ === Gem Installation
31
+ gem install tsm-<version>.gem
32
+
33
+ == Notes
@@ -0,0 +1,5 @@
1
+ require 'tsm/error'
2
+ require 'tsm/dsmio'
3
+ require 'tsm/response'
4
+ require 'tsm/dsmadmc'
5
+ require 'tsm/dsm'
@@ -0,0 +1,69 @@
1
+ module TSM
2
+ class Dsm
3
+ def self.opt(filename='dsm.opt')
4
+ sys = {}
5
+ self.new(filename) { |line|
6
+ if (m = /^(\S+)(\s+)(\S+)/i.match(line))
7
+ sys[m[1].downcase.to_sym] = m[3]
8
+ end
9
+ }
10
+ sys
11
+ end
12
+
13
+ def self.sys(filename='dsm.sys')
14
+ sys = {} and hsh = {} and name = nil
15
+ self.new(filename) { |line|
16
+ if (m = /^se([a-zA-Z]*)(\s+)(\S+)/i.match(line))
17
+ sys[name] = hsh.dup if name
18
+ hsh.clear
19
+ name = m[3].downcase.to_sym
20
+ next
21
+ end
22
+
23
+ if (m = /^(\S+)(\s+)(\S+)/i.match(line))
24
+ k = m[1].downcase
25
+ if k[0..3] == 'incl'
26
+ h = {}
27
+ self.new(m[3]) { |i|
28
+ if (n = /^(\S+)(\s+)(\S+)/.match(i))
29
+ k2 = n[1].downcase
30
+ h[k2] = [] unless h.has_key?(k2)
31
+ h[k2].push(n[3])
32
+ end
33
+ }
34
+ hsh[k.to_sym] = h.dup
35
+ else
36
+ hsh[k.to_sym] = m[3]
37
+ end
38
+ end
39
+ }
40
+ sys[name] = hsh.dup if name
41
+ sys
42
+ end
43
+
44
+ def initialize(filename,&block)
45
+ process(filename) { |line|
46
+ line = line.chomp
47
+ line.strip!
48
+
49
+ next if line[0..0] == '*' or line == ''
50
+
51
+ yield line
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def process(name,&block)
58
+ f = File.readable?(name) ? name : nil
59
+ path = ['/opt/tivoli/tsm/client/ba/bin','/usr/tivoli/tsm/client/ba/bin']
60
+ (ENV['PATH'].split(File::PATH_SEPARATOR) + path).each { |p|
61
+ if File.readable?(x=File.join(p,name))
62
+ f = x
63
+ end
64
+ }
65
+
66
+ IO.foreach(f) { |l| yield l } if f
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,207 @@
1
+ require 'pty'
2
+ require 'observer'
3
+ require 'parsedate'
4
+
5
+ module TSM
6
+ class Dsmadmc
7
+ include Observable
8
+
9
+ FORCE_OPT = {:displ => 'list',:out => true,:noc => true}
10
+ FAKE_QUIT = {:command=>"quit", :messages=>[], :rows=>[], :count=>0}
11
+
12
+ @@path = ['/opt/tivoli/tsm/client/ba/bin','/usr/tivoli/tsm/client/ba/bin']
13
+ @@quit = true
14
+
15
+ attr_accessor :timeout
16
+ attr_reader :host, :header, :prompt
17
+ attr_reader :pty, :cmd
18
+
19
+ def self.command(id,password,command,opts = {})
20
+ self.new(id,password,opts).command(command)
21
+ end
22
+
23
+ def self.quit?
24
+ @@quit
25
+ end
26
+
27
+ def self.quit=(bool)
28
+ @@quit = bool ? true : false
29
+ end
30
+
31
+ def initialize(id,password,opt = {},&block)
32
+ @timeout = 60
33
+
34
+ opt[:id] = id
35
+ opt[:pa] = password
36
+
37
+ args = [find(self.class.to_s.split(":").last.downcase)]
38
+
39
+ opt.merge(FORCE_OPT).each{|k,v| args.push("-#{k.to_s}#{'=' << v.to_s if v.class != TrueClass}") }
40
+ opt.each_key do |k|
41
+ opt.each_key {|k2| opt.delete(k2) if k.to_s.casecmp(k2.to_s) == -1}
42
+ end
43
+
44
+ @cmd = args.join(" ")
45
+
46
+ return self unless block_given?
47
+
48
+ open(&block)
49
+ ensure
50
+ close if block_given? and not closed?
51
+ end
52
+
53
+ def open(&block)
54
+ ptyval = nil and success = false
55
+ begin
56
+ PTY.spawn(@cmd) { |r,w,pid|
57
+ @header,@prompt,@host = r._dsm_prompt([/tsm: ([A-Z0-9]*)>/i,/Enter your password: /i],@timeout)
58
+ parse(TSM::Response.new,@header.to_s).validate(TSM::Error::CommandError) if @host.to_s.empty?
59
+ raise TSM::Error::FatalError if @host.to_s.empty?
60
+
61
+ @established = Time.now()
62
+ @pty = {:r => r,:w => w,:pid => pid}
63
+
64
+ notify('open',{'host'=>@host})
65
+
66
+ ptyval = yield self
67
+ success = true
68
+ }
69
+ rescue PTY::ChildExited
70
+ end
71
+
72
+ raise TSM::Error::CommandError, 'Error establishing connection' if ptyval == nil and not success
73
+
74
+ ptyval
75
+ ensure
76
+ close if not closed?
77
+ end
78
+
79
+ def close
80
+ notify('close',{'closed' => closed?})
81
+ if @pty
82
+ @pty[:r].close unless @pty[:r].closed?
83
+ @pty[:w].close unless @pty[:w].closed?
84
+ end
85
+ @pty = nil
86
+ end
87
+
88
+ def closed?
89
+ return true unless @pty
90
+ return true if @pty[:r].closed? or @pty[:w].closed?
91
+
92
+ begin
93
+ Process.kill(0,@pty[:pid])
94
+ rescue Errno::ESRCH
95
+ return true
96
+ end
97
+
98
+ false
99
+ end
100
+
101
+ def command(cmd,&block)
102
+
103
+ return self.open { |t| t.command(cmd,&block) } if closed?
104
+
105
+ cmd.to_s.strip!
106
+
107
+ notify('execute',{'command' => cmd,'closed'=>closed?})
108
+
109
+ start = Time.now()
110
+
111
+ res = nil
112
+ if cmd =~ /quit/ and not @@quit
113
+ res = TSM::Response.new(FAKE_QUIT)
114
+ else
115
+ res = TSM::Response.new
116
+ @pty[:w].puts(cmd.to_s)
117
+ @pty[:r]._dsm_response(@prompt) do |line| parse(res,line,&block) end
118
+ end
119
+
120
+ duration = Time.now() - start
121
+
122
+ notify('complete',{'duration' => "%.2f" % duration,'rows' => res[:count] || 0})
123
+
124
+ res
125
+ ensure
126
+ close if cmd =~ /quit/ and @@quit
127
+ end
128
+
129
+ def server
130
+ raise TSM::Error::PTYError, "No connection was established" unless @header
131
+
132
+ svr = {}
133
+ @header.each {|l|
134
+ l = l.chomp
135
+ l.strip!
136
+ next if l.empty?
137
+
138
+ if (m=/Command Line ([\S^-]+)([\s\S]+) - Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
139
+ svr[:interface] = {:version => m[3],:release => m[4],:level =>m[5],:type => m[1].downcase}
140
+ elsif (m=/Session established with server (\w+): (\S+)/i.match(l))
141
+ svr[:host] = {:hostname => m[1],:arch => m[2]}
142
+ elsif (m=/Server Version (\d+), Release (\d+), Level (\d(.\d))/i.match(l))
143
+ svr[:version] = {:version => m[1],:release => m[2],:level => m[3]}
144
+ elsif (m=/Server date.time: ([0-9\/]+) ([0-9:]+)(\s+)Last access: ([0-9\/]+) ([0-9:]+)/i.match(l))
145
+ svr[:established] = Time.local(*(ParseDate.parsedate("#{m[1]} #{m[2]}")))
146
+ svr[:access] = Time.local(*(ParseDate.parsedate("#{m[4]} #{m[5]}")))
147
+ end
148
+ }
149
+ svr[:time] = @established if @established
150
+
151
+ svr
152
+ end
153
+
154
+ def self.path=(path)
155
+ @@path = [path].flatten
156
+ end
157
+ def self.path
158
+ @@path
159
+ end
160
+
161
+ protected
162
+
163
+ def notify(st,opts = {})
164
+ pid = @pty[:pid] if @pty
165
+ changed and notify_observers(opts.merge({'status' => st,'time' => Time.now,'pid' => pid || 0}))
166
+ end
167
+
168
+ def find(name,path=@@path)
169
+ path ||= ['/usr/local/bin']
170
+ (ENV['PATH'].split(File::PATH_SEPARATOR) + path).each { |p|
171
+ if File.executable?(f=File.join(p,name))
172
+ return f
173
+ end
174
+ }
175
+ end
176
+
177
+ def parse(res,chrs,&block)
178
+ chrs.each { |line|
179
+ line = line.chomp
180
+ line.strip!
181
+
182
+ if (m = res.message?(line))
183
+ res.messages(m[1],m[5]) and next
184
+ end
185
+
186
+ res[:command] = line and next unless res[:command]
187
+
188
+ if line.empty? and not @tmp.empty?
189
+ res.rows(@tmp)
190
+
191
+ yield @tmp if block_given?
192
+
193
+ @tmp.clear
194
+
195
+ next
196
+ end if @tmp
197
+
198
+ if (sp = line.index(":")) != nil
199
+ @tmp = {} unless @tmp
200
+ @tmp[line[0...sp].downcase.tr(" ","_").to_sym] = line[(sp+1)..-1].lstrip
201
+ end
202
+ }
203
+
204
+ res
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,49 @@
1
+
2
+ class IO
3
+ def _dsm_prompt(pat,timeout=9999999)
4
+ buf = ''
5
+ pat = [pat] unless pat.respond_to?('each')
6
+ result = nil
7
+ while true
8
+ break if IO.select([self],nil,nil,timeout).nil?
9
+ begin
10
+ buf << getc.chr
11
+ rescue Errno::EIO
12
+ result = [buf,'']
13
+ end
14
+
15
+ pat.each { |p|
16
+ if mat=p.match(buf)
17
+ result = [buf,*mat.to_a] and break
18
+ end
19
+ }
20
+ break if result
21
+ end
22
+ result
23
+ end
24
+
25
+ def _dsm_response(str,timeout=9999999,&block)
26
+ tbuf = '' and buf = ''
27
+ mode = :chr
28
+ while true
29
+ result = nil and break if IO.select([self],nil,nil,timeout).nil?
30
+ if mode == :chr
31
+ begin
32
+ tbuf << getc.chr
33
+ rescue Errno::EIO
34
+ result = result.to_s
35
+ result << tbuf and break
36
+ end
37
+ buf << tbuf and tbuf = '' and mode = :line if str[0...tbuf.size] != tbuf
38
+ buf << tbuf and result = buf and break if tbuf == str
39
+ buf << tbuf if tbuf[-($/.size)..-1] == $/
40
+ next
41
+ end
42
+
43
+ buf << gets and mode = :chr
44
+ yield buf
45
+ buf = ''
46
+ end
47
+ result
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ module TSM
2
+ class Error < RuntimeError
3
+ class FatalError < Error; end
4
+ class CommandError < Error; end
5
+ class PTYError < Error; end
6
+ class NoData < Error; end
7
+ end
8
+ end
@@ -0,0 +1,45 @@
1
+ module TSM
2
+ class Response < Hash
3
+ def initialize(hsh = {})
4
+ {:messages => [],:rows => [],:count => 0,:command => nil}.each do |k,v|
5
+ if hsh.has_key?(k)
6
+ self.store(k,hsh[k])
7
+ else
8
+ self.store(k,v)
9
+ end
10
+ end
11
+
12
+ self
13
+ end
14
+
15
+ def message?(str)
16
+ if (m = /(([ABDEI][DEKNC][ERSDNOUIP])(\d+)([IESWK])) (.*)/.match(str))
17
+ return m
18
+ end
19
+
20
+ false
21
+ end
22
+
23
+ def messages(id,msg)
24
+ self[:messages].push({:id=>id,:msg=>msg})
25
+ end
26
+
27
+ def rows(val)
28
+ self[:rows].push(val.dup)
29
+ self[:count] += 1
30
+ end
31
+
32
+ def validate(err=TSM::Error::FatalError)
33
+ self[:messages].each do |msg|
34
+ raise TSM::Error::NoData, msg[:msg] if msg[:id] =~ /ANR2034E/i
35
+ raise err, msg[:msg] if msg[:id][-1..-1] =~ /^[ES]$/i
36
+ end
37
+
38
+ true
39
+ end
40
+
41
+ def to_hash
42
+ Hash.new().replace(self)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'tsm'
2
+
3
+ require 'yaml'
4
+ require 'test/unit'
5
+
6
+ USER = YAML.load(File.open(File.expand_path("~/auth.yaml")))
7
+
8
+ class TestTsm < Test::Unit::TestCase
9
+
10
+ def test_simple
11
+ assert_raise(TSM::Error::CommandError) {
12
+ TSM::Dsmadmc.new(USER['user'],'pass') { |t| }
13
+ }
14
+
15
+ res = TSM::Dsmadmc.new(USER['user'],USER['pass']) { |t|
16
+ assert_kind_of(Hash,t.server)
17
+ assert_kind_of(TSM::Response,t.command('q pr'))
18
+ }
19
+
20
+ assert_kind_of(TSM::Dsmadmc,res)
21
+
22
+ assert_kind_of(TSM::Response,TSM::Dsmadmc.command(USER['user'],USER['pass'],'q pr'))
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: tsm-command
5
+ version: !ruby/object:Gem::Version
6
+ version: "1.2"
7
+ date: 2006-11-07 00:00:00 -05:00
8
+ summary: Wrapper for TSM client and administrative command interfaces
9
+ require_paths:
10
+ - lib
11
+ email: s.e.collison@gmail.com
12
+ homepage: http://tsm-command.rubyforge.org/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: tsm
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: "true"
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Sarah Collison
31
+ files:
32
+ - tests/ts_tsm.rb
33
+ - lib/tsm
34
+ - lib/tsm.rb
35
+ - lib/tsm/response.rb
36
+ - lib/tsm/dsm.rb
37
+ - lib/tsm/dsmadmc.rb
38
+ - lib/tsm/error.rb
39
+ - lib/tsm/dsmio.rb
40
+ - README
41
+ test_files:
42
+ - tests/ts_tsm.rb
43
+ rdoc_options: []
44
+
45
+ extra_rdoc_files:
46
+ - README
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ requirements: []
52
+
53
+ dependencies: []
54
+