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 +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
|
+
|