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.
@@ -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