cft_smartcloud 0.1.4
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 +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
|