fog-brightbox 0.3.0 → 0.4.0

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 (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