occi 2.5.19 → 3.0.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/.gitignore +4 -0
  2. data/.travis.yml +3 -1
  3. data/AUTHORS +4 -3
  4. data/Gemfile +9 -0
  5. data/Gemfile.lock +64 -21
  6. data/README.md +19 -14
  7. data/Rakefile +12 -8
  8. data/Test Results - discovery_interface.html +862 -0
  9. data/bin/occi +333 -83
  10. data/examples/dsl_example.rb +6 -6
  11. data/examples/x509auth_example.rb +6 -6
  12. data/features/common/step_definitions/common_steps.rb +32 -0
  13. data/features/occi/core/create/create.feature +17 -0
  14. data/features/occi/core/create/step_definitions/create_steps.rb +0 -0
  15. data/features/occi/core/delete/delete.feature +14 -0
  16. data/features/occi/core/delete/step_definitions/delete_steps.rb +0 -0
  17. data/features/occi/core/discovery_interface/discovery_interface.feature +35 -0
  18. data/features/occi/core/discovery_interface/step_definitions/discovery_interface_steps.rb +19 -0
  19. data/features/occi/core/miscellaneous/miscellaneous.feature +14 -0
  20. data/features/occi/core/miscellaneous/step_definitions/miscellaneous_steps.rb +0 -0
  21. data/features/occi/core/read/read.feature +14 -0
  22. data/features/occi/core/read/step_definitions/read_steps.rb +0 -0
  23. data/features/occi/core/update/step_definitions/update_steps.rb +0 -0
  24. data/features/occi/core/update/update.feature +14 -0
  25. data/features/occi/infrastructure/create/create.feature +14 -0
  26. data/features/occi/infrastructure/create/step_definitions/create_steps.rb +0 -0
  27. data/features/support/env.rb +4 -0
  28. data/lib/occi.rb +29 -3
  29. data/lib/occi/api/client/client_amqp.rb +756 -0
  30. data/lib/occi/api/client/client_http.rb +922 -0
  31. data/lib/occi/api/client/http/httparty_fix.rb +53 -0
  32. data/lib/occi/api/client/http/net_http_fix.rb +46 -0
  33. data/lib/occi/api/dsl.rb +77 -73
  34. data/lib/occi/bin/helpers.rb +91 -0
  35. data/lib/occi/bin/occi_opts.rb +251 -0
  36. data/lib/occi/bin/resource_output_factory.rb +90 -0
  37. data/lib/occi/bin/templates/compute.erb +15 -0
  38. data/lib/occi/bin/templates/network.erb +11 -0
  39. data/lib/occi/bin/templates/os_tpl.erb +9 -0
  40. data/lib/occi/bin/templates/resource_tpl.erb +9 -0
  41. data/lib/occi/bin/templates/storage.erb +10 -0
  42. data/lib/occi/collection.rb +122 -25
  43. data/lib/occi/core.rb +18 -9
  44. data/lib/occi/core/action.rb +20 -4
  45. data/lib/occi/core/action_instance.rb +24 -0
  46. data/lib/occi/core/actions.rb +22 -0
  47. data/lib/occi/core/attribute_properties.rb +33 -84
  48. data/lib/occi/core/attributes.rb +32 -14
  49. data/lib/occi/core/categories.rb +46 -0
  50. data/lib/occi/core/category.rb +94 -20
  51. data/lib/occi/core/entities.rb +50 -0
  52. data/lib/occi/core/entity.rb +130 -89
  53. data/lib/occi/core/kind.rb +28 -35
  54. data/lib/occi/core/kinds.rb +22 -0
  55. data/lib/occi/core/link.rb +43 -40
  56. data/lib/occi/core/links.rb +34 -0
  57. data/lib/occi/core/mixin.rb +28 -23
  58. data/lib/occi/core/mixins.rb +22 -0
  59. data/lib/occi/core/related.rb +20 -0
  60. data/lib/occi/core/resource.rb +40 -40
  61. data/lib/occi/core/resources.rb +14 -0
  62. data/lib/occi/infrastructure.rb +27 -0
  63. data/lib/occi/infrastructure/compute.rb +159 -0
  64. data/lib/occi/infrastructure/network.rb +131 -0
  65. data/lib/occi/infrastructure/network/ipnetwork.rb +34 -0
  66. data/lib/occi/infrastructure/networkinterface.rb +124 -0
  67. data/lib/occi/infrastructure/networkinterface/ipnetworkinterface.rb +34 -0
  68. data/lib/occi/infrastructure/os_tpl.rb +19 -0
  69. data/lib/occi/infrastructure/resource_tpl.rb +19 -0
  70. data/lib/occi/infrastructure/storage.rb +96 -0
  71. data/lib/occi/infrastructure/storagelink.rb +73 -0
  72. data/lib/occi/log.rb +6 -2
  73. data/lib/occi/model.rb +38 -70
  74. data/lib/occi/parser.rb +108 -88
  75. data/lib/occi/version.rb +2 -2
  76. data/lib/occiantlr/OCCIANTLR.g +6 -5
  77. data/lib/occiantlr/OCCIANTLRLexer.rb +52 -52
  78. data/lib/occiantlr/OCCIANTLRParser.rb +678 -569
  79. data/lib/occiantlr/README.md +1 -1
  80. data/occi.gemspec +2 -1
  81. data/spec/cassettes/client_http_text_plain.yml +1066 -0
  82. data/spec/occi/api/client/client_amqp_spec.rb +148 -0
  83. data/spec/occi/api/client/client_http_0.5_spec.rb +292 -0
  84. data/spec/occi/api/client/client_http_spec.rb +259 -0
  85. data/spec/occi/api/dsl_spec.rb +0 -0
  86. data/spec/occi/collection_spec.rb +23 -10
  87. data/spec/occi/core/categories_spec.rb +30 -0
  88. data/spec/occi/core/category_spec.rb +41 -0
  89. data/spec/occi/core/entity_spec.rb +52 -0
  90. data/spec/occi/core/resource_spec.rb +21 -0
  91. data/spec/occi/infrastructure/compute_spec.rb +32 -0
  92. data/spec/occi/log_spec.rb +10 -10
  93. data/spec/occi/model_spec.rb +24 -24
  94. data/spec/occi/parser_spec.rb +89 -39
  95. data/spec/occi/test.json +22 -58
  96. data/spec/occiantlr/parser_spec.rb +5 -7
  97. data/spec/spec_helper.rb +13 -3
  98. metadata +116 -19
  99. data/etc/model/infrastructure/compute.json +0 -108
  100. data/etc/model/infrastructure/ipnetwork.json +0 -40
  101. data/etc/model/infrastructure/ipnetworkinterface.json +0 -40
  102. data/etc/model/infrastructure/network.json +0 -55
  103. data/etc/model/infrastructure/networkinterface.json +0 -38
  104. data/etc/model/infrastructure/os_template.json +0 -9
  105. data/etc/model/infrastructure/resource_template.json +0 -9
  106. data/etc/model/infrastructure/storage.json +0 -72
  107. data/etc/model/infrastructure/storagelink.json +0 -38
  108. data/lib/occi/api/client.rb +0 -596
  109. data/lib/occi/client/occiopts.rb +0 -146
  110. data/spec/occi/client_spec.rb +0 -12
