tsm-command 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +33 -0
- data/lib/tsm.rb +5 -0
- data/lib/tsm/dsm.rb +69 -0
- data/lib/tsm/dsmadmc.rb +207 -0
- data/lib/tsm/dsmio.rb +49 -0
- data/lib/tsm/error.rb +8 -0
- data/lib/tsm/response.rb +45 -0
- data/tests/ts_tsm.rb +24 -0
- metadata +54 -0
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
|
data/lib/tsm.rb
ADDED
data/lib/tsm/dsm.rb
ADDED
@@ -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
|
data/lib/tsm/dsmadmc.rb
ADDED
@@ -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
|
data/lib/tsm/dsmio.rb
ADDED
@@ -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
|
data/lib/tsm/error.rb
ADDED
data/lib/tsm/response.rb
ADDED
@@ -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
|
data/tests/ts_tsm.rb
ADDED
@@ -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
|
+
|