Class: Miasma::Models::Storage::Azure

Inherits:
Miasma::Models::Storage show all
Includes:
Contrib::AzureApiCore::ApiCommon
Defined in:
lib/miasma/contrib/azure/storage.rb

Constant Summary collapse

REQUIRED_ATTRIBUTES =

Required attributes for this API

[
  :azure_blob_secret_key,
  :azure_blob_account_name
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Contrib::AzureApiCore::ApiCommon

#access_token_expired?, #client_access_token, #connect, #connection, included, #make_request, #oauth_token_buffer_seconds, #oauth_token_information, #perform_request_retry, #request_client_token, #retryable_allowed?, #uri_escape

Constructor Details

#initialize(args) ⇒ Azure

Simple init override to force HOST and adjust region for signatures if required



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/miasma/contrib/azure/storage.rb', line 50

def initialize(args)
  args = args.to_smash
  REQUIRED_ATTRIBUTES.each do |name|
    unless(args[name])
      raise ArgumentError.new "Missing required credential `#{name}`!"
    end
  end
  @signer = Contrib::AzureApiCore::SignatureAzure.new(
    args[:azure_blob_secret_key],
    args[:azure_blob_account_name]
  )
  @url_signer = Contrib::AzureApiCore::SignatureAzure::SasBlob.new(
    args[:azure_blob_secret_key],
    args[:azure_blob_account_name]
  )
  super(args)
end

Instance Attribute Details

#url_signerContrib::AzureApiCore::SignatureAzure::SasBlob (readonly)



46
47
48
# File 'lib/miasma/contrib/azure/storage.rb', line 46

def url_signer
  @url_signer
end

Instance Method Details

#all_result_pages(next_token, *result_key) {|options| ... } ⇒ Array

Note:

this is customized to S3 since its API is slightly different than the usual token based fetching

Fetch all results when tokens are being used for paging results

Parameters:

  • next_token (String)
  • result_key (Array<String, Symbol>)

    path to result

Yields:

  • block to perform request

Yield Parameters:

  • options (Hash)

    request parameters (token information)

Returns:

  • (Array)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/miasma/contrib/azure/storage.rb', line 27

def all_result_pages(next_token, *result_key, &block)
  list = []
  options = next_token ? Smash.new('marker' => next_token) : Smash.new
  result = block.call(options)
  content = result.get(*result_key.dup)
  if(content.is_a?(Array))
    list += content
  else
    list << content
  end
  set = result.get(*result_key.slice(0, 2))
  if(set.is_a?(Hash) && set['IsTruncated'] && set['Contents'])
    content_key = (set['Contents'].respond_to?(:last) ? set['Contents'].last : set['Contents'])['Key']
    list += all_result_pages(content_key, *result_key, &block)
  end
  list.compact
end

#api_versionString

Returns supported API version.

Returns:

  • (String)

    supported API version



69
70
71
# File 'lib/miasma/contrib/azure/storage.rb', line 69

def api_version
  '2015-04-05'
end

#bucket_allArray<Models::Storage::Bucket>

Return all buckets

Returns:

  • (Array<Models::Storage::Bucket>)


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/miasma/contrib/azure/storage.rb', line 150

def bucket_all
  result = request(
    :path => '/',
    :params => {
      :comp => 'list'
    }
  )
  cont = result.get(:body, 'EnumerationResults', 'Containers', 'Container')
  unless(cont.is_a?(Array))
    cont = [cont].compact
  end
  cont.map do |bkt|
    Bucket.new(
      self,
      :id => bkt['Name'],
      :name => bkt['Name'],
      :custom => bkt['Properties']
    ).valid_state
  end
end

#bucket_destroy(bucket) ⇒ TrueClass, FalseClass

Destroy bucket

Parameters:

  • bucket (Models::Storage::Bucket)

Returns:

  • (TrueClass, FalseClass)


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/miasma/contrib/azure/storage.rb', line 105

def bucket_destroy(bucket)
  if(bucket.persisted?)
    request(
      :path => bucket.name,
      :method => :delete,
      :expects => 202,
      :params => {
        :restype => 'container'
      }
    )
    true
  else
    false
  end
end

#bucket_reload(bucket) ⇒ Models::Storage::Bucket

Reload the bucket

Parameters:

  • bucket (Models::Storage::Bucket)

Returns:

  • (Models::Storage::Bucket)


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/miasma/contrib/azure/storage.rb', line 125

def bucket_reload(bucket)
  if(bucket.persisted?)
    begin
      result = request(
        :path => bucket.name,
        :method => :head,
        :params => {
          :restype => 'container'
        }
      )
    rescue Error::ApiError::RequestError => e
      if(e.response.status == 404)
        bucket.data.clear
        bucket.dirty.clear
      else
        raise
      end
    end
  end
  bucket
end

#bucket_save(bucket) ⇒ Models::Storage::Bucket

Save bucket

Parameters:

  • bucket (Models::Storage::Bucket)

Returns:

  • (Models::Storage::Bucket)


82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/miasma/contrib/azure/storage.rb', line 82

def bucket_save(bucket)
  unless(bucket.persisted?)
    request(
      :path => bucket.name,
      :method => :put,
      :expects => 201,
      :params => {
        :restype => 'container'
      },
      :headers => {
        'Content-Length' => 0
      }
    )
    bucket.id = bucket.name
    bucket.valid_state
  end
  bucket
end

#endpointString

Returns API endoint.

Returns:

  • (String)

    API endoint



74
75
76
# File 'lib/miasma/contrib/azure/storage.rb', line 74

def endpoint
  "https://#{}.blob.core.windows.net"
end

#file_all(bucket) ⇒ Array<File>

Return all files within bucket

Parameters:

  • bucket (Bucket)

Returns:

  • (Array<File>)


199
200
201
# File 'lib/miasma/contrib/azure/storage.rb', line 199

def file_all(bucket)
  file_filter(bucket, {})
end

#file_body(file) ⇒ IO, HTTP::Response::Body

Fetch the contents of the file

Parameters:

  • file (Models::Storage::File)

Returns:

  • (IO, HTTP::Response::Body)


320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/miasma/contrib/azure/storage.rb', line 320

def file_body(file)
  file_content = nil
  if(file.persisted?)
    result = request(
      :path => [file.bucket.name, file_path(file)].join('/'),
      :disable_body_extraction => true
    )
    content = result[:body]
    begin
      if(content.is_a?(String))
        file_content = StringIO.new(content)
      else
        if(content.respond_to?(:stream!))
          content.stream!
        end
        file_content = content
      end
    rescue HTTP::StateError
      file_content = StringIO.new(content.to_s)
    end
  else
    file_content = StringIO.new('')
  end
  File::Streamable.new(file_content)
end

#file_destroy(file) ⇒ TrueClass, FalseClass

Destroy file

Parameters:

  • file (Models::Storage::File)

Returns:

  • (TrueClass, FalseClass)


254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/miasma/contrib/azure/storage.rb', line 254

def file_destroy(file)
  if(file.persisted?)
    request(
      :method => :delete,
      :path => [file.bucket.name, file_path(file)].join('/'),
      :expects => 202
    )
    true
  else
    false
  end
end

#file_filter(bucket, args) ⇒ Array<Models::Storage::File>

Return filtered files

Parameters:

  • args (Hash)

    filter options

Returns:

  • (Array<Models::Storage::File>)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/miasma/contrib/azure/storage.rb', line 175

def file_filter(bucket, args)
  result = request(
    :path => bucket.name,
    :params => {
      :restype => 'container',
      :comp => 'list',
      :prefix => args[:prefix]
    }
  )
  [result.get(:body, 'EnumerationResults', 'Blobs', 'Blob')].flatten.compact.map do |file|
    File.new(
      bucket,
      :id => ::File.join(bucket.name, file['Name']),
      :name => file['Name'],
      :updated => file.get('Properties', 'Last_Modified'),
      :size => file.get('Properties', 'Content_Length').to_i
    ).valid_state
  end
end

#file_path(file) ⇒ String

Returns escaped file path.

Returns:

  • (String)

    escaped file path



347
348
349
350
351
# File 'lib/miasma/contrib/azure/storage.rb', line 347

def file_path(file)
  file.name.split('/').map do |part|
    uri_escape(part)
  end.join('/')
end

#file_reload(file) ⇒ Models::Storage::File

Reload the file

Parameters:

  • file (Models::Storage::File)

Returns:

  • (Models::Storage::File)


271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/miasma/contrib/azure/storage.rb', line 271

def file_reload(file)
  if(file.persisted?)
    name = file.name
    result = request(
      :method => :head,
      :path => [file.bucket.name, file_path(file)].join('/')
    )
    file.data.clear && file.dirty.clear
    info = result[:headers]
    file.load_data(
      :id => [file.bucket.name, name].join('/'),
      :name => name,
      :updated => info[:last_modified],
      :etag => info[:etag],
      :size => info[:content_length].to_i,
      :content_type => info[:content_type]
    ).valid_state
  end
  file
end

#file_save(file) ⇒ Models::Storage::File

Save file

Parameters:

  • file (Models::Storage::File)

Returns:

  • (Models::Storage::File)


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/miasma/contrib/azure/storage.rb', line 207

def file_save(file)
  if(file.dirty?)
    file.load_data(file.attributes)
    args = Smash.new
    headers = Smash[
      Smash.new(
        :content_type => 'Content-Type',
        :content_disposition => 'Content-Disposition',
        :content_encoding => 'Content-Encoding'
      ).map do |attr, key|
        if(file.attributes[attr])
          [key, file.attributes[attr]]
        end
      end.compact
    ]
    unless(headers.empty?)
      args[:headers] = headers
    end
    if(file.attributes[:body].respond_to?(:readpartial))
      args.set(:headers, 'Content-Length', file.body.size.to_s)
      file.body.rewind
      args[:body] = file.body.readpartial(file.body.size)
      file.body.rewind
    else
      args.set(:headers, 'Content-Length', 0)
    end
    args.set(:headers, 'x-ms-blob-type', 'BlockBlob')
    result = request(
      args.merge(
        Smash.new(
          :method => :put,
          :path => [file.bucket.name, file_path(file)].join('/'),
          :expects => 201
        )
      )
    )
    file.etag = result.get(:headers, :etag)
    file.id = ::File.join(file.bucket.name, file.name)
    file.valid_state
  end
  file
end

#file_url(file, timeout_secs) ⇒ String

Create publicly accessible URL

Parameters:

  • timeout_secs (Integer)

    seconds available

Returns:

  • (String)

    URL



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/miasma/contrib/azure/storage.rb', line 296

def file_url(file, timeout_secs)
  object_path = [file.bucket.name, file_path(file)].join('/')
  sign_args = Smash.new(
    :params => Smash.new(
      :sr => 'b',
      :sv => api_version,
      :se => (Time.now.utc + timeout_secs).iso8601,
      :sp => 'r'
    )
  )
  signature = url_signer.generate(:get, object_path, sign_args)
  uri = URI.parse([endpoint, object_path].join('/'))
  uri.query = URI.encode_www_form(
    sign_args[:params].merge(
      :sig => signature
    )
  )
  uri.to_s
end