moob 0.1.1 → 0.2.0

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/bin/moob CHANGED
@@ -5,45 +5,95 @@ require 'moob'
5
5
 
6
6
  options = {
7
7
  :action => :jnlp,
8
- :type => :idrac6,
9
- :machines => []
8
+ :type => :auto,
9
+ :machines => [],
10
+ :password => ENV['password']
10
11
  }
11
12
 
12
13
  OptionParser.new do |opts|
13
- opts.on '-l', '--list',
14
- 'List LOM types' do
15
- Moob::TYPES.each do |k, v|
16
- puts "#{k} (#{v.name})"
17
- end
18
- exit 0
19
- end
20
14
  opts.on '-t', '--type t',
21
- 'LOM type (idrac6 by default)' do |t|
15
+ 'LOM type, \'auto\' for autodetection, \'list\' to list',
16
+ 'Defaults to auto' do |t|
17
+ if t == 'list'
18
+ Moob::TYPES.each do |sym, klass|
19
+ puts "#{sym} (#{klass.name})"
20
+ end
21
+ exit 0
22
+ else
22
23
  options[:type] = t.to_sym
24
+ end
25
+ end
26
+
27
+ opts.on '-a', '--action t',
28
+ 'Action to perform, \'list\' to list',
29
+ 'Defaults to jnlp' do |a|
30
+ if a == 'list'
31
+ Moob::TYPES.each do |sym, klass|
32
+ if klass.actions
33
+ puts "#{sym}:"
34
+ klass.actions.each do |name, descr|
35
+ puts " #{name}: #{descr}"
36
+ end
37
+ else
38
+ puts "#{sym} doesn't have any actions"
39
+ end
40
+ end
41
+ exit 0
42
+ else
43
+ options[:action] = a.to_sym
44
+ end
23
45
  end
46
+
24
47
  opts.on '-u', '--username u',
25
- 'LOM username' do |u|
26
- options[:username] = u
48
+ 'LOM username',
49
+ 'Defaults to the model\'s default if known' do |u|
50
+ options[:username] = u
27
51
  end
52
+
28
53
  opts.on '-p', '--password p',
29
- 'LOM password' do |p|
30
- options[:password] = p
54
+ 'LOM password',
55
+ 'Defaults to the model\'s default if known',
56
+ 'Use the environment variable PASSWORD instead!' do |p|
57
+ options[:password] = p
31
58
  end
32
59
 
33
60
  opts.on '-m', '--machines a,b,c', Array,
34
- 'Comma-separated list of machines' do |m|
35
- options[:machines] = m
61
+ 'Comma-separated list of LOM hostnames' do |m|
62
+ options[:machines] = m
63
+ end
64
+
65
+ opts.on '-v', '--verbose' do
66
+ $VERBOSE = true
36
67
  end
37
68
  end.parse!
38
69
 
39
70
  if options[:machines].empty?
40
- $stderr.puts 'No machines selected.'
71
+ $stderr.puts 'No LOMs selected.'
41
72
  exit 1
42
73
  end
43
74
 
44
- options[:machines].each do |h|
45
- case options[:action]
46
- when :jnlp
47
- Moob.start_jnlp options[:type], h, options
75
+ begin
76
+ options[:machines].each do |h|
77
+ lom = Moob.lom options[:type], h, options
78
+
79
+ puts "Trying to authenticate to #{h}..." if $VERBOSE
80
+ lom.authenticate
81
+ puts "Authenticated on #{h}." if $VERBOSE
82
+
83
+ case options[:action]
84
+ when :jnlp
85
+ Moob.start_jnlp lom
86
+ else
87
+ res = lom.send options[:action]
88
+ puts res if res
89
+ end
48
90
  end
