oraclecloud 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +202 -0
- data/README.md +208 -0
- data/Rakefile +6 -0
- data/lib/oraclecloud.rb +36 -0
- data/lib/oraclecloud/asset.rb +65 -0
- data/lib/oraclecloud/assets.rb +99 -0
- data/lib/oraclecloud/client.rb +250 -0
- data/lib/oraclecloud/exceptions.rb +36 -0
- data/lib/oraclecloud/imagelist.rb +34 -0
- data/lib/oraclecloud/imagelists.rb +45 -0
- data/lib/oraclecloud/instance.rb +79 -0
- data/lib/oraclecloud/instance_request.rb +78 -0
- data/lib/oraclecloud/instances.rb +39 -0
- data/lib/oraclecloud/ip_association.rb +36 -0
- data/lib/oraclecloud/ip_associations.rb +29 -0
- data/lib/oraclecloud/orchestration.rb +85 -0
- data/lib/oraclecloud/orchestrations.rb +61 -0
- data/lib/oraclecloud/shape.rb +42 -0
- data/lib/oraclecloud/shapes.rb +36 -0
- data/lib/oraclecloud/sshkey.rb +32 -0
- data/lib/oraclecloud/sshkeys.rb +25 -0
- data/lib/oraclecloud/version.rb +20 -0
- data/oraclecloud.gemspec +26 -0
- metadata +156 -0
data/Rakefile
ADDED
data/lib/oraclecloud.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'oraclecloud/asset'
|
20
|
+
require 'oraclecloud/assets'
|
21
|
+
require 'oraclecloud/client'
|
22
|
+
require 'oraclecloud/exceptions'
|
23
|
+
require 'oraclecloud/imagelist'
|
24
|
+
require 'oraclecloud/imagelists'
|
25
|
+
require 'oraclecloud/instance'
|
26
|
+
require 'oraclecloud/instances'
|
27
|
+
require 'oraclecloud/instance_request'
|
28
|
+
require 'oraclecloud/ip_association'
|
29
|
+
require 'oraclecloud/ip_associations'
|
30
|
+
require 'oraclecloud/orchestration'
|
31
|
+
require 'oraclecloud/orchestrations'
|
32
|
+
require 'oraclecloud/shape'
|
33
|
+
require 'oraclecloud/shapes'
|
34
|
+
require 'oraclecloud/sshkey'
|
35
|
+
require 'oraclecloud/sshkeys'
|
36
|
+
require 'oraclecloud/version'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module OracleCloud
|
19
|
+
class Asset
|
20
|
+
attr_reader :asset_data, :asset_type, :client, :container, :path
|
21
|
+
|
22
|
+
def initialize(client, path)
|
23
|
+
@client = client
|
24
|
+
@asset_data = nil
|
25
|
+
@path = path
|
26
|
+
@container = path.split('/').first
|
27
|
+
|
28
|
+
local_init
|
29
|
+
validate!
|
30
|
+
|
31
|
+
fetch
|
32
|
+
end
|
33
|
+
|
34
|
+
def local_init
|
35
|
+
# this should be redefined in each Assets subclass with things like
|
36
|
+
# the @asset_type to use in API calls
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate!
|
40
|
+
raise "#{self.class} did not define an asset_type variable" if asset_type.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def fetch
|
44
|
+
@asset_data = client.single_item(asset_type, path)
|
45
|
+
end
|
46
|
+
alias_method :refresh, :fetch
|
47
|
+
|
48
|
+
def id
|
49
|
+
asset_data['name'].split('/').last
|
50
|
+
end
|
51
|
+
alias_method :name, :id
|
52
|
+
|
53
|
+
def name_with_container
|
54
|
+
"#{container}/#{id}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def full_name
|
58
|
+
"/Compute-#{client.identity_domain}/#{name_with_container}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def strip_identity_domain(name)
|
62
|
+
name.gsub("/Compute-#{client.identity_domain}/", '')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module OracleCloud
|
19
|
+
class Assets
|
20
|
+
attr_reader :asset_klass, :asset_type, :client
|
21
|
+
attr_accessor :create_opts
|
22
|
+
|
23
|
+
def initialize(client)
|
24
|
+
@client = client
|
25
|
+
@create_opts = {}
|
26
|
+
|
27
|
+
local_init
|
28
|
+
validate!
|
29
|
+
end
|
30
|
+
|
31
|
+
def local_init
|
32
|
+
# this should be redefined in each Assets subclass with things like
|
33
|
+
# the @asset_type to use in API calls
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate!
|
37
|
+
raise "#{self.class} did not define an asset_type variable" if asset_type.nil?
|
38
|
+
raise "#{self.class} did not define an asset_klass variable" if asset_klass.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def all
|
42
|
+
all_asset_ids_by_container.each_with_object([]) do |(container, asset_names), memo|
|
43
|
+
asset_names.each do |asset_name|
|
44
|
+
memo << @asset_klass.new(client, "#{container}/#{asset_name}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def containers
|
50
|
+
directory('')
|
51
|
+
end
|
52
|
+
|
53
|
+
def ids_from_results(results)
|
54
|
+
results['result'].map { |x| x.split('/').last }
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_asset_ids_by_container
|
58
|
+
containers.each_with_object({}) do |container, memo|
|
59
|
+
memo[container] = asset_ids_for_container(container)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def asset_ids_for_container(container)
|
64
|
+
directory(container)
|
65
|
+
end
|
66
|
+
|
67
|
+
def by_name(name)
|
68
|
+
@asset_klass.new(client, strip_identity_domain(name))
|
69
|
+
end
|
70
|
+
|
71
|
+
def directory(path)
|
72
|
+
ids_from_results(client.directory(asset_type, path))
|
73
|
+
end
|
74
|
+
|
75
|
+
def create(opts)
|
76
|
+
@create_opts = opts
|
77
|
+
|
78
|
+
validate_create_options!
|
79
|
+
response = client.http_post("/#{asset_type}/", create_request_payload.to_json)
|
80
|
+
name = strip_identity_domain(response['name'])
|
81
|
+
@asset_klass.new(client, name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_request_payload
|
85
|
+
# this should be defined in each Assets subclass with a formatted
|
86
|
+
# payload used to create the Asset
|
87
|
+
raise NoMethodError, "#{self.class} does not define create_request_payload"
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_create_options!
|
91
|
+
# this should be redefined in each Assets subclass with any validation
|
92
|
+
# of creation options that should be done prior to creation
|
93
|
+
end
|
94
|
+
|
95
|
+
def strip_identity_domain(name)
|
96
|
+
name.gsub("/Compute-#{client.identity_domain}/", '')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
3
|
+
# Copyright:: Copyright (c) 2015 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'ffi_yajl'
|
20
|
+
require 'rest-client'
|
21
|
+
|
22
|
+
module OracleCloud
|
23
|
+
class Client # rubocop:disable Metrics/ClassLength
|
24
|
+
attr_reader :identity_domain, :password, :username
|
25
|
+
|
26
|
+
def initialize(opts)
|
27
|
+
@api_url = opts[:api_url]
|
28
|
+
@identity_domain = opts[:identity_domain]
|
29
|
+
@username = opts[:username]
|
30
|
+
@password = opts[:password]
|
31
|
+
@verify_ssl = opts.fetch(:verify_ssl, true)
|
32
|
+
@cookie = nil
|
33
|
+
|
34
|
+
validate_client_options!
|
35
|
+
end
|
36
|
+
|
37
|
+
#################################
|
38
|
+
#
|
39
|
+
# methods to other API objects
|
40
|
+
#
|
41
|
+
|
42
|
+
def imagelists
|
43
|
+
OracleCloud::ImageLists.new(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def instance_request(*args)
|
47
|
+
OracleCloud::InstanceRequest.new(self, *args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def instances
|
51
|
+
OracleCloud::Instances.new(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ip_associations
|
55
|
+
OracleCloud::IPAssociations.new(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def orchestrations
|
59
|
+
OracleCloud::Orchestrations.new(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
def shapes
|
63
|
+
OracleCloud::Shapes.new(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def sshkeys
|
67
|
+
OracleCloud::SSHKeys.new(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
#################################
|
71
|
+
#
|
72
|
+
# client methods
|
73
|
+
#
|
74
|
+
|
75
|
+
def validate_client_options!
|
76
|
+
raise ArgumentError, 'Username, password and identity_domain are required' if
|
77
|
+
@username.nil? || @password.nil? || @identity_domain.nil?
|
78
|
+
raise ArgumentError, 'An API URL is required' if @api_url.nil?
|
79
|
+
raise ArgumentError, "API URL #{@api_url} is not a valid URI." unless valid_uri?(@api_url)
|
80
|
+
end
|
81
|
+
|
82
|
+
def valid_uri?(uri)
|
83
|
+
uri = URI.parse(uri)
|
84
|
+
uri.is_a?(URI::HTTP)
|
85
|
+
rescue URI::InvalidURIError
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def username_with_domain
|
90
|
+
"#{compute_identity_domain}/#{@username}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def compute_identity_domain
|
94
|
+
"Compute-#{@identity_domain}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def authenticate!
|
98
|
+
path = '/authenticate/'
|
99
|
+
response = RestClient::Request.execute(method: :post,
|
100
|
+
url: full_url(path),
|
101
|
+
headers: request_headers,
|
102
|
+
payload: authenticate_payload.to_json,
|
103
|
+
verify_ssl: @verify_ssl)
|
104
|
+
|
105
|
+
rescue => e
|
106
|
+
raise_http_exception(e, path)
|
107
|
+
else
|
108
|
+
@cookie = process_auth_cookies(response.headers[:set_cookie])
|
109
|
+
end
|
110
|
+
|
111
|
+
def authenticated?
|
112
|
+
! @cookie.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
def request_headers(opts = {})
|
116
|
+
headers = { 'Content-Type' => 'application/oracle-compute-v3+json' }
|
117
|
+
|
118
|
+
if opts[:type] == :directory
|
119
|
+
headers['Accept'] = 'application/oracle-compute-v3+directory+json'
|
120
|
+
else
|
121
|
+
headers['Accept'] = 'application/oracle-compute-v3+json'
|
122
|
+
end
|
123
|
+
|
124
|
+
headers['Cookie'] = @cookie if @cookie
|
125
|
+
headers
|
126
|
+
end
|
127
|
+
|
128
|
+
def authenticate_payload
|
129
|
+
{
|
130
|
+
'user' => username_with_domain,
|
131
|
+
'password' => @password
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def full_url(path)
|
136
|
+
@api_url + path
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_auth_cookies(cookies)
|
140
|
+
cookie = cookies.find { |c| c.start_with?('nimbula=') }
|
141
|
+
raise 'No nimbula auth cookie received in authentication request' if cookie.nil?
|
142
|
+
|
143
|
+
cookie.gsub!(/ Path=.* Max-Age=.*$/, '')
|
144
|
+
cookie
|
145
|
+
end
|
146
|
+
|
147
|
+
def asset_get(request_type, asset_type, path)
|
148
|
+
url = url_with_identity_domain(asset_type, path)
|
149
|
+
http_get(request_type, url)
|
150
|
+
end
|
151
|
+
|
152
|
+
def asset_put(asset_type, path, payload = nil)
|
153
|
+
url = url_with_identity_domain(asset_type, path)
|
154
|
+
http_put(url, payload)
|
155
|
+
end
|
156
|
+
|
157
|
+
def asset_delete(asset_type, path)
|
158
|
+
url = url_with_identity_domain(asset_type, path)
|
159
|
+
http_delete(url)
|
160
|
+
end
|
161
|
+
|
162
|
+
def single_item(asset_type, path)
|
163
|
+
asset_get(:single, asset_type, path)
|
164
|
+
end
|
165
|
+
|
166
|
+
def directory(asset_type, path)
|
167
|
+
asset_get(:directory, asset_type, path)
|
168
|
+
end
|
169
|
+
|
170
|
+
def url_with_identity_domain(type, path = '')
|
171
|
+
"/#{type}/#{compute_identity_domain}/#{path}"
|
172
|
+
end
|
173
|
+
|
174
|
+
def http_get(request_type, url)
|
175
|
+
authenticate! unless authenticated?
|
176
|
+
|
177
|
+
response = RestClient::Request.execute(method: :get,
|
178
|
+
url: full_url(url),
|
179
|
+
headers: request_headers(type: request_type),
|
180
|
+
verify_ssl: @verify_ssl)
|
181
|
+
rescue => e
|
182
|
+
raise_http_exception(e, url)
|
183
|
+
else
|
184
|
+
FFI_Yajl::Parser.parse(response)
|
185
|
+
end
|
186
|
+
|
187
|
+
def http_post(path, payload)
|
188
|
+
authenticate! unless authenticated?
|
189
|
+
response = RestClient::Request.execute(method: :post,
|
190
|
+
url: full_url(path),
|
191
|
+
headers: request_headers,
|
192
|
+
payload: payload,
|
193
|
+
verify_ssl: @verify_ssl)
|
194
|
+
rescue => e
|
195
|
+
raise_http_exception(e, path)
|
196
|
+
else
|
197
|
+
FFI_Yajl::Parser.parse(response)
|
198
|
+
end
|
199
|
+
|
200
|
+
def http_put(path, payload = nil)
|
201
|
+
authenticate! unless authenticated?
|
202
|
+
response = RestClient::Request.execute(method: :put,
|
203
|
+
url: full_url(path),
|
204
|
+
headers: request_headers,
|
205
|
+
payload: payload,
|
206
|
+
verify_ssl: @verify_ssl)
|
207
|
+
rescue => e
|
208
|
+
raise_http_exception(e, path)
|
209
|
+
else
|
210
|
+
FFI_Yajl::Parser.parse(response)
|
211
|
+
end
|
212
|
+
|
213
|
+
def http_delete(path)
|
214
|
+
authenticate! unless authenticated?
|
215
|
+
response = RestClient::Request.execute(method: :delete,
|
216
|
+
url: full_url(path),
|
217
|
+
headers: request_headers,
|
218
|
+
verify_ssl: @verify_ssl)
|
219
|
+
rescue => e
|
220
|
+
raise_http_exception(e, path)
|
221
|
+
else
|
222
|
+
FFI_Yajl::Parser.parse(response)
|
223
|
+
end
|
224
|
+
|
225
|
+
def raise_http_exception(caught_exception, path)
|
226
|
+
raise unless caught_exception.respond_to?(:http_code)
|
227
|
+
|
228
|
+
if caught_exception.http_code == 404
|
229
|
+
klass = OracleCloud::Exception::HTTPNotFound
|
230
|
+
else
|
231
|
+
klass = OracleCloud::Exception::HTTPError
|
232
|
+
end
|
233
|
+
|
234
|
+
begin
|
235
|
+
error_body = FFI_Yajl::Parser.parse(caught_exception.response)
|
236
|
+
rescue
|
237
|
+
error_body = { 'message' => caught_exception.response }
|
238
|
+
end
|
239
|
+
|
240
|
+
exception = klass.new(code: caught_exception.http_code,
|
241
|
+
body: caught_exception.response,
|
242
|
+
klass: caught_exception.class,
|
243
|
+
error: error_body['message'].to_s,
|
244
|
+
path: path)
|
245
|
+
|
246
|
+
message = exception.error.empty? ? caught_exception.message : exception.error
|
247
|
+
raise exception, message
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|