visor-image 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/visor +59 -87
- data/config/server.rb +1 -0
- data/lib/image/auth.rb +108 -77
- data/lib/image/cli.rb +30 -30
- data/lib/image/client.rb +224 -125
- data/lib/image/meta.rb +57 -69
- data/lib/image/routes/delete_all_images.rb +2 -0
- data/lib/image/routes/delete_image.rb +2 -0
- data/lib/image/routes/get_image.rb +2 -0
- data/lib/image/routes/get_images.rb +3 -0
- data/lib/image/routes/get_images_detail.rb +3 -0
- data/lib/image/routes/head_image.rb +2 -0
- data/lib/image/routes/post_image.rb +4 -2
- data/lib/image/routes/put_image.rb +3 -1
- data/lib/image/server.rb +87 -65
- data/lib/image/store/cumulus.rb +2 -2
- data/lib/image/store/hdfs.rb +1 -1
- data/lib/image/store/http.rb +1 -1
- data/lib/image/store/lunacloud.rb +5 -7
- data/lib/image/store/s3.rb +2 -2
- data/lib/image/store/store.rb +1 -1
- data/lib/image/store/walrus.rb +2 -2
- data/lib/image/version.rb +1 -1
- data/spec/lib/meta_spec.rb +10 -9
- data/spec/lib/routes/delete_image_spec.rb +2 -2
- data/spec/lib/routes/get_image_spec.rb +2 -2
- data/spec/lib/routes/get_images_detail_spec.rb +6 -6
- data/spec/lib/routes/get_images_spec.rb +5 -5
- data/spec/lib/routes/head_image_spec.rb +2 -1
- data/spec/lib/routes/post_image_spec.rb +2 -2
- data/spec/lib/routes/put_image_spec.rb +2 -1
- metadata +2 -2
data/bin/visor
CHANGED
@@ -48,21 +48,20 @@ class VisorCLI
|
|
48
48
|
|
49
49
|
opts.separator ""
|
50
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"
|
51
|
+
opts.separator " brief Show brief metadata of all public and user's private images"
|
52
|
+
opts.separator " detail Show detailed metadata of all public and user's private images"
|
53
|
+
opts.separator " head Show an image detailed metadata"
|
54
54
|
opts.separator " get Retrieve an image metadata and file"
|
55
55
|
opts.separator " add Add a new image metadata and optionally upload its file"
|
56
56
|
opts.separator " update Update an image metadata and/or upload its file"
|
57
57
|
opts.separator " delete Delete an image metadata and its file"
|
58
|
-
opts.separator " clean Delete all images metadata and files"
|
59
58
|
opts.separator " help <cmd> Show help message for one of the above commands"
|
60
59
|
|
61
60
|
|
62
61
|
opts.separator ""
|
63
62
|
opts.separator "Options:"
|
64
|
-
opts.on("-a", "--address HOST", "Address of
|
65
|
-
opts.on("-p", "--port PORT", "Port
|
63
|
+
opts.on("-a", "--address HOST", "Address of the VISOR Image System server (default: #{options[:host]})") { |addr| options[:host] = addr }
|
64
|
+
opts.on("-p", "--port PORT", "Port where the VISOR Image System server listens (default: #{options[:port]})") { |port| options[:port] = port }
|
66
65
|
opts.on("-q", "--query QUERY", "HTTP query like string to filter results") do |query|
|
67
66
|
begin
|
68
67
|
options[:query] = URI.decode_www_form(query)
|
@@ -83,10 +82,9 @@ class VisorCLI
|
|
83
82
|
|
84
83
|
opts.separator ""
|
85
84
|
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
85
|
opts.on_tail('-v', '--verbose', "Enable verbose") { options[:verbose] = true }
|
88
86
|
opts.on_tail("-h", "--help", "Show this help message") { puts opts; exit 0 }
|
89
|
-
opts.on_tail('-V', '--version', "Show version") { puts "
|
87
|
+
opts.on_tail('-V', '--version', "Show version") { puts "visor CLI #{VERSION}"; exit 0 }
|
90
88
|
end
|
91
89
|
end
|
92
90
|
|
@@ -100,62 +98,62 @@ class VisorCLI
|
|
100
98
|
def run!
|
101
99
|
abort parser.to_s if command.nil?
|
102
100
|
start = Time.now
|
103
|
-
|
104
|
-
|
105
|
-
brief
|
106
|
-
|
107
|
-
detail
|
108
|
-
|
109
|
-
head
|
110
|
-
|
111
|
-
get
|
112
|
-
|
113
|
-
add
|
114
|
-
|
115
|
-
update
|
116
|
-
|
117
|
-
delete
|
118
|
-
|
119
|
-
help
|
120
|
-
|
121
|
-
|
101
|
+
begin
|
102
|
+
case command
|
103
|
+
when 'brief' then
|
104
|
+
brief
|
105
|
+
when 'detail' then
|
106
|
+
detail
|
107
|
+
when 'head' then
|
108
|
+
head
|
109
|
+
when 'get' then
|
110
|
+
get
|
111
|
+
when 'add' then
|
112
|
+
add
|
113
|
+
when 'update' then
|
114
|
+
update
|
115
|
+
when 'delete' then
|
116
|
+
delete
|
117
|
+
when 'help' then
|
118
|
+
help
|
119
|
+
else
|
120
|
+
abort "Unknown command '#{command}'"
|
121
|
+
end
|
122
|
+
rescue NotFound => e
|
123
|
+
puts e.message
|
124
|
+
rescue Errno::ECONNREFUSED
|
125
|
+
abort "Failure while executing '#{command}': VISOR Image System server not found. Is it running?"
|
126
|
+
#rescue => e
|
127
|
+
# abort "Failure while executing '#{command}': #{e.message}"
|
122
128
|
end
|
123
129
|
finish = Time.now
|
124
|
-
printf("Done in %-0.4f seconds", finish - start) if verbose?
|
130
|
+
printf("Done in %-0.4f seconds\n", finish - start) if verbose?
|
125
131
|
exit 0
|
126
132
|
end
|
127
133
|
|
128
134
|
# Show brief metadata of all public images
|
129
135
|
def brief
|
130
|
-
str = "%-37s %-
|
136
|
+
str = "%-37s %-22s %-13s %-10s %-8s %-10s %-10s\n"
|
131
137
|
images = client.get_images(options[:query])
|
132
138
|
|
133
|
-
puts "Found #{images.size}
|
139
|
+
puts "Found #{images.size} images records..."
|
134
140
|
printf(str, 'ID', 'NAME', 'ARCHITECTURE', 'TYPE', 'FORMAT', 'STORE', 'SIZE')
|
135
|
-
puts "#{'-'*36+" "+'-'*
|
141
|
+
puts "#{'-'*36+" "+'-'*21+" "+'-'*12+" "+'-'*9+" "+'-'*6+" "+'-'*10+" "+'-'*10}"
|
136
142
|
|
137
143
|
images.each do |image|
|
138
144
|
printf(str, image[:_id], image[:name], image[:architecture], image[:type] || '-', image[:format] || '-', image[:store] || '-', image[:size] || '-')
|
139
145
|
end
|
140
|
-
rescue NotFound => e
|
141
|
-
puts e.message
|
142
|
-
rescue => e
|
143
|
-
abort "Failure while executing 'brief':\n#{e.message}"
|
144
146
|
end
|
145
147
|
|
146
148
|
# Show detailed metadata of all public images
|
147
149
|
def detail
|
148
150
|
images = client.get_images_detail(options[:query])
|
149
|
-
puts "Found #{images.size}
|
151
|
+
puts "Found #{images.size} images records..."
|
150
152
|
|
151
153
|
images.each do |image|
|
152
154
|
puts('-'*80)
|
153
155
|
print_meta(image, false)
|
154
156
|
end
|
155
|
-
rescue NotFound => e
|
156
|
-
puts e.message
|
157
|
-
rescue => e
|
158
|
-
abort "Failure while executing 'detail':\n#{e.message}"
|
159
157
|
end
|
160
158
|
|
161
159
|
# Show image detailed metadata
|
@@ -164,10 +162,6 @@ class VisorCLI
|
|
164
162
|
abort "No image ID provided as first argument, please provide it." unless id
|
165
163
|
image = client.head_image(id)
|
166
164
|
print_meta(image)
|
167
|
-
rescue NotFound => e
|
168
|
-
puts e.message
|
169
|
-
rescue => e
|
170
|
-
abort "Failure while executing 'head':\n#{e.message}"
|
171
165
|
end
|
172
166
|
|
173
167
|
# Retrieve an image metadata and file
|
@@ -181,7 +175,7 @@ class VisorCLI
|
|
181
175
|
fp = File.expand_path(File.join(path, "#{id}.#{image[:format] || 'none'}"))
|
182
176
|
|
183
177
|
raise "Cannot locate directory '#{path}'." unless Dir.exists? path
|
184
|
-
|
178
|
+
raise "File #{fp} already exists." if File.exists? fp
|
185
179
|
|
186
180
|
if File.exists? fp
|
187
181
|
require "securerandom"
|
@@ -199,10 +193,6 @@ class VisorCLI
|
|
199
193
|
end
|
200
194
|
pbar.finish
|
201
195
|
file.close
|
202
|
-
rescue NotFound => e
|
203
|
-
puts e.message
|
204
|
-
rescue => e
|
205
|
-
abort "Failure while executing 'get':\n#{e.message}"
|
206
196
|
end
|
207
197
|
|
208
198
|
# Add a new image metadata and optionally upload its file
|
@@ -214,17 +204,11 @@ class VisorCLI
|
|
214
204
|
puts "Adding new metadata and uploading file..."
|
215
205
|
end
|
216
206
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
207
|
+
meta = parse_meta_from_args
|
208
|
+
image = client.post_image(meta, file)
|
209
|
+
puts "Successfully added new metadata and image with ID #{image[:_id]}." if file
|
210
|
+
puts "Successfully added new metadata with ID #{image[:_id]}." unless file
|
211
|
+
print_meta(image)
|
228
212
|
end
|
229
213
|
|
230
214
|
# Update an image metadata and/or upload its file
|
@@ -238,37 +222,25 @@ class VisorCLI
|
|
238
222
|
puts "Updating metadata and uploading file..."
|
239
223
|
end
|
240
224
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
225
|
+
meta = parse_meta_from_args
|
226
|
+
image = client.put_image(id, meta, file)
|
227
|
+
puts "Successfully updated and uploaded image #{id}." if file
|
228
|
+
puts "Successfully updated image #{id}." unless file
|
229
|
+
print_meta(image) if verbose?
|
252
230
|
end
|
253
231
|
|
254
232
|
# Delete an image metadata and its file
|
255
233
|
def delete
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
print_meta(image) if verbose?
|
266
|
-
end
|
234
|
+
if query = options[:query]
|
235
|
+
result = client.delete_by_query(query)
|
236
|
+
result.each { |image| puts "Successfully deleted image #{image[:_id]}." }
|
237
|
+
else
|
238
|
+
argv.each do |id|
|
239
|
+
abort "No image ID provided as first argument, please provide it." unless id
|
240
|
+
image = client.delete_image(id)
|
241
|
+
puts "Successfully deleted image #{id}."
|
242
|
+
print_meta(image) if verbose?
|
267
243
|
end
|
268
|
-
rescue NotFound => e
|
269
|
-
puts e.message
|
270
|
-
rescue => e
|
271
|
-
abort "Failure while executing 'delete':\n#{e.message}"
|
272
244
|
end
|
273
245
|
end
|
274
246
|
|
@@ -289,7 +261,7 @@ It's possible to sort and order results with the --sort (-s) and --dir (-d) opti
|
|
289
261
|
Examples:
|
290
262
|
$ visor brief --query 'architecture=i386'
|
291
263
|
$ visor brief --query 'architecture=i386&format=iso'
|
292
|
-
$ visor
|
264
|
+
$ visor brief --sort name --dir desc]
|
293
265
|
when :detail
|
294
266
|
%q[Usage: visor detail [options]
|
295
267
|
|
data/config/server.rb
CHANGED
@@ -12,3 +12,4 @@ logger.level = (log_level == 'INFO' ? 1 : 0)
|
|
12
12
|
config['vms'] = Visor::Image::Meta.new(host: meta_host, port: meta_port)
|
13
13
|
config['vas'] = Visor::Image::Auth.new(host: auth_host, port: auth_port)
|
14
14
|
config['configs'] = conf[:visor_store]
|
15
|
+
config['address'] = "#{conf[:visor_image][:bind_host]}:#{conf[:visor_image][:bind_port]}"
|
data/lib/image/auth.rb
CHANGED
@@ -1,99 +1,103 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'em-synchrony'
|
2
|
+
require 'em-synchrony/em-http'
|
3
3
|
require 'uri'
|
4
4
|
require 'json'
|
5
5
|
|
6
6
|
module Visor
|
7
7
|
module Image
|
8
8
|
|
9
|
-
# The
|
9
|
+
# The API for the VISOR Auth System (VAS) server. This class supports all user's manipulation operations.
|
10
10
|
#
|
11
|
-
# After Instantiate a
|
11
|
+
# After Instantiate a VAS API client object its possible to directly interact with the VAS server and its
|
12
12
|
# database backend.
|
13
13
|
#
|
14
14
|
class Auth
|
15
|
-
|
16
15
|
include Visor::Common::Exception
|
17
16
|
|
18
|
-
|
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
|
17
|
+
attr_reader :host, :port
|
24
18
|
|
25
19
|
def initialize(opts = {})
|
26
|
-
@host
|
27
|
-
@port
|
28
|
-
@ssl = opts[:ssl] || false
|
20
|
+
@host = opts[:host]
|
21
|
+
@port = opts[:port]
|
29
22
|
end
|
30
23
|
|
24
|
+
# Get information about all registered users.
|
25
|
+
#
|
26
|
+
# Options for filtering the returned results can be passed in.
|
27
|
+
#
|
28
|
+
# @option query [String] :<attribute_name> The user attribute value to filter returned results.
|
29
|
+
# @option query [String] :sort ("_id") The image attribute to sort returned results.
|
30
|
+
# @option query [String] :dir ("asc") The direction to sort results ("asc"/"desc").
|
31
|
+
#
|
32
|
+
# @return [Array] All user's accounts.
|
33
|
+
#
|
34
|
+
# @raise [NotFound] If there are no users registered on VAS.
|
35
|
+
#
|
31
36
|
def get_users(query = {})
|
32
|
-
|
33
|
-
|
34
|
-
do_request(request)
|
37
|
+
http = request.get path: '/users', query: query, head: get_headers
|
38
|
+
return_response(http)
|
35
39
|
end
|
36
40
|
|
41
|
+
# Get information about a specific user.
|
42
|
+
#
|
43
|
+
# @param access_key [String] The user access key (username).
|
44
|
+
#
|
45
|
+
# @return [Hash] The requested user account information.
|
46
|
+
#
|
47
|
+
# @raise [NotFound] If user was not found.
|
48
|
+
#
|
37
49
|
def get_user(access_key)
|
38
|
-
|
39
|
-
|
50
|
+
http = request.get path: "/users/#{access_key}", head: get_headers
|
51
|
+
return_response(http)
|
40
52
|
end
|
41
53
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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.
|
54
|
+
# Register a new user account on VAS and return its data.
|
55
|
+
#
|
56
|
+
# @param info [Hash] The user information.
|
63
57
|
#
|
64
|
-
# @
|
65
|
-
# @param response [Net::HTTPResponse] The response which contains the body to parse.
|
58
|
+
# @return [Hash] The already created user detailed information.
|
66
59
|
#
|
67
|
-
# @
|
68
|
-
#
|
60
|
+
# @raise [Invalid] If user data validation fails.
|
61
|
+
# @raise [NotFound] If user was not found after registered.
|
62
|
+
# @raise [ConflictError] access_key was already taken.
|
69
63
|
#
|
70
|
-
def
|
71
|
-
|
72
|
-
|
64
|
+
def post_user(info)
|
65
|
+
body = prepare_body(info)
|
66
|
+
http = request.post path: '/users', body: body, head: post_headers
|
67
|
+
return_response(http)
|
73
68
|
end
|
74
69
|
|
75
|
-
#
|
70
|
+
# Update an existing user information and return it.
|
71
|
+
#
|
72
|
+
# @param access_key [String] The wanted user access_key.
|
73
|
+
# @param info [Hash] The user information.
|
76
74
|
#
|
77
|
-
# @
|
75
|
+
# @return [Hash] The already updated user detailed information.
|
78
76
|
#
|
79
|
-
# @
|
77
|
+
# @raise [Invalid] If user data validation fails.
|
78
|
+
# @raise [NotFound] If user was not found.
|
80
79
|
#
|
81
|
-
def
|
82
|
-
|
80
|
+
def put_user(access_key, info)
|
81
|
+
body = prepare_body(info)
|
82
|
+
http = request.put path: "/users/#{access_key}", body: body, head: put_headers
|
83
|
+
return_response(http)
|
83
84
|
end
|
84
85
|
|
85
|
-
#
|
86
|
-
# headers for every request and additionally sets the 'content-type' header
|
87
|
-
# for POST and PUT requests.
|
86
|
+
# Delete an user and returns its information.
|
88
87
|
#
|
89
|
-
# @param
|
88
|
+
# @param access_key [String] The wanted user access_key.
|
90
89
|
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
90
|
+
# @return [Hash] The already deleted user detailed information.
|
91
|
+
#
|
92
|
+
# @raise [NotFound] If user was not found.
|
93
|
+
#
|
94
|
+
def delete_user(access_key)
|
95
|
+
http = request.delete path: "/users/#{access_key}", access_key: delete_headers
|
96
|
+
return_response(http)
|
95
97
|
end
|
96
98
|
|
99
|
+
private
|
100
|
+
|
97
101
|
# Generate a valid JSON request body for POST and PUT requests.
|
98
102
|
# It generates a JSON object encapsulated inside a :image key and then returns it.
|
99
103
|
#
|
@@ -108,7 +112,7 @@ module Visor
|
|
108
112
|
|
109
113
|
# Process requests by preparing its headers, launch them and assert or raise their response.
|
110
114
|
#
|
111
|
-
# @param
|
115
|
+
# @param http [EventMachine::HttpRequest] The request which will be launched.
|
112
116
|
#
|
113
117
|
# @return [String, Hash] If an error is raised, then it parses and returns its message,
|
114
118
|
# otherwise it properly parse and return the response body.
|
@@ -116,32 +120,59 @@ module Visor
|
|
116
120
|
# @raise [NotFound] If required image was not found (on a GET, PUT or DELETE request).
|
117
121
|
# @raise [Invalid] If image meta validation fails (on a POST or PUT request).
|
118
122
|
#
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
123
|
+
def return_response(http)
|
124
|
+
body = http.response
|
125
|
+
status = http.response_header.status.to_i
|
126
|
+
|
127
|
+
case status
|
128
|
+
when 0 then
|
129
|
+
raise InternalError, "VISOR Auth System server not found. Is it running?"
|
130
|
+
when 404 then
|
131
|
+
raise NotFound, parse(body)
|
132
|
+
when 400 then
|
133
|
+
raise Invalid, parse(body)
|
134
|
+
when 500 then
|
135
|
+
raise InternalError, parse(body)
|
127
136
|
else
|
128
|
-
parse(
|
137
|
+
parse(body)
|
129
138
|
end
|
130
139
|
end
|
131
140
|
|
141
|
+
def parse(body)
|
142
|
+
parsed = JSON.parse(body, symbolize_names: true)
|
143
|
+
parsed[:user] || parsed[:users] || parsed[:message]
|
144
|
+
end
|
145
|
+
|
132
146
|
# Generate a new HTTP or HTTPS connection based on initialization parameters.
|
133
147
|
#
|
134
|
-
# @return [
|
148
|
+
# @return [EventMachine::HttpRequest] A HTTP or HTTPS (not done yet) connection ready to use.
|
135
149
|
#
|
136
|
-
def
|
137
|
-
if @ssl
|
150
|
+
def request
|
151
|
+
#if @ssl
|
138
152
|
#TODO: ssl connection
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
153
|
+
#else
|
154
|
+
EventMachine::HttpRequest.new("http://#{@host}:#{@port}")
|
155
|
+
#end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Fill common header keys before each request. This sets the 'User-Agent' and 'Accept'
|
159
|
+
# headers for every request and additionally sets the 'content-type' header
|
160
|
+
# for POST and PUT requests.
|
161
|
+
#
|
162
|
+
def get_headers
|
163
|
+
{'User-Agent' => 'VISOR Image System',
|
164
|
+
'Accept' => 'application/json'}
|
143
165
|
end
|
144
166
|
|
167
|
+
def post_headers
|
168
|
+
{'User-Agent' => 'VISOR Image System',
|
169
|
+
'Accept' => 'application/json',
|
170
|
+
'content-type' => 'application/json'}
|
171
|
+
end
|
172
|
+
|
173
|
+
alias :delete_headers :get_headers
|
174
|
+
alias :put_headers :post_headers
|
145
175
|
end
|
146
176
|
end
|
147
177
|
end
|
178
|
+
|