occi-api 4.0.0.alpha.1

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.
Files changed (105) hide show
  1. data/.gitignore +15 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +43 -0
  4. data/.yardopts +1 -0
  5. data/AUTHORS +9 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE +13 -0
  8. data/README.md +344 -0
  9. data/Rakefile +37 -0
  10. data/examples/dsl_example.rb +167 -0
  11. data/examples/x509auth_example.rb +161 -0
  12. data/ext/mkrf_conf.rb +34 -0
  13. data/features/cassettes/Create_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
  14. data/features/cassettes/Delete_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
  15. data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__application_json_200_.yml +333 -0
  16. data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__text_plain_200_.yml +529 -0
  17. data/features/cassettes/Discovery_Interface/Retrieving_all_OCCI_Categories_supported_by_the_OCCI_Server/_http_http___141_5_99_69__text_plain_200_action_.yml +288 -0
  18. data/features/cassettes/Miscellaneous_operation_on_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
  19. data/features/cassettes/Read_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
  20. data/features/cassettes/Update_an_OCCI_Resource/_http_http___141_5_99_69__text_plain_201_.yml +288 -0
  21. data/features/common/step_definitions/common_steps.rb +32 -0
  22. data/features/occi/core/create/create.feature +18 -0
  23. data/features/occi/core/create/step_definitions/create_steps.rb +0 -0
  24. data/features/occi/core/delete/delete.feature +18 -0
  25. data/features/occi/core/delete/step_definitions/delete_steps.rb +0 -0
  26. data/features/occi/core/discovery_interface/discovery_interface.feature +37 -0
  27. data/features/occi/core/discovery_interface/step_definitions/discovery_interface_steps.rb +19 -0
  28. data/features/occi/core/miscellaneous/miscellaneous.feature +18 -0
  29. data/features/occi/core/miscellaneous/step_definitions/miscellaneous_steps.rb +0 -0
  30. data/features/occi/core/read/read.feature +18 -0
  31. data/features/occi/core/read/step_definitions/read_steps.rb +0 -0
  32. data/features/occi/core/update/step_definitions/update_steps.rb +0 -0
  33. data/features/occi/core/update/update.feature +18 -0
  34. data/features/occi/infrastructure/create/create.feature +18 -0
  35. data/features/occi/infrastructure/create/step_definitions/create_steps.rb +0 -0
  36. data/features/support/env.rb +16 -0
  37. data/lib/occi/api/client/client_amqp.rb +766 -0
  38. data/lib/occi/api/client/client_base.rb +689 -0
  39. data/lib/occi/api/client/client_http.rb +541 -0
  40. data/lib/occi/api/client/errors/authn_error.rb +7 -0
  41. data/lib/occi/api/client/errors.rb +1 -0
  42. data/lib/occi/api/client/http/authn_plugins/base.rb +27 -0
  43. data/lib/occi/api/client/http/authn_plugins/basic.rb +22 -0
  44. data/lib/occi/api/client/http/authn_plugins/digest.rb +22 -0
  45. data/lib/occi/api/client/http/authn_plugins/dummy.rb +13 -0
  46. data/lib/occi/api/client/http/authn_plugins/keystone.rb +61 -0
  47. data/lib/occi/api/client/http/authn_plugins/x509.rb +46 -0
  48. data/lib/occi/api/client/http/authn_plugins.rb +6 -0
  49. data/lib/occi/api/client/http/authn_utils.rb +87 -0
  50. data/lib/occi/api/client/http/httparty_fix.rb +53 -0
  51. data/lib/occi/api/client/http/net_http_fix.rb +21 -0
  52. data/lib/occi/api/dsl.rb +146 -0
  53. data/lib/occi/api/version.rb +5 -0
  54. data/lib/occi-api.rb +14 -0
  55. data/occi-api.gemspec +38 -0
  56. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_compute_resource.yml +266 -0
  57. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_network_resource.yml +266 -0
  58. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/creates_a_new_storage_resource.yml +266 -0
  59. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_compute_resource.yml +266 -0
  60. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_network_resource.yml +266 -0
  61. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deletes_a_storage_resource.yml +266 -0
  62. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/deploys_an_instance_based_on_OVF_OVA_file.yml +266 -0
  63. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_compute_resources.yml +368 -0
  64. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_network_resources.yml +370 -0
  65. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/describes_storage_resources.yml +430 -0
  66. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/establishes_connection.yml +266 -0
  67. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_scoped_os_tpl_mixin.yml +266 -0
  68. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_scoped_resource_tpl_mixin.yml +266 -0
  69. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/finds_and_describes_unscoped_mixin.yml +266 -0
  70. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_compute_resource_using_type_identifier.yml +266 -0
  71. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_compute_resource_using_type_name.yml +266 -0
  72. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_network_resource_using_type_identifier.yml +266 -0
  73. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_network_resource_using_type_name.yml +266 -0
  74. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_storage_resource_using_type_identifier.yml +266 -0
  75. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/instantiates_a_storage_resource_using_type_name.yml +266 -0
  76. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_entity_type_identifiers.yml +266 -0
  77. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_entity_types.yml +266 -0
  78. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_link_type_identifiers.yml +266 -0
  79. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_link_types.yml +266 -0
  80. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixin_type_identifiers.yml +266 -0
  81. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixin_types.yml +266 -0
  82. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_mixins.yml +266 -0
  83. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_resource_type_identifiers.yml +266 -0
  84. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_all_available_resource_types.yml +266 -0
  85. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_compute_resources.yml +308 -0
  86. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_network_resources.yml +308 -0
  87. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_os_tpl_mixins.yml +266 -0
  88. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_resource_tpl_mixins.yml +266 -0
  89. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/lists_storage_resources.yml +310 -0
  90. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/refreshes_its_model.yml +485 -0
  91. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_compute_resource.yml +266 -0
  92. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_network_resource.yml +266 -0
  93. data/spec/cassettes/Occi_Api_Client_ClientHttp/using_media_type_text_plain/triggers_an_action_on_a_storage_resource.yml +266 -0
  94. data/spec/occi/api/client/client_amqp_spec.rb +158 -0
  95. data/spec/occi/api/client/client_http_spec.rb +292 -0
  96. data/spec/occi/api/client/http/authn_utils_spec.rb +55 -0
  97. data/spec/occi/api/client/http/httparty_fix_spec.rb +0 -0
  98. data/spec/occi/api/client/http/net_http_fix_spec.rb +0 -0
  99. data/spec/occi/api/client/http/rocci-cred-cert.pem +3 -0
  100. data/spec/occi/api/client/http/rocci-cred-key-jruby.pem +3 -0
  101. data/spec/occi/api/client/http/rocci-cred-key.pem +3 -0
  102. data/spec/occi/api/client/http/rocci-cred.p12 +0 -0
  103. data/spec/occi/api/dsl_spec.rb +21 -0
  104. data/spec/spec_helper.rb +38 -0
  105. metadata +379 -0
