cft_smartcloud 0.2.2 → 0.3.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.
- 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']
|