cft_smartcloud 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/CHANGELOG +8 -0
- data/LICENSE +178 -0
- data/README.rdoc +71 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/smartcloud +37 -0
- data/cft_smartcloud.gemspec +176 -0
- data/lib/cli_tools/README.txt +50 -0
- data/lib/cli_tools/ic-add-keypair.cmd +29 -0
- data/lib/cli_tools/ic-add-keypair.sh +15 -0
- data/lib/cli_tools/ic-allocate-address.cmd +29 -0
- data/lib/cli_tools/ic-allocate-address.sh +14 -0
- data/lib/cli_tools/ic-attach-volume.cmd +27 -0
- data/lib/cli_tools/ic-attach-volume.sh +27 -0
- data/lib/cli_tools/ic-clone-image.cmd +27 -0
- data/lib/cli_tools/ic-clone-image.sh +14 -0
- data/lib/cli_tools/ic-clone-volume.cmd +27 -0
- data/lib/cli_tools/ic-clone-volume.sh +60 -0
- data/lib/cli_tools/ic-cmd.cmd +41 -0
- data/lib/cli_tools/ic-cmd.sh +38 -0
- data/lib/cli_tools/ic-create-instance.cmd +27 -0
- data/lib/cli_tools/ic-create-instance.sh +14 -0
- data/lib/cli_tools/ic-create-password.cmd +27 -0
- data/lib/cli_tools/ic-create-password.sh +14 -0
- data/lib/cli_tools/ic-create-volume.cmd +27 -0
- data/lib/cli_tools/ic-create-volume.sh +14 -0
- data/lib/cli_tools/ic-delete-image.cmd +27 -0
- data/lib/cli_tools/ic-delete-image.sh +14 -0
- data/lib/cli_tools/ic-delete-instance.cmd +27 -0
- data/lib/cli_tools/ic-delete-instance.sh +14 -0
- data/lib/cli_tools/ic-delete-volume.cmd +27 -0
- data/lib/cli_tools/ic-delete-volume.sh +14 -0
- data/lib/cli_tools/ic-describe-address-offerings.cmd +27 -0
- data/lib/cli_tools/ic-describe-address-offerings.sh +14 -0
- data/lib/cli_tools/ic-describe-addresses.cmd +27 -0
- data/lib/cli_tools/ic-describe-addresses.sh +14 -0
- data/lib/cli_tools/ic-describe-image-agreement.cmd +27 -0
- data/lib/cli_tools/ic-describe-image-agreement.sh +14 -0
- data/lib/cli_tools/ic-describe-image.cmd +27 -0
- data/lib/cli_tools/ic-describe-image.sh +14 -0
- data/lib/cli_tools/ic-describe-images.cmd +27 -0
- data/lib/cli_tools/ic-describe-images.sh +14 -0
- data/lib/cli_tools/ic-describe-instance.cmd +27 -0
- data/lib/cli_tools/ic-describe-instance.sh +14 -0
- data/lib/cli_tools/ic-describe-instances.cmd +27 -0
- data/lib/cli_tools/ic-describe-instances.sh +14 -0
- data/lib/cli_tools/ic-describe-keypair.cmd +27 -0
- data/lib/cli_tools/ic-describe-keypair.sh +14 -0
- data/lib/cli_tools/ic-describe-keypairs.cmd +27 -0
- data/lib/cli_tools/ic-describe-keypairs.sh +14 -0
- data/lib/cli_tools/ic-describe-location.cmd +14 -0
- data/lib/cli_tools/ic-describe-location.sh +14 -0
- data/lib/cli_tools/ic-describe-locations.cmd +14 -0
- data/lib/cli_tools/ic-describe-locations.sh +14 -0
- data/lib/cli_tools/ic-describe-request.cmd +27 -0
- data/lib/cli_tools/ic-describe-request.sh +14 -0
- data/lib/cli_tools/ic-describe-vlans.cmd +27 -0
- data/lib/cli_tools/ic-describe-vlans.sh +14 -0
- data/lib/cli_tools/ic-describe-volume-offerings.cmd +27 -0
- data/lib/cli_tools/ic-describe-volume-offerings.sh +14 -0
- data/lib/cli_tools/ic-describe-volume.cmd +27 -0
- data/lib/cli_tools/ic-describe-volume.sh +14 -0
- data/lib/cli_tools/ic-describe-volumes.cmd +27 -0
- data/lib/cli_tools/ic-describe-volumes.sh +14 -0
- data/lib/cli_tools/ic-detach-volume.cmd +27 -0
- data/lib/cli_tools/ic-detach-volume.sh +27 -0
- data/lib/cli_tools/ic-extend-reservation.cmd +27 -0
- data/lib/cli_tools/ic-extend-reservation.sh +14 -0
- data/lib/cli_tools/ic-generate-keypair.cmd +27 -0
- data/lib/cli_tools/ic-generate-keypair.sh +14 -0
- data/lib/cli_tools/ic-release-address.cmd +27 -0
- data/lib/cli_tools/ic-release-address.sh +14 -0
- data/lib/cli_tools/ic-remove-keypair.cmd +27 -0
- data/lib/cli_tools/ic-remove-keypair.sh +14 -0
- data/lib/cli_tools/ic-restart-instance.cmd +27 -0
- data/lib/cli_tools/ic-restart-instance.sh +14 -0
- data/lib/cli_tools/ic-save-instance.cmd +27 -0
- data/lib/cli_tools/ic-save-instance.sh +14 -0
- data/lib/cli_tools/ic-set-default-key.cmd +27 -0
- data/lib/cli_tools/ic-set-default-key.sh +14 -0
- data/lib/cli_tools/ic-update-instance.cmd +27 -0
- data/lib/cli_tools/ic-update-instance.sh +14 -0
- data/lib/cli_tools/ic-update-keypair.cmd +27 -0
- data/lib/cli_tools/ic-update-keypair.sh +14 -0
- data/lib/cli_tools/lib/DeveloperCloud_API_Client_JAR.jar +0 -0
- data/lib/cli_tools/lib/DeveloperCloud_CMD_Tool.jar +0 -0
- data/lib/cli_tools/lib/commons-beanutils-1.6.1.jar +0 -0
- data/lib/cli_tools/lib/commons-cli-1.2.jar +0 -0
- data/lib/cli_tools/lib/commons-codec-1.3.jar +0 -0
- data/lib/cli_tools/lib/commons-collections-3.2.1.jar +0 -0
- data/lib/cli_tools/lib/commons-digester-1.8.jar +0 -0
- data/lib/cli_tools/lib/commons-httpclient-3.1.jar +0 -0
- data/lib/cli_tools/lib/commons-lang-2.3.jar +0 -0
- data/lib/cli_tools/lib/commons-logging-1.1.1.jar +0 -0
- data/lib/cli_tools/logging.properties +7 -0
- data/lib/cli_tools/manifest.rmd +26 -0
- data/lib/config/config.yml +50 -0
- data/lib/hash_fix.rb +37 -0
- data/lib/mime-types-1.16/History.txt +107 -0
- data/lib/mime-types-1.16/Install.txt +17 -0
- data/lib/mime-types-1.16/Licence.txt +15 -0
- data/lib/mime-types-1.16/Manifest.txt +12 -0
- data/lib/mime-types-1.16/README.txt +28 -0
- data/lib/mime-types-1.16/Rakefile +316 -0
- data/lib/mime-types-1.16/lib/mime/types.rb +751 -0
- data/lib/mime-types-1.16/lib/mime/types.rb.data +1324 -0
- data/lib/mime-types-1.16/mime-types.gemspec +43 -0
- data/lib/mime-types-1.16/setup.rb +1585 -0
- data/lib/mime-types-1.16/test/test_mime_type.rb +356 -0
- data/lib/mime-types-1.16/test/test_mime_types.rb +122 -0
- data/lib/mock_smartcloud.rb +53 -0
- data/lib/rest-client-1.6.3/README.rdoc +276 -0
- data/lib/rest-client-1.6.3/Rakefile +66 -0
- data/lib/rest-client-1.6.3/VERSION +1 -0
- data/lib/rest-client-1.6.3/bin/restclient +92 -0
- data/lib/rest-client-1.6.3/history.md +112 -0
- data/lib/rest-client-1.6.3/lib/rest-client.rb +2 -0
- data/lib/rest-client-1.6.3/lib/rest_client.rb +2 -0
- data/lib/rest-client-1.6.3/lib/restclient/abstract_response.rb +106 -0
- data/lib/rest-client-1.6.3/lib/restclient/exceptions.rb +193 -0
- data/lib/rest-client-1.6.3/lib/restclient/net_http_ext.rb +21 -0
- data/lib/rest-client-1.6.3/lib/restclient/payload.rb +220 -0
- data/lib/rest-client-1.6.3/lib/restclient/raw_response.rb +34 -0
- data/lib/rest-client-1.6.3/lib/restclient/request.rb +314 -0
- data/lib/rest-client-1.6.3/lib/restclient/resource.rb +169 -0
- data/lib/rest-client-1.6.3/lib/restclient/response.rb +24 -0
- data/lib/rest-client-1.6.3/lib/restclient.rb +174 -0
- data/lib/restclient_fix.rb +41 -0
- data/lib/smartcloud.rb +616 -0
- data/lib/smartcloud_logger.rb +20 -0
- data/lib/xml-simple-1.0.12/lib/xmlsimple.rb +1028 -0
- data/script/console +3 -0
- data/test/helper.rb +22 -0
- metadata +196 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#####################################################################################
|
3
|
+
# Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
|
4
|
+
# This copyrighted material is the property of Cohesive Flexible Technologies and
|
5
|
+
# is subject to the license terms of the product it is contained within, whether
|
6
|
+
# in text or compiled form. It is licensed under the terms expressed in the
|
7
|
+
# accompanying README and LICENSE files.
|
8
|
+
#
|
9
|
+
# This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
|
10
|
+
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
#####################################################################################
|
12
|
+
|
13
|
+
# Enables modification of timeout in RestClient
|
14
|
+
module RestClient
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_accessor :timeout
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get(url, headers={}, &block)
|
21
|
+
Request.execute(:method => :get, :url => url, :headers => headers, :timeout => @timeout, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.post(url, payload, headers={}, &block)
|
25
|
+
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.patch(url, payload, headers={}, &block)
|
29
|
+
Request.execute(:method => :patch, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.put(url, payload, headers={}, &block)
|
33
|
+
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, :timeout => @timeout, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.delete(url, headers={}, &block)
|
37
|
+
Request.execute(:method => :delete, :url => url, :headers => headers, :timeout => @timeout, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
data/lib/smartcloud.rb
ADDED
@@ -0,0 +1,616 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#####################################################################################
|
3
|
+
# Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
|
4
|
+
# This copyrighted material is the property of Cohesive Flexible Technologies and
|
5
|
+
# is subject to the license terms of the product it is contained within, whether
|
6
|
+
# in text or compiled form. It is licensed under the terms expressed in the
|
7
|
+
# accompanying README and LICENSE files.
|
8
|
+
#
|
9
|
+
# This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
|
10
|
+
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
#####################################################################################
|
12
|
+
|
13
|
+
Dir["#{File.dirname(__FILE__)}/**/**"].each {|dir| $LOAD_PATH << dir }
|
14
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
15
|
+
|
16
|
+
require 'tempfile'
|
17
|
+
require 'logger'
|
18
|
+
require 'cgi'
|
19
|
+
require 'rest_client'
|
20
|
+
require 'yaml'
|
21
|
+
require 'restclient_fix'
|
22
|
+
require 'hash_fix'
|
23
|
+
require 'xmlsimple'
|
24
|
+
require 'smartcloud_logger'
|
25
|
+
|
26
|
+
IBM_TOOLS_HOME=File.join(File.dirname(__FILE__), "cli_tools") unless defined?(IBM_TOOLS_HOME)
|
27
|
+
|
28
|
+
# Encapsulates communications with IBM SmartCloud via REST
|
29
|
+
class IBMSmartCloud
|
30
|
+
|
31
|
+
attr_accessor :logger
|
32
|
+
|
33
|
+
def initialize(username, password, logger=nil, debug=false)
|
34
|
+
@username = username
|
35
|
+
@password = password
|
36
|
+
@logger = logger || SmartcloudLogger.new(STDOUT)
|
37
|
+
@debug = debug
|
38
|
+
|
39
|
+
@config = YAML.load_file(File.join(File.dirname(__FILE__), "config/config.yml"))
|
40
|
+
@states = @config["states"]
|
41
|
+
|
42
|
+
@api_url = @config["api_url"]
|
43
|
+
@api_url.gsub!("https://", "https://#{CGI::escape(username)}:#{CGI::escape(password)}@")
|
44
|
+
|
45
|
+
RestClient.timeout = 120 # ibm requests can be very slow
|
46
|
+
RestClient.log = @logger if @debug
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
@config = YAML.load_file(File.join(File.dirname(__FILE__), "config/config.yml"))
|
51
|
+
attr_reader :method_help
|
52
|
+
attr_reader :config
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.args(method, args)
|
56
|
+
@method_help||={}
|
57
|
+
@method_help[method.to_s] = args
|
58
|
+
end
|
59
|
+
|
60
|
+
def help(method=nil)
|
61
|
+
if method
|
62
|
+
args = (self.class.method_help[method.to_s])
|
63
|
+
if args.nil?
|
64
|
+
puts method.to_s
|
65
|
+
else
|
66
|
+
args = args.map do |arg|
|
67
|
+
if arg.is_a?(Hash)
|
68
|
+
# If an argument is required, just list it
|
69
|
+
if arg.values.first==:req
|
70
|
+
arg.keys.first.to_s
|
71
|
+
# If it's optional, list it in brackets
|
72
|
+
elsif arg.values.first==:opt
|
73
|
+
"[#{arg.keys.first.to_s}]"
|
74
|
+
# If there is an array of options, list them
|
75
|
+
else
|
76
|
+
"#{arg.keys.first.to_s}=>#{arg.values.first.inspect}"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
arg
|
80
|
+
end
|
81
|
+
end.join(", ")
|
82
|
+
|
83
|
+
puts "#{method.to_s}(#{args})"
|
84
|
+
end
|
85
|
+
else
|
86
|
+
methods = public_methods - Object.public_methods - ['post','get','put','delete','logger','logger=','help']
|
87
|
+
puts methods.sort.join("\n")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get a list of data centers
|
92
|
+
def describe_locations
|
93
|
+
get("/locations").Location
|
94
|
+
end
|
95
|
+
|
96
|
+
def display_locations
|
97
|
+
log = describe_locations.map {|loc| "#{loc.ID} #{loc.Location}"}.join("\n")
|
98
|
+
logger.info "\n#{log}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Create an instance. The instance_params hash can follow
|
102
|
+
# CLI-style attribute naming. I.e. "image-id" or "data-center"
|
103
|
+
# or API-style (imageID, location). If the CLI-style params are
|
104
|
+
# provided, they will be remapped to the correct API params.
|
105
|
+
args :create_instance, [:instance_params_hash]
|
106
|
+
def create_instance(instance_params)
|
107
|
+
param_remap = { "name" => "name",
|
108
|
+
"image-id" => "imageID",
|
109
|
+
"instance-type" => "instanceType",
|
110
|
+
"data-center" => "location",
|
111
|
+
"key-name" => "publicKey",
|
112
|
+
"ip-address-id" => "ip",
|
113
|
+
"volume-id" => "volumeID",
|
114
|
+
"configuration" => "ConfigurationData",
|
115
|
+
"vlan-id" => "vlanID",
|
116
|
+
"antiCollocationInstance" => "antiCollocationInstance",
|
117
|
+
"isMiniEphemeral" => "isMiniEphemeral"
|
118
|
+
}
|
119
|
+
|
120
|
+
# api does not take description
|
121
|
+
instance_params.delete("description")
|
122
|
+
|
123
|
+
# configuration data has to be changed from a string like
|
124
|
+
# <configuration>{contextmanager:baml-c3-master.cohesiveft.com,clustername:BAML_poc_pk0515,role:[nfs-client-setup|newyork_master_refdata_member|install-luci|rhel-openlikewise-client-setup|join-domain],hostname:r550n107}</configuration>
|
125
|
+
# to a standard list of POST params like
|
126
|
+
# contextmanager=baml-c3-mager&clustername=BAML...
|
127
|
+
configuration_data = instance_params.delete("configuration") || instance_params.delete("ConfigurationData")
|
128
|
+
if configuration_data
|
129
|
+
if configuration_data =~ /\s+/
|
130
|
+
logger.warn "<configuration> tag should not contain spaces! Correct format looks like: <configuration>{foo:bar,baz:quux}</configuration>. Spaces will be removed."
|
131
|
+
end
|
132
|
+
configuration_data.delete!("{}") # get rid of curly braces
|
133
|
+
config_data_params = configuration_data.split(",").map{|param| param.split(":")} # split each foo:bar into key and value
|
134
|
+
config_data_params.each {|k,v| instance_params[k]=v} # add it into the standard instance params array
|
135
|
+
end
|
136
|
+
|
137
|
+
post "/instances", instance_params, param_remap
|
138
|
+
end
|
139
|
+
|
140
|
+
# Find a location by its name. Assumes locations have unique names (not verified by ibm docs)
|
141
|
+
args :get_locations_by_name, [:name]
|
142
|
+
def get_location_by_name(name)
|
143
|
+
locations = describe_locations
|
144
|
+
locations.detect {|loc| loc.Name == name}
|
145
|
+
end
|
146
|
+
|
147
|
+
# find all ip address offerings. If a location is specified, give the offering for the location
|
148
|
+
# assumption: only one offering per location. This is assumed, not verified in docs.
|
149
|
+
args :describe_address_offerings, [{:location => :opt}]
|
150
|
+
def describe_address_offerings(location=nil)
|
151
|
+
response=get("/offerings/address").Offerings
|
152
|
+
if location
|
153
|
+
response.detect {|offering| offering.Location.to_s == location.to_s}
|
154
|
+
else
|
155
|
+
response
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Allocate IP address. Offering ID is optional. if not specified, will look up offerings
|
160
|
+
# for this location and pick one
|
161
|
+
args :allocate_address, [{:location => :req}, {:offering_id => :opt}, {:foo => [:bar, :baz, :quux]}]
|
162
|
+
def allocate_address(location, offering_id=nil)
|
163
|
+
if offering_id.nil?
|
164
|
+
offering_id = describe_address_offerings(location).ID
|
165
|
+
end
|
166
|
+
post("/addresses", :offeringID => offering_id, :location => location).Address
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
args :describe_address, [:address_id]
|
171
|
+
def describe_address(address_id)
|
172
|
+
address = get("/addresses/#{address_id}").Address
|
173
|
+
address["State"] = @config.states.ip[address.State]
|
174
|
+
address
|
175
|
+
end
|
176
|
+
|
177
|
+
# Launches a clone image call from CLI
|
178
|
+
args :clone_image, [:name, :description, :image_id]
|
179
|
+
def clone_image(name, description, image_id)
|
180
|
+
post("/offerings/image/#{image_id}", :name => name, :description => description).ImageID
|
181
|
+
end
|
182
|
+
|
183
|
+
# Launches a clone request and returns ID of new volume
|
184
|
+
# TODO: the API call does not work, we have to use the cli for now
|
185
|
+
args :clone_volume, [:name, :source_disk_id]
|
186
|
+
def clone_volume(name, source_disk_id)
|
187
|
+
# result = post("/storage", :name => name, :SourceDiskID => source_disk_id)
|
188
|
+
# result.Volume.ID
|
189
|
+
create_password_file
|
190
|
+
result = run_ibm_tool("ic-clone-volume.sh -n #{name} -S #{source_disk_id}")
|
191
|
+
result =~ /ID : (.*)/
|
192
|
+
return $1 # grab the id of the created volume
|
193
|
+
end
|
194
|
+
|
195
|
+
# Delete the volume
|
196
|
+
args :delete_volume, [:vol_id]
|
197
|
+
def delete_volume(vol_id)
|
198
|
+
delete("/storage/#{vol_id}")
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
# generates a keypair and returns the private key
|
203
|
+
args :generate_keypair, [:name]
|
204
|
+
def generate_keypair(name)
|
205
|
+
response = post("/keys", :name => name)
|
206
|
+
response.PrivateKey.KeyMaterial
|
207
|
+
end
|
208
|
+
|
209
|
+
args :remove_keypair, [:name]
|
210
|
+
def remove_keypair(name)
|
211
|
+
delete("/keys/#{name}")
|
212
|
+
true
|
213
|
+
end
|
214
|
+
|
215
|
+
args :describe_key, [:name]
|
216
|
+
def describe_key(name)
|
217
|
+
get("/keys/#{name}").PublicKey
|
218
|
+
end
|
219
|
+
|
220
|
+
# If address_id is supplied, will return info on that address only
|
221
|
+
args :describe_addresses, [{:address_id=>:opt}]
|
222
|
+
def describe_addresses(address_id=nil)
|
223
|
+
response = get("/addresses").Address
|
224
|
+
response.each do |a|
|
225
|
+
a["State"] = @states["ip"][a.State.to_i]
|
226
|
+
end
|
227
|
+
if address_id
|
228
|
+
response = response.detect {|address| address.ID.to_s == address_id.to_s}
|
229
|
+
end
|
230
|
+
response
|
231
|
+
end
|
232
|
+
|
233
|
+
# Allows you to poll for a specific storage state
|
234
|
+
# ex: storage_state_is?("123456", "UNMOUNTED")
|
235
|
+
# storage state string can be given as a string or symbol
|
236
|
+
args :describe_addresses, [{:address_id=>:req}, {:state_string=>%w(new attached etc...)}]
|
237
|
+
def address_state_is?(address_id, state_string)
|
238
|
+
v = describe_addresses(address_id)
|
239
|
+
v.State.to_s == state_string.to_s.upcase
|
240
|
+
end
|
241
|
+
|
242
|
+
def describe_keys
|
243
|
+
get("/keys").PublicKey
|
244
|
+
end
|
245
|
+
|
246
|
+
def describe_unused_keys
|
247
|
+
describe_keys.select {|key| key.Instances == {}}
|
248
|
+
end
|
249
|
+
|
250
|
+
def delete_unused_keys(auto=false)
|
251
|
+
describe_unused_keys.each do |key|
|
252
|
+
if auto
|
253
|
+
remove_keypair(key.KeyName)
|
254
|
+
else
|
255
|
+
puts "Remove key #{key.KeyName}? (type 'y' to continue): "
|
256
|
+
input = gets.strip
|
257
|
+
if input == 'y'
|
258
|
+
remove_keypair(key.KeyName)
|
259
|
+
else
|
260
|
+
puts "Not removing key #{key.KeyName}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def display_keys
|
267
|
+
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")
|
268
|
+
end
|
269
|
+
|
270
|
+
args :supported_instance_types, [:image_id]
|
271
|
+
def supported_instance_types(image_id)
|
272
|
+
img=describe_image(image_id)
|
273
|
+
arrayize(img.SupportedInstanceTypes.InstanceType).map(&:ID)
|
274
|
+
end
|
275
|
+
# NOTE: doesn't seem to be supported
|
276
|
+
# def describe_vlans
|
277
|
+
# get("/offerings/vlan").Vlan
|
278
|
+
# end
|
279
|
+
|
280
|
+
# Optionally supply a location to get offerings filtered to a particular location
|
281
|
+
# Optionally supply a name (Small, Medium, Large) to get the specific offering
|
282
|
+
args :describe_volume_offerings, [{:location => :opt}, {:name => :opt}]
|
283
|
+
def describe_volume_offerings(location=nil, name=nil)
|
284
|
+
response = get("/offerings/storage")
|
285
|
+
if location
|
286
|
+
filtered_by_location = response.Offerings.select {|o| o.Location.to_s == location.to_s}
|
287
|
+
if name
|
288
|
+
filtered_by_location.detect {|o| o.Name == name}
|
289
|
+
else
|
290
|
+
filtered_by_location
|
291
|
+
end
|
292
|
+
else
|
293
|
+
response.Offerings
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Create a volume. offering_id is optional and will be determined automatically
|
298
|
+
# for you based on the location and volume size.
|
299
|
+
#
|
300
|
+
# ex: create_volume("yan", 61, "Small", "20001208", 61)
|
301
|
+
#
|
302
|
+
# NOTE: storage area id is not currently supported by IBM (according to docs)
|
303
|
+
args :create_volume, [{:name => :req},{:location_id => :req},{:size => ['Small','Medium','Large']}, {:offering_id => :opt}, {:format => :opt}]
|
304
|
+
def create_volume(name, location, size, offering_id=nil, format="EXT3")
|
305
|
+
|
306
|
+
# figure out the offering ID automatically based on location and size
|
307
|
+
if offering_id.nil?
|
308
|
+
logger.debug "Looking up volume offerings based on location: #{location} and size: #{size}"
|
309
|
+
offering_id = describe_volume_offerings(location, size).ID
|
310
|
+
end
|
311
|
+
|
312
|
+
logger.debug "Creating volume...please wait."
|
313
|
+
result = post("/storage", :format => format, :location => location, :name => name, :size => size, :offeringID => offering_id)
|
314
|
+
result.Volume.ID
|
315
|
+
end
|
316
|
+
|
317
|
+
# Optionally takes a volume id, if none supplied, will show all volumes
|
318
|
+
args :describe_storage, [{:volume_id => :opt}]
|
319
|
+
def describe_storage(volume_id=nil)
|
320
|
+
response = volume_id ? get("/storage/#{volume_id}") : get("/storage")
|
321
|
+
|
322
|
+
if response.Volume.is_a?(Array)
|
323
|
+
response.Volume.each do |v|
|
324
|
+
v["State"] = @states["storage"][v.State.to_i]
|
325
|
+
end
|
326
|
+
elsif response.Volume
|
327
|
+
response.Volume["State"] = @states["storage"][response.Volume.State.to_i]
|
328
|
+
else
|
329
|
+
return []
|
330
|
+
end
|
331
|
+
|
332
|
+
response.Volume
|
333
|
+
end
|
334
|
+
|
335
|
+
alias describe_volumes describe_storage
|
336
|
+
alias describe_volume describe_storage
|
337
|
+
|
338
|
+
# This does a human-usable version of describe_volumes with the critical info (name, id, state)
|
339
|
+
# Optionally takes a filter (currently supports state). i.e. display_volumes(:state => :mounted)
|
340
|
+
args :display_volumes, [{:filter => :opt}]
|
341
|
+
def display_volumes(filter={})
|
342
|
+
vols = describe_volumes
|
343
|
+
vols = filter_and_sort(vols, filter)
|
344
|
+
|
345
|
+
log = "\nVolume | State | Loc | Name\n"
|
346
|
+
volsz = vols.map {|vol| vol.ID.ljust(6) + " | " + (vol.State[0..9].ljust(10) rescue '?'.ljust(10)) + " | " + vol.Location.ljust(4) + " | " + vol.Name }.join("\n")
|
347
|
+
log << volsz
|
348
|
+
logger.info log
|
349
|
+
true
|
350
|
+
end
|
351
|
+
|
352
|
+
alias display_storage display_volumes
|
353
|
+
|
354
|
+
# Allows you to poll for a specific storage state
|
355
|
+
# ex: storage_state_is?("123456", "UNMOUNTED")
|
356
|
+
# storage state string can be given as a string or symbol
|
357
|
+
args :storage_state_is?, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
|
358
|
+
def storage_state_is?(volume_id, state_string)
|
359
|
+
v = describe_storage(volume_id)
|
360
|
+
|
361
|
+
@last_storage_state||={}
|
362
|
+
if @last_storage_state[volume_id.to_s] != v.State
|
363
|
+
logger.debug "Volume: #{volume_id}; Current State: #{v.State}; Waiting for: #{state_string.to_s.upcase} " # log it every time it changes
|
364
|
+
end
|
365
|
+
@last_storage_state[volume_id.to_s] = v.State
|
366
|
+
|
367
|
+
if v.State.to_s == state_string.to_s.upcase
|
368
|
+
v
|
369
|
+
else
|
370
|
+
false
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
|
375
|
+
args :instance_state_is?, [{:instance_id=> :req}, {:state_string => %w(active stopping etc...)}]
|
376
|
+
def instance_state_is?(instance_id, state_string)
|
377
|
+
v = describe_instance(instance_id)
|
378
|
+
|
379
|
+
@last_instance_state||={}
|
380
|
+
if @last_instance_state[instance_id.to_s] != v.Status
|
381
|
+
logger.debug "Instance: #{instance_id}; Current State: #{v.Status}; Waiting for: #{state_string.to_s.upcase}" # log it every time it changes
|
382
|
+
end
|
383
|
+
@last_instance_state[instance_id.to_s] = v.Status
|
384
|
+
|
385
|
+
if v.Status.to_s == state_string.to_s.upcase
|
386
|
+
v
|
387
|
+
else
|
388
|
+
false
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Polls until volume state is matched. When it is, returns entire volume descriptor hash.
|
393
|
+
args :poll_for_volume_state, [{:volume_id => :req}, {:state_string => %w(mounted unmounted etc...)}]
|
394
|
+
def poll_for_volume_state(volume_id, state_string, polling_interval=5)
|
395
|
+
logger.debug "Polling for volume #{volume_id} to acquire state #{state_string} (interval: #{polling_interval})..."
|
396
|
+
while(true)
|
397
|
+
descriptor = storage_state_is?(volume_id, state_string)
|
398
|
+
return descriptor if descriptor
|
399
|
+
sleep(polling_interval)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# Polls until instance state is matched. When it is, returns entire instance descriptor hash.
|
404
|
+
args :poll_for_instance_state, [{:instance_id => :req}, {:state_string => %w(active stopped etc...)}]
|
405
|
+
def poll_for_instance_state(instance_id, state_string, polling_interval=5)
|
406
|
+
logger.debug "Polling for instance #{instance_id} to acquire state #{state_string} (interval: #{polling_interval})..."
|
407
|
+
while(true)
|
408
|
+
descriptor = instance_state_is?(instance_id, state_string)
|
409
|
+
return descriptor if descriptor
|
410
|
+
sleep(polling_interval)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Deletes many instances in a thread
|
415
|
+
args :delete_instances, [:instance_ids => [12345,12346,12347, '...']]
|
416
|
+
def delete_instances(instance_ids)
|
417
|
+
instance_ids.each {|id| delete_instance(id) }
|
418
|
+
end
|
419
|
+
|
420
|
+
|
421
|
+
args :delete_instance, [:instance_id]
|
422
|
+
def delete_instance(instance_id)
|
423
|
+
delete("/instances/#{instance_id}")
|
424
|
+
true
|
425
|
+
end
|
426
|
+
|
427
|
+
args :restart_instance, [:instance_id]
|
428
|
+
def restart_instance(instance_id)
|
429
|
+
put("/instances/#{instance_id}", :state => "restart")
|
430
|
+
true
|
431
|
+
end
|
432
|
+
|
433
|
+
args :describe_instance, [:instance_id]
|
434
|
+
def describe_instance(instance_id)
|
435
|
+
response = get("instances/#{instance_id}").Instance
|
436
|
+
response["Status"] = @states["instance"][response.Status.to_i]
|
437
|
+
response
|
438
|
+
end
|
439
|
+
|
440
|
+
# You can filter by any instance attributes such as
|
441
|
+
# describe_instances(:name => "FOO_BAR", :status => 'ACTIVE')
|
442
|
+
# in the case of status you can also use symbols like :status => :active
|
443
|
+
args :describe_instances, [:filters => :opt]
|
444
|
+
def describe_instances(filters={})
|
445
|
+
instances = arrayize(get("instances").Instance)
|
446
|
+
|
447
|
+
instances.each do |instance|
|
448
|
+
instance["Status"] = @states["instance"][instance.Status.to_i]
|
449
|
+
end
|
450
|
+
|
451
|
+
filters[:order] ||= "LaunchTime"
|
452
|
+
instances = filter_and_sort(instances, filters)
|
453
|
+
end
|
454
|
+
|
455
|
+
# Same as describe_instances except prints a human readable summary
|
456
|
+
# Also takes an :order param, examples:
|
457
|
+
# display_instances(:order => "Name") or :order => "LaunchTime"
|
458
|
+
#
|
459
|
+
args :display_instances, [:filters => :opt]
|
460
|
+
def display_instances(filters={})
|
461
|
+
instances = describe_instances(filters)
|
462
|
+
|
463
|
+
log = %{#{"Started".ljust(18)} | #{"Instance".ljust(8)} | #{"Image".ljust(9)} | #{"Loc".ljust(3)} | #{"Status".ljust(10)} | #{"KeyName".ljust(15)} | #{"IP".ljust(15)} | Name\n}
|
464
|
+
log << instances.map do |ins|
|
465
|
+
"#{DateTime.parse(ins.LaunchTime).strftime("%Y-%m-%d %I:%M%p")} | #{ins.ID.ljust(8)} | #{ins.ImageID.ljust(9)} | #{ins.Location.ljust(3)} | #{ins.Status[0..9].ljust(10)} | #{(ins.KeyName || "").strip[0..14].ljust(15)} | #{(ins.IP.strip=="" ? '[NONE]' : ins.IP.strip).to_s.ljust(15)} | #{ins.Name}"
|
466
|
+
end.join("\n")
|
467
|
+
logger.info "\n#{log}"
|
468
|
+
end
|
469
|
+
|
470
|
+
|
471
|
+
args :describe_image, [:image_id]
|
472
|
+
def describe_image(image_id)
|
473
|
+
image = get("offerings/image/#{image_id}").Image
|
474
|
+
image["State"] = @states["image"][image.State.to_i]
|
475
|
+
image
|
476
|
+
end
|
477
|
+
|
478
|
+
args :describe_images, [:filters => :opt]
|
479
|
+
def describe_images(filters={})
|
480
|
+
images = arrayize(get("offerings/image/").Image)
|
481
|
+
images.each {|img| img["State"] = @states["image"][img.State.to_i]}
|
482
|
+
filters[:order] ||= "Location"
|
483
|
+
images = filter_and_sort(images, filters)
|
484
|
+
end
|
485
|
+
|
486
|
+
args :display_images, [:filters => :opt]
|
487
|
+
def display_images(filters={})
|
488
|
+
images = describe_images(filters)
|
489
|
+
|
490
|
+
log = images.map do |i|
|
491
|
+
types = arrayize(i.SupportedInstanceTypes.InstanceType).map(&:ID).join(", ") rescue "[INSTANCE TYPE UNKNOWN]"
|
492
|
+
"#{i.ID} | #{i.Location} | #{i.Name} | #{types}"
|
493
|
+
end.join("\n")
|
494
|
+
logger.info "\n#{log}"
|
495
|
+
end
|
496
|
+
|
497
|
+
def delete(path)
|
498
|
+
output = RestClient.delete File.join(@api_url, path), :accept => :response
|
499
|
+
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
500
|
+
rescue => e
|
501
|
+
raise_restclient_error(e)
|
502
|
+
end
|
503
|
+
|
504
|
+
def put(path, params={}, param_remap={})
|
505
|
+
param_string = make_param_string(params, param_remap)
|
506
|
+
output = RestClient.put File.join(@api_url, path), param_string, :accept => :response
|
507
|
+
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
508
|
+
rescue => e
|
509
|
+
raise_restclient_error(e)
|
510
|
+
end
|
511
|
+
|
512
|
+
def get(path)
|
513
|
+
output = RestClient.get File.join(@api_url, path), :accept => :response
|
514
|
+
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
515
|
+
rescue => e
|
516
|
+
raise_restclient_error(e)
|
517
|
+
end
|
518
|
+
|
519
|
+
def post(path, params={}, param_remap=nil)
|
520
|
+
param_string = make_param_string(params, param_remap)
|
521
|
+
output = RestClient.post File.join(@api_url, path), param_string, :accept => :response
|
522
|
+
response = XmlSimple.xml_in(output, {'ForceArray' => nil})
|
523
|
+
response
|
524
|
+
rescue => e
|
525
|
+
raise_restclient_error(e)
|
526
|
+
end
|
527
|
+
|
528
|
+
private
|
529
|
+
# rest client error details are in the response so we want to
|
530
|
+
# display that as the error, otherwise we lose that info
|
531
|
+
def raise_restclient_error(e)
|
532
|
+
if e.respond_to?(:response) && !e.is_a?(RestClient::RequestTimeout)
|
533
|
+
raise "#{e.message} - #{e.response}"
|
534
|
+
else
|
535
|
+
raise e
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
def make_param_string(params, param_remap)
|
540
|
+
param_string = params.map do |k,v|
|
541
|
+
k=k.to_s # symbol keys turn to string
|
542
|
+
|
543
|
+
# logger.debug "Removing all spaces from parameters, smartcloud API does not allow spaces."
|
544
|
+
# v = v.gsub(/\\s+/,'') # remove all spaces! smartcloud does not like spaces in params
|
545
|
+
if param_remap && param_remap[k]
|
546
|
+
k = param_remap[k]
|
547
|
+
end
|
548
|
+
|
549
|
+
"#{CGI.escape(k)}=#{CGI.escape(v.to_s)}"
|
550
|
+
end.compact.join("&")
|
551
|
+
end
|
552
|
+
|
553
|
+
def filter_and_sort(instances=[], filters={})
|
554
|
+
order_by = filters.delete(:order)
|
555
|
+
|
556
|
+
filters.each do |filter, value|
|
557
|
+
value = value.to_s.upcase if (filter==:status || filter==:state)
|
558
|
+
if filter == :name || filter == :Name
|
559
|
+
instances = instances.select {|inst| inst.send(filter.to_s.capitalize) =~ /#{value}/}
|
560
|
+
else
|
561
|
+
instances = instances.select {|inst| inst.send(filter.to_s.capitalize) == value.to_s}
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
instances = instances.sort_by{|ins| ins.has_key?(order_by) ? ins.send(order_by) : 0 }
|
566
|
+
if order_by == "LaunchTime"
|
567
|
+
instances = instances.reverse
|
568
|
+
end
|
569
|
+
|
570
|
+
instances
|
571
|
+
end
|
572
|
+
|
573
|
+
def arrayize(array_or_object)
|
574
|
+
return [] unless array_or_object
|
575
|
+
array_or_object.is_a?(Array) ? array_or_object : [array_or_object]
|
576
|
+
end
|
577
|
+
|
578
|
+
# TODO: all methods below here can be nuked once CLI tools are removed
|
579
|
+
def run_ibm_tool(command)
|
580
|
+
cmd = "cd #{IBM_TOOLS_HOME} && ./#{command} -u #{@username} -w #{passphrase} -g #{password_file}"
|
581
|
+
logger.debug cmd
|
582
|
+
output = if @debug
|
583
|
+
IBMMockResponseGenerator.respond(command)
|
584
|
+
else
|
585
|
+
`#{cmd}`
|
586
|
+
end
|
587
|
+
|
588
|
+
logger.debug output
|
589
|
+
|
590
|
+
if output =~ /ErrorMessage : (.*)/
|
591
|
+
raise $1 # raise the error message matched above
|
592
|
+
else
|
593
|
+
return output
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
def create_password_file
|
598
|
+
# if no passphrase or password file is supplied, we'll generate it
|
599
|
+
run_ibm_tool("ic-create-password.sh -u #{@username} -p #{@password}")
|
600
|
+
end
|
601
|
+
|
602
|
+
def password_file
|
603
|
+
@password_file ||= "/tmp/smrtcloud-pwfile-#{Time.now.to_i.to_s}"
|
604
|
+
end
|
605
|
+
|
606
|
+
def passphrase
|
607
|
+
@passphrase ||= Time.now.to_i.to_s
|
608
|
+
end
|
609
|
+
# End CLI tool helpers
|
610
|
+
|
611
|
+
|
612
|
+
|
613
|
+
end
|
614
|
+
|
615
|
+
# predefine an instance for convenience
|
616
|
+
@smartcloud = IBMSmartCloud.new(ENV['SMARTCLOUD_USERNAME'], ENV['SMARTCLOUD_PASSWORD']) if ENV['SMARTCLOUD_USERNAME'] && ENV['SMARTCLOUD_PASSWORD']
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#####################################################################################
|
3
|
+
# Copyright (c) 2011, Cohesive Flexible Technologies, Inc.
|
4
|
+
# This copyrighted material is the property of Cohesive Flexible Technologies and
|
5
|
+
# is subject to the license terms of the product it is contained within, whether
|
6
|
+
# in text or compiled form. It is licensed under the terms expressed in the
|
7
|
+
# accompanying README and LICENSE files.
|
8
|
+
#
|
9
|
+
# This program is AS IS and WITHOUT ANY WARRANTY; without even the implied warranty
|
10
|
+
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
|
+
#####################################################################################
|
12
|
+
|
13
|
+
class SmartcloudLogger < Logger
|
14
|
+
def format_message(severity, timestamp, foobar, message)
|
15
|
+
message = message.to_s
|
16
|
+
|
17
|
+
"#{timestamp.strftime("%b %d %H:%M:%S")} #{severity}: #{message}\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|