miasma-azure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,356 @@
1
+ require 'stringio'
2
+ require 'miasma'
3
+
4
+ module Miasma
5
+ module Models
6
+ class Storage
7
+ class Azure < Storage
8
+
9
+ # Required attributes for this API
10
+ REQUIRED_ATTRIBUTES = [
11
+ :azure_blob_secret_key,
12
+ :azure_blob_account_name
13
+ ]
14
+
15
+ include Contrib::AzureApiCore::ApiCommon
16
+
17
+ # Fetch all results when tokens are being used
18
+ # for paging results
19
+ #
20
+ # @param next_token [String]
21
+ # @param result_key [Array<String, Symbol>] path to result
22
+ # @yield block to perform request
23
+ # @yieldparam options [Hash] request parameters (token information)
24
+ # @return [Array]
25
+ # @note this is customized to S3 since its API is slightly
26
+ # different than the usual token based fetching
27
+ def all_result_pages(next_token, *result_key, &block)
28
+ list = []
29
+ options = next_token ? Smash.new('marker' => next_token) : Smash.new
30
+ result = block.call(options)
31
+ content = result.get(*result_key.dup)
32
+ if(content.is_a?(Array))
33
+ list += content
34
+ else
35
+ list << content
36
+ end
37
+ set = result.get(*result_key.slice(0, 2))
38
+ if(set.is_a?(Hash) && set['IsTruncated'] && set['Contents'])
39
+ content_key = (set['Contents'].respond_to?(:last) ? set['Contents'].last : set['Contents'])['Key']
40
+ list += all_result_pages(content_key, *result_key, &block)
41
+ end
42
+ list.compact
43
+ end
44
+
45
+ # @return [Contrib::AzureApiCore::SignatureAzure::SasBlob]
46
+ attr_reader :url_signer
47
+
48
+ # Simple init override to force HOST and adjust region for
49
+ # signatures if required
50
+ def initialize(args)
51
+ args = args.to_smash
52
+ REQUIRED_ATTRIBUTES.each do |name|
53
+ unless(args[name])
54
+ raise ArgumentError.new "Missing required credential `#{name}`!"
55
+ end
56
+ end
57
+ @signer = Contrib::AzureApiCore::SignatureAzure.new(
58
+ args[:azure_blob_secret_key],
59
+ args[:azure_blob_account_name]
60
+ )
61
+ @url_signer = Contrib::AzureApiCore::SignatureAzure::SasBlob.new(
62
+ args[:azure_blob_secret_key],
63
+ args[:azure_blob_account_name]
64
+ )
65
+ super(args)
66
+ end
67
+
68
+ # @return [String] supported API version
69
+ def api_version
70
+ '2015-04-05'
71
+ end
72
+
73
+ # @return [String] API endoint
74
+ def endpoint
75
+ "https://#{azure_blob_account_name}.blob.core.windows.net"
76
+ end
77
+
78
+ # Save bucket
79
+ #
80
+ # @param bucket [Models::Storage::Bucket]
81
+ # @return [Models::Storage::Bucket]
82
+ def bucket_save(bucket)
83
+ unless(bucket.persisted?)
84
+ request(
85
+ :path => bucket.name,
86
+ :method => :put,
87
+ :expects => 201,
88
+ :params => {
89
+ :restype => 'container'
90
+ },
91
+ :headers => {
92
+ 'Content-Length' => 0
93
+ }
94
+ )
95
+ bucket.id = bucket.name
96
+ bucket.valid_state
97
+ end
98
+ bucket
99
+ end
100
+
101
+ # Destroy bucket
102
+ #
103
+ # @param bucket [Models::Storage::Bucket]
104
+ # @return [TrueClass, FalseClass]
105
+ def bucket_destroy(bucket)
106
+ if(bucket.persisted?)
107
+ request(
108
+ :path => bucket.name,
109
+ :method => :delete,
110
+ :expects => 202,
111
+ :params => {
112
+ :restype => 'container'
113
+ }
114
+ )
115
+ true
116
+ else
117
+ false
118
+ end
119
+ end
120
+
121
+ # Reload the bucket
122
+ #
123
+ # @param bucket [Models::Storage::Bucket]
124
+ # @return [Models::Storage::Bucket]
125
+ def bucket_reload(bucket)
126
+ if(bucket.persisted?)
127
+ begin
128
+ result = request(
129
+ :path => bucket.name,
130
+ :method => :head,
131
+ :params => {
132
+ :restype => 'container'
133
+ }
134
+ )
135
+ rescue Error::ApiError::RequestError => e
136
+ if(e.response.status == 404)
137
+ bucket.data.clear
138
+ bucket.dirty.clear
139
+ else
140
+ raise
141
+ end
142
+ end
143
+ end
144
+ bucket
145
+ end
146
+
147
+ # Return all buckets
148
+ #
149
+ # @return [Array<Models::Storage::Bucket>]
150
+ def bucket_all
151
+ result = request(
152
+ :path => '/',
153
+ :params => {
154
+ :comp => 'list'
155
+ }
156
+ )
157
+ cont = result.get(:body, 'EnumerationResults', 'Containers', 'Container')
158
+ unless(cont.is_a?(Array))
159
+ cont = [cont].compact
160
+ end
161
+ cont.map do |bkt|
162
+ Bucket.new(
163
+ self,
164
+ :id => bkt['Name'],
165
+ :name => bkt['Name'],
166
+ :custom => bkt['Properties']
167
+ ).valid_state
168
+ end
169
+ end
170
+
171
+ # Return filtered files
172
+ #
173
+ # @param args [Hash] filter options
174
+ # @return [Array<Models::Storage::File>]
175
+ def file_filter(bucket, args)
176
+ result = request(
177
+ :path => bucket.name,
178
+ :params => {
179
+ :restype => 'container',
180
+ :comp => 'list',
181
+ :prefix => args[:prefix]
182
+ }
183
+ )
184
+ [result.get(:body, 'EnumerationResults', 'Blobs', 'Blob')].flatten.compact.map do |file|
185
+ File.new(
186
+ bucket,
187
+ :id => ::File.join(bucket.name, file['Name']),
188
+ :name => file['Name'],
189
+ :updated => file.get('Properties', 'Last_Modified'),
190
+ :size => file.get('Properties', 'Content_Length').to_i
191
+ ).valid_state
192
+ end
193
+ end
194
+
195
+ # Return all files within bucket
196
+ #
197
+ # @param bucket [Bucket]
198
+ # @return [Array<File>]
199
+ def file_all(bucket)
200
+ file_filter(bucket, {})
201
+ end
202
+
203
+ # Save file
204
+ #
205
+ # @param file [Models::Storage::File]
206
+ # @return [Models::Storage::File]
207
+ def file_save(file)
208
+ if(file.dirty?)
209
+ file.load_data(file.attributes)
210
+ args = Smash.new
211
+ headers = Smash[
212
+ Smash.new(
213
+ :content_type => 'Content-Type',
214
+ :content_disposition => 'Content-Disposition',
215
+ :content_encoding => 'Content-Encoding'
216
+ ).map do |attr, key|
217
+ if(file.attributes[attr])
218
+ [key, file.attributes[attr]]
219
+ end
220
+ end.compact
221
+ ]
222
+ unless(headers.empty?)
223
+ args[:headers] = headers
224
+ end
225
+ if(file.attributes[:body].respond_to?(:readpartial))
226
+ args.set(:headers, 'Content-Length', file.body.size.to_s)
227
+ file.body.rewind
228
+ args[:body] = file.body.readpartial(file.body.size)
229
+ file.body.rewind
230
+ else
231
+ args.set(:headers, 'Content-Length', 0)
232
+ end
233
+ args.set(:headers, 'x-ms-blob-type', 'BlockBlob')
234
+ result = request(
235
+ args.merge(
236
+ Smash.new(
237
+ :method => :put,
238
+ :path => [file.bucket.name, file_path(file)].join('/'),
239
+ :expects => 201
240
+ )
241
+ )
242
+ )
243
+ file.etag = result.get(:headers, :etag)
244
+ file.id = ::File.join(file.bucket.name, file.name)
245
+ file.valid_state
246
+ end
247
+ file
248
+ end
249
+
250
+ # Destroy file
251
+ #
252
+ # @param file [Models::Storage::File]
253
+ # @return [TrueClass, FalseClass]
254
+ def file_destroy(file)
255
+ if(file.persisted?)
256
+ request(
257
+ :method => :delete,
258
+ :path => [file.bucket.name, file_path(file)].join('/'),
259
+ :expects => 202
260
+ )
261
+ true
262
+ else
263
+ false
264
+ end
265
+ end
266
+
267
+ # Reload the file
268
+ #
269
+ # @param file [Models::Storage::File]
270
+ # @return [Models::Storage::File]
271
+ def file_reload(file)
272
+ if(file.persisted?)
273
+ name = file.name
274
+ result = request(
275
+ :method => :head,
276
+ :path => [file.bucket.name, file_path(file)].join('/')
277
+ )
278
+ file.data.clear && file.dirty.clear
279
+ info = result[:headers]
280
+ file.load_data(
281
+ :id => [file.bucket.name, name].join('/'),
282
+ :name => name,
283
+ :updated => info[:last_modified],
284
+ :etag => info[:etag],
285
+ :size => info[:content_length].to_i,
286
+ :content_type => info[:content_type]
287
+ ).valid_state
288
+ end
289
+ file
290
+ end
291
+
292
+ # Create publicly accessible URL
293
+ #
294
+ # @param timeout_secs [Integer] seconds available
295
+ # @return [String] URL
296
+ def file_url(file, timeout_secs)
297
+ object_path = [file.bucket.name, file_path(file)].join('/')
298
+ sign_args = Smash.new(
299
+ :params => Smash.new(
300
+ :sr => 'b',
301
+ :sv => api_version,
302
+ :se => (Time.now.utc + timeout_secs).iso8601,
303
+ :sp => 'r'
304
+ )
305
+ )
306
+ signature = url_signer.generate(:get, object_path, sign_args)
307
+ uri = URI.parse([endpoint, object_path].join('/'))
308
+ uri.query = URI.encode_www_form(
309
+ sign_args[:params].merge(
310
+ :sig => signature
311
+ )
312
+ )
313
+ uri.to_s
314
+ end
315
+
316
+ # Fetch the contents of the file
317
+ #
318
+ # @param file [Models::Storage::File]
319
+ # @return [IO, HTTP::Response::Body]
320
+ def file_body(file)
321
+ file_content = nil
322
+ if(file.persisted?)
323
+ result = request(
324
+ :path => [file.bucket.name, file_path(file)].join('/'),
325
+ :disable_body_extraction => true
326
+ )
327
+ content = result[:body]
328
+ begin
329
+ if(content.is_a?(String))
330
+ file_content = StringIO.new(content)
331
+ else
332
+ if(content.respond_to?(:stream!))
333
+ content.stream!
334
+ end
335
+ file_content = content
336
+ end
337
+ rescue HTTP::StateError
338
+ file_content = StringIO.new(content.to_s)
339
+ end
340
+ else
341
+ file_content = StringIO.new('')
342
+ end
343
+ File::Streamable.new(file_content)
344
+ end
345
+
346
+ # @return [String] escaped file path
347
+ def file_path(file)
348
+ file.name.split('/').map do |part|
349
+ uri_escape(part)
350
+ end.join('/')
351
+ end
352
+
353
+ end
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'miasma-azure/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'miasma-azure'
5
+ s.version = MiasmaAzure::VERSION.version
6
+ s.summary = 'Smoggy Azure API'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'code@chrisroberts.org'
9
+ s.homepage = 'https://github.com/miasma-rb/miasma-azure'
10
+ s.description = 'Smoggy Azure API'
11
+ s.license = 'Apache 2.0'
12
+ s.require_path = 'lib'
13
+ s.add_development_dependency 'miasma', '>= 0.2.38'
14
+ s.add_development_dependency 'rake'
15
+ s.add_development_dependency 'rubocop'
16
+ s.add_development_dependency 'pry'
17
+ s.add_development_dependency 'vcr'
18
+ s.add_development_dependency 'mocha'
19
+ s.add_development_dependency 'webmock'
20
+ s.add_development_dependency 'minitest'
21
+ s.add_development_dependency 'minitest-vcr'
22
+ s.files = Dir['lib/**/*'] + %w(miasma-azure.gemspec README.md CHANGELOG.md LICENSE)
23
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miasma-azure
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Roberts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: miasma
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.38
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.38
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mocha
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest-vcr
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: Smoggy Azure API
140
+ email: code@chrisroberts.org
141
+ executables: []
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - CHANGELOG.md
146
+ - LICENSE
147
+ - README.md
148
+ - lib/miasma-azure.rb
149
+ - lib/miasma-azure/api.rb
150
+ - lib/miasma-azure/version.rb
151
+ - lib/miasma/contrib/azure.rb
152
+ - lib/miasma/contrib/azure/orchestration.rb
153
+ - lib/miasma/contrib/azure/storage.rb
154
+ - miasma-azure.gemspec
155
+ homepage: https://github.com/miasma-rb/miasma-azure
156
+ licenses:
157
+ - Apache 2.0
158
+ metadata: {}
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubyforge_project:
175
+ rubygems_version: 2.4.8
176
+ signing_key:
177
+ specification_version: 4
178
+ summary: Smoggy Azure API
179
+ test_files: []
180
+ has_rdoc: