softlayer_api 3.0.b1 → 3.0.b2

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.
@@ -0,0 +1,84 @@
1
+ #--
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
+ #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
6
+
7
+ module SoftLayer
8
+ #
9
+ # This class allows you to order a Firewall for a server
10
+ #
11
+ class ServerFirewallOrder
12
+ # The server that you are ordering the firewall for.
13
+ attr_reader :server
14
+
15
+ ##
16
+ # Create a new order for the given server
17
+ def initialize (server)
18
+ @server = server
19
+
20
+ raise ArgumentError, "Server does not have an active Public interface" if server.firewall_port_speed == 0
21
+ end
22
+
23
+ ##
24
+ # Calls the SoftLayer API to verify that the template provided by this order is valid
25
+ # This routine will return the order template generated by the API or will throw an exception
26
+ #
27
+ # This routine will not actually create a Bare Metal Instance and will not affect billing.
28
+ #
29
+ # If you provide a block, it will receive the order template as a parameter and
30
+ # the block may make changes to the template before it is submitted.
31
+ def verify()
32
+ order_template = firewall_order_template
33
+ order_template = yield order_template if block_given?
34
+
35
+ server.softlayer_client[:Product_Order].verifyOrder(order_template)
36
+ end
37
+
38
+ ##
39
+ # Calls the SoftLayer API to place an order for a new server based on the template in this
40
+ # order. If this succeeds then you will be billed for the new server.
41
+ #
42
+ # If you provide a block, it will receive the order template as a parameter and
43
+ # the block may make changes to the template before it is submitted.
44
+ def place_order!()
45
+ order_template = firewall_order_template
46
+ order_template = yield order_template if block_given?
47
+
48
+ server.softlayer_client[:Product_Order].placeOrder(order_template)
49
+ end
50
+
51
+ protected
52
+
53
+ ##
54
+ # Returns a hash of the creation options formatted to be sent *to*
55
+ # the SoftLayer API for either verification or completion
56
+ def firewall_order_template
57
+ client = server.softlayer_client
58
+ additional_products_package = SoftLayer::ProductPackage.additional_products_package(client)
59
+
60
+ template = {
61
+ 'complexType' => 'SoftLayer_Container_Product_Order_Network_Protection_Firewall',
62
+ 'quantity' => 1,
63
+ 'packageId' => additional_products_package.id
64
+ }
65
+
66
+ if @server.service.service_name == "SoftLayer_Virtual_Guest"
67
+ template['virtualGuests'] = [{'id' => @server.id}]
68
+ else
69
+ template['hardware'] = [{'id' => @server.id}]
70
+ end
71
+
72
+ expected_description = "#{@server.firewall_port_speed}Mbps Hardware Firewall"
73
+ firewall_items = additional_products_package.items_with_description(expected_description)
74
+
75
+ raise "Could not find a price item matching the description '#{expected_description}'" if firewall_items.empty?
76
+
77
+ firewall_item = firewall_items[0]
78
+
79
+ template['prices'] = [{ 'id' => firewall_item.price_id }] if firewall_item.respond_to?(:price_id)
80
+
81
+ template
82
+ end
83
+ end # class ServerFirewallOrder
84
+ end # module SoftLayer
@@ -20,6 +20,19 @@
20
20
 
21
21
  require 'xmlrpc/client'
22
22
 
23
+ # utility routine for swapping constants without warnings.
24
+ def with_warnings(flag)
25
+ old_verbose, $VERBOSE = $VERBOSE, flag
26
+ yield
27
+ ensure
28
+ $VERBOSE = old_verbose
29
+ end
30
+
31
+ # enable parsing of "nil" values in structures returned from the API
32
+ with_warnings(nil) {
33
+ XMLRPC::Config.const_set('ENABLE_NIL_PARSER', true)
34
+ }
35
+
23
36
  # The XML-RPC spec calls for the "faultCode" in faults to be an integer
24
37
  # but the SoftLayer XML-RPC API can return strings as the "faultCode"
25
38
  #
@@ -30,25 +43,15 @@ module XMLRPC::Convert
30
43
  def self.fault(hash)
31
44
  if hash.kind_of? Hash and hash.size == 2 and
32
45
  hash.has_key? "faultCode" and hash.has_key? "faultString" and
