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.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -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