knife-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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +12 -0
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +202 -0
  8. data/README.md +248 -0
  9. data/Rakefile +6 -0
  10. data/knife-oraclecloud.gemspec +28 -0
  11. data/lib/chef/knife/cloud/oraclecloud_service.rb +206 -0
  12. data/lib/chef/knife/cloud/oraclecloud_service_helpers.rb +48 -0
  13. data/lib/chef/knife/cloud/oraclecloud_service_options.rb +58 -0
  14. data/lib/chef/knife/oraclecloud_image_list.rb +63 -0
  15. data/lib/chef/knife/oraclecloud_orchestration_delete.rb +51 -0
  16. data/lib/chef/knife/oraclecloud_orchestration_list.rb +65 -0
  17. data/lib/chef/knife/oraclecloud_orchestration_show.rb +64 -0
  18. data/lib/chef/knife/oraclecloud_server_create.rb +105 -0
  19. data/lib/chef/knife/oraclecloud_server_delete.rb +48 -0
  20. data/lib/chef/knife/oraclecloud_server_list.rb +67 -0
  21. data/lib/chef/knife/oraclecloud_server_show.rb +52 -0
  22. data/lib/chef/knife/oraclecloud_shape_list.rb +51 -0
  23. data/lib/knife-oraclecloud/version.rb +21 -0
  24. data/spec/spec_helper.rb +17 -0
  25. data/spec/unit/cloud/oraclecloud_service_helpers_spec.rb +94 -0
  26. data/spec/unit/cloud/oraclecloud_service_spec.rb +386 -0
  27. data/spec/unit/oraclecloud_image_list_spec.rb +39 -0
  28. data/spec/unit/oraclecloud_orchestration_delete_spec.rb +59 -0
  29. data/spec/unit/oraclecloud_orchestration_list_spec.rb +56 -0
  30. data/spec/unit/oraclecloud_orchestration_show_spec.rb +96 -0
  31. data/spec/unit/oraclecloud_server_create_spec.rb +118 -0
  32. data/spec/unit/oraclecloud_server_delete_spec.rb +40 -0
  33. data/spec/unit/oraclecloud_server_list_spec.rb +59 -0
  34. data/spec/unit/oraclecloud_server_show_spec.rb +57 -0
  35. data/spec/unit/oraclecloud_shape_list_spec.rb +39 -0
  36. metadata +161 -0
