fog-softlayer 0.0.5 → 0.0.7

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 (48) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +37 -0
  3. data/CONTRIBUTING.md +30 -0
  4. data/README.md +8 -5
  5. data/docs/cla-corporate.md +133 -0
  6. data/docs/cla-individual.md +84 -0
  7. data/docs/fog-softlayer-CCLA.pdf +0 -0
  8. data/docs/fog-softlayer-CLA.pdf +0 -0
  9. data/examples/storage.md +107 -0
  10. data/fog-softlayer.gemspec +1 -0
  11. data/gemfiles/Gemfile-edge +14 -0
  12. data/gemfiles/Gemfile-ruby-1.8.7 +14 -0
  13. data/lib/fog/softlayer.rb +1 -0
  14. data/lib/fog/softlayer/compute.rb +1 -1
  15. data/lib/fog/softlayer/compute/shared.rb +1 -1
  16. data/lib/fog/softlayer/core.rb +17 -1
  17. data/lib/fog/softlayer/models/storage/directories.rb +40 -0
  18. data/lib/fog/softlayer/models/storage/directory.rb +50 -0
  19. data/lib/fog/softlayer/models/storage/file.rb +166 -0
  20. data/lib/fog/softlayer/models/storage/files.rb +99 -0
  21. data/lib/fog/softlayer/requests/storage/copy_object.rb +42 -0
  22. data/lib/fog/softlayer/requests/storage/delete_container.rb +38 -0
  23. data/lib/fog/softlayer/requests/storage/delete_multiple_objects.rb +67 -0
  24. data/lib/fog/softlayer/requests/storage/delete_object.rb +40 -0
  25. data/lib/fog/softlayer/requests/storage/delete_static_large_object.rb +43 -0
  26. data/lib/fog/softlayer/requests/storage/get_container.rb +68 -0
  27. data/lib/fog/softlayer/requests/storage/get_containers.rb +51 -0
  28. data/lib/fog/softlayer/requests/storage/get_object.rb +45 -0
  29. data/lib/fog/softlayer/requests/storage/get_object_https_url.rb +88 -0
  30. data/lib/fog/softlayer/requests/storage/head_container.rb +28 -0
  31. data/lib/fog/softlayer/requests/storage/head_containers.rb +25 -0
  32. data/lib/fog/softlayer/requests/storage/head_object.rb +23 -0
  33. data/lib/fog/softlayer/requests/storage/post_set_meta_temp_url_key.rb +37 -0
  34. data/lib/fog/softlayer/requests/storage/put_container.rb +32 -0
  35. data/lib/fog/softlayer/requests/storage/put_dynamic_obj_manifest.rb +43 -0
  36. data/lib/fog/softlayer/requests/storage/put_object.rb +61 -0
  37. data/lib/fog/softlayer/requests/storage/put_object_manifest.rb +16 -0
  38. data/lib/fog/softlayer/requests/storage/put_static_obj_manifest.rb +57 -0
  39. data/lib/fog/softlayer/storage.rb +283 -0
  40. data/lib/fog/softlayer/version.rb +1 -1
  41. data/tests/helper.rb +0 -29
  42. data/tests/helpers/mock_helper.rb +4 -98
  43. data/tests/softlayer/models/storage/directory_tests.rb +49 -0
  44. data/tests/softlayer/models/storage/file_tests.rb +56 -0
  45. data/tests/softlayer/requests/storage/auth_tests.rb +17 -0
  46. data/tests/softlayer/requests/storage/container_tests.rb +52 -0
  47. data/tests/softlayer/requests/storage/object_tests.rb +68 -0
  48. metadata +53 -2