data/bin/occi CHANGED
@@ -20,127 +20,377 @@ require 'pp'
20
20
  require 'highline/import'
21
21
  require 'openssl'
22
22
 
23
- # OcciOpts is not part of the occi required above
24
- require 'occi/client/occiopts'
23
+ # OcciOpts, ResourceOutputFactory and Helper are not part of the occi core required above
24
+ require 'occi/bin/occi_opts'
25
+ require 'occi/bin/resource_output_factory'
26
+ require 'occi/bin/helpers'
25
27
 
26
- extend OCCI::DSL
28
+ extend Occi::Api::Dsl
27
29
 
28
30
  # get arguments and validate/parse them to an ostruct
29
- options = OcciOpts.parse ARGV
31
+ options = Occi::Bin::OcciOpts.parse ARGV
30
32
 
31
- # initiate the Logger
32
- logger = OCCI::Log.new(options.log[:out])
33
+ # initialize logger
34
+ logger = Occi::Log.new(options.log[:out])
33
35
  logger.level = options.log[:level]
34
36
  options.log[:logger] = logger
35
37
 
36
- OCCI::Log.info "Starting OCCI client ..."
37
- OCCI::Log.debug "Options: #{options}"
38
+ # initialize output factory
39
+ output = Occi::Bin::ResourceOutputFactory.new options.output_format
38
40
 
