diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index 02d8654aa0..feca42c1ce 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "cpp", "TagPrefix": "cpp/storage", - "Tag": "cpp/storage_3da4d9917e" + "Tag": "cpp/storage_436298c53b" } diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp index 88b11e4759..e309b4d040 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_client.hpp @@ -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 SetTags( std::map tags, diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp index 500e9e19ec..946bf394a9 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_sas_builder.hpp @@ -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 }; /** @@ -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 VirtualDirectoryDepth; + /** * @brief The name of the blob snapshot being made accessible, or empty for a container * SAS and blob SAS. diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/rest_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/rest_client.hpp index a556cf041f..c8885fd8ad 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/rest_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/rest_client.hpp @@ -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 @@ -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. @@ -1253,6 +1257,11 @@ namespace Azure { namespace Storage { namespace Blobs { * destination tier. */ Nullable 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 SmartAccessTier; /** * SHA-256 hash of the encryption key. */ @@ -1786,6 +1795,10 @@ namespace Azure { namespace Storage { namespace Blobs { * blob was ever set. */ Nullable AccessTierChangedOn; + /** + * The underlying tier of a smart tier blob. Only returned if the blob is in Smart tier. + */ + Nullable 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 diff --git a/sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp b/sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp index 883261bf21..7ec545707d 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_sas_builder.cpp @@ -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."); @@ -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; } @@ -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(); } @@ -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; } @@ -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(); @@ -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; } @@ -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; } diff --git a/sdk/storage/azure-storage-blobs/src/rest_client.cpp b/sdk/storage/azure-storage-blobs/src/rest_client.cpp index d86f056ec0..e4bd5d6e11 100644 --- a/sdk/storage/azure-storage-blobs/src/rest_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/rest_client.cpp @@ -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"); @@ -2306,6 +2308,7 @@ namespace Azure { namespace Storage { namespace Blobs { kAccessTier, kAccessTierInferred, kArchiveStatus, + kSmartAccessTier, kCustomerProvidedKeySha256, kEncryptionScope, kAccessTierChangeTime, @@ -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}, @@ -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 @@ -2996,6 +3008,7 @@ namespace Azure { namespace Storage { namespace Blobs { kAccessTier, kAccessTierInferred, kArchiveStatus, + kSmartAccessTier, kCustomerProvidedKeySha256, kEncryptionScope, kAccessTierChangeTime, @@ -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}, @@ -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 @@ -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"); diff --git a/sdk/storage/azure-storage-blobs/swagger/README.md b/sdk/storage/azure-storage-blobs/swagger/README.md index c71c5fd03c..9a98414b85 100644 --- a/sdk/storage/azure-storage-blobs/swagger/README.md +++ b/sdk/storage/azure-storage-blobs/swagger/README.md @@ -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 @@ -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", @@ -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; diff --git a/sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp index 24f5105eb2..aadf25a989 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/blob_sas_test.cpp @@ -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("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 diff --git a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp index ecc0de1a24..2901e9f166 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/block_blob_client_test.cpp @@ -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); @@ -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 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 diff --git a/sdk/storage/azure-storage-common/test/ut/test_base.hpp b/sdk/storage/azure-storage-common/test/ut/test_base.hpp index 6ce1f98854..049b026409 100644 --- a/sdk/storage/azure-storage-common/test/ut/test_base.hpp +++ b/sdk/storage/azure-storage-common/test/ut/test_base.hpp @@ -169,7 +169,7 @@ namespace Azure { namespace Storage { protected: std::vector> m_resourceCleanupFunctions; - constexpr static bool m_useTokenCredentialByDefault = false; + constexpr static bool m_useTokenCredentialByDefault = true; private: void InitLoggingOptions(Azure::Core::_internal::ClientOptions& options); diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp index 26220a39ae..1ce9174b37 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_options.hpp @@ -855,6 +855,29 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Nullable IncludeUserPrincipalName; }; + /** + * @brief Optional parameters for + * #Azure::Storage::Files::DataLake::DataLakePathClient::GetSystemProperties. + */ + struct GetPathSystemPropertiesOptions final + { + /** + * Specify the access condition for the path. + */ + PathAccessConditions AccessConditions; + + /** + * Valid only when Hierarchical Namespace is enabled for the account. If "true", the user + * identity values returned in the owner and group fields of each list entry will be transformed + * from Azure Active Directory Object IDs to User Principal Names. If "false" or not provided, + * the values will be returned as Azure Active Directory Object IDs. Note that group and + * application Object IDs are not translated because they do not have unique friendly names. + * More Details about UserPrincipalName, See + * https://learn.microsoft.com/entra/identity/hybrid/connect/plan-connect-userprincipalname#what-is-userprincipalname + */ + Nullable IncludeUserPrincipalName; + }; + /** * @brief Optional parameters for #Azure::Storage::Files::DataLake::DataLakeFileClient::Download. * @remark Some optional parameter is mandatory in certain combination. @@ -1063,4 +1086,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { */ std::function ErrorHandler; }; + + using SetPathTagsOptions = Blobs::SetBlobTagsOptions; + using GetPathTagsOptions = Blobs::GetBlobTagsOptions; }}}} // namespace Azure::Storage::Files::DataLake diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp index 4b269f8b26..a6d97ef000 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_path_client.hpp @@ -216,6 +216,18 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { const GetPathAccessControlListOptions& options = GetPathAccessControlListOptions(), const Azure::Core::Context& context = Azure::Core::Context()) const; + /** + * @brief Returns system properties stored for the given path. + * @param options Optional parameters to get system properties from the resource the path points + * to. + * @param context Context for cancelling long running operations. + * @return Azure::Response containing the system properties. + * @remark This request is sent to dfs endpoint. + */ + Azure::Response GetSystemProperties( + const GetPathSystemPropertiesOptions& options = GetPathSystemPropertiesOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const; + /** * @brief Sets the metadata of a resource the path points to. * @param metadata User-defined metadata to be stored with the filesystem. Note that the string @@ -297,6 +309,32 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { Models::_detail::PathSetAccessControlListRecursiveMode::Remove, acls, options, context); } + /** + * @brief Sets tags on the path. + * + * @param tags The tags to set on the path. + * @param options Optional parameters to execute this function. + * @param context Context for cancelling long running operations. + * @return A SetBlobTagsResult on successfully setting tags. + * @remark This request is sent to blob endpoint. + */ + Azure::Response SetTags( + std::map tags, + const SetPathTagsOptions& options = SetPathTagsOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const; + + /** + * @brief Gets the tags associated with the path. + * + * @param options Optional parameters to execute this function. + * @param context Context for cancelling long running operations. + * @return path tags on successfully getting tags. + * @remark This request is sent to blob endpoint. + */ + Azure::Response> GetTags( + const GetPathTagsOptions& options = GetPathTagsOptions(), + const Azure::Core::Context& context = Azure::Core::Context()) const; + protected: /** @brief Url to the resource on the service */ Azure::Core::Url m_pathUrl; diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp index 0d7debf42c..1a434048d2 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/datalake_responses.hpp @@ -792,6 +792,7 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { using CreateDirectoryResult = CreatePathResult; using DeleteDirectoryResult = DeletePathResult; + using SetPathTagsResult = Blobs::Models::SetBlobTagsResult; } // namespace Models diff --git a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/rest_client.hpp b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/rest_client.hpp index cc99043f47..f321eb2e20 100644 --- a/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/rest_client.hpp +++ b/sdk/storage/azure-storage-files-datalake/inc/azure/storage/files/datalake/rest_client.hpp @@ -276,6 +276,74 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { std::string Acl; }; } // namespace _detail + /** + * @brief Response type for + * #Azure::Storage::Files::DataLake::DataLakePathClient::GetSystemProperties. + */ + struct PathSystemProperties final + { + /** + * Indicates the resource is a directory or a file. + */ + bool IsDirectory = bool(); + /** + * The size of the resource in bytes. + */ + std::int64_t FileSize = std::int64_t(); + /** + * An HTTP entity tag associated with the file or directory. + */ + Azure::ETag ETag; + /** + * The data and time the file or directory was last modified. Write operations on the file or + * directory update the last modified time. + */ + DateTime LastModified; + /** + * The owner of the file or directory. Included in the response if Hierarchical Namespace is + * enabled for the account. + */ + std::string Owner; + /** + * The owning group of the file or directory. Included in the response if Hierarchical + * Namespace is enabled for the account. + */ + std::string Group; + /** + * The POSIX access permissions for the file owner, the file owning group, and others. + * Included in the response if Hierarchical Namespace is enabled for the account. + */ + std::string Permissions; + /** + * The value of this header is set to true if the directory metadata is completely encrypted + * using the specified algorithm. Otherwise, the value is set to false. + */ + bool IsServerEncrypted = bool(); + /** + * The SHA-256 hash of the encryption key used to encrypt the blob. This header is only + * returned when the blob was encrypted with a customer-provided key. + */ + Nullable> EncryptionKeySha256; + /** + * The encryption context used to encrypt the blob. This header is only returned when the blob + * was encrypted with a customer-provided key. + */ + Nullable EncryptionContext; + /** + * Returns the name of the encryption scope used to encrypt the blob contents and application + * metadata. Note that the absence of this header implies use of the default account + * encryption scope. + */ + Nullable EncryptionScope; + /** + * Returns the date and time the blob was created. + */ + Nullable CreatedOn; + /** + * The time this blob will expire. + */ + Nullable ExpiresOn; + }; /** * @brief Optional. If "acquire" it will acquire the lease. If "auto-renew" it will renew the * lease. If "release" it will release the lease only on flush. If "acquire-release" it will @@ -493,6 +561,20 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { const Core::Url& url, const GetPathAccessControlListOptions& options, const Core::Context& context); + struct GetPathSystemPropertiesOptions final + { + Nullable Upn; + Nullable LeaseId; + ETag IfMatch; + ETag IfNoneMatch; + Nullable IfModifiedSince; + Nullable IfUnmodifiedSince; + }; + static Response GetSystemProperties( + Core::Http::_internal::HttpPipeline& pipeline, + const Core::Url& url, + const GetPathSystemPropertiesOptions& options, + const Core::Context& context); }; class FileClient final { public: diff --git a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp index 1c6569d397..aaf22b2867 100644 --- a/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/datalake_path_client.cpp @@ -449,6 +449,30 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { std::move(ret), std::move(response.RawResponse)); } + Azure::Response DataLakePathClient::GetSystemProperties( + const GetPathSystemPropertiesOptions& options, + const Azure::Core::Context& context) const + { + _detail::PathClient::GetPathSystemPropertiesOptions protocolLayerOptions; + protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; + protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch; + protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; + protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; + protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; + protocolLayerOptions.Upn = options.IncludeUserPrincipalName; + auto response = _detail::PathClient::GetSystemProperties( + *m_pipeline, m_pathUrl, protocolLayerOptions, _internal::WithReplicaStatus(context)); + if (response.RawResponse->GetHeaders().at("x-ms-resource-type") == "file") + { + response.Value.IsDirectory = false; + } + else + { + response.Value.IsDirectory = true; + } + return response; + } + Azure::Response DataLakePathClient::SetMetadata( Storage::Metadata metadata, const SetPathMetadataOptions& options, @@ -500,4 +524,19 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { return pagedResponse; } + Azure::Response DataLakePathClient::SetTags( + std::map tags, + const SetPathTagsOptions& options, + const Azure::Core::Context& context) const + { + return m_blobClient.SetTags(tags, options, context); + } + + Azure::Response> DataLakePathClient::GetTags( + const GetPathTagsOptions& options, + const Azure::Core::Context& context) const + { + return m_blobClient.GetTags(options, context); + } + }}}} // namespace Azure::Storage::Files::DataLake diff --git a/sdk/storage/azure-storage-files-datalake/src/rest_client.cpp b/sdk/storage/azure-storage-files-datalake/src/rest_client.cpp index bfcae052b0..e10bf5f58f 100644 --- a/sdk/storage/azure-storage-files-datalake/src/rest_client.cpp +++ b/sdk/storage/azure-storage-files-datalake/src/rest_client.cpp @@ -614,6 +614,86 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake { return Response( std::move(response), std::move(pRawResponse)); } + Response PathClient::GetSystemProperties( + Core::Http::_internal::HttpPipeline& pipeline, + const Core::Url& url, + const GetPathSystemPropertiesOptions& options, + const Core::Context& context) + { + auto request = Core::Http::Request(Core::Http::HttpMethod::Head, url); + request.GetUrl().AppendQueryParameter("action", "getStatus"); + if (options.Upn.HasValue()) + { + request.GetUrl().AppendQueryParameter("upn", options.Upn.Value() ? "true" : "false"); + } + if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) + { + request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); + } + if (options.IfMatch.HasValue() && !options.IfMatch.ToString().empty()) + { + request.SetHeader("If-Match", options.IfMatch.ToString()); + } + if (options.IfNoneMatch.HasValue() && !options.IfNoneMatch.ToString().empty()) + { + request.SetHeader("If-None-Match", options.IfNoneMatch.ToString()); + } + if (options.IfModifiedSince.HasValue()) + { + request.SetHeader( + "If-Modified-Since", + options.IfModifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123)); + } + if (options.IfUnmodifiedSince.HasValue()) + { + request.SetHeader( + "If-Unmodified-Since", + options.IfUnmodifiedSince.Value().ToString(Azure::DateTime::DateFormat::Rfc1123)); + } + request.SetHeader("x-ms-version", "2026-04-06"); + auto pRawResponse = pipeline.Send(request, context); + auto httpStatusCode = pRawResponse->GetStatusCode(); + if (httpStatusCode != Core::Http::HttpStatusCode::Ok) + { + throw StorageException::CreateFromResponse(std::move(pRawResponse)); + } + Models::PathSystemProperties response; + response.FileSize = std::stoll(pRawResponse->GetHeaders().at("Content-Length")); + response.ETag = ETag(pRawResponse->GetHeaders().at("ETag")); + response.LastModified = DateTime::Parse( + pRawResponse->GetHeaders().at("Last-Modified"), Azure::DateTime::DateFormat::Rfc1123); + response.Owner = pRawResponse->GetHeaders().at("x-ms-owner"); + response.Group = pRawResponse->GetHeaders().at("x-ms-group"); + response.Permissions = pRawResponse->GetHeaders().at("x-ms-permissions"); + response.IsServerEncrypted + = pRawResponse->GetHeaders().at("x-ms-server-encrypted") == std::string("true"); + if (pRawResponse->GetHeaders().count("x-ms-encryption-key-sha256") != 0) + { + response.EncryptionKeySha256 = Core::Convert::Base64Decode( + pRawResponse->GetHeaders().at("x-ms-encryption-key-sha256")); + } + if (pRawResponse->GetHeaders().count("x-ms-encryption-context") != 0) + { + response.EncryptionContext = pRawResponse->GetHeaders().at("x-ms-encryption-context"); + } + if (pRawResponse->GetHeaders().count("x-ms-encryption-scope") != 0) + { + response.EncryptionScope = pRawResponse->GetHeaders().at("x-ms-encryption-scope"); + } + if (pRawResponse->GetHeaders().count("x-ms-creation-time") != 0) + { + response.CreatedOn = DateTime::Parse( + pRawResponse->GetHeaders().at("x-ms-creation-time"), + Azure::DateTime::DateFormat::Rfc1123); + } + if (pRawResponse->GetHeaders().count("x-ms-expiry-time") != 0) + { + response.ExpiresOn = DateTime::Parse( + pRawResponse->GetHeaders().at("x-ms-expiry-time"), + Azure::DateTime::DateFormat::Rfc1123); + } + return Response(std::move(response), std::move(pRawResponse)); + } Response FileClient::Flush( Core::Http::_internal::HttpPipeline& pipeline, const Core::Url& url, diff --git a/sdk/storage/azure-storage-files-datalake/swagger/README.md b/sdk/storage/azure-storage-files-datalake/swagger/README.md index ba16a3bb2e..35ecc13681 100644 --- a/sdk/storage/azure-storage-files-datalake/swagger/README.md +++ b/sdk/storage/azure-storage-files-datalake/swagger/README.md @@ -391,12 +391,12 @@ directive: transform: > $["/{filesystem}/{path}?getAccessControlList"] = {}; $["/{filesystem}/{path}?getAccessControlList"].head = JSON.parse(JSON.stringify($["/{filesystem}/{path}"].head)); - delete $["/{filesystem}/{path}"].head; - from: swagger-document where: $["x-ms-paths"]["/{filesystem}/{path}?getAccessControlList"].head transform: > $.operationId = "Path_GetAccessControlList"; $.parameters[0]["enum"] = ["getAccessControl"]; + delete $.parameters[0]["x-ms-enum"]; $.parameters.push({"$ref": "#/parameters/ApiVersionParameter"}); delete $.responses["200"].headers["Accept-Ranges"]; delete $.responses["200"].headers["Cache-Control"]; @@ -432,6 +432,54 @@ directive: }; ``` +### GetPathSystemProperties + +```yaml +directive: + - from: swagger-document + where: $["x-ms-paths"] + transform: > + $["/{filesystem}/{path}?getStatus"] = {}; + $["/{filesystem}/{path}?getStatus"].head = JSON.parse(JSON.stringify($["/{filesystem}/{path}"].head)); + delete $["/{filesystem}/{path}"].head; + - from: swagger-document + where: $["x-ms-paths"]["/{filesystem}/{path}?getStatus"].head + transform: > + $.operationId = "Path_GetSystemProperties"; + delete $.parameters[0]["x-ms-enum"]; + $.parameters[0]["enum"] = ["getStatus"]; + $.parameters.push({"$ref": "#/parameters/ApiVersionParameter"}); + delete $.responses["200"].headers["Accept-Ranges"]; + delete $.responses["200"].headers["Cache-Control"]; + delete $.responses["200"].headers["Content-Disposition"]; + delete $.responses["200"].headers["Content-Encoding"]; + delete $.responses["200"].headers["Content-Language"]; + delete $.responses["200"].headers["Content-Range"]; + delete $.responses["200"].headers["Content-Type"]; + delete $.responses["200"].headers["Content-MD5"]; + delete $.responses["200"].headers["x-ms-resource-type"]; + delete $.responses["200"].headers["x-ms-properties"]; + delete $.responses["200"].headers["x-ms-lease-duration"]; + delete $.responses["200"].headers["x-ms-lease-state"]; + delete $.responses["200"].headers["x-ms-lease-status"]; + delete $.responses["200"].headers["x-ms-acl"]; + $.responses["200"].headers["Content-Length"]["x-ms-client-name"] = "FileSize"; + $.responses["200"].headers["x-ms-encryption-key-sha256"]["x-nullable"] = true; + $.responses["200"].headers["x-ms-encryption-context"]["x-nullable"] = true; + $.responses["200"].headers["x-ms-encryption-scope"]["x-nullable"] = true; + $.responses["200"].headers["x-ms-creation-time"]["x-nullable"] = true; + $.responses["200"].headers["x-ms-creation-time"]["x-ms-client-name"] = "CreatedOn"; + $.responses["200"].headers["x-ms-expiry-time"]["x-nullable"] = true; + $.responses["200"].schema = { + "type": "object", + "x-ms-sealed": false, + "x-ms-client-name": "PathSystemProperties", + "properties": { + "IsDirectory": {"type": "boolean", "x-ms-json": "", "description": "Indicates the resource is a directory or a file."} + } + }; +``` + ### SetAccessControlListRecursive ```yaml @@ -527,4 +575,8 @@ directive: $["200"].schema.description = "Response type for #Azure::Storage::Files::DataLake::DataLakePathClient::Delete."; $["202"].schema.properties["Deleted"].description = "Indicates if the file or directory was successfully deleted by this operation."; $["202"].schema.description = "Response type for #Azure::Storage::Files::DataLake::DataLakePathClient::Delete."; + - from: swagger-document + where: $["x-ms-paths"]["/{filesystem}/{path}?getStatus"].head.responses + transform: > + $["200"].schema.description = "Response type for #Azure::Storage::Files::DataLake::DataLakePathClient::GetSystemProperties."; ``` \ No newline at end of file diff --git a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_directory_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_directory_client_test.cpp index 7ece99dc11..ef36b1f688 100644 --- a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_directory_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_directory_client_test.cpp @@ -1014,4 +1014,19 @@ namespace Azure { namespace Storage { namespace Test { ASSERT_TRUE(paths[0].ExpiresOn.HasValue()); EXPECT_EQ(options.ExpiresOn.Value(), paths[0].ExpiresOn.Value()); } + + TEST_F(DataLakeDirectoryClientTest, PathSystemProperties) + { + auto res = m_directoryClient->GetSystemProperties(); + auto systemProperties = res.Value; + EXPECT_TRUE(systemProperties.IsDirectory); + EXPECT_TRUE(systemProperties.ETag.HasValue()); + EXPECT_FALSE(systemProperties.LastModified.ToString().empty()); + EXPECT_FALSE(systemProperties.Owner.empty()); + EXPECT_FALSE(systemProperties.Group.empty()); + EXPECT_FALSE(systemProperties.Permissions.empty()); + EXPECT_TRUE(systemProperties.IsServerEncrypted); + EXPECT_TRUE(systemProperties.CreatedOn.HasValue()); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_file_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_file_client_test.cpp index 0c8dcfe821..e2058b03af 100644 --- a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_file_client_test.cpp @@ -1335,4 +1335,18 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(DataLakeFileClientTest, PathSystemProperties) + { + auto res = m_fileClient->GetSystemProperties(); + auto systemProperties = res.Value; + EXPECT_FALSE(systemProperties.IsDirectory); + EXPECT_TRUE(systemProperties.ETag.HasValue()); + EXPECT_FALSE(systemProperties.LastModified.ToString().empty()); + EXPECT_FALSE(systemProperties.Owner.empty()); + EXPECT_FALSE(systemProperties.Group.empty()); + EXPECT_FALSE(systemProperties.Permissions.empty()); + EXPECT_TRUE(systemProperties.IsServerEncrypted); + EXPECT_TRUE(systemProperties.CreatedOn.HasValue()); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp index cb6764fe19..935418504c 100644 --- a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp @@ -592,4 +592,15 @@ namespace Azure { namespace Storage { namespace Test { = Files::DataLake::DataLakePathClient(m_pathClient->GetUrl(), credential, clientOptions); EXPECT_THROW(pathClient.GetProperties(), StorageException); } + + TEST_F(DataLakePathClientTest, DISABLED_GetSetTags) + { + std::map tags; + tags["key1"] = "value1"; + tags["key2"] = "value2"; + tags["key3 +-./:=_"] = "v1 +-./:=_"; + m_pathClient->SetTags(tags); + EXPECT_EQ(tags, m_pathClient->GetTags().Value); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/rest_client.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/rest_client.hpp index 70d56f4e69..51469ecbba 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/rest_client.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/rest_client.hpp @@ -32,7 +32,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { /** * The version used for the operations to Azure storage services. */ - constexpr static const char* ApiVersion = "2026-04-06"; + constexpr static const char* ApiVersion = "2026-06-06"; } // namespace _detail namespace Models { /** @@ -1181,6 +1181,24 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { */ std::string ParentFileId; }; + /** + * @brief SMB only, default value is New. New will forcefully add the ARCHIVE attribute flag + * and alter the permissions specified in x-ms-file-permission to inherit missing permissions + * from the parent. Restore will apply changes without further modification. + */ + class FilePropertySemantics final + : public Core::_internal::ExtendableEnumeration { + public: + /** Constructs a new FilePropertySemantics instance */ + FilePropertySemantics() = default; + /** Constructs a new FilePropertySemantics from a string. */ + explicit FilePropertySemantics(std::string value) : ExtendableEnumeration(std::move(value)) {} + + /** Constant value of type FilePropertySemantics: New */ + AZ_STORAGE_FILES_SHARES_DLLEXPORT const static FilePropertySemantics New; + /** Constant value of type FilePropertySemantics: Restore */ + AZ_STORAGE_FILES_SHARES_DLLEXPORT const static FilePropertySemantics Restore; + }; /** * @brief NFS only. Type of the file or directory. */ @@ -1682,6 +1700,11 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { * NFS only. Type of the file or directory. */ Nullable NfsFileType; + /** + * Indicates the structured message body was accepted and mirrors back the message schema + * version and properties. + */ + Nullable StructuredBodyType; }; } // namespace _detail /** @@ -2733,6 +2756,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Nullable Owner; Nullable Group; Nullable FileMode; + Nullable FilePropertySemantics; }; static Response Create( Core::Http::_internal::HttpPipeline& pipeline, @@ -2887,10 +2911,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Nullable Group; Nullable FileMode; Nullable NfsFileType; + Nullable> ContentMD5; + Nullable FilePropertySemantics; + Nullable StructuredBodyType; + Nullable StructuredContentLength; }; static Response Create( Core::Http::_internal::HttpPipeline& pipeline, const Core::Url& url, + Core::IO::BodyStream& requestBody, const CreateFileOptions& options, const Core::Context& context); struct DownloadFileOptions final diff --git a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp index a4f808c31c..7b27f4e91e 100644 --- a/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp +++ b/sdk/storage/azure-storage-files-shares/inc/azure/storage/files/shares/share_options.hpp @@ -635,6 +635,12 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { * The NFS related properties for the file. */ Models::FilePosixProperties PosixProperties; + + /** + * Optional. Only applies to SMB directories. How attributes and permissions should be set on + * the directory. + */ + Azure::Nullable PropertySemantics; }; /** @@ -951,6 +957,23 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { * The NFS related properties for the file. */ Models::FilePosixProperties PosixProperties; + + /** + * Optional. The content to upload to the file when it's created. Must be less than or equal to + * 4MiB in size. + */ + Azure::Core::IO::BodyStream* Content = nullptr; + + /** + * Optional. Configures whether to perform content validation for file uploads. + */ + Azure::Nullable ValidationOptions; + + /** + * Optional. Only applies to SMB files. How attributes and permissions should be set on the + * file. + */ + Azure::Nullable PropertySemantics; }; /** diff --git a/sdk/storage/azure-storage-files-shares/src/rest_client.cpp b/sdk/storage/azure-storage-files-shares/src/rest_client.cpp index 6eee9b9c30..bba4ddf624 100644 --- a/sdk/storage/azure-storage-files-shares/src/rest_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/rest_client.cpp @@ -185,6 +185,8 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { const FileAttributes FileAttributes::Offline("Offline"); const FileAttributes FileAttributes::NotContentIndexed("NotContentIndexed"); const FileAttributes FileAttributes::NoScrubData("NoScrubData"); + const FilePropertySemantics FilePropertySemantics::New("New"); + const FilePropertySemantics FilePropertySemantics::Restore("Restore"); const NfsFileType NfsFileType::Regular("Regular"); const NfsFileType NfsFileType::Directory("Directory"); const NfsFileType NfsFileType::SymLink("SymLink"); @@ -376,7 +378,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -401,7 +403,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -696,7 +698,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { _internal::UrlEncodeQueryParameter( ListSharesIncludeFlagsToString(options.Include.Value()))); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -1155,7 +1157,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("restype", "service"); request.GetUrl().AppendQueryParameter("comp", "userdelegationkey"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) @@ -1289,7 +1291,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-access-tier", options.AccessTier.Value().ToString()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.EnabledProtocols.HasValue() && !options.EnabledProtocols.Value().ToString().empty()) { @@ -1388,7 +1390,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -1545,7 +1547,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.DeleteSnapshots.HasValue() && !options.DeleteSnapshots.Value().ToString().empty()) { request.SetHeader("x-ms-delete-snapshots", options.DeleteSnapshots.Value().ToString()); @@ -1596,7 +1598,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Sharesnapshot.HasValue() && !options.Sharesnapshot.Value().empty()) { request.GetUrl().AppendQueryParameter( @@ -1635,7 +1637,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-lease-id", options.LeaseId); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Sharesnapshot.HasValue() && !options.Sharesnapshot.Value().empty()) { request.GetUrl().AppendQueryParameter( @@ -1677,7 +1679,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Sharesnapshot.HasValue() && !options.Sharesnapshot.Value().empty()) { request.GetUrl().AppendQueryParameter( @@ -1716,7 +1718,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-lease-id", options.LeaseId); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Sharesnapshot.HasValue() && !options.Sharesnapshot.Value().empty()) { request.GetUrl().AppendQueryParameter( @@ -1759,7 +1761,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Sharesnapshot.HasValue() && !options.Sharesnapshot.Value().empty()) { request.GetUrl().AppendQueryParameter( @@ -1797,7 +1799,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -1840,7 +1842,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("restype", "share"); request.GetUrl().AppendQueryParameter("comp", "filepermission"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -1876,7 +1878,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader( "x-ms-file-permission-format", options.FilePermissionFormat.Value().ToString()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -1911,7 +1913,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("restype", "share"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Quota.HasValue()) { request.SetHeader("x-ms-share-quota", std::to_string(options.Quota.Value())); @@ -2042,7 +2044,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -2073,7 +2075,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("restype", "share"); request.GetUrl().AppendQueryParameter("comp", "acl"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -2225,7 +2227,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader("Content-Length", std::to_string(requestBody.Length())); request.GetUrl().AppendQueryParameter("restype", "share"); request.GetUrl().AppendQueryParameter("comp", "acl"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -2257,7 +2259,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Get, url); request.GetUrl().AppendQueryParameter("restype", "share"); request.GetUrl().AppendQueryParameter("comp", "stats"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -2348,7 +2350,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FilePermission.HasValue() && !options.FilePermission.Value().empty()) { request.SetHeader("x-ms-file-permission", options.FilePermission.Value()); @@ -2396,6 +2398,12 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-mode", options.FileMode.Value()); } + if (options.FilePropertySemantics.HasValue() + && !options.FilePropertySemantics.Value().ToString().empty()) + { + request.SetHeader( + "x-ms-file-property-semantics", options.FilePropertySemantics.Value().ToString()); + } auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Created) @@ -2476,7 +2484,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -2563,7 +2571,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader( "x-ms-allow-trailing-dot", options.AllowTrailingDot.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -2587,7 +2595,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("restype", "directory"); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FilePermission.HasValue() && !options.FilePermission.Value().empty()) { request.SetHeader("x-ms-file-permission", options.FilePermission.Value()); @@ -2710,7 +2718,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -2764,7 +2772,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "maxresults", std::to_string(options.MaxResults.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Include.HasValue() && !ListFilesIncludeFlagsToString(options.Include.Value()).empty()) { @@ -3157,7 +3165,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-recursive", options.Recursive.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -3370,7 +3378,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-recursive", options.Recursive.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -3408,7 +3416,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("restype", "directory"); request.GetUrl().AppendQueryParameter("comp", "rename"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (!options.RenameSource.empty()) { request.SetHeader("x-ms-file-rename-source", options.RenameSource); @@ -3513,16 +3521,17 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { Response FileClient::Create( Core::Http::_internal::HttpPipeline& pipeline, const Core::Url& url, + Core::IO::BodyStream& requestBody, const CreateFileOptions& options, const Core::Context& context) { - auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); + auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url, &requestBody); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( "x-ms-allow-trailing-dot", options.AllowTrailingDot.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); request.SetHeader("x-ms-content-length", std::to_string(options.FileContentLength)); request.SetHeader("x-ms-type", "file"); if (options.FileContentType.HasValue() && !options.FileContentType.Value().empty()) @@ -3611,6 +3620,28 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-file-file-type", options.NfsFileType.Value().ToString()); } + if (options.ContentMD5.HasValue() + && !Core::Convert::Base64Encode(options.ContentMD5.Value()).empty()) + { + request.SetHeader("Content-MD5", Core::Convert::Base64Encode(options.ContentMD5.Value())); + } + if (options.FilePropertySemantics.HasValue() + && !options.FilePropertySemantics.Value().ToString().empty()) + { + request.SetHeader( + "x-ms-file-property-semantics", options.FilePropertySemantics.Value().ToString()); + } + request.SetHeader("Content-Length", std::to_string(requestBody.Length())); + if (options.StructuredBodyType.HasValue() && !options.StructuredBodyType.Value().empty()) + { + request.SetHeader("x-ms-structured-body", options.StructuredBodyType.Value()); + } + if (options.StructuredContentLength.HasValue()) + { + request.SetHeader( + "x-ms-structured-content-length", + std::to_string(options.StructuredContentLength.Value())); + } auto pRawResponse = pipeline.Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Created) @@ -3670,6 +3701,10 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { response.NfsFileType = Models::NfsFileType(pRawResponse->GetHeaders().at("x-ms-file-file-type")); } + if (pRawResponse->GetHeaders().count("x-ms-structured-body") != 0) + { + response.StructuredBodyType = pRawResponse->GetHeaders().at("x-ms-structured-body"); + } return Response( std::move(response), std::move(pRawResponse)); } @@ -3685,7 +3720,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader( "x-ms-allow-trailing-dot", options.AllowTrailingDot.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Range.HasValue() && !options.Range.Value().empty()) { request.SetHeader("x-ms-range", options.Range.Value()); @@ -3894,7 +3929,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4057,7 +4092,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.SetHeader( "x-ms-allow-trailing-dot", options.AllowTrailingDot.Value() ? "true" : "false"); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4088,7 +4123,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("comp", "properties"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileContentLength.HasValue()) { request.SetHeader("x-ms-content-length", std::to_string(options.FileContentLength.Value())); @@ -4249,7 +4284,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-meta-" + p.first, p.second); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4293,7 +4328,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -4331,7 +4366,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-lease-id", options.LeaseId); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -4372,7 +4407,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-proposed-lease-id", options.ProposedLeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -4410,7 +4445,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -4457,7 +4492,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("Content-MD5", Core::Convert::Base64Encode(options.ContentMD5.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4554,7 +4589,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { "x-ms-source-if-none-match-crc64", Core::Convert::Base64Encode(options.SourceIfNoneMatchCrc64.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4630,7 +4665,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { "prevsharesnapshot", _internal::UrlEncodeQueryParameter(options.Prevsharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.Range.HasValue() && !options.Range.Value().empty()) { request.SetHeader("x-ms-range", options.Range.Value()); @@ -4759,7 +4794,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { const Core::Context& context) { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); for (const auto& p : options.Metadata) { request.SetHeader("x-ms-meta-" + p.first, p.second); @@ -4886,7 +4921,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { "copyid", _internal::UrlEncodeQueryParameter(options.CopyId)); } request.SetHeader("x-ms-copy-action", "abort"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -4933,7 +4968,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -5142,7 +5177,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { request.SetHeader("x-ms-handle-id", options.HandleId); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.AllowTrailingDot.HasValue()) { request.SetHeader( @@ -5179,7 +5214,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("comp", "rename"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (!options.RenameSource.empty()) { request.SetHeader("x-ms-file-rename-source", options.RenameSource); @@ -5293,7 +5328,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("restype", "symboliclink"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); for (const auto& p : options.Metadata) { request.SetHeader("x-ms-meta-" + p.first, p.second); @@ -5378,7 +5413,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { request.GetUrl().AppendQueryParameter( "sharesnapshot", _internal::UrlEncodeQueryParameter(options.Sharesnapshot.Value())); } - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); if (options.FileRequestIntent.HasValue() && !options.FileRequestIntent.Value().ToString().empty()) { @@ -5406,7 +5441,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { { auto request = Core::Http::Request(Core::Http::HttpMethod::Put, url); request.GetUrl().AppendQueryParameter("restype", "hardlink"); - request.SetHeader("x-ms-version", "2026-04-06"); + request.SetHeader("x-ms-version", "2026-06-06"); request.SetHeader("x-ms-type", "file"); if (options.LeaseId.HasValue() && !options.LeaseId.Value().empty()) { diff --git a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp index d852a0db92..03fb6a14d4 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_directory_client.cpp @@ -211,6 +211,7 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { } protocolLayerOptions.Owner = options.PosixProperties.Owner; protocolLayerOptions.Group = options.PosixProperties.Group; + protocolLayerOptions.FilePropertySemantics = options.PropertySemantics; auto result = _detail::DirectoryClient::Create( *m_pipeline, m_shareDirectoryUrl, protocolLayerOptions, context); diff --git a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp index 36d99bbab3..a5c6f7cdb4 100644 --- a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp +++ b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp @@ -218,9 +218,55 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { protocolLayerOptions.Owner = options.PosixProperties.Owner; protocolLayerOptions.Group = options.PosixProperties.Group; protocolLayerOptions.NfsFileType = options.PosixProperties.NfsFileType; + protocolLayerOptions.FilePropertySemantics = options.PropertySemantics; + + Nullable> resultNullable; + if (options.Content) + { + Azure::Nullable validationOptions + = options.ValidationOptions.HasValue() ? options.ValidationOptions + : m_uploadValidationOptions; + if (validationOptions.HasValue() + && validationOptions.Value().Algorithm != StorageChecksumAlgorithm::None) + { + protocolLayerOptions.StructuredBodyType = _internal::CrcStructuredMessage; + protocolLayerOptions.StructuredContentLength = options.Content->Length(); + _internal::StructuredMessageEncodingStreamOptions encodingStreamOptions; + encodingStreamOptions.Flags = _internal::StructuredMessageFlags::Crc64; + auto structuredContent + = _internal::StructuredMessageEncodingStream(options.Content, encodingStreamOptions); + auto result = _detail::FileClient::Create( + *m_pipeline, m_shareFileUrl, structuredContent, protocolLayerOptions, context); + if (!result.Value.StructuredBodyType.HasValue()) + { + throw StorageException( + "Structured message response without x-ms-structured-body header."); + } + resultNullable = std::move(result); + } + } + if (!resultNullable.HasValue()) + { + if (options.Content) + { + auto result = _detail::FileClient::Create( + *m_pipeline, m_shareFileUrl, *options.Content, protocolLayerOptions, context); + resultNullable = std::move(result); + } + else + { + auto emptyBody = Core::IO::_internal::NullBodyStream(); + auto result = _detail::FileClient::Create( + *m_pipeline, + m_shareFileUrl, + options.Content ? (*options.Content) : emptyBody, + protocolLayerOptions, + context); + resultNullable = std::move(result); + } + } + auto result = std::move(resultNullable.Value()); - auto result - = _detail::FileClient::Create(*m_pipeline, m_shareFileUrl, protocolLayerOptions, context); Models::CreateFileResult ret; ret.Created = true; ret.ETag = std::move(result.Value.ETag); @@ -1313,8 +1359,9 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { protocolLayerOptions.Group = options.PosixProperties.Group; protocolLayerOptions.NfsFileType = options.PosixProperties.NfsFileType; - auto createResult - = _detail::FileClient::Create(*m_pipeline, m_shareFileUrl, protocolLayerOptions, context); + auto emptyBody = Core::IO::_internal::NullBodyStream(); + auto createResult = _detail::FileClient::Create( + *m_pipeline, m_shareFileUrl, emptyBody, protocolLayerOptions, context); auto uploadPageFunc = [&](int64_t offset, int64_t length, int64_t chunkId, int64_t numChunks) { (void)chunkId; @@ -1427,8 +1474,9 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares { protocolLayerOptions.Group = options.PosixProperties.Group; protocolLayerOptions.NfsFileType = options.PosixProperties.NfsFileType; - auto createResult - = _detail::FileClient::Create(*m_pipeline, m_shareFileUrl, protocolLayerOptions, context); + auto emptyBody = Core::IO::_internal::NullBodyStream(); + auto createResult = _detail::FileClient::Create( + *m_pipeline, m_shareFileUrl, emptyBody, protocolLayerOptions, context); auto uploadPageFunc = [&](int64_t offset, int64_t length, int64_t chunkId, int64_t numChunks) { (void)chunkId; diff --git a/sdk/storage/azure-storage-files-shares/swagger/README.md b/sdk/storage/azure-storage-files-shares/swagger/README.md index dcebc5d13a..140a8f58d2 100644 --- a/sdk/storage/azure-storage-files-shares/swagger/README.md +++ b/sdk/storage/azure-storage-files-shares/swagger/README.md @@ -9,7 +9,7 @@ package-name: azure-storage-files-shares namespace: Azure::Storage::Files::Shares 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.FileStorage/stable/2026-04-06/file.json +input-file: https://raw.githubusercontent.com/nickliu-msft/azure-rest-api-specs/a3da2fadd99e8e3bd64202ca456f8a187c4e8d4e/specification/storage/data-plane/Microsoft.FileStorage/stable/2026-06-06/file.json ``` ## ModelFour Options @@ -79,12 +79,12 @@ directive: "name": "ApiVersion", "modelAsString": false }, - "enum": ["2026-04-06"] + "enum": ["2026-06-06"] }; - from: swagger-document where: $.parameters transform: > - $.ApiVersionParameter.enum = ["2026-04-06"]; + $.ApiVersionParameter.enum = ["2026-06-06"]; ``` ### Rename Operations @@ -238,7 +238,6 @@ directive: $.FilePermissionFormat["enum"] = ["sddl", "binary"]; $.FileAttributes["required"] = true; delete $.EnableSmbDirectoryLease; - delete $.FilePropertySemantics; - from: swagger-document where: $.definitions transform: > @@ -752,10 +751,6 @@ directive: }, "x-namespace" : "_detail" }; - - from: swagger-document - where: $["x-ms-paths"]["/{shareName}/{directory}?restype=directory"].put.parameters - transform: > - $ = $.filter(p => !p["$ref"] || !p["$ref"].endsWith("#/parameters/FilePropertySemantics")); ``` ### DeleteDirectory @@ -868,10 +863,6 @@ directive: }, "x-namespace" : "_detail" }; - - from: swagger-document - where: $["x-ms-paths"]["/{shareName}/{directory}/{fileName}"].put.parameters - transform: > - $ = $.filter(p => !(p["$ref"] && (p["$ref"].endsWith("#/parameters/ContentMD5") || p["$ref"].endsWith("#/parameters/FilePropertySemantics") || p["$ref"].endsWith("#/parameters/ContentLengthOptional") || p["$ref"].endsWith("#/parameters/OptionalBody")))); ``` ### GetFileProperties diff --git a/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp index baf611596f..d9fdd094ae 100644 --- a/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/ut/share_directory_client_test.cpp @@ -1448,4 +1448,25 @@ namespace Azure { namespace Storage { namespace Test { properties.PosixProperties.NfsFileType.Value(), Files::Shares::Models::NfsFileType::Directory); } + + TEST_F(FileShareDirectoryClientTest, FilePermissionSemantics) + { + std::string permission = "O:S-1-5-21-2127521184-1604012920-1887927527-21560751G:S-1-5-21-" + "2127521184-1604012920-1887927527-513D:AI(A;;FA;;;SY)(A;;FA;;;BA)(A;;" + "0x1200a9;;;S-1-5-21-397955417-626881126-188441444-3053964)"; + + { + auto client = m_fileShareDirectoryClient->GetSubdirectoryClient(RandomString()); + Files::Shares::CreateDirectoryOptions options; + options.DirectoryPermission = permission; + options.PropertySemantics = Files::Shares::Models::FilePropertySemantics::Restore; + EXPECT_NO_THROW(client.Create(options)); + } + { + auto client = m_fileShareDirectoryClient->GetSubdirectoryClient(RandomString()); + Files::Shares::CreateDirectoryOptions options; + options.PropertySemantics = Files::Shares::Models::FilePropertySemantics::New; + EXPECT_NO_THROW(client.Create(options)); + } + } }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp b/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp index c7be915786..0a6a7995de 100644 --- a/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-shares/test/ut/share_file_client_test.cpp @@ -2814,4 +2814,53 @@ namespace Azure { namespace Storage { namespace Test { } } + TEST_F(FileShareFileClientTest, CreateWithData) + { + std::vector content = RandomBuffer(100); + auto memBodyStream = Core::IO::MemoryBodyStream(content); + + { + auto fileName = RandomString(); + auto client = m_fileShareDirectoryClient->GetFileClient(fileName); + Files::Shares::CreateFileOptions createOptions; + createOptions.Content = &memBodyStream; + EXPECT_NO_THROW(client.Create(content.size(), createOptions)); + auto downloadedContent = client.Download().Value.BodyStream->ReadToEnd(); + EXPECT_EQ(downloadedContent, content); + } + { + memBodyStream.Rewind(); + auto fileName = RandomString(); + auto client = m_fileShareDirectoryClient->GetFileClient(fileName); + Files::Shares::CreateFileOptions createOptions; + createOptions.ValidationOptions = Files::Shares::TransferValidationOptions(); + createOptions.ValidationOptions.Value().Algorithm = StorageChecksumAlgorithm::Crc64; + createOptions.Content = &memBodyStream; + EXPECT_NO_THROW(client.Create(content.size(), createOptions)); + auto downloadedContent = client.Download().Value.BodyStream->ReadToEnd(); + EXPECT_EQ(downloadedContent, content); + } + } + + TEST_F(FileShareFileClientTest, FilePermissionSemantics) + { + std::string permission = "O:S-1-5-21-2127521184-1604012920-1887927527-21560751G:S-1-5-21-" + "2127521184-1604012920-1887927527-513D:AI(A;;FA;;;SY)(A;;FA;;;BA)(A;;" + "0x1200a9;;;S-1-5-21-397955417-626881126-188441444-3053964)"; + + { + auto client = m_fileShareDirectoryClient->GetFileClient(RandomString()); + Files::Shares::CreateFileOptions options; + options.Permission = permission; + options.PropertySemantics = Files::Shares::Models::FilePropertySemantics::Restore; + EXPECT_NO_THROW(client.Create(1024, options)); + } + { + auto client = m_fileShareDirectoryClient->GetFileClient(RandomString()); + Files::Shares::CreateFileOptions options; + options.PropertySemantics = Files::Shares::Models::FilePropertySemantics::New; + EXPECT_NO_THROW(client.Create(1024, options)); + } + } + }}} // namespace Azure::Storage::Test