91
+ puts 'There might be a delay before Java Web Start shows up...' if options[:action] == :jnlp
92
+
93
+ rescue Exception => e
94
+ $stderr.puts "\033[31mFailure (#{e.class}): #{e}\033[0m\n\n" \
95
+ "Backtrace to provide in any support request:\033[2;34m"
96
+ $stderr.puts e.backtrace
97
+ $stderr.puts "\033[0m"
98
+ exit 1
49
99
  end
@@ -1,9 +1,14 @@
1
- require 'socket'
2
- require 'cgi'
3
- require 'patron'
4
-
5
1
  module Moob
6
- VERSION = [0,1,1]
2
+ VERSION = [0,2,0]
3
+
4
+ class ResponseError < Exception
5
+ def initialize response
6
+ @response = response
7
+ end
8
+ def to_s
9
+ "#{@response.url} failed (status #{@response.status})"
10
+ end
11
+ end
7
12
 
8
13
  autoload :BaseLom, 'moob/baselom.rb'
9
14
  autoload :Idrac6, 'moob/idrac6.rb'
@@ -16,33 +21,36 @@ module Moob
16
21
  :sun => SunILom
17
22
  }
18
23
 
19
- class ResponseError < Exception
20
- def initialize response
21
- @response = response
22
- end
23
- def to_s
24
- "#{@response.url} failed (status #{@response.status})"
24
+ def self.lom type, hostname, options = {}
25
+ case type
26
+ when :auto
27
+ TYPES.find do |sym, klass|
28
+ puts "Trying type #{sym}..." if $VERBOSE
29
+ lom = klass.new hostname, options
30
+ if lom.detect
31
+ puts "Type #{sym} detected." if $VERBOSE
32
+ return lom
33
+ end
34
+ false
35
+ end
36
+ raise RuntimeError.new "Couldn't detect a known LOM type"
37
+ else
38
+ return TYPES[type].new hostname, options
25
39
  end
26
40
  end
27
41
 
28
- def self.start_jnlp type, hostname, options = {}
29
-
30
- lom = TYPES[type].new hostname, options
31
- jnlp = lom.authenticate.jnlp
42
+ def self.start_jnlp lom
43
+ jnlp = lom.jnlp
32
44
 
33
45
  unless jnlp[/<\/jnlp>/]
34
46
  raise RuntimeError.new "Invalid JNLP file (\"#{jnlp}\")"
35
47
  end
36
48
 
37
- filepath = "/tmp/#{hostname}.jnlp"
49
+ filepath = "/tmp/#{lom.hostname}.jnlp"
38
50
  File.open filepath, 'w' do |f|
39
51
  f.write jnlp
40
52
  end
41
53
 
42
- system "javaws #{filepath}"
43
- end
44
-
45
- def self.cmdline
46
- start_jnlp :megatrends, 'brenda.lom.sto.spotify.net'
54
+ raise Exception.new "javaws failed" unless system "javaws #{filepath} &"
47
55
  end
48
56
  end
@@ -1,5 +1,10 @@
1
+ require 'socket'
2
+ require 'cgi'
3
+ require 'patron'
4
+ require 'set'
5
+
1
6
  class Moob::BaseLom
2
- @@name = 'Unknown'
7
+ @name = "Unknown"
3
8
 
4
9
  def initialize hostname, options = {}
5
10
  @hostname = hostname
@@ -13,22 +18,24 @@ class Moob::BaseLom
13
18
  @session.connect_timeout = 10_000
14
19
  @session.timeout = 10_000
15
20
  @session.insecure = true
16
- @session.ignore_content_length = true
17
21
  end
18
22
 
19
- def jnlp
20
- raise new NoMethodError
23
+ attr_reader :hostname, :username
24
+
25
+ def detect
26
+ false
21
27
  end
22
28
 
23
29
  def self.name
24
- @@name
30
+ @name
31
+ end
32
+
33
+ def self.actions
34
+ @actions
25
35
  end
26
36
 