33
- (hash["faultCode"].kind_of?(Integer) || hash["faultCode"].kind_of?(String)) and hash["faultString"].kind_of? String
46
+ (hash['faultCode'].kind_of?(Integer) || hash['faultCode'].kind_of?(String)) and hash['faultString'].kind_of? String
34
47
 
35
- XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
48
+ XMLRPC::FaultException.new(hash['faultCode'], hash['faultString'])
36
49
  else
37
50
  super
38
51
  end
39
52
  end
40
53
  end
41
54
 
42
- # The XMLRPC client uses a fixed user agent string, but we want to
43
- # supply our own, so we add a method to XMLRPC::Client that lets
44
- # us change it.
45
- class XMLRPC::Client
46
- def self.set_user_agent(new_agent)
47
- remove_const(:USER_AGENT) if const_defined?(:USER_AGENT)
48
- const_set(:USER_AGENT, new_agent)
49
- end
50
- end
51
-
52
55
  module SoftLayer
53
56
  # = SoftLayer API Service
54
57
  #
@@ -62,7 +65,7 @@ module SoftLayer
62
65
  #
63
66
  # client = SoftLayer::Client.new(:username => "Joe", :api_key=>"feeddeadbeefbadfood...")
64
67
  # account_service = client.service_named("Account") # returns the SoftLayer_Account service
65
- # account_service = client['Account'] # Exactly the same as above
68
+ # account_service = client[:Account] # Exactly the same as above
66
69
  #
67
70
  # For backward compatibility, a service can be constructed by passing
68
71
  # client initialization options, however if you do so you will need to
