deltacloud-core 0.0.6 → 0.0.7

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.
@@ -0,0 +1,253 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/accept_media_types'
3
+
4
+ # Accept header parsing was looked at but deemed
5
+ # too much of an irregularity to deal with. Problems with the header
6
+ # differences from IE, Firefox, Safari, and every other UA causes
7
+ # problems with the expected output. The general expected behavior
8
+ # would be serve html when no extension provided, but most UAs say
9
+ # they will accept application/xml with out a quality indicator, meaning
10
+ # you'd get the xml block served insead. Just plain retarded, use the
11
+ # extension and you'll never be suprised.
12
+
13
+ module Sinatra
14
+ module RespondTo
15
+ class UnhandledFormat < Sinatra::NotFound; end
16
+ class MissingTemplate < Sinatra::NotFound
17
+ def code; 500 end
18
+ end
19
+
20
+ TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
21
+ :csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
22
+ :log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
23
+ :s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
24
+ :yml, :ics, :png]
25
+
26
+ def self.registered(app)
27
+ app.helpers RespondTo::Helpers
28
+
29
+ app.set :default_charset, 'utf-8'
30
+ app.set :default_content, :html
31
+ app.set :assume_xhr_is_js, true
32
+
33
+ #deltacloud: removed the code that tried to 'guess' content based on extension
34
+ #as this broke blobstore api (stripping blob names). Use ?format if present, otherwise
35
+ #use http accept, otherwise set to default
36
+ app.before do
37
+ if (params[:format])
38
+ @mime_types = [Helpers::mime_type(params[:format])]
39
+ format params[:format]
40
+ elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
41
+ ext = options.default_content
42
+ end
43
+ end
44
+
45
+ app.configure :development do |dev|
46
+ dev.error UnhandledFormat do
47
+ content_type :html, :charset => 'utf-8'
48
+
49
+ (<<-HTML).gsub(/^ {10}/, '')
50
+ <!DOCTYPE html>
51
+ <html>
52
+ <head>
53
+ <style type="text/css">
54
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
55
+ color:#888;margin:20px}
56
+ #c {margin:0 auto;width:500px;text-align:left}
57
+ </style>
58
+ </head>
59
+ <body>
60
+ <h2>Sinatra doesn't know this ditty.</h2>
61
+ <img src='/__sinatra__/404.png'>
62
+ <div id="c">
63
+ Try this:
64
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n end\nend</pre>
65
+ </div>
66
+ </body>
67
+ </html>
68
+ HTML
69
+ end
70
+
71
+ dev.error MissingTemplate do
72
+ content_type :html, :charset => 'utf-8'
73
+ response.status = request.env['sinatra.error'].code
74
+
75
+ engine = request.env['sinatra.error'].message.split('.').last
76
+ engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
77
+
78
+ path = File.basename(request.path_info)
79
+ path = "root" if path.nil? || path.empty?
80
+
81
+ format = engine == 'builder' ? 'xml' : 'html'
82
+
83
+ layout = case engine
84
+ when 'haml' then "!!!\n%html\n %body= yield"
85
+ when 'erb' then "<html>\n <body>\n <%= yield %>\n </body>\n</html>"
86
+ when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml << yield" : "builder do |xml|\n xml << yield\nend"
87
+ end
88
+
89
+ layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
90
+
91
+ (<<-HTML).gsub(/^ {10}/, '')
92
+ <!DOCTYPE html>
93
+ <html>
94
+ <head>
95
+ <style type="text/css">
96
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
97
+ color:#888;margin:20px}
98
+ #c {margin:0 auto;width:500px;text-align:left;}
99
+ small {float:right;clear:both;}
100
+ pre {clear:both;}
101
+ </style>
102
+ </head>
103
+ <body>
104
+ <h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
105
+ <img src='/__sinatra__/500.png'>
106
+ <div id="c">
107
+ Try this:<br />
108
+ #{layout}
109
+ <small>#{path}.#{format}.#{engine}</small>
110
+ <pre>Hello World!</pre>
111
+ <small>application.rb</small>
112
+ <pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n end\nend</pre>
113
+ </div>
114
+ </body>
115
+ </html>
116
+ HTML
117
+ end
118
+
119
+ end
120
+
121
+ app.class_eval do
122
+ private
123
+ def accept_list
124
+ @mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
125
+ end
126
+
127
+ # Changes in 1.0 Sinatra reuse render for layout so we store
128
+ # the original value to tell us if this is an automatic attempt
129
+ # to do a layout call. If it is, it might fail with Errno::ENOENT
130
+ # and we want to pass that back to sinatra since it isn't a MissingTemplate
131
+ # error
132
+ def render_with_format(*args, &block)
133
+ assumed_layout = args[1] == :layout
134
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
135
+ render_without_format *args, &block
136
+ rescue Errno::ENOENT => e
137
+ raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
138
+ raise e
139
+ end
140
+ alias_method :render_without_format, :render
141
+ alias_method :render, :render_with_format
142
+
143
+ if ::Sinatra::VERSION =~ /^0\.9/
144
+ def lookup_layout_with_format(*args)
145
+ args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
146
+ lookup_layout_without_format *args
147
+ end
148
+ alias_method :lookup_layout_without_format, :lookup_layout
149
+ alias_method :lookup_layout, :lookup_layout_with_format
150
+ end
151
+ end
152
+ end
153
+
154
+ module Helpers
155
+ # Patch the content_type function to remember the set type
156
+ # This helps cut down on time in the format helper so it
157
+ # doesn't have to do a reverse lookup on the header
158
+ def self.included(klass)
159
+ klass.class_eval do
160
+ def content_type_with_save(*args)
161
+ content_type_without_save *args
162
+ @_format = args.first.to_sym
163
+ response['Content-Type']
164
+ end
165
+ alias_method :content_type_without_save, :content_type
166
+ alias_method :content_type, :content_type_with_save
167
+ end if ::Sinatra::VERSION =~ /^1.0/
168
+ end
169
+
170
+ def self.mime_type(sym)
171
+ ::Sinatra::Base.respond_to?(:mime_type) && ::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
172
+ end
173
+
174
+ def format(val=nil)
175
+ unless val.nil?
176
+ mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
177
+ fail "Unknown media type #{val}\nTry registering the extension with a mime type" if mime_type.nil?
178
+
179
+ @_format = val.to_sym
180
+ response['Content-Type'].sub!(/^[^;]+/, mime_type)
181
+ charset options.default_charset if Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
182
+ end
183
+
184
+ @_format
185
+ end
186
+
187
+ # This is mostly just a helper so request.path_info isn't changed when
188
+ # serving files from the public directory
189
+ def static_file?(path)
190
+ public_dir = File.expand_path(options.public)
191
+ path = File.expand_path(File.join(public_dir, unescape(path)))
192
+
193
+ path[0, public_dir.length] == public_dir && File.file?(path)
194
+ end
195
+
196
+ def charset(val=nil)
197
+ fail "Content-Type must be set in order to specify a charset" if response['Content-Type'].nil?
198
+
199
+ if response['Content-Type'] =~ /charset=[^;]+/
200
+ response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') || "charset=#{val}")
201
+ else
202
+ response['Content-Type'] += ";charset=#{val}"
203
+ end unless val.nil?
204
+
205
+ response['Content-Type'][/charset=([^;]+)/, 1]
206
+ end
207
+
208
+ def respond_to(&block)
209
+ wants = Format.new
210
+ yield wants
211
+ fmt, type, handler = match_accept_type(accept_list, wants)
212
+ raise UnhandledFormat if fmt.nil?
213
+ format fmt
214
+ handler.nil? ? nil : handler.call
215
+ end
216
+
217
+ def match_accept_type(mime_types, format)
218
+ selected = []
219
+
220
+ accepted_types = mime_types.map {|type| Regexp.escape(type).gsub(/\\\*/,'.*') }
221
+ # Fix for Chrome based browsers which returns XML when 'xhtml' is requested.
222
+ if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
223
+ accepted_types[0], accepted_types[1] = accepted_types[1], accepted_types[0]
224
+ if accepted_types[0].eql?('application/xhtml\\+xml')
225
+ accepted_types[0] = 'text/html'
226
+ end
227
+ end
228
+ accepted_types.each do |at|
229
+ format.each do |fmt, ht, handler|
230
+ (selected = [fmt, ht, handler]) and break if ht.match(at)
231
+ end
232
+ break unless selected.empty?
233
+ end
234
+ selected
235
+ end
236
+
237
+ # NOTE Array instead of hash because order matters (wildcard type
238
+ # matches first handler)
239
+ class Format < Array #:nodoc:
240
+ def method_missing(format, *args, &handler)
241
+ mt = Sinatra::RespondTo::Helpers.mime_type(format)
242
+ if mt.nil?
243
+ Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{format}\nTry registering the extension with a mime type")
244
+ end
245
+ self << [format.to_s, mt, handler]
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
253
+ Sinatra::Application.register Sinatra::RespondTo
data/server.rb CHANGED
@@ -9,6 +9,7 @@ require 'sinatra/lazy_auth'
9
9
  require 'erb'
