occi-cli 4.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.travis.yml +43 -0
- data/.yardopts +1 -0
- data/AUTHORS +9 -0
- data/Gemfile +12 -0
- data/LICENSE +13 -0
- data/README.md +211 -0
- data/Rakefile +29 -0
- data/bin/occi +351 -0
- data/config/warble.rb +151 -0
- data/doc/macosx.md +27 -0
- data/ext/mkrf_conf.rb +34 -0
- data/lib/occi/cli/helpers.rb +149 -0
- data/lib/occi/cli/occi_opts.rb +388 -0
- data/lib/occi/cli/resource_output_factory.rb +95 -0
- data/lib/occi/cli/templates/compute.erb +18 -0
- data/lib/occi/cli/templates/network.erb +9 -0
- data/lib/occi/cli/templates/os_tpl.erb +7 -0
- data/lib/occi/cli/templates/resource_tpl.erb +7 -0
- data/lib/occi/cli/templates/storage.erb +8 -0
- data/lib/occi/cli/version.rb +5 -0
- data/lib/occi-cli.rb +9 -0
- data/occi-cli.gemspec +35 -0
- data/spec/occi/cli/helpers_spec.rb +9 -0
- data/spec/occi/cli/occi_opts_spec.rb +57 -0
- data/spec/occi/cli/resource_output_factory_spec.rb +9 -0
- data/spec/spec_helper.rb +13 -0
- metadata +240 -0
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'optparse'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require 'occi/cli/resource_output_factory'
|
6
|
+
|
7
|
+
module Occi::Cli
|
8
|
+
|
9
|
+
class OcciOpts
|
10
|
+
|
11
|
+
AUTH_METHODS = [:x509, :basic, :digest, :none].freeze
|
12
|
+
MEDIA_TYPES = ["application/occi+json", "application/occi+xml", "text/plain,text/occi", "text/plain"].freeze
|
13
|
+
ACTIONS = [:list, :describe, :create, :delete, :trigger].freeze
|
14
|
+
LOG_OUTPUTS = [:stdout, :stderr].freeze
|
15
|
+
|
16
|
+
def self.parse(args, test_env = false)
|
17
|
+
|
18
|
+
@@quiet = test_env
|
19
|
+
|
20
|
+
options = OpenStruct.new
|
21
|
+
|
22
|
+
options.debug = false
|
23
|
+
options.verbose = false
|
24
|
+
|
25
|
+
options.log = {}
|
26
|
+
options.log[:out] = STDERR
|
27
|
+
options.log[:level] = Occi::Log::WARN
|
28
|
+
|
29
|
+
options.filter = nil
|
30
|
+
options.dump_model = false
|
31
|
+
|
32
|
+
options.interactive = false
|
33
|
+
|
34
|
+
options.endpoint = "https://localhost:3300/"
|
35
|
+
|
36
|
+
options.auth = {}
|
37
|
+
options.auth[:type] = "none"
|
38
|
+
options.auth[:user_cert] = ENV['HOME'] + "/.globus/usercred.pem"
|
39
|
+
options.auth[:ca_path] = "/etc/grid-security/certificates"
|
40
|
+
options.auth[:username] = "anonymous"
|
41
|
+
options.auth[:ca_file] = nil
|
42
|
+
options.auth[:voms] = nil
|
43
|
+
|
44
|
+
options.output_format = :plain
|
45
|
+
|
46
|
+
options.mixins = nil
|
47
|
+
options.links = nil
|
48
|
+
options.attributes = nil
|
49
|
+
options.context_vars = nil
|
50
|
+
|
51
|
+
# TODO: change media type back to occi+json after the rOCCI-server update
|
52
|
+
#options.media_type = "application/occi+json"
|
53
|
+
options.media_type = "text/plain,text/occi"
|
54
|
+
|
55
|
+
opts = OptionParser.new do |opts|
|
56
|
+
opts.banner = %{Usage: occi [OPTIONS]
|
57
|
+
|
58
|
+
Examples:
|
59
|
+
occi --interactive --endpoint https://localhost:3300/ --auth x509
|
60
|
+
|
61
|
+
occi --endpoint https://localhost:3300/ --action list --resource os_tpl --auth x509
|
62
|
+
|
63
|
+
occi --endpoint https://localhost:3300/ --action list --resource resource_tpl --auth x509
|
64
|
+
|
65
|
+
occi --endpoint https://localhost:3300/ --action describe --resource os_tpl#debian6 --auth x509
|
66
|
+
|
67
|
+
occi --endpoint https://localhost:3300/ --action create --resource compute --mixin os_tpl#debian6 --mixin resource_tpl#small --attributes title="My rOCCI VM" --auth x509
|
68
|
+
|
69
|
+
occi --endpoint https://localhost:3300/ --action delete --resource /compute/65sd4f654sf65g4-s5fg65sfg465sfg-sf65g46sf5g4sdfg --auth x509}
|
70
|
+
|
71
|
+
opts.separator ""
|
72
|
+
opts.separator "Options:"
|
73
|
+
|
74
|
+
opts.on("-i",
|
75
|
+
"--interactive",
|
76
|
+
"Run as an interactive client without additional arguments") do |interactive|
|
77
|
+
options.interactive = interactive
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-e",
|
81
|
+
"--endpoint URI",
|
82
|
+
String,
|
83
|
+
"OCCI server URI, defaults to '#{options.endpoint}'") do |endpoint|
|
84
|
+
options.endpoint = URI(endpoint).to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on("-n",
|
88
|
+
"--auth METHOD",
|
89
|
+
AUTH_METHODS,
|
90
|
+
"Authentication method, defaults to '#{options.auth[:type]}'") do |auth|
|
91
|
+
options.auth[:type] = auth.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("-u",
|
95
|
+
"--username USER",
|
96
|
+
String,
|
97
|
+
"Username for basic or digest authentication, defaults to '#{options.auth[:username]}'") do |username|
|
98
|
+
options.auth[:username] = username
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on("-p",
|
102
|
+
"--password PASSWORD",
|
103
|
+
String,
|
104
|
+
"Password for basic, digest and x509 authentication") do |password|
|
105
|
+
options.auth[:password] = password
|
106
|
+
options.auth[:user_cert_password] = password
|
107
|
+
end
|
108
|
+
|
109
|
+
opts.on("-c",
|
110
|
+
"--ca-path PATH",
|
111
|
+
String,
|
112
|
+
"Path to CA certificates directory, defaults to '#{options.auth[:ca_path]}'") do |ca_path|
|
113
|
+
raise ArgumentError, "Path specified in --ca-path is not a directory!" unless File.directory? ca_path
|
114
|
+
raise ArgumentError, "Path specified in --ca-path is not readable!" unless File.readable? ca_path
|
115
|
+
|
116
|
+
options.auth[:ca_path] = ca_path
|
117
|
+
end
|
118
|
+
|
119
|
+
opts.on("-f",
|
120
|
+
"--ca-file PATH",
|
121
|
+
String,
|
122
|
+
"Path to CA certificates in a file") do |ca_file|
|
123
|
+
raise ArgumentError, "File specified in --ca-file is not a file!" unless File.file? ca_file
|
124
|
+
raise ArgumentError, "File specified in --ca-file is not readable!" unless File.readable? ca_file
|
125
|
+
|
126
|
+
options.auth[:ca_file] = ca_file
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on("-F",
|
130
|
+
"--filter CATEGORY",
|
131
|
+
String,
|
132
|
+
"Category type identifier to filter categories from model, must be used together with the -m option") do |filter|
|
133
|
+
options.filter = filter
|
134
|
+
end
|
135
|
+
|
136
|
+
opts.on("-x",
|
137
|
+
"--user-cred FILE",
|
138
|
+
String,
|
139
|
+
"Path to user's x509 credentials, defaults to '#{options.auth[:user_cert]}'") do |user_cred|
|
140
|
+
raise ArgumentError, "File specified in --user-cred is not a file!" unless File.file? user_cred
|
141
|
+
raise ArgumentError, "File specified in --user-cred is not readable!" unless File.readable? user_cred
|
142
|
+
|
143
|
+
options.auth[:user_cert] = user_cred
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on("-X",
|
147
|
+
"--voms",
|
148
|
+
"Using VOMS credentials; modifies behavior of the X509 authN module") do |voms|
|
149
|
+
|
150
|
+
options.auth[:voms] = true
|
151
|
+
end
|
152
|
+
|
153
|
+
opts.on("-y",
|
154
|
+
"--media-type MEDIA_TYPE",
|
155
|
+
MEDIA_TYPES,
|
156
|
+
"Media type for client <-> server communication, defaults to '#{options.media_type}'") do |media_type|
|
157
|
+
options.media_type = media_type
|
158
|
+
end
|
159
|
+
|
160
|
+
opts.on("-r",
|
161
|
+
"--resource RESOURCE",
|
162
|
+
String,
|
163
|
+
"Resource to be queried (e.g. network, compute, storage etc.), required") do |resource|
|
164
|
+
options.resource = resource
|
165
|
+
end
|
166
|
+
|
167
|
+
opts.on("-t",
|
168
|
+
"--attributes ATTRS",
|
169
|
+
Array,
|
170
|
+
"Comma separated attributes for new resources such as title=\"Name\", required") do |attributes|
|
171
|
+
options.attributes = {}
|
172
|
+
|
173
|
+
attributes.each do |attribute|
|
174
|
+
ary = /^(.+?)=(.+)$/.match(attribute).to_a.drop 1
|
175
|
+
raise ArgumentError, "Attributes must always contain ATTR=VALUE pairs!" if ary.length != 2
|
176
|
+
|
177
|
+
options.attributes[ary[0].to_sym] = ary[1]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
opts.on("-T",
|
182
|
+
"--context CTX_VARS",
|
183
|
+
Array,
|
184
|
+
"Comma separated context variables for new compute resources such as SSH_KEY=\"ssh-rsa dfsdf...adfdf== user@localhost\"") do |context|
|
185
|
+
options.context_vars = {}
|
186
|
+
|
187
|
+
context.each do |ctx|
|
188
|
+
ary = /^(.+?)=(.+)$/.match(ctx).to_a.drop 1
|
189
|
+
raise ArgumentError, "Context variables must always contain ATTR=VALUE pairs!" if ary.length != 2
|
190
|
+
|
191
|
+
options.context_vars[ary[0].to_sym] = ary[1]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
opts.on("-a",
|
196
|
+
"--action ACTION",
|
197
|
+
ACTIONS,
|
198
|
+
"Action to be performed on the resource, required") do |action|
|
199
|
+
options.action = action
|
200
|
+
end
|
201
|
+
|
202
|
+
opts.on("-M",
|
203
|
+
"--mixin NAME",
|
204
|
+
String,
|
205
|
+
"Type and name of the mixin as TYPE#NAME (e.g. os_tpl#monitoring, resource_tpl#medium)") do |mixin|
|
206
|
+
parts = mixin.split("#")
|
207
|
+
|
208
|
+
raise "Unknown mixin format! Use TYPE#NAME!" unless parts.length == 2
|
209
|
+
|
210
|
+
options.mixins = {} if options.mixins.nil?
|
211
|
+
options.mixins[parts[0]] = [] if options.mixins[parts[0]].nil?
|
212
|
+
options.mixins[parts[0]] << parts[1]
|
213
|
+
end
|
214
|
+
|
215
|
+
opts.on("-j",
|
216
|
+
"--link URI",
|
217
|
+
String,
|
218
|
+
"Link specified resource to the resource being created, only for action CREATE and resource COMPUTE") do |link|
|
219
|
+
link_relative_path = URI(link).path
|
220
|
+
|
221
|
+
raise ArgumentError, "Specified link URI is not valid!" unless link_relative_path.start_with? '/'
|
222
|
+
|
223
|
+
options.links = [] if options.links.nil?
|
224
|
+
options.links << link_relative_path
|
225
|
+
end
|
226
|
+
|
227
|
+
opts.on("-g",
|
228
|
+
"--trigger-action TRIGGER",
|
229
|
+
String,
|
230
|
+
"Action to be triggered on the resource") do |trigger_action|
|
231
|
+
options.trigger_action = trigger_action
|
232
|
+
end
|
233
|
+
|
234
|
+
opts.on("-l",
|
235
|
+
"--log-to OUTPUT",
|
236
|
+
LOG_OUTPUTS,
|
237
|
+
"Log to the specified device, defaults to 'STDERR'") do |log_to|
|
238
|
+
options.log[:out] = STDOUT if log_to == :stdout or log_to == :STDOUT
|
239
|
+
end
|
240
|
+
|
241
|
+
opts.on("-o",
|
242
|
+
"--output-format FORMAT",
|
243
|
+
Occi::Cli::ResourceOutputFactory.allowed_formats,
|
244
|
+
"Output format, defaults to human-readable 'plain'") do |output_format|
|
245
|
+
options.output_format = output_format
|
246
|
+
end
|
247
|
+
|
248
|
+
opts.on_tail("-m",
|
249
|
+
"--dump-model",
|
250
|
+
"Contact the endpoint and dump its model, cannot be used with the interactive mode") do |dump_model|
|
251
|
+
options.dump_model = dump_model
|
252
|
+
end
|
253
|
+
|
254
|
+
opts.on_tail("-d",
|
255
|
+
"--debug",
|
256
|
+
"Enable debugging messages") do |debug|
|
257
|
+
options.debug = debug
|
258
|
+
options.log[:level] = Occi::Log::DEBUG
|
259
|
+
end
|
260
|
+
|
261
|
+
opts.on_tail("-b",
|
262
|
+
"--verbose",
|
263
|
+
"Be more verbose, less intrusive than debug mode") do |verbose|
|
264
|
+
options.verbose = verbose
|
265
|
+
options.log[:level] = Occi::Log::INFO unless options.log[:level] == Occi::Log::DEBUG
|
266
|
+
end
|
267
|
+
|
268
|
+
opts.on_tail("-h",
|
269
|
+
"--help",
|
270
|
+
"Show this message") do
|
271
|
+
if @@quiet
|
272
|
+
exit true
|
273
|
+
else
|
274
|
+
puts opts
|
275
|
+
exit! true
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
opts.on_tail("-v",
|
280
|
+
"--version",
|
281
|
+
"Show version") do
|
282
|
+
if @@quiet
|
283
|
+
exit true
|
284
|
+
else
|
285
|
+
puts Occi::Cli::VERSION
|
286
|
+
exit! true
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
begin
|
292
|
+
opts.parse!(args)
|
293
|
+
rescue Exception => ex
|
294
|
+
if @@quiet
|
295
|
+
exit false
|
296
|
+
else
|
297
|
+
puts ex.message.capitalize
|
298
|
+
puts opts
|
299
|
+
exit!
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
check_restrictions options, opts
|
304
|
+
|
305
|
+
options
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def self.check_restrictions(options, opts)
|
311
|
+
if options.interactive && options.dump_model
|
312
|
+
if @@quiet
|
313
|
+
exit false
|
314
|
+
else
|
315
|
+
puts "You cannot use '--dump-model' and '--interactive' at the same time!"
|
316
|
+
puts opts
|
317
|
+
exit!
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
if !options.dump_model && options.filter
|
322
|
+
if @@quiet
|
323
|
+
exit false
|
324
|
+
else
|
325
|
+
puts "You cannot use '--filter' without '--dump-model'!"
|
326
|
+
puts opts
|
327
|
+
exit!
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
if options.voms && options.auth[:type] != "x509"
|
332
|
+
if @@quiet
|
333
|
+
exit false
|
334
|
+
else
|
335
|
+
puts "You cannot use '--voms' without '--auth x509'!"
|
336
|
+
puts opts
|
337
|
+
exit!
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
return if options.interactive || options.dump_model
|
342
|
+
|
343
|
+
mandatory = []
|
344
|
+
|
345
|
+
if options.action == :trigger
|
346
|
+
mandatory << :trigger_action
|
347
|
+
end
|
348
|
+
|
349
|
+
if options.action == :create
|
350
|
+
if !options.links.nil?
|
351
|
+
mandatory << :links
|
352
|
+
else
|
353
|
+
mandatory << :mixins
|
354
|
+
end
|
355
|
+
|
356
|
+
mandatory << :attributes
|
357
|
+
check_attrs = true
|
358
|
+
end
|
359
|
+
|
360
|
+
mandatory.concat [:resource, :action]
|
361
|
+
|
362
|
+
check_hash options, mandatory, opts
|
363
|
+
|
364
|
+
if check_attrs
|
365
|
+
mandatory = [:title]
|
366
|
+
check_hash options.attributes, mandatory, opts
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.check_hash(hash, mandatory, opts)
|
371
|
+
if !hash.is_a? Hash
|
372
|
+
hash = hash.marshal_dump
|
373
|
+
end
|
374
|
+
|
375
|
+
missing = mandatory.select{ |param| hash[param].nil? }
|
376
|
+
if !missing.empty?
|
377
|
+
if @@quiet
|
378
|
+
exit false
|
379
|
+
else
|
380
|
+
puts "Missing required arguments: #{missing.join(', ')}"
|
381
|
+
puts opts
|
382
|
+
exit!
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Occi::Cli
|
5
|
+
|
6
|
+
class ResourceOutputFactory
|
7
|
+
|
8
|
+
@@allowed_formats = [:json, :plain].freeze
|
9
|
+
@@allowed_resource_types = [:compute, :storage, :network, :os_tpl, :resource_tpl].freeze
|
10
|
+
@@allowed_data_types = [:locations, :resources].freeze
|
11
|
+
|
12
|
+
attr_reader :output_format
|
13
|
+
|
14
|
+
def initialize(output_format = :plain)
|
15
|
+
raise "Unsupported output format!" unless @@allowed_formats.include? output_format
|
16
|
+
@output_format = output_format
|
17
|
+
end
|
18
|
+
|
19
|
+
def format(data, data_type, resource_type)
|
20
|
+
raise "Data has to be in an array!" unless data.is_a? Array
|
21
|
+
raise "Unsupported resource type! Got: #{resource_type.to_s}" unless @@allowed_resource_types.include? resource_type
|
22
|
+
raise "Unsupported data type! Got: #{data_type.to_s}" unless @@allowed_data_types.include? data_type
|
23
|
+
|
24
|
+
# validate incoming data
|
25
|
+
if data_type == :resources
|
26
|
+
data.each do |occi_collection|
|
27
|
+
raise "I need the resources to be in a Collection!" unless occi_collection.respond_to? :as_json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# construct method name from data type and output format
|
32
|
+
method = (data_type.to_s + "_to_" + output_format.to_s).to_sym
|
33
|
+
|
34
|
+
ResourceOutputFactory.send method, data, resource_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.allowed_formats
|
38
|
+
@@allowed_formats
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.allowed_resource_types
|
42
|
+
@@allowed_resource_types
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.allowed_data_types
|
46
|
+
@@allowed_data_types
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.resources_to_json(occi_resources, resource_type)
|
50
|
+
# generate JSON document from an array of JSON objects
|
51
|
+
JSON.generate occi_resources
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.resources_to_plain(occi_resources, resource_type)
|
55
|
+
# using ERB templates for known resource and mixin types
|
56
|
+
file = File.expand_path("..", __FILE__) + '/templates/' + resource_type.to_s + ".erb"
|
57
|
+
template = ERB.new File.new(file).read
|
58
|
+
|
59
|
+
formatted_output = ""
|
60
|
+
|
61
|
+
occi_resources.each do |occi_resource|
|
62
|
+
json_resource = occi_resource.as_json
|
63
|
+
formatted_output << template.result(binding) unless json_resource.nil? || json_resource.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
formatted_output
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.locations_to_json(url_locations, resource_type)
|
70
|
+
# give all locatios a common key and convert to JSON
|
71
|
+
locations = { resource_type => [] }
|
72
|
+
|
73
|
+
url_locations.each do |location|
|
74
|
+
location = location.split("/").last if [:os_tpl, :resource_tpl].include? resource_type
|
75
|
+
locations[resource_type] << location
|
76
|
+
end
|
77
|
+
|
78
|
+
locations.to_json
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.locations_to_plain(url_locations, resource_type)
|
82
|
+
# just an attempt to make the array more readable
|
83
|
+
output = "\n" + resource_type.to_s.capitalize + " locations:\n"
|
84
|
+
|
85
|
+
url_locations.each do |location|
|
86
|
+
location = location.split("/").last if [:os_tpl, :resource_tpl].include? resource_type
|
87
|
+
output << "\t" << location << "\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
output
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%# We always get a JSON %>
|
2
|
+
COMPUTE:
|
3
|
+
ID: <%= json_resource.resources.first.attributes.occi.core.id %>
|
4
|
+
TITLE: <%= json_resource.resources.first.attributes.occi.core.title %>
|
5
|
+
STATE: <%= json_resource.resources.first.attributes.occi.compute.state %>
|
6
|
+
LINKS:
|
7
|
+
<% if json_resource.links %><% json_resource.links.each do |link| %>
|
8
|
+
LINK "<%= link.kind %>":
|
9
|
+
ID: <%= link.attributes.occi.core.id %>
|
10
|
+
TITLE: <%= link.attributes.occi.core.title %>
|
11
|
+
TARGET: <%= link.target %>
|
12
|
+
<% if link.attributes.occi.networkinterface %>
|
13
|
+
IP ADDRESS: <%= link.attributes.occi.networkinterface.address %>
|
14
|
+
MAC ADDRESS: <%= link.attributes.occi.networkinterface.mac %>
|
15
|
+
<% elsif link.attributes.occi.storagelink %>
|
16
|
+
MOUNT POINT: <%= link.attributes.occi.storagelink.deviceid %>
|
17
|
+
<% end %>
|
18
|
+
<% end %><% end %>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<%# We always get a JSON %>
|
2
|
+
NETWORK:
|
3
|
+
ID: <%= json_resource.resources.first.attributes.occi.core.id %>
|
4
|
+
TITLE: <%= json_resource.resources.first.attributes.occi.core.title %>
|
5
|
+
STATE: <%= json_resource.resources.first.attributes.occi.network.state %>
|
6
|
+
ALLOCATION: <%= json_resource.resources.first.attributes.occi.network.allocation %>
|
7
|
+
ADDRESS: <%= json_resource.resources.first.attributes.occi.network.address %>
|
8
|
+
|
9
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%# We always get a JSON %>
|
2
|
+
STORAGE:
|
3
|
+
ID: <%= json_resource.resources.first.attributes.occi.core.id %>
|
4
|
+
TITLE: <%= json_resource.resources.first.attributes.occi.core.title %>
|
5
|
+
STATE: <%= json_resource.resources.first.attributes.occi.storage.state%>
|
6
|
+
DESCRIPTION: <%= json_resource.resources.first.attributes.occi.core.summary %>
|
7
|
+
|
8
|
+
|
data/lib/occi-cli.rb
ADDED
data/occi-cli.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
4
|
+
|
5
|
+
require 'occi/cli/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |gem|
|
8
|
+
gem.name = "occi-cli"
|
9
|
+
gem.version = Occi::Cli::VERSION
|
10
|
+
gem.authors = ["Florian Feldhaus","Piotr Kasprzak", "Boris Parak"]
|
11
|
+
gem.email = ["florian.feldhaus@gwdg.de", "piotr.kasprzak@gwdg.de", "xparak@mail.muni.cz"]
|
12
|
+
gem.description = %q{OCCI is a collection of classes to simplify the implementation of the Open Cloud Computing API in Ruby}
|
13
|
+
gem.summary = %q{OCCI toolkit}
|
14
|
+
gem.homepage = 'https://github.com/gwdg/rOCCI-cli'
|
15
|
+
|
16
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
gem.files = `git ls-files`.split("\n")
|
18
|
+
gem.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.extensions = 'ext/mkrf_conf.rb'
|
21
|
+
|
22
|
+
gem.add_dependency 'occi-api'
|
23
|
+
gem.add_dependency 'highline'
|
24
|
+
|
25
|
+
gem.add_development_dependency "rspec"
|
26
|
+
gem.add_development_dependency "rake"
|
27
|
+
gem.add_development_dependency "builder"
|
28
|
+
gem.add_development_dependency "simplecov"
|
29
|
+
gem.add_development_dependency "yard"
|
30
|
+
gem.add_development_dependency "yard-sinatra"
|
31
|
+
gem.add_development_dependency "yard-rspec"
|
32
|
+
gem.add_development_dependency "yard-cucumber"
|
33
|
+
|
34
|
+
gem.required_ruby_version = ">= 1.8.7"
|
35
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Occi
|
2
|
+
module Cli
|
3
|
+
describe OcciOpts do
|
4
|
+
|
5
|
+
it "terminates without arguments" do
|
6
|
+
expect{Occi::Cli::OcciOpts.parse [], true}.to raise_error()
|
7
|
+
end
|
8
|
+
|
9
|
+
it "terminates when it encounters an unknown argument" do
|
10
|
+
expect{Occi::Cli::OcciOpts.parse ["--non-existent", "fake"], true}.to raise_error()
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses minimal number of required arguments without errors" do
|
14
|
+
expect{Occi::Cli::OcciOpts.parse ["--resource", "compute", "--action", "list"], true}.not_to raise_error()
|
15
|
+
end
|
16
|
+
|
17
|
+
it "parses resource and action arguments without errors" do
|
18
|
+
begin
|
19
|
+
parsed = Occi::Cli::OcciOpts.parse ["--resource", "compute", "--action", "list"], true
|
20
|
+
rescue SystemExit
|
21
|
+
fail
|
22
|
+
end
|
23
|
+
|
24
|
+
parsed.should_not be_nil
|
25
|
+
|
26
|
+
parsed.resource.should_not be_nil
|
27
|
+
parsed.action.should_not be_nil
|
28
|
+
|
29
|
+
parsed.resource.should eq("compute")
|
30
|
+
parsed.action.should eq(:list)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "shows version"
|
34
|
+
|
35
|
+
it "shows help"
|
36
|
+
|
37
|
+
it "doesn't accept unsupported authN methods"
|
38
|
+
|
39
|
+
it "doesn't accept an invalid URI as an endpoint"
|
40
|
+
|
41
|
+
it "doesn't accept an invalid URI as a resource"
|
42
|
+
|
43
|
+
it "doesn't accept unsupported actions"
|
44
|
+
|
45
|
+
it "doesn't allow create actions without attributes present"
|
46
|
+
|
47
|
+
it "doesn't allow create actions without either link(s) or mixin(s)"
|
48
|
+
|
49
|
+
it "doesn't allow create actions without attribute 'title' present"
|
50
|
+
|
51
|
+
it "doesn't accept malformed mixins"
|
52
|
+
|
53
|
+
it "doesn't accept malformed attributes"
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'occi-cli'
|
3
|
+
|
4
|
+
# enable coverage reports
|
5
|
+
if ENV['COVERAGE']
|
6
|
+
require 'simplecov'
|
7
|
+
SimpleCov.start
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec.configure do |c|
|
11
|
+
# in RSpec 3 this will no longer be necessary.
|
12
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
end
|