@@ -0,0 +1,21 @@
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
+ module KnifeOracleCloud
20
+ VERSION = '1.0.0'
21
+ end
@@ -0,0 +1,17 @@
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
+ #
@@ -0,0 +1,94 @@
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 'spec_helper'
20
+ require 'chef/knife'
21
+ require 'chef/knife/cloud/oraclecloud_service'
22
+ require 'chef/knife/cloud/oraclecloud_service_helpers'
23
+
24
+ class HelpersTester
25
+ include Chef::Knife::Cloud::OraclecloudServiceHelpers
26
+ attr_accessor :ui
27
+ end
28
+
29
+ describe 'Chef::Knife::Cloud::OraclecloudServiceHelpers' do
30
+ let(:tester) { HelpersTester.new }
31
+
32
+ before do
33
+ tester.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
34
+ end
35
+
36
+ describe '#create_service_instance' do
37
+ it 'creates a service instance' do
38
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_username).and_return('test_user')
39
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_password).and_return('test_password')
40
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_api_url).and_return('https://cloud.oracle.com')
41
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_domain).and_return('test_domain')
42
+ allow(tester).to receive(:locate_config_value).with(:wait_time).and_return(300)
43
+ allow(tester).to receive(:locate_config_value).with(:request_refresh_rate).and_return(5)
44
+ allow(tester).to receive(:verify_ssl?).and_return(true)
45
+
46
+ expect(Chef::Knife::Cloud::OraclecloudService).to receive(:new)
47
+ .with(username: 'test_user',
48
+ password: 'test_password',
49
+ api_url: 'https://cloud.oracle.com',
50
+ identity_domain: 'test_domain',
51
+ wait_time: 300,
52
+ refresh_time: 5,
53
+ verify_ssl: true)
54
+
55
+ tester.create_service_instance
56
+ end
57
+ end
58
+
59
+ describe '#verify_ssl?' do
60
+ context 'when oraclecloud_disable_ssl_verify is true' do
61
+ it 'returns false' do
62
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_disable_ssl_verify).and_return(true)
63
+ expect(tester.verify_ssl?).to be false
64
+ end
65
+ end
66
+
67
+ context 'when oraclecloud_disable_ssl_verify is false' do
68
+ it 'returns true' do
69
+ allow(tester).to receive(:locate_config_value).with(:oraclecloud_disable_ssl_verify).and_return(false)
70
+ expect(tester.verify_ssl?).to be true
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#check_for_missing_config_values!' do
76
+ context 'when all values exist' do
77
+ it 'does not raise an error' do
78
+ allow(tester).to receive(:locate_config_value).with(:key1).and_return('value')
79
+ allow(tester).to receive(:locate_config_value).with(:key2).and_return('value')
80
+ expect(tester.ui).not_to receive(:error)
81
+ expect { tester.check_for_missing_config_values!(:key1, :key2) }.not_to raise_error
82
+ end
83
+ end
84
+
85
+ context 'when a value does not exist' do
86
+ it 'prints an error and exits' do
87
+ allow(tester).to receive(:locate_config_value).with(:key1).and_return('value')
88
+ allow(tester).to receive(:locate_config_value).with(:key2).and_return(nil)
89
+ expect(tester.ui).to receive(:error)
90
+ expect { tester.check_for_missing_config_values!(:key1, :key2) }.to raise_error(SystemExit)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,386 @@
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
+ require 'spec_helper'
19
+ require 'chef/knife'
20
+ require 'chef/knife/cloud/exceptions'
21
+ require 'chef/knife/cloud/oraclecloud_service'
22
+ require 'support/shared_examples_for_service'
23
+
24
+ describe Chef::Knife::Cloud::OraclecloudService do
25
+ let(:service) do
26
+ Chef::Knife::Cloud::OraclecloudService.new(username: 'myuser',
27
+ password: 'mypassword',
28
+ api_url: 'https://cloud.oracle.com',
29
+ identity_domain: 'mydomain',
30
+ verify_ssl: true)
31
+ end
32
+
33
+ before do
34
+ service.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
35
+ allow(service.ui).to receive(:msg)
36
+ end
37
+
38
+ describe '#connection' do
39
+ it 'creates an OracleCloud::Client instance' do
40
+ expect(service.connection).to be_an_instance_of(OracleCloud::Client)
41
+ end
42
+ end
43
+
44
+ describe '#prepend_identity_domain' do
45
+ let(:connection) { double('connection') }
46
+ it 'prepends the identity domain' do
47
+ allow(service).to receive(:connection).and_return(connection)
48
+ allow(connection).to receive(:compute_identity_domain).and_return('test_domain')
49
+
50
+ expect(service.prepend_identity_domain('foo')).to eq('test_domain/foo')
51
+ end
52
+ end
53
+
54
+ describe '#create_server' do
55
+ let(:orchestration) { double('orchestration') }
56
+ let(:instance) { double('instance') }
57
+ let(:options) { { key: 'value' } }
58
+
59
+ before do
60
+ allow(service).to receive(:create_orchestration).and_return(orchestration)
61
+ allow(orchestration).to receive(:start)
62
+ allow(orchestration).to receive(:name_with_container)
63
+ allow(orchestration).to receive(:instances).and_return([ instance ])
64
+ allow(service).to receive(:wait_for_status)
65
+ allow(service).to receive(:orchestration_summary)
66
+ end
67
+
68
+ it 'creates the orchestration' do
69
+ expect(service).to receive(:create_orchestration).with(options)
70
+
71
+ service.create_server(options)
72
+ end
73
+
74
+ it 'starts the orchestration' do
75
+ expect(orchestration).to receive(:start)
76
+
77
+ service.create_server(options)
78
+ end
79
+
80
+ it 'waits for the orchestration to become ready' do
81
+ expect(service).to receive(:wait_for_status).with(orchestration, 'ready')
82
+
83
+ service.create_server(options)
84
+ end
85
+
86
+ it 'prints out an orchestration summary' do
87
+ expect(service).to receive(:orchestration_summary).with(orchestration)
88
+
89
+ service.create_server(options)
90
+ end
91
+
92
+ it 'gathers the instances from the orchestration' do
93
+ expect(orchestration).to receive(:instances).and_return([ instance ])
94
+
95
+ service.create_server(options)
96
+ end
97
+
98
+ it 'returns the instance to the caller' do
99
+ expect(service.create_server(options)).to eq(instance)
100
+ end
101
+
102
+ context 'when more than one instance is returned' do
103
+ it 'raises an exception' do
104
+ allow(orchestration).to receive(:instances).and_return(%w(instance1 instance2))
105
+
106
+ expect { service.create_server(options) }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ServerCreateError)
107
+ end
108
+ end
109
+
110
+ context 'when more no instances are returned' do
111
+ it 'raises an exception' do
112
+ allow(orchestration).to receive(:instances).and_return([])
113
+
114
+ expect { service.create_server(options) }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ServerCreateError)
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#delete_server' do
120
+ let(:server) { double('server') }
121
+
122
+ before do
123
+ allow(service).to receive(:get_server).and_return(server)
124
+ allow(service).to receive(:server_summary)
125
+ allow(server).to receive(:orchestration)
126
+ allow(server).to receive(:delete)
127
+ allow(service.ui).to receive(:confirm)
128
+ end
129
+
130
+ it 'fetches the server' do
131
+ expect(service).to receive(:get_server).with('server1')
132
+
133
+ service.delete_server('server1')
134
+ end
135
+
136
+ it 'prints out a server summary' do
137
+ expect(service).to receive(:server_summary).with(server)
138
+
139
+ service.delete_server('server1')
140
+ end
141
+
142
+ it 'confirms that the user wishes to actually delete' do
143
+ expect(service.ui).to receive(:confirm).with('Do you really want to delete this server')
144
+
145
+ service.delete_server('server1')
146
+ end
147
+
148
+ it 'deletes the server' do
149
+ expect(server).to receive(:delete)
150
+
151
+ service.delete_server('server1')
152
+ end
153
+
154
+ context 'when the server has no orchestration' do
155
+ it 'does not print an error or raise an exception' do
156
+ expect(service.ui).not_to receive(:error)
157
+ expect { service.delete_server('server1') }.not_to raise_error
158
+ end
159
+ end
160
+
161
+ context 'when the server is part of an orchestration' do
162
+ it 'prints an error and exits' do
163
+ allow(server).to receive(:orchestration).and_return('test_orch')
164
+ expect(service.ui).to receive(:error)
165
+ expect { service.delete_server('server1') }.to raise_error(SystemExit)
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#create_orchestration' do
171
+ let(:connection) { double('connection') }
172
+ let(:orchestration) { double('orchestration') }
173
+ let(:orchestrations) { double('orchestrations') }
174
+ let(:instance_request) { double('instance_request') }
175
+ let(:options) { { name: 'test_name' } }
176
+
177
+ it 'creates an orchestration instance and returns it' do
178
+ allow(service).to receive(:connection).and_return(connection)
179
+ allow(connection).to receive(:orchestrations).and_return(orchestrations)
180
+ allow(connection).to receive(:username).and_return('test_username')
181
+
182
+ expect(service).to receive(:instance_request).with(options).and_return(instance_request)
183
+ expect(orchestrations).to receive(:create).with(name: 'test_name',
184
+ description: 'test_name by test_username via Knife',
185
+ instances: [ instance_request ])
186
+ .and_return(orchestration)
187
+ expect(service.create_orchestration(options)).to eq(orchestration)
188
+ end
189
+ end
190
+
191
+ describe '#delete_orchestration' do
192
+ let(:orchestration) { double('orchestration') }
193
+
194
+ before do
195
+ allow(service).to receive(:get_orchestration).and_return(orchestration)
196
+ allow(service).to receive(:orchestration_summary)
197
+ allow(service).to receive(:wait_for_status)
198
+ allow(service.ui).to receive(:confirm)
199
+ allow(orchestration).to receive(:stop)
200
+ allow(orchestration).to receive(:delete)
201
+ end
202
+
203
+ it 'fetches the orchestration' do
204
+ expect(service).to receive(:get_orchestration).with('orch1').and_return(orchestration)
205
+
206
+ service.delete_orchestration('orch1')
207
+ end
208
+
209
+ it 'prints an orchestration summary' do
210
+ expect(service).to receive(:orchestration_summary).with(orchestration)
211
+
212
+ service.delete_orchestration('orch1')
213
+ end
214
+
215
+ it 'confirms that the user wishes to actually delete' do
216
+ expect(service.ui).to receive(:confirm).with('Do you really want to delete this orchestration')
217
+
218
+ service.delete_orchestration('orch1')
219
+ end
220
+
221
+ it 'stops the orchestration' do
222
+ expect(orchestration).to receive(:stop)
223
+
224
+ service.delete_orchestration('orch1')
225
+ end
226
+
227
+ it 'deletes the orchestration' do
228
+ expect(orchestration).to receive(:delete)
229
+
230
+ service.delete_orchestration('orch1')
231
+ end
232
+ end
233
+
234
+ describe '#instance_request' do
235
+ let(:connection) { double('connection') }
236
+ let(:instance_request) { double('instance_request') }
237
+ let(:options) do
238
+ {
239
+ name: 'test_name',
240
+ shape: 'test_shape',
241
+ image: 'test_imagelist',
242
+ sshkeys: 'test_sshkeys',
243
+ label: 'test_label',
244
+ public_ip: 'test_public_ip'
245
+ }
246
+ end
247
+
248
+ it 'creates an instance request and returns it' do
249
+ allow(service).to receive(:connection).and_return(connection)
250
+ expect(connection).to receive(:instance_request).with(name: 'test_name',
251
+ shape: 'test_shape',
252
+ imagelist: 'test_imagelist',
253
+ sshkeys: 'test_sshkeys',
254
+ label: 'test_label',
255
+ public_ip: 'test_public_ip')
256
+ .and_return(instance_request)
257
+ expect(service.instance_request(options)).to eq(instance_request)
258
+ end
259
+ end
260
+
261
+ describe '#list_servers' do
262
+ let(:connection) { double('connection') }
263
+ let(:instances) { double('instances') }
264
+ it 'retrieves the instances and returns them' do
265
+ allow(service).to receive(:connection).and_return(connection)
266
+ allow(connection).to receive(:instances).and_return(instances)
267
+
268
+ expect(instances).to receive(:all).and_return('list_of_instances')
269
+ expect(service.list_servers).to eq('list_of_instances')
270
+ end
271
+ end
272
+
273
+ describe '#list_orchestrations' do
274
+ let(:connection) { double('connection') }
275
+ let(:orchestrations) { double('orchestrations') }
276
+ it 'retrieves the orchestrations and returns them' do
277
+ allow(service).to receive(:connection).and_return(connection)
278
+ allow(connection).to receive(:orchestrations).and_return(orchestrations)
279
+
280
+ expect(orchestrations).to receive(:all).and_return('list_of_orchestrations')
281
+ expect(service.list_orchestrations).to eq('list_of_orchestrations')
282
+ end
283
+ end
284
+
285
+ describe '#list_images' do
286
+ let(:connection) { double('connection') }
287
+ let(:images) { double('images') }
288
+ it 'retrieves the images and returns them' do
289
+ allow(service).to receive(:connection).and_return(connection)
290
+ allow(connection).to receive(:imagelists).and_return(images)
291
+
292
+ expect(images).to receive(:all).and_return('list_of_images')
293
+ expect(service.list_images).to eq('list_of_images')
294
+ end
295
+ end
296
+
297
+ describe '#list_images' do
298
+ let(:connection) { double('connection') }
299
+ let(:shapes) { double('shapes') }
300
+ it 'retrieves the shapes and returns them' do
301
+ allow(service).to receive(:connection).and_return(connection)
302
+ allow(connection).to receive(:shapes).and_return(shapes)
303
+
304
+ expect(shapes).to receive(:all).and_return('list_of_shapes')
305
+ expect(service.list_shapes).to eq('list_of_shapes')
306
+ end
307
+ end
308
+
309
+ describe '#get_server' do
310
+ let(:connection) { double('connection') }
311
+ let(:instances) { double('instances') }
312
+ let(:server) { double('server') }
313
+
314
+ it 'fetches the server and returns it' do
315
+ allow(service).to receive(:connection).and_return(connection)
316
+ allow(connection).to receive(:instances).and_return(instances)
317
+
318
+ expect(instances).to receive(:by_name).with('server1').and_return(server)
319
+ expect(service.get_server('server1')).to eq(server)
320
+ end
321
+ end
322
+
323
+ describe '#get_orchestration' do
324
+ let(:connection) { double('connection') }
325
+ let(:orchestration) { double('orchestration') }
326
+ let(:orchestrations) { double('orchestrations') }
327
+
328
+ it 'fetches the orchestration and returns it' do
329
+ allow(service).to receive(:connection).and_return(connection)
330
+ allow(connection).to receive(:orchestrations).and_return(orchestrations)
331
+
332
+ expect(orchestrations).to receive(:by_name).with('orch1').and_return(orchestration)
333
+ expect(service.get_orchestration('orch1')).to eq(orchestration)
334
+ end
335
+ end
336
+
337
+ describe '#wait_for_status' do
338
+ let(:item) { double('item') }
339
+
340
+ before do
341
+ allow(service).to receive(:wait_time).and_return(600)
342
+ allow(service).to receive(:refresh_time).and_return(2)
343
+
344
+ # muffle any stdout output from this method
345
+ allow(service).to receive(:print)
346
+
347
+ # don't actually sleep
348
+ allow(service).to receive(:sleep)
349
+ end
350
+
351
+ context 'when the items completes normally, 3 loops' do
352
+ it 'only refreshes the item 3 times' do
353
+ allow(item).to receive(:status).exactly(3).times.and_return('working', 'working', 'complete')
354
+ expect(item).to receive(:refresh).exactly(3).times
355
+
356
+ service.wait_for_status(item, 'complete')
357
+ end
358
+ end
359
+
360
+ context 'when the item is completed on the first loop' do
361
+ it 'only refreshes the item 1 time' do
362
+ allow(item).to receive(:status).once.and_return('complete')
363
+ expect(item).to receive(:refresh).once
364
+
365
+ service.wait_for_status(item, 'complete')
366
+ end
367
+ end
368
+
369
+ context 'when the timeout is exceeded' do
370
+ it 'prints a warning and exits' do
371
+ allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
372
+ expect(service.ui).to receive(:msg).with('')
373
+ expect(service.ui).to receive(:error)
374
+ .with('Request did not complete in 600 seconds. Check the Oracle Cloud Web UI for more information.')
375
+ expect { service.wait_for_status(item, 'complete') }.to raise_error(SystemExit)
376
+ end
377
+ end
378
+
379
+ context 'when a non-timeout exception is raised' do
380
+ it 'raises the original exception' do
381
+ allow(item).to receive(:refresh).and_raise(RuntimeError)
382
+ expect { service.wait_for_status(item, 'complete') }.to raise_error(RuntimeError)
383
+ end
384
+ end
385
+ end
386
+ end