deltacloud-client 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/DISCLAIMER +8 -0
- data/{COPYING → LICENSE} +0 -0
- data/NOTICE +8 -0
- data/Rakefile +10 -0
- data/bin/deltacloudc +86 -3
- data/lib/base_object.rb +45 -2
- data/lib/client_bucket_methods.rb +54 -0
- data/lib/deltacloud.rb +93 -18
- data/lib/instance_state.rb +17 -0
- data/lib/plain_formatter.rb +54 -2
- data/specs/data/storage_volumes/vol1.yml +1 -0
- data/specs/data/storage_volumes/vol2.yml +1 -0
- data/specs/data/storage_volumes/vol3.yml +1 -0
- data/specs/fixtures/storage_volumes/vol1.yml +1 -0
- data/specs/fixtures/storage_volumes/vol2.yml +1 -0
- data/specs/fixtures/storage_volumes/vol3.yml +1 -0
- data/specs/instances_spec.rb +12 -0
- metadata +65 -60
data/DISCLAIMER
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Apache Deltacloud is an effort undergoing incubation at The Apache Software
|
2
|
+
Foundation (ASF), sponsored by the Incubator PMC. Incubation is required of
|
3
|
+
all newly accepted projects until a further review indicates that the
|
4
|
+
infrastructure, communications, and decision making process have stabilized
|
5
|
+
in a manner consistent with other successful ASF projects. While
|
6
|
+
incubation status is not necessarily a reflection of the completeness or
|
7
|
+
stability of the code, it does indicate that the project has yet to be
|
8
|
+
fully endorsed by the ASF.
|
data/{COPYING → LICENSE}
RENAMED
File without changes
|
data/NOTICE
ADDED
data/Rakefile
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
# under the License.
|
18
18
|
|
19
19
|
require 'rake/gempackagetask'
|
20
|
+
require 'rake/testtask'
|
20
21
|
|
21
22
|
load 'deltacloud-client.gemspec'
|
22
23
|
|
@@ -25,6 +26,7 @@ task 'documentation' do
|
|
25
26
|
load 'lib/documentation.rb'
|
26
27
|
end
|
27
28
|
|
29
|
+
|
28
30
|
spec = Gem::Specification.load('deltacloud-client.gemspec')
|
29
31
|
Rake::GemPackageTask.new(spec) do |pkg|
|
30
32
|
pkg.need_tar = true
|
@@ -44,6 +46,14 @@ task 'fixtures' do
|
|
44
46
|
FileUtils.cp_r( File.dirname( __FILE__ ) + '/specs/fixtures', File.dirname( __FILE__ ) + '/specs/data' )
|
45
47
|
end
|
46
48
|
|
49
|
+
namespace :test do
|
50
|
+
Rake::TestTask.new(:cmd) do |t|
|
51
|
+
t.libs << "tests"
|
52
|
+
t.test_files = FileList['tests/cmd.rb']
|
53
|
+
t.verbose = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
47
57
|
desc "Clean Fixtures"
|
48
58
|
task 'fixtures:clean' do
|
49
59
|
FileUtils.rm_rf( File.dirname( __FILE__ ) + '/specs/data' )
|
data/bin/deltacloudc
CHANGED
@@ -38,31 +38,66 @@ deltacloudc collection operation [options]
|
|
38
38
|
URL format:
|
39
39
|
API_URL=http://[user]:[password]@[api_url][port][/uri]
|
40
40
|
|
41
|
+
Examples:
|
42
|
+
|
43
|
+
1. To list collections for deltacloud api on port 3333 of server deltacloud.foo
|
44
|
+
|
45
|
+
deltacloudc -l -u http://user:password@deltacloud.foo:3333/api
|
46
|
+
|
47
|
+
2. To list the operations for the 'images' collection:
|
48
|
+
|
49
|
+
deltacloudc images -l -u http://user:password@deltacloud.foo:3333/api
|
50
|
+
|
51
|
+
3. To list all images (i.e. call the 'index' operation of 'images'):
|
52
|
+
|
53
|
+
deltacloudc images index -u http://user:password@deltacloud.foo:3333/api
|
54
|
+
|
55
|
+
4. To get the details of image '5':
|
56
|
+
|
57
|
+
deltacloudc images show -i 5 -u http://user:password@deltacloud.foo:3333/api
|
58
|
+
|
41
59
|
Options:
|
42
60
|
BANNER
|
43
61
|
opts.on( '-i', '--id ID', 'ID for operation') { |id| options[:id] = id }
|
44
62
|
opts.on( '-d', '--image-id ID', 'Image ID') { |id| options[:image_id] = id }
|
63
|
+
opts.on( '-b', '--bucket-id ID', 'Bucket ID') {|id| options[:bucket_id] = id }
|
45
64
|
opts.on( '-a', '--arch ARCH', 'Architecture (x86, x86_64)') { |id| options[:architecture] = id }
|
46
65
|
opts.on( '-p', '--hardware-profile HARDWARE_PROFILE', 'Hardware Profile') { |id| options[:hwp_id] = id }
|
47
66
|
opts.on( '-n', '--name NAME', 'Name (for instance eg.)') { |name| options[:name] = name }
|
48
67
|
opts.on( '-s', '--state STATE', 'Instance state (RUNNING, STOPPED)') { |state| options[:state] = state }
|
49
68
|
opts.on( '-u', '--url URL', 'API url ($API_URL variable)') { |url| options[:api_url] = url }
|
50
69
|
opts.on( '-l', '--list', 'List collections/operations') { |id| options[:list] = true }
|
51
|
-
opts.on( '-h', '--help', 'Display this screen' ) { puts
|
70
|
+
opts.on( '-h', '--help', 'Display this screen' ) { puts @optparse; Kernel.exit! }
|
52
71
|
opts.on( '-v', '--version', 'Display API version' ) { options[:version]=true }
|
53
72
|
opts.on( '-V', '--verbose', 'Print verbose messages' ) { options[:verbose]=true }
|
73
|
+
opts.on( '-f', '--file-path PATH', 'local path for new blob data') {|path| options[:file_path]=path }
|
74
|
+
opts.on( '-m', '--blob-metadata k1=v1, k2=v2', 'Comma seperated k=v pairs for blob metadata (for create operation)') do |meta|
|
75
|
+
blob_meta = {}
|
76
|
+
meta.gsub!(/ /,"")
|
77
|
+
meta.scan(/(\w+)=(\w+)/).map {|k,v| blob_meta[k] = v }
|
78
|
+
options[:blob_metadata] = blob_meta
|
79
|
+
end
|
54
80
|
end
|
55
81
|
|
56
82
|
def invalid_usage(error_msg='')
|
57
|
-
puts "ERROR: #{error_msg}"
|
83
|
+
puts "\n ERROR: #{error_msg} \n\n"
|
84
|
+
puts @optparse
|
58
85
|
exit(1)
|
59
86
|
end
|
60
87
|
|
61
|
-
|
88
|
+
begin
|
89
|
+
@optparse.parse!
|
90
|
+
rescue Exception => e
|
91
|
+
invalid_usage(e.message)
|
92
|
+
end
|
62
93
|
|
63
94
|
# First try to get API_URL from environment
|
64
95
|
options[:api_url] = ENV['API_URL'] if options[:api_url].nil?
|
65
96
|
|
97
|
+
if(options[:api_url].nil?)
|
98
|
+
invalid_usage("You must supply the url to the deltacloud api; either use '-u' flag or set the 'API_URL' environment variable")
|
99
|
+
end
|
100
|
+
|
66
101
|
url = URI.parse(options[:api_url])
|
67
102
|
api_url = "http://#{url.host}#{url.port ? ":#{url.port}" : ''}#{url.path}"
|
68
103
|
|
@@ -75,6 +110,8 @@ collections = client.entry_points.keys
|
|
75
110
|
|
76
111
|
# Exclude collection which don't have methods in client library yet
|
77
112
|
collections.delete(:instance_states)
|
113
|
+
#add blob collection if buckets is present
|
114
|
+
collections << :blob if collections.include?(:buckets)
|
78
115
|
|
79
116
|
# If list parameter passed print out available collection
|
80
117
|
# with API documentation
|
@@ -88,6 +125,14 @@ end
|
|
88
125
|
# If collection parameter is present and user requested list
|
89
126
|
# print all operation defined for collection with API documentation
|
90
127
|
if options[:list] and options[:collection]
|
128
|
+
#deal with blobs again - bypass 'normal' docs procedure
|
129
|
+
if options[:collection] =~ /blob/i
|
130
|
+
puts "create \t\t: Create a new blob in a specified bucket (POST /api/buckets/:bucket)"
|
131
|
+
puts "destroy \t: Delete a specified blob in a specified bucket (DELETE /api/buckets/:bucket/:blob)"
|
132
|
+
puts "show \t\t: Get details of a specified blob in a specified bucket (GET /api/buckets/:bucket/:blob)"
|
133
|
+
puts "data \t\t: Get the contents of a specified blob - the blob data content itself"
|
134
|
+
exit(0)
|
135
|
+
end
|
91
136
|
doc = client.documentation(options[:collection])
|
92
137
|
doc.operations.each do |c|
|
93
138
|
puts sprintf("%-20s: %s", c.operation, c.description)
|
@@ -104,6 +149,8 @@ end
|
|
104
149
|
# Do same if 'index' operation is set
|
105
150
|
if options[:collection] and ( options[:operation].nil? or options[:operation].eql?('index') )
|
106
151
|
invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym)
|
152
|
+
#cannot list blobs - can only show a specific blob
|
153
|
+
invalid_usage("You must specify a particular blob with -i and a particular bucket with -b") if options[:collection] =~ (/blob/i)
|
107
154
|
params = {}
|
108
155
|
params.merge!(:architecture => options[:architecture]) if options[:architecture]
|
109
156
|
params.merge!(:id => options[:id]) if options[:id]
|
@@ -123,6 +170,27 @@ if options[:collection] and options[:operation]
|
|
123
170
|
|
124
171
|
# If collection is set and requested operation is 'show' just 'singularize'
|
125
172
|
# collection name and print item with specified id (-i parameter)
|
173
|
+
#Blobs are a special case so deal with first -
|
174
|
+
if options[:collection] =~ (/blob/i)
|
175
|
+
invalid_usage("Please specify the bucket for this blob using the -b option") unless options[:bucket_id]
|
176
|
+
invalid_usage("Missing blob ID, please specify with -i option") unless options[:id]
|
177
|
+
params = {}
|
178
|
+
params.merge!(:id => options[:id], 'bucket' => options[:bucket_id])
|
179
|
+
params.merge!('metadata'=>options[:blob_metadata]) unless options[:blob_metadata].nil?
|
180
|
+
case options[:operation]
|
181
|
+
when 'show' then puts format(client.send( options[:collection], params))
|
182
|
+
when 'data' then puts (client.blob_data(params))
|
183
|
+
when 'create' then
|
184
|
+
invalid_usage("Specify the location of the new blob data (full local path) using -f option") unless options[:file_path]
|
185
|
+
params.merge!('file_path'=>options[:file_path])
|
186
|
+
blob = client.create_blob(params)
|
187
|
+
puts format(blob)
|
188
|
+
when 'destroy' then client.destroy_blob(params)
|
189
|
+
else invalid_usage("Please specify a valid operation for the blob collection - try -l to see available operations")
|
190
|
+
end
|
191
|
+
exit(0)
|
192
|
+
end
|
193
|
+
|
126
194
|
if options[:operation].eql?('show')
|
127
195
|
invalid_usage("Missing ID, must be provided with --id") unless options[:id]
|
128
196
|
puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id]))
|
@@ -145,6 +213,21 @@ if options[:collection] and options[:operation]
|
|
145
213
|
exit(0)
|
146
214
|
end
|
147
215
|
|
216
|
+
#Create and Destroy a bucket - require the bucket id:
|
217
|
+
if options[:collection].eql?('buckets')
|
218
|
+
if options[:operation].eql?('create')
|
219
|
+
invalid_usage("Please specify an id for the new bucket with -i") unless options[:id]
|
220
|
+
bucket = client.create_bucket('id'=>options[:id])
|
221
|
+
puts format(bucket)
|
222
|
+
elsif options[:operation].eql?('destroy')
|
223
|
+
invalid_usage("Please specify the bucket you wish to destroy with -i") unless options[:id]
|
224
|
+
client.destroy_bucket('id'=>options[:id])
|
225
|
+
else
|
226
|
+
invalid_usage("Please specify a valid operation on buckets - use -l to see valid operations")
|
227
|
+
end
|
228
|
+
exit(0)
|
229
|
+
end
|
230
|
+
|
148
231
|
# All other operations above collections is done there:
|
149
232
|
if options[:collection].eql?('instances')
|
150
233
|
instance = client.instance(options[:id])
|
data/lib/base_object.rb
CHANGED
@@ -100,6 +100,7 @@ module DeltaCloud
|
|
100
100
|
when :text then return m[:value]
|
101
101
|
when :property then return m[:property]
|
102
102
|
when :collection then return m[:values]
|
103
|
+
when :list then return m[:value].join(", ")
|
103
104
|
else raise NoHandlerForMethod
|
104
105
|
end
|
105
106
|
end
|
@@ -108,7 +109,6 @@ module DeltaCloud
|
|
108
109
|
# First of all search throught array for method name
|
109
110
|
m = search_for_method(method_name)
|
110
111
|
if m.nil?
|
111
|
-
warn "[WARNING] Method unsupported by API: '#{self.class}.#{method_name}(#{args.inspect})'"
|
112
112
|
return nil
|
113
113
|
else
|
114
114
|
# Call appropriate handler for method
|
@@ -116,6 +116,23 @@ module DeltaCloud
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
+
# This method adds blobs to the blob_list property
|
120
|
+
# of a bucket
|
121
|
+
def add_blob!(blob_name)
|
122
|
+
if @blob_list.nil?
|
123
|
+
@blob_list = [blob_name]
|
124
|
+
@objects << {
|
125
|
+
:type => :list,
|
126
|
+
:method_name => "blob_list",
|
127
|
+
:value => @blob_list
|
128
|
+
}
|
129
|
+
else
|
130
|
+
@blob_list << blob_name
|
131
|
+
current = search_for_method('blob_list')
|
132
|
+
current[:value] = @blob_list
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
119
136
|
private
|
120
137
|
|
121
138
|
def search_for_method(name)
|
@@ -225,12 +242,21 @@ module DeltaCloud
|
|
225
242
|
end
|
226
243
|
|
227
244
|
def action_trigger(action)
|
228
|
-
# Refresh object state after action
|
245
|
+
# Refresh object state after action unless the object was destroyed
|
246
|
+
return if action.to_s == "destroy"
|
229
247
|
@new_state_object = @client.send(self.base_name, self.id)
|
230
248
|
@state = @new_state_object.state
|
231
249
|
self.update_actions!
|
232
250
|
end
|
233
251
|
|
252
|
+
def add_run_action!(id, link)
|
253
|
+
@objects << {
|
254
|
+
:method_name => 'run',
|
255
|
+
:type => :run,
|
256
|
+
:url => link,
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
234
260
|
alias :action_method_handler :method_handler
|
235
261
|
|
236
262
|
def method_handler(m, args=[])
|
@@ -239,6 +265,7 @@ module DeltaCloud
|
|
239
265
|
rescue NoHandlerForMethod
|
240
266
|
case m[:type]
|
241
267
|
when :state then evaluate_state(m[:state], @state)
|
268
|
+
when :run then run_command(m[:url][:href], args)
|
242
269
|
else raise NoHandlerForMethod
|
243
270
|
end
|
244
271
|
end
|
@@ -246,6 +273,22 @@ module DeltaCloud
|
|
246
273
|
|
247
274
|
# private
|
248
275
|
|
276
|
+
def run_command(instance_url, args)
|
277
|
+
credentials = args[1]
|
278
|
+
params = {
|
279
|
+
:cmd => args[0],
|
280
|
+
:private_key => credentials[:pem] ? File.read(credentials[:pem]) : nil,
|
281
|
+
}
|
282
|
+
params.merge!({
|
283
|
+
:username => credentials[:username],
|
284
|
+
:password => credentials[:password]
|
285
|
+
}) if credentials[:username] and credentials[:password]
|
286
|
+
@client.request(:post, instance_url, {}, params) do |response|
|
287
|
+
output = Nokogiri::XML(response)
|
288
|
+
(output/'/instance/output').first.text
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
249
292
|
def evaluate_state(method_state, current_state)
|
250
293
|
method_state.eql?(current_state)
|
251
294
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module ClientBucketMethods
|
2
|
+
|
3
|
+
def create_bucket(params)
|
4
|
+
obj = nil
|
5
|
+
request(:post, "#{api_uri.to_s}/buckets", {:name => params['id'] }) do |response|
|
6
|
+
handle_backend_error(response) if response.code!=201
|
7
|
+
obj = base_object(:bucket, response)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def destroy_bucket(params)
|
12
|
+
#actually response here is 204 - no content - so nothing returned to client?
|
13
|
+
request(:delete, "#{api_uri.to_s}/buckets/#{params['id']}") do |response|
|
14
|
+
handle_backend_error(response) if response.code!=204
|
15
|
+
response
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_blob(params)
|
20
|
+
blob = nil
|
21
|
+
resource = RestClient::Resource.new("#{api_uri.to_s}/buckets/#{params['bucket']}", :open_timeout => 10, :timeout => 45)
|
22
|
+
headers = default_headers.merge(extended_headers)
|
23
|
+
unless params['metadata'].nil?
|
24
|
+
metadata_headers = {}
|
25
|
+
params['metadata'].each do |k,v|
|
26
|
+
metadata_headers["X-Deltacloud-Blobmeta-#{k}"] = v
|
27
|
+
end
|
28
|
+
headers = headers.merge(metadata_headers)
|
29
|
+
end
|
30
|
+
resource.send(:post, {:blob_data => File.new(params['file_path'], 'rb'), :blob_id => params[:id]}, headers) do |response, request, block|
|
31
|
+
handle_backend_error(response) if response.code.eql?(500)
|
32
|
+
blob = base_object(:blob, response)
|
33
|
+
yield blob if block_given?
|
34
|
+
end
|
35
|
+
return blob
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy_blob(params)
|
39
|
+
request(:delete, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}") do |response|
|
40
|
+
handle_backend_error(response) if response.code!=204
|
41
|
+
response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#RestClient doesn't do streaming 'get' yet - we already opened a pull request on this see
|
46
|
+
#https://github.com/archiloque/rest-client/issues/closed#issue/62 - apparently its going to
|
47
|
+
#be in the next version - unknown when. For now get full response. FIXME
|
48
|
+
def blob_data(params)
|
49
|
+
request(:get, "#{api_uri.to_s}/buckets/#{params['bucket']}/#{params[:id]}/content") do |response|
|
50
|
+
response
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/deltacloud.rb
CHANGED
@@ -20,11 +20,11 @@ require 'nokogiri'
|
|
20
20
|
require 'rest_client'
|
21
21
|
require 'base64'
|
22
22
|
require 'logger'
|
23
|
-
|
24
23
|
require 'hwp_properties'
|
25
24
|
require 'instance_state'
|
26
25
|
require 'documentation'
|
27
26
|
require 'base_object'
|
27
|
+
require 'client_bucket_methods'
|
28
28
|
|
29
29
|
module DeltaCloud
|
30
30
|
|
@@ -32,11 +32,15 @@ module DeltaCloud
|
|
32
32
|
#
|
33
33
|
# @param [String, user_name] API user name
|
34
34
|
# @param [String, password] API password
|
35
|
-
# @param [String,
|
35
|
+
# @param [String, url] API URL (eg. http://localhost:3001/api)
|
36
36
|
# @return [DeltaCloud::API]
|
37
|
-
def self.new(user_name, password, api_url, opts={}, &block)
|
38
|
-
|
39
|
-
|
37
|
+
#def self.new(user_name, password, api_url, opts={}, &block)
|
38
|
+
# opts ||= {}
|
39
|
+
# API.new(user_name, password, api_url, opts, &block)
|
40
|
+
#end
|
41
|
+
|
42
|
+
def self.new(user_name, password, api_url, &block)
|
43
|
+
API.new(user_name, password, api_url, &block)
|
40
44
|
end
|
41
45
|
|
42
46
|
# Check given credentials if their are valid against
|
@@ -46,8 +50,8 @@ module DeltaCloud
|
|
46
50
|
# @param [String, password] API password
|
47
51
|
# @param [String, user_name] API URL (eg. http://localhost:3001/api)
|
48
52
|
# @return [true|false]
|
49
|
-
def self.valid_credentials?(user_name, password, api_url)
|
50
|
-
api=API.new(user_name, password, api_url)
|
53
|
+
def self.valid_credentials?(user_name, password, api_url, opts={})
|
54
|
+
api=API.new(user_name, password, api_url, opts)
|
51
55
|
result = false
|
52
56
|
api.request(:get, '', :force_auth => '1') do |response|
|
53
57
|
result = true if response.code.eql?(200)
|
@@ -74,9 +78,44 @@ module DeltaCloud
|
|
74
78
|
@features, @entry_points = {}, {}
|
75
79
|
@verbose = opts[:verbose] || false
|
76
80
|
discover_entry_points
|
81
|
+
if entry_points.include?(:buckets)
|
82
|
+
extend(ClientBucketMethods)
|
83
|
+
end
|
77
84
|
yield self if block_given?
|
78
85
|
end
|
79
86
|
|
87
|
+
# This method can be used to switch back-end cloud
|
88
|
+
# for API instance using HTTP headers.
|
89
|
+
# Options must include:
|
90
|
+
# {
|
91
|
+
# :driver => 'rhevm|ec2|gogrid|...',
|
92
|
+
# :username => 'API key for backend',
|
93
|
+
# :password => 'API secret key for backend',
|
94
|
+
# }
|
95
|
+
# Optionally you can pass also :provider option to change
|
96
|
+
# provider entry-point
|
97
|
+
#
|
98
|
+
# Example usage:
|
99
|
+
# client = Deltacloud::new('url', 'username', 'password')
|
100
|
+
# ...
|
101
|
+
# client.with_config(:driver => 'ec2', :username => '', :password => '') do |ec2|
|
102
|
+
# ec2.realms
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# Note: After this block finish client instance will be set back to default
|
106
|
+
# state
|
107
|
+
#
|
108
|
+
# @param [Hash, opts] New provider configuration
|
109
|
+
def with_config(opts, &block)
|
110
|
+
api_instance = self.dup
|
111
|
+
api_instance.use_driver(opts[:driver],
|
112
|
+
:username => opts[:username],
|
113
|
+
:password => opts[:password],
|
114
|
+
:provider => opts[:provider])
|
115
|
+
yield api_instance if block_given?
|
116
|
+
api_instance
|
117
|
+
end
|
118
|
+
|
80
119
|
def connect(&block)
|
81
120
|
yield self
|
82
121
|
end
|
@@ -93,7 +132,6 @@ module DeltaCloud
|
|
93
132
|
# Define methods based on 'rel' attribute in entry point
|
94
133
|
# Two methods are declared: 'images' and 'image'
|
95
134
|
def declare_entry_points_methods(entry_points)
|
96
|
-
|
97
135
|
API.instance_eval do
|
98
136
|
entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
|
99
137
|
|
@@ -114,8 +152,20 @@ module DeltaCloud
|
|
114
152
|
self.send(model.to_s.singularize.to_sym, $1)
|
115
153
|
end
|
116
154
|
|
155
|
+
end
|
156
|
+
|
157
|
+
#define methods for blobs:
|
158
|
+
if(entry_points.include?(:buckets))
|
159
|
+
define_method :"blob" do |*args|
|
160
|
+
bucket = args[0]["bucket"]
|
161
|
+
blob = args[0][:id]
|
162
|
+
request(:get, "#{entry_points[:buckets]}/#{bucket}/#{blob}") do |response|
|
163
|
+
base_object("blob", response)
|
164
|
+
end
|
117
165
|
end
|
118
166
|
end
|
167
|
+
|
168
|
+
end
|
119
169
|
end
|
120
170
|
|
121
171
|
def base_object_collection(model, response)
|
@@ -144,13 +194,11 @@ module DeltaCloud
|
|
144
194
|
params.merge!({ :initial_state => (item/'state').text.sanitize }) if (item/'state').length > 0
|
145
195
|
|
146
196
|
obj = base_object.new(params)
|
147
|
-
|
148
197
|
# Traverse across XML document and deal with elements
|
149
198
|
item.xpath('./*').each do |attribute|
|
150
|
-
|
151
199
|
# Do a link for elements which are links to other REST models
|
152
200
|
if self.entry_points.keys.include?(:"#{attribute.name}s")
|
153
|
-
obj.add_link!(attribute.name, attribute['id']) && next
|
201
|
+
obj.add_link!(attribute.name, attribute['id']) && next unless (attribute.name == 'bucket' && item.name == 'blob')
|
154
202
|
end
|
155
203
|
|
156
204
|
# Do a HWP property for hardware profile properties
|
@@ -165,25 +213,40 @@ module DeltaCloud
|
|
165
213
|
# If there are actions, add they to ActionObject/StateFullObject
|
166
214
|
if attribute.name == 'actions'
|
167
215
|
(attribute/'link').each do |link|
|
216
|
+
(obj.add_run_action!(item['id'], link) && next) if link[:rel] == 'run'
|
168
217
|
obj.add_action_link!(item['id'], link)
|
169
218
|
end && next
|
170
219
|
end
|
171
220
|
|
172
221
|
if attribute.name == 'mount'
|
173
|
-
obj.add_link!("instance", (attribute/"./instance/@id").first)
|
222
|
+
obj.add_link!("instance", (attribute/"./instance/@id").first.value)
|
174
223
|
obj.add_text!("device", (attribute/"./device/@name").first.value)
|
175
224
|
next
|
176
225
|
end
|
177
226
|
|
227
|
+
#deal with blob metadata
|
228
|
+
if(attribute.name == 'user_metadata')
|
229
|
+
meta = {}
|
230
|
+
attribute.children.select {|x| x.name=="entry" }.each do |element|
|
231
|
+
value = element.content.gsub!(/(\n) +/,'')
|
232
|
+
meta[element['key']] = value
|
233
|
+
end
|
234
|
+
obj.add_collection!(attribute.name, meta.inspect) && next
|
235
|
+
end
|
236
|
+
|
178
237
|
# Deal with collections like public-addresses, private-addresses
|
179
238
|
if (attribute/'./*').length > 0
|
180
239
|
obj.add_collection!(attribute.name, (attribute/'*').collect { |value| value.text }) && next
|
181
240
|
end
|
182
241
|
|
242
|
+
#deal with blobs for buckets
|
243
|
+
if(attribute.name == 'blob')
|
244
|
+
obj.add_blob!(attribute.attributes['id'].value) && next
|
245
|
+
end
|
246
|
+
|
183
247
|
# Anything else is treaten as text object
|
184
248
|
obj.add_text!(attribute.name, attribute.text.convert)
|
185
249
|
end
|
186
|
-
|
187
250
|
return obj
|
188
251
|
end
|
189
252
|
|
@@ -237,6 +300,9 @@ module DeltaCloud
|
|
237
300
|
|
238
301
|
request(:post, entry_points[:"#{$1}s"], {}, params) do |response|
|
239
302
|
obj = base_object(:"#{$1}", response)
|
303
|
+
# All create calls must respond 201 HTTP code
|
304
|
+
# to indicate that resource was created.
|
305
|
+
handle_backend_error(response) if response.code!=201
|
240
306
|
yield obj if block_given?
|
241
307
|
end
|
242
308
|
return obj
|
@@ -245,7 +311,7 @@ module DeltaCloud
|
|
245
311
|
end
|
246
312
|
|
247
313
|
def use_driver(driver, opts={})
|
248
|
-
if
|
314
|
+
if driver
|
249
315
|
@api_driver = driver
|
250
316
|
@driver_name = driver
|
251
317
|
discover_entry_points
|
@@ -256,6 +322,11 @@ module DeltaCloud
|
|
256
322
|
return self
|
257
323
|
end
|
258
324
|
|
325
|
+
def use_config!(opts={})
|
326
|
+
@api_uri = URI.parse(opts[:url]) if opts[:url]
|
327
|
+
use_driver(opts[:driver], opts)
|
328
|
+
end
|
329
|
+
|
259
330
|
def extended_headers
|
260
331
|
headers = {}
|
261
332
|
headers["X-Deltacloud-Driver"] = @api_driver.to_s if @api_driver
|
@@ -270,14 +341,17 @@ module DeltaCloud
|
|
270
341
|
:method => (args[0] || 'get').to_sym,
|
271
342
|
:path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
|
272
343
|
:query_args => args[2] || {},
|
273
|
-
:form_data => args[3] || {}
|
344
|
+
:form_data => args[3] || {},
|
345
|
+
:timeout => args[4] || 45,
|
346
|
+
:open_timeout => args[5] || 10
|
274
347
|
}
|
275
348
|
if conf[:query_args] != {}
|
276
349
|
conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
|
277
350
|
end
|
278
351
|
|
279
352
|
if conf[:method].eql?(:post)
|
280
|
-
RestClient.
|
353
|
+
resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
|
354
|
+
resource.send(:post, conf[:form_data], default_headers.merge(extended_headers)) do |response, request, block|
|
281
355
|
handle_backend_error(response) if response.code.eql?(500)
|
282
356
|
if response.respond_to?('body')
|
283
357
|
yield response.body if block_given?
|
@@ -286,7 +360,8 @@ module DeltaCloud
|
|
286
360
|
end
|
287
361
|
end
|
288
362
|
else
|
289
|
-
RestClient.
|
363
|
+
resource = RestClient::Resource.new(conf[:path], :open_timeout => conf[:open_timeout], :timeout => conf[:timeout])
|
364
|
+
resource.send(conf[:method], default_headers.merge(extended_headers)) do |response, request, block|
|
290
365
|
handle_backend_error(response) if response.code.eql?(500)
|
291
366
|
if conf[:method].eql?(:get) and [301, 302, 307].include? response.code
|
292
367
|
response.follow_redirection(request) do |response, request, block|
|
@@ -309,7 +384,7 @@ module DeltaCloud
|
|
309
384
|
|
310
385
|
# Re-raise backend errors as on exception in client with message from
|
311
386
|
# backend
|
312
|
-
class BackendError <
|
387
|
+
class BackendError < StandardError
|
313
388
|
def initialize(opts={})
|
314
389
|
@message = opts[:message]
|
315
390
|
end
|
data/lib/instance_state.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# Copyright (C) 2009, 2010 Red Hat, Inc.
|
2
|
+
#
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
4
|
+
# contributor license agreements. See the NOTICE file distributed with
|
5
|
+
# this work for additional information regarding copyright ownership. The
|
6
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance with the
|
8
|
+
# License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
14
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
15
|
+
# License for the specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
1
18
|
module DeltaCloud
|
2
19
|
module InstanceState
|
3
20
|
|
data/lib/plain_formatter.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# Copyright (C) 2009, 2010 Red Hat, Inc.
|
2
|
+
#
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
4
|
+
# contributor license agreements. See the NOTICE file distributed with
|
5
|
+
# this work for additional information regarding copyright ownership. The
|
6
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance with the
|
8
|
+
# License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
14
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
15
|
+
# License for the specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
1
18
|
module DeltaCloud
|
2
19
|
module PlainFormatter
|
3
20
|
module FormatObject
|
@@ -33,8 +50,11 @@ module DeltaCloud
|
|
33
50
|
|
34
51
|
class HardwareProfile < Base
|
35
52
|
def format
|
53
|
+
architecture = @obj.architecture ? @obj.architecture.value[0,6] : 'opaque'
|
54
|
+
memory = @obj.memory ? @obj.memory.value.to_s[0,10] : 'opaque'
|
55
|
+
storage = @obj.storage ? @obj.storage.value.to_s[0,10] : 'opaque'
|
36
56
|
sprintf("%-15s | %-6s | %10s | %10s ", @obj.id[0, 15],
|
37
|
-
|
57
|
+
architecture , memory, storage)
|
38
58
|
end
|
39
59
|
end
|
40
60
|
|
@@ -74,10 +94,42 @@ module DeltaCloud
|
|
74
94
|
end
|
75
95
|
end
|
76
96
|
|
97
|
+
class Bucket < Base
|
98
|
+
def format
|
99
|
+
sprintf("%-s | %-s | %-s | %-s",
|
100
|
+
@obj.id,
|
101
|
+
@obj.name,
|
102
|
+
@obj.size ? @obj.size : "0",
|
103
|
+
@obj.blob_list ? @obj.blob_list : ""
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class Blob < Base
|
109
|
+
def format
|
110
|
+
sprintf("%-s | %-s | %-d | %-s | %-s | %-s " ,
|
111
|
+
@obj.id,
|
112
|
+
@obj.bucket,
|
113
|
+
@obj.content_length,
|
114
|
+
@obj.content_type,
|
115
|
+
@obj.last_modified,
|
116
|
+
@obj.user_metadata
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Driver < Base
|
122
|
+
def format
|
123
|
+
sprintf("%-15s | %-15s | %-s",
|
124
|
+
@obj.id,
|
125
|
+
@obj.name,
|
126
|
+
@obj.url)
|
127
|
+
end
|
128
|
+
end
|
77
129
|
end
|
78
130
|
|
79
131
|
def format(obj)
|
80
|
-
object_name = obj.class.name.classify.gsub(/^DeltaCloud::API::/, '')
|
132
|
+
object_name = obj.class.name.classify.gsub(/^DeltaCloud::API::(\w+)::/, '')
|
81
133
|
format_class = DeltaCloud::PlainFormatter::FormatObject.const_get(object_name)
|
82
134
|
format_class.new(obj).format
|
83
135
|
end
|
data/specs/instances_spec.rb
CHANGED
@@ -190,5 +190,17 @@ describe "instances" do
|
|
190
190
|
instance.state.should eql( "RUNNING" )
|
191
191
|
end
|
192
192
|
end
|
193
|
+
|
194
|
+
it "should not throw exception when destroying an instance" do
|
195
|
+
DeltaCloud.new( API_NAME, API_PASSWORD, API_URL ) do |client|
|
196
|
+
instance = client.create_instance( 'img1',
|
197
|
+
:name=>'TestDestroyInstance',
|
198
|
+
:hardware_profile => 'm1-xlarge' )
|
199
|
+
instance.stop!
|
200
|
+
lambda {
|
201
|
+
instance.destroy!
|
202
|
+
}.should_not raise_error
|
203
|
+
end
|
204
|
+
end
|
193
205
|
end
|
194
206
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deltacloud-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Red Hat, Inc.
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-05-05 00:00:00 +02:00
|
19
19
|
default_executable: deltacloudc
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -73,53 +73,58 @@ executables:
|
|
73
73
|
extensions: []
|
74
74
|
|
75
75
|
extra_rdoc_files:
|
76
|
-
-
|
76
|
+
- LICENSE
|
77
|
+
- NOTICE
|
78
|
+
- DISCLAIMER
|
77
79
|
files:
|
78
80
|
- Rakefile
|
79
|
-
- lib/
|
81
|
+
- lib/base_object.rb
|
82
|
+
- lib/client_bucket_methods.rb
|
83
|
+
- lib/deltacloud.rb
|
80
84
|
- lib/documentation.rb
|
81
85
|
- lib/hwp_properties.rb
|
82
86
|
- lib/instance_state.rb
|
87
|
+
- lib/plain_formatter.rb
|
83
88
|
- lib/string.rb
|
84
|
-
- lib/base_object.rb
|
85
|
-
- lib/deltacloud.rb
|
86
89
|
- init.rb
|
87
90
|
- bin/deltacloudc
|
88
|
-
-
|
89
|
-
-
|
90
|
-
-
|
91
|
-
- specs/
|
92
|
-
- specs/
|
93
|
-
- specs/
|
94
|
-
- specs/
|
91
|
+
- LICENSE
|
92
|
+
- NOTICE
|
93
|
+
- DISCLAIMER
|
94
|
+
- specs/data/images/img1.yml
|
95
|
+
- specs/data/images/img2.yml
|
96
|
+
- specs/data/images/img3.yml
|
97
|
+
- specs/data/instances/inst0.yml
|
98
|
+
- specs/data/instances/inst1.yml
|
99
|
+
- specs/data/instances/inst2.yml
|
100
|
+
- specs/data/storage_snapshots/snap1.yml
|
101
|
+
- specs/data/storage_snapshots/snap2.yml
|
102
|
+
- specs/data/storage_snapshots/snap3.yml
|
103
|
+
- specs/data/storage_volumes/vol1.yml
|
104
|
+
- specs/data/storage_volumes/vol2.yml
|
105
|
+
- specs/data/storage_volumes/vol3.yml
|
106
|
+
- specs/fixtures/images/img1.yml
|
95
107
|
- specs/fixtures/images/img2.yml
|
96
108
|
- specs/fixtures/images/img3.yml
|
97
|
-
- specs/fixtures/
|
109
|
+
- specs/fixtures/instances/inst0.yml
|
98
110
|
- specs/fixtures/instances/inst1.yml
|
99
111
|
- specs/fixtures/instances/inst2.yml
|
100
|
-
- specs/fixtures/
|
101
|
-
- specs/
|
112
|
+
- specs/fixtures/storage_snapshots/snap1.yml
|
113
|
+
- specs/fixtures/storage_snapshots/snap2.yml
|
114
|
+
- specs/fixtures/storage_snapshots/snap3.yml
|
115
|
+
- specs/fixtures/storage_volumes/vol1.yml
|
116
|
+
- specs/fixtures/storage_volumes/vol2.yml
|
117
|
+
- specs/fixtures/storage_volumes/vol3.yml
|
118
|
+
- specs/hardware_profiles_spec.rb
|
102
119
|
- specs/images_spec.rb
|
103
120
|
- specs/initialization_spec.rb
|
104
121
|
- specs/instance_states_spec.rb
|
122
|
+
- specs/instances_spec.rb
|
105
123
|
- specs/realms_spec.rb
|
124
|
+
- specs/shared/resources.rb
|
125
|
+
- specs/spec_helper.rb
|
106
126
|
- specs/storage_snapshot_spec.rb
|
107
|
-
- specs/hardware_profiles_spec.rb
|
108
127
|
- specs/storage_volume_spec.rb
|
109
|
-
- specs/spec_helper.rb
|
110
|
-
- specs/instances_spec.rb
|
111
|
-
- specs/data/storage_volumes/vol1.yml
|
112
|
-
- specs/data/storage_volumes/vol3.yml
|
113
|
-
- specs/data/storage_volumes/vol2.yml
|
114
|
-
- specs/data/storage_snapshots/snap3.yml
|
115
|
-
- specs/data/storage_snapshots/snap1.yml
|
116
|
-
- specs/data/storage_snapshots/snap2.yml
|
117
|
-
- specs/data/images/img2.yml
|
118
|
-
- specs/data/images/img3.yml
|
119
|
-
- specs/data/images/img1.yml
|
120
|
-
- specs/data/instances/inst1.yml
|
121
|
-
- specs/data/instances/inst2.yml
|
122
|
-
- specs/data/instances/inst0.yml
|
123
128
|
has_rdoc: true
|
124
129
|
homepage: http://www.deltacloud.org
|
125
130
|
licenses: []
|
@@ -150,42 +155,42 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
155
|
requirements: []
|
151
156
|
|
152
157
|
rubyforge_project:
|
153
|
-
rubygems_version: 1.
|
158
|
+
rubygems_version: 1.6.1
|
154
159
|
signing_key:
|
155
160
|
specification_version: 3
|
156
161
|
summary: Deltacloud REST Client
|
157
162
|
test_files:
|
158
|
-
- specs/
|
159
|
-
- specs/
|
160
|
-
- specs/
|
161
|
-
- specs/
|
162
|
-
- specs/
|
163
|
-
- specs/
|
163
|
+
- specs/data/images/img1.yml
|
164
|
+
- specs/data/images/img2.yml
|
165
|
+
- specs/data/images/img3.yml
|
166
|
+
- specs/data/instances/inst0.yml
|
167
|
+
- specs/data/instances/inst1.yml
|
168
|
+
- specs/data/instances/inst2.yml
|
169
|
+
- specs/data/storage_snapshots/snap1.yml
|
170
|
+
- specs/data/storage_snapshots/snap2.yml
|
171
|
+
- specs/data/storage_snapshots/snap3.yml
|
172
|
+
- specs/data/storage_volumes/vol1.yml
|
173
|
+
- specs/data/storage_volumes/vol2.yml
|
174
|
+
- specs/data/storage_volumes/vol3.yml
|
175
|
+
- specs/fixtures/images/img1.yml
|
164
176
|
- specs/fixtures/images/img2.yml
|
165
177
|
- specs/fixtures/images/img3.yml
|
166
|
-
- specs/fixtures/
|
178
|
+
- specs/fixtures/instances/inst0.yml
|
167
179
|
- specs/fixtures/instances/inst1.yml
|
168
180
|
- specs/fixtures/instances/inst2.yml
|
169
|
-
- specs/fixtures/
|
170
|
-
- specs/
|
181
|
+
- specs/fixtures/storage_snapshots/snap1.yml
|
182
|
+
- specs/fixtures/storage_snapshots/snap2.yml
|
183
|
+
- specs/fixtures/storage_snapshots/snap3.yml
|
184
|
+
- specs/fixtures/storage_volumes/vol1.yml
|
185
|
+
- specs/fixtures/storage_volumes/vol2.yml
|
186
|
+
- specs/fixtures/storage_volumes/vol3.yml
|
187
|
+
- specs/hardware_profiles_spec.rb
|
171
188
|
- specs/images_spec.rb
|
172
189
|
- specs/initialization_spec.rb
|
173
190
|
- specs/instance_states_spec.rb
|
191
|
+
- specs/instances_spec.rb
|
174
192
|
- specs/realms_spec.rb
|
193
|
+
- specs/shared/resources.rb
|
194
|
+
- specs/spec_helper.rb
|
175
195
|
- specs/storage_snapshot_spec.rb
|
176
|
-
- specs/hardware_profiles_spec.rb
|
177
196
|
- specs/storage_volume_spec.rb
|
178
|
-
- specs/spec_helper.rb
|
179
|
-
- specs/instances_spec.rb
|
180
|
-
- specs/data/storage_volumes/vol1.yml
|
181
|
-
- specs/data/storage_volumes/vol3.yml
|
182
|
-
- specs/data/storage_volumes/vol2.yml
|
183
|
-
- specs/data/storage_snapshots/snap3.yml
|
184
|
-
- specs/data/storage_snapshots/snap1.yml
|
185
|
-
- specs/data/storage_snapshots/snap2.yml
|
186
|
-
- specs/data/images/img2.yml
|
187
|
-
- specs/data/images/img3.yml
|
188
|
-
- specs/data/images/img1.yml
|
189
|
-
- specs/data/instances/inst1.yml
|
190
|
-
- specs/data/instances/inst2.yml
|
191
|
-
- specs/data/instances/inst0.yml
|