27
- protected
28
- # Get rid of security checks.
29
- # Might break some features,
30
- # please let me know if that's the case!
31
- def desecurize_jnlp jnlp
32
- jnlp.sub /<security>.*<\/security>/m, ''
37
+ def self.action sym, descr
38
+ @actions ||= {}
39
+ @actions[sym] = descr
33
40
  end
34
- end
41
+ end
@@ -1,10 +1,75 @@
1
- class Moob::Idrac6 < Moob::BaseLom
2
- @@name = 'Dell iDrac 6'
1
+ module Moob
2
+ class Idrac6 < BaseLom
3
+ @name = 'Dell iDrac 6'
4
+
5
+ INFO_FIELDS = %w{
6
+ sysDesc
7
+ biosVer
8
+ svcTag
9
+ expSvcCode
10
+ hostName
11
+ osName
12
+ osVersion
13
+ sysRev
14
+ LCCfwVersion
15
+ recoveryAction
16
+ initCountdown
17
+ presentCountdown
18
+ datetime
19
+ fwVersion
20
+ fwUpdated
21
+ hwVersion
22
+ macAddr
23
+ v4Enabled
24
+ v4IPAddr
25
+ v4Gateway
26
+ v4NetMask
27
+ v4DHCPEnabled
28
+ v4DHCPServers
29
+ v4DNS1
30
+ v4DNS2
31
+ v6Enabled
32
+ v6Addr
33
+ v6Gateway
34
+ v6DHCPEnabled
35
+ v6LinkLocal
36
+ v6Prefix
37
+ v6SiteLocal
38
+ v6SiteLocal3
39
+ v6SiteLocal4
40
+ v6SiteLocal5
41
+ v6SiteLocal6
42
+ v6SiteLocal7
43
+ v6SiteLocal8
44
+ v6SiteLocal9
45
+ v6SiteLocal10
46
+ v6SiteLocal11
47
+ v6SiteLocal12
48
+ v6SiteLocal13
49
+ v6SiteLocal14
50
+ v6SiteLocal15
51
+ racName
52
+ v6DHCPServers
53
+ v6DNS1
54
+ v6DNS2
55
+ NicEtherMac1
56
+ NicEtherMac2
57
+ NicEtherMac3
58
+ NicEtherMac4
59
+ NiciSCSIMac1
60
+ NiciSCSIMac2
61
+ NiciSCSIMac3
62
+ NiciSCSIMac4
63
+ NicEtherVMac1
64
+ NicEtherVMac2
65
+ NicEtherVMac3
66
+ NicEtherVMac4
67
+ }
3
68
 
4
69
  def initialize hostname, options = {}
5
70
  super hostname, options
6
- @username ||= 'root'
7
- @password ||= 'calvin'
71
+ @username ||= 'root'
72
+ @password ||= 'calvin'
8
73
  end
9
74
 
10
75
  def authenticate
@@ -16,34 +81,96 @@ class Moob::Idrac6 < Moob::BaseLom
16
81
  "user=#{@username}&password=#{@password}"
17
82
  raise ResponseError.new auth unless auth.status == 200
18
83
 
19
- result_match = auth.body.match /<authResult>([^<]+)<\/authResult>/
20
- raise Exception.new 'Cannot find auth result' unless result_match
21
- result_code = result_match[1]
22
- unless result_code == "0"
23
- raise Exception.new "Auth failed with: \"#{auth.body}\""
24
- end
84
+ auth.body =~ /<authResult>([^<]+)<\/authResult>/
85
+ raise Exception.new 'Cannot find auth result' unless $&
86
+ raise Exception.new "Auth failed with: \"#{auth.body}\"" unless $1 == "0"
25
87
  return self
26
88
  end
27
89
 
90
+ def detect
91
+ begin
92
+ home = @session.get 'login.html'
93
+ home.body =~ /Integrated Dell Remote Access Controller 6/
94
+ rescue
95
+ false
96
+ end
97
+ end
98
+
99
+ action :jnlp, 'Remote control'
28
100
  def jnlp
