visor-image 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/visor +423 -0
- data/bin/visor-image +10 -0
- data/config/server.rb +14 -0
- data/lib/image/auth.rb +147 -0
- data/lib/image/cli.rb +397 -0
- data/lib/image/client.rb +490 -0
- data/lib/image/meta.rb +219 -0
- data/lib/image/routes/delete_all_images.rb +40 -0
- data/lib/image/routes/delete_image.rb +62 -0
- data/lib/image/routes/get_image.rb +78 -0
- data/lib/image/routes/get_images.rb +54 -0
- data/lib/image/routes/get_images_detail.rb +54 -0
- data/lib/image/routes/head_image.rb +51 -0
- data/lib/image/routes/post_image.rb +189 -0
- data/lib/image/routes/put_image.rb +205 -0
- data/lib/image/server.rb +307 -0
- data/lib/image/store/cumulus.rb +126 -0
- data/lib/image/store/file_system.rb +119 -0
- data/lib/image/store/hdfs.rb +149 -0
- data/lib/image/store/http.rb +78 -0
- data/lib/image/store/lunacloud.rb +126 -0
- data/lib/image/store/s3.rb +121 -0
- data/lib/image/store/store.rb +39 -0
- data/lib/image/store/walrus.rb +130 -0
- data/lib/image/version.rb +5 -0
- data/lib/visor-image.rb +30 -0
- data/spec/lib/client_spec.rb +0 -0
- data/spec/lib/meta_spec.rb +230 -0
- data/spec/lib/routes/delete_image_spec.rb +98 -0
- data/spec/lib/routes/get_image_spec.rb +78 -0
- data/spec/lib/routes/get_images_detail_spec.rb +104 -0
- data/spec/lib/routes/get_images_spec.rb +104 -0
- data/spec/lib/routes/head_image_spec.rb +51 -0
- data/spec/lib/routes/post_image_spec.rb +112 -0
- data/spec/lib/routes/put_image_spec.rb +109 -0
- data/spec/lib/server_spec.rb +62 -0
- data/spec/lib/store/cumulus_spec.rb +0 -0
- data/spec/lib/store/file_system_spec.rb +32 -0
- data/spec/lib/store/http_spec.rb +56 -0
- data/spec/lib/store/s3_spec.rb +37 -0
- data/spec/lib/store/store_spec.rb +36 -0
- data/spec/lib/store/walrus_spec.rb +0 -0
- metadata +217 -0
data/bin/visor
ADDED
@@ -0,0 +1,423 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'uri'
|
5
|
+
require 'progressbar'
|
6
|
+
require 'visor-common'
|
7
|
+
|
8
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
9
|
+
require 'image/version'
|
10
|
+
require 'image/client'
|
11
|
+
|
12
|
+
# VISoR management command line interface script.
|
13
|
+
#
|
14
|
+
# Commands:
|
15
|
+
#
|
16
|
+
# brief Show brief metadata of all public images
|
17
|
+
# detail Show detailed metadata of all public images
|
18
|
+
# head Show image detailed metadata
|
19
|
+
# get Retrieve an image metadata and file
|
20
|
+
# add Add a new image metadata and optionally upload its file
|
21
|
+
# update Update an image metadata and/or upload its file
|
22
|
+
# delete Delete an image metadata and its file
|
23
|
+
# help <cmd> Show help message for one of the above commands
|
24
|
+
#
|
25
|
+
# Run <visor -h> to get more usage help.
|
26
|
+
#
|
27
|
+
class VisorCLI
|
28
|
+
include Visor::Common::Exception
|
29
|
+
include Visor::Common::Config
|
30
|
+
|
31
|
+
# VISoR Image CLI version
|
32
|
+
VERSION = '0.0.1'
|
33
|
+
|
34
|
+
attr_reader :argv, :options, :parser, :command
|
35
|
+
|
36
|
+
# Initialize a new CLI
|
37
|
+
def initialize(argv=ARGV)
|
38
|
+
@argv = argv
|
39
|
+
@options = load_conf_file
|
40
|
+
@parser = parser
|
41
|
+
@command = parse!
|
42
|
+
end
|
43
|
+
|
44
|
+
# OptionParser parser
|
45
|
+
def parser
|
46
|
+
OptionParser.new do |opts|
|
47
|
+
opts.banner = "Usage: visor <command> [options]"
|
48
|
+
|
49
|
+
opts.separator ""
|
50
|
+
opts.separator "Commands:"
|
51
|
+
opts.separator " brief Show brief metadata of all public images"
|
52
|
+
opts.separator " detail Show detailed metadata of all public images"
|
53
|
+
opts.separator " head Show image detailed metadata"
|
54
|
+
opts.separator " get Retrieve an image metadata and file"
|
55
|
+
opts.separator " add Add a new image metadata and optionally upload its file"
|
56
|
+
opts.separator " update Update an image metadata and/or upload its file"
|
57
|
+
opts.separator " delete Delete an image metadata and its file"
|
58
|
+
opts.separator " clean Delete all images metadata and files"
|
59
|
+
opts.separator " help <cmd> Show help message for one of the above commands"
|
60
|
+
|
61
|
+
|
62
|
+
opts.separator ""
|
63
|
+
opts.separator "Options:"
|
64
|
+
opts.on("-a", "--address HOST", "Address of VISoR Image host (default: #{options[:host]})") { |addr| options[:host] = addr }
|
65
|
+
opts.on("-p", "--port PORT", "Port were VISoR Image host listens (default: #{options[:port]})") { |port| options[:port] = port }
|
66
|
+
opts.on("-q", "--query QUERY", "HTTP query like string to filter results") do |query|
|
67
|
+
begin
|
68
|
+
options[:query] = URI.decode_www_form(query)
|
69
|
+
rescue
|
70
|
+
abort "The provided query string is not valid."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
opts.on("-s", "--sort ATTRIBUTE", "Attribute to sort results (default: _id)") do |attr|
|
74
|
+
options[:query] = [] unless options[:query]
|
75
|
+
options[:query] << URI.decode_www_form("sort=#{attr}").flatten
|
76
|
+
end
|
77
|
+
opts.on("-d", "--dir DIRECTION", "Direction to sort results (asc/desc) (default: asc)") do |dir|
|
78
|
+
options[:query] = [] unless options[:query]
|
79
|
+
options[:query] << URI.decode_www_form("dir=#{dir}").flatten
|
80
|
+
end
|
81
|
+
opts.on("-f", "--file IMAGE", "Image file path to upload") { |path| options[:file] = path }
|
82
|
+
opts.on("-S", "--save DIRECTORY", "Directory to save downloaded image (default: './')") { |path| options[:save] = path }
|
83
|
+
|
84
|
+
opts.separator ""
|
85
|
+
opts.separator "Common options:"
|
86
|
+
opts.on_tail("-D", "--dry-run", "Don't persist results, just print what would it do") { options[:dry] = true }
|
87
|
+
opts.on_tail('-v', '--verbose', "Enable verbose") { options[:verbose] = true }
|
88
|
+
opts.on_tail("-h", "--help", "Show this help message") { puts opts; exit 0 }
|
89
|
+
opts.on_tail('-V', '--version', "Show version") { puts "VISoR Image CLI v#{VERSION}"; exit 0 }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Parse argv arguments
|
94
|
+
def parse!
|
95
|
+
parser.parse! ARGV
|
96
|
+
ARGV.shift
|
97
|
+
end
|
98
|
+
|
99
|
+
# Parse the current shell arguments and run the command
|
100
|
+
def run!
|
101
|
+
abort parser.to_s if command.nil?
|
102
|
+
start = Time.now
|
103
|
+
case command
|
104
|
+
when 'brief' then
|
105
|
+
brief
|
106
|
+
when 'detail' then
|
107
|
+
detail
|
108
|
+
when 'head' then
|
109
|
+
head
|
110
|
+
when 'get' then
|
111
|
+
get
|
112
|
+
when 'add' then
|
113
|
+
add
|
114
|
+
when 'update' then
|
115
|
+
update
|
116
|
+
when 'delete' then
|
117
|
+
delete
|
118
|
+
when 'help' then
|
119
|
+
help
|
120
|
+
else
|
121
|
+
abort "Unknown command '#{command}'"
|
122
|
+
end
|
123
|
+
finish = Time.now
|
124
|
+
printf("Done in %-0.4f seconds", finish - start) if verbose?
|
125
|
+
exit 0
|
126
|
+
end
|
127
|
+
|
128
|
+
# Show brief metadata of all public images
|
129
|
+
def brief
|
130
|
+
str = "%-37s %-21s %-13s %-12s %-8s %-10s %-10s\n"
|
131
|
+
images = client.get_images(options[:query])
|
132
|
+
|
133
|
+
puts "Found #{images.size} public images records..."
|
134
|
+
printf(str, 'ID', 'NAME', 'ARCHITECTURE', 'TYPE', 'FORMAT', 'STORE', 'SIZE')
|
135
|
+
puts "#{'-'*36+" "+'-'*20+" "+'-'*12+" "+'-'*11+" "+'-'*6+" "+'-'*10+" "+'-'*10}"
|
136
|
+
|
137
|
+
images.each do |image|
|
138
|
+
printf(str, image[:_id], image[:name], image[:architecture], image[:type] || '-', image[:format] || '-', image[:store] || '-', image[:size] || '-')
|
139
|
+
end
|
140
|
+
rescue NotFound => e
|
141
|
+
puts e.message
|
142
|
+
rescue => e
|
143
|
+
abort "Failure while executing 'brief':\n#{e.message}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Show detailed metadata of all public images
|
147
|
+
def detail
|
148
|
+
images = client.get_images_detail(options[:query])
|
149
|
+
puts "Found #{images.size} public images records..."
|
150
|
+
|
151
|
+
images.each do |image|
|
152
|
+
puts('-'*80)
|
153
|
+
print_meta(image, false)
|
154
|
+
end
|
155
|
+
rescue NotFound => e
|
156
|
+
puts e.message
|
157
|
+
rescue => e
|
158
|
+
abort "Failure while executing 'detail':\n#{e.message}"
|
159
|
+
end
|
160
|
+
|
161
|
+
# Show image detailed metadata
|
162
|
+
def head
|
163
|
+
id = argv.shift
|
164
|
+
abort "No image ID provided as first argument, please provide it." unless id
|
165
|
+
image = client.head_image(id)
|
166
|
+
print_meta(image)
|
167
|
+
rescue NotFound => e
|
168
|
+
puts e.message
|
169
|
+
rescue => e
|
170
|
+
abort "Failure while executing 'head':\n#{e.message}"
|
171
|
+
end
|
172
|
+
|
173
|
+
# Retrieve an image metadata and file
|
174
|
+
def get
|
175
|
+
id = argv.shift
|
176
|
+
abort "No image ID provided as first argument, please provide it." unless id
|
177
|
+
|
178
|
+
image = client.head_image(id)
|
179
|
+
print_meta(image)
|
180
|
+
path = File.expand_path(options[:save] || './')
|
181
|
+
fp = File.expand_path(File.join(path, "#{id}.#{image[:format] || 'none'}"))
|
182
|
+
|
183
|
+
raise "Cannot locate directory '#{path}'." unless Dir.exists? path
|
184
|
+
#raise "File #{fp} already exists." if File.exists? fp #TODO: restore this
|
185
|
+
|
186
|
+
if File.exists? fp
|
187
|
+
require "securerandom"
|
188
|
+
fp = "#{fp}_#{SecureRandom.random_number.to_s[11..-1]}"
|
189
|
+
end
|
190
|
+
|
191
|
+
file = File.open(fp, 'wb')
|
192
|
+
pbar = ProgressBar.new("Progress", image[:size])
|
193
|
+
pbar.bar_mark = '='
|
194
|
+
|
195
|
+
puts "Downloading image #{id}..."
|
196
|
+
client.get_image(id) do |chunk|
|
197
|
+
pbar.inc(chunk.size)
|
198
|
+
file.write(chunk)
|
199
|
+
end
|
200
|
+
pbar.finish
|
201
|
+
file.close
|
202
|
+
rescue NotFound => e
|
203
|
+
puts e.message
|
204
|
+
rescue => e
|
205
|
+
abort "Failure while executing 'get':\n#{e.message}"
|
206
|
+
end
|
207
|
+
|
208
|
+
# Add a new image metadata and optionally upload its file
|
209
|
+
def add
|
210
|
+
file = options[:file]
|
211
|
+
if file
|
212
|
+
fp = File.expand_path(file)
|
213
|
+
abort "Cannot locate image file at #{fp}." unless File.exists? fp
|
214
|
+
puts "Adding new metadata and uploading file..."
|
215
|
+
end
|
216
|
+
|
217
|
+
begin
|
218
|
+
meta = parse_meta_from_args
|
219
|
+
image = client.post_image(meta, file)
|
220
|
+
puts "Successfully added new metadata and image with ID #{image[:_id]}." if file
|
221
|
+
puts "Successfully added new metadata with ID #{image[:_id]}." unless file
|
222
|
+
print_meta(image)
|
223
|
+
rescue NotFound => e
|
224
|
+
puts e.message
|
225
|
+
rescue => e
|
226
|
+
abort "Failure while executing 'add':\n#{e.message}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Update an image metadata and/or upload its file
|
231
|
+
def update
|
232
|
+
id = argv.shift
|
233
|
+
file = options[:file]
|
234
|
+
abort "No image ID provided as first argument, please provide it." unless id
|
235
|
+
if file
|
236
|
+
fp = File.expand_path(file)
|
237
|
+
abort "Cannot locate image file at #{fp}." unless File.exists?(fp)
|
238
|
+
puts "Updating metadata and uploading file..."
|
239
|
+
end
|
240
|
+
|
241
|
+
begin
|
242
|
+
meta = parse_meta_from_args
|
243
|
+
image = client.put_image(id, meta, file)
|
244
|
+
puts "Successfully updated and uploaded image #{id}." if file
|
245
|
+
puts "Successfully updated image #{id}." unless file
|
246
|
+
print_meta(image) if verbose?
|
247
|
+
rescue NotFound => e
|
248
|
+
puts e.message
|
249
|
+
rescue => e
|
250
|
+
abort "Failure while executing 'update':\n#{e.message}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Delete an image metadata and its file
|
255
|
+
def delete
|
256
|
+
begin
|
257
|
+
if query = options[:query]
|
258
|
+
result = client.delete_by_query(query)
|
259
|
+
result.each { |image| puts "Successfully deleted image #{image[:_id]}." }
|
260
|
+
else
|
261
|
+
argv.each do |id|
|
262
|
+
abort "No image ID provided as first argument, please provide it." unless id
|
263
|
+
image = client.delete_image(id)
|
264
|
+
puts "Successfully deleted image #{id}."
|
265
|
+
print_meta(image) if verbose?
|
266
|
+
end
|
267
|
+
end
|
268
|
+
rescue NotFound => e
|
269
|
+
puts e.message
|
270
|
+
rescue => e
|
271
|
+
abort "Failure while executing 'delete':\n#{e.message}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Show help message for one of the above commands
|
276
|
+
def help
|
277
|
+
cmd = argv[0]
|
278
|
+
abort "Please provide a command name as argument (example: visor help brief)." unless cmd
|
279
|
+
|
280
|
+
case cmd.to_sym
|
281
|
+
when :brief
|
282
|
+
puts %q[Usage: visor brief [options]
|
283
|
+
|
284
|
+
Returns brief metadata of all public images.
|
285
|
+
|
286
|
+
You can filter results based on a query using the --query (-q) option.
|
287
|
+
It's possible to sort and order results with the --sort (-s) and --dir (-d) options.
|
288
|
+
|
289
|
+
Examples:
|
290
|
+
$ visor brief --query 'architecture=i386'
|
291
|
+
$ visor brief --query 'architecture=i386&format=iso'
|
292
|
+
$ visor detail --sort name --dir desc]
|
293
|
+
when :detail
|
294
|
+
%q[Usage: visor detail [options]
|
295
|
+
|
296
|
+
Returns detailed metadata of all public images.
|
297
|
+
|
298
|
+
You can filter results based on a query using the --query (-q) option.
|
299
|
+
It's possible to sort and order results with the --sort (-s) and --dir (-d) options.
|
300
|
+
|
301
|
+
Examples:
|
302
|
+
$ visor detail --query 'architecture=i386'
|
303
|
+
$ visor detail --query 'architecture=i386&format=iso'
|
304
|
+
$ visor detail --sort name --dir desc]
|
305
|
+
when :head
|
306
|
+
puts "Usage: visor head <ID> [options]\n\nReturns detailed metadata of the image with the given ID."
|
307
|
+
when :get
|
308
|
+
puts %q[Usage: visor get <ID> [options]
|
309
|
+
|
310
|
+
Returns detailed metadata and image file of the image with the given ID.
|
311
|
+
You can provide the --save (-S) option defining the path where the image should be saved.
|
312
|
+
|
313
|
+
Examples:
|
314
|
+
$ visor get 8074d23e-a9c0-454d-b935-cda5f6eb1bc8 --save '~/VMs/']
|
315
|
+
when :add
|
316
|
+
puts %q[Usage: visor add <ATTRIBUTES> [options]
|
317
|
+
|
318
|
+
Add new metadata and optionally upload the image file.
|
319
|
+
|
320
|
+
The following attributes can be specified as key/value pairs:
|
321
|
+
|
322
|
+
name: The image name
|
323
|
+
architecture: The Image operating system architecture (available: i386 x86_64)
|
324
|
+
access: If the image is public or private (available: public private)
|
325
|
+
format: The format of the image's disk (available: none iso vhd vdi vmdk ami aki ari)
|
326
|
+
type: The type of the image (available: none kernel ramdisk amazon eucalyptus openstack opennebula nimbus)
|
327
|
+
store: The storage system to save image in (available: s3 http file)
|
328
|
+
location: The location URI of the already somewhere stored image
|
329
|
+
|
330
|
+
Any other custom image property can be passed too as additional key/value pairs.
|
331
|
+
|
332
|
+
Provide the --file option with the path to the image to be uploaded and the 'store' attribute,
|
333
|
+
defining the store where the image should be uploaded to.
|
334
|
+
|
335
|
+
Examples:
|
336
|
+
$ visor add name='Ubuntu 11.10' architecture='x86_64' location='http://www.domain.com/path-to-image'
|
337
|
+
$ visor add name='Ubuntu 11.10' architecture='x86_64' store='s3' --file '~/VMs/ubuntu-11.10-x86_64.iso']
|
338
|
+
when :update
|
339
|
+
puts %q[Usage: visor update <ID> [options]
|
340
|
+
|
341
|
+
Updates metadata and/or uploads image file of the image with the given ID.
|
342
|
+
|
343
|
+
The following attributes can be specified as key/value pairs:
|
344
|
+
|
345
|
+
name: The image name
|
346
|
+
architecture: The Image operating system architecture (available: i386 x86_64)
|
347
|
+
access: If the image is public or private (available: public private)
|
348
|
+
format: The format of the image's disk (available: none iso vhd vdi vmdk ami aki ari)
|
349
|
+
type: The type of the image (available: none kernel ramdisk amazon eucalyptus openstack opennebula nimbus)
|
350
|
+
store: The storage system to save image in (available: s3 http file)
|
351
|
+
location: The location URI of the already somewhere stored image
|
352
|
+
|
353
|
+
Any other custom image property can be passed too as additional key/value pairs.
|
354
|
+
|
355
|
+
It is possible to upload and assign an image file to an already registered metadata:
|
356
|
+
|
357
|
+
Provide the --file option with the path to the image to be uploaded and the 'store' attribute,
|
358
|
+
defining the store where the image should be uploaded to.
|
359
|
+
|
360
|
+
Examples:
|
361
|
+
$ visor update 8074d23e... format='iso'
|
362
|
+
$ visor update 8074d23e... format='iso' location='http://www.domain.com/path-to-image'
|
363
|
+
$ visor update 8074d23e... store='s3' --file '~/VMs/ubuntu-11.10-x86_64.iso']
|
364
|
+
when :delete
|
365
|
+
puts "Usage: visor delete <ID> [options]\n\nDeletes metadata and image file of the image with the given ID."
|
366
|
+
else
|
367
|
+
abort "Unknown command '#{cmd}'"
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
private
|
372
|
+
|
373
|
+
# Pretty print image metadata
|
374
|
+
def print_meta(meta, breaklines=true)
|
375
|
+
puts '' if breaklines
|
376
|
+
order_attributes(meta).each { |k, v| puts "#{k.upcase.to_s.rjust(12)}: #{v}" }
|
377
|
+
puts '' if breaklines
|
378
|
+
end
|
379
|
+
|
380
|
+
# Load configuration file options
|
381
|
+
def load_conf_file
|
382
|
+
config = Visor::Common::Config.load_config(:visor_image)
|
383
|
+
{host: config[:bind_host], port: config[:bind_port]}
|
384
|
+
rescue => e
|
385
|
+
raise "There was an error loading the configuration file: #{e.message}"
|
386
|
+
end
|
387
|
+
|
388
|
+
# Get a new VISoR Image Client instance
|
389
|
+
def client
|
390
|
+
Visor::Image::Client.new(options)
|
391
|
+
end
|
392
|
+
|
393
|
+
# Find if verbose mode is active
|
394
|
+
def verbose?
|
395
|
+
options[:verbose]
|
396
|
+
end
|
397
|
+
|
398
|
+
# Properly order metadata hashes
|
399
|
+
def order_attributes(hash)
|
400
|
+
order = {_id: '-', uri: '-', name: '-', architecture: '-', access: '-', status: '-', type: '-', format: '-',
|
401
|
+
size: '-', store: '-', location: '-', kernel: '-', ramdisk: '-', created_at: '-', updated_at: '-', checksum: '-'}
|
402
|
+
order.merge(hash)
|
403
|
+
end
|
404
|
+
|
405
|
+
# Parse key/value pair arguments to a valid metadata hash
|
406
|
+
def parse_meta_from_args
|
407
|
+
meta = {}
|
408
|
+
raise "You should provide at least one key=value pair." if argv.empty?
|
409
|
+
argv.each do |arg|
|
410
|
+
k, v = arg.split('=')
|
411
|
+
raise "Arguments should be in the form of key=value pairs." unless k && v
|
412
|
+
meta[k.downcase.sub('-', '_')] = v
|
413
|
+
end
|
414
|
+
meta
|
415
|
+
end
|
416
|
+
|
417
|
+
end
|
418
|
+
|
419
|
+
# Execute if file is called
|
420
|
+
#if __FILE__ == $0
|
421
|
+
VisorCLI.new.run!
|
422
|
+
#end
|
423
|
+
|
data/bin/visor-image
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# VISoR Image Server command line interface script.
|
4
|
+
# Run <visor-image -h> to get more usage help.
|
5
|
+
|
6
|
+
require File.expand_path('../../lib/visor-image', __FILE__)
|
7
|
+
|
8
|
+
ENV['GOLIATH_CONF'] = File.expand_path('../../config/server.rb', __FILE__)
|
9
|
+
STDERR.puts ENV['GOLIATH_CONF']
|
10
|
+
Visor::Image::CLI.new(ARGV).run!
|
data/config/server.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
conf = Visor::Common::Config.load_config
|
2
|
+
|
3
|
+
meta_host = conf[:visor_meta][:bind_host]
|
4
|
+
meta_port = conf[:visor_meta][:bind_port]
|
5
|
+
|
6
|
+
auth_host = conf[:visor_auth][:bind_host]
|
7
|
+
auth_port = conf[:visor_auth][:bind_port]
|
8
|
+
|
9
|
+
log_level = conf[:visor_image][:log_level]
|
10
|
+
|
11
|
+
logger.level = (log_level == 'INFO' ? 1 : 0)
|
12
|
+
config['vms'] = Visor::Image::Meta.new(host: meta_host, port: meta_port)
|
13
|
+
config['vas'] = Visor::Image::Auth.new(host: auth_host, port: auth_port)
|
14
|
+
config['configs'] = conf[:visor_store]
|
data/lib/image/auth.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Visor
|
7
|
+
module Image
|
8
|
+
|
9
|
+
# The Client API for the VISoR Auth.
|
10
|
+
#
|
11
|
+
# After Instantiate a Client object its possible to directly interact with the auth server and its
|
12
|
+
# database backend.
|
13
|
+
#
|
14
|
+
class Auth
|
15
|
+
|
16
|
+
include Visor::Common::Exception
|
17
|
+
|
18
|
+
configs = Common::Config.load_config :visor_auth
|
19
|
+
|
20
|
+
DEFAULT_HOST = configs[:bind_host] || '0.0.0.0'
|
21
|
+
DEFAULT_PORT = configs[:bind_port] || 4567
|
22
|
+
|
23
|
+
attr_reader :host, :port, :ssl
|
24
|
+
|
25
|
+
def initialize(opts = {})
|
26
|
+
@host = opts[:host] || DEFAULT_HOST
|
27
|
+
@port = opts[:port] || DEFAULT_PORT
|
28
|
+
@ssl = opts[:ssl] || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_users(query = {})
|
32
|
+
str = build_query(query)
|
33
|
+
request = Net::HTTP::Get.new("/users#{str}")
|
34
|
+
do_request(request)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_user(access_key)
|
38
|
+
request = Net::HTTP::Get.new("/users/#{access_key}")
|
39
|
+
do_request(request)
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_user(info)
|
43
|
+
request = Net::HTTP::Post.new('/users')
|
44
|
+
request.body = prepare_body(info)
|
45
|
+
do_request(request)
|
46
|
+
end
|
47
|
+
|
48
|
+
def put_user(access_key, info)
|
49
|
+
request = Net::HTTP::Put.new("/users/#{access_key}")
|
50
|
+
request.body = prepare_body(info)
|
51
|
+
do_request(request)
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_user(access_key)
|
55
|
+
request = Net::HTTP::Delete.new("/users/#{access_key}")
|
56
|
+
do_request(request)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Parses a response body with the JSON parser and extracts and returns a single
|
62
|
+
# key value from it if defined, otherwise returns all the body.
|
63
|
+
#
|
64
|
+
# @param key (nil) [Symbol] The hash key to extract the wanted value.
|
65
|
+
# @param response [Net::HTTPResponse] The response which contains the body to parse.
|
66
|
+
#
|
67
|
+
# @return [String, Hash] If key is provided and exists on the response body, them return
|
68
|
+
# its value, otherwise return all the body hash.
|
69
|
+
#
|
70
|
+
def parse(key=nil, response)
|
71
|
+
parsed = JSON.parse(response.body, symbolize_names: true)
|
72
|
+
key ? parsed[key] : parsed
|
73
|
+
end
|
74
|
+
|
75
|
+
# Generate a valid URI query string from key/value pairs of the given hash.
|
76
|
+
#
|
77
|
+
# @param opts [Hash] The hash with the key/value pairs to generate query from.
|
78
|
+
#
|
79
|
+
# @return [String] The generated query in the form of "?k=v&k1=v1".
|
80
|
+
#
|
81
|
+
def build_query(opts)
|
82
|
+
opts.empty? ? '' : '?' + URI.encode_www_form(opts)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Fill common header keys before each request. This sets the 'User-Agent' and 'Accept'
|
86
|
+
# headers for every request and additionally sets the 'content-type' header
|
87
|
+
# for POST and PUT requests.
|
88
|
+
#
|
89
|
+
# @param request [Net::HTTPResponse] The request which will be modified in its headers.
|
90
|
+
#
|
91
|
+
def prepare_headers(request)
|
92
|
+
request['User-Agent'] = 'VISoR image server'
|
93
|
+
request['Accept'] = 'application/json'
|
94
|
+
request['content-type'] = 'application/json' if ['POST', 'PUT'].include?(request.method)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generate a valid JSON request body for POST and PUT requests.
|
98
|
+
# It generates a JSON object encapsulated inside a :image key and then returns it.
|
99
|
+
#
|
100
|
+
# @param hash [Hash] The hash with the key/value pairs to generate a JSON object from.
|
101
|
+
#
|
102
|
+
# @return [Hash] If an :image key is already present in the hash, it just returns the plain
|
103
|
+
# JSON object, otherwise, encapsulate the hash inside a :image key and returns it.
|
104
|
+
#
|
105
|
+
def prepare_body(hash)
|
106
|
+
hash.has_key?(:user) ? hash.to_json : {user: hash}.to_json
|
107
|
+
end
|
108
|
+
|
109
|
+
# Process requests by preparing its headers, launch them and assert or raise their response.
|
110
|
+
#
|
111
|
+
# @param request [Net::HTTPResponse] The request which will be launched.
|
112
|
+
#
|
113
|
+
# @return [String, Hash] If an error is raised, then it parses and returns its message,
|
114
|
+
# otherwise it properly parse and return the response body.
|
115
|
+
#
|
116
|
+
# @raise [NotFound] If required image was not found (on a GET, PUT or DELETE request).
|
117
|
+
# @raise [Invalid] If image meta validation fails (on a POST or PUT request).
|
118
|
+
#
|
119
|
+
def do_request(request)
|
120
|
+
prepare_headers(request)
|
121
|
+
response = http_or_https.request(request)
|
122
|
+
case response
|
123
|
+
when Net::HTTPNotFound then
|
124
|
+
raise NotFound, parse(:message, response)
|
125
|
+
when Net::HTTPBadRequest then
|
126
|
+
raise Invalid, parse(:message, response)
|
127
|
+
else
|
128
|
+
parse(:user, response) or parse(:users, response)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Generate a new HTTP or HTTPS connection based on initialization parameters.
|
133
|
+
#
|
134
|
+
# @return [Net::HTTP] A HTTP or HTTPS (not done yet) connection ready to use.
|
135
|
+
#
|
136
|
+
def http_or_https
|
137
|
+
if @ssl
|
138
|
+
#TODO: ssl connection
|
139
|
+
#https://github.com/augustl/net-http-cheat-sheet/blob/master/ssl_and_https.rb
|
140
|
+
else
|
141
|
+
Net::HTTP.new(@host, @port)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|