39
- # running with an empty password?
40
- if options.auth[:password].nil? or options.auth[:user_cert_password].nil?
41
- options.auth[:password] = options.auth[:user_cert_password] = ask("Enter password: ") { |q| q.echo = false } unless options.auth[:type] == "none"
41
+ Occi::Log.info "Starting OCCI client ..."
42
+ Occi::Log.debug "Options: #{options}"
43
+
44
+ # to make the interactive mode completely self-sufficient
45
+ # ask for endpoint and auth method (provide defaults)
46
+ if options.interactive
47
+ Occi::Log.debug "Checking for endpoint and auth changes ..."
48
+
49
+ options.endpoint = ask("What endpoint should I use? ") {
50
+ |q| q.default = options.endpoint
51
+ }
52
+
53
+ # separate menus
54
+ say "\n"
55
+
56
+ choose do |menu|
57
+ menu.prompt = "Which auth method should I use? "
58
+
59
+ Occi::Bin::OcciOpts::AUTH_METHODS.each do |auth_m|
60
+ menu.choice(auth_m) { options.auth[:type] = auth_m.to_s }
61
+ end
62
+ end
42
63
  end
43
64
 
65
+ # running with an empty password, we should ask the user for one
66
+ # if auth method is not "none"
67
+ if options.auth[:password].nil? or options.auth[:user_cert_password].nil? or options.auth[:token].nil?
68
+ Occi::Log.debug "Password is not set, asking for it now ..."
69
+
70
+ say("\n")
71
+
72
+ options.auth[:user_cert_password] = ask("Enter password: ") {
73
+ |q| q.echo = false
74
+ } unless options.auth[:type] == "none"
75
+
76
+ options.auth[:password] = options.auth[:user_cert_password]
77
+ end
78
+
79
+ # establish a connection before entering the loop
80
+ # this will considerably speed-up the interactive mode and has
81
+ # no effect on the non-interactive one
44
82
  begin
83
+ Occi::Log.info "Establishing a connection to #{options.endpoint} ..."
84
+ connect :http, options.endpoint, options.auth, options.log, true, options.media_type
85
+ rescue OpenSSL::SSL::SSLError => ssl_ex
86
+ # generic SSL error raised whilst establishing a connection
87
+ # possibly an untrusted server cert or invalid user credentials
88
+ Occi::Log.error "An SSL error occurred! Please, make sure your credentials " \
89
+ "are valid and recognized by the endpoint! Message: #{ssl_ex.message}"
45
90
 
46
- OCCI::Log.info "Establishing a connection to #{options.endpoint} ..."
47
- connect options.endpoint, options.auth, options.log, true, options.media_type
91
+ raise ssl_ex if options.debug
92
+ exit!
93
+ rescue OpenSSL::PKey::RSAError => key_ex
94
+ # generic X509 error raised whilst reading user's credentials from a file
95
+ # possibly a wrong password or mangled/unsupported credential format
96
+ Occi::Log.error "An X509 error occurred! Please, make sure you are using the " \
97
+ "right password and the file contains both your certificate " \
98
+ "and your private key! Message: #{key_ex.message}"
48
99
 
49
- OCCI::Log.info "Executing action #{options.action.to_s} on #{options.resource} ..."
50
- case options.action
51
- when :list
100
+ raise key_ex if options.debug
101
+ exit!
102
+ rescue Errno::ECONNREFUSED
103
+ # the remote server has refused our connection attempt(s)
104
+ # there is nothing we can do ...
105
+ Occi::Log.error "Connection refused!"
106
+ exit!
107
+ rescue Exception => ex
108
+ # something went wrong during the execution
109
+ # hide the stack trace in non-debug modes
110
+ Occi::Log.error "An error occurred! Message: #{ex.message}"
52
111
 
53
- if resource_types.include? options.resource or resource_type_identifiers.include? options.resource
54
- pp list options.resource
55
- elsif mixin_types.include? options.resource
56
- pp mixins options.resource
57
- elsif mixin_type_identifiers.include? options.resource
58
- pp mixins options.resource.split('#').last
59
- else
60
- puts "Unknown resource #{options.resource}, there is nothing to list here!"
61
- end
112
+ raise ex if options.debug
113
+ exit!
114
+ end
62
115
 
