diff --git a/CHANGELOG.md b/CHANGELOG.md index 95558c9..b78f229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] +- Add support for setting tags when uploading a blob +- Add get_blob_tags ## [0.5.2] 2024-09-12 diff --git a/lib/azure_blob/client.rb b/lib/azure_blob/client.rb index f310f70..e89cf7c 100644 --- a/lib/azure_blob/client.rb +++ b/lib/azure_blob/client.rb @@ -4,6 +4,7 @@ require_relative "blob_list" require_relative "blob" require_relative "container" +require_relative "tags" require_relative "http" require_relative "shared_key_signer" require_relative "entra_id_signer" @@ -28,7 +29,7 @@ def initialize(account_name:, access_key: nil, principal_id: nil, container:, ** ) end @signer = using_managed_identities ? - AzureBlob::EntraIdSigner.new(account_name:, host:, principal_id: ) : + AzureBlob::EntraIdSigner.new(account_name:, host:, principal_id:) : AzureBlob::SharedKeySigner.new(account_name:, access_key:) end @@ -157,6 +158,20 @@ def get_blob_properties(key, options = {}) Blob.new(response) end + # Returns the tags associated with a blob + # + # Calls to the {Get Blob Tags}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-tags] endpoint. + # + # Takes a key (path) of the blob. + # + # Returns a hash of the blob's tags. + 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 + # Returns a Container object. # # Calls to {Get Container Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-container-properties] @@ -230,7 +245,7 @@ def create_append_blob(key, options = {}) "x-ms-blob-content-disposition": options[:content_disposition], } - Http.new(uri, headers, metadata: options[:metadata], signer:).put(nil) + Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(nil) end # Append a block to an Append Blob @@ -305,7 +320,7 @@ def commit_blob_blocks(key, block_ids, options = {}) "x-ms-blob-content-disposition": options[:content_disposition], } - Http.new(uri, headers, metadata: options[:metadata], signer:).put(content) + Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(content) end private @@ -337,7 +352,7 @@ def put_blob_single(key, content, options = {}) "x-ms-blob-content-disposition": options[:content_disposition], } - Http.new(uri, headers, metadata: options[:metadata], signer:).put(content.read) + Http.new(uri, headers, signer:, **options.slice(:metadata, :tags)).put(content.read) end def host diff --git a/lib/azure_blob/http.rb b/lib/azure_blob/http.rb index d549149..d5436c1 100644 --- a/lib/azure_blob/http.rb +++ b/lib/azure_blob/http.rb @@ -24,12 +24,16 @@ class IntegrityError < Error; end include REXML - def initialize(uri, headers = {}, signer: nil, metadata: {}, debug: false, raise_on_error: true) + def initialize(uri, headers = {}, signer: nil, metadata: {}, tags: {}, debug: false, raise_on_error: true) @raise_on_error = raise_on_error @date = Time.now.httpdate @uri = uri @signer = signer - @headers = headers.merge(Metadata.new(metadata).headers) + @headers = headers.merge( + Metadata.new(metadata).headers, + Tags.new(tags).headers, + ) + sanitize_headers @http = Net::HTTP.new(uri.hostname, uri.port) diff --git a/lib/azure_blob/tags.rb b/lib/azure_blob/tags.rb new file mode 100644 index 0000000..f5c22ef --- /dev/null +++ b/lib/azure_blob/tags.rb @@ -0,0 +1,35 @@ +require "rexml/document" + +module AzureBlob + class Tags # :nodoc: + def self.from_response(response) + document = REXML::Document.new(response) + tags = {} + document.elements.each("Tags/TagSet/Tag") do |tag| + key = tag.elements["Key"].text + value = tag.elements["Value"].text + tags[key] = value + end + new(tags) + end + + def initialize(tags = nil) + @tags = tags || {} + end + + def headers + return {} if @tags.empty? + + { + "x-ms-tags": + @tags.map do |key, value| + %(#{key}=#{value}) + end.join("&"), + } + end + + def to_h + @tags + end + end +end diff --git a/main.tf b/main.tf index d243219..11a2441 100644 --- a/main.tf +++ b/main.tf @@ -106,7 +106,7 @@ resource "azurerm_user_assigned_identity" "vm" { resource "azurerm_role_assignment" "vm" { scope = azurerm_storage_account.main.id - role_definition_name = "Storage Blob Data Contributor" + role_definition_name = "Storage Blob Data Owner" principal_id = azurerm_user_assigned_identity.vm.principal_id } diff --git a/test/client/test_client.rb b/test/client/test_client.rb index a34368f..89ea6de 100644 --- a/test/client/test_client.rb +++ b/test/client/test_client.rb @@ -350,4 +350,12 @@ def test_create_container container = client.get_container_properties refute container.present? end + + def test_get_blob_tags + client.create_block_blob(key, content, tags: { tag1: "value 1", "tag 2": "value 2" }) + + tags = client.get_blob_tags(key) + + assert_equal({ "tag1" => "value 1", "tag 2" => "value 2" }, tags) + end end