Skip to content
Open
2 changes: 1 addition & 1 deletion sdk/storage/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "cpp",
"TagPrefix": "cpp/storage",
"Tag": "cpp/storage_3da4d9917e"
"Tag": "cpp/storage_436298c53b"
}
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ namespace Azure { namespace Storage { namespace Blobs {
* @param tags The tags to set on the blob.
* @param options Optional parameters to execute this function.
* @param context Context for cancelling long running operations.
* @return A SetBlobTagsInfo on successfully setting tags.
* @return A SetBlobTagsResult on successfully setting tags.
*/
Azure::Response<Models::SetBlobTagsResult> SetTags(
std::map<std::string, std::string> tags,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ namespace Azure { namespace Storage { namespace Sas {
* corresponding root blob.
*/
BlobVersion,

/**
* @brief Grants access to the blobs in a virtual directory and to list blobs in the virtual
* directory.
*/
VirtualDirectory
};

/**
Expand Down Expand Up @@ -233,10 +239,24 @@ namespace Azure { namespace Storage { namespace Sas {
std::string BlobContainerName;

/**
* @brief The name of the blob being made accessible, or empty for a container SAS..
* @brief The name of the blob being made accessible, or empty for a container SAS.
* Beginning in version 2020-02-10, setting \p IsVirtualDirectory to true means this is a
* virtual directory name for a directory SAS. Do not prefix or suffix \p BlobName with slashes
* for directory SAS.
*/
std::string BlobName;

/**
* @brief This value defines whether \p BlobName is a virtual directory.
*/
bool IsVirtualDirectory = false;

/**
* @brief Required when \p IsVirtualDirectory is set to true. Depth of the virtual blob
* directory.
*/
Azure::Nullable<int32_t> VirtualDirectoryDepth;

/**
* @brief The name of the blob snapshot being made accessible, or empty for a container
* SAS and blob SAS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,8 @@ namespace Azure { namespace Storage { namespace Blobs {
AZ_STORAGE_BLOBS_DLLEXPORT const static AccessTier Premium;
/** Constant value of type AccessTier: Cold */
AZ_STORAGE_BLOBS_DLLEXPORT const static AccessTier Cold;
/** Constant value of type AccessTier: Smart */
AZ_STORAGE_BLOBS_DLLEXPORT const static AccessTier Smart;
};
/**
* @brief For blob storage LRS accounts, valid values are
Expand All @@ -1007,6 +1009,8 @@ namespace Azure { namespace Storage { namespace Blobs {
AZ_STORAGE_BLOBS_DLLEXPORT const static ArchiveStatus RehydratePendingToCool;
/** Constant value of type ArchiveStatus: RehydratePendingToCold */
AZ_STORAGE_BLOBS_DLLEXPORT const static ArchiveStatus RehydratePendingToCold;
/** Constant value of type ArchiveStatus: RehydratePendingToSmart */
AZ_STORAGE_BLOBS_DLLEXPORT const static ArchiveStatus RehydratePendingToSmart;
};
/**
* @brief Optional: Indicates the priority with which to rehydrate an archived blob.
Expand Down Expand Up @@ -1253,6 +1257,11 @@ namespace Azure { namespace Storage { namespace Blobs {
* destination tier.
*/
Nullable<Models::ArchiveStatus> ArchiveStatus;
/**
* The tier of page blob on a premium storage account or tier of block blob on blob storage or
* general purpose v2 account.
*/
Nullable<Models::AccessTier> SmartAccessTier;
/**
* SHA-256 hash of the encryption key.
*/
Expand Down Expand Up @@ -1786,6 +1795,10 @@ namespace Azure { namespace Storage { namespace Blobs {
* blob was ever set.
*/
Nullable<DateTime> AccessTierChangedOn;
/**
* The underlying tier of a smart tier blob. Only returned if the blob is in Smart tier.
*/
Nullable<Models::AccessTier> SmartAccessTier;
/**
* A DateTime value returned by the service that uniquely identifies the blob. The value of
* this header indicates the blob version, and may be used in subsequent requests to access
Expand Down
26 changes: 22 additions & 4 deletions sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ namespace Azure { namespace Storage { namespace Sas {
{
return "bv";
}
else if (resource == BlobSasResource::VirtualDirectory)
{
return "d";
}
else
{
throw std::invalid_argument("Unknown BlobSasResource value.");
Expand Down Expand Up @@ -171,7 +175,8 @@ namespace Azure { namespace Storage { namespace Sas {
{
std::string canonicalName = "/blob/" + credential.AccountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
|| Resource == BlobSasResource::BlobVersion
|| Resource == BlobSasResource::VirtualDirectory)
{
canonicalName += "/" + BlobName;
}
Expand Down Expand Up @@ -256,6 +261,11 @@ namespace Azure { namespace Storage { namespace Sas {
{
builder.AppendQueryParameter("ses", _internal::UrlEncodeQueryParameter(EncryptionScope));
}
if (IsVirtualDirectory && VirtualDirectoryDepth.HasValue())
{
builder.AppendQueryParameter(
"sdd", _internal::UrlEncodeQueryParameter(std::to_string(VirtualDirectoryDepth.Value())));
}

return builder.GetAbsoluteUrl();
}
Expand All @@ -266,7 +276,8 @@ namespace Azure { namespace Storage { namespace Sas {
{
std::string canonicalName = "/blob/" + accountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
|| Resource == BlobSasResource::BlobVersion
|| Resource == BlobSasResource::VirtualDirectory)
{
canonicalName += "/" + BlobName;
}
Expand Down Expand Up @@ -378,6 +389,11 @@ namespace Azure { namespace Storage { namespace Sas {
{
builder.AppendQueryParameter("ses", _internal::UrlEncodeQueryParameter(EncryptionScope));
}
if (IsVirtualDirectory && VirtualDirectoryDepth.HasValue())
{
builder.AppendQueryParameter(
"sdd", _internal::UrlEncodeQueryParameter(std::to_string(VirtualDirectoryDepth.Value())));
}
builder.AppendQueryParameter("sig", _internal::UrlEncodeQueryParameter(signature));

return builder.GetAbsoluteUrl();
Expand All @@ -387,7 +403,8 @@ namespace Azure { namespace Storage { namespace Sas {
{
std::string canonicalName = "/blob/" + credential.AccountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
|| Resource == BlobSasResource::BlobVersion
|| Resource == BlobSasResource::VirtualDirectory)
{
canonicalName += "/" + BlobName;
}
Expand Down Expand Up @@ -426,7 +443,8 @@ namespace Azure { namespace Storage { namespace Sas {
{
std::string canonicalName = "/blob/" + accountName + "/" + BlobContainerName;
if (Resource == BlobSasResource::Blob || Resource == BlobSasResource::BlobSnapshot
|| Resource == BlobSasResource::BlobVersion)
|| Resource == BlobSasResource::BlobVersion
|| Resource == BlobSasResource::VirtualDirectory)
{
canonicalName += "/" + BlobName;
}
Expand Down
27 changes: 27 additions & 0 deletions sdk/storage/azure-storage-blobs/src/rest_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,11 @@ namespace Azure { namespace Storage { namespace Blobs {
const AccessTier AccessTier::Archive("Archive");
const AccessTier AccessTier::Premium("Premium");
const AccessTier AccessTier::Cold("Cold");
const AccessTier AccessTier::Smart("Smart");
const ArchiveStatus ArchiveStatus::RehydratePendingToHot("rehydrate-pending-to-hot");
const ArchiveStatus ArchiveStatus::RehydratePendingToCool("rehydrate-pending-to-cool");
const ArchiveStatus ArchiveStatus::RehydratePendingToCold("rehydrate-pending-to-cold");
const ArchiveStatus ArchiveStatus::RehydratePendingToSmart("rehydrate-pending-to-smart");
const RehydratePriority RehydratePriority::High("High");
const RehydratePriority RehydratePriority::Standard("Standard");
const ObjectReplicationStatus ObjectReplicationStatus::Complete("complete");
Expand Down Expand Up @@ -2306,6 +2308,7 @@ namespace Azure { namespace Storage { namespace Blobs {
kAccessTier,
kAccessTierInferred,
kArchiveStatus,
kSmartAccessTier,
kCustomerProvidedKeySha256,
kEncryptionScope,
kAccessTierChangeTime,
Expand Down Expand Up @@ -2367,6 +2370,7 @@ namespace Azure { namespace Storage { namespace Blobs {
{"AccessTier", XmlTagEnum::kAccessTier},
{"AccessTierInferred", XmlTagEnum::kAccessTierInferred},
{"ArchiveStatus", XmlTagEnum::kArchiveStatus},
{"SmartAccessTier", XmlTagEnum::kSmartAccessTier},
{"CustomerProvidedKeySha256", XmlTagEnum::kCustomerProvidedKeySha256},
{"EncryptionScope", XmlTagEnum::kEncryptionScope},
{"AccessTierChangeTime", XmlTagEnum::kAccessTierChangeTime},
Expand Down Expand Up @@ -2651,6 +2655,14 @@ namespace Azure { namespace Storage { namespace Blobs {
{
vectorElement1.Details.ArchiveStatus = Models::ArchiveStatus(node.Value);
}
else if (
xmlPath.size() == 5 && xmlPath[0] == XmlTagEnum::kEnumerationResults
&& xmlPath[1] == XmlTagEnum::kBlobs && xmlPath[2] == XmlTagEnum::kBlob
&& xmlPath[3] == XmlTagEnum::kProperties
&& xmlPath[4] == XmlTagEnum::kSmartAccessTier)
{
vectorElement1.Details.SmartAccessTier = Models::AccessTier(node.Value);
}
else if (
xmlPath.size() == 5 && xmlPath[0] == XmlTagEnum::kEnumerationResults
&& xmlPath[1] == XmlTagEnum::kBlobs && xmlPath[2] == XmlTagEnum::kBlob
Expand Down Expand Up @@ -2996,6 +3008,7 @@ namespace Azure { namespace Storage { namespace Blobs {
kAccessTier,
kAccessTierInferred,
kArchiveStatus,
kSmartAccessTier,
kCustomerProvidedKeySha256,
kEncryptionScope,
kAccessTierChangeTime,
Expand Down Expand Up @@ -3059,6 +3072,7 @@ namespace Azure { namespace Storage { namespace Blobs {
{"AccessTier", XmlTagEnum::kAccessTier},
{"AccessTierInferred", XmlTagEnum::kAccessTierInferred},
{"ArchiveStatus", XmlTagEnum::kArchiveStatus},
{"SmartAccessTier", XmlTagEnum::kSmartAccessTier},
{"CustomerProvidedKeySha256", XmlTagEnum::kCustomerProvidedKeySha256},
{"EncryptionScope", XmlTagEnum::kEncryptionScope},
{"AccessTierChangeTime", XmlTagEnum::kAccessTierChangeTime},
Expand Down Expand Up @@ -3351,6 +3365,14 @@ namespace Azure { namespace Storage { namespace Blobs {
{
vectorElement1.Details.ArchiveStatus = Models::ArchiveStatus(node.Value);
}
else if (
xmlPath.size() == 5 && xmlPath[0] == XmlTagEnum::kEnumerationResults
&& xmlPath[1] == XmlTagEnum::kBlobs && xmlPath[2] == XmlTagEnum::kBlob
&& xmlPath[3] == XmlTagEnum::kProperties
&& xmlPath[4] == XmlTagEnum::kSmartAccessTier)
{
vectorElement1.Details.SmartAccessTier = Models::AccessTier(node.Value);
}
else if (
xmlPath.size() == 5 && xmlPath[0] == XmlTagEnum::kEnumerationResults
&& xmlPath[1] == XmlTagEnum::kBlobs && xmlPath[2] == XmlTagEnum::kBlob
Expand Down Expand Up @@ -4163,6 +4185,11 @@ namespace Azure { namespace Storage { namespace Blobs {
pRawResponse->GetHeaders().at("x-ms-access-tier-change-time"),
Azure::DateTime::DateFormat::Rfc1123);
}
if (pRawResponse->GetHeaders().count("x-ms-smart-access-tier") != 0)
{
response.SmartAccessTier
= Models::AccessTier(pRawResponse->GetHeaders().at("x-ms-smart-access-tier"));
}
if (pRawResponse->GetHeaders().count("x-ms-version-id") != 0)
{
response.VersionId = pRawResponse->GetHeaders().at("x-ms-version-id");
Expand Down
10 changes: 7 additions & 3 deletions sdk/storage/azure-storage-blobs/swagger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package-name: azure-storage-blobs
namespace: Azure::Storage::Blobs
output-folder: generated
clear-output-folder: true
input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/refs/heads/main/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-04-06/blob.json
input-file: https://raw.githubusercontent.com/nickliu-msft/azure-rest-api-specs/1ee23319226b26175ed9dbb27fc65d24932b9227/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-06-06/blob.json
```

## ModelFour Options
Expand Down Expand Up @@ -323,7 +323,8 @@ directive:
{"value": "Cool", "name": "Cool"},
{"value": "Archive", "name": "Archive"},
{"value": "Premium", "name": "Premium"},
{"value": "Cold", "name": "Cold"}
{"value": "Cold", "name": "Cold"},
{"value": "Smart", "name": "Smart"},
];
$.EncryptionAlgorithm = {
"type": "string",
Expand Down Expand Up @@ -1121,8 +1122,11 @@ directive:
$["x-ms-encryption-key-sha256"]["x-nullable"] = true;
$["x-ms-encryption-scope"]["x-nullable"] = true;
$["x-ms-access-tier"]["x-nullable"] = true;
$["x-ms-access-tier"]["enum"] = ["P1", "P2", "P3", "P4", "P6", "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", "P80", "Hot", "Cool", "Archive"];
$["x-ms-access-tier"]["enum"] = ["P1", "P2", "P3", "P4", "P6", "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", "P80", "Hot", "Cool", "Archive", "Smart"];
$["x-ms-access-tier"]["x-ms-enum"] = {"name": "AccessTier", "modelAsString": true};
$["x-ms-smart-access-tier"]["x-nullable"] = true;
$["x-ms-smart-access-tier"]["enum"] = ["P1", "P2", "P3", "P4", "P6", "P10", "P15", "P20", "P30", "P40", "P50", "P60", "P70", "P80", "Hot", "Cool", "Archive", "Smart"];
$["x-ms-smart-access-tier"]["x-ms-enum"] = {"name": "AccessTier", "modelAsString": true};
$["x-ms-access-tier-inferred"]["x-ms-client-name"] = "IsAccessTierInferred";
$["x-ms-access-tier-inferred"]["x-nullable"] = true;
$["x-ms-archive-status"]["x-nullable"] = true;
Expand Down
36 changes: 36 additions & 0 deletions sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1070,4 +1070,40 @@ namespace Azure { namespace Storage { namespace Test {
EXPECT_THROW(blobClient2.Download(downloadOptions), StorageException);
}

TEST_F(BlobSasTest, VirtualDirectorySas_LIVEONLY_)
{
auto sasStartsOn = std::chrono::system_clock::now() - std::chrono::minutes(5);
auto sasExpiresOn = std::chrono::system_clock::now() + std::chrono::minutes(60);

auto keyCredential
= _internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential;

const std::string blobName = "foo/bar/baz/qux/" + RandomString();
auto blobClient = GetBlockBlobClientForTest(blobName);
blobClient.UploadFrom(reinterpret_cast<const uint8_t*>("a"), 1);

Sas::BlobSasBuilder blobSasBuilder;
blobSasBuilder.Protocol = Sas::SasProtocol::HttpsOnly;
blobSasBuilder.StartsOn = sasStartsOn;
blobSasBuilder.ExpiresOn = sasExpiresOn;
blobSasBuilder.BlobContainerName = m_containerName;
blobSasBuilder.BlobName = "foo/bar";
blobSasBuilder.SetPermissions(
Sas::BlobContainerSasPermissions::Read | Sas::BlobContainerSasPermissions::List);
blobSasBuilder.IsVirtualDirectory = true;
blobSasBuilder.VirtualDirectoryDepth = 2;
blobSasBuilder.Resource = Sas::BlobSasResource::VirtualDirectory;

auto sasToken = blobSasBuilder.GenerateSasToken(*keyCredential);
VerifyBlobSasRead(blobClient, sasToken);

blobSasBuilder.VirtualDirectoryDepth = 3;
sasToken = blobSasBuilder.GenerateSasToken(*keyCredential);
VerifyBlobSasNonRead(blobClient, sasToken);

blobSasBuilder.BlobName = "foo/bar/baz";
sasToken = blobSasBuilder.GenerateSasToken(*keyCredential);
VerifyBlobSasRead(blobClient, sasToken);
}

}}} // namespace Azure::Storage::Test
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,27 @@ namespace Azure { namespace Storage { namespace Test {
blobItem.Details.RehydratePriority.Value(), Blobs::Models::RehydratePriority::Standard);
}

TEST_F(BlockBlobClientTest, DISABLED_RehydrateTierToSmart)
{
m_blockBlobClient->SetAccessTier(Blobs::Models::AccessTier::Archive);
m_blockBlobClient->SetAccessTier(Blobs::Models::AccessTier::Smart);
auto properties = m_blockBlobClient->GetProperties().Value;
ASSERT_TRUE(properties.ArchiveStatus.HasValue());
EXPECT_EQ(
properties.ArchiveStatus.Value(), Blobs::Models::ArchiveStatus::RehydratePendingToSmart);
ASSERT_TRUE(properties.RehydratePriority.HasValue());
EXPECT_EQ(properties.RehydratePriority.Value(), Blobs::Models::RehydratePriority::Standard);

auto blobItem = GetBlobItem(m_blobName);
ASSERT_TRUE(blobItem.Details.ArchiveStatus.HasValue());
EXPECT_EQ(
blobItem.Details.ArchiveStatus.Value(),
Blobs::Models::ArchiveStatus::RehydratePendingToSmart);
ASSERT_TRUE(blobItem.Details.RehydratePriority.HasValue());
EXPECT_EQ(
blobItem.Details.RehydratePriority.Value(), Blobs::Models::RehydratePriority::Standard);
}

TEST_F(BlockBlobClientTest, SetTierCold)
{
m_blockBlobClient->SetAccessTier(Blobs::Models::AccessTier::Cold);
Expand Down Expand Up @@ -2648,4 +2669,27 @@ namespace Azure { namespace Storage { namespace Test {
deleteOptions.AccessConditions.IfModifiedSince = lastModifiedTime - std::chrono::hours(1);
EXPECT_NO_THROW(blobClient.Delete(deleteOptions));
}

TEST_F(BlockBlobClientTest, DISABLED_SmartTier)
{
const auto blobName = RandomString();
auto blobClient = GetBlockBlobClientForTest(blobName);

std::vector<uint8_t> emptyContent;
Blobs::UploadBlockBlobFromOptions options;
options.AccessTier = Blobs::Models::AccessTier::Smart;
blobClient.UploadFrom(emptyContent.data(), emptyContent.size(), options);

auto ret = blobClient.GetProperties().Value;
EXPECT_TRUE(ret.AccessTier.HasValue());
EXPECT_EQ(ret.AccessTier.Value(), Blobs::Models::AccessTier::Smart);
EXPECT_TRUE(ret.SmartAccessTier.HasValue());
EXPECT_FALSE(ret.SmartAccessTier.Value().ToString().empty());

auto blobItem = GetBlobItem(blobName);
EXPECT_TRUE(blobItem.Details.AccessTier.HasValue());
EXPECT_EQ(blobItem.Details.AccessTier.Value(), Blobs::Models::AccessTier::Smart);
EXPECT_TRUE(blobItem.Details.SmartAccessTier.HasValue());
EXPECT_FALSE(blobItem.Details.SmartAccessTier.Value().ToString().empty());
}
}}} // namespace Azure::Storage::Test
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-common/test/ut/test_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ namespace Azure { namespace Storage {

protected:
std::vector<std::function<void()>> m_resourceCleanupFunctions;
constexpr static bool m_useTokenCredentialByDefault = false;
constexpr static bool m_useTokenCredentialByDefault = true;

private:
void InitLoggingOptions(Azure::Core::_internal::ClientOptions& options);
Expand Down
Loading