From d4b2c4fa4b124bc2b0d5ee988e48786a4caa5f32 Mon Sep 17 00:00:00 2001 From: David Georgiou Date: Thu, 17 Oct 2024 13:51:39 +0100 Subject: [PATCH 1/7] adds get_blob_tags to client --- lib/azure_blob/client.rb | 14 ++++++++++++++ lib/azure_blob/tags.rb | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 lib/azure_blob/tags.rb diff --git a/lib/azure_blob/client.rb b/lib/azure_blob/client.rb index f310f70..c6a433a 100644 --- a/lib/azure_blob/client.rb +++ b/lib/azure_blob/client.rb @@ -156,6 +156,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.new(response).to_hash + end # Returns a Container object. # diff --git a/lib/azure_blob/tags.rb b/lib/azure_blob/tags.rb new file mode 100644 index 0000000..08131a5 --- /dev/null +++ b/lib/azure_blob/tags.rb @@ -0,0 +1,20 @@ +require 'rexml/document' + +module AzureBlob + class Tags + def initialize(xml_response) + @xml_response = xml_response + end + + def to_hash + document = REXML::Document.new(@xml_response) + tags = {} + document.elements.each('Tags/TagSet/Tag') do |tag| + key = tag.elements['Key'].text + value = tag.elements['Value'].text + tags[key] = value + end + tags + end + end +end From 8da96c95544ea4a17d368d007ee4446a3d83fdaf Mon Sep 17 00:00:00 2001 From: David Georgiou Date: Tue, 22 Oct 2024 15:36:31 +0100 Subject: [PATCH 2/7] require tag --- lib/azure_blob/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/azure_blob/client.rb b/lib/azure_blob/client.rb index c6a433a..f992238 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" From b684fffca35f8252c527a133514ebabb200f3c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9=20Dupuis?= Date: Thu, 31 Oct 2024 16:30:26 -0700 Subject: [PATCH 3/7] Allow setting tags through when uploading a blob --- lib/azure_blob/client.rb | 10 +++++----- lib/azure_blob/http.rb | 8 ++++++-- lib/azure_blob/tags.rb | 31 +++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/azure_blob/client.rb b/lib/azure_blob/client.rb index f992238..a3efc21 100644 --- a/lib/azure_blob/client.rb +++ b/lib/azure_blob/client.rb @@ -157,7 +157,7 @@ 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. @@ -169,7 +169,7 @@ def get_blob_tags(key) uri = generate_uri("#{container}/#{key}?comp=tags") response = Http.new(uri, signer:).get - Tags.new(response).to_hash + Tags.from_response(response).to_h end # Returns a Container object. @@ -245,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 @@ -320,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 @@ -352,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 index 08131a5..a85b87e 100644 --- a/lib/azure_blob/tags.rb +++ b/lib/azure_blob/tags.rb @@ -1,20 +1,35 @@ require 'rexml/document' module AzureBlob - class Tags - def initialize(xml_response) - @xml_response = xml_response - end - - def to_hash - document = REXML::Document.new(@xml_response) + 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 - tags + 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 From 62e0f54710217f1e524abf4e448442af7eb5426f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9=20Dupuis?= Date: Thu, 31 Oct 2024 16:30:51 -0700 Subject: [PATCH 4/7] test_get_blob_tags --- test/client/test_client.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/client/test_client.rb b/test/client/test_client.rb index a34368f..0eb1b62 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 From 3b2acd730e69492a19a6eb008a4153f93c57a2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9=20Dupuis?= Date: Thu, 31 Oct 2024 17:11:27 -0700 Subject: [PATCH 5/7] Switch vm role to support tags --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } From 9a9b3f9387ad9a7ebed6c14ea4ae9a7f8f975fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9=20Dupuis?= Date: Thu, 31 Oct 2024 17:13:26 -0700 Subject: [PATCH 6/7] Lint --- lib/azure_blob/client.rb | 2 +- lib/azure_blob/tags.rb | 14 +++++++------- test/client/test_client.rb | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/azure_blob/client.rb b/lib/azure_blob/client.rb index a3efc21..e89cf7c 100644 --- a/lib/azure_blob/client.rb +++ b/lib/azure_blob/client.rb @@ -29,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 diff --git a/lib/azure_blob/tags.rb b/lib/azure_blob/tags.rb index a85b87e..f5c22ef 100644 --- a/lib/azure_blob/tags.rb +++ b/lib/azure_blob/tags.rb @@ -1,13 +1,13 @@ -require 'rexml/document' +require "rexml/document" module AzureBlob class Tags # :nodoc: - def self.from_response response + 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 + document.elements.each("Tags/TagSet/Tag") do |tag| + key = tag.elements["Key"].text + value = tag.elements["Value"].text tags[key] = value end new(tags) @@ -23,8 +23,8 @@ def headers { "x-ms-tags": @tags.map do |key, value| - %{#{key}=#{value}} - end.join("&") + %(#{key}=#{value}) + end.join("&"), } end diff --git a/test/client/test_client.rb b/test/client/test_client.rb index 0eb1b62..89ea6de 100644 --- a/test/client/test_client.rb +++ b/test/client/test_client.rb @@ -352,10 +352,10 @@ def test_create_container end def test_get_blob_tags - client.create_block_blob(key, content, tags: { tag1: "value 1", "tag 2": "value 2"}) + 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) + assert_equal({ "tag1" => "value 1", "tag 2" => "value 2" }, tags) end end From e34af31c270cbd88a590634d038a3882231b781c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9=20Dupuis?= Date: Thu, 31 Oct 2024 17:18:42 -0700 Subject: [PATCH 7/7] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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