@@ -315,6 +318,7 @@ using either client.service_named('<service_name_here>') or client['<service_nam
315
318
  end
316
319
 
317
320
  @xmlrpc_client.http.set_debug_output($stderr)
321
+ @xmlrpc_client.http.instance_variable_set(:@verify_mode, OpenSSL::SSL::VERIFY_NONE)
318
322
  end # $DEBUG
319
323
  end
320
324
 
@@ -25,14 +25,14 @@ module SoftLayer
25
25
  ##
26
26
  # Returns true if the ticket has "unread" updates
27
27
  def has_updates?
28
- self["newUpdatesFlag"]
28
+ self['newUpdatesFlag']
29
29
  end
30
30
 
31
31
  ##
32
32
  # Returns true if the ticket is a server admin ticket
33
33
  def server_admin_ticket?
34
34
  # note that serverAdministrationFlag comes from the server as an Integer (0, or 1)
35
- self["serverAdministrationFlag"] != 0
35
+ self['serverAdministrationFlag'] != 0
36
36
  end
37
37
 
38
38
  ##
@@ -46,7 +46,7 @@ module SoftLayer
46
46
  # Override of service from ModelBase. Returns the SoftLayer_Ticket service
47
47
  # set up to talk to the ticket with my ID.
48
48
  def service
49
- return softlayer_client["Ticket"].object_with_id(self.id)
49
+ return softlayer_client[:Ticket].object_with_id(self.id)
50
50
  end
51
51
 
52
52
  ##
@@ -96,7 +96,7 @@ module SoftLayer
96
96
  softlayer_client = client || Client.default_client
97
97
  raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
98
98
 
99
- @ticket_subjects = softlayer_client['Ticket_Subject'].getAllObjects();
99
+ @ticket_subjects = softlayer_client[:Ticket_Subject].getAllObjects();
100
100
  end
101
101
 
102
102
  @ticket_subjects
@@ -122,7 +122,7 @@ module SoftLayer
122
122
  object_mask = default_object_mask.to_sl_object_mask
123
123
  end
124
124
 
125
- ticket_data = softlayer_client["Ticket"].object_with_id(ticket_id).object_mask(object_mask).getObject()
125
+ ticket_data = softlayer_client[:Ticket].object_with_id(ticket_id).object_mask(object_mask).getObject()
126
126
 
127
127
  return new(softlayer_client, ticket_data)
128
128
  end
@@ -153,8 +153,8 @@ module SoftLayer
153
153
  assigned_user_id = options[:assigned_user_id]
154
154
 
155
155
  if(nil == assigned_user_id)
156
- current_user = softlayer_client["Account"].object_mask("id").getCurrentUser()
157
- assigned_user_id = current_user["id"]
156
+ current_user = softlayer_client[:Account].object_mask("id").getCurrentUser()
157
+ assigned_user_id = current_user['id']
158
158
  end
159
159
 
160
160
  new_ticket = {
@@ -164,7 +164,7 @@ module SoftLayer
164
164
  'title' => title
165
165
  }
166
166
 
167
- ticket_data = softlayer_client["Ticket"].createStandardTicket(new_ticket, body)
167
+ ticket_data = softlayer_client[:Ticket].createStandardTicket(new_ticket, body)
168
168
  return new(softlayer_client, ticket_data)
169
169
  end
170
170
  end
@@ -0,0 +1,280 @@
1
+ #--
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
+ #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
6
+
7
+ module SoftLayer
8
+ # The VLANFirewall class represents the firewall that protects
9
+ # all the servers on a VLAN in the SoftLayer Environment. It is
10
+ # also known as a "Dedicated Firewall" in some documentation.
11
+ #
12
+ # Instances of this class are a bit odd because they actually represent a
13
+ # VLAN (the VLAN protected by the firewall to be specific), and not the
14
+ # physical hardware implementing the firewall itself. (although the device
15
+ # is accessible as the "networkVlanFirewall" property)
16
+ #
17
+ # As a result, instances of this class correspond to certain instances
18
+ # in the SoftLayer_Network_Vlan service.
19
+ #
20
+ class VLANFirewall < SoftLayer::ModelBase
21
+ include ::SoftLayer::DynamicAttribute
22
+
23
+ ##
24
+ #:attr_reader:
25
+ #
26
+ # The number of the VLAN protected by this firewall.
27
+ #
28
+ sl_attr :VLAN_number, 'vlanNumber'
29
+
30
+ ##
31
+ # :attr_reader:
32
+ #
33
+ # The set of rules applied by this firewall to incoming traffic.
34
+ # The object will retrieve the rules from the network API every
35
+ # time you ask it for the rules.
36
+ #
37
+ # The code will sort the rules by their "orderValue" which is the
38
+ # order that the firewall applies the rules, however please see
39
+ # the important note in change_rules! concerning the "orderValue"
40
+ # property of the rules.
41
+ sl_dynamic_attr :rules do |firewall_rules|
42
+ firewall_rules.should_update? do
43
+ # firewall rules update every time you ask for them.
44
+ return true
45
+ end
46
+
47
+ firewall_rules.to_update do
48
+ acl_id = rules_ACL_id()
49
+ rules_data = self.softlayer_client[:Network_Firewall_AccessControlList].object_with_id(acl_id).object_mask(self.class.default_rules_mask).getRules
50
+ rules_data.sort { |lhs, rhs| lhs['orderValue'] <=> rhs['orderValue'] }
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Returns the name of the primary router the firewall is attached to.
56
+ # This is often a "customer router" in one of the datacenters.
57
+ def primaryRouter
58
+ return self['primaryRouter']['hostname']
59
+ end
60
+
61
+ ##
62
+ # The fully qualified domain name of the physical device the
63
+ # firewall is implemented by.
64
+ def fullyQualifiedDomainName
65
+ if self.has_sl_property?('networkVlanFirewall')
66
+ return self['networkVlanFirewall']['fullyQualifiedDomainName']
67
+ else
68
+ return @softlayer_hash
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Returns true if this is a "high availability" firewall, that is a firewall
74
+ # that exists as one member of a redundant pair.
75
+ def high_availability?
76
+ # note that highAvailabilityFirewallFlag is a boolean in the softlayer hash
77
+ return self.has_sl_property?('highAvailabilityFirewallFlag') && self['highAvailabilityFirewallFlag']
78
+ end
79
+
80
+ ##
81
+ # Cancel the firewall
82
+ #
83
+ # This method cancels the firewall and releases its
84
+ # resources. The cancellation is processed immediately!
85
+ # Call this method with careful deliberation!
86
+ #
87
+ # Notes is a string that describes the reason for the
88
+ # cancellation. If empty or nil, a default string will
89
+ # be added.
90
+ #
91
+ def cancel!(notes = nil)
92
+ user = self.softlayer_client[:Account].object_mask("mask[id,account.id]").getCurrentUser
93
+ notes = "Cancelled by a call to #{__method__} in the softlayer_api gem" if notes == nil || notes == ""
94
+
95
+ cancellation_request = {
96
+ 'accountId' => user['account']['id'],
97
+ 'userId' => user['id'],
98
+ 'items' => [ {
99
+ 'billingItemId' => self['networkVlanFirewall']['billingItem']['id'],
100
+ 'immediateCancellationFlag' => true
101
+ } ],
102
+ 'notes' => notes
103
+ }
104
+
105
+ self.softlayer_client[:Billing_Item_Cancellation_Request].createObject(cancellation_request)
106
+ end
107
+
108
+ ##
109
+ # Change the set of rules for the firewall.
110
+ # The rules_data parameter should be an array of hashes where
111
+ # each hash gives the conditions of the rule. The keys of the
112
+ # hashes should be entries from the array returned by
113
+ # SoftLayer::ServerFirewall.default_rules_mask_keys
114
+ #
115
+ # *NOTE!* When changing the rules on the firewall, you must
116
+ # pass in a complete set of rules each time. The rules you
117
+ # submit will replace the entire ruleset on the destination
118
+ # firewall.
119
+ #
120
+ # *NOTE!* The rules themselves have an "orderValue" property.
121
+ # It is this property, and *not* the order that the rules are
122
+ # found in the rules_data array, which will determine in which
123
+ # order the firewall applies its rules to incomming traffic.
124
+ #
125
+ # *NOTE!* Changes to the rules are not applied immediately
126
+ # on the server side. Instead, they are enqueued by the
127
+ # firewall update service and updated periodically. A typical
128
+ # update will take about one minute to apply, but times may vary
129
+ # depending on the system load and other circumstances.
130
+ def change_rules!(rules_data)
131
+ change_object = {
132
+ "firewallContextAccessControlListId" => rules_ACL_id(),
133
+ "rules" => rules_data
134
+ }
135
+
136
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
137
+ end
138
+
139
+ ##
140
+ # This method asks the firewall to ignore its rule set and pass all traffic
141
+ # through the firewall. Compare the behavior of this routine with
142
+ # change_routing_bypass!
143
+ #
144
+ # It is important to note that changing the bypass to :bypass_firewall_rules
145
+ # removes ALL the protection offered by the firewall. This routine should be
146
+ # used with extreme discretion.
147
+ #
148
+ # Note that this routine queues a rule change and rule changes may take
149
+ # time to process. The change will probably not take effect immediately.
150
+ #
151
+ # The two symbols accepted as arguments by this routine are:
152
+ # :apply_firewall_rules - The rules of the firewall are applied to traffic. This is the default operating mode of the firewall
153
+ # :bypass_firewall_rules - The rules of the firewall are ignored. In this configuration the firewall provides no protection.
154
+ #
155
+ def change_rules_bypass!(bypass_symbol)
156
+ change_object = {
157
+ "firewallContextAccessControlListId" => rules_ACL_id(),
158
+ "rules" => self.rules
159
+ }
160
+
161
+ case bypass_symbol
162
+ when :apply_firewall_rules
163
+ change_object['bypassFlag'] = false
164
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
165
+ when :bypass_firewall_rules
166
+ change_object['bypassFlag'] = true
167
+ self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
168
+ else
169
+ raise ArgumentError, "An invalid parameter was sent to #{__method__}. It accepts :apply_firewall_rules and :bypass_firewall_rules"
170
+ end
171
+ end
172
+
173
+ ##
174
+ # This method allows you to route traffic around the firewall
175
+ # and directly to the servers it protects. Compare the behavior of this routine with
176
+ # change_rules_bypass!
177
+ #
178
+ # It is important to note that changing the routing to :route_around_firewall
179
+ # removes ALL the protection offered by the firewall. This routine should be
180
+ # used with extreme discretion.
181
+ #
182
+ # Note that this routine constructs a transaction. The Routing change
183
+ # may not happen immediately.
184
+ #
185
+ # The two symbols accepted as arguments by the routine are:
186
+ # :route_through_firewall - Network traffic is sent through the firewall to the servers in the VLAN segment it protects. This is the usual operating mode of the firewall.
187
+ # :route_around_firewall - Network traffic will be sent directly to the servers in the VLAN segment protected by this firewall. This means that the firewall will *NOT* be protecting those servers.
188
+ #
189
+ def change_routing_bypass!(routing_symbol)
190
+ vlan_firewall_id = self['networkVlanFirewall']['id']
191
+
192
+ raise "Could not identify the device for a VLAN firewall" if !vlan_firewall_id
193
+
194
+ case routing_symbol
195
+ when :route_through_firewall
196
+ self.softlayer_client[:Network_Vlan_Firewall].object_with_id(vlan_firewall_id).updateRouteBypass(false)
197
+ when :route_around_firewall
198
+ self.softlayer_client[:Network_Vlan_Firewall].object_with_id(vlan_firewall_id).updateRouteBypass(true)
199
+ else
200
+ raise ArgumentError, "An invalid parameter was sent to #{__method__}. It accepts :route_through_firewall and :route_around_firewall"
201
+ end
202
+ end
203
+
204
+ ##
205
+ # Collect a list of the firewalls on the account.
206
+ #
207
+ # This list is obtained by asking the account for all the VLANs
208
+ # it has that also have a networkVlanFirewall component.
209
+ def self.find_firewalls(client = nil)
210
+ softlayer_client = client || Client.default_client
211
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
212
+
213
+ # only VLAN firewallas have a networkVlanFirewall component
214
+ vlan_firewall_filter = SoftLayer::ObjectFilter.new() { |filter|
215
+ filter.accept("networkVlans.networkVlanFirewall").when_it is_not_null
216
+ }
217
+
218
+ vlan_firewalls = softlayer_client[:Account].object_mask(vlan_firewall_mask).object_filter(vlan_firewall_filter).getNetworkVlans
219
+ vlan_firewalls.collect { |firewall_data| SoftLayer::VLANFirewall.new(softlayer_client, firewall_data)}
220
+ end
221
+
222
+
223
+ #--
224
+ # Methods for the SoftLayer model
225
+ #++
226
+
227
+ def service
228
+ # Objects of this class are a bit odd because they actually represent VLANs (the VLAN protected by the firewall)
229
+ # and not the physical hardware implementing the firewall itself. (although the device is accessible as the
230
+ # "networkVlanFirewall" property)
231
+ self.softlayer_client[:Network_Vlan].object_with_id(self.id)
232
+ end
233
+
234
+ def softlayer_properties(object_mask = nil)
235
+ service = self.service
236
+ service = service.object_mask(object_mask) if object_mask
237
+ service.object_mask(self.class.vlan_firewall_mask).getObject
238
+ end
239
+
240
+ #--
241
+ #++
242
+ private
243
+
244
+ # Searches the set of access control lists for the firewall device in order to locate the one that
245
+ # sits on the "outside" side of the network and handles 'in'coming traffic.
246
+ def rules_ACL_id
247
+ outside_interface_data = self['firewallInterfaces'].find { |interface_data| interface_data['name'] == 'outside' }
248
+ incoming_ACL = outside_interface_data['firewallContextAccessControlLists'].find { |firewallACL_data| firewallACL_data['direction'] == 'in' } if outside_interface_data
249
+
250
+ if incoming_ACL
251
+ return incoming_ACL['id']
252
+ else
253
+ return nil
254
+ end
255
+ end
256
+
257
+ def self.vlan_firewall_mask
258
+ return "mask[primaryRouter,highAvailabilityFirewallFlag," +
259
+ "firewallInterfaces.firewallContextAccessControlLists," +
260
+ "networkVlanFirewall[id,datacenter,primaryIpAddress,firewallType,fullyQualifiedDomainName,billingItem.id]]"
261
+ end
262
+
263
+ def self.default_rules_mask
264
+ return { "mask" => default_rules_mask_keys }.to_sl_object_mask
265
+ end
266
+
267
+ def self.default_rules_mask_keys
268
+ ['orderValue',
269
+ 'action',
270
+ 'destinationIpAddress',
271
+ 'destinationIpSubnetMask',
272
+ 'protocol',
273
+ 'destinationPortRangeStart',
274
+ 'destinationPortRangeEnd',
275
+ 'sourceIpAddress',
276
+ 'sourceIpSubnetMask',
277
+ 'version']
278
+ end
279
+ end # class Firewall
280
+ end # module SoftLayer