10
10
  require 'haml'
11
11
  require 'open3'
12
+ require 'lib/deltacloud/helpers/blob_stream'
12
13
 
13
14
  configure do
14
15
  set :raise_errors => false
@@ -37,6 +38,8 @@ error Deltacloud::BackendError do
37
38
  report_error(500, "backend_error")
38
39
  end
39
40
 
41
+ Sinatra::Application.register Sinatra::RespondTo
42
+
40
43
  # Redirect to /api
41
44
  get '/' do redirect url_for('/api'); end
42
45
 
@@ -74,7 +77,8 @@ END
74
77
 
75
78
  operation :index do
76
79
  description <<END
77
- Operation will list all available realms. For specific architecture use "architecture" parameter.
80
+ Operation will list all available realms. Realms can be filtered using
81
+ the "architecture" parameter.
78
82
  END
79
83
  param :id, :string
80
84
  param :architecture, :string, :optional, [ 'i386', 'x86_64' ]
@@ -98,9 +102,9 @@ END
98
102
 
99
103
  operation :index do
100
104
  description <<END
101
- The instances collection will return a set of all images
102
- available to the current use. You can filter images using
103
- "owner_id" and "architecture" parameter
105
+ The images collection will return a set of all images
106
+ available to the current use. Images can be filtered using the
107
+ "owner_id" and "architecture" parameters.
104
108
  END