29
101
  idx = @session.get 'index.html'
30
102
  raise ResponseError.new idx unless idx.status == 200
31
103
 
32
- name_match = idx.body.match /var DnsName += +"([^"]+)"/
33
- raise Exception.new 'Couldn\'t find the DNS name' unless name_match
34
- dns_name = name_match[1]
104
+ idx.body =~ /var DnsName += +"([^"]+)"/
105
+ raise Exception.new "Couldn't find the DNS name" unless $&
106
+ dns_name = $1
35
107
 
36
- sys_match = idx.body.match /var sysNameStr += +"([^"]+)"/
37
- raise Exception.new 'Couldn\'t find the system name' unless name_match
38
- sys_name = sys_match[1] # eg PowerEdge R610
108
+ idx.body =~ /var sysNameStr += +"([^"]+)"/
109
+ raise Exception.new "Couldn't find the system name" unless $&
110
+ sys_name = $1 # eg PowerEdge R610
39
111
 
40
- # eg escaped "idrac-D4MHZ4J, PowerEdge R610, User:root"
112
+ # eg escaped "idrac-A1BCD2E, PowerEdge R610, User:root"
41
113
  title = CGI::escape "#{dns_name}, #{sys_name}, User:#{@username}"
42
- path = "viewer.jnlp(#{@hostname}@0@#{title}@#{Time.now.to_i * 1000})"
43
114
 
44
- viewer = @session.get path
115
+ viewer = @session.get "viewer.jnlp(#{@hostname}@0@#{title}@#{Time.now.to_i * 1000})"
45
116
  raise ResponseError.new viewer unless viewer.status == 200
46
117
 
47
- return desecurize_jnlp viewer.body
118
+ return viewer.body
119
+ end
120
+
121
+ def power_action action
122
+ req = @session.post "data?set=pwState:#{action}", {}
123
+ raise ResponseError.new req unless req.status == 200
124
+ raise Exception.new 'The answer looks wrong' unless req.body =~ /<status>ok<\/status>/
125
+ return nil
126
+ end
127
+
128
+ action :poff, 'Power Off System'
129
+ action :pon, 'Power On System'
130
+ action :pcycle, 'Power Cycle System (cold boot)'
131
+ action :reboot, 'Reset System (warm boot)'
132
+ action :nmi, 'NMI (Non-Masking Interrupt)'
133
+ action :shudown, 'Graceful Shutdown'
134
+
135
+ def poff; power_action 0; end
136
+ def pon; power_action 1; end
137
+ def pcycle; power_action 2; end
138
+ def reboot; power_action 3; end
139
+ def nmi; power_action 4; end
140
+ def shutdown; power_action 5; end
141
+
142
+ def get_infos keys
143
+ infos = @session.post "data?get=#{keys.join(',')}", {}
144
+
145
+ raise ResponseError.new infos unless infos.status == 200
146
+ raise Exception.new "The status isn't OK" unless infos.body =~ /<status>ok<\/status>/
147
+
148
+ return Hash[keys.collect do |k|
149
+ if infos.body =~ /<#{k}>(.*?)<\/#{k}>/
150
+ [k, $1]
151
+ else
152
+ [k, nil]
153
+ end
154
+ end]
155
+ end
156
+
157
+ action :pstatus, 'Power status'
158
+ def pstatus
159
+ case get_infos(['pwState'])['pwState']
160
+ when '0'
161
+ return :off
162
+ when '1'
163
+ return :on
164
+ else
165
+ return nil
166
+ end
167
+ end
168
+
169
+ action :infos, 'Get system information'
170
+ def infos
171
+ return get_infos(INFO_FIELDS).collect do |k,v|
172
+ "#{k}: #{v}"
173
+ end.join "\n"
48
174
  end
