deltacloud-core 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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