├── .coveralls.yml
├── Gemfile
├── .travis.yml
├── .gitignore
├── spec
├── spec_helper.rb
├── Server_spec.rb
├── fixtures
│ ├── test_account.json
│ ├── datacenter_locations.json
│ ├── ticket_subjects.json
│ ├── Product_Package.json
│ ├── test_bare_metal.json
│ ├── test_tickets.json
│ └── test_virtual_servers.json
├── XMLRPC_Convert_spec.rb
├── Datacenter_spec.rb
├── shared_server.rb
├── BareMetalServer_spec.rb
├── Ticket_spec.rb
├── ModelBase_spec.rb
├── ServerFirewall_spec.rb
├── Config_spec.rb
├── VirtualServer_spec.rb
├── ProductPackage_spec.rb
├── object_mask_helpers_spec.rb
├── ObjectMaskProperty_spec.rb
├── APIParameterFilter_spec.rb
├── DynamicAttribute_spec.rb
├── Account_spec.rb
├── VLANFirewall_spec.rb
├── VirtualServerUpgradeOrder_spec.rb
└── Client_spec.rb
├── rakefile
├── lib
├── softlayer
│ ├── NetworkComponent.rb
│ ├── VirtualDiskImageSoftware.rb
│ ├── base.rb
│ ├── ObjectMaskProperty.rb
│ ├── Datacenter.rb
│ ├── ObjectMaskTokenizer.rb
│ ├── ObjectMaskToken.rb
│ ├── NetworkStorageAllowedHost.rb
│ ├── ServerFirewallOrder.rb
│ ├── NetworkService.rb
│ ├── VLANFirewallOrder.rb
│ ├── UserCustomerExternalBinding.rb
│ ├── object_mask_helpers.rb
│ ├── NetworkMessageDelivery.rb
│ ├── ModelBase.rb
│ ├── ObjectMaskParser.rb
│ ├── VirtualServerUpgradeOrder.rb
│ ├── DynamicAttribute.rb
│ ├── NetworkStorageGroup.rb
│ ├── UserCustomer.rb
│ └── Ticket.rb
└── softlayer_api.rb
├── LICENSE.md
├── softlayer_api.gemspec
├── examples
├── account_info.rb
├── open_tickets.rb
├── ticket_info.rb
├── account_servers.rb
├── order_server_firewall.rb
├── create_ticket.rb
└── order_virtual_server.rb
├── README.md
├── doc_src
├── Welcome.md
├── Model Layer.md
└── cla-individual.md
└── CHANGELOG.textile
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - "2.3.0"
4 | - "2.2.4"
5 | - "2.1.8"
6 | - "2.0.0"
7 | - jruby-19mode
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.*~
3 | *.gem
4 | .bundle/
5 | log/
6 | internal_examples/
7 | doc/
8 | coverage/
9 | Gemfile.lock
10 | vendor/
11 | pkg/
12 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
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 | require 'coveralls'
8 | Coveralls.wear!
9 |
10 | require 'json'
11 |
12 | def fixture_from_json(json_file_name)
13 | full_name = File.basename(json_file_name, ".json") + ".json"
14 | JSON.parse(File.read(File.join(File.dirname(__FILE__), "fixtures/#{full_name}")))
15 | end
--------------------------------------------------------------------------------
/spec/Server_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::Server do
14 | it "is an abstract base class" do
15 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
16 | expect { SoftLayer::Server.new(mock_client, { "id" => 12345 }) }.to raise_error(RuntimeError)
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/fixtures/test_account.json:
--------------------------------------------------------------------------------
1 | {
2 | "accountStatusId": 1001,
3 | "address1": "123 Main Street",
4 | "allowedPptpVpnQuantity": 5,
5 | "alternatePhone": "555.456.7891",
6 | "brandId": 2,
7 | "city": "Anytown",
8 | "claimedTaxExemptTxFlag": false,
9 | "companyName": "UpAndComing Software",
10 | "country": "US",
11 | "createDate": "2012-03-30T14:41:16-07:00",
12 | "email": "just@a@test.com",
13 | "firstName": "Don ",
14 | "id": 99999,
15 | "isReseller": 0,
16 | "lastName": "Joe",
17 | "lateFeeProtectionFlag": true,
18 | "modifyDate": "2012-03-30T14:41:50-07:00",
19 | "officePhone": "555.123.4567",
20 | "postalCode": "778899",
21 | "state": "TX",
22 | "statusDate": null
23 | }
24 |
--------------------------------------------------------------------------------
/spec/fixtures/datacenter_locations.json:
--------------------------------------------------------------------------------
1 | [{"id":265592,"longName":"Amsterdam 1","name":"ams01"},{"id":358698,"longName":"Ashburn 3","name":"wdc03"},{"id":3,"longName":"Dallas 1","name":"dal01"},{"id":154770,"longName":"Dallas 2","name":"dal02"},{"id":167092,"longName":"Dallas 4","name":"dal04"},{"id":138124,"longName":"Dallas 5","name":"dal05"},{"id":154820,"longName":"Dallas 6","name":"dal06"},{"id":142776,"longName":"Dallas 7","name":"dal07"},{"id":352494,"longName":"Hong Kong 2","name":"hkg02"},{"id":142775,"longName":"Houston 2","name":"hou02"},{"id":358694,"longName":"London 2","name":"lon02"},{"id":168642,"longName":"San Jose 1","name":"sjc01"},{"id":18171,"longName":"Seattle","name":"sea01"},{"id":224092,"longName":"Singapore 1","name":"sng01"},{"id":448994,"longName":"Toronto 1","name":"tor01"},{"id":37473,"longName":"Washington 1","name":"wdc01"}]
--------------------------------------------------------------------------------
/rakefile:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__)))
8 |
9 | require 'rubygems'
10 | require 'bundler/gem_tasks'
11 | require 'rspec/core/rake_task'
12 | require 'rdoc/task'
13 |
14 | Rake::RDocTask.new do |rd|
15 | require 'lib/softlayer/base.rb'
16 |
17 | puts RDoc::Markdown::DEFAULT_EXTENSIONS
18 |
19 | rd.title = "softlayer_api gem #{SoftLayer::VERSION}"
20 | rd.main = "Welcome.md"
21 | rd.rdoc_dir = "doc"
22 | rd.rdoc_files.include('lib/**/*.rb')
23 | rd.options << '--page-dir=doc_src'
24 | end
25 |
26 | RSpec::Core::RakeTask.new(:spec) do |t|
27 | $DEBUG = 1
28 | t.rspec_opts = ["-c"]
29 | end
30 |
31 | Rake::Task[:build].enhance [:rerdoc]
32 |
33 | task :gem => [:build]
34 | task :default => :spec
--------------------------------------------------------------------------------
/spec/XMLRPC_Convert_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 |
12 | describe XMLRPC::Convert,"::fault" do
13 | it "converts faults with faultCodes that are strings to FaultException objects" do
14 | fault_hash = { "faultCode" => "The SLAPI returns strings where it shouldn't", "faultString" => "This is the actual fault"}
15 |
16 | expect { XMLRPC::Convert.fault(fault_hash) }.not_to raise_error
17 | exception = XMLRPC::Convert.fault(fault_hash)
18 | expect(exception).to be_kind_of(XMLRPC::FaultException)
19 | expect(exception.faultCode).to eq(fault_hash['faultCode'])
20 | expect(exception.faultString).to eq(fault_hash['faultString'])
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/fixtures/ticket_subjects.json:
--------------------------------------------------------------------------------
1 | [{"id" : 1001, "name" : "Accounting Request"},
2 | {"id" : 1002, "name" : "Sales Request"},
3 | {"id" : 1003, "name" : "Reboots and Console Access"},
4 | {"id" : 1041, "name" : "DNS Request"},
5 | {"id" : 1021, "name" : "Hardware Issue"},
6 | {"id" : 1022, "name" : "Public Network Question"},
7 | {"id" : 1061, "name" : "Private Network Question"},
8 | {"id" : 1201, "name" : "DOS/Abuse Issue"},
9 | {"id" : 1101, "name" : "Security Issue"},
10 | {"id" : 1121, "name" : "Hardware Firewall Question"},
11 | {"id" : 1122, "name" : "Hardware Load Balancer Question"},
12 | {"id" : 1004, "name" : "OS Reload Question"},
13 | {"id" : 1005, "name" : "Portal Information Question"},
14 | {"id" : 1081, "name" : "Licensing Question"},
15 | {"id" : 1141, "name" : "Mail Server Issue"},
16 | {"id" : 1161, "name" : "StorageLayer Question"},
17 | {"id" : 1181, "name" : "CDNLayer Question"},
18 | {"id" : 1221, "name" : "Transcoding Question"},
19 | {"id" : 1261, "name" : "Colocation Service Request"}]
--------------------------------------------------------------------------------
/lib/softlayer/NetworkComponent.rb:
--------------------------------------------------------------------------------
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 | class NetworkComponent < SoftLayer::ModelBase
9 | ##
10 | # :attr_reader: max_speed
11 | # A network component's maximum allowed speed,
12 | sl_attr :max_speed, 'maxSpeed'
13 |
14 | ##
15 | # :attr_reader:
16 | # A network component's maximum allowed speed,
17 | #
18 | # DEPRECATION WARNING: This attribute is deprecated in favor of max_speed
19 | # and will be removed in the next major release.
20 | sl_attr :maxSpeed
21 |
22 | ##
23 | # :attr_reader:
24 | # A network component's short name.
25 | sl_attr :name
26 |
27 | # :attr_reader:
28 | # A network component's port number.
29 | sl_attr :port
30 |
31 | ##
32 | # :attr_reader:
33 | # A network component's speed, measured in Mbit per second.
34 | sl_attr :speed
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2010-2014 [SoftLayer Technologies, Inc.](http://www.softlayer.com/) All rights reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/spec/Datacenter_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::Datacenter do
14 | let (:mock_client) do
15 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
16 | allow(mock_client[:Location]).to receive(:call_softlayer_api_with_params) do |method_name, parameters, args|
17 | fixture_from_json('datacenter_locations.json')
18 | end
19 |
20 | mock_client
21 | end
22 |
23 | it "retrieves a list of datacenters" do
24 | datacenters = SoftLayer::Datacenter.datacenters(mock_client)
25 | names = datacenters.collect { |datacenter| datacenter.name }
26 | expect(names.sort).to eq ["ams01", "dal01", "dal02", "dal04", "dal05", "dal06", "dal07", "hkg02", "hou02", "lon02", "sea01", "sjc01", "sng01", "tor01", "wdc01", "wdc03"]
27 | end
28 |
29 | it "retrieves a particular datacenter by name" do
30 | dal05 = SoftLayer::Datacenter.datacenter_named("dal05", mock_client)
31 | expect(dal05.name).to eq "dal05"
32 | expect(dal05.id).to be 138124
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/softlayer_api.gemspec:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__)))
8 | require 'lib/softlayer/base'
9 |
10 | Gem::Specification.new do |s|
11 | s.name = %q{softlayer_api}
12 | s.version = SoftLayer::VERSION
13 | s.author = "SoftLayer Development Team"
14 | s.email = %q{sldn@softlayer.com}
15 | s.description = %q{The softlayer_api gem offers a convenient mechanism for invoking the services of the SoftLayer API from Ruby.}
16 | s.summary = %q{Library for accessing the SoftLayer API}
17 | s.homepage = %q{http://sldn.softlayer.com/}
18 | s.license = %q{MIT}
19 |
20 | s.files = Dir["README.textile", "LICENSE.textile", "CHANGELOG.textile", "lib/**/*.rb", "test/**/*.rb", "examples/**/*.rb"]
21 | s.require_paths = ["lib"]
22 |
23 | s.has_rdoc = true
24 | s.required_ruby_version = '>= 1.9.2'
25 | s.add_runtime_dependency 'configparser', '~>0.1.2'
26 | s.add_development_dependency 'rake'
27 | s.add_development_dependency 'rspec'
28 | s.add_development_dependency 'rdoc', '>=2.4.2'
29 | s.add_development_dependency 'json', '~> 1.8', '>= 1.8.1'
30 | # Fixing the following gems' versions to avoid requiring
31 | # Ruby 2.0.
32 | s.add_development_dependency 'mime-types', '= 2.99.3'
33 | s.add_development_dependency 'coveralls', '= 0.7.2'
34 | end
35 |
--------------------------------------------------------------------------------
/spec/shared_server.rb:
--------------------------------------------------------------------------------
1 | shared_examples_for "server with mutable hostname" do
2 | it "has a method to change the host name" do
3 | expect(server).to respond_to(:set_hostname!)
4 | end
5 |
6 | it "rejects nil hostnames" do
7 | expect { server.set_hostname!(nil) }.to raise_error(ArgumentError)
8 | end
9 |
10 | it "rejects empty hostnames" do
11 | expect { server.set_hostname!("") }.to raise_error(ArgumentError)
12 | end
13 | end
14 |
15 | shared_examples_for "server with port speed" do
16 | it "has a method to change port speed" do
17 | expect(server).to respond_to(:change_port_speed)
18 | end
19 |
20 | it "changes public port speed if no interface is specified" do
21 | expect(server.service.target).to receive(:call_softlayer_api_with_params).with(:setPublicNetworkInterfaceSpeed, instance_of(SoftLayer::APIParameterFilter),[10])
22 | server.change_port_speed(10)
23 | end
24 |
25 | it "changes public port speed if told to do so" do
26 | expect(server.service.target).to receive(:call_softlayer_api_with_params).with(:setPublicNetworkInterfaceSpeed, instance_of(SoftLayer::APIParameterFilter),[10])
27 | server.change_port_speed(10, true)
28 | end
29 |
30 | it "changes private port speed if told to change private" do
31 | expect(server.service.target).to receive(:call_softlayer_api_with_params).with(:setPrivateNetworkInterfaceSpeed, instance_of(SoftLayer::APIParameterFilter),[10])
32 | server.change_port_speed(10, false)
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/softlayer/VirtualDiskImageSoftware.rb:
--------------------------------------------------------------------------------
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 | # Each SoftLayer VirtualDiskImageSoftware is a record that connects
10 | # a computing instance's virtual disk images with software records.
11 | #
12 | # This class roughly corresponds to the entity SoftLayer_Virtual_Disk_Image_Software
13 | # in the API.
14 | #
15 | class VirtualDiskImageSoftware < ModelBase
16 | include ::SoftLayer::DynamicAttribute
17 |
18 | ##
19 | # The manufacturer, name and version of a piece of software.
20 | #
21 | def description
22 | self['softwareDescription']['longDescription']
23 | end
24 |
25 | ##
26 | # The name of this specific piece of software.
27 | #
28 | def name
29 | self['softwareDescription']['name']
30 | end
31 |
32 | ##
33 | # The password for this specific virtual disk image software instance.
34 | #
35 | def passwords
36 | self['passwords']
37 | end
38 |
39 | protected
40 |
41 | def self.default_object_mask
42 | {
43 | "mask(SoftLayer_Virtual_Disk_Image_Software)" => [
44 | 'id',
45 | 'passwords[password,username]',
46 | 'softwareDescription[longDescription,name]'
47 | ]
48 | }.to_sl_object_mask
49 | end
50 | end
51 | end #SoftLayer
52 |
--------------------------------------------------------------------------------
/lib/softlayer/base.rb:
--------------------------------------------------------------------------------
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 | require 'rubygems'
8 |
9 | ##
10 | # The SoftLayer module provides a namespace for SoftLayer code.
11 | #
12 | module SoftLayer
13 | # The version number (including major, minor, and bugfix numbers)
14 | # This should change in accordance with the concept of Semantic Versioning
15 | VERSION = "3.2.3" # version history in the CHANGELOG.textile file at the root of the source
16 |
17 | # The base URL of the SoftLayer API available to the public internet.
18 | API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/'
19 |
20 | # The base URL of the SoftLayer API available through SoftLayer's private network
21 | API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3/'
22 |
23 | #--
24 | # These globals can be used to simplify client creation
25 | #++
26 |
27 | # Set this if you want to provide a default username for each client as it is created.
28 | # usernames provided to the client initializer will override the global
29 | $SL_API_USERNAME = nil
30 |
31 | # Set this if you want to provide a default api_key for each client as it is
32 | # created. API keys provided in the constructor when a client is created will
33 | # override the values in this global
34 | $SL_API_KEY = nil
35 |
36 | # The base URL used for the SoftLayer API's
37 | $SL_API_BASE_URL = SoftLayer::API_PUBLIC_ENDPOINT
38 | end # module SoftLayer
39 |
40 | #
41 | # History:
42 | #
43 | # The history can be found in the CHANGELOG.textile file in the project root directory
44 |
--------------------------------------------------------------------------------
/spec/fixtures/Product_Package.json:
--------------------------------------------------------------------------------
1 | {"description":"
Quad Processor Multi-core Servers
","id":32,"name":"Quad Processor, Quad Core Intel","availableLocations":[{"isAvailable":0,"locationId":2,"packageId":32,"location":{"id":2,"longName":"Corporate HQ Lab","name":"dal00"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":3,"packageId":32,"location":{"id":3,"longName":"Dallas 1","name":"dal01"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":18171,"packageId":32,"location":{"id":18171,"longName":"Seattle","name":"sea01"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":37473,"packageId":32,"location":{"id":37473,"longName":"Washington, DC 1","name":"wdc01"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":138124,"packageId":32,"location":{"id":138124,"longName":"Dallas 5","name":"dal05"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":142776,"packageId":32,"location":{"id":142776,"longName":"Dallas 7","name":"dal07"}},{"isAvailable":1,"locationId":154820,"packageId":32,"location":{"id":154820,"longName":"Dallas 6","name":"dal06"}},{"deliveryTimeInformation":"Typical Installation time is 2 to 4 hours.","isAvailable":1,"locationId":168642,"packageId":32,"location":{"id":168642,"longName":"San Jose 1","name":"sjc01"}},{"isAvailable":1,"locationId":224092,"packageId":32,"location":{"id":224092,"longName":"Singapore 1","name":"sng01"}},{"isAvailable":1,"locationId":265592,"packageId":32,"location":{"id":265592,"longName":"Amsterdam 1","name":"ams01"}},{"isAvailable":1,"locationId":352494,"packageId":32,"location":{"id":352494,"longName":"Hong Kong 2","name":"hkg02"}}]}
--------------------------------------------------------------------------------
/examples/account_info.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | begin
28 | softlayer_client = SoftLayer::Client.new(
29 | # :username => "joecustomer", # enter your username here
30 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
31 | )
32 |
33 | # Demonstrates using the low-level capabilities of the softlayer_api
34 | # gem to get directly at methods in the SoftLayer API and extract
35 | # data from them.
36 | account_service = softlayer_client['Account'];
37 | account = account_service.getObject
38 |
39 | pp account
40 | rescue Exception => exception
41 | puts "Unable to retrieve account information: #{exception}"
42 | end
43 |
--------------------------------------------------------------------------------
/spec/BareMetalServer_spec.rb:
--------------------------------------------------------------------------------
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 |
8 |
9 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
10 |
11 | require 'rubygems'
12 | require 'softlayer_api'
13 | require 'rspec'
14 |
15 | require 'shared_server'
16 |
17 | describe SoftLayer::BareMetalServer do
18 | let (:sample_server) do
19 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
20 | allow(mock_client).to receive(:[]) do |service_name|
21 | service = mock_client.service_named(service_name)
22 | allow(service).to receive(:call_softlayer_api_with_params)
23 | service
24 | end
25 |
26 | SoftLayer::BareMetalServer.new(mock_client, { "id" => 12345 })
27 | end
28 |
29 | it "identifies itself with the SoftLayer_Hardware_Server service" do
30 | service = sample_server.service
31 | expect(service.server_object_id).to eq(12345)
32 | expect(service.target.service_name).to eq "SoftLayer_Hardware_Server"
33 | end
34 |
35 | it_behaves_like "server with port speed" do
36 | let (:server) { sample_server }
37 | end
38 |
39 | it_behaves_like "server with mutable hostname" do
40 | let (:server) { sample_server }
41 | end
42 |
43 | it "can be cancelled" do
44 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
45 | allow(mock_client).to receive(:[]) do |service_name|
46 | expect(service_name).to eq :Ticket
47 |
48 | service = mock_client.service_named(service_name)
49 | expect(service).to receive(:createCancelServerTicket).with(12345, 'Migrating to larger server', 'moving on up!', true, 'HARDWARE')
50 | allow(service).to receive(:call_softlayer_api_with_params)
51 | service
52 | end
53 |
54 | fake_server = SoftLayer::BareMetalServer.new(mock_client, { "id" => 12345 })
55 | fake_server.cancel!(:migrate_larger, 'moving on up!' )
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/softlayer_api.rb:
--------------------------------------------------------------------------------
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 | # requirements from the core libraries
8 | require 'date'
9 | require 'time'
10 |
11 | # Requirements for the Foundation Layer
12 | require 'softlayer/base'
13 | require 'softlayer/object_mask_helpers'
14 | require 'softlayer/APIParameterFilter'
15 | require 'softlayer/ObjectFilter'
16 | require 'softlayer/ObjectMaskParser'
17 | require 'softlayer/Config'
18 | require 'softlayer/Client'
19 | require 'softlayer/Service'
20 |
21 | # Requirements for the Model Layer
22 | require 'softlayer/ModelBase'
23 | require 'softlayer/Datacenter'
24 | require 'softlayer/DynamicAttribute'
25 | require 'softlayer/Account'
26 | require 'softlayer/AccountPassword'
27 | require 'softlayer/NetworkMonitor'
28 | require 'softlayer/Server'
29 | require 'softlayer/BareMetalServer'
30 | require 'softlayer/BareMetalServerOrder'
31 | require 'softlayer/BareMetalServerOrder_Package'
32 | require 'softlayer/ImageTemplate'
33 | require 'softlayer/NetworkComponent'
34 | require 'softlayer/NetworkMessageDelivery'
35 | require 'softlayer/NetworkService'
36 | require 'softlayer/NetworkStorageAllowedHost'
37 | require 'softlayer/NetworkStorageCredential'
38 | require 'softlayer/NetworkStorageGroup'
39 | require 'softlayer/NetworkStorage'
40 | require 'softlayer/ProductPackage'
41 | require 'softlayer/ProductItemCategory'
42 | require 'softlayer/ServerFirewall'
43 | require 'softlayer/ServerFirewallOrder'
44 | require 'softlayer/SoftwarePassword'
45 | require 'softlayer/Software'
46 | require 'softlayer/Ticket'
47 | require 'softlayer/UserCustomerExternalBinding'
48 | require 'softlayer/UserCustomer'
49 | require 'softlayer/VirtualDiskImage'
50 | require 'softlayer/VirtualDiskImageSoftware'
51 | require 'softlayer/VirtualServer'
52 | require 'softlayer/VirtualServerOrder'
53 | require 'softlayer/VirtualServerOrder_Package'
54 | require 'softlayer/VirtualServerUpgradeOrder'
55 | require 'softlayer/VLANFirewall'
56 | require 'softlayer/VLANFirewallOrder'
57 |
--------------------------------------------------------------------------------
/lib/softlayer/ObjectMaskProperty.rb:
--------------------------------------------------------------------------------
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 | # A class representing a SoftLayer Object's property as represented
10 | # in an Object Mask.
11 | #
12 | # The Object Mask Parser parses Object Mask Strings into ObjectMaskProperty
13 | # structures.
14 | #
15 | # Another useful property ObjectMaskProperty structures is that they can
16 | # can merge with compatible structures to create a new structure which
17 | # incorporates the properties of both, but in a streamlined construct
18 | #
19 | class ObjectMaskProperty
20 | attr_reader :name, :type
21 | attr_reader :children
22 |
23 | def initialize(name, type = nil)
24 | @name = name
25 | @type = type
26 | @children = []
27 | end
28 |
29 | def to_s
30 | full_name = @name
31 |
32 | if @type && !@type.empty?
33 | full_name += "(#{@type})"
34 | end
35 |
36 | if @children.count == 1
37 | full_name + ".#{@children[0].to_s}"
38 | elsif @children.count > 1
39 | full_name + "[#{@children.collect { |child| child.to_s }.join(',')}]"
40 | else
41 | full_name
42 | end
43 | end
44 |
45 | def can_merge_with? (other_property)
46 | (self.name == other_property.name) && (self.type == other_property.type)
47 | end
48 |
49 | def add_child(new_child)
50 | mergeable_child = @children.find { |existing_child| existing_child.can_merge_with? new_child }
51 | if mergeable_child
52 | mergeable_child.merge new_child
53 | else
54 | @children.push new_child
55 | end
56 | end
57 |
58 | def add_children(new_children)
59 | new_children.each { |new_child| add_child(new_child) }
60 | end
61 |
62 | # DANGER: assumes you've already checked can_merge_with? before calling this routine!
63 | def merge(other_property)
64 | add_children other_property.children
65 | end
66 | end
67 | end # module softlayer
--------------------------------------------------------------------------------
/examples/open_tickets.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | # One way to provide a username and api key is to provide them
28 | # as globals.
29 | # $SL_API_USERNAME = "joecustomer" # enter your username here
30 | # $SL_API_KEY = "feeddeadbeefbadf00d..." # enter your api key here
31 |
32 | # The client constructed here must get it's credentials from somewhere
33 | # In this script you might uncomment the globals above and assign your
34 | # credentials there
35 | SoftLayer::Client.default_client = SoftLayer::Client.new()
36 |
37 | # The openTickets routine will pick up the default client established above.
38 | open_tickets = SoftLayer::Ticket.open_tickets()
39 |
40 | open_tickets.sort!{ |lhs, rhs| -(lhs.lastEditDate <=> rhs.lastEditDate) }
41 | open_tickets.each do |ticket|
42 | printf "#{ticket.id} - #{ticket.title}"
43 |
44 | ticket.has_updates? ? printf("\t*\n") : printf("\n")
45 | end
46 |
--------------------------------------------------------------------------------
/spec/fixtures/test_bare_metal.json:
--------------------------------------------------------------------------------
1 | [{
2 | "accountId": "99999",
3 | "bareMetalInstanceFlag": 1,
4 | "domain": "example.pqz",
5 | "fullyQualifiedDomainName": "test_server_1.example.pqz",
6 | "hardwareStatusId": 5,
7 | "hostname": "test_server_1",
8 | "id": "124437",
9 | "manufacturerSerialNumber": "c4a2554a50567f0c13e951eaf56a06",
10 | "notes": "Test notes.",
11 | "serialNumber": "SL1696570",
12 | "serviceProviderId": 1,
13 | "serviceProviderResourceId": 186908,
14 | "hardwareStatus": {
15 | "id": 5,
16 | "status": "ACTIVE"
17 | },
18 | "networkManagementIpAddress": "0.88.0.24",
19 | "primaryBackendIpAddress": "0.88.0.23",
20 | "primaryIpAddress": "0.23.0.198",
21 | "privateIpAddress": "0.88.0.23"
22 | }, {
23 | "accountId": "99999",
24 | "bareMetalInstanceFlag": 0,
25 | "domain": "example.pqz",
26 | "fullyQualifiedDomainName": "test_server_2.example.pqz",
27 | "hardwareStatusId": 5,
28 | "hostname": "test_server_2",
29 | "id": "161282",
30 | "manufacturerSerialNumber": "1d41493be0db259bb59e4ba31b3f74",
31 | "serialNumber": "SL1861192",
32 | "serviceProviderId": 1,
33 | "serviceProviderResourceId": 125195,
34 | "globalIdentifier": "85450b0a-d86d-4144-8b4e-1918ef5061bb",
35 | "hardwareStatus": {
36 | "id": 5,
37 | "status": "ACTIVE"
38 | },
39 | "networkManagementIpAddress": "0.54.0.67",
40 | "primaryBackendIpAddress": "0.54.0.66",
41 | "primaryIpAddress": "0.23.0.2",
42 | "privateIpAddress": "0.54.0.66"
43 | }, {
44 | "accountId": "99999",
45 | "bareMetalInstanceFlag": 0,
46 | "domain": "example.pqz",
47 | "fullyQualifiedDomainName": "test_server_3.example.pqz",
48 | "hardwareStatusId": 5,
49 | "hostname": "test_server_3",
50 | "id": "116771",
51 | "manufacturerSerialNumber": "2f4ea9bdb51c378b5019efc6e03740",
52 | "serialNumber": "SL1110013",
53 | "serviceProviderId": 1,
54 | "serviceProviderResourceId": 84576,
55 | "globalIdentifier": "04192495-5228-439d-a014-d2a15a059023",
56 | "hardwareStatus": {
57 | "id": 5,
58 | "status": "ACTIVE"
59 | },
60 | "networkManagementIpAddress": "0.54.0.73",
61 | "primaryBackendIpAddress": "0.54.0.72",
62 | "primaryIpAddress": "0.23.0.6",
63 | "privateIpAddress": "0.54.0.72"
64 | }]
65 |
--------------------------------------------------------------------------------
/lib/softlayer/Datacenter.rb:
--------------------------------------------------------------------------------
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 | # A Data Center in the SoftLayer network
10 | #
11 | # This class corresponds to the SoftLayer_Location++ data type:
12 | #
13 | # http://sldn.softlayer.com/reference/datatypes/SoftLayer_Location
14 | #
15 | # Although in this context it is used to represent a data center and
16 | # not the more general class of locations that that data type can
17 | # represent.
18 |
19 | class Datacenter < SoftLayer::ModelBase
20 |
21 | ##
22 | # :attr_reader:
23 | # A short location description
24 | sl_attr :name
25 |
26 | ##
27 | # :attr_reader: long_name
28 | # A longer location description
29 | sl_attr :long_name, "longName"
30 |
31 | ##
32 | # Return the datacenter with the given name ('sng01' or 'dal05')
33 | def self.datacenter_named(name, client = nil)
34 | datacenters(client).find{ | datacenter | datacenter.name == name.to_s.downcase }
35 | end
36 |
37 | ##
38 | # Return a list of all the datacenters
39 | #
40 | # If the client parameter is not provided, the routine
41 | # will try to use Client::default_client. If no client
42 | # can be found, the routine will raise an exception
43 | #
44 | # This routine will only retrieve the list of datacenters from
45 | # the network once and keep it in memory unless you
46 | # pass in force_reload as true.
47 | #
48 | @@data_centers = nil
49 | def self.datacenters(client = nil, force_reload = false)
50 | softlayer_client = client || Client.default_client
51 | raise "Datacenter.datacenters requires a client to call the network API" if !softlayer_client
52 |
53 | if(!@@data_centers || force_reload)
54 | datacenters_data = softlayer_client[:Location].getDatacenters
55 | @@data_centers = datacenters_data.collect { | datacenter_data | self.new(softlayer_client, datacenter_data) }
56 | end
57 |
58 | @@data_centers
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/examples/ticket_info.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | softlayer_client = SoftLayer::Client.new(
28 | # :username => "joecustomer", # enter your username here
29 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
30 | )
31 |
32 | begin
33 | # Demonstrates using the low-level capabilities of the gem to get
34 | # at information. In this case we are talking directly to the ticket
35 | # service
36 | ticket_service = softlayer_client.service_named("Ticket");
37 |
38 | # Retrive a particular ticket by ID (you will have to substitute an existing ticket's ID here)
39 | ticket_ref = ticket_service.object_with_id(12345)
40 |
41 | # Retrive very specific information about the ticket
42 | ticket = ticket_ref.object_mask("mask[updates[entry,createDate],assignedUserId,attachedHardware.datacenter]").getObject
43 |
44 | pp ticket
45 | rescue Exception => exception
46 | puts "Unable to retrieve the ticket #{exception}"
47 | end
--------------------------------------------------------------------------------
/lib/softlayer/ObjectMaskTokenizer.rb:
--------------------------------------------------------------------------------
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 | require 'softlayer/ObjectMaskToken'
8 | require 'strscan'
9 |
10 | module SoftLayer
11 | #
12 | # This class is an implementation detail of the ObjectMaskParser
13 | #
14 | # It takes an Object Mask String and breaks it down
15 | # into ObjectMaskToken instances.
16 | #
17 | class ObjectMaskTokenizer
18 | ObjectMask_Token_Specs = [
19 | [/\[/, :property_set_start],
20 | [/\,/, :property_set_separator],
21 | [/\]/, :property_set_end],
22 | [/\(/, :property_type_start],
23 | [/\)/, :property_type_end],
24 | [/\./, :property_child_separator],
25 | [/[a-z][a-z0-9_]*/i, :identifier]
26 | ]
27 |
28 | def initialize(mask_string)
29 | @mask_string = mask_string.clone
30 | @scanner = StringScanner.new(@mask_string)
31 | @current_token = nil
32 | end
33 |
34 | def more_tokens?
35 | return @current_token == nil || !@current_token.end_of_string?
36 | end
37 |
38 | def current_token
39 | @current_token = next_token if !@current_token
40 | @current_token
41 | end
42 |
43 | def next_token
44 | # if we're at the end of the string, we keep returning the
45 | # EOS token
46 | if more_tokens? then
47 |
48 | if !@scanner.eos?
49 | # skip whitespace
50 | @scanner.skip(/\s+/)
51 |
52 | # search through the token specs to find which (if any) matches
53 | token_spec = ObjectMask_Token_Specs.find() do |token_spec|
54 | @scanner.check(token_spec[0])
55 | end
56 |
57 | # if a good token spec was found, set the current token to the one found
58 | if token_spec
59 | @current_token = ObjectMaskToken.new(token_spec.last, @scanner.scan(token_spec[0]))
60 | else
61 | @current_token = ObjectMaskToken.new(:invalid_token, @scanner.rest)
62 | @scanner.terminate
63 | end
64 | else
65 | @current_token = ObjectMaskToken.new(:eos)
66 | end
67 | end
68 |
69 | @current_token
70 | end
71 | end
72 | end # module SoftLayer
--------------------------------------------------------------------------------
/examples/account_servers.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | # We can set the default client to be our client and that way
28 | # we can avoid supplying it later
29 | SoftLayer::Client.default_client = SoftLayer::Client.new(
30 | # :username => "joecustomer" # enter your username here
31 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
32 | )
33 |
34 | account = SoftLayer::Account.account_for_client()
35 |
36 | # grab a list of all the servers on the account.
37 | servers = account.servers
38 |
39 | # measure their fully qualified domain names so we can print a pretty table
40 | max_name_len = servers.inject(0) { |max_name, server| [max_name, server.fullyQualifiedDomainName.length].max }
41 |
42 | printf "%#{-max_name_len}s\tPrimary Public IP\n", "Server FQDN"
43 | printf "%#{-max_name_len}s\t-----------------\n", "-----------"
44 |
45 | servers.each do |server|
46 | ip_field = server.primary_public_ip ? server.primary_public_ip : "No Public Interface"
47 | printf "%#{-max_name_len}s\t#{ip_field}\n", server.fullyQualifiedDomainName
48 | end
--------------------------------------------------------------------------------
/lib/softlayer/ObjectMaskToken.rb:
--------------------------------------------------------------------------------
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 is an implementation detail of the Object Mask Parser
10 | # It represents a single semantic token as parsed out of an
11 | # Object Mask String
12 | #
13 | # The class also generates error messages that the parser can use
14 | # when it encounters an unexpected token
15 | #
16 | class ObjectMaskToken
17 | attr_reader :type
18 | attr_reader :value
19 |
20 | KnownTokenTypes = [
21 | :invalid_token,
22 | :eos, # end of string
23 | :identifier,
24 | :property_set_start,
25 | :property_set_separator,
26 | :property_set_end,
27 | :property_type_start,
28 | :property_type_end,
29 | :property_child_separator,
30 | ]
31 |
32 | def initialize(token_type, token_value = nil)
33 | @type = token_type
34 | @value = token_value
35 | end
36 |
37 | def inspect
38 | "<#{@type.inspect}, #{@value.inspect}>"
39 | end
40 |
41 | def eql?(other_token)
42 | @type.eql?(other_token.type) && @value.eql?(other_token.value)
43 | end
44 |
45 | def invalid?
46 | return @type = :invalid_token
47 | end
48 |
49 | def end_of_string?
50 | return @type == :eos
51 | end
52 |
53 | def mask_root_marker?
54 | return @type == :identifier && (@value == "mask" || @value == "filterMask")
55 | end
56 |
57 | def valid_property_name?
58 | return @type == :identifier && @value.match(/\A[a-z][a-z0-9]*\z/i)
59 | end
60 |
61 | def valid_property_type?
62 | return @type == :identifier && @value.match(/\A[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*\z/i)
63 | end
64 |
65 | def self.error_for_unexpected_token(token)
66 | case token.type
67 | when :invalid_token
68 | "Unrecognized token '#{token.value}'"
69 | when :eos
70 | "Unexpected end of string"
71 | when :identifier
72 | "Unexpected identifier '#{token.value}'"
73 | when :property_set_start
74 | "Unexpected '['"
75 | when :property_set_separator
76 | "Unexpected ','"
77 | when :property_set_end
78 | "Unexpected ']'"
79 | when :property_type_start
80 | "Unexpected '('"
81 | when :property_type_end
82 | "Unexpected ')'"
83 | when :property_child_separator
84 | "Unexpected '.'"
85 | else
86 | "Unexpected value (invalid token type)"
87 | end
88 | end
89 | end
90 |
91 | end # module SoftLayer
--------------------------------------------------------------------------------
/spec/Ticket_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | require 'spec_helper'
14 |
15 | describe SoftLayer::Ticket do
16 | before (:each) do
17 | SoftLayer::Ticket.instance_eval { @ticket_subjects = nil }
18 | end
19 |
20 | it "retrieves ticket subjects from API once" do
21 | fakeTicketSubjects = fixture_from_json("ticket_subjects")
22 |
23 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key=> 'fakekey')
24 | allow(mock_client).to receive(:[]) do |service_name|
25 | expect(service_name).to eq :Ticket_Subject
26 |
27 | mock_service = SoftLayer::Service.new("SoftLayer_Ticket_Subject", :client => mock_client)
28 | expect(mock_service).to receive(:getAllObjects).once.and_return(fakeTicketSubjects)
29 | expect(mock_service).to_not receive(:call_softlayer_api_with_params)
30 |
31 | mock_service
32 | end
33 |
34 | expect(SoftLayer::Ticket.ticket_subjects(mock_client)).to be(fakeTicketSubjects)
35 |
36 | # call for the subjects again which should NOT re-request them from the client
37 | # (so :getAllObjects on the service should not be called again)
38 | expect(SoftLayer::Ticket.ticket_subjects(mock_client)).to be(fakeTicketSubjects)
39 | end
40 |
41 | it "raises an error if you try to get ticket subjects with no client" do
42 | SoftLayer::Client.default_client = nil
43 | expect {SoftLayer::Ticket.ticket_subjects() }.to raise_error(RuntimeError)
44 | end
45 |
46 | it "identifies itself with the ticket service" do
47 | fake_ticket_data = fixture_from_json("test_tickets").first
48 |
49 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key=> 'fakekey')
50 | allow(mock_client).to receive(:[]) do |service_name|
51 | expect(service_name).to eq :Ticket
52 | mock_service = SoftLayer::Service.new("SoftLayer_Ticket", :client => mock_client)
53 |
54 | # mock out call_softlayer_api_with_params so the service doesn't actually try to
55 | # communicate with the api endpoint
56 | allow(mock_service).to receive(:call_softlayer_api_with_params)
57 |
58 | mock_service
59 | end
60 |
61 | fake_ticket = SoftLayer::Ticket.new(mock_client, fake_ticket_data)
62 | ticket_service = fake_ticket.service
63 | expect(ticket_service.server_object_id).to eq(12345)
64 | expect(ticket_service.target.service_name).to eq "SoftLayer_Ticket"
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://badge.fury.io/rb/softlayer_api)
2 | [](https://travis-ci.org/SLsthompson/softlayer-ruby)
3 | [](https://coveralls.io/r/SLsthompson/softlayer-ruby?branch=master)
4 |
5 | # SoftLayer API for Ruby
6 |
7 | The softlayer-ruby project creates a [Ruby Gem](http://rubygems.org/gems/softlayer_api) which provides language bindings to the [SoftLayer API](http://sldn.softlayer.com/article/The_SoftLayer_API) for the [Ruby](http://www.ruby-lang.org) programming language.
8 |
9 | The heart of the project a foundation layer (represented by the Client and Service classes) that allows SoftLayer customers to make direct calls to the SoftLayer API. The Gem also builds a object heirarchy on top of that foundation which provides an abstract model which insulates scripts from direct dependency on the low-level details of the SoftLayer API.
10 |
11 | More comprehensive documentation on using the `softlayer_api` Gem, and contributing to the project, may be found by cloning this project and generating the documentation.
12 |
13 | ## Generating Documentation
14 |
15 | Once you have cloned the project, from the main project directory you can generate documentation by installing the bundle and using `rake`:
16 |
17 | $ cd softlayer-ruby
18 | $ bundle install
19 | $ bundle exec rake rdoc
20 |
21 | This will create a new folder named `doc` inside of the project source and populate it with project documentation. You may then open the documentation file `doc/index.html` in your browser to begin exploring the documentation.
22 |
23 | ## Author
24 |
25 | This software is written by the SoftLayer Development Team [sldn@softlayer.com](mailto:sldn@softlayer.com).
26 |
27 | Please join us at [Stack Overflow](https://stackoverflow.com/). Stack Overflow Best Practices Tip: Tag your posts with “SoftLayer” so our team can easily find your post.
28 |
29 | # Contributer License Agreement
30 |
31 | Contributions to the softlayer-ruby project require the submission of a contributer license agreement. Please generate the documentation and carefully refer to the Contribution Guide to participate.
32 |
33 | # Copyright and License
34 |
35 | The `softlayer_api` Ruby Gem and it's source files are Copyright © 2010-2014 [SoftLayer Technologies, Inc](http://www.softlayer.com/). The software is provided under an MIT license. Details of that license can be found in the embedded LICENSE.md file.
36 |
37 | #Warranty
38 |
39 | This software is provided “as is” and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose.
40 |
--------------------------------------------------------------------------------
/examples/order_server_firewall.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | # This is the id of the server you want to protect with a firewall.
28 | # The server can be Bare Metal or Virtual. It should have a public
29 | # network interface, and it should not already have a firewall on it.
30 | server_id = 257696 # 12345
31 |
32 | # In this example, we assume this is a Bare Metal Server
33 | is_virtual_server = false
34 |
35 | # Work with the SoftLayer API begins with a client. By setting
36 | # the "default" client we avoid having to specify the client repeatedly
37 | # in calls that follow.
38 | SoftLayer::Client.default_client = SoftLayer::Client.new(
39 | # :username => "joecustomer" # enter your username here
40 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
41 | )
42 |
43 | # in this case we go straight to the appropriate class to find the server
44 | # an alternative might be to create the account for this client and
45 | # search the list of servers for the one with the appropriate ID.
46 | if is_virtual_server
47 | server = SoftLayer::VirtualServer.server_with_id(server_id)
48 | else
49 | server = SoftLayer::BareMetalServer.server_with_id(server_id)
50 | end
51 |
52 | # Create an instance of SoftLayer::ServerFirewallOrder
53 | order = SoftLayer::ServerFirewallOrder.new(server)
54 |
55 | begin
56 | # this example calls order.verify which will build the order, submit it
57 | # to the network API, and will throw an exception if the order is
58 | # invalid.
59 | order.verify()
60 | puts "Firewall order is good for #{server.fullyQualifiedDomainName}"
61 | rescue => exception
62 | puts "Firewall order failed for #{server.fullyQualifiedDomainName} because #{exception}"
63 | end
--------------------------------------------------------------------------------
/examples/create_ticket.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | softlayer_client = SoftLayer::Client.new(
28 | # :username => "joecustomer" # enter your username here
29 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
30 | )
31 |
32 | begin
33 | # To create a ticket we have to assign the ticket to some user so
34 | # assign our new ticket to the current user
35 | account = SoftLayer::Account.account_for_client(softlayer_client)
36 | account_user = account.service.getCurrentUser
37 | my_user_id = account_user["id"]
38 |
39 | # We also need a subject for the ticket. Subjects are specified by id
40 | # This code prints out a table of all the ticket subjects with their
41 | # ids:
42 | ticket_subjects = SoftLayer::Ticket.ticket_subjects(softlayer_client)
43 | ticket_subjects.each do |subject|
44 | puts "#{subject['id']}\t#{subject['name']}"
45 | end
46 |
47 | # For this example we'll use 'Public Network Question' as the subject. That's id 1022
48 | public_network_question_id = 1022
49 |
50 | # A title is optional, but we'll provide one and we offer the body of the ticket
51 | # remember to pass the client to create_standard_ticket
52 | new_ticket = SoftLayer::Ticket.create_standard_ticket(
53 | :client => softlayer_client,
54 | :title => "This is a test ticket, please simply close it",
55 | :body => "This test ticket was created to test the Ruby API client. Please ignore it.",
56 | :subject_id => public_network_question_id,
57 | :assigned_user_id => my_user_id
58 | )
59 |
60 | puts "Created a new ticket : #{new_ticket.id} - #{new_ticket.title}"
61 |
62 | # we can also add an update to the ticket:
63 | new_ticket.update("This is a ticket update sent from the Ruby library")
64 |
65 | rescue Exception => exception
66 | $stderr.puts "An exception occurred while trying to complete the SoftLayer API calls #{exception}"
67 | end
68 |
--------------------------------------------------------------------------------
/spec/ModelBase_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::ModelBase do
14 | describe "#initialize" do
15 | it "rejects hashes without an id" do
16 | mock_client = double("Mock SoftLayer Client")
17 | expect { SoftLayer::ModelBase.new(mock_client, {}) }.to raise_error(ArgumentError)
18 | expect { SoftLayer::ModelBase.new(mock_client, {"id" => "someID"}) }.not_to raise_error
19 | end
20 |
21 | it "rejects models created with no client" do
22 | expect { SoftLayer::ModelBase.new(nil, nil) }.to raise_error(ArgumentError)
23 | end
24 |
25 | it "rejects nil hashes" do
26 | mock_client = double("Mock SoftLayer Client")
27 | expect { SoftLayer::ModelBase.new(mock_client, nil) }.to raise_error(ArgumentError)
28 | end
29 |
30 | it "remembers its first argument as the client" do
31 | mock_client = double("Mock SoftLayer Client")
32 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345"});
33 | expect(test_model.softlayer_client).to be(mock_client)
34 | end
35 | end
36 |
37 | it "allows access to raw softlayer properties" do
38 | mock_client = double("Mock SoftLayer Client")
39 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345"});
40 | expect(test_model[:id]).to eq("12345")
41 | expect(test_model['id']).to eq("12345")
42 | end
43 |
44 | it "allows access to exposed softlayer properties" do
45 | mock_client = double("Mock SoftLayer Client")
46 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345"});
47 | expect(test_model.id).to eq("12345")
48 | end
49 |
50 | it "returns nil from to_ary" do
51 | mock_client = double("Mock SoftLayer Client")
52 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345" })
53 | expect(test_model).to respond_to(:to_ary)
54 | expect(test_model.to_ary).to be_nil
55 | end
56 |
57 | it "realizes when low-level hash keys are added" do
58 | mock_client = double("Mock SoftLayer Client")
59 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345" })
60 | allow(test_model).to receive(:softlayer_properties) { { "id" => "12345", "newInfo" => "fun" } }
61 | expect(test_model.has_sl_property? :newInfo).to be(false)
62 | test_model.refresh_details()
63 | expect(test_model.has_sl_property? :newInfo).to be(true)
64 | end
65 |
66 | it "realizes when low-level hash keys are removed" do
67 | mock_client = double("Mock SoftLayer Client")
68 | test_model = SoftLayer::ModelBase.new(mock_client, { "id" => "12345", "newInfo" => "fun" })
69 | allow(test_model).to receive(:softlayer_properties) { { "id" => "12345" } }
70 | expect(test_model.has_sl_property? :newInfo).to be(true)
71 | test_model.refresh_details()
72 | expect(test_model.has_sl_property? :newInfo).to be(false)
73 | end
74 | end
--------------------------------------------------------------------------------
/lib/softlayer/NetworkStorageAllowedHost.rb:
--------------------------------------------------------------------------------
1 | module SoftLayer
2 | ##
3 | # Each SoftLayer NetworkStorageAllowedHost instance provides information about
4 | # a hosts allowed access to a storage product group.
5 | #
6 | # This class roughly corresponds to the entity SoftLayer_Network_Storage_Allowed_Host
7 | # in the API.
8 | #
9 | class NetworkStorageAllowedHost < ModelBase
10 | include ::SoftLayer::DynamicAttribute
11 |
12 | ##
13 | # :attr_reader:
14 | # The name of allowed host, usually an IQN or other identifier
15 | sl_attr :name
16 |
17 | ##
18 | # Retrieve the NetworkStorageGroup instances assigned to this host
19 | # :call-seq:
20 | # assigned_groups(force_update=false)
21 | sl_dynamic_attr :assigned_groups do |resource|
22 | resource.should_update? do
23 | #only retrieved once per instance
24 | @assigned_groups == nil
25 | end
26 |
27 | resource.to_update do
28 | assigned_groups = self.service.object_mask(NetworkStorageGroup.default_object_mask).getAssignedGroups
29 | assigned_groups.collect { |assigned_group| NetworkStorageGroup.new(softlayer_client, assigned_group) unless assigned_group.empty? }.compact
30 | end
31 | end
32 |
33 | ##
34 | # Retrieve the NetworkStorage instances assigned to this host
35 | # :call-seq:
36 | # assigned_volumes(force_update=false)
37 | sl_dynamic_attr :assigned_volumes do |resource|
38 | resource.should_update? do
39 | #only retrieved once per instance
40 | @assigned_volumes == nil
41 | end
42 |
43 | resource.to_update do
44 | assigned_volumes = self.service.object_mask(NetworkStorage.default_object_mask).getAssignedVolumes
45 | assigned_volumes.collect { |assigned_volume| NetworkStorage.new(softlayer_client, assigned_volume) unless assigned_volume.empty? }.compact
46 | end
47 | end
48 |
49 | ##
50 | # Retrieve the NetworkStorageCredential instance used to access NetworkStorage for this host
51 | # :call-seq:
52 | # credential(force_update=false)
53 | sl_dynamic_attr :credential do |resource|
54 | resource.should_update? do
55 | #only retrieved once per instance
56 | @credential == nil
57 | end
58 |
59 | resource.to_update do
60 | credential = self.service.object_mask(NetworkStorageCredential.default_object_mask).getCredential
61 | NetworkStorageCredential.new(softlayer_client, credential) unless credential.empty?
62 | end
63 | end
64 |
65 | ##
66 | # Returns the service for interacting with this network storage through the network API
67 | #
68 | def service
69 | softlayer_client[:Network_Storage_Allowed_Host].object_with_id(self.id)
70 | end
71 |
72 | protected
73 |
74 | def self.default_object_mask
75 | {
76 | "mask(SoftLayer_Network_Storage_Allowed_Host)" => [
77 | 'id',
78 | 'name'
79 | ]
80 | }.to_sl_object_mask
81 | end
82 | end
83 | end #SoftLayer
84 |
--------------------------------------------------------------------------------
/spec/ServerFirewall_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::ServerFirewall do
14 | describe "firewall rules bypass" do
15 | let(:mock_client) {
16 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
17 | }
18 |
19 | it "responds to the method change_routing_bypass!" do
20 | mock_firewall = SoftLayer::ServerFirewall.new("not really a client", { "id" => 12345 })
21 | expect(mock_firewall).to respond_to(:change_rules_bypass!)
22 | end
23 |
24 | it "accepts :apply_firewall_rules" do
25 | mock_firewall = SoftLayer::ServerFirewall.new(mock_client, {"id" => 12345})
26 | allow(mock_firewall).to receive(:rules) { {} }
27 |
28 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
29 |
30 | expect(firewall_update_service).to receive(:call_softlayer_api_with_params) do |method, parameters, arguments|
31 | expect(arguments[0]['bypassFlag']).to be(false)
32 | end
33 |
34 | mock_firewall.change_rules_bypass!(:apply_firewall_rules)
35 | end
36 |
37 | it "accepts :bypass_firewall_rules!" do
38 | mock_firewall = SoftLayer::ServerFirewall.new(mock_client, {"id" => 12345})
39 | allow(mock_firewall).to receive(:rules) { {} }
40 |
41 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
42 | expect(firewall_update_service).to receive(:call_softlayer_api_with_params) do |method, parameters, arguments|
43 | expect(arguments[0]['bypassFlag']).to be(true)
44 | end
45 |
46 | mock_firewall.change_rules_bypass!(:bypass_firewall_rules)
47 | end
48 |
49 | it "rejects other parameters (particularly true and false)" do
50 | mock_firewall = SoftLayer::ServerFirewall.new("not really a client", { "id" => 12345 })
51 | allow(mock_firewall).to receive(:rules) { {} }
52 |
53 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
54 |
55 | allow(firewall_update_service).to receive(:call_softlayer_api_with_params)
56 |
57 | expect{ mock_firewall.change_rules_bypass!(true) }.to raise_error(ArgumentError)
58 | expect{ mock_firewall.change_rules_bypass!(false) }.to raise_error(ArgumentError)
59 | expect{ mock_firewall.change_rules_bypass!(:route_around_firewall) }.to raise_error(ArgumentError)
60 | expect{ mock_firewall.change_rules_bypass!(:route_through_firewall) }.to raise_error(ArgumentError)
61 | expect{ mock_firewall.change_rules_bypass!("apply_firewall_rules") }.to raise_error(ArgumentError)
62 | expect{ mock_firewall.change_rules_bypass!("bypass_firewall_rules") }.to raise_error(ArgumentError)
63 | expect{ mock_firewall.change_rules_bypass!(nil) }.to raise_error(ArgumentError)
64 | expect{ mock_firewall.change_rules_bypass!(1) }.to raise_error(ArgumentError)
65 | expect{ mock_firewall.change_rules_bypass!(0) }.to raise_error(ArgumentError)
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/spec/Config_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | require 'tempfile'
14 |
15 | describe SoftLayer::Config do
16 |
17 | before :each do
18 | ENV.delete("SL_USERNAME")
19 | ENV.delete("SL_API_KEY")
20 | ENV.delete("SL_API_USER_AGENT")
21 | ENV.delete("SL_PROFILE")
22 | end
23 |
24 | it "retrieves config information from environment variables" do
25 | ENV.store("SL_USERNAME", "PoohBear")
26 | ENV.store("SL_API_KEY", "DEADBEEFBADF00D")
27 | ENV.store("SL_API_USER_AGENT", "de trackerz")
28 |
29 | expect(SoftLayer::Config.environment_settings).to eq({ :user_agent => "de trackerz", :username => "PoohBear", :api_key => "DEADBEEFBADF00D" })
30 | end
31 |
32 | it "retrieves the properties from a custom file" do
33 | file = Tempfile.new('properties_from_file')
34 | begin
35 | file.puts("[softlayer]")
36 | file.puts("username = PoohBear")
37 | file.puts("api_key = DEADBEEFBADF00D")
38 | file.puts("timeout = 40")
39 | file.close
40 |
41 | settings = SoftLayer::Config.file_settings(file.path)
42 | ensure
43 | file.close
44 | file.unlink
45 | end
46 |
47 | expect(settings[:username]).to eq("PoohBear")
48 | expect(settings[:api_key]).to eq("DEADBEEFBADF00D")
49 | expect(settings[:timeout]).to eq(40)
50 | end
51 |
52 | it "retrieves the properties from a custom file using a custom profile" do
53 | ENV.store("SL_PROFILE", "softlayer_qa")
54 | file = Tempfile.new('properties_from_file')
55 | begin
56 | file.puts("[softlayer_dev]")
57 | file.puts("username = PoohBear")
58 | file.puts("api_key = DEADBEEFBADF00D")
59 | file.puts("timeout = 40")
60 | file.puts("\n")
61 | file.puts("[softlayer_qa]")
62 | file.puts("username = Piglet")
63 | file.puts("api_key = MOOOOOOOO")
64 | file.puts("timeout = 60")
65 | file.puts("\n")
66 | file.puts("[softlayer_prod]")
67 | file.puts("username = Eeyore")
68 | file.puts("api_key = VEG_ALL_THE_WAY")
69 | file.puts("timeout = 80")
70 | file.close
71 |
72 | settings = SoftLayer::Config.file_settings(file.path)
73 | ensure
74 | file.close
75 | file.unlink
76 | end
77 |
78 | expect(settings[:username]).to eq("Piglet")
79 | expect(settings[:api_key]).to eq("MOOOOOOOO")
80 | expect(settings[:timeout]).to eq(60)
81 | end
82 |
83 | it "retrieves the timeout field as an integer when presented as a string" do
84 | file = Tempfile.new('config_test')
85 | begin
86 | file.puts("[softlayer]")
87 | file.puts("username = PoohBear")
88 | file.puts("api_key = DEADBEEFBADF00D")
89 | file.puts("timeout = 40")
90 | file.close
91 |
92 | settings = SoftLayer::Config.file_settings(file.path)
93 | ensure
94 | file.close
95 | file.unlink
96 | end
97 |
98 | expect(settings[:timeout]).to eq(40)
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/lib/softlayer/ServerFirewallOrder.rb:
--------------------------------------------------------------------------------
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
85 |
--------------------------------------------------------------------------------
/doc_src/Welcome.md:
--------------------------------------------------------------------------------
1 | # Welcome
2 |
3 | The `softlayer_api` Ruby Gem provides a convenient way to call into the SoftLayer API from the Ruby programming language. This is accomplished using the XML-RPC interface provided by SoftLayer and the XMLRPC client built into the core Ruby language.
4 |
5 | For more information about the SoftLayer API, and the routines and data structures it offers should visit the [SoftLayer Developer Network (SLDN) website](http://sldn.softlayer.com).
6 |
7 | This document is written for Ruby developers who wish to interact with their SoftLayer accounts through Ruby scripts. SoftLayer also offers a command line tool for performing common tasks. If prefer to use that tool, we invite you to look into the [command line interface](https://softlayer-python.readthedocs.org/en/latest/cli.html) that is part of the [git](http://github.com/softlayer/softlayer-python) project.
8 |
9 | This documentation is also written for Ruby developers who wish to contribute to the `softlayer_api` Gem. The Gem is a work in progress. We welcome the support of the SoftLayer development community to improve the Gem. The project is open source and we hope that source will serve as a useful library, stand as sample code to assist exploration, and serve as an opportunity for developers to shape it for both those needs.
10 |
11 | The primary repository for the Gem's source code is the [softlayer-ruby](http://github.com/softlayer/softlayer-ruby) github project.
12 |
13 | # Overview
14 |
15 | These Ruby language bindings allow access to the SoftLayer API on two different levels. A Foundation layer for low-level interaction with the SoftLayer API, and an abstraction layer that simplifies and isolates scripts from some of the details found in the Foundation.
16 |
17 | The Foundation layer, makes use of the [XMLRPC client](http://www.ruby-doc.org/stdlib-2.1.2/libdoc/xmlrpc/rdoc/XMLRPC/Client.html) which is part of the Core library of Ruby itself. This foundation is embodied primarily in the `SoftLayer::Client` and `SoftLayer::Service` classes. Requests are made, and responses retrieved using fundamental Ruby types such as Hashes, Arrays, Strings, and Integers.
18 |
19 | The Model layer is built atop the foundation as object class hierarchy. The class hierarchy models the structures found in the SoftLayer environment using the object-oriented features of Ruby. It does this to abstract out some of the implementation detail that a developer would commonly have to work with to communicate with SoftLayer through the foundation layer.
20 |
21 | The Model layer is by no means complete; quite to the contrary it is in its infancy and we believe that much of the development effort in the Gem will focus on incorporating new models into this layer. Because it is incomplete, however, we have put some effort into bridges from the functionality of the model, down to the lower level foundation, without trouble. Also, as a result of this, developers interested in using the Model layer should also should familiarize themselves with the Foundation.
22 |
23 | All developers should continue their exploration of the `softlayer_api` gem by examining the Foundation documentation. Clients that wish to make use of the abstractions provided in the object hierarchy may continue their exploration by looking at the Model Layer documentation. Developers who wish to expand the models found in the `softlayer_api` Gem should read the [Contribution Guide](ContributionGuide.md)
24 |
--------------------------------------------------------------------------------
/spec/VirtualServer_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | require 'shared_server'
14 |
15 | describe SoftLayer::VirtualServer do
16 | let(:sample_server) {
17 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
18 | allow(mock_client).to receive(:[]) do |service_name|
19 | service = mock_client.service_named(service_name)
20 | allow(service).to receive(:call_softlayer_api_with_params)
21 | service
22 | end
23 |
24 | SoftLayer::VirtualServer.new(mock_client, { "id" => 12345 })
25 | }
26 |
27 | it "identifies itself with the SoftLayer_Virtual_Guest service" do
28 | service = sample_server.service
29 | expect(service.server_object_id).to eq(12345)
30 | expect(service.target.service_name).to eq "SoftLayer_Virtual_Guest"
31 | end
32 |
33 | it "implements softlayer properties inherited from Server" do
34 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
35 |
36 | test_servers = fixture_from_json('test_virtual_servers')
37 | test_server = SoftLayer::VirtualServer.new(mock_client,test_servers.first)
38 |
39 | expect(test_server.hostname).to eq("test-server-1")
40 | expect(test_server.domain).to eq("softlayer-api-test.rb")
41 | expect(test_server.fullyQualifiedDomainName).to eq("test-server-1.softlayer-api-test.rb")
42 | expect(test_server.datacenter).to eq({"id"=>17936, "longName"=>"Dallas 6", "name"=>"dal06"})
43 | expect(test_server.primary_public_ip).to eq("198.51.100.121")
44 | expect(test_server.primary_private_ip).to eq("203.0.113.82")
45 | expect(test_server.notes).to eq("These are test notes")
46 | end
47 |
48 | it_behaves_like "server with port speed" do
49 | let (:server) { sample_server }
50 | end
51 |
52 | it_behaves_like "server with mutable hostname" do
53 | let (:server) { sample_server }
54 | end
55 |
56 | describe "component upgrades" do
57 | let(:mock_client) do
58 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
59 | virtual_guest_service = mock_client[:Virtual_Guest]
60 |
61 | allow(virtual_guest_service).to receive(:call_softlayer_api_with_params) do |api_method, parameters, api_arguments|
62 | api_return = nil
63 |
64 | case api_method
65 | when :getUpgradeItemPrices
66 | api_return = fixture_from_json('virtual_server_upgrade_options')
67 | else
68 | fail "Unexpected call to the SoftLayer_Virtual_Guest service"
69 | end
70 |
71 | api_return
72 | end
73 |
74 | mock_client
75 | end
76 |
77 | it "retrieves the item upgrades for a server from the API once" do
78 | fake_virtual_server = SoftLayer::VirtualServer.new(mock_client, {"id" => 12345})
79 | expect(fake_virtual_server.upgrade_options).to eq fixture_from_json('virtual_server_upgrade_options')
80 |
81 | # once we've retrieve the options once, we shouldn't be calling back into the service to get them again
82 | expect(mock_client[:Virtual_Guest]).to_not receive(:call_softlayer_api_with_params)
83 | fake_virtual_server.upgrade_options
84 | end
85 | end
86 | end
--------------------------------------------------------------------------------
/lib/softlayer/NetworkService.rb:
--------------------------------------------------------------------------------
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 | # Each SoftLayer NetworkService instance provides connectivity
10 | # information for a specific Network Service Resource.
11 | #
12 | # This class roughly corresponds to the entity SoftLayer_Network_Service_Resource
13 | # in the API.
14 | #
15 | class NetworkService < ModelBase
16 | include ::SoftLayer::DynamicAttribute
17 |
18 | ##
19 | # :attr_reader:
20 | # The name associated with this resource
21 | sl_attr :name
22 |
23 | ##
24 | # :attr_reader: private_ip
25 | # The backend IP address for this resource
26 | sl_attr :private_ip, 'backendIpAddress'
27 |
28 | ##
29 | # :attr_reader: public_ip
30 | # The frontend IP address for this resource
31 | sl_attr :public_ip, 'frontendIpAddress'
32 |
33 | ##
34 | # :attr_reader: ssh_username
35 | # The ssh username of for this resource
36 | sl_attr :ssh_username, 'sshUsername'
37 |
38 | ##
39 | # Retrieve the datacenter that this network service resource is available in
40 | # :call-seq:
41 | # datacenter(force_update=false)
42 | sl_dynamic_attr :datacenter do |resource|
43 | resource.should_update? do
44 | #only retrieved once per instance
45 | @datacenter == nil
46 | end
47 |
48 | resource.to_update do
49 | Datacenter::datacenter_named(self['datacenter']['name'], self.softlayer_client)
50 | end
51 | end
52 |
53 | ##
54 | # Returns the api properties used to connect to the network service resource
55 | #
56 | def api
57 | {
58 | 'host' => self['apiHost'],
59 | 'password' => self['apiPassword'],
60 | 'path' => self['apiPath'],
61 | 'port' => self['apiPort'],
62 | 'protocol' => self['apiProtocol'],
63 | 'username' => self['apiUsername']
64 | }
65 | end
66 |
67 | ##
68 | # Returns the network service resource type name
69 | #
70 | def type
71 | self['type']['type']
72 | end
73 |
74 | protected
75 |
76 | def self.default_object_mask
77 | {
78 | "mask(SoftLayer_Network_Service_Resource)" => [
79 | 'apiHost',
80 | 'apiPassword',
81 | 'apiPath',
82 | 'apiPort',
83 | 'apiProtocol',
84 | 'apiUsername',
85 | 'backendIpAddress',
86 | 'datacenter',
87 | 'frontendIpAddress',
88 | 'id',
89 | 'name',
90 | 'networkDevice.id',
91 | 'sshUsername',
92 | 'type.type'
93 | ]
94 | }.to_sl_object_mask
95 | end
96 | end
97 | end #SoftLayer
98 |
--------------------------------------------------------------------------------
/lib/softlayer/VLANFirewallOrder.rb:
--------------------------------------------------------------------------------
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 VLAN
10 | #
11 | class VLANFirewallOrder
12 | ##
13 | # The VLAN that you are ordering the firewall for.
14 | attr_reader :vlan_id
15 |
16 | ##
17 | # Set high_availability to true if you want redundant
18 | # firewall devices (defaults to false, no high_availability)
19 | attr_accessor :high_availability
20 |
21 | ##
22 | # Create a new order for the given VLAN
23 | # Note that the vlan_id is NOT the same as the vlan number.
24 | def initialize (vlan_id, client = nil)
25 | @softlayer_client = client || Client.default_client
26 | raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !@softlayer_client
27 |
28 | @vlan_id = vlan_id
29 | @high_availability = false
30 | end
31 |
32 | ##
33 | # Calls the SoftLayer API to verify that the template provided by this order is valid
34 | # This routine will return the order template generated by the API or will throw an exception
35 | #
36 | # This routine will not actually create a Bare Metal Instance and will not affect billing.
37 | #
38 | # If you provide a block, it will receive the order template as a parameter and
39 | # the block may make changes to the template before it is submitted.
40 | def verify()
41 | order_template = firewall_order_template
42 | order_template = yield order_template if block_given?
43 |
44 | @softlayer_client[:Product_Order].verifyOrder(order_template)
45 | end
46 |
47 | ##
48 | # Calls the SoftLayer API to place an order for a new server based on the template in this
49 | # order. If this succeeds then you will be billed for the new server.
50 | #
51 | # If you provide a block, it will receive the order template as a parameter and
52 | # the block may make changes to the template before it is submitted.
53 | def place_order!()
54 | order_template = firewall_order_template
55 | order_template = yield order_template if block_given?
56 |
57 | @softlayer_client[:Product_Order].placeOrder(order_template)
58 | end
59 |
60 | protected
61 |
62 | ##
63 | # Returns a hash of the creation options formatted to be sent to
64 | # the SoftLayer API for either verification or completion
65 | def firewall_order_template
66 | client = @softlayer_client
67 | additional_products_package = SoftLayer::ProductPackage.additional_products_package(client)
68 |
69 | template = {
70 | 'complexType' => 'SoftLayer_Container_Product_Order_Network_Protection_Firewall_Dedicated',
71 | 'quantity' => 1,
72 | 'packageId' => additional_products_package.id,
73 | 'vlanId' => @vlan_id
74 | }
75 |
76 | if @high_availability
77 | expected_description = "Hardware Firewall (High Availability)"
78 | else
79 | expected_description = "Hardware Firewall (Dedicated)"
80 | end
81 |
82 | firewall_items = additional_products_package.items_with_description(expected_description)
83 |
84 | raise "Could not find a price item matching the description '#{expected_description}'" if firewall_items.empty?
85 |
86 | firewall_item = firewall_items[0]
87 |
88 | template['prices'] = [{ 'id' => firewall_item.price_id }] if firewall_item.respond_to?(:price_id)
89 |
90 | template
91 | end
92 | end # class VLANFirewallOrder
93 | end # module SoftLayer
94 |
--------------------------------------------------------------------------------
/lib/softlayer/UserCustomerExternalBinding.rb:
--------------------------------------------------------------------------------
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 | # Each SoftLayer UserCustomerExternalBinding instance provides information
10 | # for a single user customer's external binding.
11 | #
12 | # This class roughly corresponds to the entity SoftLayer_User_Customer_External_Binding
13 | # in the API.
14 | #
15 | class UserCustomerExternalBinding < ModelBase
16 | include ::SoftLayer::DynamicAttribute
17 |
18 | ##
19 | # :attr_reader:
20 | # The flag that determines whether the external binding is active will be
21 | # used for authentication or not.
22 | sl_attr :active
23 |
24 | ##
25 | # :attr_reader: created_at
26 | # The date that the external authentication binding was created.
27 | sl_attr :created_at, 'createDate'
28 |
29 | ##
30 | # :attr_reader: created
31 | # The date that the external authentication binding was created.
32 | # DEPRECATION WARNING: This attribute is deprecated in favor of created_at
33 | # and will be removed in the next major release.
34 | sl_attr :created, 'createDate'
35 |
36 | ##
37 | # :attr_reader:
38 | # The password used to authenticate the external id at an external
39 | # authentication source.
40 | sl_attr :password
41 |
42 | ##
43 | # Retrieve an optional note for identifying the external binding.
44 | # :call-seq:
45 | # note(force_update=false)
46 | sl_dynamic_attr :note do |resource|
47 | resource.should_update? do
48 | #only retrieved once per instance
49 | @note == nil
50 | end
51 |
52 | resource.to_update do
53 | self.service.getNote
54 | end
55 | end
56 |
57 | ##
58 | # Retrieve the user friendly name of a type of external authentication binding.
59 | # :call-seq:
60 | # type(force_update=false)
61 | sl_dynamic_attr :type do |resource|
62 | resource.should_update? do
63 | #only retrieved once per instance
64 | @type == nil
65 | end
66 |
67 | resource.to_update do
68 | type = self.service.getType
69 | type['name']
70 | end
71 | end
72 |
73 | ##
74 | # Retrieve the user friendly name of an external binding vendor.
75 | # :call-seq:
76 | # vendor(force_update=false)
77 | sl_dynamic_attr :vendor do |resource|
78 | resource.should_update? do
79 | #only retrieved once per instance
80 | @vendor == nil
81 | end
82 |
83 | resource.to_update do
84 | vendor = self.service.getVendor
85 | vendor['name']
86 | end
87 | end
88 |
89 | ##
90 | # Returns the service for interacting with this user customer external binding
91 | # through the network API
92 | #
93 | def service
94 | softlayer_client[:User_Customer_External_Binding].object_with_id(self.id)
95 | end
96 |
97 | protected
98 |
99 | def self.default_object_mask
100 | {
101 | "mask(SoftLayer_User_Customer_External_Binding)" => [
102 | 'active',
103 | 'createDate',
104 | 'id',
105 | 'password'
106 | ]
107 | }.to_sl_object_mask
108 | end
109 | end
110 | end #SoftLayer
111 |
--------------------------------------------------------------------------------
/spec/ProductPackage_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::ProductPackage do
14 | it "requests packages by key name" do
15 | client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
16 | product_package_service = client[:Product_Package]
17 |
18 | expect(product_package_service).to receive(:call_softlayer_api_with_params) do |method_name, parameters, args|
19 | expect(method_name).to be(:getAllObjects)
20 | expect(parameters.server_object_filter).to_not be_nil
21 | expect(args).to be_empty
22 |
23 | []
24 | end
25 |
26 | SoftLayer::ProductPackage.packages_with_key_name('FAKE_KEY_NAME', client)
27 | end
28 |
29 | it "identifies itself with the Product_Package service" do
30 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
31 | allow(mock_client).to receive(:[]) do |service_name|
32 | expect(service_name).to eq :Product_Package
33 | mock_service = SoftLayer::Service.new("SoftLayer_Product_Package", :client => mock_client)
34 |
35 | # mock out call_softlayer_api_with_params so the service doesn't actually try to
36 | # communicate with the api endpoint
37 | allow(mock_service).to receive(:call_softlayer_api_with_params)
38 |
39 | mock_service
40 | end
41 |
42 | fake_package = SoftLayer::ProductPackage.new(mock_client, {"id" => 12345})
43 | expect(fake_package.service.server_object_id).to eq(12345)
44 | expect(fake_package.service.target.service_name).to eq "SoftLayer_Product_Package"
45 | end
46 |
47 | describe "class methods for getting to packages" do
48 | let(:mock_client) do
49 | client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
50 | product_package_service = client[:Product_Package]
51 |
52 | allow(product_package_service).to receive(:call_softlayer_api_with_params).with(:getAllObjects, instance_of(SoftLayer::APIParameterFilter), []).and_return([fixture_from_json("Product_Package")])
53 | client
54 | end
55 |
56 | it "calling with a client should work fine" do
57 | expect { SoftLayer::ProductPackage.packages_with_key_name('BARE_METAL_CORE', mock_client) }.to_not raise_error
58 | expect { SoftLayer::ProductPackage.virtual_server_package(mock_client) }.to_not raise_error
59 | expect { SoftLayer::ProductPackage.bare_metal_instance_package(mock_client) }.to_not raise_error
60 | expect { SoftLayer::ProductPackage.bare_metal_server_packages(mock_client) }.to_not raise_error
61 | end
62 |
63 | it "calling with default client should work" do
64 | SoftLayer::Client.default_client = mock_client
65 | expect { SoftLayer::ProductPackage.packages_with_key_name('BARE_METAL_CORE') }.to_not raise_error
66 | expect { SoftLayer::ProductPackage.virtual_server_package() }.to_not raise_error
67 | expect { SoftLayer::ProductPackage.bare_metal_instance_package() }.to_not raise_error
68 | expect { SoftLayer::ProductPackage.bare_metal_server_packages() }.to_not raise_error
69 | SoftLayer::Client.default_client = nil
70 | end
71 |
72 | it "calling with no client should raise" do
73 | SoftLayer::Client.default_client = nil
74 | expect { SoftLayer::ProductPackage.packages_with_key_name('BARE_METAL_CORE') }.to raise_error(RuntimeError)
75 | expect { SoftLayer::ProductPackage.virtual_server_package() }.to raise_error(RuntimeError)
76 | expect { SoftLayer::ProductPackage.bare_metal_instance_package() }.to raise_error(RuntimeError)
77 | expect { SoftLayer::ProductPackage.bare_metal_server_packages() }.to raise_error(RuntimeError)
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/object_mask_helpers_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe String, "#_to_sl_object_mask_property" do
14 | it "converts to the string itself" do
15 | expect("blah"._to_sl_object_mask_property).to eql("blah")
16 | expect(" blah"._to_sl_object_mask_property).to eql("blah")
17 | expect(" blah "._to_sl_object_mask_property).to eql("blah")
18 | expect(" blah \t\n"._to_sl_object_mask_property).to eql("blah")
19 | end
20 |
21 | it "echos the empty string" do
22 | expect(""._to_sl_object_mask_property).to eql("")
23 | end
24 | end
25 |
26 | describe Array,"#_to_sl_object_mask_property" do
27 | it "converts an empty array to the empty string" do
28 | expect([]._to_sl_object_mask_property).to eql("")
29 | end
30 |
31 | it "handles simple arrays" do
32 | expect(["foo", "bar", "baz"]._to_sl_object_mask_property).to eql("foo,bar,baz")
33 | end
34 |
35 | it "flattens inner arrays to simple lists" do
36 | expect(["foo", ["bar", "baz"]]._to_sl_object_mask_property).to eql("foo,bar,baz")
37 | end
38 |
39 | it "handles nils in the array" do
40 | expect(["foo", nil, "bar"]._to_sl_object_mask_property()).to eql("foo,bar")
41 | end
42 | end
43 |
44 | describe Hash, "#_to_sl_object_mask_property" do
45 | it "returns the empty string for an empty hash" do
46 | expect({}._to_sl_object_mask_property).to eql("")
47 | end
48 |
49 | it "constructs a dot expression for a simple string value" do
50 | expect({"foo" => "foobar"}._to_sl_object_mask_property).to eql("foo.foobar")
51 | end
52 |
53 | it "builds a bracket expression with array values" do
54 | expect({"foo" => ["one", "two", "three"]}._to_sl_object_mask_property).to eql("foo[one,two,three]")
55 | end
56 |
57 | it "builds bracket expressions for nested hashes" do
58 | expect({"foo" => {"sub" => "resub"}}._to_sl_object_mask_property).to eql("foo[sub.resub]")
59 | end
60 |
61 | it "resolves simple inner values to simple dot expressions" do
62 | expect({"top" => [ "middle1", {"middle2" => "end"}]}._to_sl_object_mask_property).to eql("top[middle1,middle2.end]")
63 | end
64 |
65 | it "accepts an inner empty hash and returns a mask" do
66 | expect({ "ipAddress" => { "ipAddress" => {}}}._to_sl_object_mask_property).to eql("ipAddress[ipAddress]")
67 | end
68 | end
69 |
70 | describe Hash, "#to_sl_object_mask" do
71 | it "rejects the empty hash" do
72 | expect { {}.to_sl_object_mask }.to raise_error(RuntimeError)
73 | end
74 |
75 | it "constructs a dot expression for a simple string value" do
76 | expect({"mask" => "foobar"}.to_sl_object_mask).to eql("mask.foobar")
77 | end
78 |
79 | it "builds a bracket expression with array values" do
80 | expect({"mask" => ["one", "two", "three"]}.to_sl_object_mask).to eql("mask[one,two,three]")
81 | end
82 |
83 | it "builds bracket expressions for nested hashes" do
84 | expect({"mask(some_type)" => {"sub" => "resub"}}.to_sl_object_mask).to eql("mask(some_type)[sub.resub]")
85 | end
86 |
87 | it "resolves simple inner values to simple dot expressions" do
88 | expect({"mask" => [ "middle1", {"middle2" => "end"}]}.to_sl_object_mask).to eql("mask[middle1,middle2.end]")
89 | end
90 |
91 | it "accepts an inner empty hash and returns a mask" do
92 | expect({ "mask" => { "ipAddress" => {}}}.to_sl_object_mask).to eql("mask[ipAddress]")
93 | end
94 |
95 | it "converts masks with different roots" do
96 | object_mask = { "mask" => { "ipAddress" => {}},
97 | "mask(duck_type)" => {"webbed" => "feet"}}.to_sl_object_mask
98 |
99 | expect(["[mask[ipAddress],mask(duck_type)[webbed.feet]]", "mask(duck_type)[webbed.feet],[mask[ipAddress]]"].find(object_mask)).to_not be_nil
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/spec/ObjectMaskProperty_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'rspec'
11 | require 'softlayer/ObjectMaskProperty'
12 |
13 | describe SoftLayer::ObjectMaskProperty do
14 | it "obtains a name when created" do
15 | property = SoftLayer::ObjectMaskProperty.new("propertyName")
16 | expect(property.name).to eq "propertyName"
17 | expect(property.type).to be_nil
18 | expect(property.children).to eq []
19 | end
20 |
21 | it "may obtain a type when created" do
22 | property = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
23 | expect(property.name).to eq "propertyName"
24 | expect(property.type).to eq "SomeType"
25 | expect(property.children).to eq []
26 | end
27 |
28 | it "knows it can merge with properties that have the same name" do
29 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", nil)
30 | property2 = SoftLayer::ObjectMaskProperty.new("propertyName", nil)
31 |
32 | expect(property1.can_merge_with?(property2)).to be(true)
33 | end
34 |
35 | it "knows it can merge with properties that have the same name and type" do
36 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
37 | property2 = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
38 |
39 | expect(property1.can_merge_with?(property2)).to be(true)
40 | end
41 |
42 | it "knows it cannot merge if the names don't match" do
43 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", nil)
44 | property2 = SoftLayer::ObjectMaskProperty.new("someOtherName", nil)
45 |
46 | expect(property1.can_merge_with?(property2)).to be(false)
47 | end
48 |
49 | it "knows it cannot merge if the types don't match" do
50 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
51 | property2 = SoftLayer::ObjectMaskProperty.new("propertyName", "AnotherType")
52 |
53 | expect(property1.can_merge_with?(property2)).to be(false)
54 | end
55 |
56 | it "collects children" do
57 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
58 | property2 = SoftLayer::ObjectMaskProperty.new("propertyName", "AnotherType")
59 |
60 | property1.add_children([property2])
61 | expect(property1.children.count).to eq 1
62 | expect(property1.children[0]).to eq property2
63 | end
64 |
65 | it "collects children" do
66 | property1 = SoftLayer::ObjectMaskProperty.new("propertyName", "SomeType")
67 | property2 = SoftLayer::ObjectMaskProperty.new("propertyName", "AnotherType")
68 |
69 | property1.add_children([property2])
70 | property1.add_children([property2])
71 |
72 | expect(property1.children.count).to eq 1
73 | expect(property1.children[0]).to eq property2
74 | end
75 |
76 | it "merges children" do
77 | property1 = SoftLayer::ObjectMaskProperty.new("parent")
78 | first_child = SoftLayer::ObjectMaskProperty.new("child")
79 | first_subchild = SoftLayer::ObjectMaskProperty.new("first_subchild")
80 |
81 | first_child.add_child(first_subchild)
82 | property1.add_child(first_child)
83 |
84 | property2 = SoftLayer::ObjectMaskProperty.new("parent")
85 | second_child = SoftLayer::ObjectMaskProperty.new("child")
86 | second_subchild = SoftLayer::ObjectMaskProperty.new("second_subchild")
87 |
88 | second_child.add_child(second_subchild)
89 | property2.add_child(second_child)
90 |
91 | expect(property1.can_merge_with?(property2)).to be(true)
92 | property1.add_children(property2.children)
93 |
94 | expect(property1.children.count).to eq 1
95 | child = property1.children[0]
96 | expect(child.name).to eq "child"
97 | expect(child.children.count).to eq 2
98 | expect(child.children).to include(first_subchild)
99 | expect(child.children).to include(second_subchild)
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/examples/order_virtual_server.rb:
--------------------------------------------------------------------------------
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 'rubygems'
24 | require 'softlayer_api'
25 | require 'pp'
26 |
27 | begin
28 | # This sample walks through the creation of a Virtual Server.
29 | # It explores techniques for discovering what configuration options
30 | # exist, puts together an order, then sends that order to
31 | # SoftLayer for verification.
32 |
33 | client = SoftLayer::Client.new(
34 | # :username => "joecustomer" # enter your username here
35 | # :api_key => "feeddeadbeefbadf00d..." # enter your api key here
36 | )
37 |
38 | # We begin by creating a VirtualServerOrder and filling out the hostname and domain
39 | # attributes.
40 | server_order = SoftLayer::VirtualServerOrder.new(client)
41 | server_order.hostname = "server1"
42 | server_order.domain = "ruby-api-test.org"
43 |
44 | # We must tell the system in which datacenter we want our server created
45 | # We can ask the class to give us a list of options:
46 | puts SoftLayer::VirtualServerOrder.datacenter_options(client).inspect
47 |
48 | # The list will look something like ["ams01", "dal01", "dal05",...
49 | # Let's put our server in the 'dal05' (Dallas 5) datacenter
50 | server_order.datacenter = SoftLayer::Datacenter.datacenter_named 'dal05', client
51 |
52 | # The order must know how many computing cores we want in our virtual
53 | # server. Again we can ask the class for options. The result will
54 | # be something like [1, 2, 4, 8, 12, 16]
55 | # 2 sounds like a good number of cores for our simple server
56 | puts SoftLayer::VirtualServerOrder.core_options(client).inspect
57 | server_order.cores = 2
58 |
59 | # We must indicate how much memory the virtual server should have.
60 | # Again we can query for options and select a good value
61 | puts SoftLayer::VirtualServerOrder.memory_options(client).inspect
62 | server_order.memory = 2 #GB
63 |
64 | # Similarly we can choose an operating system for the server:
65 | puts SoftLayer::VirtualServerOrder.os_reference_code_options(client).inspect
66 | server_order.os_reference_code = 'CENTOS_6_64'
67 |
68 | # Finally, in spite of the fact that our server is simple, we want it
69 | # to have a blazing fast connection speed. Let's look at the options and choose
70 | # the fastest! (it's probably 1 Gbps)
71 | server_order.max_port_speed = SoftLayer::VirtualServerOrder.max_port_speed_options(client).max
72 |
73 | # The server order is now complete. This sample will ask it to verify itself with the
74 | # SoftLayer ordering system, but a simple change from verify to place_order! would ask
75 | # the system to provision the server (and charge it to our account)
76 | begin
77 | server_order.verify
78 | puts "The server order is OK"
79 | rescue Exception => e
80 | puts "The server order failed verification :-( -- #{e}"
81 | end
82 | rescue Exception => exception
83 | $stderr.puts "An exception occurred while trying to complete the SoftLayer API calls #{exception}"
84 | end
85 |
86 |
--------------------------------------------------------------------------------
/spec/APIParameterFilter_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::APIParameterFilter do
14 | let(:filter) {filter = SoftLayer::APIParameterFilter.new(nil)}
15 |
16 | describe "#object_with_id" do
17 | it "initializes with empty properties" do
18 | expect(filter.server_object_id).to be_nil
19 | expect(filter.server_object_mask).to be_nil
20 | end
21 |
22 | it "rejects nil object masks" do
23 | expect { filter.object_mask(nil) }.to raise_error(ArgumentError)
24 | end
25 |
26 | it "stores its value in server_object_id when called " do
27 | result = filter.object_with_id(12345)
28 | expect(result.server_object_id).to eq 12345
29 | expect(result.parameters).to eq({:server_object_id => 12345})
30 | end
31 |
32 | it "allows call chaining with object_mask " do
33 | result = filter.object_with_id(12345).object_mask("mask.fish", "mask.cow", "mask.duck")
34 | expect(result.server_object_id).to eq 12345
35 | expect(result.server_object_mask.to_s).to eq "mask[fish,cow,duck]"
36 | end
37 | end
38 |
39 | describe "#object_mask" do
40 | it "rejects nil object masks" do
41 | expect { filter.object_mask(nil) }.to raise_error(ArgumentError)
42 | end
43 |
44 | it "rejects calls that pass things other than strings" do
45 | expect { filter.object_mask(['anArray']) }.to raise_error(ArgumentError)
46 | expect { filter.object_mask({"a" => "hash"}) }.to raise_error(ArgumentError)
47 | expect { filter.object_mask(Object.new) }.to raise_error(ArgumentError)
48 | end
49 |
50 | it "accepts strings representing a property set" do
51 | masked_filter = nil
52 |
53 | expect { masked_filter = filter.object_mask("[mask.firstProperty, mask.secondProperty]") }.to_not raise_error
54 | expect(masked_filter.server_object_mask).to eq "mask[firstProperty,secondProperty]"
55 | end
56 |
57 | it "stores its value in server_object_mask when called" do
58 | result = filter.object_mask("mask.fish", "mask[cow]", "mask(typed).duck", "mask(typed)[chicken]")
59 | expect(result.server_object_mask).to eq '[mask[fish,cow],mask(typed)[duck,chicken]]'
60 | end
61 |
62 | it "allows call chaining with object_with_id" do
63 | result = filter.object_mask("mask.fish", "mask[cow]", "mask(typed).duck", "mask(typed)[chicken]").object_with_id(12345)
64 | expect(result.server_object_id).to eq 12345
65 | expect(result.server_object_mask).to eq "[mask[fish,cow],mask(typed)[duck,chicken]]"
66 | end
67 |
68 | it "allows call chaining with other object masks" do
69 | result = filter.object_mask("mask.fish").object_mask("mask[cow]").object_mask("mask(typed).duck").object_mask("mask(typed)[chicken]")
70 | expect(result.server_object_mask).to eq "[mask[fish,cow],mask(typed)[duck,chicken]]"
71 | end
72 | end
73 |
74 | describe "#object_filter" do
75 | it "rejects nil filters" do
76 | expect { filter.object_filter(nil) }.to raise_error(ArgumentError)
77 | end
78 |
79 | it "stores its value in server_object_filter when called" do
80 | test_filter = SoftLayer::ObjectFilter.new()
81 | test_filter.set_criteria_for_key_path("fish", "cow")
82 |
83 | result = filter.object_filter(test_filter)
84 | expect(result.server_object_filter).to eq({"fish" => "cow"})
85 | end
86 | end
87 |
88 | describe "#method_missing" do
89 | it "invokes call_softlayer_api_with_params(method_name, self, args, &block) on its target with itself and the method_missing parameters" do
90 | target = double("method_missing_target")
91 |
92 | filter = SoftLayer::APIParameterFilter.new(target).object_mask("mask.fish", "mask[cow]", "mask(typed).duck", "mask(typed)[chicken]").object_with_id(12345)
93 | expect(target).to receive(:call_softlayer_api_with_params).with(:getObject, filter, ['marshmallow'])
94 |
95 | filter.getObject("marshmallow")
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/lib/softlayer/object_mask_helpers.rb:
--------------------------------------------------------------------------------
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 | ##
8 | # This extension to the Hash class to allows object masks to be constructed
9 | # from built-in Ruby types and converted to object masks strings for presentation
10 | # to the SoftLayer API
11 | class Hash
12 | # Given a hash, generate an Object Mask string from the structure
13 | # found within the hash. This allows object masks to be constructed
14 | # as hashes, then converted to strings when they must be passed
15 | # to the API. The routine does some very rudimentary validation to
16 | # ensure that the hash represents a valid object mask, but care must
17 | # still be taken when constructing the hash.
18 | def to_sl_object_mask()
19 | raise RuntimeError, "An object mask must contain properties" if empty?
20 | raise RuntimeError, "An object mask must start with root properties" if keys().find { |key| !__valid_root_property_key?(key) }
21 |
22 | key_strings = __sl_object_mask_properties_for_keys();
23 | key_strings.count > 1 ? "[#{key_strings.join(',')}]" : "#{key_strings[0]}"
24 | end
25 |
26 | # Returns a string representing the hash as a property within a larger
27 | # object mask. This routine is an implementation detail used in the conversion
28 | # of hashes to object mask strings. You should not have to call this method directly.
29 | def _to_sl_object_mask_property()
30 | key_strings = __sl_object_mask_properties_for_keys();
31 | "#{key_strings.join(',')}"
32 | end
33 |
34 | private
35 |
36 | def __valid_root_property_key?(key_string)
37 | return key_string == "mask" || (0 == (key_string =~ /\Amask\([a-z][a-z0-9_]*\)\z/i))
38 | end
39 |
40 | def __sl_object_mask_properties_for_keys
41 | key_strings = [];
42 |
43 | each do |key, value|
44 | return "" if !value
45 |
46 | string_for_key = key._to_sl_object_mask_property
47 |
48 | if value.kind_of?(String) || value.kind_of?(Symbol) then
49 | string_for_key = "#{string_for_key}.#{value._to_sl_object_mask_property}"
50 | end
51 |
52 | if value.kind_of?(Array) || value.kind_of?(Hash) then
53 | value_string = value._to_sl_object_mask_property
54 | if value_string && !value_string.empty?
55 | string_for_key = "#{string_for_key}[#{value_string}]"
56 | end
57 | end
58 |
59 | key_strings.push(string_for_key)
60 | end
61 |
62 | key_strings
63 | end
64 | end
65 |
66 | ##
67 | # SoftLayer Extensions to the Array class to support using arrays to create
68 | # object masks
69 | class Array
70 | # Returns a string representing the object mask content represented by the
71 | # Array. Each value in the array is converted to its object mask equivalent
72 | # This routine is an implementation detail used in the conversion of hashes
73 | # to object mask strings. You should not have to call this method directly.
74 | def _to_sl_object_mask_property()
75 | return "" if self.empty?
76 | property_content = map { |item| item ? item._to_sl_object_mask_property() : nil }.compact.flatten.join(",")
77 | "#{property_content}"
78 | end
79 | end
80 |
81 | ##
82 | # SoftLayer Extensions to the String class to support using strings to create
83 | # object masks
84 | class String
85 | # Returns a string representing the object mask content represented by the
86 | # String. Strings are simply represented as copies of themselves. We make
87 | # a copy in case the original String is modified somewhere along the way
88 | # This routine is an implementation detail used in the conversion of hashes
89 | # to object mask strings. You should not have to call this method directly.
90 | def _to_sl_object_mask_property()
91 | return self.strip
92 | end
93 | end
94 |
95 | ##
96 | # SoftLayer Extensions to the Symbol class to support using symbols to create
97 | # object masks
98 | class Symbol
99 | # Converts the Symbol to a string, then converts the string to an
100 | # object mask property. This routine is an implementation detail used in
101 | # the conversion of hashes to object mask strings. You should not have to
102 | # call this method directly.
103 | def _to_sl_object_mask_property()
104 | self.to_s._to_sl_object_mask_property()
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/spec/fixtures/test_tickets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "assignedUser": {
4 | "firstName": "Jonathan",
5 | "lastName": "Doe",
6 | "username": "slfakeuser"
7 | },
8 | "awaitingUserResponseFlag": false,
9 | "createDate": "2013-07-11T12:52:57-06:00",
10 | "id": 12345,
11 | "lastEditDate": "2013-07-11T12:53:59-06:00",
12 | "newUpdatesFlag": false,
13 | "serverAdministrationFlag": 0,
14 | "serviceProvider": {
15 | "description": "Infrastructure as a Service including Dedicated, Virtualized, and Cloud Hosting",
16 | "id": 1,
17 | "keyName": "SOFTLAYER",
18 | "name": "SoftLayer Technologies, Inc."
19 | },
20 | "serviceProviderResourceId": 7129684,
21 | "status": {
22 | "id": 1001
23 | },
24 | "title": "What's in a title anyway? Tickets are exciting!"
25 | },
26 | {
27 | "assignedUser": {
28 | "firstName": "Stephen",
29 | "lastName": "Everyman",
30 | "username": "SLEveryman"
31 | },
32 | "awaitingUserResponseFlag": false,
33 | "createDate": "2013-07-12T16:48:35-06:00",
34 | "id": 12346,
35 | "lastEditDate": "2013-07-12T17:16:05-06:00",
36 | "newUpdatesFlag": false,
37 | "serverAdministrationFlag": 0,
38 | "serviceProvider": {
39 | "description": "Infrastructure as a Service including Dedicated, Virtualized, and Cloud Hosting",
40 | "id": 1,
41 | "keyName": "SOFTLAYER",
42 | "name": "SoftLayer Technologies, Inc."
43 | },
44 | "serviceProviderResourceId": 7135016,
45 | "status": {
46 | "id": 1001
47 | },
48 | "title": "A fake title used in the place of a real title"
49 | },
50 | {
51 | "assignedUser": {
52 | "firstName": "Joe",
53 | "lastName": "Kool",
54 | "username": "sljcool"
55 | },
56 | "awaitingUserResponseFlag": false,
57 | "createDate": "2013-07-25T07:38:07-06:00",
58 | "id": 12347,
59 | "lastEditDate": "2013-07-25T08:25:25-06:00",
60 | "newUpdatesFlag": false,
61 | "serverAdministrationFlag": 0,
62 | "serviceProvider": {
63 | "description": "Infrastructure as a Service including Dedicated, Virtualized, and Cloud Hosting",
64 | "id": 1,
65 | "keyName": "SOFTLAYER",
66 | "name": "SoftLayer Technologies, Inc."
67 | },
68 | "serviceProviderResourceId": 7194676,
69 | "status": {
70 | "id": 1001
71 | },
72 | "title": "A riveting ticket that you can't put down"
73 | },
74 | {
75 | "assignedUser": {
76 | "firstName": "Joe",
77 | "lastName": "Kool",
78 | "username": "sljcool"
79 | },
80 | "awaitingUserResponseFlag": false,
81 | "createDate": "2013-07-30T09:51:44-06:00",
82 | "id": 12348,
83 | "lastEditDate": "2013-07-30T09:56:04-06:00",
84 | "newUpdatesFlag": false,
85 | "serverAdministrationFlag": 0,
86 | "serviceProvider": {
87 | "description": "Infrastructure as a Service including Dedicated, Virtualized, and Cloud Hosting",
88 | "id": 1,
89 | "keyName": "SOFTLAYER",
90 | "name": "SoftLayer Technologies, Inc."
91 | },
92 | "serviceProviderResourceId": 7236607,
93 | "status": {
94 | "id": 1001
95 | },
96 | "title": "A moderately interesting ticket"
97 | },
98 | {
99 | "assignedUser": {
100 | "firstName": "Ralf",
101 | "lastName": "Cramden",
102 | "username": "slsomeguy"
103 | },
104 | "awaitingUserResponseFlag": false,
105 | "createDate": "2013-07-31T14:34:50-06:00",
106 | "id": 12349,
107 | "lastEditDate": "2013-07-31T14:42:34-06:00",
108 | "newUpdatesFlag": false,
109 | "serverAdministrationFlag": 1
110 | ,
111 | "serviceProvider": {
112 | "description": "Infrastructure as a Service including Dedicated, Virtualized, and Cloud Hosting",
113 | "id": 1,
114 | "keyName": "SOFTLAYER",
115 | "name": "SoftLayer Technologies, Inc."
116 | },
117 | "serviceProviderResourceId": 7243384,
118 | "status": {
119 | "id": 1001
120 | },
121 | "title": "A really boring ticket that you don't care about"
122 | }
123 | ]
124 |
--------------------------------------------------------------------------------
/lib/softlayer/NetworkMessageDelivery.rb:
--------------------------------------------------------------------------------
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 | # Each SoftLayer NetworkMessageDelivery instance provides information about
10 | # the username/password combination for a specific Network Message Delivery
11 | # account.
12 | #
13 | # This class roughly corresponds to the entity SoftLayer_Network_Message_Delivery
14 | # in the API.
15 | #
16 | class NetworkMessageDelivery < ModelBase
17 | include ::SoftLayer::DynamicAttribute
18 |
19 | ##
20 | # :attr_reader: created_at
21 | # The date this username/password pair was created.
22 | sl_attr :created_at, 'createDate'
23 |
24 | ##
25 | # :attr_reader: created
26 | # The date this username/password pair was created.
27 | # DEPRECATION WARNING: This attribute is deprecated in favor of created_at
28 | # and will be removed in the next major release.
29 | sl_attr :created, 'createDate'
30 |
31 | ##
32 | # :attr_reader: modified_at
33 | # The date of the last modification to this username/password pair.
34 | sl_attr :modified_at, 'modifyDate'
35 |
36 | ##
37 | # :attr_reader: modified
38 | # The date of the last modification to this username/password pair.
39 | # DEPRECATION WARNING: This attribute is deprecated in favor of modified_at
40 | # and will be removed in the next major release.
41 | sl_attr :modified, 'modifyDate'
42 |
43 | ##
44 | # :attr_reader:
45 | # The password part of the username/password pair.
46 | sl_attr :password
47 |
48 | ##
49 | # :attr_reader:
50 | # The username part of the username/password pair.
51 | sl_attr :username
52 |
53 | ##
54 | # Retrieve the message delivery type description of a network message delivery account.
55 | # :call-seq:
56 | # description(force_update=false)
57 | sl_dynamic_attr :description do |resource|
58 | resource.should_update? do
59 | #only retrieved once per instance
60 | @description == nil
61 | end
62 |
63 | resource.to_update do
64 | type = self.service.getType
65 | type['description']
66 | end
67 | end
68 |
69 | ##
70 | # Retrieve the message delivery type name of a network message delivery account.
71 | # :call-seq:
72 | # name(force_update=false)
73 | sl_dynamic_attr :name do |resource|
74 | resource.should_update? do
75 | #only retrieved once per instance
76 | @name == nil
77 | end
78 |
79 | resource.to_update do
80 | type = self.service.getType
81 | type['name']
82 | end
83 | end
84 |
85 | ##
86 | # Retrieve the vendor name for a network message delivery account.
87 | # :call-seq:
88 | # vendor(force_update=false)
89 | sl_dynamic_attr :vendor do |resource|
90 | resource.should_update? do
91 | #only retrieved once per instance
92 | @vendor == nil
93 | end
94 |
95 | resource.to_update do
96 | vendor = self.service.getVendor
97 | vendor['name']
98 | end
99 | end
100 |
101 | ##
102 | # Updates the password for the current account password.
103 | #
104 | def password=(password)
105 | raise ArgumentError, "The new password cannot be nil" unless password
106 | raise ArgumentError, "The new password cannot be empty" if password.empty?
107 |
108 | self.service.editObject({ "password" => password.to_s })
109 | self.refresh_details()
110 | end
111 |
112 | ##
113 | # Returns the service for interacting with the network message delivery instance
114 | # through the network API
115 | #
116 | def service
117 | softlayer_client[:Network_Message_Delivery].object_with_id(self.id)
118 | end
119 |
120 | ##
121 | # Make an API request to SoftLayer and return the latest properties hash
122 | # for this object.
123 | #
124 | def softlayer_properties(object_mask = nil)
125 | my_service = self.service
126 |
127 | if(object_mask)
128 | my_service = my_service.object_mask(object_mask)
129 | else
130 | my_service = my_service.object_mask(self.class.default_object_mask)
131 | end
132 |
133 | my_service.getObject()
134 | end
135 |
136 | protected
137 |
138 | def self.default_object_mask
139 | {
140 | "mask(SoftLayer_Network_Message_Delivery)" => [
141 | 'createDate',
142 | 'id',
143 | 'modifyDate',
144 | 'password',
145 | 'username'
146 | ]
147 | }.to_sl_object_mask
148 | end
149 | end
150 | end #SoftLayer
151 |
--------------------------------------------------------------------------------
/lib/softlayer/ModelBase.rb:
--------------------------------------------------------------------------------
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 | # The SoftLayer Gem defines an Object Hierarchy representing entities in
10 | # an account's SoftLayer environment. This class is the base object class
11 | # for objects in that hierarchy
12 | #
13 | # The SoftLayer API represents entities as a hash of properties. This class
14 | # stores that hash and allows the use of subscripting to access those properties
15 | # directly.
16 | #
17 | # The class also has a model for making network requests that will refresh
18 | # the stored hash so that it reflects the most up-to-date information about
19 | # an entity from the server. Subclasses should override softlayer_properties
20 | # to retrieve information from the server. Client code should call
21 | # refresh_details to ask an object to update itself.
22 | #
23 | class ModelBase
24 | # The client environment that this model object belongs to
25 | attr_reader :softlayer_client
26 |
27 | ##
28 | # :attr_reader: id
29 | # The unique identifier of this object within its API service
30 |
31 | # Construct a new model object in the environment of the given client and
32 | # with the given hash of network data (presumably returned by the SoftLayer API)
33 | def initialize(softlayer_client, network_hash)
34 | raise ArgumentError, "A hash is required" if nil == network_hash
35 | raise ArgumentError, "Model objects must be created in the context of a client" if nil == softlayer_client
36 |
37 | @softlayer_client = softlayer_client
38 | @softlayer_hash = network_hash
39 |
40 | raise ArgumentError, "The hash used to construct a softlayer model object must have an id" unless has_sl_property?(:id)
41 | raise ArgumentError, "id must be non-nil and non-empty" unless self[:id]
42 | end
43 |
44 | ##
45 | # The service method of a Model object should return a SoftLayer Service
46 | # that best represents the modeled object. For example, a Ticket models
47 | # a particular entity in the SoftLayer_Ticket service. The particular
48 | # entity is identified by its id so the Ticket class would return
49 | #
50 | # softlayer_client[:Ticket].object_with_id
51 | #
52 | # which is a service which would allow calls to the ticket service
53 | # through that particular object.
54 | def service
55 | raise "Abstract method service in ModelBase was called"
56 | end
57 |
58 | ##
59 | # Asks a model object to reload itself from the SoftLayer API.
60 | #
61 | # Subclasses should not override this method, rather they should
62 | # implement softlayer_properties to actually make the API request
63 | # and return the new hash.
64 | #
65 | def refresh_details(object_mask = nil)
66 | @softlayer_hash = self.softlayer_properties(object_mask)
67 | end
68 |
69 | ##
70 | # Returns the value of of the given property as stored in the
71 | # softlayer_hash. This gives you access to the low-level, raw
72 | # properties that underlie this model object. The need for this
73 | # is not uncommon, but using this method should still be done
74 | # with deliberation.
75 | def [](softlayer_property)
76 | self.softlayer_hash[softlayer_property.to_s]
77 | end
78 |
79 | ##
80 | # Returns true if the given property can be found in the softlayer hash
81 | def has_sl_property?(softlayer_property)
82 | softlayer_hash && softlayer_hash.has_key?(softlayer_property.to_s)
83 | end
84 |
85 | ##
86 | # allows subclasses to define attributes as sl_attr
87 | # sl_attr are attributes that draw their value from the
88 | # low-level hash representation of the object.
89 | def self.sl_attr(attribute_symbol, hash_key = nil)
90 | raise "The sl_attr expects a symbol for the attribute to define" unless attribute_symbol.kind_of?(Symbol)
91 | raise "The hash key used to define an attribute cannot be empty" if hash_key && hash_key.empty?
92 |
93 | define_method(attribute_symbol.to_sym) { self[hash_key ? hash_key : attribute_symbol.to_s]}
94 | end
95 |
96 | sl_attr :id
97 |
98 | # When printing to the console using puts, ruby will call the
99 | # to_ary method trying to convert an object into an array of lines
100 | # for stdio. We override to_ary to return nil for model objects
101 | # so they may be printed
102 | def to_ary()
103 | return nil;
104 | end
105 |
106 | protected
107 |
108 | ##
109 | # Subclasses should implement this method as part of enabling the
110 | # refresh_details functionality The implementation should make a request
111 | # to the SoftLayer API and retrieve an up-to-date SoftLayer hash
112 | # representation of this object. That hash should be the return value
113 | # of this routine.
114 | #
115 | def softlayer_properties(object_mask = nil)
116 | raise "Abstract method softlayer_properties in ModelBase was called"
117 | end
118 |
119 | ##
120 | # The softlayer_hash stores the low-level information about an
121 | # object as it was retrieved from the SoftLayer API.
122 | attr_reader :softlayer_hash
123 |
124 | end # class ModelBase
125 | end # module SoftLayer
126 |
--------------------------------------------------------------------------------
/lib/softlayer/ObjectMaskParser.rb:
--------------------------------------------------------------------------------
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 | require "softlayer/ObjectMaskTokenizer"
8 | require "softlayer/ObjectMaskProperty"
9 |
10 | module SoftLayer
11 | class ObjectMaskParserError < RuntimeError
12 | end
13 |
14 | #
15 | # A parser that can examine and validate SoftLayer Object Mask strings
16 | #
17 | # The Object Mask Parser parses Object Mask Strings into ObjectMaskProperty
18 | # structures.
19 | #
20 | # The Object Mask parser allows the Gem to merge Object Mask Strings
21 | # to avoid errors from the SoftLayer API server about duplicate properties being
22 | # provided when the same property is provided in different Object Masks
23 | #
24 | class ObjectMaskParser
25 | attr_reader :stack
26 |
27 | def initialize()
28 | @stack = []
29 | end
30 |
31 | def parse(mask_string)
32 | @tokenizer = ObjectMaskTokenizer.new(mask_string)
33 |
34 | token = @tokenizer.current_token
35 | if token.type == :identifier
36 | property = parse_property(@tokenizer)
37 | elsif token.type == :property_set_start
38 | property_set = parse_property_set(@tokenizer)
39 | else
40 | 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)
41 | end
42 |
43 | recognize_token(@tokenizer, :eos, "Extraneous text after object mask: ")
44 |
45 | if property && (property.name != "mask" && property.name != "filterMask")
46 | raise ObjectMaskParserError, "Object Mask must begin with a 'mask' or 'filterMask' root property"
47 | end
48 |
49 | if property_set && property_set.find { |subproperty| subproperty.name != 'mask' && subproperty.name != 'filterMask' }
50 | raise ObjectMaskParserError, "A root property set must contain only 'mask' or 'filterMask' root properties"
51 | end
52 |
53 | property || property_set
54 | end
55 |
56 | def parse_property_set(tokenizer)
57 | token = recognize_token(tokenizer, :property_set_start, "Expected '[': ")
58 | property_sequence = parse_property_sequence(tokenizer)
59 | token = recognize_token(tokenizer, :property_set_end, "Expected ']': ")
60 | property_sequence
61 | end
62 |
63 | def parse_property_sequence(tokenizer)
64 | first_property = parse_property(tokenizer)
65 |
66 | other_children = []
67 | token = tokenizer.current_token
68 | if(token.type.equal?(:property_set_separator))
69 | # skip the separator
70 | tokenizer.next_token
71 |
72 | # find another property
73 | other_children = parse_property_sequence(tokenizer)
74 | end
75 |
76 | return other_children.unshift(first_property)
77 | end
78 |
79 | def parse_property (tokenizer)
80 | property_name = nil
81 | property_type = nil
82 | property_children = nil
83 |
84 | property_name = parse_property_name(tokenizer)
85 |
86 | # look for a property type
87 | property_type = nil
88 | token = tokenizer.current_token
89 | if(token.type.equal?(:property_type_start))
90 | property_type = parse_property_type(tokenizer)
91 | end
92 |
93 | token = tokenizer.current_token
94 | if(token.type.equal?(:property_child_separator))
95 | property_children = [ parse_property_child(tokenizer) ]
96 | elsif (token.type.equal?(:property_set_start))
97 | property_children = parse_property_set(tokenizer)
98 | end
99 |
100 | new_property = ObjectMaskProperty.new(property_name, property_type)
101 | new_property.add_children(property_children) if property_children
102 |
103 | return new_property
104 | end
105 |
106 | def parse_property_child(tokenizer)
107 | token = recognize_token(tokenizer, :property_child_separator, "Expected a '.': ")
108 | parse_property(tokenizer)
109 | end
110 |
111 | def parse_property_name(tokenizer)
112 | token = recognize_token(tokenizer, :identifier, "Expected a valid property type: ") { |token| token.valid_property_name? }
113 | return token.value
114 | end
115 |
116 | def parse_property_type(tokenizer)
117 | token = recognize_token(tokenizer, :property_type_start, "Expected '(': ")
118 | property_type = parse_property_type_name(tokenizer)
119 | token = recognize_token(tokenizer, :property_type_end, "Expected ')': ")
120 | return property_type
121 | end
122 |
123 | def parse_property_type_name(tokenizer)
124 | token = recognize_token(tokenizer, :identifier, "Expected a valid property type: ") { |token| token.valid_property_type? }
125 | return token.value
126 | end
127 |
128 | def recognize_token(tokenizer, expected_type, error_string, &predicate)
129 | token = tokenizer.current_token
130 | if token.type.equal?(expected_type) && (!predicate || predicate.call(token))
131 | tokenizer.next_token
132 | else
133 | raise ObjectMaskParserError, error_string + ObjectMaskToken.error_for_unexpected_token(token)
134 | token = nil;
135 | end
136 |
137 | return token
138 | end
139 |
140 | end
141 | end # Module SoftLayer
142 |
--------------------------------------------------------------------------------
/spec/DynamicAttribute_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | # class TestClass
14 | # include SoftLayer::DynamicAttribute
15 | #
16 | # sl_dynamic_attr(:test_attribute) do |attribute|
17 | # attribute.refresh_every(5 * 60) #refresh every 5 minutes
18 | # attribute.update do
19 | # "test_attribute value!"
20 | # end
21 | # end
22 | # end
23 |
24 | describe SoftLayer::DynamicAttribute do
25 | context "when included in a class" do
26 | before do
27 | class TestClass
28 | include SoftLayer::DynamicAttribute
29 | end
30 | end
31 |
32 | it "defines ::sl_dynamic_attr in the class" do
33 | expect(TestClass).to respond_to(:sl_dynamic_attr)
34 | end
35 |
36 | it "defines ::sl_dynamic_attr_definition in the class" do
37 | expect(TestClass).to respond_to(:sl_dynamic_attr_definition)
38 | end
39 | end
40 |
41 | describe "::sl_dynamic_attr" do
42 | before do
43 | class TestClass
44 | include SoftLayer::DynamicAttribute
45 | sl_dynamic_attr(:test_attribute) { |test_attribute| }
46 | end
47 | end
48 |
49 | it "adds a attribute definition" do
50 | expect(TestClass.sl_dynamic_attr_definition(:test_attribute)).to_not be_nil
51 | end
52 |
53 | it "adds a attribute getter to instances" do
54 | sample_instance = TestClass.new()
55 | expect(sample_instance).to respond_to(:test_attribute)
56 |
57 | expect(sample_instance.test_attribute).to be_nil
58 | end
59 |
60 | it "adds a predicate to check for updates" do
61 | sample_instance = TestClass.new()
62 | expect(sample_instance).to respond_to(:should_update_test_attribute?)
63 | end
64 |
65 | it "adds a method to perform updates" do
66 | sample_instance = TestClass.new()
67 | expect(sample_instance).to respond_to(:update_test_attribute!)
68 | end
69 | end
70 |
71 | describe "a simple attribute definition" do
72 | before do
73 | class TestClass
74 | include SoftLayer::DynamicAttribute
75 | sl_dynamic_attr(:test_attribute) do |rsrc|
76 | rsrc.to_update do
77 | method_in_test_class_instance_context
78 | "Value update accomplished!"
79 | end
80 | end
81 | def method_in_test_class_instance_context
82 | nil
83 | end
84 | end # TestClass
85 | end
86 |
87 | it "should obtain an updated value when called" do
88 | sample_instance = TestClass.new
89 |
90 | expect(sample_instance.instance_variable_defined?(:@test_attribute)).to be(false)
91 |
92 | expect(sample_instance).to receive(:method_in_test_class_instance_context)
93 | expect(sample_instance.test_attribute).to eq "Value update accomplished!"
94 | expect(sample_instance.instance_variable_defined?(:@test_attribute)).to be(true)
95 | expect(sample_instance.instance_variable_get(:@test_attribute)).to eq "Value update accomplished!"
96 | end
97 | end
98 |
99 | describe "a attribute with delayed update" do
100 | before do
101 | class TestClass
102 | include SoftLayer::DynamicAttribute
103 | sl_dynamic_attr(:test_attribute) do |rsrc|
104 | rsrc.should_update? do
105 | @last_test_attribute_update ||= Time.at(0)
106 | (Time.now - @last_test_attribute_update) > 0.5 # update once a second
107 | end
108 |
109 | rsrc.to_update do
110 | @last_test_attribute_update = Time.now
111 | Time.now
112 | end
113 | end
114 | end # TestClass
115 | end
116 |
117 | it "should obtain an updated value when called" do
118 | sample_instance = TestClass.new
119 | last_update = sample_instance.test_attribute
120 | next_update = sample_instance.test_attribute
121 | expect(next_update).to eq last_update
122 | sleep(0.75)
123 | final_update = sample_instance.test_attribute
124 | expect(final_update).to_not eq last_update
125 | end
126 | end
127 |
128 | describe SoftLayer::DynamicAttribute::DynamicAttributeDefinition do
129 | let(:test_definition) do
130 | SoftLayer::DynamicAttribute::DynamicAttributeDefinition.new(:test_attribute)
131 | end
132 |
133 | it "raises an exception if passed an invalid name" do
134 | expect { SoftLayer::DynamicAttribute::DynamicAttributeDefinition.new(nil) }.to raise_error(ArgumentError)
135 | expect { SoftLayer::DynamicAttribute::DynamicAttributeDefinition.new("") }.to raise_error(ArgumentError)
136 | end
137 |
138 | it "has valid initial values" do
139 | expect(test_definition.attribute_name).to be(:test_attribute)
140 | expect(test_definition.update_block).to_not be_nil
141 | end
142 |
143 | it "allows DSL syntax" do
144 | test_definition.should_update? { "Yea!" }
145 | test_definition.to_update do
146 | "test_attribute value!"
147 | end
148 |
149 | expect(test_definition.update_block.call).to eq "test_attribute value!"
150 | expect(test_definition.should_update_block.call).to eq "Yea!"
151 | end
152 | end
153 | end
154 |
--------------------------------------------------------------------------------
/CHANGELOG.textile:
--------------------------------------------------------------------------------
1 | *3.2.3*
2 | * Support for profiles.
3 | * Support for HVM.
4 |
5 |
6 | *3.2.2*
7 | * Fix NoMethodError when getting the datacenters out of an ImageTemplate and the value was nil.
8 | * Fix NoMethodError when getting public and private images with a result limit of 1.
9 |
10 | *3.2.1*
11 | * Fix a crashing issue where a Bare Metal server order tried to retrieve the hardware ordered before it has been provisioned.
12 |
13 | *3.2*
14 | * Add password-based authentication with `SoftLayer::Client.with_password(username: '...', password: '...', ...)`.
15 |
16 | *3.1.0*
17 | * Many SoftLayer Attributes are now shadowed with snake_case names. The old CamelCase versions are deprecated and will be removed in the next major release
18 | * New functionality related to network monitoring has been added
19 |
20 | *3.0*
21 | * Substantially rewrote the ObjectFilter class. ObjectFilters used to be hashes which made it easy to manipulate their content incorrectly. The new implementation has a strict interface that makes it harder to manipulate filters incorrectly.
22 | * Added a model for Virtual Server Image Templates (SoftLayer::ImageTemplate) - VirtualServerOrder now requires an instance of this class rather than allowing you to provide the global_id of an image
23 | * Added a model for data centers (SoftLayer::Datacenter). Bare Metal, Bare Metal Package, and Virtual server orders now use an instance of Datacenter to identify where their servers will be provisioned. The routines in those classes which used to provide lists of valid data center names now return data center objects.
24 | * Virtual Server Upgrades are now handled by the VirtualServerUpgradeOrder class and not the VirtualServer class. This change was made for several reasons. Firt and foremost, it allows multiple aspects of a virtual server to be upgraded at once without having to wait on separate transactions to complete between upgrades. Secondly it opens the door for additional upgrades (for example, to disk configuration) to be added in the future.
25 | * Added a method to reboot servers.
26 | * The routine to retreive the open tickets on an account has been moved from the Ticket class. The set of open tickets is now a dynamic property of an account object.
27 | * The Model Layer now includes models for Server (aka. Shared) and VLAN (aka. Dedicated) firewalls in the ServerFirewall, and VLANFireall classes respectively. There are corresponding classes for ordering firewalls (ServerFirewallOrder and VLANFirewallOrder). To facilitate the process of locating the 'id' for a firewall, the Account class includes the find_VLAN_with_number routine which lets you look up the segments of a firewall from the VLAN nubmer.
28 |
29 | *2.2.2*
30 | * Fixed a bug in BareMetalServerOrder_Package.rb where the order template did not use an array for the "hardware" key. This lead to an order template that would be accepted by verifyOrder, but rejected by placeOrder. An internal issue to review verifyOrder has also been generated. (reported by Rohit Singh)
31 |
32 | *2.2*
33 | * Added the ability to set a timout for network requests. The timeout is given when a client is created by passing the :timeout hash parameter when creating a client. The value of the parameter is an integer number of seconds.
34 | * Fixed a bug in VirtualServer#capture_image
35 |
36 | *2.1.1*
37 | * Virtual server upgrades no longer raise exceptions
38 | * Formalized the RDoc documentation process. Added overview and welcome documentation and changed the README so it directs folks to the new documentation.
39 |
40 | *2.1.0*
41 | * Began implementing a model framework that allows Ruby developers to work with elements in the SoftLayer API in a more object-oriented fashion. The first release of this framework includes the Ticket, VirtualServer, and BareMetalServer classes.
42 |
43 | *2.0.1*
44 | * Fix broken gem configparser dependency
45 |
46 | *2.0.0*
47 | * Switched the Ruby API client to use XML-RPC when calling the SoftLayer API rather than using the REST-like interface.
48 | * Result limits are now specified using @result_limit(offset,limit)@.
49 | The @result_offset@ API filter has been removed.
50 | * The @object_mask@ call modifier no longer accepts Ruby structures. It accepts strings that are Object Masks in the "Extended Object Mask":http://sldn.softlayer.com/article/Object-Masks format.
51 | * Changed the mechanism for obtaining services to include the Client class. This makes the Ruby API very similar to the API presented by the Python bindings. The old mechanism for obtaining services still works to preserve backward compatibility but you will receive deprecation warnings in debug mode.
52 |
53 | *1.0.8*
54 | * Set a default User-Agent string to be sent with all requests to SoftLayer API. Provide interface to set a custom User-Agent string.
55 |
56 | *1.0.7*
57 | * Calls to the @getObject@ method of any service should not take parameters. The gem now warns if you make this type of call and ignores the parameters. This prevents @SoftLayer_Virtual_Guest::getObject@ from accidentally creating (billable) VirtualServer instances.
58 |
59 | *1.0.6*
60 | * Make all API calls with either a @GET@ or a @POST@ as the HTTP verb.
61 |
62 | *1.0.5*
63 | * Fixed a bug where empty hashes and empty arrays would not generate meaningful object masks
64 |
65 | *1.0.4*
66 | * Fixed a bug where the @result_limit@ and @result_offset@ object filters were just not working.
67 |
68 | *1.0.3*
69 | * Added a request filter to add result limits to request. Submitted by JN. Thanks!
70 |
71 | *1.0.2*
72 | * We have some API routines that start with 'get' but expect arguments anyway. The code now uses HTTP POST to send requests for which the user has provided arguments regardless of the name of the routine.
73 |
74 | *1.0*, *1.0.1*
75 | * Initial release of the gem
76 |
--------------------------------------------------------------------------------
/lib/softlayer/VirtualServerUpgradeOrder.rb:
--------------------------------------------------------------------------------
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 | # This class is used to order changes to a virtual server. Although
9 | # the class is named "upgrade" this class can also be used for "downgrades"
10 | # (i.e. changing attributes to a smaller, or slower, value)
11 | #
12 | # The class can also be used to discover what upgrades are available
13 | # for a given virtual server.
14 | #
15 | class VirtualServerUpgradeOrder
16 | # The virtual server that this order is designed to upgrade.
17 | attr_reader :virtual_server
18 |
19 | # The number of cores the server should have after the upgrade.
20 | # If this is nil, the the number of cores will not change
21 | attr_accessor :cores
22 |
23 | # The amount of RAM (in GB) that the server should have after the upgrade
24 | # If this is nil, the ram will not change
25 | attr_accessor :ram
26 |
27 | # The port speed (in Mega bits per second) that the server should have
28 | # after the upgrade. This is typically a value like 100, or 1000
29 | # If this is nil, the port speeds will not change
30 | attr_accessor :max_port_speed
31 |
32 | # The date and time when you would like the upgrade to be processed.
33 | # This should simply be a Time object. If nil then the upgrade
34 | # will be performed immediately
35 | attr_accessor :upgrade_at
36 |
37 | ##
38 | # Create an upgrade order for the virtual server provided.
39 | #
40 | def initialize(virtual_server)
41 | raise "A virtual server must be provided at the time a virtual server order is created" if !virtual_server || !virtual_server.kind_of?(SoftLayer::VirtualServer)
42 | @virtual_server = virtual_server
43 | end
44 |
45 | ##
46 | # Sends the order represented by this object to SoftLayer for validation.
47 | #
48 | # If a block is passed to verify, the code will send the order template
49 | # being constructed to the block before the order is actually sent for
50 | # validation.
51 | #
52 | def verify()
53 | if has_order_items?
54 | order_template = order_object
55 | order_template = yield order_object if block_given?
56 | @virtual_server.softlayer_client[:Product_Order].verifyOrder(order_template)
57 | end
58 | end
59 |
60 | ##
61 | # Places the order represented by this object. This is likely to
62 | # involve a change to the charges on an account.
63 | #
64 | # If a block is passed to this routine, the code will send the order template
65 | # being constructed to that block before the order is sent
66 | #
67 | def place_order!()
68 | if has_order_items?
69 | order_template = order_object
70 | order_template = yield order_object if block_given?
71 |
72 | @virtual_server.softlayer_client[:Product_Order].placeOrder(order_template)
73 | end
74 | end
75 |
76 | ##
77 | # Return a list of values that are valid for the :cores attribute
78 | def core_options()
79 | self._item_prices_in_category("guest_core").map { |item_price| item_price['item']['capacity'].to_i}.sort.uniq
80 | end
81 |
82 | ##
83 | # Return a list of values that are valid for the :memory attribute
84 | def memory_options()
85 | self._item_prices_in_category("ram").map { |item_price| item_price['item']['capacity'].to_i}.sort.uniq
86 | end
87 |
88 | ##
89 | # Returns a list of valid values for max_port_speed
90 | def max_port_speed_options(client = nil)
91 | self._item_prices_in_category("port_speed").map { |item_price| item_price['item']['capacity'].to_i}.sort.uniq
92 | end
93 |
94 | private
95 |
96 | ##
97 | # Returns true if this order object has any upgrades specified
98 | #
99 | def has_order_items?
100 | @cores != nil || @ram != nil || @max_port_speed != nil
101 | end
102 |
103 | ##
104 | # Returns a list of the update item prices, in the given category, for the server
105 | #
106 | def _item_prices_in_category(which_category)
107 | @virtual_server.upgrade_options.select { |item_price| item_price['categories'].find { |category| category['categoryCode'] == which_category } }
108 | end
109 |
110 | ##
111 | # Searches through the upgrade items prices known to this server for the one that is in a particular category
112 | # and whose capacity matches the value given. Returns the item_price or nil
113 | #
114 | def _item_price_with_capacity(which_category, capacity)
115 | _item_prices_in_category(which_category).find { |item_price| item_price['item']['capacity'].to_i == capacity}
116 | end
117 |
118 | ##
119 | # construct an order object
120 | #
121 | def order_object
122 | prices = []
123 |
124 | cores_price_item = @cores ? _item_price_with_capacity("guest_core", @cores) : nil
125 | ram_price_item = @ram ? _item_price_with_capacity("ram", @ram) : nil
126 | max_port_speed_price_item = @max_port_speed ? _item_price_with_capacity("port_speed", @max_port_speed) : nil
127 |
128 | prices << { "id" => cores_price_item['id'] } if cores_price_item
129 | prices << { "id" => ram_price_item['id'] } if ram_price_item
130 | prices << { "id" => max_port_speed_price_item['id'] } if max_port_speed_price_item
131 |
132 | # put together an order
133 | upgrade_order = {
134 | 'complexType' => 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade',
135 | 'virtualGuests' => [{'id' => @virtual_server.id }],
136 | 'properties' => [{'name' => 'MAINTENANCE_WINDOW', 'value' => @upgrade_at ? @upgrade_at.iso8601 : Time.now.iso8601}],
137 | 'prices' => prices
138 | }
139 | end
140 | end # VirtualServerUpgradeOrder
141 | end # SoftLayer Module
142 |
--------------------------------------------------------------------------------
/spec/Account_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | require 'spec_helper'
14 |
15 | describe SoftLayer::Account do
16 | it "should exist" do
17 | expect(SoftLayer::Account).to_not be_nil
18 | end
19 |
20 | it "knows its id" do
21 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
22 | test_account = SoftLayer::Account.new(mock_client, "id" => 232279, "firstName" => "kangaroo")
23 | expect(test_account.id).to eq(232279)
24 | end
25 |
26 | it "identifies itself with the Account service" do
27 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
28 | allow(mock_client).to receive(:[]) do |service_name|
29 | expect(service_name).to eq :Account
30 | mock_service = SoftLayer::Service.new("SoftLayer_Account", :client => mock_client)
31 |
32 | # mock out call_softlayer_api_with_params so the service doesn't actually try to
33 | # communicate with the api endpoint
34 | allow(mock_service).to receive(:call_softlayer_api_with_params)
35 |
36 | mock_service
37 | end
38 |
39 | fake_account = SoftLayer::Account.new(mock_client, "id" => 12345)
40 | expect(fake_account.service.server_object_id).to eq(12345)
41 | expect(fake_account.service.target.service_name).to eq "SoftLayer_Account"
42 | end
43 |
44 | it "should allow the user to get the default account for a service" do
45 | test_client = double("mockClient")
46 | allow(test_client).to receive(:[]) do |service_name|
47 | expect(service_name).to eq :Account
48 |
49 | test_service = double("mockService")
50 | allow(test_service).to receive(:getObject) do
51 | { "id" => "232279", "firstName" => "kangaroo" }
52 | end
53 |
54 | test_service
55 | end
56 |
57 | test_account = SoftLayer::Account.account_for_client(test_client)
58 | expect(test_account.softlayer_client).to eq(test_client)
59 | expect(test_account.id).to eq("232279")
60 | expect(test_account.firstName).to eq("kangaroo")
61 | end
62 |
63 | describe "softlayer attributes" do
64 | let (:test_account) {
65 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "fake_api_key")
66 | SoftLayer::Account.new(mock_client, fixture_from_json("test_account"))
67 | }
68 |
69 | it "exposes a great many softlayer attributes" do
70 | expect(test_account.companyName).to eq "UpAndComing Software"
71 | expect(test_account.firstName).to eq "Don "
72 | expect(test_account.lastName).to eq "Joe"
73 | expect(test_account.address1).to eq "123 Main Street"
74 | expect(test_account.address2).to eq nil
75 | expect(test_account.city).to eq "Anytown"
76 | expect(test_account.state).to eq "TX"
77 | expect(test_account.country).to eq "US"
78 | expect(test_account.postalCode).to eq "778899"
79 | expect(test_account.officePhone).to eq "555.123.4567"
80 | end
81 | end
82 |
83 | it "fetches a list of open tickets" do
84 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "fake_api_key")
85 | account_service = mock_client[:Account]
86 |
87 | expect(account_service).to receive(:call_softlayer_api_with_params).with(:getOpenTickets, instance_of(SoftLayer::APIParameterFilter),[]) do
88 | fixture_from_json("test_tickets")
89 | end
90 |
91 | test_account = SoftLayer::Account.new(mock_client, fixture_from_json("test_account"))
92 | open_tickets = nil
93 | expect { open_tickets = test_account.open_tickets }.to_not raise_error
94 | ticket_ids = open_tickets.collect { |ticket| ticket.id }
95 | expect(ticket_ids.sort).to eq [12345, 12346, 12347, 12348, 12349].sort
96 | end
97 |
98 | describe "relationship to servers" do
99 | it "should respond to a request for servers" do
100 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "fake_api_key")
101 | account_service = mock_client[:Account]
102 | allow(account_service).to receive(:getObject).and_return(fixture_from_json("test_account"))
103 | allow(account_service).to receive(:call_softlayer_api_with_params) do |api_method, api_filter, arguments|
104 | case api_method
105 | when :getHardware
106 | fixture_bare_metal_data = fixture_from_json("test_bare_metal")
107 | when :getVirtualGuests
108 | fixture_from_json("test_virtual_servers")
109 | when :getObject
110 | fixture_from_json("test_account")
111 | end
112 | end
113 |
114 | test_account = SoftLayer::Account.account_for_client(mock_client)
115 |
116 | expect(test_account).to respond_to(:servers)
117 | expect(test_account).to_not respond_to(:servers=)
118 |
119 | servers = test_account.servers
120 | expect(servers.length).to eq(6)
121 | end
122 | end
123 |
124 | describe "Account.account_for_client" do
125 | it "raises an error if there is no client available" do
126 | SoftLayer::Client.default_client = nil
127 | expect {SoftLayer::Account.account_for_client}.to raise_error(RuntimeError)
128 | end
129 |
130 | it "uses the default client if one is available" do
131 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "fake_api_key")
132 | allow(mock_client).to receive(:[]) do |service_name|
133 | mock_service = Object.new()
134 | allow(mock_service).to receive(:getObject).and_return({"id" => 12345})
135 | mock_service
136 | end
137 |
138 | SoftLayer::Client.default_client = mock_client
139 | mock_account = SoftLayer::Account.account_for_client
140 | expect(mock_account).to be_instance_of(SoftLayer::Account)
141 | expect(mock_account.id).to be(12345)
142 | end
143 | end
144 | end
145 |
--------------------------------------------------------------------------------
/spec/VLANFirewall_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::VLANFirewall do
14 | it "should have a class representing a firewall" do
15 | expect{ SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 }) }.to_not raise_error
16 | end
17 |
18 | describe "firewall rules bypass" do
19 | let(:mock_client) {
20 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
21 | }
22 |
23 | it "responds to the method change_routing_bypass!" do
24 | mock_firewall = SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 })
25 | expect(mock_firewall).to respond_to(:change_rules_bypass!)
26 | end
27 |
28 | it "accepts :apply_firewall_rules" do
29 | mock_firewall = SoftLayer::VLANFirewall.new(mock_client, { "id" => 12345, 'networkVlanFirewall' => {'id' => 67890}})
30 | allow(mock_firewall).to receive(:rules_ACL_id) { 0 }
31 | allow(mock_firewall).to receive(:rules) { {} }
32 |
33 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
34 |
35 | expect(firewall_update_service).to receive(:call_softlayer_api_with_params) do |method, parameters, arguments|
36 | expect(arguments[0]['bypassFlag']).to be(false)
37 | end
38 |
39 | mock_firewall.change_rules_bypass!(:apply_firewall_rules)
40 | end
41 |
42 | it "accepts :bypass_firewall_rules!" do
43 | mock_firewall = SoftLayer::VLANFirewall.new(mock_client, { "id" => 12345, 'networkVlanFirewall' => {'id' => 67890}})
44 | allow(mock_firewall).to receive(:rules_ACL_id) { 0 }
45 | allow(mock_firewall).to receive(:rules) { {} }
46 |
47 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
48 | expect(firewall_update_service).to receive(:call_softlayer_api_with_params) do |method, parameters, arguments|
49 | expect(arguments[0]['bypassFlag']).to be(true)
50 | end
51 |
52 | mock_firewall.change_rules_bypass!(:bypass_firewall_rules)
53 | end
54 |
55 | it "rejects other parameters (particularly true and false)" do
56 | mock_firewall = SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 })
57 | allow(mock_firewall).to receive(:rules) { {} }
58 |
59 | firewall_update_service = mock_client[:Network_Firewall_Update_Request]
60 |
61 | allow(firewall_update_service).to receive(:call_softlayer_api_with_params)
62 |
63 | expect{ mock_firewall.change_rules_bypass!(true) }.to raise_error(NoMethodError)
64 | expect{ mock_firewall.change_rules_bypass!(false) }.to raise_error(NoMethodError)
65 | expect{ mock_firewall.change_rules_bypass!(:route_around_firewall) }.to raise_error(NoMethodError)
66 | expect{ mock_firewall.change_rules_bypass!(:route_through_firewall) }.to raise_error(NoMethodError)
67 | expect{ mock_firewall.change_rules_bypass!("apply_firewall_rules") }.to raise_error(NoMethodError)
68 | expect{ mock_firewall.change_rules_bypass!("bypass_firewall_rules") }.to raise_error(NoMethodError)
69 | expect{ mock_firewall.change_rules_bypass!(nil) }.to raise_error(NoMethodError)
70 | expect{ mock_firewall.change_rules_bypass!(1) }.to raise_error(NoMethodError)
71 | expect{ mock_firewall.change_rules_bypass!(0) }.to raise_error(NoMethodError)
72 | end
73 | end
74 |
75 | describe "firewall routing changes" do
76 | let(:mock_client) {
77 | mock_client = SoftLayer::Client.new(:username => "fake_user", :api_key => "BADKEY")
78 | }
79 |
80 | it "responds to the method change_routing_bypass!" do
81 | mock_firewall = SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 })
82 | expect(mock_firewall).to respond_to(:change_routing_bypass!)
83 | end
84 |
85 | it "accepts :route_through_firewall" do
86 | mock_firewall = SoftLayer::VLANFirewall.new(mock_client, { "id" => 12345, 'networkVlanFirewall' => {'id' => 67890}})
87 | allow(mock_firewall).to receive(:rules_ACL_id) { 0 }
88 | vlan_firewall_service = mock_client[:Network_Vlan_Firewall]
89 |
90 | expect(vlan_firewall_service).to receive(:call_softlayer_api_with_params).with(:updateRouteBypass,an_instance_of(SoftLayer::APIParameterFilter),[false])
91 | mock_firewall.change_routing_bypass!(:route_through_firewall)
92 | end
93 |
94 | it "accepts :route_around_firewall" do
95 | mock_firewall = SoftLayer::VLANFirewall.new(mock_client, { "id" => 12345, 'networkVlanFirewall' => {'id' => 67890}})
96 | allow(mock_firewall).to receive(:rules_ACL_id) { 0 }
97 | vlan_firewall_service = mock_client[:Network_Vlan_Firewall]
98 |
99 | expect(vlan_firewall_service).to receive(:call_softlayer_api_with_params).with(:updateRouteBypass,an_instance_of(SoftLayer::APIParameterFilter),[true])
100 | mock_firewall.change_routing_bypass!(:route_around_firewall)
101 | end
102 |
103 | it "rejects other parameters (particularly true and false)" do
104 | mock_firewall = SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 })
105 | allow(mock_firewall).to receive(:rules_ACL_id) { 0 }
106 |
107 | vlan_firewall_service = mock_client[:Network_Vlan_Firewall]
108 |
109 | allow(vlan_firewall_service).to receive(:call_softlayer_api_with_params)
110 |
111 | expect{ mock_firewall.change_routing_bypass!(true) }.to raise_error(NoMethodError)
112 | expect{ mock_firewall.change_routing_bypass!(false) }.to raise_error(NoMethodError)
113 | expect{ mock_firewall.change_routing_bypass!(:apply_firewall_rules) }.to raise_error(NoMethodError)
114 | expect{ mock_firewall.change_routing_bypass!(:bypass_firewall_rules) }.to raise_error(NoMethodError)
115 | expect{ mock_firewall.change_routing_bypass!("route_around_firewall") }.to raise_error(NoMethodError)
116 | expect{ mock_firewall.change_routing_bypass!("route_through_firewall") }.to raise_error(NoMethodError)
117 | expect{ mock_firewall.change_routing_bypass!(nil) }.to raise_error(NoMethodError)
118 | expect{ mock_firewall.change_routing_bypass!(1) }.to raise_error(NoMethodError)
119 | expect{ mock_firewall.change_routing_bypass!(0) }.to raise_error(NoMethodError)
120 | end
121 | end
122 | end
123 |
--------------------------------------------------------------------------------
/lib/softlayer/DynamicAttribute.rb:
--------------------------------------------------------------------------------
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 | ##
10 | # This module is intended to be used by classes in the SoftLayer
11 | # object model. It creates a small DSL for creating attributes
12 | # that update themselves dynamically (usually by making requests
13 | # to the SoftLayer API)
14 | #
15 | # +sl_dynamic_attr+ is an implementation of a memoization scheme
16 | # The module creates a getter which is implemented in terms of a
17 | # predicate (identifying whether or not the attribute needs to be updated) and
18 | # an update routine
19 | #
20 | # When the getter is called, it checks the predicate routine to see if
21 | # the attribute needs to be updated. If it doesn't, then the getter simply
22 | # returns the cached value for the attribute. If the attribute does need
23 | # to be updated, the getter calls the update routine to get a new value
24 | # and caches that value off before returning it to the caller.
25 | #
26 | # Declaring a attribute adds three methods to a class and
27 | # a corresponding instance variable in instances of the class
28 | # All three are based on the name of the attribute:
29 | #
30 | # * The getter simply has the same name as the attribute
31 | # * The predicate routine is called +should_update_?+
32 | # * The updating routine is called +update_!+
33 | #
34 | # The getter can also be called with a boolean argument. If that
35 | # argument is true, the getter will force the attribute to be updated
36 | # without consulting the +should_update?+ predicate
37 | #
38 | # When a attribute is defined, the definition takes a block.
39 | # Inside the block there is a small DSL that allows you to
40 | # set the behavior of the +should_update?+ predicate and the +update_!+
41 | # routine.
42 | #
43 | # A attribute definition might look something like this:
44 | #
45 | # sl_dynamic_attr :lollipop do |lollipop|
46 | # lollipop.should_update? do
47 | # self.lollipop_supply_is_low?
48 | # end
49 | #
50 | # lollipop.to_update do
51 | # candy_store.buy_lollipops(bakers_dozen)
52 | # end
53 | # end
54 | #
55 | module DynamicAttribute
56 |
57 | # The DynamicAttributeDefinition inner class is to collect and
58 | # store information about how and when a sl_dynamic_attr
59 | # should be updated. This class is an implementation detail
60 | # of dynamic attributes and is not intended to be useful
61 | # outside of that context.
62 | class DynamicAttributeDefinition
63 | # the name of the attribute this definition is for
64 | attr_reader :attribute_name
65 |
66 | # The block to call in order to update the attribute. The
67 | # return value of this block should be the new value of the
68 | # attribute.
69 | attr_reader :update_block
70 |
71 | # The block to call to see if the attribute needs to be updated.
72 | attr_reader :should_update_block
73 |
74 | def initialize(attribute_name)
75 | raise ArgumentError if attribute_name.nil?
76 | raise ArgumentError if attribute_name.to_s.empty?
77 |
78 | @attribute_name = attribute_name;
79 | @update_block = Proc.new { nil; };
80 | @should_update_block = Proc.new { true; }
81 | end
82 |
83 | # This method is used to provide behavior for the
84 | # should_update_ predicate for the attribute
85 | def should_update? (&block)
86 | @should_update_block = block
87 | end
88 |
89 | # This method is used to provide the behavior for
90 | # the update_! method for the attribute.
91 | def to_update (&block)
92 | @update_block = block
93 | end
94 | end
95 |
96 | module ClassMethods
97 | # sl_dynamic_attr declares a new dynamic softlayer attribute and accepts
98 | # a block in which the should_update? and to_update methods for the
99 | # attribute are established.
100 | def sl_dynamic_attr (attribute_name, &block)
101 | attribute_definition = DynamicAttributeDefinition.new(attribute_name)
102 |
103 | # allow the block to update the attribute definition
104 | yield attribute_definition if block_given?
105 |
106 | # store off the attribute definition where we can find it later
107 | @attribute_definitions ||= {};
108 | @attribute_definitions[attribute_name] = attribute_definition;
109 |
110 | # define a method called "update_!" which calls the update block
111 | # stored in the attribute definition
112 | update_symbol = "update_#{attribute_name}!".to_sym
113 | define_method(update_symbol, &attribute_definition.update_block)
114 |
115 | # define a method called "should_update_?" which calls the
116 | # should update block stored in the attribute definition
117 | should_update_symbol = "should_update_#{attribute_name}?".to_sym
118 | define_method(should_update_symbol, &attribute_definition.should_update_block)
119 |
120 | # define an instance method of the class this is being
121 | # called on which will get the value of the attribute.
122 | #
123 | # The getter will take one argument "force_update" which
124 | # is treated as boolean value. If true, then the getter will
125 | # force the attribute to update (by using its "to_update") block.
126 | #
127 | # If the force variable is false, or not given, then the
128 | # getter will call the "should update" block to find out if the
129 | # attribute needs to be updated.
130 | #
131 | getter_name = attribute_name.to_sym
132 | value_instance_variable = "@#{attribute_name}".to_sym
133 |
134 | define_method(getter_name) do |*args|
135 | force_update = args[0] || false
136 |
137 | if force_update || __send__(should_update_symbol)
138 | instance_variable_set(value_instance_variable, __send__(update_symbol))
139 | end
140 |
141 | instance_variable_get(value_instance_variable)
142 | end
143 | end
144 |
145 | def sl_dynamic_attr_definition(attribute_name)
146 | @attribute_definitions[attribute_name]
147 | end
148 | end
149 |
150 | def self.included(included_in)
151 | included_in.extend(ClassMethods)
152 | end
153 | end
154 | end
155 |
--------------------------------------------------------------------------------
/spec/VirtualServerUpgradeOrder_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::VirtualServerUpgradeOrder do
14 | before(:each) do
15 | SoftLayer::VirtualServerUpgradeOrder.send(:public, *SoftLayer::VirtualServerUpgradeOrder.private_instance_methods)
16 | end
17 |
18 | let(:test_virtual_server) do
19 | mock_client = SoftLayer::Client.new(:username => "fakeuser", :api_key => "DEADBEEFBADF00D")
20 | virtual_guest_service = mock_client[:Virtual_Guest]
21 | allow(virtual_guest_service).to receive(:call_softlayer_api_with_params) do |api_method, parameters, api_arguments|
22 | api_return = nil
23 |
24 | case api_method
25 | when :getUpgradeItemPrices
26 | api_return = fixture_from_json('virtual_server_upgrade_options')
27 | else
28 | fail "Unexpected call to the SoftLayer_Virtual_Guest service"
29 | end
30 |
31 | api_return
32 | end
33 |
34 | test_servers = fixture_from_json('test_virtual_servers')
35 | SoftLayer::VirtualServer.new(mock_client, test_servers.first)
36 | end
37 |
38 | it "requires a virtual server when initialized" do
39 | expect { SoftLayer::VirtualServerUpgradeOrder.new(nil) }.to raise_error(RuntimeError)
40 | expect { SoftLayer::VirtualServerUpgradeOrder.new("foo") }.to raise_error(RuntimeError)
41 | expect { SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server) }.to_not raise_error
42 | end
43 |
44 | it "initializes with none of the upgrades specified" do
45 | upgrade_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
46 | expect(upgrade_order.cores == nil)
47 | expect(upgrade_order.ram == nil)
48 | expect(upgrade_order.max_port_speed == nil)
49 | expect(upgrade_order.upgrade_at == nil)
50 | end
51 |
52 | it "identifies what options are available for upgrading the number of cores" do
53 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
54 | expect(sample_order.core_options).to eq [1, 2, 4, 8, 12, 16]
55 | end
56 |
57 | it "identifies what options are available for upgrading ram" do
58 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
59 | expect(sample_order.memory_options).to eq [1, 2, 4, 6, 8, 12, 16, 32, 48, 64]
60 | end
61 |
62 | it "identifies what options are available for upgrading max port speed" do
63 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
64 | expect(sample_order.max_port_speed_options).to eq [10, 100, 1000]
65 | end
66 |
67 | it "places the number of cores asked for into the order template" do
68 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
69 | sample_order.core_options.each do |num_cores|
70 | sample_order.cores = num_cores
71 | test_template = sample_order.order_object
72 | expect(sample_order.order_object['prices'].length).to be(1)
73 |
74 | item = sample_order._item_price_with_capacity("guest_core", num_cores)
75 | expect(test_template['prices'].first['id']).to eq item['id']
76 | end
77 | end
78 |
79 | it "places the amount of RAM asked for into the order template" do
80 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
81 | sample_order.memory_options.each do |ram_in_GB|
82 | sample_order.ram = ram_in_GB
83 | test_template = sample_order.order_object
84 | expect(sample_order.order_object['prices'].length).to be(1)
85 |
86 | item = sample_order._item_price_with_capacity("ram", ram_in_GB)
87 | expect(test_template['prices'].first['id']).to eq item['id']
88 | end
89 | end
90 |
91 | it "places the port speed asked for into the order template" do
92 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
93 | sample_order.max_port_speed_options.each do |port_speed|
94 | sample_order.max_port_speed = port_speed
95 | test_template = sample_order.order_object
96 | expect(sample_order.order_object['prices'].length).to be(1)
97 |
98 | item = sample_order._item_price_with_capacity("port_speed", port_speed)
99 | expect(test_template['prices'].first['id']).to eq item['id']
100 | end
101 | end
102 |
103 | it "adds the default maintenance window of 'now' if none is given" do
104 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
105 | sample_order.cores = 2
106 | test_template = sample_order.order_object
107 |
108 | expect(sample_order.order_object['properties'].first['name']).to eq('MAINTENANCE_WINDOW')
109 |
110 | time_string = sample_order.order_object['properties'].first['value']
111 | maintenance_time = Time.iso8601(time_string)
112 |
113 | expect((Time.now - maintenance_time) <= 1.0).to be(true)
114 | end
115 |
116 | it "adds the appointed maintenance window one is given" do
117 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
118 | sample_order.cores = 2
119 |
120 | upgrade_time = Time.now + 3600 # in an hour
121 | sample_order.upgrade_at = upgrade_time
122 |
123 | test_template = sample_order.order_object
124 |
125 | expect(sample_order.order_object['properties'].first['name']).to eq('MAINTENANCE_WINDOW')
126 |
127 | time_string = sample_order.order_object['properties'].first['value']
128 | expect(time_string).to eq upgrade_time.iso8601
129 | end
130 |
131 | it "verifies product orders" do
132 | product_order_service = test_virtual_server.softlayer_client[:Product_Order]
133 |
134 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
135 | sample_order.cores = 2
136 |
137 | order_object = sample_order.order_object
138 | expect(product_order_service).to receive(:call_softlayer_api_with_params).with(:verifyOrder, anything, [order_object])
139 |
140 | sample_order.verify()
141 | end
142 |
143 | it "places product orders" do
144 | product_order_service = test_virtual_server.softlayer_client[:Product_Order]
145 |
146 | sample_order = SoftLayer::VirtualServerUpgradeOrder.new(test_virtual_server)
147 | sample_order.cores = 2
148 |
149 | order_object = sample_order.order_object
150 | expect(product_order_service).to receive(:call_softlayer_api_with_params).with(:placeOrder, anything, [order_object])
151 |
152 | sample_order.place_order!()
153 | end
154 | end
155 |
--------------------------------------------------------------------------------
/doc_src/Model Layer.md:
--------------------------------------------------------------------------------
1 | # `softlayer_api` Model Layer
2 |
3 | The Model Layer builds upon the Foundation layer and offers abstractions of the entities in the SoftLayer environment. For example, computing assets in the SoftLayer environment are found in two separate services [SoftLayer_Hardware](http://sldn.softlayer.com/reference/services/SoftLayer_Hardware) and [SoftLayer_Virtual_Guest](http://sldn.softlayer.com/reference/services/SoftLayer_Virtual_Guest). The Model Layer, however, creates a Server abstraction on top of those services. By doing so, a Ruby script can work with an instance of the SoftLayer::Server object class, send that object the "cancel!" message, and trust the underlying object framework to route the message to the appropriate service and routine within the network API.
4 |
5 | The Model Layer is not meant to be complete. In fact, it models just a few key elements of the SoftLayer environment at this point, however we hope the Model Layer will grow and encompass more of the entities found in the API over time. In the interim, however, the current framework includes some convenient bridges that allow Ruby scripts to move from working with the abstractions of the Model Layer to the lower-level routines of the Foundation API easily.
6 |
7 | The details of the individual classes that for the object class hierarchy of the Model layer are included in the API documentation. This document will discuss some of the general features of the Model Layer and the bridges that are in place to help code move between the Foundation and Model Layers.
8 |
9 | # The ModelBase Class
10 |
11 | The ModelBase is the abstract base class of object class hierarchy that forms the Model Layer of the `softlayer_api` Gem. An instance of ModelBase represents a single entity within the SoftLayer API.
12 |
13 | In the Foundation layer, SoftLayer entities are represented as a Ruby hash whose keys and values are the are property names and property values of the entity. In the Model Layer, SoftLayer entities are represented by instances of the concrete subclasses of the Model Base class.
14 |
15 | In implementation terms, an instance of the ModelBase class (or more accurately and instance of a concrete subclass of the ModelBase class) encapsulates the hashes of the Foundation layer defines the attributes and operations that form a convenient model for working with the underlying entity.
16 |
17 | Here we will discuss the general features that all concrete subclasses of ModelBase share.
18 |
19 | ## Initializing Instances
20 |
21 | The initializer for classes in the ModelBase hierarchy are declared:
22 |
23 | def initialize(softlayer_client, network_hash)
24 | …
25 | end
26 |
27 | The first argument is the client that the object may use to make requests to the network API. The second is the `network_hash`, the hash representation of the entity as returned by the network API.
28 |
29 | The hash used to initialize an instance of ModelBase *must* contain a key, `id`, whose value is the `id` of the SoftLayer entity that the object model instance will represent. Correspondingly, the ModelBase class defines the `id` as having the same value as the `id` property in the network hash.
30 |
31 | ## Updating Instances from the Network
32 |
33 | If you wish to ask an instance of ModelBase to update itself with the latest information from the SoftLayer network API, you may call the `refresh_details` method defined in the ModelBase class. This method is likely to make one or more calls through to the network API and will, consequently, incur the overhead of network communication. (Note: subclasses should not override `refresh_details` but should, instead, override the `softlayer_properties` method. Correspondingly, the softlayer_properties method is an implementation detail of ModelBase and is not intended to be called by outside code.)
34 |
35 | ## Bridging to the Foundation layer
36 |
37 | Because the Model Layer offers limited coverage of the vast expanse of functionality found in the network API, it is often necessary to move from an object instance in the Model Layer to the implementation details from the Foundation layer. The Model Base class includes several features to help bridge the between layers gap.
38 |
39 | ### Accessing Properties
40 |
41 | The ModelBase class defines the subscript operator (`[]`) to accept a property name as a string and return that property of the underlying hash. For example, given a ticket, represented by an instance of the SoftLayer::Ticket model layer class, there is no Model Layer representation corresponding to the `serviceProvider` property of the SoftLayer ticket entity. However, the subscript operator can be used to access that property from a model layer object:
42 |
43 | ticket = SoftLayer::Ticket.ticket_with_id(123456)
44 | service_provider = ticket['serviceProvider']
45 |
46 | In this case we ask the ticket for the value of the `serviceProvider` property. Note that the argument to the subscript operator is a string containing the property name.
47 |
48 | This technique can only return values stored in the `softlayer_hash` encapsulated in the ModelBase class. Many classes in the Model Layer limit the information retrieved from the network (using object masks) to a subset of the full set of properties available through the network API. Scripts can check whether or not a given property is included in the underlying hash by calling the `has_sl_property?` method of ModelBase.
49 |
50 | ### Calling Network API routines
51 |
52 | Where possible, each subclass of ModelBase should provide a `service` attribute. The value of that attribute should be an instance of SoftLayer::Service, with Service Helpers applied, suitable for addressing the SoftLayer entity represented by the object through the network API. For example, consider an instance of the Model Layer class SoftLayer::Ticket which represents a service ticket within the network API. The Ticket class defines the `service` attribute of that instance to be the value:
53 |
54 | self.softlayer_client['Ticket'].object_with_id(self.id)
55 |
56 | because this service object includes `object_with_id` service helper the result can be used to call through to the network API and that call will be directed at the SoftLayer_Ticket entity with the same id as the model object. For example:
57 |
58 | ticket_object.service.getAttachedFile(file_id)
59 |
60 | calls the `getAttachedFile` method of the SoftLayer_Ticket service which is not yet available in the SoftLayer::Ticket object model class.
61 |
62 | ## The rest of the model
63 |
64 | The ModelBase class is literally the tip of the iceberg for the Model Layer, the bulk of functionality found in this layer is defined by concrete classes like SoftLayer::Ticket and SoftLayer::BareMetalServer, and in other abstract base classes like SoftLayer::Server. To understand the model presented by each class, we invite you to explore the API documentation.
--------------------------------------------------------------------------------
/lib/softlayer/NetworkStorageGroup.rb:
--------------------------------------------------------------------------------
1 | module SoftLayer
2 | ##
3 | # Each SoftLayer NetworkStorageGroup instance provides information about
4 | # a storage product group and hosts allowed access.
5 | #
6 | # This class roughly corresponds to the entity SoftLayer_Network_Storage_Group
7 | # in the API.
8 | #
9 | class NetworkStorageGroup < ModelBase
10 | include ::SoftLayer::DynamicAttribute
11 |
12 | ##
13 | # :attr_reader:
14 | # The friendly name of this group
15 | sl_attr :alias
16 |
17 | ##
18 | # :attr_reader: created_at
19 | # The date this group was created.
20 | sl_attr :created_at, 'createDate'
21 |
22 | ##
23 | # :attr_reader: created
24 | # The date this group was created.
25 | # DEPRECATION WARNING: This attribute is deprecated in favor of created_at
26 | # and will be removed in the next major release.
27 | sl_attr :created, 'createDate'
28 |
29 | ##
30 | # :attr_reader: modified_at
31 | # The date this group was modified.
32 | sl_attr :modified_at, 'modifyDate'
33 |
34 | ##
35 | # :attr_reader: modified
36 | # The date this group was modified.
37 | # DEPRECATION WARNING: This attribute is deprecated in favor of modified_at
38 | # and will be removed in the next major release.
39 | sl_attr :modified, 'modifyDate'
40 |
41 | ##
42 | # Retrieve the SoftLayer_Account which owns this group.
43 | # :call-seq:
44 | # account(force_update=false)
45 | sl_dynamic_attr :account do |resource|
46 | resource.should_update? do
47 | #only retrieved once per instance
48 | @account == nil
49 | end
50 |
51 | resource.to_update do
52 | account = self.service.getAccount
53 | Account.new(softlayer_client, account) unless account.empty?
54 | end
55 | end
56 |
57 | ##
58 | # Retrieve the allowed hosts list for this group.
59 | # :call-seq:
60 | # allowed_hosts(force_update=false)
61 | sl_dynamic_attr :allowed_hosts do |resource|
62 | resource.should_update? do
63 | #only retrieved once per instance
64 | @allowed_hosts == nil
65 | end
66 |
67 | resource.to_update do
68 | allowed_hosts = self.service.object_mask(NetworkStorageAllowedHost.default_object_mask).getAllowedHosts
69 | allowed_hosts.collect { |allowed_host| NetworkStorageAllowedHost.new(softlayer_client, allowed_host) unless allowed_host.empty? }.compact
70 | end
71 | end
72 |
73 | ##
74 | # Retrieve the network storage volumes this group is attached to.
75 | # :call-seq:
76 | # attached_volumes(force_update=false)
77 | sl_dynamic_attr :attached_volumes do |resource|
78 | resource.should_update? do
79 | #only retrieved once per instance
80 | @attached_volumes == nil
81 | end
82 |
83 | resource.to_update do
84 | attached_volumes = self.service.object_mask(NetworkStorage.default_object_mask).getAttachedVolumes
85 | attached_volumes.collect { |attached_volume| NetworkStorage.new(softlayer_client, attached_volume) unless attached_volume.empty? }.compact
86 | end
87 | end
88 |
89 | ##
90 | # Retrieve the IP address for for SoftLayer_Network_Storage_Allowed_Host objects within this group.
91 | # :call-seq:
92 | # ip_address(force_update=false)
93 | sl_dynamic_attr :ip_address do |resource|
94 | resource.should_update? do
95 | #only retrieved once per instance
96 | @ip_address == nil
97 | end
98 |
99 | resource.to_update do
100 | network_connection_details = self.service.getNetworkConnectionDetails
101 | network_connection_details["ipAddress"] unless network_connection_details.empty?
102 | end
103 | end
104 |
105 | ##
106 | # Retrieve the description of the SoftLayer_Network_Storage_OS_Type
107 | # Operating System designation that this group was created for.
108 | # :call-seq:
109 | # os_description(force_update=false)
110 | sl_dynamic_attr :os_description do |resource|
111 | resource.should_update? do
112 | #only retrieved once per instance
113 | @os_description == nil
114 | end
115 |
116 | resource.to_update do
117 | os_type = self.service.getOsType
118 | os_type["description"] unless os_type.empty?
119 | end
120 | end
121 |
122 | ##
123 | # Retrieve the name of the SoftLayer_Network_Storage_OS_Type
124 | # Operating System designation that this group was created for.
125 | # :call-seq:
126 | # os_name(force_update=false)
127 | sl_dynamic_attr :os_name do |resource|
128 | resource.should_update? do
129 | #only retrieved once per instance
130 | @os_name == nil
131 | end
132 |
133 | resource.to_update do
134 | os_type = self.service.getOsType
135 | os_type["name"] unless os_type.empty?
136 | end
137 | end
138 |
139 | ##
140 | # Retrieve the network resource this group is created on.
141 | # :call-seq:
142 | # service_resource(force_update=false)
143 | sl_dynamic_attr :service_resource do |resource|
144 | resource.should_update? do
145 | #only retrieved once per instance
146 | @service_resource == nil
147 | end
148 |
149 | resource.to_update do
150 | service_resource = self.service.object_mask(NetworkService.default_object_mask).getServiceResource
151 | NetworkService.new(softlayer_client, service_resource) unless service_resource.empty?
152 | end
153 | end
154 |
155 | ##
156 | # Retrieve the name of the SoftLayer_Network_Storage_Group_Type which describes this group.
157 | # :call-seq:
158 | # type(force_update=false)
159 | sl_dynamic_attr :type do |resource|
160 | resource.should_update? do
161 | #only retrieved once per instance
162 | @type == nil
163 | end
164 |
165 | resource.to_update do
166 | group_type = self.service.getGroupType
167 | group_type["name"]
168 | end
169 | end
170 |
171 | ##
172 | # Returns the service for interacting with this network storage through the network API
173 | #
174 | def service
175 | softlayer_client[:Network_Storage_Group].object_with_id(self.id)
176 | end
177 |
178 | protected
179 |
180 | def self.default_object_mask
181 | {
182 | "mask(SoftLayer_Network_Storage_Group)" => [
183 | 'alias',
184 | 'createDate',
185 | 'id',
186 | 'modifyDate'
187 | ]
188 | }.to_sl_object_mask
189 | end
190 | end
191 | end #SoftLayer
192 |
--------------------------------------------------------------------------------
/spec/fixtures/test_virtual_servers.json:
--------------------------------------------------------------------------------
1 | [{"notes":"These are test notes","createDate":"2014-06-10T09:14:15-06:00","dedicatedAccountHostOnlyFlag":false,"domain":"softlayer-api-test.rb","fullyQualifiedDomainName":"test-server-1.softlayer-api-test.rb","hostname":"test-server-1","id":62674,"maxCpu":1,"maxMemory":1024,"modifyDate":"2014-06-10T09:15:22-06:00","globalIdentifier":"feee0ce0-d3d8-0131-ec22-002500f2ef54","hourlyBillingFlag":true,"primaryBackendIpAddress":"203.0.113.82","primaryIpAddress":"198.51.100.121","privateNetworkOnlyFlag":false,"provisionDate":"2014-06-10T09:18:50-06:00","billingItem":{"id":26241,"recurringFee":"0"},"blockDevices":[{"bootableFlag":1,"createDate":"2014-06-10T09:14:53-06:00","device":"0","diskImageId":79652,"guestId":55344,"hotPlugFlag":0,"id":29549,"modifyDate":"","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee0e40-d3d8-0131-ec22-002500f2ef54"},{"bootableFlag":0,"createDate":"2014-06-10T09:14:46-06:00","device":"1","diskImageId":97104,"guestId":97146,"hotPlugFlag":0,"id":91153,"modifyDate":"","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee0ec0-d3d8-0131-ec22-002500f2ef54"}],"datacenter":{"id":17936,"longName":"Dallas 6","name":"dal06"},"networkComponents":[{"id":25665,"macAddress":"d1:78:1b:ec:76:35","maxSpeed":100,"name":"eth","port":0,"speed":100,"status":"ACTIVE","primaryIpAddress":"198.51.100.183","primarySubnet":{"broadcastAddress":"203.0.113.196","gateway":"192.0.2.230","id":96221,"netmask":"255.255.255.192","networkIdentifier":"203.0.113.168"},"uuid":"feee0f90-d3d8-0131-ec22-002500f2ef54"},{"id":84691,"macAddress":"61:42:24:63:1e:10","maxSpeed":100,"name":"eth","port":1,"speed":100,"status":"ACTIVE","primaryIpAddress":"203.0.113.92","primarySubnet":{"broadcastAddress":"203.0.113.245","gateway":"198.51.100.69","id":2621,"netmask":"255.255.255.224","networkIdentifier":"203.0.113.239"},"uuid":"feee10b0-d3d8-0131-ec22-002500f2ef54"}],"networkVlans":[{"id":83133,"vlanNumber":1318,"networkSpace":"PRIVATE"},{"id":49221,"vlanNumber":1854,"networkSpace":"PUBLIC"}],"operatingSystem":{"hardwareId":"","id":12055,"manufacturerLicenseInstance":"","passwords":["top_secret"],"softwareLicense":{"id":9547,"softwareDescriptionId":887,"softwareDescription":{"manufacturer":"CentOS","name":"CentOS","referenceCode":"CENTOS_6_64","version":"6.0-64 Minimal for VSI"}}},"powerState":{"name":"Running"},"status":{"keyName":"ACTIVE","name":"Active"},"tagReferences":[],"userData":[]},{"createDate":"2014-05-27T16:06:38-06:00","dedicatedAccountHostOnlyFlag":false,"domain":"softlayer-api-test.rb","fullyQualifiedDomainName":"test-server-2.softlayer-api-test.rb","hostname":"test-server-2","id":57152,"maxCpu":1,"maxMemory":1024,"modifyDate":"2014-05-27T16:08:01-06:00","globalIdentifier":"feee1680-d3d8-0131-ec22-002500f2ef54","hourlyBillingFlag":true,"primaryBackendIpAddress":"192.0.2.203","primaryIpAddress":"192.0.2.101","privateNetworkOnlyFlag":false,"provisionDate":"2014-05-27T16:10:37-06:00","billingItem":{"id":95857,"recurringFee":"0"},"blockDevices":[{"bootableFlag":1,"createDate":"2014-05-27T16:07:34-06:00","device":"0","diskImageId":33787,"guestId":1303,"hotPlugFlag":0,"id":71563,"modifyDate":"","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee1700-d3d8-0131-ec22-002500f2ef54"},{"bootableFlag":0,"createDate":"2014-05-27T16:07:13-06:00","device":"1","diskImageId":92918,"guestId":84492,"hotPlugFlag":0,"id":37371,"modifyDate":"","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee1760-d3d8-0131-ec22-002500f2ef54"}],"datacenter":{"id":94135,"longName":"Dallas 6","name":"dal06"},"networkComponents":[{"id":18080,"macAddress":"77:19:82:b4:88:55","maxSpeed":100,"name":"eth","port":0,"speed":100,"status":"ACTIVE","primaryIpAddress":"192.0.2.91","primarySubnet":{"broadcastAddress":"192.0.2.91","gateway":"203.0.113.230","id":45526,"netmask":"255.255.255.192","networkIdentifier":"192.0.2.22"},"uuid":"feee1810-d3d8-0131-ec22-002500f2ef54"},{"id":4025,"macAddress":"16:a0:b0:40:3a:95","maxSpeed":100,"name":"eth","port":1,"speed":100,"status":"ACTIVE","primaryIpAddress":"203.0.113.17","primarySubnet":{"broadcastAddress":"203.0.113.231","gateway":"203.0.113.184","id":44482,"netmask":"255.255.255.224","networkIdentifier":"198.51.100.240"},"uuid":"feee1900-d3d8-0131-ec22-002500f2ef54"}],"networkVlans":[{"id":41407,"vlanNumber":1854,"networkSpace":"PUBLIC"},{"id":13505,"vlanNumber":1318,"networkSpace":"PRIVATE"}],"operatingSystem":{"hardwareId":"","id":95684,"manufacturerLicenseInstance":"","passwords":["top_secret"],"softwareLicense":{"id":75930,"softwareDescriptionId":888,"softwareDescription":{"manufacturer":"CentOS","name":"CentOS","referenceCode":"CENTOS_6_64","version":"6.0-64 LAMP for VSI"}}},"powerState":{"name":"Running"},"status":{"keyName":"ACTIVE","name":"Active"},"tagReferences":[],"userData":[]},{"createDate":"2013-05-31T07:58:45-06:00","dedicatedAccountHostOnlyFlag":false,"domain":"softlayer-api-test.rb","fullyQualifiedDomainName":"test-server-3.softlayer-api-test.rb","hostname":"test-server-3","id":78144,"maxCpu":1,"maxMemory":1024,"modifyDate":"2014-01-20T20:46:18-06:00","globalIdentifier":"feee1ee0-d3d8-0131-ec22-002500f2ef54","hourlyBillingFlag":false,"primaryBackendIpAddress":"192.0.2.153","primaryIpAddress":"192.0.2.160","privateNetworkOnlyFlag":false,"provisionDate":"2013-05-31T08:04:36-06:00","billingItem":{"id":54285,"recurringFee":"0"},"blockDevices":[{"bootableFlag":1,"createDate":"2013-05-31T07:59:16-06:00","device":"0","diskImageId":34967,"guestId":81749,"hotPlugFlag":0,"id":8730,"modifyDate":"2014-01-20T20:45:32-06:00","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee1f60-d3d8-0131-ec22-002500f2ef54"},{"bootableFlag":0,"createDate":"2014-01-20T20:45:37-06:00","device":"1","diskImageId":34880,"guestId":88106,"hotPlugFlag":0,"id":52390,"modifyDate":"","mountMode":"RW","mountType":"Disk","statusId":1,"uuid":"feee1fc0-d3d8-0131-ec22-002500f2ef54"}],"datacenter":{"id":60948,"longName":"San Jose 1","name":"sjc01"},"networkComponents":[{"id":60582,"macAddress":"ca:19:22:ef:15:7d","maxSpeed":100,"name":"eth","port":0,"speed":100,"status":"ACTIVE","primaryIpAddress":"198.51.100.83","primarySubnet":{"broadcastAddress":"198.51.100.65","gateway":"192.0.2.63","id":41408,"netmask":"255.255.255.192","networkIdentifier":"198.51.100.67"},"uuid":"feee2070-d3d8-0131-ec22-002500f2ef54"},{"id":31860,"macAddress":"15:3a:17:4b:5d:80","maxSpeed":100,"name":"eth","port":1,"speed":100,"status":"ACTIVE","primaryIpAddress":"203.0.113.170","primarySubnet":{"broadcastAddress":"203.0.113.118","gateway":"192.0.2.209","id":38290,"netmask":"255.255.255.224","networkIdentifier":"203.0.113.195"},"uuid":"feee2160-d3d8-0131-ec22-002500f2ef54"}],"networkVlans":[{"id":71784,"vlanNumber":928,"networkSpace":"PRIVATE"},{"id":42267,"vlanNumber":932,"networkSpace":"PUBLIC"}],"operatingSystem":{"hardwareId":"","id":48341,"manufacturerLicenseInstance":"","passwords":["top_secret"],"softwareLicense":{"id":82561,"softwareDescriptionId":885,"softwareDescription":{"manufacturer":"CentOS","name":"CentOS","referenceCode":"CENTOS_6_32","version":"6.0-32 Minimal for VSI"}}},"powerState":{"name":"Running"},"status":{"keyName":"ACTIVE","name":"Active"},"tagReferences":[],"userData":[]}]
--------------------------------------------------------------------------------
/lib/softlayer/UserCustomer.rb:
--------------------------------------------------------------------------------
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 | # Each SoftLayer UserCustomer instance provides information
10 | # relating to a single SoftLayer customer portal user
11 | #
12 | # This class roughly corresponds to the entity SoftLayer_User_Customer
13 | # in the API.
14 | #
15 | class UserCustomer < ModelBase
16 | include ::SoftLayer::DynamicAttribute
17 |
18 | ##
19 | # :attr_reader: alternate_phone
20 | # A portal user's secondary phone number.
21 | sl_attr :alternate_phone, 'alternatePhone'
22 |
23 | ##
24 | # :attr_reader: created_at
25 | # The date a portal user's record was created.
26 | sl_attr :created_at, 'createDate'
27 |
28 | ##
29 | # :attr_reader: created
30 | # The date a portal user's record was created.
31 | # DEPRECATION WARNING: This attribute is deprecated in favor of created_at
32 | # and will be removed in the next major release.
33 | sl_attr :created, 'createDate'
34 |
35 | ##
36 | # :attr_reader: display_name
37 | # The portal user's display name.
38 | sl_attr :display_name, 'displayName'
39 |
40 | ##
41 | # :attr_reader:
42 | # A portal user's email address.
43 | sl_attr :email
44 |
45 | ##
46 | # :attr_reader: first_name
47 | # A portal user's first name.
48 | sl_attr :first_name, 'firstName'
49 |
50 | ##
51 | # :attr_reader: last_name
52 | # A portal user's last name.
53 | sl_attr :last_name, 'lastName'
54 |
55 | ##
56 | # :attr_reader: modified_at
57 | # The date a portal user's record was last modified.
58 | sl_attr :modified_at, 'modifyDate'
59 |
60 | ##
61 | # :attr_reader: modified
62 | # The date a portal user's record was last modified.
63 | # DEPRECATION WARNING: This attribute is deprecated in favor of modified_at
64 | # and will be removed in the next major release.
65 | sl_attr :modified, 'modifyDate'
66 |
67 | ##
68 | # :attr_reader: office_phone
69 | # A portal user's office phone number.
70 | sl_attr :office_phone, 'officePhone'
71 |
72 | ##
73 | # :attr_reader: password_expires
74 | # The expiration date for the user's password.
75 | sl_attr :password_expires, 'passwordExpireDate'
76 |
77 | ##
78 | # :attr_reader: status_changed
79 | # The date a portal users record's last status change.
80 | sl_attr :status_changed, 'statusDate'
81 |
82 | ##
83 | # :attr_reader:
84 | # A portal user's username.
85 | sl_attr :username
86 |
87 | ##
88 | # Retrieve a portal user's additional email addresses.
89 | # These email addresses are contacted when updates are made to support tickets.
90 | # :call-seq:
91 | # additional_emails(force_update=false)
92 | sl_dynamic_attr :additional_emails do |resource|
93 | resource.should_update? do
94 | #only retrieved once per instance
95 | @additional_emails == nil
96 | end
97 |
98 | resource.to_update do
99 | additional_emails = self.service.getAdditionalEmails
100 | additional_emails.collect { |additional_email| additional_email['email'] }
101 | end
102 | end
103 |
104 | ##
105 | # Retrieve a portal user's API Authentication keys.
106 | # There is a max limit of two API keys per user.
107 | # :call-seq:
108 | # api_authentication_keys(force_update=false)
109 | sl_dynamic_attr :api_authentication_keys do |resource|
110 | resource.should_update? do
111 | #only retrieved once per instance
112 | @api_authentication_keys == nil
113 | end
114 |
115 | resource.to_update do
116 | self.service.object_mask("mask[authenticationKey,ipAddressRestriction]").getApiAuthenticationKeys
117 | end
118 | end
119 |
120 | ##
121 | # Retrieve the external authentication bindings that link an external identifier to a SoftLayer user.
122 | # :call-seq:
123 | # external_bindings(force_update=false)
124 | sl_dynamic_attr :external_bindings do |resource|
125 | resource.should_update? do
126 | #only retrieved once per instance
127 | @external_bindings == nil
128 | end
129 |
130 | resource.to_update do
131 | external_bindings = self.service.object_mask(UserCustomerExternalBinding.default_object_mask).getExternalBindings
132 | external_bindings.collect { |external_binding| UserCustomerExternalBinding.new(softlayer_client, external_binding) }
133 | end
134 | end
135 |
136 | ##
137 | # Retrieve the user customer associated with the specified username
138 | #
139 | def self.user_customer_with_username(username, client = nil)
140 | softlayer_client = client || Client.default_client
141 | raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
142 | raise "#{__method__} requires a user customer username but none was given" if !username || username.empty?
143 |
144 | user_customer_object_filter = ObjectFilter.new()
145 |
146 | user_customer_object_filter.modify { |filter| filter.accept('users.username').when_it is(username) }
147 |
148 | account_service = Account.account_for_client(softlayer_client).service
149 | account_service = account_service.object_filter(user_customer_object_filter)
150 | account_service = account_service.object_mask(UserCustomer.default_object_mask)
151 |
152 | user_customer_data = account_service.getUsers
153 |
154 | if user_customer_data.length == 1
155 | UserCustomer.new(softlayer_client, user_customer_data.first)
156 | end
157 | end
158 |
159 | ##
160 | # Returns the service for interacting with this user customer through the network API
161 | #
162 | def service
163 | softlayer_client[:User_Customer].object_with_id(self.id)
164 | end
165 |
166 | protected
167 |
168 | def self.default_object_mask
169 | {
170 | "mask(SoftLayer_User_Customer)" => [
171 | 'alternatePhone',
172 | 'createDate',
173 | 'displayName',
174 | 'email',
175 | 'firstName',
176 | 'id',
177 | 'lastName',
178 | 'modifyDate',
179 | 'officePhone',
180 | 'passwordExpireDate',
181 | 'statusDate',
182 | 'username'
183 | ]
184 | }.to_sl_object_mask
185 | end
186 | end
187 | end #SoftLayer
188 |
--------------------------------------------------------------------------------
/lib/softlayer/Ticket.rb:
--------------------------------------------------------------------------------
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 | class Ticket < SoftLayer::ModelBase
9 |
10 | ##
11 | # :attr_reader:
12 | # The title is an identifying string set when the ticket is created
13 | sl_attr :title
14 |
15 | ##
16 | # :attr_reader:
17 | # The ticket system maintains a fixed set of subjects for tickets that are used to ensure tickets make it to the right folks quickly
18 | sl_attr :subject
19 |
20 | ##
21 | # :attr_reader: last_edited_at
22 | # The date the ticket was last updated.
23 | sl_attr :last_edited_at, 'lastEditDate'
24 |
25 | ##
26 | # :attr_reader:
27 | # The date the ticket was last updated.
28 | #
29 | # DEPRECATION WARNING: This attribute is deprecated in favor of last_edited_at
30 | # and will be removed in the next major release.
31 | sl_attr :lastEditDate
32 |
33 | ##
34 | # Returns true if the ticket has "unread" updates
35 | def has_updates?
36 | self['newUpdatesFlag']
37 | end
38 |
39 | ##
40 | # Returns true if the ticket is a server admin ticket
41 | def server_admin_ticket?
42 | # note that serverAdministrationFlag comes from the server as an Integer (0, or 1)
43 | self['serverAdministrationFlag'] != 0
44 | end
45 |
46 | ##
47 | # Add an update to this ticket.
48 | #
49 | def update(body = nil)
50 | self.service.edit(self.softlayer_hash, body)
51 | end
52 |
53 | ##
54 | # Override of service from ModelBase. Returns the SoftLayer_Ticket service
55 | # set up to talk to the ticket with my ID.
56 | def service
57 | return softlayer_client[:Ticket].object_with_id(self.id)
58 | end
59 |
60 | ##
61 | # Override from model base. Requests new details about the ticket
62 | # from the server.
63 | def softlayer_properties(object_mask = nil)
64 | my_service = self.service
65 |
66 | if(object_mask)
67 | my_service = service.object_mask(object_mask)
68 | else
69 | my_service = service.object_mask(self.class.default_object_mask.to_sl_object_mask)
70 | end
71 |
72 | my_service.getObject()
73 | end
74 |
75 | ##
76 | # Returns the default object mask,as a hash, that is used when
77 | # retrieving ticket information from the SoftLayer server.
78 | def self.default_object_mask
79 | {
80 | "mask" => [
81 | 'id', # This is an internal ticket ID, not the one usually seen in the portal
82 | 'serviceProvider',
83 | 'serviceProviderResourceId', # This is the ticket ID usually seen in the portal
84 | 'title',
85 | 'subject',
86 | {'assignedUser' => ['username', 'firstName', 'lastName'] },
87 | 'status.id',
88 | 'createDate',
89 | 'lastEditDate',
90 | 'newUpdatesFlag', # This comes in from the server as a Boolean value
91 | 'awaitingUserResponseFlag', # This comes in from the server as a Boolean value
92 | 'serverAdministrationFlag', # This comes in from the server as an integer :-(
93 | ]
94 | }.to_sl_object_mask
95 | end
96 |
97 | ##
98 | # Queries the SoftLayer API to retrieve a list of the valid
99 | # ticket subjects.
100 | def self.ticket_subjects(client = nil)
101 | @ticket_subjects ||= nil
102 |
103 | if !@ticket_subjects
104 | softlayer_client = client || Client.default_client
105 | raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
106 |
107 | @ticket_subjects = softlayer_client[:Ticket_Subject].getAllObjects();
108 | end
109 |
110 | @ticket_subjects
111 | end
112 |
113 | ##
114 | # Find the ticket with the given ID and return it
115 | #
116 | # Options should contain:
117 | #
118 | # +:client+ - the client in which to search for the ticket
119 | #
120 | # If a client is not provided then the routine will search Client::default_client
121 | # If Client::default_client is also nil the routine will raise an error.
122 | #
123 | # Additionally you may provide options related to the request itself:
124 | # * *:object_mask* (string) - The object mask of properties you wish to receive for the items returned.
125 | # If not provided, the result will use the default object mask
126 | def self.ticket_with_id(ticket_id, options = {})
127 | softlayer_client = options[:client] || Client.default_client
128 | raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
129 |
130 | ticket_service = softlayer_client[:Ticket].object_with_id(ticket_id)
131 | ticket_service = ticket_service.object_mask(default_object_mask.to_sl_object_mask)
132 | ticket_service = ticket_service.object_mask(options[:object_mask]) if options[:object_mask]
133 |
134 | ticket_data = ticket_service.getObject()
135 |
136 | return Ticket.new(softlayer_client, ticket_data)
137 | end
138 |
139 | ##
140 | # Create and submit a new support Ticket to SoftLayer.
141 | #
142 | # The options parameter should contain:
143 | #
144 | # +:client+ - The client used to connect to the API
145 | #
146 | # If no client is given, then the routine will try to use Client.default_client
147 | # If no client can be found the routine will raise an error.
148 | #
149 | # The options should also contain:
150 | #
151 | # * +:title+ (String) - The user provided title for the ticket.
152 | # * +:body+ (String) - The content of the ticket
153 | # * +:subject_id+ (Int) - The id of a subject to use for the ticket. A list of ticket subjects can be returned by SoftLayer::Ticket.ticket_subjects
154 | # * +:assigned_user_id+ (Int) - The id of a user to whom the ticket should be assigned
155 | def self.create_standard_ticket(options = {})
156 | softlayer_client = options[:client] || SoftLayer::Client.default_client
157 | raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
158 |
159 | title = options[:title]
160 | body = options[:body]
161 | subject_id = options[:subject_id]
162 | assigned_user_id = options[:assigned_user_id]
163 |
164 | if(nil == assigned_user_id)
165 | current_user = softlayer_client[:Account].object_mask("id").getCurrentUser()
166 | assigned_user_id = current_user['id']
167 | end
168 |
169 | new_ticket = {
170 | 'subjectId' => subject_id,
171 | 'contents' => body,
172 | 'assignedUserId' => assigned_user_id,
173 | 'title' => title
174 | }
175 |
176 | ticket_data = softlayer_client[:Ticket].createStandardTicket(new_ticket, body)
177 | return new(softlayer_client, ticket_data)
178 | end
179 | end
180 | end
181 |
--------------------------------------------------------------------------------
/doc_src/cla-individual.md:
--------------------------------------------------------------------------------
1 | #### International Business Machines, Inc. (IBM)
2 | #####Individual Contributor License Agreement ("Agreement")
3 |
4 | http://www.github.com/softlayer/softlayer-ruby
5 |
6 | Thank you for your interest in the softlayer-ruby project ("the Project").
7 |
8 | In order to clarify the intellectual property license granted with Contributions
9 | from any person or entity, IBM must have a Contributor License Agreement ("CLA")
10 | on file that has been signed by each Contributor, indicating agreement to the
11 | license terms below. This license is for your protection as a Contributor as
12 | well as the protection of IBM and its customers; it does not change your rights
13 | to use your own Contributions for any other purpose.
14 |
15 | If you have not already done so, please complete and sign, then scan and email a
16 | pdf file of this Agreement to SLDNDeveloperRelations@wwpdl.vnet.ibm.com.
17 |
18 | Please read this document carefully before signing and keep a copy for your
19 | records.
20 |
21 | Full name: ______________________________________________________
22 |
23 | (optional) Public name: _________________________________________
24 |
25 | Mailing Address: ________________________________________________
26 |
27 | Country: ______________________________________________________
28 |
29 | Telephone: ______________________________________________________
30 |
31 | E-Mail: ______________________________________________________
32 |
33 |
34 | You accept and agree to the following terms and conditions for Your present and
35 | future Contributions submitted to the Project. Except for the license granted
36 | herein to IBM and recipients of software distributed by IBM, You reserve all
37 | right, title, and interest in and to Your Contributions.
38 |
39 | 1. Definitions.
40 |
41 | "You" (or "Your") shall mean the copyright owner or legal entity
42 | authorized by the copyright owner that is making this Agreement
43 | with IBM. For legal entities, the entity making a Contribution and
44 | all other entities that control, are controlled by, or are under
45 | common control with that entity are considered to be a single
46 | Contributor. For the purposes of this definition, "control" means
47 | (i) the power, direct or indirect, to cause the direction or
48 | management of such entity, whether by contract or otherwise,
49 | or (ii) ownership of fifty percent (50%) or more of the outstanding
50 | shares, or (iii) beneficial ownership of such entity.
51 |
52 | "Contribution" shall mean any original work of authorship,
53 | including any modifications or additions to an existing work, that
54 | is intentionally submitted by You to the Project for inclusion
55 | in, or documentation of, the Project (”the Work”). For the purposes
56 | of this definition, "submitted" means any form of electronic, verbal,
57 | or written communication sent to the Project or its representatives,
58 | including but not limited to communication on electronic mailing lists,
59 | source code control systems, and issue tracking systems that are
60 | managed by, or on behalf of, the Project for the purpose of discussing
61 | and improving the Work, but excluding communication that is conspicuously
62 | marked or otherwise designated in writing by You as "Not a Contribution."
63 |
64 | 2. Grant of Copyright License.
65 |
66 | Subject to the terms and conditions of this Agreement, You hereby grant
67 | to IBM and to recipients of software distributed by IBM a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright
69 | license to reproduce, prepare derivative works of, publicly display,
70 | publicly perform, sublicense, and distribute Your Contributions and
71 | such derivative works.
72 |
73 | 3. Grant of Patent License.
74 |
75 | Subject to the terms and conditions of this Agreement, You hereby grant
76 | to IBM and to recipients of software distributed by IBM a perpetual,
77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except
78 | as stated in this section) patent license to make, have made, use, offer
79 | to sell, sell, import, and otherwise transfer the Work to which Your
80 | Contribution(s) were submitted, where such license applies only to those
81 | patent claims licensable by You that are necessarily infringed by Your
82 | Contribution(s) alone or by combination of Your Contribution(s) with the
83 | Work to which such Contribution(s) was submitted. If any entity institutes
84 | patent litigation against You or any other entity (including a cross-claim
85 | or counterclaim in a lawsuit) alleging that your Contribution, or the Work
86 | to which you have contributed, constitutes direct or contributory patent
87 | infringement, then any patent licenses granted to that entity under this
88 | Agreement for that Contribution or Work shall terminate as of the date
89 | such litigation is filed.
90 |
91 | 4. You represent that you are legally entitled to grant the above
92 | license.
93 |
94 | If your employer(s) has rights to intellectual property
95 | that you create that includes your Contributions, you represent
96 | that you have received permission to make Contributions on behalf
97 | of that employer, that your employer has waived such rights for
98 | your Contributions to the Project, or that your employer has
99 | executed a separate Corporate CLA with IBM.
100 |
101 | 5. You represent that each of Your Contributions is Your original
102 | creation (see section 7 for submissions on behalf of others). You
103 | represent that Your Contribution submissions include complete
104 | details of any third-party license or other restriction (including,
105 | but not limited to, related patents and trademarks) of which you
106 | are personally aware and which are associated with any part of Your
107 | Contributions.
108 |
109 | 6. You are not expected to provide support for Your Contributions,
110 | except to the extent You desire to provide support.
111 |
112 | You may provide support for free, for a fee, or not at all.
113 | Unless required by applicable law or agreed to in writing, You provide Your
114 | Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
115 | OF ANY KIND, either express or implied, including, without
116 | limitation, any warranties or conditions of TITLE, NON-
117 | INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
118 |
119 | 7. Should You wish to submit work that is not Your original creation,
120 | You may submit it to the Project separately from any
121 | Contribution, identifying the complete details of its source and of
122 | any license or other restriction (including, but not limited to,
123 | related patents, trademarks, and license agreements) of which you
124 | are personally aware, and conspicuously marking the work as
125 | "Submitted on behalf of a third-party: [named here]".
126 |
127 | 8. You agree to notify IBM of any facts or circumstances of
128 | which you become aware that would make these representations
129 | inaccurate in any respect.
130 |
131 | Please sign: __________________________________ Date: ________________
132 |
133 |
134 |
--------------------------------------------------------------------------------
/spec/Client_spec.rb:
--------------------------------------------------------------------------------
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 | $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
8 |
9 | require 'rubygems'
10 | require 'softlayer_api'
11 | require 'rspec'
12 |
13 | describe SoftLayer::Client do
14 | before do
15 | $SL_API_USERNAME = nil
16 | $SL_API_KEY = nil
17 | $SL_API_BASE_URL = nil
18 | end
19 |
20 | it 'accepts a user name from the global variable' do
21 | $SL_API_USERNAME = 'sample'
22 | client = SoftLayer::Client.new(:api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
23 | expect(client.username).to eq 'sample'
24 | end
25 |
26 | it 'accepts a username in options' do
27 | $SL_API_USERNAME = 'sample'
28 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
29 | expect(client.username).to eq 'fake_user'
30 | end
31 |
32 | it 'accepts an api key from the global variable' do
33 | $SL_API_KEY = 'sample'
34 | client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/')
35 | expect(client.api_key).to eq 'sample'
36 | end
37 |
38 | it 'accepts an api key in options' do
39 | $SL_API_KEY = 'sample'
40 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
41 | expect(client.api_key).to eq 'fake_key'
42 | end
43 |
44 | it 'produces empty auth headers if the username is empty' do
45 |
46 | $SL_API_USERNAME = ''
47 | client = SoftLayer::Client.new(:api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
48 |
49 | expect(client.authentication_headers.empty?).to be true
50 |
51 | $SL_API_USERNAME = 'good_username'
52 | $SL_API_KEY = 'sample'
53 | client = SoftLayer::Client.new(:username => '', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
54 |
55 | expect(client.authentication_headers.empty?).to be true
56 | end
57 |
58 | it 'produces empty auth headers if the username is nil' do
59 | $SL_API_USERNAME = nil
60 | client = SoftLayer::Client.new(:username => nil, :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
61 |
62 | expect(client.authentication_headers.empty?).to be true
63 | end
64 |
65 | it 'produces empty auth headers if the api_key is empty' do
66 | $SL_API_KEY = ''
67 | client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/')
68 |
69 | expect(client.authentication_headers.empty?).to be true
70 |
71 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => '', :endpoint_url => 'http://fakeurl.org/')
72 |
73 | expect(client.authentication_headers.empty?).to be true
74 | end
75 |
76 | it 'produces empty auth headers if the api_key is nil' do
77 | $SL_API_KEY = nil
78 | client = SoftLayer::Client.new(:username => 'fake_user', :endpoint_url => 'http://fakeurl.org/', :api_key => nil)
79 |
80 | expect(client.authentication_headers.empty?).to be true
81 | end
82 |
83 | it 'initializes by default with nil as the timeout' do
84 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
85 | expect(client.network_timeout).to be_nil
86 | end
87 |
88 | it 'Accepts a timeout given as a config parameter' do
89 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/', :timeout => 60)
90 | expect(client.network_timeout).to eq 60
91 | end
92 |
93 | it 'gets the default endpoint even if none is provided' do
94 | $SL_API_BASE_URL = nil
95 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key')
96 | expect(client.endpoint_url).to eq SoftLayer::API_PUBLIC_ENDPOINT
97 | end
98 |
99 | it 'allows the default endpoint to be overridden by globals' do
100 | $SL_API_BASE_URL = 'http://someendpoint.softlayer.com/from/globals'
101 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key')
102 | expect(client.endpoint_url).to eq 'http://someendpoint.softlayer.com/from/globals'
103 | end
104 |
105 | it 'allows the default endpoint to be overriden by options' do
106 | $SL_API_BASE_URL = 'http://this/wont/be/used'
107 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
108 | expect(client.endpoint_url).to eq 'http://fakeurl.org/'
109 | end
110 |
111 | it 'has a read/write user_agent property' do
112 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
113 | expect(client).to respond_to(:user_agent)
114 | expect(client).to respond_to(:user_agent=)
115 | end
116 |
117 | it 'has a reasonable default user agent string' do
118 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
119 | expect(client.user_agent).to eq "softlayer_api gem/#{SoftLayer::VERSION} (Ruby #{RUBY_PLATFORM}/#{RUBY_VERSION})"
120 | end
121 |
122 | it 'should allow the user agent to change' do
123 | client = SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
124 | client.user_agent = "Some Random User Agent"
125 | expect(client.user_agent).to eq "Some Random User Agent"
126 | end
127 |
128 | describe "obtaining services" do
129 | let(:test_client) {
130 | SoftLayer::Client.new(:username => 'fake_user', :api_key => 'fake_key', :endpoint_url => 'http://fakeurl.org/')
131 | }
132 |
133 | it "should have a service_named method" do
134 | expect(test_client).to respond_to(:service_named)
135 | end
136 |
137 | it "should reject empty or nil service names" do
138 | expect { test_client.service_named('') }.to raise_error(ArgumentError)
139 | expect { test_client.service_named(nil) }.to raise_error(ArgumentError)
140 | end
141 |
142 | it "should be able to construct a service" do
143 | test_service = test_client.service_named('Account')
144 | expect(test_service).to_not be_nil
145 | expect(test_service.service_name).to eq "SoftLayer_Account"
146 | expect(test_service.client).to be(test_client)
147 | end
148 |
149 | it "allows bracket dereferences as an alternate service syntax" do
150 | test_service = test_client[:Account]
151 | expect(test_service).to_not be_nil
152 | expect(test_service.service_name).to eq "SoftLayer_Account"
153 | expect(test_service.client).to be(test_client)
154 | end
155 |
156 | it "returns the same service repeatedly when asked more than once" do
157 | first_account_service = test_client[:Account]
158 | second_account_service = test_client.service_named('Account')
159 |
160 | expect(first_account_service).to be(second_account_service)
161 | end
162 |
163 | it "recognizes a symbol as an acceptable service name" do
164 | account_service = test_client[:Account]
165 | expect(account_service).to_not be_nil
166 |
167 | trying_again = test_client[:Account]
168 | expect(trying_again).to be(account_service)
169 |
170 | yet_again = test_client[:SoftLayer_Account]
171 | expect(yet_again).to be(account_service)
172 |
173 | once_more = test_client[:SoftLayer_Account]
174 | expect(once_more).to be(account_service)
175 | end
176 |
177 | end
178 | end
179 |
--------------------------------------------------------------------------------