fog 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Gemfile.lock +1 -1
  2. data/README.rdoc +1 -0
  3. data/fog.gemspec +44 -1
  4. data/lib/fog.rb +2 -1
  5. data/lib/fog/bin.rb +2 -0
  6. data/lib/fog/google.rb +22 -0
  7. data/lib/fog/google/bin.rb +20 -0
  8. data/lib/fog/google/models/storage/directories.rb +43 -0
  9. data/lib/fog/google/models/storage/directory.rb +48 -0
  10. data/lib/fog/google/models/storage/file.rb +87 -0
  11. data/lib/fog/google/models/storage/files.rb +94 -0
  12. data/lib/fog/google/parsers/storage/access_control_list.rb +46 -0
  13. data/lib/fog/google/parsers/storage/copy_object.rb +22 -0
  14. data/lib/fog/google/parsers/storage/get_bucket.rb +46 -0
  15. data/lib/fog/google/parsers/storage/get_bucket_logging.rb +40 -0
  16. data/lib/fog/google/parsers/storage/get_bucket_object_versions.rb +88 -0
  17. data/lib/fog/google/parsers/storage/get_bucket_versioning.rb +24 -0
  18. data/lib/fog/google/parsers/storage/get_request_payment.rb +20 -0
  19. data/lib/fog/google/parsers/storage/get_service.rb +32 -0
  20. data/lib/fog/google/requests/storage/copy_object.rb +72 -0
  21. data/lib/fog/google/requests/storage/delete_bucket.rb +46 -0
  22. data/lib/fog/google/requests/storage/delete_object.rb +50 -0
  23. data/lib/fog/google/requests/storage/get_bucket.rb +110 -0
  24. data/lib/fog/google/requests/storage/get_bucket_acl.rb +55 -0
  25. data/lib/fog/google/requests/storage/get_object.rb +104 -0
  26. data/lib/fog/google/requests/storage/get_object_acl.rb +66 -0
  27. data/lib/fog/google/requests/storage/get_object_torrent.rb +55 -0
  28. data/lib/fog/google/requests/storage/get_object_url.rb +54 -0
  29. data/lib/fog/google/requests/storage/get_service.rb +53 -0
  30. data/lib/fog/google/requests/storage/head_object.rb +64 -0
  31. data/lib/fog/google/requests/storage/put_bucket.rb +68 -0
  32. data/lib/fog/google/requests/storage/put_bucket_acl.rb +80 -0
  33. data/lib/fog/google/requests/storage/put_object.rb +71 -0
  34. data/lib/fog/google/requests/storage/put_object_url.rb +54 -0
  35. data/lib/fog/google/storage.rb +192 -0
  36. data/spec/google/models/storage/directories_spec.rb +49 -0
  37. data/spec/google/models/storage/directory_spec.rb +83 -0
  38. data/spec/google/models/storage/file_spec.rb +121 -0
  39. data/spec/google/models/storage/files_spec.rb +141 -0
  40. data/spec/google/requests/storage/copy_object_spec.rb +61 -0
  41. data/spec/google/requests/storage/delete_bucket_spec.rb +35 -0
  42. data/spec/google/requests/storage/delete_object_spec.rb +38 -0
  43. data/spec/google/requests/storage/get_bucket_spec.rb +110 -0
  44. data/spec/google/requests/storage/get_object_spec.rb +58 -0
  45. data/spec/google/requests/storage/get_service_spec.rb +32 -0
  46. data/spec/google/requests/storage/head_object_spec.rb +26 -0
  47. data/spec/google/requests/storage/put_bucket_spec.rb +21 -0
  48. data/spec/google/requests/storage/put_object_spec.rb +43 -0
  49. metadata +45 -2