105
109
  param :id, :string
106
110
  param :architecture, :string, :optional
@@ -138,7 +142,10 @@ collection :instance_states do
138
142
  format.png do
139
143
  # Trick respond_to into looking up the right template for the
140
144
  # graphviz file
141
- format(:gv); gv = erb :"instance_states/show"; format(:png)
145
+ format_backup = format
146
+ format(:gv)
147
+ gv = erb(:"instance_states/show")
148
+ format(format_backup)
142
149
  png = ''
143
150
  cmd = 'dot -Kdot -Gpad="0.2,0.2" -Gsize="5.0,8.0" -Gdpi="180" -Tpng'
144
151
  Open3.popen3( cmd ) do |stdin, stdout, stderr|
@@ -146,6 +153,7 @@ collection :instance_states do
146
153
  stdin.close()
147
154
  png = stdout.read
148
155
  end
156
+ content_type 'image/png'
149
157
  png
150
158
  end
151
159
  end
@@ -170,7 +178,7 @@ collection :instances do
170
178
  END
171
179
 
172
180
  operation :index do
173
- description "List all instances"
181
+ description "List all instances."
174
182
  param :id, :string, :optional
175
183
  param :state, :string, :optional
176
184
  control { filter_all(:instances) }
@@ -183,7 +191,7 @@ END
183
191
  end
184
192
 
185
193
  operation :create do
186
- description "Create a new instance"
194
+ description "Create a new instance."
187
195
  param :image_id, :string, :required
188
196
  param :realm_id, :string, :optional
189
197
  param :hwp_id, :string, :optional
@@ -206,25 +214,25 @@ END
206
214
  end
207
215
 
208
216
  operation :reboot, :method => :post, :member => true do
209
- description "Reboot running instance"
217
+ description "Reboot a running instance."
210
218
  param :id, :string, :required
211
219
  control { instance_action(:reboot) }
212
220
  end
213
221
 
214
222
  operation :start, :method => :post, :member => true do
215
- description "Start an instance"
223
+ description "Start an instance."
216
224
  param :id, :string, :required
217
225
  control { instance_action(:start) }
218
226
  end
219
227
 
220
228
  operation :stop, :method => :post, :member => true do
221
- description "Stop running instance"
229
+ description "Stop a running instance."
222
230
  param :id, :string, :required
223
231
  control { instance_action(:stop) }
224
232
  end
225
233
 
226
234
  operation :destroy do
227
- description "Destroy instance"
235
+ description "Destroy an instance."
228
236
  param :id, :string, :required
229
237
  control { instance_action(:destroy) }
230
238
  end
@@ -239,7 +247,7 @@ collection :hardware_profiles do
239
247
  END
240
248
 
241
249
  operation :index do
242
- description "List of available hardware profiles"
250
+ description "List of available hardware profiles."
243
251
  param :id, :string
244
252
  param :architecture, :string, :optional, [ 'i386', 'x86_64' ]
245
253
  control do
@@ -253,7 +261,7 @@ END
253
261
  end
254
262
 
255
263
  operation :show do
256
- description "Show specific hardware profile"
264
+ description "Show specific hardware profile."
257
265
  param :id, :string, :required
258
266
  control do
259
267
  @profile = driver.hardware_profile(credentials, params[:id])
@@ -275,13 +283,13 @@ collection :storage_snapshots do
275
283
  description "Storage snapshots description here"
276
284
 
277
285
  operation :index do