@@ -0,0 +1,541 @@
1
+ require 'httparty'
2
+
3
+ require 'occi/api/client/http/net_http_fix'
4
+ require 'occi/api/client/http/httparty_fix'
5
+ require 'occi/api/client/http/authn_utils'
6
+
7
+ module Occi
8
+ module Api
9
+ module Client
10
+
11
+ class ClientHttp < ClientBase
12
+
13
+ # HTTParty for raw HTTP requests
14
+ include HTTParty
15
+
16
+ # TODO: uncomment the following line as JSON is properly implemented in OpenStack
17
+ # headers 'Accept' => 'application/occi+json,text/plain;q=0.8,text/occi;q=0.2'
18
+ headers 'Accept' => 'text/plain,text/occi;q=0.2'
19
+
20
+ # hash mapping HTTP response codes to human-readable messages
21
+ HTTP_CODES = {
22
+ "100" => "Continue",
23
+ "101" => "Switching Protocols",
24
+ "200" => "OK",
25
+ "201" => "Created",
26
+ "202" => "Accepted",
27
+ "203" => "Non-Authoritative Information",
28
+ "204" => "No Content",
29
+ "205" => "Reset Content",
30
+ "206" => "Partial Content",
31
+ "300" => "Multiple Choices",
32
+ "301" => "Moved Permanently",
33
+ "302" => "Found",
34
+ "303" => "See Other",
35
+ "304" => "Not Modified",
36
+ "305" => "Use Proxy",
37
+ "307" => "Temporary Redirect",
38
+ "400" => "Bad Request",
39
+ "401" => "Unauthorized",
40
+ "402" => "Payment Required",
41
+ "403" => "Forbidden",
42
+ "404" => "Not Found",
43
+ "405" => "Method Not Allowed",
44
+ "406" => "Not Acceptable",
45
+ "407" => "Proxy Authentication Required",
46
+ "408" => "Request Time-out",
47
+ "409" => "Conflict",
48
+ "410" => "Gone",
49
+ "411" => "Length Required",
50
+ "412" => "Precondition Failed",
51
+ "413" => "Request Entity Too Large",
52
+ "414" => "Request-URI Too Large",
53
+ "415" => "Unsupported Media Type",
54
+ "416" => "Requested range not satisfiable",
55
+ "417" => "Expectation Failed",
56
+ "500" => "Internal Server Error",
57
+ "501" => "Not Implemented",
58
+ "502" => "Bad Gateway",
59
+ "503" => "Service Unavailable",
60
+ "504" => "Gateway Time-out",
61
+ "505" => "HTTP Version not supported"
62
+ }
63
+
64
+ # Initializes client data structures and retrieves OCCI model
65
+ # from the server.
66
+ #
67
+ # @example
68
+ # options = {
69
+ # :endpoint => "http://localhost:3300/",
70
+ # :auth => {:type => "none"},
71
+ # :log => {:out => STDERR, :level => Occi::Log::WARN, :logger => nil},
72
+ # :auto_connect => "value", auto_connect => true,
73
+ # :media_type => nil
74
+ # }
75
+ #
76
+ # Occi::Api::Client::ClientHttp.new options # => #<Occi::Api::Client::ClientHttp>
77
+ #
78
+ # @param [Hash] options, for available options and defaults see examples
79
+ # @return [Occi::Api::Client::ClientHttp] client instance
80
+ def initialize(options = {})
81
+ super options
82
+
83
+ # get model information from the endpoint
84
+ # and create Occi::Model instance
85
+ model = get('/-/')
86
+ set_model model
87
+
88
+ # auto-connect?
89
+ @connected = @options[:auto_connect]
90
+ end
91
+
92
+ # @see Occi::Api::Client::ClientBase
93
+ def list(resource_type_identifier=nil)
94
+ if resource_type_identifier
95
+ # convert type to type identifier
96
+ kinds = @model.kinds.select {
97
+ |kind| kind.term == resource_type_identifier
98
+ }
99
+ if kinds.any?
100
+ resource_type_identifier = kinds.first.type_identifier
101
+ end
102
+
103
+ raise 'Unkown resource type identifier!' unless resource_type_identifier
104
+ unless @model.get_by_id resource_type_identifier
105
+ raise "Resource type identifier not allowed with this model! [#{resource_type_identifier}]"
106
+ end
107
+
108
+ # split the type identifier and get the most important part
109
+ uri_part = resource_type_identifier.split('#').last
110
+
111
+ # request uri-list from the server
112
+ path = uri_part + '/'
113
+ end
114
+
115
+ path = '/' unless path
116
+
117
+ headers = self.class.headers.clone
118
+ headers['Accept'] = 'text/uri-list'
119
+
120
+ response = self.class.get(
121
+ @endpoint + path,
122
+ :headers => headers
123
+ )
124
+
125
+ # TODO: remove the gsub OCCI-OS hack as soon as they stop using 'uri:'
126
+ response.body.gsub(/\# uri:\/(compute|storage|network)\/[\n]?/, '').split("\n").compact
127
+ end
128
+
129
+ # @see Occi::Api::Client::ClientBase
130
+ def describe(resource_type_identifier=nil)
131
+
132
+ # convert type to type identifier whenever possible
133
+ if resource_type_identifier
134
+ kinds = @model.kinds.select {
135
+ |kind| kind.term == resource_type_identifier
136
+ }
137
+ if kinds.any?
138
+ resource_type_identifier = kinds.first.type_identifier
139
+ end
140
+ end
141
+
142
+ descriptions = []
143
+
144
+ if resource_type_identifier.nil?
145
+ # no filters, describe all available resources
146
+ descriptions << get('/')
147
+ elsif @model.get_by_id(resource_type_identifier)
148
+ # we got type identifier
149
+ # get all available resources of this type
150
+ locations = list(resource_type_identifier)
151
+
152
+ # make the requests
153
+ locations.each do |location|
154
+ descriptions << get(sanitize_resource_link(location))
155
+ end
156
+ elsif resource_type_identifier.start_with?(@endpoint) || resource_type_identifier.start_with?('/')
157
+ # this is a link of a specific resource (obsolute or relative)
158
+ descriptions << get(sanitize_resource_link(resource_type_identifier))
159
+ else
160
+ raise "Unkown resource type identifier! [#{resource_type_identifier}]"
161
+ end
162
+
163
+ descriptions
164
+ end
165
+
166
+ # @see Occi::Api::Client::ClientBase
167
+ def create(entity)
168
+
169
+ raise "#{entity} not an entity!" unless entity.kind_of? Occi::Core::Entity
170
+
171
+ # is this entity valid?
172
+ entity.model = @model
173
+ entity.check
174
+
175
+ Occi::Log.debug "Entity kind: #{entity.kind}"
176
+ kind = entity.kind
177
+ raise "No kind found for #{entity}" unless kind
178
+
179
+ # get location for this kind of entity
180
+ Occi::Log.debug "Kind location: #{entity.kind.location}"
181
+ location = kind.location
182
+ collection = Occi::Collection.new
183
+
184
+ # is this entity a Resource or a Link?
185
+ Occi::Log.debug "Entity class: #{entity.class.name}"
186
+ collection.resources << entity if entity.kind_of? Occi::Core::Resource
187
+ collection.links << entity if entity.kind_of? Occi::Core::Link
188
+
189
+ # make the request
190
+ post location, collection
191
+ end
192
+
193
+ # @see Occi::Api::Client::ClientBase
194
+ def deploy(location)
195
+ raise "File #{location} does not exist!" unless File.exist? location
196
+
197
+ file = File.read(location)
198
+
199
+ if location.include? '.ovf'
200
+ deploy_ovf file
201
+ elsif location.include? '.ova'
202
+ deploy_ova file
203
+ else
204
+ raise "Unsupported descriptor format! Only OVF or OVA files are supported."
205
+ end
206
+ end
207
+
208
+ # @see Occi::Api::Client::ClientBase
209
+ def deploy_ovf(descriptor)
210
+ media_types = self.class.head(@endpoint).headers['accept'].to_s
211
+
212
+ if media_types.include? 'application/ovf'
213
+ headers = self.class.headers.clone
214
+ headers['Content-Type'] = 'application/ovf'
215
+ self.class.post(@endpoint + '/compute/',
216
+ :body => descriptor,
217
+ :headers => headers)
218
+ else
219
+ raise "Unsupported descriptor format! Server does not support OVF descriptors."
220
+ end
221
+ end
222
+
223
+ # @see Occi::Api::Client::ClientBase
224
+ def deploy_ova(descriptor)
225
+ media_types = self.class.head(@endpoint).headers['accept'].to_s
226
+
227
+ if media_types.include? ' application/ova '
228
+ headers = self.class.headers.clone
229
+ headers['Content-Type'] = 'application/ova'
230
+ self.class.post(@endpoint + '/compute/',
231
+ :body => descriptor,
232
+ :headers => headers)
233
+ else
234
+ raise "Unsupported descriptor format! Server does not support OVA descriptors."
235
+ end
236
+ end
237
+
238
+ # @see Occi::Api::Client::ClientBase
239
+ def delete(resource_type_identifier)
240
+ raise 'Resource not provided!' unless resource_type_identifier
241
+ path = path_for_resource_type(resource_type_identifier)
242
+
243
+ Occi::Log.debug("Deleting #{path} ...")
244
+ del path
245
+ end
246
+
247
+ # @see Occi::Api::Client::ClientBase
248
+ def trigger(resource_type_identifier, action)
249
+ # TODO: not tested
250
+ raise 'Resource not provided!' unless resource_type_identifier
251
+ type_identifiers = @model.kinds.select {
252
+ |kind| kind.term == resource_type_identifier
253
+ }
254
+
255
+ if type_identifiers.any?
256
+ type_identifier = @model.kinds.select {
257
+ |kind| kind.term == resource_type_identifier
258
+ }.first.type_identifier
259
+
260
+ location = @model.get_by_id(type_identifier).location
261
+ resource_type_identifier = @endpoint + location
262
+ end
263
+
264
+ raise "Unknown resource identifier! #{resource_type_identifier}" unless resource_type_identifier.start_with? @endpoint
265
+
266
+ # encapsulate the acion in a collection
267
+ collection = Occi::Collection.new
268
+ scheme, term = action.split(' #')
269
+ collection.actions << Occi::Core::Action.new(scheme + '#', term)
270
+
271
+ # make the request
272
+ path = sanitize_resource_link(resource_type_identifier) + '?action=' + term
273
+ post path, collection
274
+ end
275
+
276
+ # @see Occi::Api::Client::ClientBase
277
+ def refresh
278
+ # re-download the model from the server
279
+ model = get('/-/')
280
+ set_model model
281
+ end
282
+
283
+ private
284
+
285
+ # Performs GET request and parses the responses to collections.
286
+ #
287
+ # @example
288
+ # get "/-/" # => #<Occi::Collection>
289
+ # get "/compute/" # => #<Occi::Collection>
290
+ # get "/compute/fs65g4fs6g-sf54g54gsf-aa12faddf52" # => #<Occi::Collection>
291
+ #
292
+ # @param [String] path for the GET request
293
+ # @param [Occi::Collection] collection of filters
294
+ # @return [Occi::Collection] parsed result of the request
295
+ def get(path='', filter=nil)
296
+ # remove the leading slash
297
+ path = path.gsub(/\A\//, '')
298
+
299
+ response = if filter
300
+ categories = filter.categories.collect { |category| category.to_text }.join(',')
301
+ attributes = filter.entities.collect { |entity| entity.attributes.combine.collect { |k, v| k + '=' + v } }.join(',')
302
+
303
+ headers = self.class.headers.clone
304
+ headers['Content-Type'] = 'text/occi'
305
+ headers['Category'] = categories unless categories.empty?
306
+ headers['X-OCCI-Attributes'] = attributes unless attributes.empty?
307
+
308
+ self.class.get(@endpoint + path, :headers => headers)
309
+ else
310
+ self.class.get(@endpoint + path)
311
+ end
312
+
313
+ response_msg = response_message response
314
+ raise "HTTP GET failed! #{response_msg}" unless response.code.between? 200, 300
315
+
316
+ Occi::Log.debug "Response location: #{('/' + path).match(/\/.*\//).to_s}"
317
+ kind = @model.get_by_location(('/' + path).match(/\/.*\//).to_s) if @model
318
+
319
+ Occi::Log.debug "Response kind: #{kind}"
320
+
321
+ if kind
322
+ kind.related_to? Occi::Core::Resource ? entity_type = Occi::Core::Resource : entity_type = nil
323
+ entity_type = Occi::Core::Link if kind.related_to? Occi::Core::Link
324
+ end
325
+
326
+ Occi::Log.debug "Parser call: #{response.content_type} #{entity_type} #{path.include?('-/')}"
327
+ collection = Occi::Parser.parse(response.content_type, response.body, path.include?('-/'), entity_type, response.headers)
328
+
329
+ Occi::Log.debug "Parsed collection: empty? #{collection.empty?}"
330
+ collection
331
+ end
332
+
333
+ # Performs POST requests and returns URI locations. Resource data must be provided
334
+ # in an Occi::Collection instance.
335
+ #
336
+ # @example
337
+ # collection = Occi::Collection.new
338
+ # collection.resources << entity if entity.kind_of? Occi::Core::Resource
339
+ # collection.links << entity if entity.kind_of? Occi::Core::Link
340
+ #
341
+ # post "/compute/", collection # => "http://localhost:3300/compute/23sf4g65as-asdgsg2-sdfgsf2g"
342
+ # post "/network/", collection # => "http://localhost:3300/network/23sf4g65as-asdgsg2-sdfgsf2g"
343
+ # post "/storage/", collection # => "http://localhost:3300/storage/23sf4g65as-asdgsg2-sdfgsf2g"
344
+ #
345
+ # @param [String] path for the POST request
346
+ # @param [Occi::Collection] resource data to be POSTed
347
+ # @return [String] URI location
348
+ def post(path, collection)
349
+ # remove the leading slash
350
+ path = path.gsub(/\A\//, '')
351
+
352
+ headers = self.class.headers.clone
353
+ headers['Content-Type'] = @media_type
354
+
355
+ response = case @media_type
356
+ when 'application/occi+json'
357
+ self.class.post(@endpoint + path,
358
+ :body => collection.to_json,
359
+ :headers => headers)
360
+ when 'text/occi'
361
+ self.class.post(@endpoint + path,
362
+ :headers => collection.to_header.merge(headers))
363
+ else
364
+ self.class.post(@endpoint + path,
365
+ :body => collection.to_text,
366
+ :headers => headers)
367
+ end
368
+
369
+ response_msg = response_message response
370
+
371
+ case response.code
372
+ when 200
373
+ collection = Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
374
+
375
+ if collection.empty?
376
+ Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
377
+ else
378
+ collection.resources.first.location if collection.resources.first
379
+ end
380
+ when 201
381
+ Occi::Parser.locations(response.header["content-type"].split(";").first, response.body, response.headers).first
382
+ else
383
+ raise "HTTP POST failed! #{response_msg}"
384
+ end
385
+ end
386
+
387
+ # Performs PUT requests and parses responses to collections.
388
+ #
389
+ # @example
390
+ # TODO: add examples
391
+ #
392
+ # @param [String] path for the PUT request
393
+ # @param [Occi::Collection] resource data to send
394
+ # @return [Occi::Collection] parsed result of the request
395
+ def put(path, collection)
396
+ # remove the leading slash
397
+ path = path.gsub(/\A\//, '')
398
+
399
+ headers = self.class.headers.clone
400
+ headers['Content-Type'] = @media_type
401
+
402
+ response = case @media_type
403
+ when 'application/occi+json'
404
+ self.class.post(@endpoint + path,
405
+ :body => collection.to_json,
406
+ :headers => headers)
407
+ when 'text/occi'
408
+ self.class.post(@endpoint + path,
409
+ :headers => collection.to_header.merge(headers))
410
+ else
411
+ self.class.post(@endpoint + path,
412
+ :body => collection.to_text,
413
+ :headers => headers)
414
+ end
415
+
416
+ response_msg = response_message response
417
+
418
+ case response.code
419
+ when 200, 201
420
+ Occi::Parser.parse(response.header["content-type"].split(";").first, response.body)
421
+ else
422
+ raise "HTTP POST failed! #{response_msg}"
423
+ end
424
+ end
425
+
426
+ # Performs DELETE requests and returns True on success.
427
+ #
428
+ # @example
429
+ # del "/compute/65sf4g65sf4g-sf6g54sf5g-sfgsf32g3" # => true
430
+ #
431
+ # @param [String] path for the DELETE request
432
+ # @param [Occi::Collection] collection of filters (currently NOT used)
433
+ # @return [Boolean] status
434
+ def del(path, filter=nil)
435
+ # remove the leading slash
436
+ path = path.gsub(/\A\//, '')
437
+
438
+ response = self.class.delete(@endpoint + path)
439
+
440
+ response_msg = response_message response
441
+ raise "HTTP DELETE failed! #{response_msg}" unless response.code.between? 200, 300
442
+
443
+ true
444
+ end
445
+
446
+ # @see Occi::Api::Client::ClientBase
447
+ def set_logger(log_options)
448
+ super log_options
449
+
450
+ self.class.debug_output $stderr if log_options[:level] == Occi::Log::DEBUG
451
+ end
452
+
453
+ # @see Occi::Api::Client::ClientBase
454
+ def set_auth(auth_options, fallback = false)
455
+ @auth_options = auth_options
456
+
457
+ case @auth_options[:type]
458
+ when "basic"
459
+ @authn_plugin = Http::AuthnPlugins::Basic.new self, @auth_options
460
+ when "digest"
461
+ @authn_plugin = Http::AuthnPlugins::Digest.new self, @auth_options
462
+ when "x509"
463
+ @authn_plugin = Http::AuthnPlugins::X509.new self, @auth_options
464
+ when "keystone"
465
+ raise ::Occi::Api::Client::Errors::AuthnError, "This authN method is for fallback only!" unless fallback
466
+ @authn_plugin = Http::AuthnPlugins::Keystone.new self, @auth_options
467
+ when "none", nil
468
+ @authn_plugin = Http::AuthnPlugins::Dummy.new self
469
+ else
470
+ raise ::Occi::Api::Client::Errors::AuthnError, "Unknown authN method [#{@auth_options[:type]}]!"
471
+ end
472
+
473
+ @authn_plugin.setup
474
+ end
475
+
476
+ # @see Occi::Api::Client::ClientBase
477
+ def preauthenticate
478
+ begin
479
+ @authn_plugin.authenticate
480
+ rescue ::Occi::Api::Client::Errors::AuthnError => e
481
+ Occi::Log.debug e.message
482
+
483
+ if @authn_plugin.fallbacks.any?
484
+ @auth_options[:original_type] = @auth_options[:type]
485
+ @auth_options[:type] = @authn_plugin.fallbacks.first
486
+
487
+ set_auth @auth_options, true
488
+ @authn_plugin.authenticate
489
+ else
490
+ raise e
491
+ end
492
+ end
493
+ end
494
+
495
+ # @see Occi::Api::Client::ClientBase
496
+ def set_media_type(force_type = nil)
497
+ # force media_type if provided
498
+ if force_type
499
+ self.class.headers 'Accept' => force_type
500
+ @media_type = force_type
501
+ else
502
+ media_types = self.class.head(@endpoint).headers['accept']
503
+ Occi::Log.debug("Available media types: #{media_types}")
504
+ @media_type = case media_types
505
+ when /application\/occi\+json/
506
+ 'application/occi+json'
507
+ else
508
+ 'text/plain'
509
+ end
510
+ end
511
+ end
512
+
513
+ # Generates a human-readable response message based on the HTTP response code.
514
+ #
515
+ # @example
516
+ # response_message self.class.delete(@endpoint + path)
517
+ # # => 'HTTP Response status: [200] OK'
518
+ #
519
+ # @param [HTTParty::Response] HTTParty response object
520
+ # @return [String] message
521
+ def response_message(response)
522
+ @last_response = response
523
+ 'HTTP Response status: [' + response.code.to_s + '] ' + reason_phrase(response.code)
524
+ end
525
+
526
+ # Converts HTTP response codes to human-readable phrases.
527
+ #
528
+ # @example
529
+ # reason_phrase(500) # => "Internal Server Error"
530
+ #
531
+ # @param [Integer] HTTP response code
532
+ # @return [String] human-readable phrase
533
+ def reason_phrase(code)
534
+ HTTP_CODES[code.to_s]
535
+ end
536
+
537
+ end
538
+
539
+ end
540
+ end
541
+ end
@@ -0,0 +1,7 @@
1
+ module Occi::Api::Client
2
+ module Errors
3
+
4
+ class AuthnError < RuntimeError; end
5
+
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ require 'occi/api/client/errors/authn_error'
@@ -0,0 +1,27 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+ module AuthnPlugins
4
+
5
+ class Base
6
+ attr_reader :env_ref
7
+ attr_reader :options
8
+ attr_reader :fallbacks
9
+
10
+ def initialize(env_ref, options = {})
11
+ @options = options
12
+ @env_ref = env_ref
13
+ @fallbacks = []
14
+ end
15
+
16
+ def setup(options = {}); end
17
+
18
+ def authenticate(options = {})
19
+ response = @env_ref.class.head @env_ref.endpoint
20
+ raise ::Occi::Api::Client::Errors::AuthnError, "Authentication failed with code #{response.code.to_s}!" unless response.success?
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+ module AuthnPlugins
4
+
5
+ class Basic < Base
6
+
7
+ def initialize(env_ref, options = {})
8
+ super env_ref, options
9
+ @fallbacks = %w(keystone)
10
+ end
11
+
12
+ def setup(options = {})
13
+ # set up basic auth
14
+ raise ArgumentError, "Missing required options 'username' and 'password' for basic auth!" unless @options[:username] && @options[:password]
15
+ @env_ref.class.basic_auth @options[:username], @options[:password]
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+ module AuthnPlugins
4
+
5
+ class Digest < Base
6
+
7
+ def initialize(env_ref, options = {})
8
+ super env_ref, options
9
+ @fallbacks = %w(keystone)
10
+ end
11
+
12
+ def setup(options = {})
13
+ # set up digest auth
14
+ raise ArgumentError, "Missing required options 'username' and 'password' for digest auth!" unless @options[:username] and @options[:password]
15
+ @env_ref.class.digest_auth @options[:username], @options[:password]
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Occi::Api::Client
2
+ module Http
3
+ module AuthnPlugins
4
+
5
+ class Dummy < Base
6
+
7
+ def authenticate(options = {}); end
8
+
9
+ end
10
+
11
+ end
12
+ end
13
+ end