class AzureBlob::Client
through an instance of this class.
AzureBlob Client class. You interact with the Azure Blob api
def append_blob_block(key, content, options = {})
Will ensure integrity of the upload. The checksum must be a base64 digest. Can be produced with +OpenSSL::Digest::MD5.base64digest+.
[+:content_md5+]
Options:
Calls to {Append Block}[https://learn.microsoft.com/en-us/rest/api/storageservices/append-block]
Append a block to an Append Blob
def append_blob_block(key, content, options = {}) uri = generate_uri("#{container}/#{key}") uri.query = URI.encode_www_form(comp: "appendblock") headers = { "Content-Length": content.size, "Content-Type": options[:content_type], "Content-MD5": options[:content_md5], } Http.new(uri, headers, signer:).put(content) end
def blob_exist?(key, options = {})
Returns a boolean indicating if the blob exists.
def blob_exist?(key, options = {}) get_blob_properties(key, options).present? rescue AzureBlob::Http::FileNotFoundError false end
def commit_blob_blocks(key, block_ids, options = {})
This is the checksum for the whole blob. The checksum is saved on the blob, but it is not validated!
[+:content_md5+]
Options:
Takes a key (path) and an array of block ids
Calls to {Put Block List}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list]
Commits the list of blocks to a blob.
def commit_blob_blocks(key, block_ids, options = {}) block_list = BlockList.new(block_ids) content = block_list.to_s uri = generate_uri("#{container}/#{key}") uri.query = URI.encode_www_form(comp: "blocklist") headers = { "Content-Length": content.size, "Content-Type": options[:content_type], "x-ms-blob-content-md5": options[:content_md5], "x-ms-blob-content-disposition": options[:content_disposition], } Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(content) end
def container_exist?(options = {})
Returns a boolean indicating if the container exists.
def container_exist?(options = {}) get_container_properties(options = {}).present? end
def create_append_blob(key, options = {})
[+:content_disposition+]
Will be saved on the blob in Azure.
[+:content_type+]
Options:
You are expected to append blocks to the blob with append_blob_block after creating the blob.
Calls to {Put Blob}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob]
Creates a Blob of type append.
def create_append_blob(key, options = {}) uri = generate_uri("#{container}/#{key}") headers = { "x-ms-blob-type": "AppendBlob", "Content-Length": 0, "Content-Type": options[:content_type], "Content-MD5": options[:content_md5], "x-ms-blob-content-disposition": options[:content_disposition], } Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(nil) end
def create_block_blob(key, content, options = {})
[+:block_size+]
a checksum for each block, then commit the blocks with commit_blob_blocks.
The checksum is only checked on a single upload! To verify checksum when uploading multiple blocks, call directly put_blob_block with
Will ensure integrity of the upload. The checksum must be a base64 digest. Can be produced with +OpenSSL::Digest::MD5.base64digest+.
[+:content_md5+]
Will be saved on the blob in Azure.
[+:content_disposition+]
Will be saved on the blob in Azure.
[+:content_type+]
Options:
Takes a key (path), the content (String or IO object), and options.
followed by a {Put Block List}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list] to commit the block list.
If the blob is too big, the blob is split in blocks sent through a series of {Put Block}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-block] requests
When the blob is small enough this method will send the blob through {Put Blob}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob]
Create a blob of type block. Will automatically split the the blob in multiple block and send the blob in pieces (blocks) if the blob is too big.
def create_block_blob(key, content, options = {}) if content.size > (options[:block_size] || DEFAULT_BLOCK_SIZE) put_blob_multiple(key, content, **options) else put_blob_single(key, content, **options) end end
def create_container(options = {})
Create the container
def create_container(options = {}) uri = generate_uri(container) headers = {} headers[:"x-ms-blob-public-access"] = "blob" if options[:public_access] headers[:"x-ms-blob-public-access"] = options[:public_access] if ["container","blob"].include?(options[:public_access]) uri.query = URI.encode_www_form(restype: "container") response = Http.new(uri, headers, signer:).put end
def delete_blob(key, options = {})
[+:delete_snapshots+]
Options:
Takes a key (path) and options.
Calls to {Delete Blob}[https://learn.microsoft.com/en-us/rest/api/storageservices/delete-blob]
Delete a blob
def delete_blob(key, options = {}) uri = generate_uri("#{container}/#{key}") headers = { "x-ms-delete-snapshots": options[:delete_snapshots] || "include", } Http.new(uri, headers, signer:).delete end
def delete_container(options = {})
Delete the container
def delete_container(options = {}) uri = generate_uri(container) uri.query = URI.encode_www_form(restype: "container") response = Http.new(uri, signer:).delete end
def delete_prefix(prefix, options = {})
Takes a prefix and options
followed to a series of calls to {Delete Blob}[https://learn.microsoft.com/en-us/rest/api/storageservices/delete-blob]
Calls to {List blobs}[https://learn.microsoft.com/en-us/rest/api/storageservices/list-blobs]
Delete all blobs prefixed by the given prefix.
def delete_prefix(prefix, options = {}) results = list_blobs(prefix:) results.each { |key| delete_blob(key) } end
def generate_block_id(index)
def generate_block_id(index) Base64.urlsafe_encode64(index.to_s.rjust(6, "0")) end
def generate_uri(path)
Return a URI object to a resource in the container. Takes a path.
def generate_uri(path) URI.parse(URI::DEFAULT_PARSER.escape(File.join(host, path))) end
def get_blob(key, options = {})
[+:end+]
Starting point in bytes
[+:start+]
Options:
Takes a key (path) and options.
Calls to the {Get Blob}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob] endpoint.
Returns the full or partial content of the blob
def get_blob(key, options = {}) uri = generate_uri("#{container}/#{key}") headers = { "x-ms-range": options[:start] && "bytes=#{options[:start]}-#{options[:end]}", } Http.new(uri, headers, signer:).get end
def get_blob_properties(key, options = {})
This can be used to obtain metadata such as content type, disposition, checksum or Azure custom metadata.
Calls to {Get Blob Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-properties]
Returns a Blob object without the content.
def get_blob_properties(key, options = {}) uri = generate_uri("#{container}/#{key}") response = Http.new(uri, signer:).head Blob.new(response) end
def get_blob_tags(key)
Takes a key (path) of the blob.
Calls to the {Get Blob Tags}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-tags] endpoint.
Returns the tags associated with a blob
def get_blob_tags(key) uri = generate_uri("#{container}/#{key}?comp=tags") response = Http.new(uri, signer:).get Tags.from_response(response).to_h end
def get_container_properties(options = {})
Calls to {Get Container Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-container-properties]
Returns a Container object.
def get_container_properties(options = {}) uri = generate_uri(container) uri.query = URI.encode_www_form(restype: "container") response = Http.new(uri, signer:, raise_on_error: false).head Container.new(response) end
def host
def host @host ||= "https://#{account_name}.blob.#{CLOUD_REGIONS_SUFFIX[cloud_regions]}" end
def initialize(account_name:, access_key: nil, principal_id: nil, container:, host: nil, **options)
def initialize(account_name:, access_key: nil, principal_id: nil, container:, host: nil, **options) @account_name = account_name @container = container @host = host @cloud_regions = options[:cloud_regions]&.to_sym || :global @access_key = access_key @principal_id = principal_id @use_managed_identities = options[:use_managed_identities] signer unless options[:lazy] end
def list_blobs(options = {})
[:+max_results+]
Prefix of the blobs to be listed. Defaults to listing everything in the container.
[+:prefix+]
Options:
Calls to {List blobs}[https://learn.microsoft.com/en-us/rest/api/storageservices/list-blobs]
Returns a BlobList containing a list of keys (paths)
def list_blobs(options = {}) uri = generate_uri(container) query = { comp: "list", restype: "container", prefix: options[:prefix].to_s.gsub(/\\/, "/"), } query[:maxresults] = options[:max_results] if options[:max_results] uri.query = URI.encode_www_form(**query) fetcher = ->(marker) do query[:marker] = marker query.reject! { |key, value| value.to_s.empty? } uri.query = URI.encode_www_form(**query) response = Http.new(uri, signer:).get end BlobList.new(fetcher) end
def put_blob_block(key, index, content, options = {})
[+:content_md5+]
Options:
Returns the id of the block. Required to commit the list of blocks to a blob.
Calls to {Put Block}[https://learn.microsoft.com/en-us/rest/api/storageservices/put-block]
Uploads a block to a blob.
def put_blob_block(key, index, content, options = {}) block_id = generate_block_id(index) uri = generate_uri("#{container}/#{key}") uri.query = URI.encode_www_form(comp: "block", blockid: block_id) headers = { "Content-Length": content.size, "Content-Type": options[:content_type], "Content-MD5": options[:content_md5], } Http.new(uri, headers, signer:).put(content) block_id end
def put_blob_multiple(key, content, options = {})
def put_blob_multiple(key, content, options = {}) content = StringIO.new(content) if content.is_a? String block_size = options[:block_size] || DEFAULT_BLOCK_SIZE block_count = (content.size.to_f / block_size).ceil block_ids = block_count.times.map do |i| put_blob_block(key, i, content.read(block_size)) end commit_blob_blocks(key, block_ids, options) end
def put_blob_single(key, content, options = {})
def put_blob_single(key, content, options = {}) content = StringIO.new(content) if content.is_a? String uri = generate_uri("#{container}/#{key}") headers = { "x-ms-blob-type": "BlockBlob", "Content-Length": content.size, "Content-Type": options[:content_type], "x-ms-blob-content-md5": options[:content_md5], "x-ms-blob-content-disposition": options[:content_disposition], } Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(content.read) end
def signed_uri(key, permissions:, expiry:, **options)
- expiry as a UTC iso8601 time string
- A permission string (+"r"+, +"rw"+)
- key (path)
Takes a
Returns an SAS signed URI
def signed_uri(key, permissions:, expiry:, **options) uri = generate_uri("#{container}/#{key}") uri.query = signer.sas_token(uri, permissions:, expiry:, **options) uri end
def signer
def signer @signer ||= begin no_access_key = access_key.nil? || access_key&.empty? using_managed_identities = no_access_key && !principal_id.nil? || use_managed_identities if !using_managed_identities && no_access_key raise AzureBlob::Error.new( "`access_key` cannot be empty. To use managed identities instead, pass a `principal_id` or set `use_managed_identities` to true." ) end using_managed_identities ? AzureBlob::EntraIdSigner.new(account_name:, host:, principal_id:) : AzureBlob::SharedKeySigner.new(account_name:, access_key:, host:) end end