49
- end
175
+ end
176
+ end
@@ -1,12 +1,13 @@
1
- class Moob::Megatrends < Moob::BaseLom
2
- @@name = 'American Megatrends'
1
+ module Moob
2
+ class Megatrends < BaseLom
3
+ @name = 'American Megatrends'
3
4
 
4
5
  def initialize hostname, options = {}
5
6
  super hostname, options
6
7
  @username ||= 'root'
7
8
  @password ||= 'superuser'
8
9
  begin
9
- @ip = Socket.getaddrinfo(hostname, nil)[0][2]
10
+ @ip = Socket.getaddrinfo(hostname, nil)[0][3]
10
11
  rescue
11
12
  raise Exception.new "Couldn't resolve \"#{hostname}\""
12
13
  end
@@ -19,19 +20,65 @@ class Moob::Megatrends < Moob::BaseLom
19
20
 
20
21
  raise ResponseError.new auth unless auth.status == 200
21
22
 
22
- cookie_match = auth.body.match /'SESSION_COOKIE' *: *'([^']+)'/
23
- unless cookie_match
24
- raise Exception.new "Couldn't find auth cookie in \"#{auth.body}\""
25
- end
23
+ auth.body =~ /'SESSION_COOKIE' *: *'([^']+)'/
24
+ raise Exception.new "Couldn't find auth cookie in \"#{auth.body}\"" unless $&
26
25
 
27
- @cookie = "test=1; path=/; SessionCookie=#{cookie_match[1]}"
26
+ @cookie = "test=1; path=/; SessionCookie=#{$1}"
28
27
  return self
29
28
  end
30
29
 
30
+ def detect
31
+ begin
32
+ home = @session.get 'page/login.html'
33
+ home.body =~ /\.\.\/res\/banner_right.png/
34
+ rescue
35
+ false
36
+ end
37
+ end
38
+
39
+ action :jnlp, 'Remote control'
31
40
  def jnlp
41
+ @session.ignore_content_length = true
32
42
  viewer = @session.get 'Java/jviewer.jnlp', { 'Cookie' => @cookie }
33
43
  raise ResponseError.new viewer unless viewer.status == 200
34
44
 
35
- return desecurize_jnlp viewer.body
45
+ return viewer.body
46
+ end
47
+
48
+ def power_action action
49
+ req = @session.post 'rpc/hostctl.asp',
50
+ { 'WEBVAR_POWER_CMD' => action },
51
+ { 'Cookie' => @cookie }
52
+ raise ResponseError.new req unless req.status == 200
53
+ unless req.body =~ /WEBVAR_STRUCTNAME_HL_POWERSTATUS/
54
+ raise Exception.new 'The answer looks wrong'
55
+ end
56
+ return nil
36
57
  end
58
+
59
+ action :poff, 'Power Off'
60
+ action :pon, 'Power On'
61
+ action :pcycle, 'Power Cycle'
62
+ action :preset, 'Power Reset'
63
+ action :soft_poff, 'Soft Power Off'
64
+ def poff; power_action 0; end
65
+ def pon; power_action 1; end
66
+ def pcycle; power_action 2; end
67
+ def preset; power_action 3; end
68
+ def soft_poff; power_action 5; end
69
+
70
+ action :pstatus, 'Power status'
71
+ def pstatus
72
+ status = @session.get 'rpc/hoststatus.asp',
73
+ { 'Cookie' => @cookie }
74
+ raise ResponseError.new status unless status.status == 200
75
+ raise Exception.new 'Couldn\'t read the state' unless status.body =~ /'JF_STATE' : (.),/
76
+ case $1
77
+ when '0'
78
+ return :off
79
+ when '1'
80
+ return :on
81
+ end
82
+ end
83
+ end
37
84
  end
@@ -1,5 +1,6 @@
1
- class Moob::SunILom < Moob::BaseLom
2
- @@name = 'Sun Integrated Lights Out Manager'
1
+ module Moob
2
+ class SunILom < BaseLom
3
+ @name = 'Sun Integrated Lights Out Manager'
3
4
 
