softlayer_api 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.textile +36 -0
- data/README.textile +17 -7
- data/examples/account_info.rb +6 -3
- data/examples/account_servers.rb +48 -0
- data/examples/create_ticket.rb +33 -22
- data/examples/open_tickets.rb +14 -19
- data/examples/order_bare_metal_package.rb +154 -0
- data/examples/order_virtual_server.rb +85 -0
- data/examples/ticket_info.rb +13 -14
- data/lib/softlayer/APIParameterFilter.rb +100 -23
- data/lib/softlayer/Account.rb +140 -0
- data/lib/softlayer/BareMetalServer.rb +233 -0
- data/lib/softlayer/BareMetalServerOrder.rb +227 -0
- data/lib/softlayer/BareMetalServerOrder_Package.rb +162 -0
- data/lib/softlayer/Client.rb +54 -9
- data/lib/softlayer/Config.rb +2 -3
- data/lib/softlayer/DynamicAttribute.rb +170 -0
- data/lib/softlayer/ModelBase.rb +141 -0
- data/lib/softlayer/ObjectFilter.rb +61 -21
- data/lib/softlayer/ObjectMaskParser.rb +157 -0
- data/lib/softlayer/ObjectMaskProperty.rb +83 -0
- data/lib/softlayer/ObjectMaskToken.rb +107 -0
- data/lib/softlayer/ObjectMaskTokenizer.rb +88 -0
- data/lib/softlayer/ProductItemCategory.rb +137 -0
- data/lib/softlayer/ProductPackage.rb +196 -0
- data/lib/softlayer/Server.rb +245 -0
- data/lib/softlayer/Service.rb +12 -9
- data/lib/softlayer/Ticket.rb +210 -0
- data/lib/softlayer/VirtualServer.rb +388 -0
- data/lib/softlayer/VirtualServerOrder.rb +263 -0
- data/lib/softlayer/base.rb +9 -9
- data/lib/softlayer/object_mask_helpers.rb +46 -18
- data/lib/softlayer_api.rb +15 -0
- metadata +49 -15
@@ -0,0 +1,157 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
require "softlayer/ObjectMaskTokenizer"
|
24
|
+
require "softlayer/ObjectMaskProperty"
|
25
|
+
|
26
|
+
module SoftLayer
|
27
|
+
class ObjectMaskParserError < RuntimeError
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# A parser that can examine and validate SoftLayer Object Mask strings
|
32
|
+
#
|
33
|
+
# The Object Mask Parser parses Object Mask Strings into ObjectMaskProperty
|
34
|
+
# structures.
|
35
|
+
#
|
36
|
+
# The Object Mask parser allows the Gem to merge Object Mask Strings
|
37
|
+
# to avoid errors from the SoftLayer API server about duplicate properties being
|
38
|
+
# provided when the same property is provided in different Object Masks
|
39
|
+
#
|
40
|
+
class ObjectMaskParser
|
41
|
+
attr_reader :stack
|
42
|
+
|
43
|
+
def initialize()
|
44
|
+
@stack = []
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(mask_string)
|
48
|
+
@tokenizer = ObjectMaskTokenizer.new(mask_string)
|
49
|
+
|
50
|
+
token = @tokenizer.current_token
|
51
|
+
if token.type == :identifier
|
52
|
+
property = parse_property(@tokenizer)
|
53
|
+
elsif token.type == :property_set_start
|
54
|
+
property_set = parse_property_set(@tokenizer)
|
55
|
+
else
|
56
|
+
raise ObjectMaskParserError, "A valid Object mask is a 'mask' or 'filterMask' root property, or a property set containing root properties" + ObjectMaskToken.error_for_unexpected_token(token)
|
57
|
+
end
|
58
|
+
|
59
|
+
recognize_token(@tokenizer, :eos, "Extraneous text after object mask: ")
|
60
|
+
|
61
|
+
if property && (property.name != "mask" && propertyName != "filterMask")
|
62
|
+
raise ObjectMaskParserError, "Object Mask must begin with a 'mask' or 'filterMask' root property"
|
63
|
+
end
|
64
|
+
|
65
|
+
if property_set && property_set.find { |subproperty| subproperty.name != 'mask' && subproperty.name != 'filterMask' }
|
66
|
+
raise ObjectMaskParserError, "A root property set must contain only 'mask' or 'filterMask' root properties"
|
67
|
+
end
|
68
|
+
|
69
|
+
property || property_set
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_property_set(tokenizer)
|
73
|
+
token = recognize_token(tokenizer, :property_set_start, "Expected '[': ")
|
74
|
+
property_sequence = parse_property_sequence(tokenizer)
|
75
|
+
token = recognize_token(tokenizer, :property_set_end, "Expected ']': ")
|
76
|
+
property_sequence
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_property_sequence(tokenizer)
|
80
|
+
first_property = parse_property(tokenizer)
|
81
|
+
|
82
|
+
other_children = []
|
83
|
+
token = tokenizer.current_token
|
84
|
+
if(token.type.equal?(:property_set_separator))
|
85
|
+
# skip the separator
|
86
|
+
tokenizer.next_token
|
87
|
+
|
88
|
+
# find another property
|
89
|
+
other_children = parse_property_sequence(tokenizer)
|
90
|
+
end
|
91
|
+
|
92
|
+
return other_children.unshift(first_property)
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_property (tokenizer)
|
96
|
+
property_name = nil
|
97
|
+
property_type = nil
|
98
|
+
property_children = nil
|
99
|
+
|
100
|
+
property_name = parse_property_name(tokenizer)
|
101
|
+
|
102
|
+
# look for a property type
|
103
|
+
property_type = nil
|
104
|
+
token = tokenizer.current_token
|
105
|
+
if(token.type.equal?(:property_type_start))
|
106
|
+
property_type = parse_property_type(tokenizer)
|
107
|
+
end
|
108
|
+
|
109
|
+
token = tokenizer.current_token
|
110
|
+
if(token.type.equal?(:property_child_separator))
|
111
|
+
property_children = [ parse_property_child(tokenizer) ]
|
112
|
+
elsif (token.type.equal?(:property_set_start))
|
113
|
+
property_children = parse_property_set(tokenizer)
|
114
|
+
end
|
115
|
+
|
116
|
+
new_property = ObjectMaskProperty.new(property_name, property_type)
|
117
|
+
new_property.add_children(property_children) if property_children
|
118
|
+
|
119
|
+
return new_property
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse_property_child(tokenizer)
|
123
|
+
token = recognize_token(tokenizer, :property_child_separator, "Expected a '.': ")
|
124
|
+
parse_property(tokenizer)
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_property_name(tokenizer)
|
128
|
+
token = recognize_token(tokenizer, :identifier, "Expected a valid property type: ") { |token| token.valid_property_name? }
|
129
|
+
return token.value
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_property_type(tokenizer)
|
133
|
+
token = recognize_token(tokenizer, :property_type_start, "Expected '(': ")
|
134
|
+
property_type = parse_property_type_name(tokenizer)
|
135
|
+
token = recognize_token(tokenizer, :property_type_end, "Expected ')': ")
|
136
|
+
return property_type
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_property_type_name(tokenizer)
|
140
|
+
token = recognize_token(tokenizer, :identifier, "Expected a valid property type: ") { |token| token.valid_property_type? }
|
141
|
+
return token.value
|
142
|
+
end
|
143
|
+
|
144
|
+
def recognize_token(tokenizer, expected_type, error_string, &predicate)
|
145
|
+
token = tokenizer.current_token
|
146
|
+
if token.type.equal?(expected_type) && (!predicate || predicate.call(token))
|
147
|
+
tokenizer.next_token
|
148
|
+
else
|
149
|
+
raise ObjectMaskParserError, error_string + ObjectMaskToken.error_for_unexpected_token(token)
|
150
|
+
token = nil;
|
151
|
+
end
|
152
|
+
|
153
|
+
return token
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end # Module SoftLaye
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
module SoftLayer
|
24
|
+
#
|
25
|
+
# A class representing a SoftLayer Object's property as represented
|
26
|
+
# in an Object Mask.
|
27
|
+
#
|
28
|
+
# The Object Mask Parser parses Object Mask Strings into ObjectMaskProperty
|
29
|
+
# structures.
|
30
|
+
#
|
31
|
+
# Another useful property ObjectMaskProperty structures is that they can
|
32
|
+
# can merge with compatible structures to create a new structure which
|
33
|
+
# incorporates the properties of both, but in a streamlined construct
|
34
|
+
#
|
35
|
+
class ObjectMaskProperty
|
36
|
+
attr_reader :name, :type
|
37
|
+
attr_reader :children
|
38
|
+
|
39
|
+
def initialize(name, type = nil)
|
40
|
+
@name = name
|
41
|
+
@type = type
|
42
|
+
@children = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
full_name = @name
|
47
|
+
|
48
|
+
if @type && !@type.empty?
|
49
|
+
full_name += "(#{@type})"
|
50
|
+
end
|
51
|
+
|
52
|
+
if @children.count == 1
|
53
|
+
full_name + ".#{@children[0].to_s}"
|
54
|
+
elsif @children.count > 1
|
55
|
+
full_name + "[#{@children.collect { |child| child.to_s }.join(',')}]"
|
56
|
+
else
|
57
|
+
full_name
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def can_merge_with? (other_property)
|
62
|
+
(self.name == other_property.name) && (self.type == other_property.type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_child(new_child)
|
66
|
+
mergeable_child = @children.find { |existing_child| existing_child.can_merge_with? new_child }
|
67
|
+
if mergeable_child
|
68
|
+
mergeable_child.merge new_child
|
69
|
+
else
|
70
|
+
@children.push new_child
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_children(new_children)
|
75
|
+
new_children.each { |new_child| add_child(new_child) }
|
76
|
+
end
|
77
|
+
|
78
|
+
# DANGER: assumes you've already checked can_merge_with? before calling this routine!
|
79
|
+
def merge(other_property)
|
80
|
+
add_children other_property.children
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end # module softlayer
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
module SoftLayer
|
24
|
+
#
|
25
|
+
# This class is an implementation detail of the Object Mask Parser
|
26
|
+
# It represents a single semantic token as parsed out of an
|
27
|
+
# Object Mask String
|
28
|
+
#
|
29
|
+
# The class also generates error messages that the parser can use
|
30
|
+
# when it encounters an unexpected token
|
31
|
+
#
|
32
|
+
class ObjectMaskToken
|
33
|
+
attr_reader :type
|
34
|
+
attr_reader :value
|
35
|
+
|
36
|
+
KnownTokenTypes = [
|
37
|
+
:invalid_token,
|
38
|
+
:eos, # end of string
|
39
|
+
:identifier,
|
40
|
+
:property_set_start,
|
41
|
+
:property_set_separator,
|
42
|
+
:property_set_end,
|
43
|
+
:property_type_start,
|
44
|
+
:property_type_end,
|
45
|
+
:property_child_separator,
|
46
|
+
]
|
47
|
+
|
48
|
+
def initialize(token_type, token_value = nil)
|
49
|
+
@type = token_type
|
50
|
+
@value = token_value
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"<#{@type.inspect}, #{@value.inspect}>"
|
55
|
+
end
|
56
|
+
|
57
|
+
def eql?(other_token)
|
58
|
+
@type.eql?(other_token.type) && @value.eql?(other_token.value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def invalid?
|
62
|
+
return @type = :invalid_token
|
63
|
+
end
|
64
|
+
|
65
|
+
def end_of_string?
|
66
|
+
return @type == :eos
|
67
|
+
end
|
68
|
+
|
69
|
+
def mask_root_marker?
|
70
|
+
return @type == :identifier && (@value == "mask" || @value == "filterMask")
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_property_name?
|
74
|
+
return @type == :identifier && @value.match(/\A[a-z][a-z0-9]*\z/i)
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_property_type?
|
78
|
+
return @type == :identifier && @value.match(/\A[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*\z/i)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.error_for_unexpected_token(token)
|
82
|
+
case token.type
|
83
|
+
when :invalid_token
|
84
|
+
"Unrecognized token '#{token.value}'"
|
85
|
+
when :eos
|
86
|
+
"Unexpected end of string"
|
87
|
+
when :identifier
|
88
|
+
"Unexpected identifier '#{token.value}'"
|
89
|
+
when :property_set_start
|
90
|
+
"Unexpected '['"
|
91
|
+
when :property_set_separator
|
92
|
+
"Unexpected ','"
|
93
|
+
when :property_set_end
|
94
|
+
"Unexpected ']'"
|
95
|
+
when :property_type_start
|
96
|
+
"Unexpected '('"
|
97
|
+
when :property_type_end
|
98
|
+
"Unexpected ')'"
|
99
|
+
when :property_child_separator
|
100
|
+
"Unexpected '.'"
|
101
|
+
else
|
102
|
+
"Unexpected value (invalid token type)"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end # module SoftLayer
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'softlayer/ObjectMaskToken'
|
24
|
+
require 'strscan'
|
25
|
+
|
26
|
+
module SoftLayer
|
27
|
+
#
|
28
|
+
# This class is an implementation detail of the ObjectMaskParser
|
29
|
+
#
|
30
|
+
# It takes an Object Mask String and breaks it down
|
31
|
+
# into ObjectMaskToken instances.
|
32
|
+
#
|
33
|
+
class ObjectMaskTokenizer
|
34
|
+
ObjectMask_Token_Specs = [
|
35
|
+
[/\[/, :property_set_start],
|
36
|
+
[/\,/, :property_set_separator],
|
37
|
+
[/\]/, :property_set_end],
|
38
|
+
[/\(/, :property_type_start],
|
39
|
+
[/\)/, :property_type_end],
|
40
|
+
[/\./, :property_child_separator],
|
41
|
+
[/[a-z][a-z0-9_]*/i, :identifier]
|
42
|
+
]
|
43
|
+
|
44
|
+
def initialize(mask_string)
|
45
|
+
@mask_string = mask_string.clone
|
46
|
+
@scanner = StringScanner.new(@mask_string)
|
47
|
+
@current_token = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def more_tokens?
|
51
|
+
return @current_token == nil || !@current_token.end_of_string?
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_token
|
55
|
+
@current_token = next_token if !@current_token
|
56
|
+
@current_token
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_token
|
60
|
+
# if we're at the end of the string, we keep returning the
|
61
|
+
# EOS token
|
62
|
+
if more_tokens? then
|
63
|
+
|
64
|
+
if !@scanner.eos?
|
65
|
+
# skip whitespace
|
66
|
+
@scanner.skip(/\s+/)
|
67
|
+
|
68
|
+
# search through the token specs to find which (if any) matches
|
69
|
+
token_spec = ObjectMask_Token_Specs.find() do |token_spec|
|
70
|
+
@scanner.check(token_spec[0])
|
71
|
+
end
|
72
|
+
|
73
|
+
# if a good token spec was found, set the current token to the one found
|
74
|
+
if token_spec
|
75
|
+
@current_token = ObjectMaskToken.new(token_spec.last, @scanner.scan(token_spec[0]))
|
76
|
+
else
|
77
|
+
@current_token = ObjectMaskToken.new(:invalid_token, @scanner.rest)
|
78
|
+
@scanner.terminate
|
79
|
+
end
|
80
|
+
else
|
81
|
+
@current_token = ObjectMaskToken.new(:eos)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@current_token
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end # module SoftLayer
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
|
23
|
+
module SoftLayer
|
24
|
+
# This struct represents a configuration option that can be included in
|
25
|
+
# a product order. Strictly speaking the only information required for
|
26
|
+
# the product order is the price_id, the rest of the information is provided
|
27
|
+
# to make the object friendly to humans who may be searching for the
|
28
|
+
# meaning of a given price_id.
|
29
|
+
ProductConfigurationOption = Struct.new(:price_id, :description, :capacity, :units, :setupFee, :laborFee, :oneTimeFee, :recurringFee, :hourlyRecurringFee) do
|
30
|
+
|
31
|
+
# Is it evil, or just incongruous to give methods to a struct?
|
32
|
+
|
33
|
+
# returns true if the configurtion option has no fees associated with it.
|
34
|
+
def free?
|
35
|
+
self.setupFee == 0 && self.laborFee == 0 && self.oneTimeFee == 0 && self.recurringFee == 0 && self.hourlyRecurringFee == 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# The goal of this class is to make it easy for scripts (and scripters) to
|
40
|
+
# discover what product configuration options exist that can be added to a
|
41
|
+
# product order.
|
42
|
+
#
|
43
|
+
# Instances of this class are created by and discovered in the context
|
44
|
+
# of a ProductPackage object. There should not be a need to create instances
|
45
|
+
# of this class directly.
|
46
|
+
#
|
47
|
+
# This class rougly represents entities in the +SoftLayer_Product_Item_Category+
|
48
|
+
# service.
|
49
|
+
class ProductItemCategory < ModelBase
|
50
|
+
include ::SoftLayer::DynamicAttribute
|
51
|
+
|
52
|
+
##
|
53
|
+
# :attr_reader:
|
54
|
+
# The categoryCode is a primary identifier for a particular
|
55
|
+
# category. It is a string like 'os' or 'ram'
|
56
|
+
sl_attr :categoryCode
|
57
|
+
|
58
|
+
##
|
59
|
+
# :attr_reader:
|
60
|
+
# The name of a category is a friendly, readable string
|
61
|
+
sl_attr :name
|
62
|
+
|
63
|
+
sl_dynamic_attr :configuration_options do |config_opts|
|
64
|
+
config_opts.should_update? do
|
65
|
+
# only retrieved once per instance
|
66
|
+
@configuration_options == nil
|
67
|
+
end
|
68
|
+
|
69
|
+
config_opts.to_update do
|
70
|
+
# This method assumes that the group and price item data was sent in
|
71
|
+
# as part of the +network_hash+ used to initialize this object (as is done)
|
72
|
+
# by the ProductPackage class. That class, in turn, gets its information
|
73
|
+
# from SoftLayer_Product_Package::getCategories which does some complex
|
74
|
+
# work on the back end to ensure the prices returned are correct.
|
75
|
+
#
|
76
|
+
# If this object was created in any other way, the configuration
|
77
|
+
# options might be incorrect. So Caveat Emptor.
|
78
|
+
#
|
79
|
+
# Options are divided into groups (for convenience in the
|
80
|
+
# web UI), but this code collapses the groups.
|
81
|
+
self['groups'].collect do |group|
|
82
|
+
group['prices'].sort{|lhs,rhs| lhs['sort'] <=> rhs['sort']}.collect do |price_item|
|
83
|
+
ProductConfigurationOption.new(
|
84
|
+
price_item['id'],
|
85
|
+
price_item['item']['description'],
|
86
|
+
price_item['item']['capacity'],
|
87
|
+
price_item['item']['units'],
|
88
|
+
price_item['setupFee'] ? price_item['setupFee'].to_f : 0.0,
|
89
|
+
price_item['laborFee'] ? price_item['laborFee'].to_f : 0.0,
|
90
|
+
price_item['oneTimeFee'] ? price_item['oneTimeFee'].to_f : 0.0,
|
91
|
+
price_item['recurringFee'] ? price_item['recurringFee'].to_f : 0.0,
|
92
|
+
price_item['hourlyRecurringFee'] ? price_item['hourlyRecurringFee'].to_f : 0.0
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end.flatten # flatten out the individual group arrays.
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def service
|
100
|
+
softlayer_client["SoftLayer_Product_Item_Category"].object_with_id(self.id)
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# If the category has a single option (regardless of fees) this method will return
|
105
|
+
# that option. If the category has more than one option, this method will
|
106
|
+
# return the first that it finds with no fees associated with it.
|
107
|
+
#
|
108
|
+
# If there are multiple options with no fees, it simply returns the first it finds
|
109
|
+
#
|
110
|
+
# Note that the option found may NOT be the same default option that is given
|
111
|
+
# in the web-based ordering system.
|
112
|
+
#
|
113
|
+
# If there are multiple options, and all of them have associated fees, then this method
|
114
|
+
# **will** return nil.
|
115
|
+
#
|
116
|
+
def default_option
|
117
|
+
if configuration_options.count == 1
|
118
|
+
configuration_options.first
|
119
|
+
else
|
120
|
+
configuration_options.find { |option| option.free? }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# The ProductItemCategory class augments the base initialization by accepting
|
125
|
+
# a boolean variable, +is_required+, which (when true) indicates that this category
|
126
|
+
# is required for orders against the package that created it.
|
127
|
+
def initialize(softlayer_client, network_hash, is_required)
|
128
|
+
super(softlayer_client, network_hash)
|
129
|
+
@is_required = is_required
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns true if this category is required in its package
|
133
|
+
def required?()
|
134
|
+
return @is_required
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|