fog-brightbox 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/fog/brightbox.rb +1 -0
  4. data/lib/fog/brightbox/config.rb +102 -2
  5. data/lib/fog/brightbox/core.rb +1 -0
  6. data/lib/fog/brightbox/models/compute/api_client.rb +3 -1
  7. data/lib/fog/brightbox/models/storage/directories.rb +45 -0
  8. data/lib/fog/brightbox/models/storage/directory.rb +53 -0
  9. data/lib/fog/brightbox/models/storage/file.rb +166 -0
  10. data/lib/fog/brightbox/models/storage/files.rb +104 -0
  11. data/lib/fog/brightbox/oauth2.rb +140 -136
  12. data/lib/fog/brightbox/requests/storage/copy_object.rb +27 -0
  13. data/lib/fog/brightbox/requests/storage/delete_container.rb +22 -0
  14. data/lib/fog/brightbox/requests/storage/delete_multiple_objects.rb +67 -0
  15. data/lib/fog/brightbox/requests/storage/delete_object.rb +23 -0
  16. data/lib/fog/brightbox/requests/storage/delete_static_large_object.rb +43 -0
  17. data/lib/fog/brightbox/requests/storage/get_container.rb +44 -0
  18. data/lib/fog/brightbox/requests/storage/get_containers.rb +33 -0
  19. data/lib/fog/brightbox/requests/storage/get_object.rb +29 -0
  20. data/lib/fog/brightbox/requests/storage/get_object_http_url.rb +21 -0
  21. data/lib/fog/brightbox/requests/storage/get_object_https_url.rb +85 -0
  22. data/lib/fog/brightbox/requests/storage/head_container.rb +28 -0
  23. data/lib/fog/brightbox/requests/storage/head_containers.rb +25 -0
  24. data/lib/fog/brightbox/requests/storage/head_object.rb +23 -0
  25. data/lib/fog/brightbox/requests/storage/post_set_meta_temp_url_key.rb +37 -0
  26. data/lib/fog/brightbox/requests/storage/put_container.rb +27 -0
  27. data/lib/fog/brightbox/requests/storage/put_dynamic_obj_manifest.rb +43 -0
  28. data/lib/fog/brightbox/requests/storage/put_object.rb +42 -0
  29. data/lib/fog/brightbox/requests/storage/put_object_manifest.rb +16 -0
  30. data/lib/fog/brightbox/requests/storage/put_static_obj_manifest.rb +57 -0
  31. data/lib/fog/brightbox/storage.rb +166 -0
  32. data/lib/fog/brightbox/storage/authentication_request.rb +52 -0
  33. data/lib/fog/brightbox/storage/config.rb +23 -0
  34. data/lib/fog/brightbox/storage/connection.rb +83 -0
  35. data/lib/fog/brightbox/storage/errors.rb +11 -0
  36. data/lib/fog/brightbox/version.rb +1 -1
  37. data/spec/fog/brightbox/config_spec.rb +62 -1
  38. data/spec/fog/brightbox/storage/authentication_request_spec.rb +77 -0
  39. data/spec/fog/brightbox/storage/config_spec.rb +40 -0
  40. data/spec/fog/brightbox/storage/connection_errors_spec.rb +54 -0
  41. data/spec/fog/brightbox/storage/connection_spec.rb +120 -0
  42. data/spec/fog/storage/brightbox_spec.rb +290 -0
  43. metadata +40 -3