63
- when :describe
116
+ # dump the occi model provided by the server and exit
117
+ if options.dump_model
64
118
 
65
- if resource_types.include? options.resource or resource_type_identifiers.include? options.resource or options.resource.start_with? options.endpoint
66
- pp describe options.resource
67
- elsif mixin_types.include? options.resource
68
- mixins(options.resource).each do |mxn|
69
- mxn = mxn.split("#").last
70
- pp mixin(mxn, options.resource, true)
71
- end
72
- elsif mixin_type_identifiers.include? options.resource
73
- mixins(options.resource.split('#').last).each do |mxn|
74
- mxn = mxn.split("#").last
75
- pp mixin(mxn, options.resource, true)
119
+ if !model.respond_to? :instance_variables
120
+ puts "Your Ruby doesn't support 'instance_variables' calls!"
121
+ exit!
122
+ end
123
+
124
+ # iterate through available instance variables
125
+ model.instance_variables.each do |inst_var_sym|
126
+ puts "#"*79
127
+ puts "Dumping #{inst_var_sym.to_s}:"
128
+
129
+ inst_var = model.instance_variable_get(inst_var_sym)
130
+ next unless inst_var.respond_to? :each
131
+
132
+ # iterate through collection elements
133
+ inst_var.each do |coll_elm|
134
+ # respect user's output-format preferences
135
+ if options.output_format == :json and coll_elm.respond_to? :as_json
136
+ puts "\n"
137
+ pp coll_elm.as_json
138
+ puts "\n"
139
+ elsif coll_elm.respond_to? :to_string
140
+ puts "\n#{coll_elm.to_string}\n"
141
+ else
142
+ puts "\n#{coll_elm.inspect}\n"
76
143
  end
77
- elsif mixins.include? options.resource
78
- mxn_type,mxn = options.resource.split("/").last.split('#')
79
- pp mixin(mxn, mxn_type, true)
80
- elsif mixin_types.include? options.resource.split('#').first
81
- mxn_type,mxn = options.resource.split('#')
82
- pp mixin(mxn, mxn_type, true)
83
- else
84
- puts "Unknown resource #{options.resource}, there is nothing to describe here!"
85
144
  end
86
145
 