@@ -0,0 +1,42 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def copy_object(source_container, source_object, target_container, target_object, options={})
6
+ response = Excon::Response.new
7
+ if @containers[source_container].nil? || @containers[source_container][source_object].nil? || @containers[target_container].nil?
8
+ response.body = '<html><h1>Not Found</h1><p>The resource could not be found.</p></html>'
9
+ response.status = 404
10
+ else # Success
11
+ @containers[target_container][target_object] = @containers[source_container][source_object]
12
+ response.body = ''
13
+ response.status = 201
14
+ end
15
+ response
16
+ end
17
+ end
18
+
19
+ class Real
20
+
21
+ # Copy object
22
+ #
23
+ # ==== Parameters
24
+ # * source_container_name<~String> - Name of source bucket
25
+ # * source_object_name<~String> - Name of source object
26
+ # * target_container_name<~String> - Name of bucket to create copy in
27
+ # * target_object_name<~String> - Name for new copy of object
28
+ # * options<~Hash> - Additional headers
29
+ def copy_object(source_container, source_object, target_container, target_object, options={})
30
+ headers = { 'X-Copy-From' => "/#{source_container}/#{source_object}" }.merge(options)
31
+ request({
32
+ :expects => 201,
33
+ :headers => headers,
34
+ :method => 'PUT',
35
+ :path => "#{Fog::Softlayer.escape(target_container)}/#{Fog::Softlayer.escape(target_object)}"
36
+ })
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def delete_container(name)
6
+ response = Excon::Response.new
7
+ if @containers[name].nil? # Container doesn't exist.
8
+ response.body = '<html><h1>Not Found</h1><p>The resource could not be found.</p></html>'
9
+ response.status = 404
10
+ elsif @containers[name].length > 0 # Container not empty
11
+ response.body = '<html><h1>Conflict</h1><p>There was a conflict when trying to complete your request.</p></html>'
12
+ response.status = 409
13
+ else # Success
14
+ response.body = ''
15
+ response.status = 204
16
+ end
17
+ response
18
+ end
19
+ end
20
+
21
+ class Real
22
+ # Delete an existing container
23
+ #
24
+ # ==== Parameters
25
+ # * name<~String> - Name of container to delete
26
+ #
27
+ def delete_container(name)
28
+ request(
29
+ :expects => 204,
30
+ :method => 'DELETE',
31
+ :path => Fog::Softlayer.escape(name)
32
+ )
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Real
5
+
6
+ # Deletes multiple objects or containers with a single request.
7
+ #
8
+ # To delete objects from a single container, +container+ may be provided
9
+ # and +object_names+ should be an Array of object names within the container.
10
+ #
11
+ # To delete objects from multiple containers or delete containers,
12
+ # +container+ should be +nil+ and all +object_names+ should be prefixed with a container name.
13
+ #
14
+ # Containers must be empty when deleted. +object_names+ are processed in the order given,
15
+ # so objects within a container should be listed first to empty the container.
16
+ #
17
+ # Up to 10,000 objects may be deleted in a single request.
18
+ # The server will respond with +200 OK+ for all requests.
19
+ # +response.body+ must be inspected for actual results.
20
+ #
21
+ # @example Delete objects from a container
22
+ # object_names = ['object', 'another/object']
23
+ # conn.delete_multiple_objects('my_container', object_names)
24
+ #
25
+ # @example Delete objects from multiple containers
26
+ # object_names = ['container_a/object', 'container_b/object']
27
+ # conn.delete_multiple_objects(nil, object_names)
28
+ #
29
+ # @example Delete a container and all it's objects
30
+ # object_names = ['my_container/object_a', 'my_container/object_b', 'my_container']
31
+ # conn.delete_multiple_objects(nil, object_names)
32
+ #
33
+ # @param container [String,nil] Name of container.
34
+ # @param object_names [Array<String>] Object names to be deleted.
35
+ # @param options [Hash] Additional request headers.
36
+ #
37
+ # @return [Excon::Response]
38
+ # * body [Hash] - Results of the operation.
39
+ # * "Number Not Found" [Integer] - Number of missing objects or containers.
40
+ # * "Response Status" [String] - Response code for the subrequest of the last failed operation.
41
+ # * "Errors" [Array<object_name, response_status>]
42
+ # * object_name [String] - Object that generated an error when the delete was attempted.
43
+ # * response_status [String] - Response status from the subrequest for object_name.
44
+ # * "Number Deleted" [Integer] - Number of objects or containers deleted.
45
+ # * "Response Body" [String] - Response body for "Response Status".
46
+ def delete_multiple_objects(container, object_names, options = {})
47
+ body = object_names.map do |name|
48
+ object_name = container ? "#{ container }/#{ name }" : name
49
+ URI.encode(object_name)
50
+ end.join("\n")
51
+
52
+ response = request({
53
+ :expects => 200,
54
+ :method => 'DELETE',
55
+ :headers => options.merge('Content-Type' => 'text/plain',
56
+ 'Accept' => 'application/json'),
57
+ :body => body,
58
+ :query => { 'bulk-delete' => true }
59
+ }, false)
60
+ response.body = Fog::JSON.decode(response.body)
61
+ response
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+
6
+ def delete_object(container, object)
7
+ response = Excon::Response.new
8
+ if @containers[container].nil? || @containers[container][object].nil? # Container or object doesn't exist.
9
+ response.body = '<html><h1>Not Found</h1><p>The resource could not be found.</p></html>'
10
+ response.status = 404
11
+ else # Success
12
+ response.body = ''
13
+ response.status = 204
14
+ end
15
+ response
16
+ end
17
+
18
+ end
19
+
20
+
21
+ class Real
22
+
23
+ # Delete an existing object
24
+ #
25
+ # ==== Parameters
26
+ # * container<~String> - Name of container to delete
27
+ # * object<~String> - Name of object to delete
28
+ #
29
+ def delete_object(container, object)
30
+ request(
31
+ :expects => 204,
32
+ :method => 'DELETE',
33
+ :path => "#{Fog::Softlayer.escape(container)}/#{Fog::Softlayer.escape(object)}"
34
+ )
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Real
5
+
6
+ # Delete a static large object.
7
+ #
8
+ # Deletes the SLO manifest +object+ and all segments that it references.
9
+ # The server will respond with +200 OK+ for all requests.
10
+ # +response.body+ must be inspected for actual results.
11
+ #
12
+ # @param container [String] Name of container.
13
+ # @param object [String] Name of the SLO manifest object.
14
+ # @param options [Hash] Additional request headers.
15
+ #
16
+ # @return [Excon::Response]
17
+ # * body [Hash] - Results of the operation.
18
+ # * "Number Not Found" [Integer] - Number of missing segments.
19
+ # * "Response Status" [String] - Response code for the subrequest of the last failed operation.
20
+ # * "Errors" [Array<object_name, response_status>]
21
+ # * object_name [String] - Object that generated an error when the delete was attempted.
22
+ # * response_status [String] - Response status from the subrequest for object_name.
23
+ # * "Number Deleted" [Integer] - Number of segments deleted.
24
+ # * "Response Body" [String] - Response body for Response Status.
25
+ #
26
+ # @see http://docs.openstack.org/api/openstack-object-storage/1.0/content/static-large-objects.html
27
+ def delete_static_large_object(container, object, options = {})
28
+ response = request({
29
+ :expects => 200,
30
+ :method => 'DELETE',
31
+ :headers => options.merge('Content-Type' => 'text/plain',
32
+ 'Accept' => 'application/json'),
33
+ :path => "#{Fog::Softlayer.escape(container)}/#{Fog::Softlayer.escape(object)}",
34
+ :query => { 'multipart-manifest' => 'delete' }
35
+ }, false)
36
+ response.body = Fog::JSON.decode(response.body)
37
+ response
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,68 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def get_container(container, options = {})
6
+ if @containers[container]
7
+ response = Excon::Response.new
8
+ response.body = @containers[container].map do |name, object|
9
+ {
10
+ 'hash' => object.respond_to?(:to_s) ? Digest::MD5.hexdigest(object.to_s) : 'e4d909c290d0fb1ca068ffaddf22cbd0',
11
+ 'last_modified' => Time.now,
12
+ 'bytes' => Memory.analyze(container).bytes,
13
+ 'content/type' => 'application/json'
14
+ }
15
+ end
16
+ response.status = 200
17
+ response
18
+ else
19
+ response = Excon::Response.new
20
+ response.body = '<html><h1>Not Found</h1><p>The resource could not be found.</p></html>'
21
+ response.status = 404
22
+ response.headers = {"Content-Length"=>"70", "Content-Type"=>"text/html; charset=UTF-8", "X-Trans-Id"=>"abcdefghijklmnopqrstuvwx-0123456789", "Date"=>Time.now}
23
+ response
24
+ end
25
+ end
26
+ end
27
+
28
+ class Real
29
+
30
+ # Get details for container and total bytes stored
31
+ #
32
+ # ==== Parameters
33
+ # * container<~String> - Name of container to retrieve info for
34
+ # * options<~String>:
35
+ # * 'limit'<~String> - Maximum number of objects to return
36
+ # * 'marker'<~String> - Only return objects whose name is greater than marker
37
+ # * 'prefix'<~String> - Limits results to those starting with prefix
38
+ # * 'path'<~String> - Return objects nested in the pseudo path
39
+ #
40
+ # ==== Returns
41
+ # * response<~Excon::Response>:
42
+ # * headers<~Hash>:
43
+ # * 'X-Account-Container-Count'<~String> - Count of containers
44
+ # * 'X-Account-Bytes-Used'<~String> - Bytes used
45
+ # * body<~Array>:
46
+ # * 'bytes'<~Integer> - Number of bytes used by container
47
+ # * 'count'<~Integer> - Number of items in container
48
+ # * 'name'<~String> - Name of container
49
+ # * item<~Hash>:
50
+ # * 'bytes'<~String> - Size of object
51
+ # * 'content_type'<~String> Content-Type of object
52
+ # * 'hash'<~String> - Hash of object (etag?)
53
+ # * 'last_modified'<~String> - Last modified timestamp
54
+ # * 'name'<~String> - Name of object
55
+ def get_container(container, options = {})
56
+ options = options.reject {|key, value| value.nil?}
57
+ request(
58
+ :expects => 200,
59
+ :method => 'GET',
60
+ :path => Fog::Softlayer.escape(container),
61
+ :query => {'format' => 'json'}.merge!(options)
62
+ )
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,51 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def get_containers(options = {})
6
+ response = Excon::Response.new
7
+ response.body = _format_containers(@containers)
8
+ response.status = 200
9
+ response
10
+ end
11
+
12
+ private
13
+
14
+ def _format_containers(containers)
15
+ containers.map do |name, container|
16
+ meta = Memory.analyze(container)
17
+ {'count' => container.length, 'bytes' => meta.bytes, 'name' => name}
18
+ end
19
+ end
20
+ end
21
+
22
+ class Real
23
+
24
+ # List existing storage containers
25
+ #
26
+ # ==== Parameters
27
+ # * options<~Hash>:
28
+ # * 'limit'<~Integer> - Upper limit to number of results returned
29
+ # * 'marker'<~String> - Only return objects with name greater than this value
30
+ #
31
+ # ==== Returns
32
+ # * response<~Excon::Response>:
33
+ # * body<~Array>:
34
+ # * container<~Hash>:
35
+ # * 'bytes'<~Integer>: - Number of bytes used by container
36
+ # * 'count'<~Integer>: - Number of items in container
37
+ # * 'name'<~String>: - Name of container
38
+ def get_containers(options = {})
39
+ options = options.reject {|key, value| value.nil?}
40
+ request(
41
+ :expects => [200, 204],
42
+ :method => 'GET',
43
+ :path => '',
44
+ :query => {'format' => 'json'}.merge!(options)
45
+ )
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def get_object(container, object, &block)
6
+ if @containers[container] && @containers[container][object]
7
+ response = Excon::Response.new
8
+ response.body = @containers[container][object]
9
+ response.status = 200
10
+ response
11
+ else
12
+ response = Excon::Response.new
13
+ response.body = '<html><h1>Not Found</h1><p>The resource could not be found.</p></html>'
14
+ response.status = 404
15
+ response.headers = {"Content-Length"=>"70", "Content-Type"=>"text/html; charset=UTF-8", "X-Trans-Id"=>"abcdefghijklmnopqrstuvwx-0123456789", "Date"=>Time.now}
16
+ response
17
+ end
18
+ end
19
+ end
20
+ class Real
21
+
22
+ # Get details for object
23
+ #
24
+ # ==== Parameters
25
+ # * container<~String> - Name of container to look in
26
+ # * object<~String> - Name of object to look for
27
+ #
28
+ def get_object(container, object, &block)
29
+ params = {
30
+ :expects => 200,
31
+ :method => 'GET',
32
+ :path => "#{Fog::Softlayer.escape(container)}/#{Fog::Softlayer.escape(object)}"
33
+ }
34
+
35
+ if block_given?
36
+ params[:response_block] = block
37
+ end
38
+
39
+ request(params, false)
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,88 @@
1
+ module Fog
2
+ module Storage
3
+ class Softlayer
4
+ class Mock
5
+ def get_object_https_url(container, object, expires, options = {})
6
+ "https://cluster.objectstorage.softlayer.net:443/v1/AUTH_abcdefghijklmnopqrstuvwxyz/#{container}/#{object}?temp_url_sig=1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a&temp_url_expires=1111111111111"
7
+ end
8
+ end
9
+
10
+ class Real
11
+
12
+ # Get an expiring object https url from Cloud Files
13
+ #
14
+ # ==== Parameters
15
+ # * container<~String> - Name of container containing object
16
+ # * object<~String> - Name of object to get expiring url for
17
+ # * expires<~Time> - An expiry time for this url
18
+ #
19
+ # ==== Returns
20
+ # * response<~Excon::Response>:
21
+ # * body<~String> - url for object
22
+ def get_object_https_url(container, object, expires, options = {})
23
+ create_temp_url(container, object, expires, "GET", options.merge(:scheme => "https"))
24
+ end
25
+
26
+ # creates a temporary url
27
+ #
28
+ # ==== Parameters
29
+ # * container<~String> - Name of container containing object
30
+ # * object<~String> - Name of object to get expiring url for
31
+ # * expires<~Time> - An expiry time for this url
32
+ # * method<~String> - The method to use for accessing the object (GET, PUT, HEAD)
33
+ # * scheme<~String> - The scheme to use (http, https)
34
+ # * options<~Hash> - An optional options hash
35
+ #
36
+ # ==== Returns
37
+ # * response<~Excon::Response>:
38
+ # * body<~String> - url for object
39
+ #
40
+ def create_temp_url(container, object, expires, method, options = {})
41
+ raise ArgumentError, "Insufficient parameters specified." unless (container && object && expires && method)
42
+ raise ArgumentError, "Storage must be instantiated with the :temp_url_key option" if @temp_url_key.nil?
43
+
44
+ scheme = options[:scheme] || @scheme
45
+
46
+ # POST not allowed
47
+ allowed_methods = %w{GET PUT HEAD}
48
+ unless allowed_methods.include?(method)
49
+ raise ArgumentError.new("Invalid method '#{method}' specified. Valid methods are: #{allowed_methods.join(', ')}")
50
+ end
51
+
52
+ expires = expires.to_i
53
+ object_path_escaped = "#{@path}/#{Fog::Softlayer.escape(container)}/#{Fog::Softlayer.escape(object,"/")}"
54
+ object_path_unescaped = "#{@path}/#{Fog::Softlayer.escape(container)}/#{object}"
55
+ string_to_sign = "#{method}\n#{expires}\n#{object_path_unescaped}"
56
+
57
+ hmac = Fog::HMAC.new('sha1', @temp_url_key)
58
+ sig = sig_to_hex(hmac.sign(string_to_sign))
59
+
60
+ temp_url_options = {
61
+ :scheme => scheme,
62
+ :host => @host,
63
+ :port => @port,
64
+ :path => object_path_escaped,
65
+ :query => URI.encode_www_form(
66
+ :temp_url_sig => sig,
67
+ :temp_url_expires => expires
68
+ )
69
+ }
70
+
71
+ URI::Generic.build(temp_url_options).to_s
72
+ end
73
+
74
+ private
75
+
76
+ def sig_to_hex(str)
77
+ str.unpack("C*").map { |c|
78
+ c.to_s(16)
79
+ }.map { |h|
80
+ h.size == 1 ? "0#{h}" : h
81
+ }.join
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
88
+ end