@@ -0,0 +1,27 @@
1
+ module Fog
2
+ module Storage
3
+ class Brightbox
4
+ class Real
5
+
6
+ # Create a new container
7
+ #
8
+ # ==== Parameters
9
+ # * name<~String> - Name for container, should be < 256 bytes and must not contain '/'
10
+ #
11
+ def put_container(name, options = {})
12
+ headers = options[:headers] || {}
13
+ headers['X-Container-Read'] ||= options.delete(:read_permissions)
14
+ headers['X-Container-Write'] ||= options.delete(:write_permissions)
15
+
16
+ request(
17
+ :expects => [201, 202],
18
+ :method => 'PUT',
19
+ :path => Fog::Storage::Brightbox.escape(name),
20
+ :headers => headers
21
+ )
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ module Fog
2
+ module Storage
3
+ class Brightbox
4
+ class Real
5
+
6
+ # Create a new dynamic large object manifest
7
+ #
8
+ # Creates an object with a +X-Object-Manifest+ header that specifies the common prefix ("<container>/<prefix>")
9
+ # for all uploaded segments. Retrieving the manifest object streams all segments matching this prefix.
10
+ # Segments must sort in the order they should be concatenated. Note that any future objects stored in the container
11
+ # along with the segments that match the prefix will be included when retrieving the manifest object.
12
+ #
13
+ # All segments must be stored in the same container, but may be in a different container than the manifest object.
14
+ # The default +X-Object-Manifest+ header is set to "+container+/+object+", but may be overridden in +options+
15
+ # to specify the prefix and/or the container where segments were stored.
16
+ # If overridden, names should be CGI escaped (excluding spaces) if needed (see {Fog::Storage::Brightbox.escape}).
17
+ #
18
+ # @param container [String] Name for container where +object+ will be stored. Should be < 256 bytes and must not contain '/'
19
+ # @param object [String] Name for manifest object.
20
+ # @param options [Hash] Config headers for +object+.
21
+ # @option options [String] 'X-Object-Manifest' ("container/object") "<container>/<prefix>" for segment objects.
22
+ #
23
+ # @raise [Fog::Storage::Brightbox::NotFound] HTTP 404
24
+ # @raise [Excon::Errors::BadRequest] HTTP 400
25
+ # @raise [Excon::Errors::Unauthorized] HTTP 401
26
+ # @raise [Excon::Errors::HTTPStatusError]
27
+ #
28
+ # @see http://docs.brightbox.org/api/brightbox-object-storage/1.0/content/dynamic-large-object-creation.html
29
+ def put_dynamic_obj_manifest(container, object, options = {})
30
+ path = "#{Fog::Storage::Brightbox.escape(container)}/#{Fog::Storage::Brightbox.escape(object)}"
31
+ headers = {'X-Object-Manifest' => path}.merge(options)
32
+ request(
33
+ :expects => 201,
34
+ :headers => headers,
35
+ :method => 'PUT',
36
+ :path => path
37
+ )
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ module Fog
2
+ module Storage
3
+ class Brightbox
4
+ class Real
5
+
6
+ # Create a new object
7
+ #
8
+ # When passed a block, it will make a chunked request, calling
9
+ # the block for chunks until it returns an empty string.
10
+ # In this case the data parameter is ignored.
11
+ #
12
+ # ==== Parameters
13
+ # * container<~String> - Name for container, should be < 256 bytes and must not contain '/'
14
+ # * object<~String> - Name for object
15
+ # * data<~String|File> - data to upload
16
+ # * options<~Hash> - config headers for object. Defaults to {}.
17
+ # * block<~Proc> - chunker
18
+ #
19
+ def put_object(container, object, data, options = {}, &block)
20
+ if block_given?
21
+ params = { :request_block => block }
22
+ headers = options
23
+ else
24
+ data = Fog::Storage.parse_data(data)
25
+ headers = data[:headers].merge!(options)
26
+ params = { :body => data[:body] }
27
+ end
28
+
29
+ params.merge!(
30
+ :expects => 201,
31
+ :idempotent => !params[:request_block],
32
+ :headers => headers,
33
+ :method => 'PUT',
34
+ :path => "#{Fog::Storage::Brightbox.escape(container)}/#{Fog::Storage::Brightbox.escape(object)}"
35
+ )
36
+
37
+ request(params)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ module Fog
2
+ module Storage
3
+ class Brightbox
4
+ class Real
5
+
6
+ # Create a new dynamic large object manifest
7
+ #
8
+ # This is an alias for {#put_dynamic_obj_manifest} for backward compatibility.
9
+ def put_object_manifest(container, object, options = {})
10
+ put_dynamic_obj_manifest(container, object, options)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ module Fog
2
+ module Storage
3
+ class Brightbox
4
+ class Real
5
+
6
+ # Create a new static large object manifest.
7
+ #
8
+ # A static large object is similar to a dynamic large object. Whereas a GET for a dynamic large object manifest
9
+ # will stream segments based on the manifest's +X-Object-Manifest+ object name prefix, a static large object
10
+ # manifest streams segments which are defined by the user within the manifest. Information about each segment is
11
+ # provided in +segments+ as an Array of Hash objects, ordered in the sequence which the segments should be streamed.
12
+ #
13
+ # When the SLO manifest is received, each segment's +etag+ and +size_bytes+ will be verified.
14
+ # The +etag+ for each segment is returned in the response to {#put_object}, but may also be calculated.
15
+ # e.g. +Digest::MD5.hexdigest(segment_data)+
16
+ #
17
+ # The maximum number of segments for a static large object is 1000, and all segments (except the last) must be
18
+ # at least 1 MiB in size. Unlike a dynamic large object, segments are not required to be in the same container.
19
+ #
20
+ # @example
21
+ # segments = [
22
+ # { :path => 'segments_container/first_segment',
23
+ # :etag => 'md5 for first_segment',
24
+ # :size_bytes => 'byte size of first_segment' },
25
+ # { :path => 'segments_container/second_segment',
26
+ # :etag => 'md5 for second_segment',
27
+ # :size_bytes => 'byte size of second_segment' }
28
+ # ]
29
+ # put_static_obj_manifest('my_container', 'my_large_object', segments)
30
+ #
31
+ # @param container [String] Name for container where +object+ will be stored.
32
+ # Should be < 256 bytes and must not contain '/'
33
+ # @param object [String] Name for manifest object.
34
+ # @param segments [Array<Hash>] Segment data for the object.
35
+ # @param options [Hash] Config headers for +object+.
36
+ #
37
+ # @raise [Fog::Storage::Brightbox::NotFound] HTTP 404
38
+ # @raise [Excon::Errors::BadRequest] HTTP 400
39
+ # @raise [Excon::Errors::Unauthorized] HTTP 401
40
+ # @raise [Excon::Errors::HTTPStatusError]
41
+ #
42
+ # @see http://docs.brightbox.org/api/brightbox-object-storage/1.0/content/static-large-objects.html
43
+ def put_static_obj_manifest(container, object, segments, options = {})
44
+ request(
45
+ :expects => 201,
46
+ :method => 'PUT',
47
+ :headers => options,
48
+ :body => Fog::JSON.encode(segments),
49
+ :path => "#{Fog::Storage::Brightbox.escape(container)}/#{Fog::Storage::Brightbox.escape(object)}",
50
+ :query => { 'multipart-manifest' => 'put' }
51
+ )
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,166 @@
1
+ require "fog/brightbox/core"
2
+ require "fog/brightbox/storage/errors"
3
+ require "fog/brightbox/storage/config"
4
+ require "fog/brightbox/storage/authentication_request"
5
+ require "fog/brightbox/storage/connection"
6
+
7
+ module Fog
8
+ module Storage
9
+ class Brightbox < Fog::Service
10
+
11
+ requires :brightbox_client_id,
12
+ :brightbox_secret
13
+ recognizes :persistent, :brightbox_service_name,
14
+ :brightbox_storage_url,
15
+ :brightbox_service_type, :brightbox_tenant,
16
+ :brightbox_region, :brightbox_temp_url_key
17
+
18
+ model_path "fog/brightbox/models/storage"
19
+ model :directory
20
+ collection :directories
21
+ model :file
22
+ collection :files
23
+
24
+ request_path "fog/brightbox/requests/storage"
25
+ request :copy_object
26
+ request :delete_container
27
+ request :delete_object
28
+ request :delete_multiple_objects
29
+ request :delete_static_large_object
30
+ request :get_container
31
+ request :get_containers
32
+ request :get_object
33
+ request :get_object_http_url
34
+ request :get_object_https_url
35
+ request :head_container
36
+ request :head_containers
37
+ request :head_object
38
+ request :post_set_meta_temp_url_key
39
+ request :put_container
40
+ request :put_dynamic_obj_manifest
41
+ request :put_object
42
+ request :put_object_manifest
43
+ request :put_static_obj_manifest
44
+
45
+ class Mock
46
+ def initialize(options={})
47
+ end
48
+ end
49
+
50
+ class Real
51
+ def initialize(config)
52
+ if config.respond_to?(:config_service?) && config.config_service?
53
+ @config = config
54
+ else
55
+ @config = Fog::Brightbox::Config.new(config)
56
+ end
57
+ @config = Fog::Brightbox::Storage::Config.new(@config)
58
+
59
+ @temp_url_key = @config.storage_temp_key
60
+ end
61
+
62
+ def needs_to_authenticate?
63
+ @config.must_authenticate?
64
+ end
65
+
66
+ def authentication_url
67
+ @auth_url ||= URI.parse(@config.storage_url.to_s)
68
+ end
69
+
70
+ def management_url
71
+ @config.storage_management_url
72
+ end
73
+
74
+ def reload
75
+ @connection.reset
76
+ end
77
+
78
+ def account
79
+ @config.account
80
+ end
81
+
82
+ def change_account(account)
83
+ @config.change_account(account)
84
+ end
85
+
86
+ def reset_account_name
87
+ @config.reset_account
88
+ end
89
+
90
+ def connection
91
+ @connection ||= Fog::Brightbox::Storage::Connection.new(@config)
92
+ end
93
+
94
+ def request(params, parse_json = true)
95
+ begin
96
+ authenticate if @config.must_authenticate?
97
+ connection.request(params, parse_json)
98
+ rescue Fog::Brightbox::Storage::AuthenticationRequired => error
99
+ if @config.managed_tokens?
100
+ @config.expire_tokens!
101
+ authenticate
102
+ retry
103
+ else # bad credentials
104
+ raise error
105
+ end
106
+ rescue Excon::Errors::HTTPStatusError => error
107
+ raise case error
108
+ when Excon::Errors::NotFound
109
+ Fog::Storage::Brightbox::NotFound.slurp(error)
110
+ else
111
+ error
112
+ end
113
+ end
114
+ end
115
+
116
+ def authenticate
117
+ if !management_url || needs_to_authenticate?
118
+ response = Fog::Brightbox::Storage::AuthenticationRequest.new(@config).authenticate
119
+ if response.nil?
120
+ return false
121
+ else
122
+ update_config_from_auth_response(response)
123
+ end
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ # @param [URI] uri A URI object to extract the account from
130
+ # @return [String] The account
131
+ def extract_account_from_url(url)
132
+ url.path.split("/")[2]
133
+ end
134
+
135
+ private
136
+
137
+ def update_config_from_auth_response(response)
138
+ @config.update_tokens(response.access_token)
139
+
140
+ # Only update the management URL if not set
141
+ return true if management_url && account
142
+
143
+ unless management_url
144
+ new_management_url = response.management_url
145
+ if new_management_url && new_management_url != management_url.to_s
146
+ @config.storage_management_url = URI.parse(new_management_url)
147
+ end
148
+ end
149
+
150
+ unless account
151
+ # Extract the account ID sent by the server
152
+ change_account(extract_account_from_url(@config.storage_management_url))
153
+ end
154
+ true
155
+ end
156
+ end
157
+
158
+ # CGI.escape, but without special treatment on spaces
159
+ def self.escape(str,extra_exclude_chars = "")
160
+ str.gsub(/([^a-zA-Z0-9_.-#{extra_exclude_chars}]+)/) do
161
+ "%" + $1.unpack("H2" * $1.bytesize).join("%").upcase
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,52 @@
1
+ module Fog
2
+ module Brightbox
3
+ module Storage
4
+ class AuthenticationRequest
5
+ attr_accessor :access_token, :management_url
6
+ attr_accessor :user, :tenant
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def authenticate
13
+ response = authentication_request
14
+
15
+ self.access_token = response.headers["X-Auth-Token"]
16
+ self.management_url = response.headers["X-Server-Management-Url"] || response.headers["X-Storage-Url"]
17
+ self
18
+ rescue Excon::Errors::Error
19
+ nil
20
+ end
21
+
22
+ private
23
+
24
+ def authentication_request
25
+ authentication_url = URI.parse(@config.storage_url.to_s)
26
+ connection = Fog::Core::Connection.new(authentication_url.to_s)
27
+ request_settings = {
28
+ :expects => [200, 204],
29
+ :headers => auth_headers,
30
+ :method => "GET",
31
+ :path => "v1"
32
+ }
33
+ connection.request(request_settings)
34
+ end
35
+
36
+ def auth_headers
37
+ if @config.user_credentials?
38
+ {
39
+ "X-Auth-User" => @config.username,
40
+ "X-Auth-Key" => @config.password
41
+ }
42
+ else
43
+ {
44
+ "X-Auth-User" => @config.client_id,
45
+ "X-Auth-Key" => @config.client_secret
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ require "delegate"
2
+
3
+ module Fog
4
+ module Brightbox
5
+ module Storage
6
+ class Config < SimpleDelegator
7
+ def initialize(config)
8
+ super
9
+ @config = config
10
+ raise ArgumentError unless required_args_available?
11
+ end
12
+
13
+ private
14
+
15
+ def required_args_available?
16
+ return false unless @config.client_id
17
+ return false unless @config.client_secret
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,83 @@
1
+ require "fog/core/connection"
2
+ require "fog/brightbox/storage/errors"
3
+
4
+ module Fog
5
+ module Brightbox
6
+ module Storage
7
+ class Connection < Fog::Core::Connection
8
+ def initialize(config)
9
+ @config = config
10
+
11
+ unless management_url
12
+ raise ManagementUrlUnknown
13
+ else
14
+ @connection = super(management_url.to_s, persistent?, connection_options)
15
+ end
16
+ end
17
+
18
+ def request(params, parse_json = true)
19
+ begin
20
+ raise ArgumentError if params.nil?
21
+ request_params = params.merge({
22
+ :headers => request_headers(params),
23
+ :path => path_in_params(params)
24
+ })
25
+ response = @connection.request(request_params)
26
+ rescue Excon::Errors::Unauthorized => error
27
+ raise AuthenticationRequired.slurp(error)
28
+ rescue Excon::Errors::NotFound => error
29
+ raise Fog::Storage::Brightbox::NotFound.slurp(error)
30
+ rescue Excon::Errors::HTTPStatusError => error
31
+ raise error
32
+ end
33
+
34
+ if !response.body.empty? && parse_json && response.get_header("Content-Type") =~ %r{application/json}
35
+ response.body = Fog::JSON.decode(response.body)
36
+ end
37
+ response
38
+ end
39
+
40
+ def management_url
41
+ @config.storage_management_url
42
+ end
43
+
44
+ def path_in_params(params)
45
+ if params.respond_to?(:key?) && params.key?(:path)
46
+ URI.join(@config.storage_management_url.to_s + "/", params[:path]).path
47
+ else
48
+ @config.storage_management_url.path
49
+ end
50
+ end
51
+
52
+ def request_headers(excon_params)
53
+ if excon_params.respond_to?(:key?) && excon_params.key?(:headers)
54
+ authenticated_headers.merge(excon_params[:headers])
55
+ else
56
+ authenticated_headers
57
+ end
58
+ end
59
+
60
+ def authenticated_headers
61
+ default_headers.merge({
62
+ "X-Auth-Token" => @config.latest_access_token
63
+ })
64
+ end
65
+
66
+ def default_headers
67
+ {
68
+ "Content-Type" => "application/json",
69
+ "Accept" => "application/json"
70
+ }
71
+ end
72
+
73
+ def persistent?
74
+ @config.connection_persistent?
75
+ end
76
+
77
+ def connection_options
78
+ @config.storage_connection_options
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end