87
- when :create
88
-
89
- if resource_types.include? options.resource or resource_type_identifiers.include? options.resource
90
- raise "Not yet implemented!" unless options.resource.include? "compute"
91
-
92
- res = resource options.resource
93
-
94
- OCCI::Log.debug "Creating #{options.resource}:\n#{res.inspect}"
95
- OCCI::Log.debug "with mixins:#{options.mixin}"
96
-
97
- options.mixin.keys.each do |type|
98
- OCCI::Log.debug "Adding mixins of type #{type} to #{options.resource}"
99
- options.mixin[type].each do |name|
100
- mxn = mixin name, type
101
-
102
- raise "Unknown mixin #{type}##{name}, stopping here!" if mxn.nil?
103
- OCCI::Log.debug "Adding mixin #{mxn} to #{options.resource}"
104
- res.mixins << mxn
146
+ #
147
+ puts "#"*79
148
+ end
149
+
150
+ exit! true
151
+ end
152
+
153
+ # start of the main loop, this part of the code is responsible for
154
+ # interactive menus and actions execution
155
+ # this block will run while options.interactive is True, i.e.
156
+ # only once in the non-interactive mode
157
+ begin
158
+
159
+ # display menus in the interactive mode, there are two main variables
160
+ # that need to be set here: options.action and options.resource
161
+ if options.interactive
162
+
163
+ Occi::Log.debug "Running in an interactive mode ..."
164
+
165
+ # reset action and resource, just to be sure
166
+ options.action = nil
167
+ options.resource = nil
168
+
169
+ # offer just the resource types we will be able to process
170
+ menu_resources = Occi::Bin::ResourceOutputFactory.allowed_resource_types
171
+
172
+ # separate menus from each other
173
+ say("\n")
174
+
175
+ # first we need an action
176
+ choose do |menu|
177
+ menu.prompt = "Please, choose an action: "
178
+
179
+ # list action requires a resource type
180
+ menu.choice(:list) {
181
+ options.action = :list
182
+
183
+ # separate menus from each other
184
+ say("\n")
185
+
186
+ choose do |list_menu|
187
+ list_menu.prompt = "Which one should I list? "
188
+
189
+ menu_resources.each do |menu_resource|
190
+ list_menu.choice(menu_resource) { options.resource = menu_resource.to_s }
191
+ end
192
+
193
+ list_menu.choice(:back) { options.action = :skip }
105
194
  end
106
- end
195
+ }
196
+
197
+ # describe action requires a resource type or a resource location
198
+ menu.choice(:describe) {
199
+ options.action = :describe
107
200
 
108
- #TODO: set other attributes
109
- res.attributes.occi!.core!.title = options.resource_title
201
+ # separate menus from each other
202
+ say("\n")
110
203
 
111
- puts create res
204
+ # display the resource types first
205
+ choose do |describe_menu|
206
+ describe_menu.prompt = "Which one should I describe? "
207
+
208
+ menu_resources.each do |menu_resource|
209
+ describe_menu.choice(menu_resource) {
210
+ options.resource = menu_resource.to_s
211
+
212
+ # separate menus from each other
213
+ say("\n")
214
+
215
+ # display available resources for this resource type
216
+ choose do |describe_menu_spec|
217
+ describe_menu_spec.prompt = "Should I describe a specific resource? "
218
+
219
+ describe_menu_spec.choice(:all) {
220
+ # leave options.resource set to compute/network/storage
221
+ }
222
+
223
+ found = helper_list options
224
+ found.each do |found_resource|
225
+ describe_menu_spec.choice(found_resource.to_sym) { options.resource = found_resource }
226
+ end
227
+
228
+ describe_menu_spec.choice(:back) { options.action = :skip }
229
+ end unless menu_resource.to_s.reverse.start_with? "lpt_"
230
+ }
231
+ end
232
+
233
+ describe_menu.choice(:back) { options.action = :skip }
234
+ end
235
+ }
236
+
237
+ # create action requires resource type, resource title
238
+ # and optionally mixins (usually two, os_tpl and resource_tpl)
239
+ menu.choice(:create) {
240
+ options.action = :create
241
+
242
+ # separate menus from each other
243
+ say("\n")
244
+
245
+ # display the resource types
246
+ choose do |create_menu|
247
+ create_menu.prompt = "Which one should I create? "
248
+
249
+ menu_resources.each do |menu_resource|
250
+ create_menu.choice(menu_resource) {
251
+ options.resource = menu_resource.to_s
252
+ } unless menu_resource.to_s.reverse.start_with? "lpt_"
253
+ end
254
+
255
+ create_menu.choice(:back) { options.action = :skip }
256
+ end
257
+
258
+ # if the user didn't choose "Back", ask for details
259
+ # TODO: currently only COMPUTE is supported
260
+ if options.action == :create
261
+ options.resource_title = ask("What name should I give to the new resource? ")
262
+ number_of_mixins = ask("How many mixins do you wish me to mix into this resource? ",
263
+ Integer) { |q| q.in = 0..2 }
264
+
265
+ options.mixin = {}
266
+ (1..number_of_mixins).each do |mixin_number|
267
+ mixin = ask("What mixin should I mix in? ") { |q| q.validate = /\A\w+#\w+\Z/ }
268
+ parts = mixin.split("#")
269
+
270
+ options.mixin[parts[0]] = [] if options.mixin[parts[0]].nil?
271
+ options.mixin[parts[0]] << parts[1]
272
+ end
273
+ end
274
+ }
275
+
276
+ # delete action requires a resource location
277
+ menu.choice(:delete) {
278
+ options.action = :delete
279
+
280
+ # separate menus from each other
281
+ say("\n")
282
+
283
+ # display the resource types first
284
+ choose do |delete_menu|
285
+ delete_menu.prompt = "Please, choose a resource type: "
286
+
287
+ menu_resources.each do |menu_resource|
288
+ delete_menu.choice(menu_resource) {
289
+
290
+ # separate menus from each other
291
+ say("\n")
292
+
293
+ # display available resources for this type
294
+ choose do |delete_menu_spec|
295
+ delete_menu_spec.prompt = "Which resource should I delete? "
296
+
297
+ opts = OpenStruct.new
298
+ opts.resource = menu_resource.to_s
299
+
300
+ found = helper_list opts
301
+ found.each do |found_resource|
302
+ delete_menu_spec.choice(found_resource.to_sym) { options.resource = found_resource }
303
+ end
304
+
305
+ delete_menu_spec.choice(:back) { options.action = :skip }
306
+ end unless menu_resource.to_s.reverse.start_with? "lpt_"
307
+ } unless menu_resource.to_s.reverse.start_with? "lpt_"
308
+ end
309
+
310
+ delete_menu.choice(:back) { options.action = :skip }
311
+ end
312
+ }
313
+
314
+ # TODO: trigger is not yet implemented
315
+ menu.choice(:trigger) {
316
+ options.action = :skip
317
+ say("Not implemented yet!")
318
+ }
319
+
320
+ # refresh the OCCI model structures without exiting/re-launching
321
+ # the client, useful when adding new os_tpls/resource_tpls on
322
+ # the server
323
+ menu.choice(:refresh) {
324
+ options.action = :refresh
325
+ }
326
+
327
+ # enough is enough, bye!
328
+ menu.choice(:quit) {
329
+ say("Good bye!")
330
+ exit!(true)
331
+ }
332
+ end
333
+ end
334
+
335
+ Occi::Log.info "Executing action #{options.action.to_s} on #{options.resource} ..."
336
+
337
+ # call the appropriate helper and then format its output
338
+ case options.action
339
+ when :list
340
+ found = helper_list options
341
+
342
+ valid = Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? options.resource.to_sym
343
+ Occi::Log.error "Not printing, the resource type is not supported!" unless valid
344
+
345
+ puts output.format(found, :locations, options.resource.to_sym) if valid
346
+ when :describe
347
+ found = helper_describe options
348
+
349
+ if options.resource.start_with? options.endpoint
350
+ resource_type = options.resource.split("/")[3].to_sym
351
+ elsif mixin_types.include? options.resource.split('#').first
352
+ resource_type = options.resource.split('#').first.to_sym
112
353
  else
113
- puts "Unknown resource #{options.resource}, there is nothing to create here!"
354
+ resource_type = options.resource.to_sym
114
355
  end
115
356
 
116
- when :delete
117
- result = delete options.resource
357
+ valid = Occi::Bin::ResourceOutputFactory.allowed_resource_types.include? resource_type
358
+ Occi::Log.error "Not printing, the resource type is not supported!" unless valid
118
359
 
360
+ puts output.format(found, :resources, resource_type) if valid
361
+ when :create
362
+ location = helper_create options
363
+ puts location
364
+ when :delete
365
+ result = helper_delete options
366
+
119
367
  if result
120
368
  puts "Resource #{options.resource} successfully removed!"
121
369
  else
122
370
  puts "Failed to remove resource #{options.resource}!"
123
371
  end
124
372
  when :trigger
125
- raise "Not yet implemented!"
373
+ helper_trigger options
374
+ when :refresh
375
+ refresh
376
+ when :skip
377
+ Occi::Log.info "Skipping this action, probably not implemented yet!"
126
378
  else
127
379
  raise "Unknown action [#{options.action}]!"
128
380
  end
129
381
 
130
- rescue OpenSSL::SSL::SSLError => ssl_ex
131
- OCCI::Log.error "An SSL Auth Error occurred! Please, make sure your credentials are valid and the endpoind is trusted! Message: #{ssl_ex.message}"
132
- raise ssl_ex if options.debug
133
- exit!
134
- rescue OpenSSL::PKey::RSAError => key_ex
135
- OCCI::Log.error "An X509 Error occurred! Please, check your credentials! Message: #{key_ex.message}"
136
- raise key_ex if options.debug
382
+ rescue Errno::ECONNREFUSED
383
+ # remote server refused our connection attempt(s)
384
+ # even though initial connect was successful
385
+ Occi::Log.error "Connection refused!"
137
386
  exit!
138
387
  rescue Exception => ex
139
- OCCI::Log.error "An error occurred! Message: #{ex.message}"
388
+ # something went wrong during the execution
389
+ # hide the stack trace in non-debug modes
390
+ Occi::Log.error "An error occurred! Message: #{ex.message}"
391
+
140
392
  raise ex if options.debug
141
393
  exit!
142
- end
143
-
144
- OCCI::Log.info "OCCI client is shutting down ..."
394
+ end while options.interactive
145
395
 
146
- exit!(true)
396
+ Occi::Log.info "OCCI client is shutting down ..."