visor-image 0.0.3 → 0.0.4
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.
- 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
|
+
|