278
- description "Listing of storage snapshots"
286
+ description "List of storage snapshots."
279
287
  param :id, :string
280
288
  control { filter_all(:storage_snapshots) }
281
289
  end
282
290
 
283
291
  operation :show do
284
- description "Show storage snapshot"
292
+ description "Show storage snapshot."
285
293
  param :id, :string, :required
286
294
  control { show(:storage_snapshot) }
287
295
  end
@@ -291,13 +299,13 @@ collection :storage_volumes do
291
299
  description "Storage volumes description here"
292
300
 
293
301
  operation :index do
294
- description "Listing of storage volumes"
302
+ description "List of storage volumes."
295
303
  param :id, :string
296
304
  control { filter_all(:storage_volumes) }
297
305
  end
298
306
 
299
307
  operation :show do
300
- description "Show storage volume"
308
+ description "Show storage volume."
301
309
  param :id, :string, :required
302
310
  control { show(:storage_volume) }
303
311
  end
@@ -310,23 +318,23 @@ get '/api/keys/new' do
310
318
  end
311
319
 
312
320
  collection :keys do
313
- description "Instance authentication credentials"
321
+ description "Instance authentication credentials."
314
322
 
315
323
  operation :index do
316
- description "List all available credentials which could be used for instance authentication"
324
+ description "List all available credentials which could be used for instance authentication."
317
325
  control do
318
326
  filter_all :keys
319
327
  end
320
328
  end
321
329
 
322
330
  operation :show do
323
- description "Show details about given instance credential"
331
+ description "Show details about given instance credential."
324
332
  param :id, :string, :required
325
333
  control { show :key }
326
334
  end
327
335
 
328
336
  operation :create do
329
- description "Create a new instance credential if backend supports this"
337
+ description "Create a new instance credential if backend supports this."
330
338
  param :name, :string, :required
331
339
  control do
332
340
  unless driver.respond_to?(:create_key)
@@ -342,7 +350,7 @@ collection :keys do
342
350
  end
343
351
 
344
352
  operation :destroy do
345
- description "Destroy given instance credential if backend supports this"
353
+ description "Destroy given instance credential if backend supports this."
346
354
  param :id, :string, :required
347
355
  control do
348
356
  unless driver.respond_to?(:destroy_key)
@@ -355,3 +363,77 @@ collection :keys do
355
363
  end
356
364
 
357
365
  end
366
+
367
+ get '/api/buckets/:bucket/:blob' do
368
+ @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
369
+ if @blob
370
+ respond_to do |format|
371
+ format.html { haml :"blobs/show" }
372
+ format.xml { haml :"blobs/show" }
373
+ format.json { convert_to_json(blobs, @blob) }
374
+ end
375
+ else
376
+ report_error(404, 'not_found')
377
+ end
378
+ end
379
+
380
+ get '/api/buckets/new' do
381
+ respond_to do |format|
382
+ format.html { haml :"buckets/new" }
383
+ end
384
+ end
385
+
386
+
387
+ get '/api/buckets/:bucket/:blob/content' do
388
+ @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]})
389
+ params['content_length'] = @blob.content_length
390
+ params['content_type'] = @blob.content_type
391
+ BlobStream.call(env, credentials, params)
392
+ end
393
+
394
+ collection :buckets do
395
+ description "Cloud Storage buckets - aka buckets|directories|folders"
396
+
397
+ operation :index do
398
+ description "List buckets associated with this account"
399
+ param :id, :string
400
+ param :name, :string
401
+ param :size, :string
402
+ control { filter_all(:buckets) }
403
+ end
404
+
405
+ operation :show do
406
+ description "Show bucket"
407
+ param :id, :string
408
+ control { show(:bucket) }
409
+ end
410
+
411
+ operation :create do
412
+ description "Create a new bucket (POST /api/buckets)"
413
+ param :name, :string, :required
414
+ control do
415
+ @bucket = driver.create_bucket(credentials, params[:name], params)
416
+ respond_to do |format|
417
+ format.xml do
418
+ response.status = 201 # Created
419
+ response['Location'] = bucket_url(@bucket.id)
420
+ haml :"buckets/show"
421
+ end
422
+ format.html do
423
+ redirect bucket_url(@bucket.id) if @bucket and @bucket.id
424
+ redirect buckets_url
425
+ end
426
+ end
427
+ end
428
+ end
429
+
430
+ operation :destroy do
431
+ description "Delete a bucket by name - bucket must be empty"
432
+ param :id, :string, :required
433
+ control do
434
+ driver.delete_bucket(credentials, params[:id], params)
435
+ redirect(buckets_url)
436
+ end
437
+ end
438
+
439
+ end