@@ -0,0 +1,53 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ require 'fog/google/parsers/storage/get_service'
7
+
8
+ # List information about Google Storage buckets for authorized user
9
+ #
10
+ # ==== Returns
11
+ # * response<~Excon::Response>:
12
+ # * body<~Hash>:
13
+ # * 'Buckets'<~Hash>:
14
+ # * 'Name'<~String> - Name of bucket
15
+ # * 'CreationTime'<~Time> - Timestamp of bucket creation
16
+ # * 'Owner'<~Hash>:
17
+ # * 'DisplayName'<~String> - Display name of bucket owner
18
+ # * 'ID'<~String> - Id of bucket owner
19
+ def get_service
20
+ request({
21
+ :expects => 200,
22
+ :headers => {},
23
+ :host => @host,
24
+ :idempotent => true,
25
+ :method => 'GET',
26
+ :parser => Fog::Parsers::Google::Storage::GetService.new,
27
+ :url => @host
28
+ })
29
+ end
30
+
31
+ end
32
+
33
+ class Mock
34
+
35
+ def get_service
36
+ response = Excon::Response.new
37
+ response.headers['Status'] = 200
38
+ buckets = @data[:buckets].values.map do |bucket|
39
+ bucket.reject do |key, value|
40
+ !['CreationDate', 'Name'].include?(key)
41
+ end
42
+ end
43
+ response.body = {
44
+ 'Buckets' => buckets,
45
+ 'Owner' => { 'DisplayName' => 'owner', 'ID' => 'some_id'}
46
+ }
47
+ response
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,64 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ # Get headers for an object from Google Storage
7
+ #
8
+ # ==== Parameters
9
+ # * bucket_name<~String> - Name of bucket to read from
10
+ # * object_name<~String> - Name of object to read
11
+ # * options<~Hash>:
12
+ # * 'If-Match'<~String> - Returns object only if its etag matches this value, otherwise returns 412 (Precondition Failed).
13
+ # * 'If-Modified-Since'<~Time> - Returns object only if it has been modified since this time, otherwise returns 304 (Not Modified).
14
+ # * 'If-None-Match'<~String> - Returns object only if its etag differs from this value, otherwise returns 304 (Not Modified)
15
+ # * 'If-Unmodified-Since'<~Time> - Returns object only if it has not been modified since this time, otherwise returns 412 (Precodition Failed).
16
+ # * 'Range'<~String> - Range of object to download
17
+ # * 'versionId'<~String> - specify a particular version to retrieve
18
+ #
19
+ # ==== Returns
20
+ # * response<~Excon::Response>:
21
+ # * body<~String> - Contents of object
22
+ # * headers<~Hash>:
23
+ # * 'Content-Length'<~String> - Size of object contents
24
+ # * 'Content-Type'<~String> - MIME type of object
25
+ # * 'ETag'<~String> - Etag of object
26
+ # * 'Last-Modified'<~String> - Last modified timestamp for object
27
+ def head_object(bucket_name, object_name, options={})
28
+ unless bucket_name
29
+ raise ArgumentError.new('bucket_name is required')
30
+ end
31
+ unless object_name
32
+ raise ArgumentError.new('object_name is required')
33
+ end
34
+ if version_id = options.delete('versionId')
35
+ query = {'versionId' => version_id}
36
+ end
37
+ headers = {}
38
+ headers['If-Modified-Since'] = options['If-Modified-Since'].utc.strftime("%a, %d %b %Y %H:%M:%S +0000") if options['If-Modified-Since']
39
+ headers['If-Unmodified-Since'] = options['If-Unmodified-Since'].utc.strftime("%a, %d %b %Y %H:%M:%S +0000") if options['If-Modified-Since']
40
+ headers.merge!(options)
41
+ request({
42
+ :expects => 200,
43
+ :headers => headers,
44
+ :host => "#{bucket_name}.#{@host}",
45
+ :method => 'HEAD',
46
+ :path => CGI.escape(object_name),
47
+ :query => query
48
+ })
49
+ end
50
+
51
+ end
52
+
53
+ class Mock
54
+
55
+ def head_object(bucket_name, object_name, options = {})
56
+ response = get_object(bucket_name, object_name, options)
57
+ response.body = nil
58
+ response
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,68 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ # Create an Google Storage bucket
7
+ #
8
+ # ==== Parameters
9
+ # * bucket_name<~String> - name of bucket to create
10
+ # * options<~Hash> - config arguments for bucket. Defaults to {}.
11
+ # * :location_constraint<~Symbol> - sets the location for the bucket
12
+ #
13
+ # ==== Returns
14
+ # * response<~Excon::Response>:
15
+ # * status<~Integer> - 200
16
+ def put_bucket(bucket_name, options = {})
17
+ if options['LocationConstraint']
18
+ data =
19
+ <<-DATA
20
+ <CreateBucketConfiguration>
21
+ <LocationConstraint>#{options['LocationConstraint']}</LocationConstraint>
22
+ </CreateBucketConfiguration>
23
+ DATA
24
+ else
25
+ data = nil
26
+ end
27
+ request({
28
+ :expects => 200,
29
+ :body => data,
30
+ :headers => {},
31
+ :idempotent => true,
32
+ :host => "#{bucket_name}.#{@host}",
33
+ :method => 'PUT'
34
+ })
35
+ end
36
+
37
+ end
38
+
39
+ class Mock
40
+
41
+ def put_bucket(bucket_name, options = {})
42
+ response = Excon::Response.new
43
+ response.status = 200
44
+ bucket = {
45
+ :objects => {},
46
+ 'Name' => bucket_name,
47
+ 'CreationDate' => Time.now,
48
+ 'Owner' => { 'DisplayName' => 'owner', 'ID' => 'some_id'},
49
+ 'Payer' => 'BucketOwner'
50
+ }
51
+ if options['LocationConstraint']
52
+ bucket['LocationConstraint'] = options['LocationConstraint']
53
+ else
54
+ bucket['LocationConstraint'] = ''
55
+ end
56
+ if @data[:buckets][bucket_name].nil?
57
+ @data[:buckets][bucket_name] = bucket
58
+ else
59
+ response.status = 409
60
+ raise(Excon::Errors.status_error({:expects => 200}, response))
61
+ end
62
+ response
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,80 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ # Change access control list for an Google Storage bucket
7
+ #
8
+ # ==== Parameters
9
+ # * bucket_name<~String> - name of bucket to modify
10
+ # * acl<~Hash>:
11
+ # * Owner<~Hash>:
12
+ # * ID<~String>: id of owner
13
+ # * DisplayName<~String>: display name of owner
14
+ # * AccessControlList<~Array>:
15
+ # * Grantee<~Hash>:
16
+ # * 'DisplayName'<~String> - Display name of grantee
17
+ # * 'ID'<~String> - Id of grantee
18
+ # or
19
+ # * 'EmailAddress'<~String> - Email address of grantee
20
+ # or
21
+ # * 'URI'<~String> - URI of group to grant access for
22
+ # * Permission<~String> - Permission, in [FULL_CONTROL, WRITE, WRITE_ACP, READ, READ_ACP]
23
+ def put_bucket_acl(bucket_name, acl)
24
+ data =
25
+ <<-DATA
26
+ <AccessControlPolicy>
27
+ <Owner>
28
+ <ID>#{acl['Owner']['ID']}</ID>
29
+ <DisplayName>#{acl['Owner']['DisplayName']}</DisplayName>
30
+ </Owner>
31
+ <AccessControlList>
32
+ DATA
33
+
34
+ acl['AccessControlList'].each do |grant|
35
+ data << " <Grant>"
36
+ type = case grant['Grantee'].keys.sort
37
+ when ['DisplayName', 'ID']
38
+ 'CanonicalUser'
39
+ when ['EmailAddress']
40
+ 'AmazonCustomerByEmail'
41
+ when ['URI']
42
+ 'Group'
43
+ end
44
+ data << " <Grantee xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"#{type}\">"
45
+ for key, value in grant['Grantee']
46
+ data << " <#{key}>#{value}</#{key}>"
47
+ end
48
+ data << " </Grantee>"
49
+ data << " <Permission>#{grant['Permission']}</Permission>"
50
+ data << " </Grant>"
51
+ end
52
+
53
+ data <<
54
+ <<-DATA
55
+ </AccessControlList>
56
+ </AccessControlPolicy>
57
+ DATA
58
+
59
+ request({
60
+ :body => data,
61
+ :expects => 200,
62
+ :headers => {},
63
+ :host => "#{bucket_name}.#{@host}",
64
+ :method => 'PUT',
65
+ :query => {'acl' => nil}
66
+ })
67
+ end
68
+
69
+ end
70
+
71
+ class Mock
72
+
73
+ def put_bucket_acl(bucket_name, acl)
74
+ Fog::Mock.not_implemented
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ # Create an object in an Google Storage bucket
7
+ #
8
+ # ==== Parameters
9
+ # * bucket_name<~String> - Name of bucket to create object in
10
+ # * object_name<~String> - Name of object to create
11
+ # * data<~File> - File or String to create object from
12
+ # * options<~Hash>:
13
+ # * 'Cache-Control'<~String> - Caching behaviour
14
+ # * 'Content-Disposition'<~String> - Presentational information for the object
15
+ # * 'Content-Encoding'<~String> - Encoding of object data
16
+ # * 'Content-Length'<~String> - Size of object in bytes (defaults to object.read.length)
17
+ # * 'Content-MD5'<~String> - Base64 encoded 128-bit MD5 digest of message (defaults to Base64 encoded MD5 of object.read)
18
+ # * 'Content-Type'<~String> - Standard MIME type describing contents (defaults to MIME::Types.of.first)
19
+ # * 'x-goog-acl'<~String> - Permissions, must be in ['private', 'public-read', 'public-read-write', 'authenticated-read']
20
+ # * "x-goog-meta-#{name}" - Headers to be returned with object, note total size of request without body must be less than 8 KB.
21
+ #
22
+ # ==== Returns
23
+ # * response<~Excon::Response>:
24
+ # * headers<~Hash>:
25
+ # * 'ETag'<~String> - etag of new object
26
+ def put_object(bucket_name, object_name, data, options = {})
27
+ data = parse_data(data)
28
+ headers = data[:headers].merge!(options)
29
+ request({
30
+ :body => data[:body],
31
+ :expects => 200,
32
+ :headers => headers,
33
+ :host => "#{bucket_name}.#{@host}",
34
+ :idempotent => true,
35
+ :method => 'PUT',
36
+ :path => CGI.escape(object_name)
37
+ })
38
+ end
39
+
40
+ end
41
+
42
+ class Mock
43
+
44
+ def put_object(bucket_name, object_name, data, options = {})
45
+ data = parse_data(data)
46
+ unless data[:body].is_a?(String)
47
+ data[:body] = data[:body].read
48
+ end
49
+ response = Excon::Response.new
50
+ if (bucket = @data[:buckets][bucket_name])
51
+ response.status = 200
52
+ bucket[:objects][object_name] = {
53
+ :body => data[:body],
54
+ 'ETag' => Fog::Google::Mock.etag,
55
+ 'Key' => object_name,
56
+ 'LastModified' => Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S +0000"),
57
+ 'Size' => data[:headers]['Content-Length'],
58
+ 'StorageClass' => 'STANDARD'
59
+ }
60
+ bucket[:objects][object_name]['Content-Type'] = data[:headers]['Content-Type']
61
+ else
62
+ response.status = 404
63
+ raise(Excon::Errors.status_error({:expects => 200}, response))
64
+ end
65
+ response
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,54 @@
1
+ module Fog
2
+ module Google
3
+ class Storage
4
+ class Real
5
+
6
+ # Get an expiring object url from Google Storage for putting an object
7
+ #
8
+ # ==== Parameters
9
+ # * bucket_name<~String> - Name of bucket containing object
10
+ # * object_name<~String> - Name of object to get expiring url for
11
+ # * expires<~Time> - An expiry time for this url
12
+ #
13
+ # ==== Returns
14
+ # * response<~Excon::Response>:
15
+ # * body<~String> - url for object
16
+ #
17
+ def put_object_url(bucket_name, object_name, expires)
18
+ unless bucket_name
19
+ raise ArgumentError.new('bucket_name is required')
20
+ end
21
+ unless object_name
22
+ raise ArgumentError.new('object_name is required')
23
+ end
24
+ url({
25
+ :headers => {},
26
+ :host => "#{bucket_name}.#{@host}",
27
+ :method => 'PUT',
28
+ :path => CGI.escape(object_name)
29
+ }, expires)
30
+ end
31
+
32
+ end
33
+
34
+ class Mock
35
+
36
+ def put_object_url(bucket_name, object_name, expires)
37
+ unless bucket_name
38
+ raise ArgumentError.new('bucket_name is required')
39
+ end
40
+ unless object_name
41
+ raise ArgumentError.new('object_name is required')
42
+ end
43
+ url({
44
+ :headers => {},
45
+ :host => "#{bucket_name}.#{@host}",
46
+ :method => 'PUT',
47
+ :path => CGI.escape(object_name)
48
+ }, expires)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,192 @@
1
+ module Fog
2
+ module Google
3
+ class Storage < Fog::Service
4
+
5
+ requires :google_storage_access_key_id, :google_storage_secret_access_key
6
+
7
+ model_path 'fog/google/models/storage'
8
+ collection :directories
9
+ model :directory
10
+ collection :files
11
+ model :file
12
+
13
+ request_path 'fog/google/requests/storage'
14
+ request :copy_object
15
+ request :delete_bucket
16
+ request :delete_object
17
+ request :get_bucket
18
+ request :get_bucket_acl
19
+ request :get_object
20
+ request :get_object_acl
21
+ request :get_object_torrent
22
+ request :get_object_url
23
+ request :get_service
24
+ request :head_object
25
+ request :put_bucket
26
+ request :put_bucket_acl
27
+ request :put_object
28
+ request :put_object_url
29
+
30
+ module Utils
31
+
32
+ def parse_data(data)
33
+ metadata = {
34
+ :body => nil,
35
+ :headers => {}
36
+ }
37
+
38
+ if data.is_a?(String)
39
+ metadata[:body] = data
40
+ metadata[:headers]['Content-Length'] = metadata[:body].size.to_s
41
+ else
42
+ filename = ::File.basename(data.path)
43
+ unless (mime_types = MIME::Types.of(filename)).empty?
44
+ metadata[:headers]['Content-Type'] = mime_types.first.content_type
45
+ end
46
+ metadata[:body] = data
47
+ metadata[:headers]['Content-Length'] = ::File.size(data.path).to_s
48
+ end
49
+ # metadata[:headers]['Content-MD5'] = Base64.encode64(Digest::MD5.digest(metadata[:body])).strip
50
+ metadata
51
+ end
52
+
53
+ def url(params, expires)
54
+ params[:headers]['Date'] = expires.to_i
55
+ query = [params[:query]].compact
56
+ query << "GoogleAccessKeyId=#{@google_storage_access_key_id}"
57
+ query << "Signature=#{CGI.escape(signature(params))}"
58
+ query << "Expires=#{params[:headers]['Date']}"
59
+ "http://#{params[:host]}/#{params[:path]}?#{query.join('&')}"
60
+ end
61
+
62
+ end
63
+
64
+ class Mock
65
+ include Utils
66
+
67
+ def self.data
68
+ @data ||= Hash.new do |hash, key|
69
+ hash[key] = {
70
+ :buckets => {}
71
+ }
72
+ end
73
+ end
74
+
75
+ def self.reset_data(keys=data.keys)
76
+ for key in [*keys]
77
+ data.delete(key)
78
+ end
79
+ end
80
+
81
+ def initialize(options={})
82
+ @google_storage_access_key_id = options[:google_storage_access_key_id]
83
+ @data = self.class.data[@google_storage_access_key_id]
84
+ end
85
+
86
+ def signature(params)
87
+ "foo"
88
+ end
89
+ end
90
+
91
+ class Real
92
+ include Utils
93
+ extend Fog::Deprecation
94
+ deprecate(:reset, :reload)
95
+
96
+ # Initialize connection to Google Storage
97
+ #
98
+ # ==== Notes
99
+ # options parameter must include values for :google_storage_access_key_id and
100
+ # :google_storage_secret_access_key in order to create a connection
101
+ #
102
+ # ==== Examples
103
+ # google_storage = Storage.new(
104
+ # :google_storage_access_key_id => your_google_storage_access_key_id,
105
+ # :google_storage_secret_access_key => your_google_storage_secret_access_key
106
+ # )
107
+ #
108
+ # ==== Parameters
109
+ # * options<~Hash> - config arguments for connection. Defaults to {}.
110
+ #
111
+ # ==== Returns
112
+ # * Storage object with connection to google.
113
+ def initialize(options={})
114
+ @google_storage_access_key_id = options[:google_storage_access_key_id]
115
+ @google_storage_secret_access_key = options[:google_storage_secret_access_key]
116
+ @hmac = Fog::HMAC.new('sha1', @google_storage_secret_access_key)
117
+ @host = options[:host] || 'commondatastorage.googleapis.com'
118
+ @port = options[:port] || 443
119
+ @scheme = options[:scheme] || 'https'
120
+ @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", options[:persistent] || true)
121
+ end
122
+
123
+ def reload
124
+ @connection.reset
125
+ end
126
+
127
+ private
128
+
129
+ def request(params, &block)
130
+ params[:headers]['Date'] = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S +0000")
131
+ params[:headers]['Authorization'] = "GOOG1 #{@google_storage_access_key_id}:#{signature(params)}"
132
+
133
+ response = @connection.request(params, &block)
134
+
135
+ response
136
+ end
137
+
138
+ def signature(params)
139
+ string_to_sign =
140
+ <<-DATA
141
+ #{params[:method]}
142
+ #{params[:headers]['Content-MD5']}
143
+ #{params[:headers]['Content-Type']}
144
+ #{params[:headers]['Date']}
145
+ DATA
146
+
147
+ google_headers, canonical_google_headers = {}, ''
148
+ for key, value in params[:headers]
149
+ if key[0..6] == 'x-goog-'
150
+ google_headers[key] = value
151
+ end
152
+ end
153
+
154
+ google_headers = google_headers.sort {|x, y| x[0] <=> y[0]}
155
+ for key, value in google_headers
156
+ canonical_google_headers << "#{key}:#{value}\n"
157
+ end
158
+ string_to_sign << "#{canonical_google_headers}"
159
+
160
+ subdomain = params[:host].split(".#{@host}").first
161
+ unless subdomain =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}$))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]$/
162
+ Formatador.display_line("[yellow][WARN] fog: the specified google storage bucket name(#{subdomain}) is not a valid dns name. See: http://code.google.com/apis/storage/docs/developer-guide.html#naming[/]")
163
+ params[:host] = params[:host].split("#{subdomain}.")[-1]
164
+ if params[:path]
165
+ params[:path] = "#{subdomain}/#{params[:path]}"
166
+ else
167
+ params[:path] = "#{subdomain}"
168
+ end
169
+ subdomain = nil
170
+ end
171
+
172
+ canonical_resource = "/"
173
+ unless subdomain.nil? || subdomain == @host
174
+ canonical_resource << "#{CGI.escape(subdomain).downcase}/"
175
+ end
176
+ canonical_resource << "#{params[:path]}"
177
+ canonical_resource << '?'
178
+ for key in (params[:query] || {}).keys
179
+ if ['acl', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key)
180
+ canonical_resource << "#{key}&"
181
+ end
182
+ end
183
+ canonical_resource.chop!
184
+ string_to_sign << "#{canonical_resource}"
185
+
186
+ signed_string = @hmac.sign(string_to_sign)
187
+ signature = Base64.encode64(signed_string).chomp!
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end