4
5
  def initialize hostname, options = {}
5
6
  super hostname, options
@@ -15,19 +16,38 @@ class Moob::SunILom < Moob::BaseLom
15
16
 
16
17
  raise ResponseError.new auth unless auth.status == 200
17
18
 
18
- cookie_match = auth.body.match /SetWebSessionString\("([^"]+)","([^"]+)"\);/
19
- unless cookie_match
20
- raise Exception.new "Couldn't find session cookie in \"#{auth.body}\""
19
+ if auth.body =~ /\/iPages\/i_login.asp\?msg=([^"])+"/
20
+ error = "code #{$1}"
21
+ error_page = @session.get "iPages/i_login.asp?msg=#{$1}"
22
+ if error_page.body =~ /<div class="AlrtErrTxt">(.*?)<\/div>/
23
+ error = "\"#{$1.gsub /<[^>]+>/, ''}\""
24
+ end
25
+
26
+ raise Exception.new "Auth failed (#{error})"
21
27
  end
22
28
 
23
- @cookie = "#{cookie_match[1]}=#{cookie_match[2]}; langsetting=EN"
29
+ auth.body =~ /SetWebSessionString\("([^"]+)","([^"]+)"\);/
30
+ raise Exception.new "Couldn't find session cookie in \"#{auth.body}\"" unless $&
31
+
32
+ @cookie = "#{$1}=#{$2}; langsetting=EN"
24
33
  return self
25
34
  end
26
35
 
36
+ action :jnlp, 'Remote control'
27
37
  def jnlp
28
38
  viewer = @session.get 'cgi-bin/jnlpgenerator-8', { 'Cookie' => @cookie }
29
39
  raise ResponseError.new viewer unless viewer.status == 200
30
40
 
31
41
  return viewer.body
32
42
  end
43
+
44
+ def detect
45
+ begin
46
+ home = @session.get 'iPages/i_login.asp'
47
+ home.body =~ /Sun\(TM\) Sun Integrated Lights Out Manager/
48
+ rescue
49
+ false
50
+ end
51
+ end
52
+ end
33
53
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moob
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 23
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 1
8
- - 1
9
- version: 0.1.1
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Pierre Carrier
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2011-07-25 00:00:00 +02:00
18
+ date: 2011-07-30 00:00:00 +02:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -25,11 +26,12 @@ dependencies:
25
26
  requirements:
26
27
  - - ~>
27
28
  - !ruby/object:Gem::Version
29
+ hash: 19
28
30
  segments:
29
31
  - 0
30
32
  - 4
31
- - 13
32
- version: 0.4.13
33
+ - 14
34
+ version: 0.4.14
33
35
  type: :runtime
34
36
  version_requirements: *id001
35
37
  description: Control systems using Web-based out-of-band managers without a browser
@@ -51,8 +53,8 @@ files:
51
53
  - COPYING
52
54
  has_rdoc: true
53
55
  homepage: http://github.com/pcarrier/moob
54
- licenses: []
55
-
56
+ licenses:
57
+ - Public-domain-like
56
58
  post_install_message:
57
59
  rdoc_options: []
58
60
 
@@ -63,6 +65,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
65
  requirements:
64
66
  - - ">="
65
67
  - !ruby/object:Gem::Version
68
+ hash: 3
66
69
  segments:
67
70
  - 0
68
71
  version: "0"
@@ -71,6 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
74
  requirements:
72
75
  - - ">="
73
76
  - !ruby/object:Gem::Version
77
+ hash: 31
74
78
  segments:
75
79
  - 1
76
80
  - 2
@@ -79,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
83
  requirements: []
80
84
 
81
85
  rubyforge_project: moob
82
- rubygems_version: 1.3.7
86
+ rubygems_version: 1.6.2
83
87
  signing_key:
84
88
  specification_version: 3
85
89
  summary: Manage Out-Of-Band!