collins_client 0.2.7
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/Gemfile +15 -0
- data/Gemfile.lock +50 -0
- data/README.md +46 -0
- data/Rakefile +66 -0
- data/VERSION +1 -0
- data/collins_client.gemspec +73 -0
- data/lib/collins/address.rb +74 -0
- data/lib/collins/api.rb +119 -0
- data/lib/collins/api/admin.rb +19 -0
- data/lib/collins/api/asset.rb +184 -0
- data/lib/collins/api/asset_state.rb +85 -0
- data/lib/collins/api/attributes.rb +76 -0
- data/lib/collins/api/ip_address.rb +87 -0
- data/lib/collins/api/logging.rb +137 -0
- data/lib/collins/api/management.rb +84 -0
- data/lib/collins/api/tag.rb +46 -0
- data/lib/collins/api/util.rb +28 -0
- data/lib/collins/api/util/errors.rb +45 -0
- data/lib/collins/api/util/parameters.rb +44 -0
- data/lib/collins/api/util/requests.rb +136 -0
- data/lib/collins/api/util/responses.rb +46 -0
- data/lib/collins/asset.rb +311 -0
- data/lib/collins/asset_client.rb +57 -0
- data/lib/collins/client.rb +100 -0
- data/lib/collins/errors.rb +56 -0
- data/lib/collins/ipmi.rb +41 -0
- data/lib/collins/logging.rb +33 -0
- data/lib/collins/monkeypatch.rb +24 -0
- data/lib/collins/option.rb +220 -0
- data/lib/collins/power.rb +99 -0
- data/lib/collins/profile.rb +73 -0
- data/lib/collins/simple_callback.rb +141 -0
- data/lib/collins/state.rb +50 -0
- data/lib/collins/util.rb +145 -0
- data/lib/collins_client.rb +7 -0
- metadata +100 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module Collins
|
2
|
+
|
3
|
+
class CollinsError < StandardError; end
|
4
|
+
class ExpectationFailedError < CollinsError; end
|
5
|
+
class UnexpectedResponseError < CollinsError; end
|
6
|
+
class RequestError < CollinsError
|
7
|
+
attr_accessor :code, :uri
|
8
|
+
def initialize message, code
|
9
|
+
super(message)
|
10
|
+
@code = code.to_i
|
11
|
+
end
|
12
|
+
def description verbose = false
|
13
|
+
<<-D
|
14
|
+
#{message}
|
15
|
+
Response Code: #{code}
|
16
|
+
URI: #{uri}
|
17
|
+
D
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class RichRequestError < RequestError
|
22
|
+
attr_accessor :class_of, :remote_description, :remote_message, :stacktrace
|
23
|
+
def initialize message, code, description, details = {}
|
24
|
+
super(message, code)
|
25
|
+
@code = code
|
26
|
+
@remote_description = description
|
27
|
+
@class_of = details["classOf"]
|
28
|
+
@remote_message = details["message"]
|
29
|
+
@stacktrace = details["stackTrace"]
|
30
|
+
end
|
31
|
+
def get_remote_stacktrace verbose
|
32
|
+
if verbose then
|
33
|
+
stacktrace
|
34
|
+
else
|
35
|
+
"Suppressed"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
def description verbose = false
|
39
|
+
<<-D
|
40
|
+
#{message}
|
41
|
+
Response Code: #{code}
|
42
|
+
URI: #{uri}
|
43
|
+
Remote Description: #{remote_description}
|
44
|
+
Remote Exception Class: #{class_of}
|
45
|
+
Remote Message:
|
46
|
+
#{remote_message}
|
47
|
+
|
48
|
+
Remote Backtrace:
|
49
|
+
#{get_remote_stacktrace(verbose)}
|
50
|
+
D
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class AuthenticationError < CollinsError; end
|
55
|
+
|
56
|
+
end
|
data/lib/collins/ipmi.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Collins
|
2
|
+
|
3
|
+
class Ipmi
|
4
|
+
|
5
|
+
include Collins::Util
|
6
|
+
|
7
|
+
attr_accessor :address, :asset_id, :gateway, :id, :netmask, :password, :username
|
8
|
+
|
9
|
+
def self.from_json json
|
10
|
+
Collins::Ipmi.new json
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize opts = {}
|
14
|
+
hash = symbolize_hash(opts).inject({}) do |result, (k,v)|
|
15
|
+
key = k.to_s.downcase.sub(/^ipmi_/, "").to_sym
|
16
|
+
result[key] = v
|
17
|
+
result
|
18
|
+
end
|
19
|
+
@address = hash[:address].to_s
|
20
|
+
@asset_id = hash[:asset_id].to_s.to_i
|
21
|
+
@gateway = hash[:gateway].to_s
|
22
|
+
@id = hash[:id].to_s.to_i
|
23
|
+
@netmask = hash[:netmask].to_s
|
24
|
+
@password = hash[:password].to_s
|
25
|
+
@username = hash[:username].to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
@id == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
if empty? then
|
34
|
+
"Ipmi(None)"
|
35
|
+
else
|
36
|
+
"Ipmi(id = #{id}, asset_id = #{asset_id}, address = #{address}, gateway = #{gateway}, netmask = #{netmask}, username = #{username}, password = #{password})"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'collins/monkeypatch'
|
2
|
+
require 'collins/option'
|
3
|
+
|
4
|
+
module Collins; module Util
|
5
|
+
|
6
|
+
module Logging
|
7
|
+
|
8
|
+
DEFAULT_LOG_FORMAT = "%Y-%m-%d %H:%M:%S.%L"
|
9
|
+
|
10
|
+
def get_logger options = {}
|
11
|
+
return options[:logger] if options[:logger]
|
12
|
+
trace = Collins::Option(options[:trace]).get_or_else(false)
|
13
|
+
debug = Collins::Option(options[:debug]).get_or_else(false)
|
14
|
+
progname = Collins::Option(options[:progname] || options[:program]).get_or_else('unknown')
|
15
|
+
logfile = Collins::Option(options[:logfile]).get_or_else(STDOUT)
|
16
|
+
logger = Logger.new(logfile)
|
17
|
+
if trace then
|
18
|
+
logger.level = Logger::TRACE
|
19
|
+
elsif debug then
|
20
|
+
logger.level = Logger::DEBUG
|
21
|
+
else
|
22
|
+
logger.level = Logger::INFO
|
23
|
+
end
|
24
|
+
logger.progname = File.basename(progname)
|
25
|
+
logger.formatter = Proc.new do |severity, datetime, progname, message|
|
26
|
+
date_s = datetime.strftime(Collins::Util::Logging::DEFAULT_LOG_FORMAT)
|
27
|
+
"#{severity} [#{date_s}] #{progname}: #{message}\n"
|
28
|
+
end
|
29
|
+
logger
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end; end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Logger
|
4
|
+
|
5
|
+
module Severity
|
6
|
+
TRACE = -1
|
7
|
+
end
|
8
|
+
|
9
|
+
def trace?; @level <= TRACE; end
|
10
|
+
|
11
|
+
def trace(progname = nil, &block)
|
12
|
+
add(TRACE, nil, progname, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def format_severity severity
|
17
|
+
if severity == TRACE then
|
18
|
+
'TRACE'
|
19
|
+
else
|
20
|
+
SEV_LABEL[severity] || 'ANY'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
module Collins
|
2
|
+
|
3
|
+
# Convenience method for creating an `Option`
|
4
|
+
def self.Option value
|
5
|
+
if value.nil? then
|
6
|
+
::Collins::None()
|
7
|
+
else
|
8
|
+
::Collins::Some(value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
# Convenience method for creating a `None`
|
12
|
+
def self.None
|
13
|
+
::Collins::None.new
|
14
|
+
end
|
15
|
+
# Convenience method for creating a `Some`
|
16
|
+
def self.Some value
|
17
|
+
::Collins::Some.new(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Represents optional values. Instances of `Option` are either an instance of `Some` or `None`
|
21
|
+
# @note This is pretty much a straight rip off of the scala version
|
22
|
+
# @example
|
23
|
+
# name = get_parameter("name")
|
24
|
+
# upper = Option(name).map{|s| s.strip}.filter{|s|s.size > 0}.map{|s|s.upcase}
|
25
|
+
# puts(upper.get_or_else(""))
|
26
|
+
class Option
|
27
|
+
|
28
|
+
# @return [Boolean] True if the value is undefined
|
29
|
+
def empty?
|
30
|
+
raise NotImplementedError.new("empty? not implemented")
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Boolean] True if the value is defined
|
34
|
+
def defined?
|
35
|
+
!empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Object] Value, if defined
|
39
|
+
# @raise [NameError] if value is undefined
|
40
|
+
def get
|
41
|
+
raise NotImplementedError.new("get not implemented")
|
42
|
+
end
|
43
|
+
|
44
|
+
# The value associated with this option, or the default
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# # Raises an exception
|
48
|
+
# Option(nil).get_or_else { raise Exception.new("Stuff") }
|
49
|
+
# # Returns -1
|
50
|
+
# Option("23").map {|i| i.to_i}.filter{|i| i > 25}.get_or_else -1
|
51
|
+
#
|
52
|
+
# @param [Object] default A default value to use if the option value is undefined
|
53
|
+
# @yield [] Provide a default with a block instead of a parameter
|
54
|
+
# @return [Object] If None, default, otherwise the value
|
55
|
+
def get_or_else *default
|
56
|
+
if empty? then
|
57
|
+
if block_given? then
|
58
|
+
yield
|
59
|
+
else
|
60
|
+
default.first
|
61
|
+
end
|
62
|
+
else
|
63
|
+
get
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return this `Option` if non-empty, otherwise return the result of evaluating the default
|
68
|
+
# @example
|
69
|
+
# Option(nil).or_else { "foo" } == Some("foo")
|
70
|
+
# @return [Option<Object>]
|
71
|
+
def or_else *default
|
72
|
+
if empty? then
|
73
|
+
res = if block_given? then
|
74
|
+
yield
|
75
|
+
else
|
76
|
+
default.first
|
77
|
+
end
|
78
|
+
if res.is_a?(Option) then
|
79
|
+
res
|
80
|
+
else
|
81
|
+
::Collins::Option(res)
|
82
|
+
end
|
83
|
+
else
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return true if non-empty and predicate is true for the value
|
89
|
+
# @return [Boolean] test passed
|
90
|
+
def exists? &predicate
|
91
|
+
!empty? && predicate.call(get)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Apply the block specified to the value if non-empty
|
95
|
+
# @return [NilClass]
|
96
|
+
def foreach &f
|
97
|
+
if self.defined? then
|
98
|
+
f.call(get)
|
99
|
+
end
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# If the option value is defined, apply the specified block to that value
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# Option("15").map{|i| i.to_i}.get == 15
|
107
|
+
#
|
108
|
+
# @yieldparam [Object] block The current value
|
109
|
+
# @yieldreturn [Object] The new value
|
110
|
+
# @return [Option<Object>] Optional value
|
111
|
+
def map &block
|
112
|
+
if empty? then
|
113
|
+
None.new
|
114
|
+
else
|
115
|
+
Some.new(block.call(get))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Same as map, but flatten the results
|
120
|
+
#
|
121
|
+
# This is useful when operating on an object that will return an `Option`.
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# Option(15).flat_map {|i| Option(i).filter{|i2| i2 > 0}} == Some(15)
|
125
|
+
#
|
126
|
+
# @see #map
|
127
|
+
# @return [Option<Object>] Optional value
|
128
|
+
def flat_map &block
|
129
|
+
if empty? then
|
130
|
+
None.new
|
131
|
+
else
|
132
|
+
res = block.call(get)
|
133
|
+
if res.is_a?(Some) then
|
134
|
+
res
|
135
|
+
else
|
136
|
+
Some.new(res)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Convert to `None` if predicate fails
|
142
|
+
#
|
143
|
+
# Returns this option if it is non-empty *and* applying the predicate to this options returns
|
144
|
+
# true. Otherwise return `None`.
|
145
|
+
#
|
146
|
+
# @yieldparam [Object] predicate The current value
|
147
|
+
# @yieldreturn [Boolean] result of testing value
|
148
|
+
# @return [Option<Object>] `None` if predicate fails, or already `None`
|
149
|
+
def filter &predicate
|
150
|
+
if empty? || predicate.call(get) then
|
151
|
+
self
|
152
|
+
else
|
153
|
+
None.new
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Inverse of `filter` operation.
|
158
|
+
#
|
159
|
+
# Returns this option if it is non-empty *and* applying the predicate to this option returns
|
160
|
+
# false. Otherwise return `None`.
|
161
|
+
#
|
162
|
+
# @see #filter
|
163
|
+
# @return [Option<Object>]
|
164
|
+
def filter_not &predicate
|
165
|
+
if empty? || !predicate.call(get) then
|
166
|
+
self
|
167
|
+
else
|
168
|
+
None.new
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Represents a missing value
|
174
|
+
class None < Option
|
175
|
+
# Always true for `None`
|
176
|
+
# @see Option#empty?
|
177
|
+
def empty?
|
178
|
+
true
|
179
|
+
end
|
180
|
+
# Always raises a NameError
|
181
|
+
# @raise [NameError]
|
182
|
+
def get
|
183
|
+
raise NameError.new("None.get")
|
184
|
+
end
|
185
|
+
def eql? other
|
186
|
+
self.class.equal?(other.class)
|
187
|
+
end
|
188
|
+
alias == eql?
|
189
|
+
end
|
190
|
+
|
191
|
+
# Represents a present value
|
192
|
+
#
|
193
|
+
# A number of equality and comparison methods are implemented so that `Some` values are compared
|
194
|
+
# using the value of `x`.
|
195
|
+
class Some < Option
|
196
|
+
def initialize value
|
197
|
+
@x = value
|
198
|
+
end
|
199
|
+
def empty?
|
200
|
+
false
|
201
|
+
end
|
202
|
+
def get
|
203
|
+
x
|
204
|
+
end
|
205
|
+
def eql? other
|
206
|
+
self.class.equal?(other.class) && x.eql?(other.x)
|
207
|
+
end
|
208
|
+
alias == eql?
|
209
|
+
def hash
|
210
|
+
x.hash
|
211
|
+
end
|
212
|
+
def <=>(other)
|
213
|
+
self.class == other.class ?
|
214
|
+
(x <=> other.x) : nil
|
215
|
+
end
|
216
|
+
protected
|
217
|
+
attr_reader :x
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'collins/errors'
|
2
|
+
|
3
|
+
module Collins
|
4
|
+
|
5
|
+
class InvalidPowerStatus < CollinsError; end
|
6
|
+
|
7
|
+
class PowerUnit
|
8
|
+
include Collins::Util
|
9
|
+
|
10
|
+
attr_accessor :key, :value, :type, :label, :position, :is_required, :unique
|
11
|
+
|
12
|
+
def initialize model = {}
|
13
|
+
hash = symbolize_hash(model).inject({}) do |result, (k,v)|
|
14
|
+
result[k.downcase] = v
|
15
|
+
result
|
16
|
+
end
|
17
|
+
@key = hash[:key].to_s
|
18
|
+
@value = hash[:value].to_s
|
19
|
+
@type = hash[:type].to_s
|
20
|
+
@label = hash[:label].to_s
|
21
|
+
@position = hash[:position].to_s.to_i
|
22
|
+
@is_required = hash[:is_required]
|
23
|
+
@unique = hash[:unique]
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_required?
|
27
|
+
@is_required == true
|
28
|
+
end
|
29
|
+
def unique?
|
30
|
+
@unique == true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Power
|
35
|
+
|
36
|
+
include Collins::Util
|
37
|
+
|
38
|
+
attr_accessor :unit_id, :units
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def from_json json
|
42
|
+
return [] if (json.nil? or json.empty?)
|
43
|
+
if not json.is_a?(Array) then
|
44
|
+
json = [json]
|
45
|
+
end
|
46
|
+
json.map { |j| Collins::Power.new j }
|
47
|
+
end
|
48
|
+
def normalize_action action
|
49
|
+
case action.to_s.downcase.to_sym
|
50
|
+
when :off, :poweroff
|
51
|
+
"powerOff"
|
52
|
+
when :on, :poweron
|
53
|
+
"powerOn"
|
54
|
+
when :powersoft
|
55
|
+
"powerSoft"
|
56
|
+
when :soft, :rebootsoft
|
57
|
+
"rebootSoft"
|
58
|
+
when :hard, :reboothard
|
59
|
+
"rebootHard"
|
60
|
+
when :status, :powerstate
|
61
|
+
"powerState"
|
62
|
+
when :verify
|
63
|
+
"verify"
|
64
|
+
when :identify
|
65
|
+
"identify"
|
66
|
+
else
|
67
|
+
raise InvalidPowerStatus.new("#{action} is not a valid power status")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize model = {}
|
73
|
+
hash = symbolize_hash(model).inject({}) do |result, (k,v)|
|
74
|
+
result[k.downcase] = v
|
75
|
+
result
|
76
|
+
end
|
77
|
+
@unit_id = hash[:unit_id].to_s.to_i
|
78
|
+
@units = (hash[:units] || []).map {|u| Collins::PowerUnit.new(u)}
|
79
|
+
end
|
80
|
+
|
81
|
+
def keys
|
82
|
+
units.map{|u| u.key }
|
83
|
+
end
|
84
|
+
def values
|
85
|
+
units.map{|u| u.value}
|
86
|
+
end
|
87
|
+
def types
|
88
|
+
units.map{|u| u.type}
|
89
|
+
end
|
90
|
+
def labels
|
91
|
+
units.map{|u| u.label}
|
92
|
+
end
|
93
|
+
def positions
|
94
|
+
units.map{|u| u.position}
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|