cft_smartcloud 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +11 -0
- data/README.md +106 -0
- data/VERSION +1 -1
- data/bin/cft_smartcloud +33 -7
- data/bin/smartcloud +33 -7
- data/cft_smartcloud.gemspec +84 -20
- data/lib/config/config.yml +2 -0
- data/lib/curl_client.rb +42 -0
- data/lib/rest-client-1.6.6-master/.gitignore +6 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/README.rdoc +10 -1
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/Rakefile +0 -0
- data/lib/rest-client-1.6.6-master/VERSION +1 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/bin/restclient +5 -4
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/history.md +22 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/rest-client.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/rest_client.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/abstract_response.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/exceptions.rb +0 -0
- data/lib/rest-client-1.6.6-master/lib/restclient/net_http_ext.rb +55 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/payload.rb +17 -2
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/raw_response.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/request.rb +21 -19
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/resource.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient/response.rb +0 -0
- data/lib/{rest-client-1.6.3 → rest-client-1.6.6-master}/lib/restclient.rb +0 -0
- data/lib/rest-client-1.6.6-master/rest-client.gemspec +76 -0
- data/lib/rest-client-1.6.6-master/spec/abstract_response_spec.rb +85 -0
- data/lib/rest-client-1.6.6-master/spec/base.rb +16 -0
- data/lib/rest-client-1.6.6-master/spec/exceptions_spec.rb +98 -0
- data/lib/rest-client-1.6.6-master/spec/integration/certs/equifax.crt +19 -0
- data/lib/rest-client-1.6.6-master/spec/integration/certs/verisign.crt +14 -0
- data/lib/rest-client-1.6.6-master/spec/integration/request_spec.rb +25 -0
- data/lib/rest-client-1.6.6-master/spec/integration_spec.rb +38 -0
- data/lib/rest-client-1.6.6-master/spec/master_shake.jpg +0 -0
- data/lib/rest-client-1.6.6-master/spec/payload_spec.rb +234 -0
- data/lib/rest-client-1.6.6-master/spec/raw_response_spec.rb +17 -0
- data/lib/rest-client-1.6.6-master/spec/request2_spec.rb +40 -0
- data/lib/rest-client-1.6.6-master/spec/request_spec.rb +536 -0
- data/lib/rest-client-1.6.6-master/spec/resource_spec.rb +134 -0
- data/lib/rest-client-1.6.6-master/spec/response_spec.rb +169 -0
- data/lib/rest-client-1.6.6-master/spec/restclient_spec.rb +73 -0
- data/lib/slop-2.3.1/.gemtest +0 -0
- data/lib/slop-2.3.1/.gitignore +6 -0
- data/lib/slop-2.3.1/.yardopts +6 -0
- data/lib/slop-2.3.1/CHANGES.md +137 -0
- data/lib/slop-2.3.1/LICENSE +20 -0
- data/lib/slop-2.3.1/README.md +293 -0
- data/lib/slop-2.3.1/Rakefile +6 -0
- data/lib/slop-2.3.1/lib/slop.rb +1022 -0
- data/lib/slop-2.3.1/slop.gemspec +11 -0
- data/lib/slop-2.3.1/test/commands_test.rb +151 -0
- data/lib/slop-2.3.1/test/helper.rb +13 -0
- data/lib/slop-2.3.1/test/option_test.rb +198 -0
- data/lib/slop-2.3.1/test/slop_test.rb +574 -0
- data/lib/smartcloud.rb +186 -116
- data/lib/terminal-table-1.4.4/History.rdoc +53 -0
- data/lib/terminal-table-1.4.4/Manifest +24 -0
- data/lib/terminal-table-1.4.4/README.rdoc +240 -0
- data/lib/terminal-table-1.4.4/Rakefile +15 -0
- data/lib/terminal-table-1.4.4/Todo.rdoc +14 -0
- data/lib/terminal-table-1.4.4/examples/examples.rb +80 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/cell.rb +88 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/core_ext.rb +8 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/import.rb +4 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/row.rb +48 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/separator.rb +14 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/style.rb +61 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/table.rb +217 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/table_helper.rb +9 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table/version.rb +6 -0
- data/lib/terminal-table-1.4.4/lib/terminal-table.rb +27 -0
- data/lib/terminal-table-1.4.4/spec/cell_spec.rb +54 -0
- data/lib/terminal-table-1.4.4/spec/core_ext_spec.rb +18 -0
- data/lib/terminal-table-1.4.4/spec/import_spec.rb +11 -0
- data/lib/terminal-table-1.4.4/spec/spec.opts +1 -0
- data/lib/terminal-table-1.4.4/spec/spec_helper.rb +8 -0
- data/lib/terminal-table-1.4.4/spec/table_spec.rb +525 -0
- data/lib/terminal-table-1.4.4/tasks/docs.rake +13 -0
- data/lib/terminal-table-1.4.4/tasks/gemspec.rake +3 -0
- data/lib/terminal-table-1.4.4/tasks/spec.rake +25 -0
- data/lib/terminal-table-1.4.4/terminal-table.gemspec +30 -0
- data/responses/addresses +26 -0
- data/responses/addresses.blank +2 -0
- data/responses/instances +74 -0
- data/responses/keys +33 -0
- data/responses/locations +142 -0
- data/responses/offerings_image +3780 -0
- data/responses/storage +379 -0
- metadata +86 -22
- data/README.rdoc +0 -75
- data/lib/rest-client-1.6.3/VERSION +0 -1
- data/lib/rest-client-1.6.3/lib/restclient/net_http_ext.rb +0 -21
data/lib/smartcloud.rb
CHANGED
@@ -22,32 +22,39 @@ require 'restclient_fix'
|
|
22
22
|
require 'hash_fix'
|
23
23
|
require 'xmlsimple'
|
24
24
|
require 'smartcloud_logger'
|
25
|
+
require 'curl_client'
|
26
|
+
require 'terminal-table'
|
25
27
|
|
26
28
|
IBM_TOOLS_HOME=File.join(File.dirname(__FILE__), "cli_tools") unless defined?(IBM_TOOLS_HOME)
|
27
29
|
|
28
30
|
# Encapsulates communications with IBM SmartCloud via REST
|
29
|
-
class IBMSmartCloud
|
30
31
|
|
32
|
+
class IBMSmartCloud
|
33
|
+
|
31
34
|
attr_accessor :logger
|
32
35
|
|
33
|
-
def initialize(
|
36
|
+
def initialize(opts={})
|
37
|
+
@save_response = opts[:save_response]
|
38
|
+
@simulated_response = opts[:simulated_response_file] && File.read(opts[:simulated_response_file])
|
39
|
+
|
34
40
|
# For handling errors
|
35
|
-
@retries = 120
|
36
|
-
@sleep_interval = 30
|
41
|
+
@retries = (opts[:retries] || 120).to_i
|
42
|
+
@sleep_interval = (opts[:sleep_interval] || 30).to_i
|
37
43
|
|
38
|
-
@username = username
|
39
|
-
@password = password
|
40
|
-
@logger = logger || SmartcloudLogger.new(STDOUT)
|
41
|
-
@debug = debug
|
44
|
+
@username = opts[:username] || ENV['SMARTCLOUD_USERNAME'] || raise(RuntimeError, "Please specify username in an option or as ENV variable SMARTCLOUD_USERNAME")
|
45
|
+
@password = opts[:password]|| ENV['SMARTCLOUD_PASSWORD'] || raise(RuntimeError, "Please specify password in an option or as ENV variable SMARTCLOUD_PASSWORD")
|
46
|
+
@logger = opts[:logger] || SmartcloudLogger.new(STDOUT)
|
47
|
+
@debug = opts[:debug] || false
|
42
48
|
|
43
49
|
@config = YAML.load_file(File.join(File.dirname(__FILE__), "config/config.yml"))
|
44
50
|
@states = @config["states"]
|
45
51
|
|
46
|
-
@api_url = @config["api_url"]
|
47
|
-
@api_url.gsub!("https://", "https://#{CGI::escape(username)}:#{CGI::escape(password)}@")
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
@api_url = (opts[:api_url] || @config["api_url"]).to_s.dup # gotta dup it because the option string is frozen
|
53
|
+
@api_url.gsub!("https://", "https://#{CGI::escape(@username)}:#{CGI::escape(@password)}@")
|
54
|
+
|
55
|
+
@http_client = Kernel.const_get(@config["http_client"])
|
56
|
+
@http_client.timeout = 120 # ibm requests can be very slow
|
57
|
+
@http_client.log = @logger if @debug
|
51
58
|
end
|
52
59
|
|
53
60
|
class << self
|
@@ -57,7 +64,7 @@ class IBMSmartCloud
|
|
57
64
|
attr_reader :config
|
58
65
|
end
|
59
66
|
|
60
|
-
def self.
|
67
|
+
def self.help_for(method, args, extra_help={})
|
61
68
|
@method_help||={}
|
62
69
|
@method_help_supplemental||={}
|
63
70
|
@method_help[method.to_s] = args
|
@@ -71,10 +78,7 @@ class IBMSmartCloud
|
|
71
78
|
return "Sorry, I don't know method: #{method}"
|
72
79
|
end
|
73
80
|
|
74
|
-
|
75
|
-
puts method.to_s
|
76
|
-
else
|
77
|
-
args = args.map do |arg|
|
81
|
+
args = args && args.map do |arg|
|
78
82
|
if arg.is_a?(Hash)
|
79
83
|
# If an argument is required, just list it
|
80
84
|
if arg.values.first==:req
|
@@ -93,11 +97,11 @@ class IBMSmartCloud
|
|
93
97
|
|
94
98
|
extra_help = self.class.method_help_supplemental[method.to_s] || ""
|
95
99
|
|
96
|
-
puts
|
97
|
-
end
|
100
|
+
puts %{ * #{method.to_s}#{'(' + args + ')' if args}#{extra_help + "\n" if extra_help}}
|
98
101
|
else
|
99
102
|
methods = public_methods - Object.public_methods - ['post','get','put','delete','logger','logger=','help']
|
100
|
-
|
103
|
+
methods.sort.each {|m| help(m)}
|
104
|
+
nil
|
101
105
|
end
|
102
106
|
end
|
103
107
|
|
@@ -111,15 +115,22 @@ class IBMSmartCloud
|
|
111
115
|
end
|
112
116
|
|
113
117
|
def display_locations
|
114
|
-
|
115
|
-
|
118
|
+
|
119
|
+
table = Terminal::Table.new do |t|
|
120
|
+
t.headings = ['ID', 'Loc', 'Name']
|
121
|
+
describe_locations.each do |loc|
|
122
|
+
t.add_row [loc.ID, loc.Location, loc.Name]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
logger.info table
|
116
127
|
end
|
117
128
|
|
118
129
|
# Create an instance. The instance_params hash can follow
|
119
130
|
# CLI-style attribute naming. I.e. "image-id" or "data-center"
|
120
131
|
# or API-style (imageID, location). If the CLI-style params are
|
121
132
|
# provided, they will be remapped to the correct API params.
|
122
|
-
|
133
|
+
help_for :create_instance, [:instance_params_hash], %{
|
123
134
|
Available hash keys: :name, :imageID, :instanceType, :location, :publicKey, :ip, :volumeID,
|
124
135
|
:ConfigurationData, :vlanID, :antiCollocationInstance, :isMiniEphemeral
|
125
136
|
|
@@ -161,7 +172,7 @@ class IBMSmartCloud
|
|
161
172
|
end
|
162
173
|
|
163
174
|
# Find a location by its name. Assumes locations have unique names (not verified by ibm docs)
|
164
|
-
|
175
|
+
help_for :get_location_by_name, [:name]
|
165
176
|
def get_location_by_name(name)
|
166
177
|
locations = describe_locations
|
167
178
|
locations.detect {|loc| loc.Name == name}
|
@@ -169,7 +180,7 @@ class IBMSmartCloud
|
|
169
180
|
|
170
181
|
# find all ip address offerings. If a location is specified, give the offering for the location
|
171
182
|
# assumption: only one offering per location. This is assumed, not verified in docs.
|
172
|
-
|
183
|
+
help_for :describe_address_offerings, [{:location => :opt}]
|
173
184
|
def describe_address_offerings(location=nil)
|
174
185
|
response=get("/offerings/address").Offerings
|
175
186
|
if location
|
@@ -181,7 +192,7 @@ class IBMSmartCloud
|
|
181
192
|
|
182
193
|
# Allocate IP address. Offering ID is optional. if not specified, will look up offerings
|
183
194
|
# for this location and pick one
|
184
|
-
|
195
|
+
help_for :allocate_address, [{:location => :req}, {:offering_id => :opt}, {:foo => [:bar, :baz, :quux]}]
|
185
196
|
def allocate_address(location, offering_id=nil)
|
186
197
|
if offering_id.nil?
|
187
198
|
offering_id = describe_address_offerings(location).ID
|
@@ -190,7 +201,7 @@ class IBMSmartCloud
|
|
190
201
|
end
|
191
202
|
|
192
203
|
|
193
|
-
|
204
|
+
help_for :describe_address, [:address_id]
|
194
205
|
def describe_address(address_id)
|
195
206
|
address = get("/addresses/#{address_id}").Address
|
196
207
|
address["State"] = @config.states.ip[address.State]
|
@@ -198,37 +209,56 @@ class IBMSmartCloud
|
|
198
209
|
end
|
199
210
|
|
200
211
|
# Launches a clone image call from CLI
|
201
|
-
|
212
|
+
help_for :clone_image, [:name, :description, :image_id]
|
202
213
|
def clone_image(name, description, image_id)
|
203
214
|
post("/offerings/image/#{image_id}", :name => name, :description => description).ImageID
|
204
215
|
end
|
205
216
|
|
217
|
+
# Export an image to a volume
|
218
|
+
help_for :export_image, [{:name=>:req}, {:size => ['Small','Medium','Large']}, {:image_id => :req}, {:location => :req}]
|
219
|
+
def export_image(name, size, image_id, location)
|
220
|
+
# Note we figure out the correct size based on the name and location
|
221
|
+
storage_offering=describe_storage_offerings(location, size)
|
222
|
+
|
223
|
+
response = post("/storage", :name => name, :size => storage_offering.Capacity, :format => 'EXT3', :offeringID => storage_offering.ID, :location => location, :imageID => image_id)
|
224
|
+
response.Volume.ID
|
225
|
+
end
|
226
|
+
|
227
|
+
help_for :import_image, [{:name=>:req, :volume_id => :req}]
|
228
|
+
def import_image(name, volume_id)
|
229
|
+
# TODO: this is a complete guess as we have no info from IBM as to the URL for this api, only the parameters
|
230
|
+
response = post("/offerings/image", :volumeID => volume_id, :name => name)
|
231
|
+
response.ImageID
|
232
|
+
end
|
233
|
+
|
206
234
|
# Launches a clone request and returns ID of new volume
|
207
235
|
# TODO: the API call does not work, we have to use the cli for now
|
208
|
-
|
236
|
+
help_for :clone_volume, [:name, :source_disk_id]
|
209
237
|
def clone_volume(name, source_disk_id)
|
210
238
|
result = post("/storage", :name => name, :sourceDiskID => source_disk_id, :type => 'clone')
|
211
239
|
result.Volume.ID
|
212
240
|
end
|
213
241
|
|
214
|
-
|
242
|
+
help_for :attach_volume, [:instance_id, :volume_id]
|
215
243
|
def attach_volume(instance_id, volume_id)
|
216
244
|
put("/instances/#{instance_id}", :volumeID => volume_id, :attach => true)
|
217
245
|
end
|
218
246
|
|
219
|
-
|
247
|
+
help_for :detach_volume, [:instance_id, :volume_id]
|
220
248
|
def detach_volume(instance_id, volume_id)
|
221
249
|
put("/instances/#{instance_id}", :volumeID => volume_id, :detach => true)
|
222
250
|
end
|
223
251
|
|
224
252
|
# Delete the volume
|
225
|
-
|
253
|
+
help_for :delete_volume, [:vol_id]
|
226
254
|
def delete_volume(vol_id)
|
227
255
|
delete("/storage/#{vol_id}")
|
228
256
|
true
|
229
257
|
end
|
230
258
|
|
231
|
-
|
259
|
+
help_for :delete_volumes, [:volume_ids], %{
|
260
|
+
Example: smartcloud "delete_volumes(12345,12346,12347)"
|
261
|
+
}
|
232
262
|
def delete_volumes(*vol_id_list)
|
233
263
|
threads=[]
|
234
264
|
vol_id_list.each {|vol|
|
@@ -239,7 +269,10 @@ class IBMSmartCloud
|
|
239
269
|
end
|
240
270
|
|
241
271
|
# generates a keypair and returns the private key
|
242
|
-
|
272
|
+
help_for :generate_keypair, [{:name=>:req}, {:publicKey=>:opt}], %{
|
273
|
+
If publicKey parameter is given, creates a key with that public key, and returns nothing.
|
274
|
+
If public key is omitted, generates a new key and returns the pirvate key.
|
275
|
+
}
|
243
276
|
def generate_keypair(name, publicKey=nil)
|
244
277
|
options = {:name => name}
|
245
278
|
options[:publicKey] = publicKey if publicKey
|
@@ -251,26 +284,28 @@ class IBMSmartCloud
|
|
251
284
|
end
|
252
285
|
end
|
253
286
|
|
254
|
-
|
287
|
+
help_for :remove_keypair, [:name]
|
255
288
|
def remove_keypair(name)
|
256
289
|
delete("/keys/#{name}")
|
257
290
|
true
|
258
291
|
end
|
292
|
+
alias remove_key remove_keypair
|
293
|
+
alias delete_key remove_keypair
|
259
294
|
|
260
|
-
|
295
|
+
help_for :describe_key, [:name]
|
261
296
|
def describe_key(name)
|
262
297
|
get("/keys/#{name}").PublicKey
|
263
298
|
end
|
264
299
|
|
265
|
-
|
300
|
+
help_for :update_key, [:name, :publicKey]
|
266
301
|
def update_key(name, key)
|
267
302
|
put("/keys/#{name}", :publicKey => key)
|
268
303
|
end
|
269
304
|
|
270
305
|
# If address_id is supplied, will return info on that address only
|
271
|
-
|
306
|
+
help_for :describe_addresses, [{:address_id=>:opt}]
|
272
307
|
def describe_addresses(address_id=nil)
|
273
|
-
response = get("/addresses").Address
|
308
|
+
response = arrayize(get("/addresses").Address)
|
274
309
|
response.each do |a|
|
275
310
|
a["State"] = @states["ip"][a.State.to_i]
|
276
311
|
end
|
@@ -283,49 +318,48 @@ class IBMSmartCloud
|
|
283
318
|
def display_addresses
|
284
319
|
addresses = describe_addresses
|
285
320
|
|
286
|
-
|
287
|
-
|
288
|
-
|
321
|
+
table = Terminal::Table.new do |t|
|
322
|
+
t.headings = ['ID', 'Loc', 'State', 'IP']
|
323
|
+
addresses.each do |a|
|
324
|
+
t.add_row [a.ID, a.Location, a.State, a.IP]
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
logger.info table
|
289
329
|
end
|
290
330
|
|
291
331
|
# Allows you to poll for a specific storage state
|
292
332
|
# ex: storage_state_is?("123456", "UNMOUNTED")
|
293
333
|
# storage state string can be given as a string or symbol
|
294
|
-
|
334
|
+
help_for :describe_addresses, [{:address_id=>:req}, {:state_string=>%w(new attached etc...)}]
|
295
335
|
def address_state_is?(address_id, state_string)
|
296
336
|
v = describe_addresses(address_id)
|
297
337
|
v.State.to_s == state_string.to_s.upcase
|
298
338
|
end
|
299
339
|
|
300
340
|
def describe_keys
|
301
|
-
get("/keys").PublicKey
|
341
|
+
arrayize(get("/keys").PublicKey)
|
302
342
|
end
|
303
343
|
|
304
344
|
def describe_unused_keys
|
305
345
|
describe_keys.select {|key| key.Instances == {}}
|
306
346
|
end
|
307
347
|
|
308
|
-
def
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
if input == 'y'
|
316
|
-
remove_keypair(key.KeyName)
|
317
|
-
else
|
318
|
-
puts "Not removing key #{key.KeyName}"
|
319
|
-
end
|
348
|
+
def display_keys
|
349
|
+
keys = describe_keys
|
350
|
+
|
351
|
+
table = Terminal::Table.new do |t|
|
352
|
+
t.headings = ['KeyName', 'Used By Instances', 'Last Modified']
|
353
|
+
keys.each do |k|
|
354
|
+
t.add_row [k.KeyName, (k.Instances.empty? ? '[NONE]' : arrayize(k.Instances.InstanceID).join("\n")), time_format( k.LastModifiedTime )]
|
320
355
|
end
|
321
356
|
end
|
357
|
+
|
358
|
+
logger.info table
|
322
359
|
end
|
360
|
+
alias display_keypairs display_keys
|
323
361
|
|
324
|
-
|
325
|
-
logger.info "\nKeyName".ljust(50) + " | Instance ID's\n" + describe_keys.map {|key| key.KeyName.strip.ljust(50) + " | " + (key.Instances.empty? ? '[]' : key.Instances.InstanceID.inspect )}.join("\n")
|
326
|
-
end
|
327
|
-
|
328
|
-
args :supported_instance_types, [:image_id]
|
362
|
+
help_for :supported_instance_types, [:image_id]
|
329
363
|
def supported_instance_types(image_id)
|
330
364
|
img=describe_image(image_id)
|
331
365
|
arrayize(img.SupportedInstanceTypes.InstanceType).map(&:ID)
|
@@ -337,8 +371,8 @@ class IBMSmartCloud
|
|
337
371
|
|
338
372
|
# Optionally supply a location to get offerings filtered to a particular location
|
339
373
|
# Optionally supply a name (Small, Medium, Large) to get the specific offering
|
340
|
-
|
341
|
-
|
374
|
+
help_for :describe_volume_offerings, [{:location => :opt}, {:name => :opt}]
|
375
|
+
help_for :describe_storage_offerings, [{:location => :opt}, {:name => :opt}]
|
342
376
|
def describe_storage_offerings(location=nil, name=nil)
|
343
377
|
response = get("/offerings/storage")
|
344
378
|
if location
|
@@ -361,13 +395,18 @@ class IBMSmartCloud
|
|
361
395
|
# ex: create_volume("yan", 61, "Small", "20001208", 61)
|
362
396
|
#
|
363
397
|
# NOTE: storage area id is not currently supported by IBM (according to docs)
|
364
|
-
|
398
|
+
help_for :create_volume, [{:name => :req},{:location_id => :req},{:size => ['Small','Medium','Large']}, {:offering_id => :opt}, {:format => :opt}]
|
365
399
|
def create_volume(name, location, size, offering_id=nil, format="EXT3")
|
366
400
|
|
367
401
|
# figure out the offering ID automatically based on location and size
|
368
402
|
if offering_id.nil?
|
369
403
|
logger.debug "Looking up volume offerings based on location: #{location} and size: #{size}"
|
370
|
-
|
404
|
+
offering = describe_volume_offerings(location, size)
|
405
|
+
offering_id = if offering
|
406
|
+
offering.ID
|
407
|
+
else
|
408
|
+
raise "Unable to locate offering with location #{location}, size #{size}."
|
409
|
+
end
|
371
410
|
end
|
372
411
|
|
373
412
|
logger.debug "Creating volume...please wait."
|
@@ -376,21 +415,13 @@ class IBMSmartCloud
|
|
376
415
|
end
|
377
416
|
|
378
417
|
# Optionally takes a volume id, if none supplied, will show all volumes
|
379
|
-
|
418
|
+
help_for :describe_storage, [{:volume_id => :opt}]
|
380
419
|
def describe_storage(volume_id=nil)
|
381
420
|
response = volume_id ? get("/storage/#{volume_id}") : get("/storage")
|
382
421
|
|
383
|
-
|
384
|
-
|
385
|
-
v["State"] = @states["storage"][v.State.to_i]
|
386
|
-
end
|
387
|
-
elsif response.Volume
|
388
|
-
response.Volume["State"] = @states["storage"][response.Volume.State.to_i]
|
389
|
-
else
|
390
|
-
return []
|
422
|
+
arrayize(response.Volume).each do |v|
|
423
|
+
v["State"] = @states["storage"][v.State.to_i]
|
391
424
|
end
|
392
|
-
|
393
|
-
response.Volume
|
394
425
|
end
|
395
426
|
|
396
427
|
alias describe_volumes describe_storage
|
@@ -398,16 +429,21 @@ class IBMSmartCloud
|
|
398
429
|
|
399
430
|
# This does a human-usable version of describe_volumes with the critical info (name, id, state)
|
400
431
|
# Optionally takes a filter (currently supports state). i.e. display_volumes(:state => :mounted)
|
401
|
-
|
432
|
+
help_for :display_volumes, [{:filters => :opt}], %{
|
433
|
+
Example filters: {:Name => "match-name", :Location => 101}
|
434
|
+
}
|
402
435
|
def display_volumes(filter={})
|
403
436
|
vols = describe_volumes
|
404
437
|
vols = filter_and_sort(vols, filter)
|
405
438
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
439
|
+
table = Terminal::Table.new do |t|
|
440
|
+
t.headings = %w(Volume State Loc Name CreatedTime)
|
441
|
+
vols.each do |vol|
|
442
|
+
t.add_row [vol.ID, vol.State, vol.Location, vol.Name, time_format(vol.CreatedTime)]
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
logger.info table
|
411
447
|
end
|
412
448
|
|
413
449
|
alias display_storage display_volumes
|
@@ -415,9 +451,10 @@ class IBMSmartCloud
|
|
415
451
|
# Allows you to poll for a specific storage state
|
416
452
|
# ex: storage_state_is?("123456", "UNMOUNTED")
|
417
453
|
# storage state string can be given as a string or symbol
|
418
|
-
|
454
|
+
help_for :storage_state_is?, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
|
419
455
|
def storage_state_is?(volume_id, state_string)
|
420
456
|
v = describe_storage(volume_id)
|
457
|
+
v = v.first if v.is_a?(Array)
|
421
458
|
|
422
459
|
@last_storage_state||={}
|
423
460
|
if @last_storage_state[volume_id.to_s] != v.State
|
@@ -433,7 +470,7 @@ class IBMSmartCloud
|
|
433
470
|
|
434
471
|
end
|
435
472
|
|
436
|
-
|
473
|
+
help_for :instance_state_is?, [{:instance_id=> :req}, {:state_string => %w(active stopping etc...)}]
|
437
474
|
def instance_state_is?(instance_id, state_string)
|
438
475
|
v = describe_instance(instance_id)
|
439
476
|
|
@@ -451,7 +488,7 @@ class IBMSmartCloud
|
|
451
488
|
end
|
452
489
|
|
453
490
|
# Polls until volume state is matched. When it is, returns entire volume descriptor hash.
|
454
|
-
|
491
|
+
help_for :poll_for_volume_state, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
|
455
492
|
def poll_for_volume_state(volume_id, state_string, polling_interval=5)
|
456
493
|
logger.debug "Polling for volume #{volume_id} to acquire state #{state_string} (interval: #{polling_interval})..."
|
457
494
|
while(true)
|
@@ -462,7 +499,7 @@ class IBMSmartCloud
|
|
462
499
|
end
|
463
500
|
|
464
501
|
# Polls until instance state is matched. When it is, returns entire instance descriptor hash.
|
465
|
-
|
502
|
+
help_for :poll_for_instance_state, [{:instance_id => :req}, {:state_string => %w(active stopped etc...)}]
|
466
503
|
def poll_for_instance_state(instance_id, state_string, polling_interval=5)
|
467
504
|
logger.debug "Polling for instance #{instance_id} to acquire state #{state_string} (interval: #{polling_interval})..."
|
468
505
|
while(true)
|
@@ -473,7 +510,9 @@ class IBMSmartCloud
|
|
473
510
|
end
|
474
511
|
|
475
512
|
# Deletes many instances in a thread
|
476
|
-
|
513
|
+
help_for :delete_instances, [:instance_ids], %{
|
514
|
+
Example: smartcloud "delete_instances(12345,12346,12347)"
|
515
|
+
}
|
477
516
|
def delete_instances(*instance_ids)
|
478
517
|
threads=[]
|
479
518
|
instance_ids.each {|id| logger.info "Sending delete request for: #{id}..."; threads << Thread.new { delete_instance(id) }; logger.info "Finished delete request for #{id}" }
|
@@ -481,24 +520,25 @@ class IBMSmartCloud
|
|
481
520
|
true
|
482
521
|
end
|
483
522
|
|
484
|
-
|
523
|
+
help_for :rename_instance, [:instance_id, :new_name]
|
485
524
|
def rename_instance(instance_id, new_name)
|
486
525
|
put("/instances/#{instance_id}", :name => new_name)
|
526
|
+
true
|
487
527
|
end
|
488
528
|
|
489
|
-
|
529
|
+
help_for :delete_instance, [:instance_id]
|
490
530
|
def delete_instance(instance_id)
|
491
531
|
delete("/instances/#{instance_id}")
|
492
532
|
true
|
493
533
|
end
|
494
534
|
|
495
|
-
|
535
|
+
help_for :restart_instance, [:instance_id]
|
496
536
|
def restart_instance(instance_id)
|
497
537
|
put("/instances/#{instance_id}", :state => "restart")
|
498
538
|
true
|
499
539
|
end
|
500
540
|
|
501
|
-
|
541
|
+
help_for :describe_instance, [:instance_id]
|
502
542
|
def describe_instance(instance_id)
|
503
543
|
response = get("instances/#{instance_id}").Instance
|
504
544
|
response["Status"] = @states["instance"][response.Status.to_i]
|
@@ -508,7 +548,7 @@ class IBMSmartCloud
|
|
508
548
|
# You can filter by any instance attributes such as
|
509
549
|
# describe_instances(:name => "FOO_BAR", :status => 'ACTIVE')
|
510
550
|
# in the case of status you can also use symbols like :status => :active
|
511
|
-
|
551
|
+
help_for :describe_instances, [:filters => :opt]
|
512
552
|
def describe_instances(filters={})
|
513
553
|
instances = arrayize(get("instances").Instance)
|
514
554
|
|
@@ -525,30 +565,34 @@ class IBMSmartCloud
|
|
525
565
|
# display_instances(:order => "Name") or :order => "LaunchTime"
|
526
566
|
#
|
527
567
|
|
528
|
-
|
568
|
+
help_for :display_instances, [:filters => :opt], %{
|
569
|
+
Example: smartcloud "display_instances(:name=>'matchMe')"
|
570
|
+
}
|
529
571
|
def display_instances(filters={})
|
530
572
|
instances = describe_instances(filters)
|
531
573
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
574
|
+
table = Terminal::Table.new do |t|
|
575
|
+
t.headings = ['ID', 'LaunchTime', 'Name', 'Key', 'ID', 'Loc', 'Status', 'IP', 'Volume']
|
576
|
+
instances.each do |ins|
|
577
|
+
launchTime = time_format(ins.LaunchTime)
|
578
|
+
t.add_row [ins.ID, launchTime, ins.Name, ins.KeyName, ins.ImageID, ins.Location, ins.Status, ins.IP && ins.IP.strip, ins.Volume]
|
579
|
+
end
|
580
|
+
end
|
581
|
+
table.style = {:padding_left => 0, :padding_right => 0, :border_y => ' ', :border_x => '-'}
|
582
|
+
logger.info table
|
539
583
|
end
|
540
584
|
|
541
585
|
|
542
|
-
|
586
|
+
help_for :describe_image, [:image_id]
|
543
587
|
def describe_image(image_id)
|
544
588
|
image = get("offerings/image/#{image_id}").Image
|
545
589
|
image["State"] = @states["image"][image.State.to_i]
|
546
590
|
image
|
547
591
|
end
|
548
592
|
|
549
|
-
|
593
|
+
help_for :describe_images, [:filters => :opt]
|
550
594
|
def describe_images(filters={})
|
551
|
-
images = arrayize(get("offerings/image
|
595
|
+
images = arrayize(get("offerings/image").Image)
|
552
596
|
images.each {|img| img["State"] = @states["image"][img.State.to_i]}
|
553
597
|
filters[:order] ||= "Location"
|
554
598
|
images = filter_and_sort(images, filters)
|
@@ -558,20 +602,25 @@ class IBMSmartCloud
|
|
558
602
|
get("offerings/image/#{image_id}/manifest").Manifest
|
559
603
|
end
|
560
604
|
|
561
|
-
|
605
|
+
help_for :display_images, [:filters => :opt]
|
562
606
|
def display_images(filters={})
|
563
607
|
images = describe_images(filters)
|
564
608
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
609
|
+
table = Terminal::Table.new do |t|
|
610
|
+
t.headings = ['ID', 'Loc', 'Name & Type', 'Platform', 'Arch']
|
611
|
+
images.each do |i|
|
612
|
+
types = arrayize(i.SupportedInstanceTypes.InstanceType).map(&:ID).join("\n") rescue "[INSTANCE TYPE UNKNOWN]"
|
613
|
+
t.add_row [i.ID, i.Location, i.Name + "\n" + types, i.Platform, i.Architecture]
|
614
|
+
t.add_separator unless i == images.last
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
logger.info table
|
570
619
|
end
|
571
620
|
|
572
621
|
def delete(path)
|
573
622
|
rescue_and_retry_errors do
|
574
|
-
output =
|
623
|
+
output = @http_client.delete File.join(@api_url, path), :accept => :response
|
575
624
|
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
576
625
|
end
|
577
626
|
end
|
@@ -579,22 +628,38 @@ class IBMSmartCloud
|
|
579
628
|
def put(path, params={}, param_remap={})
|
580
629
|
rescue_and_retry_errors do
|
581
630
|
param_string = make_param_string(params, param_remap)
|
582
|
-
output =
|
631
|
+
output = @http_client.put File.join(@api_url, path), param_string, :accept => :response
|
583
632
|
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
584
633
|
end
|
585
634
|
end
|
586
635
|
|
587
636
|
def get(path)
|
588
637
|
rescue_and_retry_errors do
|
589
|
-
output =
|
590
|
-
|
638
|
+
output = if @simulated_response
|
639
|
+
@simulated_response
|
640
|
+
else
|
641
|
+
@http_client.get File.join(@api_url, path)
|
642
|
+
end
|
643
|
+
|
644
|
+
# Save Response for posterity
|
645
|
+
if @save_response && !output.empty?
|
646
|
+
response_file = "responses/#{path.gsub('/','_').gsub(/^_/,'')}.#{Time.now.to_i}"
|
647
|
+
logger.info "Saving response to: #{response_file}"
|
648
|
+
File.open(response_file,'w') {|f| f.write(output)}
|
649
|
+
end
|
650
|
+
|
651
|
+
if output && !output.empty?
|
652
|
+
XmlSimple.xml_in(output, {'ForceArray' => nil})
|
653
|
+
else
|
654
|
+
raise "Empty response!"
|
655
|
+
end
|
591
656
|
end
|
592
657
|
end
|
593
658
|
|
594
659
|
def post(path, params={}, param_remap=nil)
|
595
660
|
rescue_and_retry_errors do
|
596
661
|
param_string = make_param_string(params, param_remap)
|
597
|
-
output =
|
662
|
+
output = @http_client.post File.join(@api_url, path), param_string, :accept => :response
|
598
663
|
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
599
664
|
end
|
600
665
|
end
|
@@ -618,6 +683,8 @@ class IBMSmartCloud
|
|
618
683
|
def rescue_and_retry_errors(&block)
|
619
684
|
block.call
|
620
685
|
rescue timeouts_and_500s => e
|
686
|
+
logger.warn e.inspect
|
687
|
+
|
621
688
|
is_500 = e.message =~ /500/
|
622
689
|
|
623
690
|
@retries -= 1
|
@@ -687,7 +754,10 @@ class IBMSmartCloud
|
|
687
754
|
array_or_object.is_a?(Array) ? array_or_object : [array_or_object]
|
688
755
|
end
|
689
756
|
|
757
|
+
def time_format(time)
|
758
|
+
DateTime.parse(time).strftime("%Y-%m-%d %I:%M%p")
|
759
|
+
end
|
690
760
|
end
|
691
761
|
|
692
762
|
# predefine an instance for convenience
|
693
|
-
@smartcloud = IBMSmartCloud.new
|
763
|
+
@smartcloud = IBMSmartCloud.new if ENV['SMARTCLOUD_USERNAME'] && ENV['SMARTCLOUD_PASSWORD']
|