diff --git a/sdk/storage/azure-storage-blobs/CHANGELOG.md b/sdk/storage/azure-storage-blobs/CHANGELOG.md index 9088c353d..68b6a64bb 100644 --- a/sdk/storage/azure-storage-blobs/CHANGELOG.md +++ b/sdk/storage/azure-storage-blobs/CHANGELOG.md @@ -4,10 +4,20 @@ ### Features Added +- Added lease ID access condition and tags access condition for `BlobClient::SetAccessTier()`. +- Added source ETag access conditions and last-modified access conditions for `PageBlobClient::UploadPagesFromUri()`. +- Added three new fields `IsServerEncrypted`, `EncryptionKeySha256` and `EncryptionScope` into `SetBlobMetadataResult`. +- Added support for setting blob tags when creating or copying blobs. +- Added new fields `AccessTierChangedOn`, `ArchiveStatus`, `RehydratePriority`, `CopyId`, `CopySource`, `CopyStatus`, `CopyStatusDescription`, `IsIncrementalCopy`, `IncrementalCopyDestinationSnapshot`, `CopyProgress`, `CopyCompletedOn`, `TagCount`, `Tags`, `DeletedOn` and `RemainingRetentionDays` into `BlobItemDetails`. +- Added support for including blob tags when listing blobs. + ### Breaking Changes ### Bugs Fixed +- Fixed a bug where lease ID didn't work for `BlobContainerClient::GetAccessPolicy()`. +- Fixed a bug where `BlobItemDetails::EncryptionKeySha256` was always null because it wasn't correctly parsed from xml. + ### Other Changes ## 12.0.1 (2021-07-07) diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp index aa8d9b48f..ecce9b255 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp @@ -435,6 +435,12 @@ namespace Azure { namespace Storage { namespace Blobs { * same blob. */ Azure::Nullable RehydratePriority; + /** + * @brief Optional conditions that must be met to perform this operation. + */ + struct : public LeaseAccessConditions, public TagAccessConditions + { + } AccessConditions; }; /** @@ -451,6 +457,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Storage::Metadata Metadata; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Optional conditions that must be met to perform this operation. */ @@ -458,8 +469,15 @@ namespace Azure { namespace Storage { namespace Blobs { /** * @brief Optional conditions that the source must meet to perform this operation. + * + * @note Lease access condition only works for API versions before 2012-02-12. */ - BlobAccessConditions SourceAccessConditions; + struct : public Azure::ModifiedConditions, + public Azure::MatchConditions, + public LeaseAccessConditions, + public TagAccessConditions + { + } SourceAccessConditions; /** * @brief Specifies the tier to be set on the target blob. @@ -698,6 +716,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Storage::Metadata Metadata; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Indicates the tier to be set on blob. */ @@ -724,6 +747,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Storage::Metadata Metadata; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Indicates the tier to be set on blob. */ @@ -816,6 +844,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Storage::Metadata Metadata; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Indicates the tier to be set on blob. */ @@ -861,6 +894,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Storage::Metadata Metadata; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Optional conditions that must be met to perform this operation. */ @@ -945,6 +983,11 @@ namespace Azure { namespace Storage { namespace Blobs { */ Azure::Nullable AccessTier; + /** + * @brief The tags to set for this blob. + */ + std::map Tags; + /** * @brief Optional conditions that must be met to perform this operation. */ @@ -985,6 +1028,13 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Optional conditions that must be met to perform this operation. */ PageBlobAccessConditions AccessConditions; + + /** + * @brief Optional conditions that the source must meet to perform this operation. + */ + struct : public Azure::ModifiedConditions, public Azure::MatchConditions + { + } SourceAccessConditions; }; /** diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp index 438d0a125..d1c85bf55 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/protocol/blob_rest_client.hpp @@ -846,6 +846,18 @@ namespace Azure { namespace Storage { namespace Blobs { * True if the access tier is not explicitly set on the blob. */ Azure::Nullable IsAccessTierInferred; + /** + * The date and time the tier was changed on the object. + */ + Azure::Nullable AccessTierChangedOn; + /** + * Indicates if the blob is being rehydrated. + */ + Azure::Nullable ArchiveStatus; + /** + * Priority of rehydrate if the blob is being rehydrated. + */ + Azure::Nullable RehydratePriority; /** * The current lease status of the blob. */ @@ -886,6 +898,66 @@ namespace Azure { namespace Storage { namespace Blobs { * Only valid when Object Replication is enabled and current blob is the source. */ std::vector ObjectReplicationSourceProperties; + /** + * String identifier for the last attempted Copy Blob operation where this blob was the + * destination. This value is null if this blob has never been the destination of a copy + * operation, or if this blob has been modified after a concluded copy operation. + */ + Azure::Nullable CopyId; + /** + * URL that specifies the source blob or file used in the last attempted copy operation where + * this blob was the destination blob. This value is null if this blob has never been the + * destination of a copy operation, or if this blob has been modified after a concluded copy + * operation. + */ + Azure::Nullable CopySource; + /** + * State of the copy operation identified by the copy ID. Possible values include success, + * pending, aborted, failed etc. This value is null if this blob has never been the + * destination of a copy operation, or if this blob has been modified after a concluded copy + * operation. + */ + Azure::Nullable CopyStatus; + /** + * Describes the cause of the last fatal or non-fatal copy operation failure. This is not null + * only when copy status is failed or pending. + */ + Azure::Nullable CopyStatusDescription; + /** + * True if the copy operation is incremental copy. + */ + Azure::Nullable IsIncrementalCopy; + /** + * Snapshot time of the last successful incremental copy snapshot for this blob. + */ + Azure::Nullable IncrementalCopyDestinationSnapshot; + /** + * Contains the number of bytes copied and the total bytes in the source in the last attempted + * copy operation where this blob was the destination blob. + */ + Azure::Nullable CopyProgress; + /** + * Conclusion time of the last attempted copy operation where this blob was the destination + * blob. + */ + Azure::Nullable CopyCompletedOn; + /** + * the number of tags stored on the blob. + */ + Azure::Nullable TagCount; + /** + * User-defined tags for this blob. + */ + std::map Tags; + /** + * Data and time at which this blob was deleted. Only valid when this blob was deleted. + */ + Azure::Nullable DeletedOn; + /** + * Remaining days before this blob will be permanantely deleted. Only valid when this blob was + * deleted. + */ + Azure::Nullable RemainingRetentionDays; }; // struct BlobItemDetails /** @@ -1921,6 +1993,10 @@ namespace Azure { namespace Storage { namespace Blobs { * Uncommitted blobs should be included. */ UncomittedBlobs = 32, + /** + * Tags should be included. + */ + Tags = 64, }; // bitwise enum ListBlobsIncludeFlags inline ListBlobsIncludeFlags operator|(ListBlobsIncludeFlags lhs, ListBlobsIncludeFlags rhs) @@ -2082,6 +2158,16 @@ namespace Azure { namespace Storage { namespace Blobs { */ struct SetBlobExpiryResult final { + /** + * The ETag contains a value that you can use to perform operations conditionally. + */ + Azure::ETag ETag; + /** + * The date and time the container was last modified. Any operation that modifies the blob, + * including an update of the metadata or properties, changes the last-modified time of the + * blob. + */ + Azure::DateTime LastModified; }; // struct SetBlobExpiryResult /** @@ -2122,10 +2208,23 @@ namespace Azure { namespace Storage { namespace Blobs { */ Azure::DateTime LastModified; /** - * The current sequence number for a page blob. This value is null for block blobs or append - * blobs. + * This value is always null, don't use it. */ Azure::Nullable SequenceNumber; + /** + * True if the blob data and metadata are completely encrypted using the specified algorithm. + * Otherwise, the value is set to false (when the blob is unencrypted, or if only parts of the + * blob/application metadata are encrypted). + */ + bool IsServerEncrypted = false; + /** + * The SHA-256 hash of the encryption key used to encrypt the blob data and metadata. + */ + Azure::Nullable> EncryptionKeySha256; + /** + * Name of the encryption scope used to encrypt the blob data and metadata. + */ + Azure::Nullable EncryptionScope; }; // struct SetBlobMetadataResult /** @@ -2486,6 +2585,9 @@ namespace Azure { namespace Storage { namespace Blobs { namespace _detail { struct GetBlobTagsResult final { + /** + * User-defined tags for this blob. + */ std::map Tags; }; // struct GetBlobTagsResult } // namespace _detail @@ -2575,11 +2677,6 @@ namespace Azure { namespace Storage { namespace Blobs { * blob. */ Azure::DateTime LastModified; - /** - * The current sequence number for a page blob. This value is null for block blobs or append - * blobs. - */ - Azure::Nullable SequenceNumber; }; // struct ReleaseBlobLeaseResult } // namespace _detail @@ -2745,6 +2842,7 @@ namespace Azure { namespace Storage { namespace Blobs { ListBlobsIncludeFlags::Snapshots, ListBlobsIncludeFlags::Versions, ListBlobsIncludeFlags::UncomittedBlobs, + ListBlobsIncludeFlags::Tags, }; const char* string_list[] = { "copy", @@ -2753,6 +2851,7 @@ namespace Azure { namespace Storage { namespace Blobs { "snapshots", "versions", "uncommittedblobs", + "tags", }; std::string ret; for (size_t i = 0; i < sizeof(value_list) / sizeof(ListBlobsIncludeFlags); ++i) @@ -4986,6 +5085,10 @@ namespace Azure { namespace Storage { namespace Blobs { } request.GetUrl().AppendQueryParameter("restype", "container"); request.GetUrl().AppendQueryParameter("comp", "acl"); + if (options.LeaseId.HasValue()) + { + request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); + } auto pHttpResponse = pipeline.Send(request, context); Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse; BlobContainerAccessPolicy response; @@ -5687,15 +5790,32 @@ namespace Azure { namespace Storage { namespace Blobs { k_BlobType, k_AccessTier, k_AccessTierInferred, + k_AccessTierChangeTime, + k_ArchiveStatus, + k_RehydratePriority, k_LeaseStatus, k_LeaseState, k_LeaseDuration, k_ServerEncrypted, - k_EncryptionKeySHA256, + k_CustomerProvidedKeySha256, + k_EncryptionScope, k_Sealed, k_xmsblobsequencenumber, + k_CopyId, + k_CopyStatus, + k_CopySource, + k_CopyProgress, + k_CopyCompletionTime, + k_CopyStatusDescription, + k_IncrementalCopy, + k_CopyDestinationSnapshot, + k_DeletedTime, + k_RemainingRetentionDays, + k_TagCount, k_Metadata, k_OrMetadata, + k_Tags, + k_TagSet, k_Unknown, }; std::vector path; @@ -5803,6 +5923,18 @@ namespace Azure { namespace Storage { namespace Blobs { { path.emplace_back(XmlTagName::k_AccessTierInferred); } + else if (node.Name == "AccessTierChangeTime") + { + path.emplace_back(XmlTagName::k_AccessTierChangeTime); + } + else if (node.Name == "ArchiveStatus") + { + path.emplace_back(XmlTagName::k_ArchiveStatus); + } + else if (node.Name == "RehydratePriority") + { + path.emplace_back(XmlTagName::k_RehydratePriority); + } else if (node.Name == "LeaseStatus") { path.emplace_back(XmlTagName::k_LeaseStatus); @@ -5819,9 +5951,13 @@ namespace Azure { namespace Storage { namespace Blobs { { path.emplace_back(XmlTagName::k_ServerEncrypted); } - else if (node.Name == "EncryptionKeySHA256") + else if (node.Name == "CustomerProvidedKeySha256") { - path.emplace_back(XmlTagName::k_EncryptionKeySHA256); + path.emplace_back(XmlTagName::k_CustomerProvidedKeySha256); + } + else if (node.Name == "EncryptionScope") + { + path.emplace_back(XmlTagName::k_EncryptionScope); } else if (node.Name == "Sealed") { @@ -5831,6 +5967,50 @@ namespace Azure { namespace Storage { namespace Blobs { { path.emplace_back(XmlTagName::k_xmsblobsequencenumber); } + else if (node.Name == "CopyId") + { + path.emplace_back(XmlTagName::k_CopyId); + } + else if (node.Name == "CopyStatus") + { + path.emplace_back(XmlTagName::k_CopyStatus); + } + else if (node.Name == "CopySource") + { + path.emplace_back(XmlTagName::k_CopySource); + } + else if (node.Name == "CopyProgress") + { + path.emplace_back(XmlTagName::k_CopyProgress); + } + else if (node.Name == "CopyCompletionTime") + { + path.emplace_back(XmlTagName::k_CopyCompletionTime); + } + else if (node.Name == "CopyStatusDescription") + { + path.emplace_back(XmlTagName::k_CopyStatusDescription); + } + else if (node.Name == "IncrementalCopy") + { + path.emplace_back(XmlTagName::k_IncrementalCopy); + } + else if (node.Name == "CopyDestinationSnapshot") + { + path.emplace_back(XmlTagName::k_CopyDestinationSnapshot); + } + else if (node.Name == "DeletedTime") + { + path.emplace_back(XmlTagName::k_DeletedTime); + } + else if (node.Name == "RemainingRetentionDays") + { + path.emplace_back(XmlTagName::k_RemainingRetentionDays); + } + else if (node.Name == "TagCount") + { + path.emplace_back(XmlTagName::k_TagCount); + } else if (node.Name == "Metadata") { path.emplace_back(XmlTagName::k_Metadata); @@ -5839,6 +6019,14 @@ namespace Azure { namespace Storage { namespace Blobs { { path.emplace_back(XmlTagName::k_OrMetadata); } + else if (node.Name == "Tags") + { + path.emplace_back(XmlTagName::k_Tags); + } + else if (node.Name == "TagSet") + { + path.emplace_back(XmlTagName::k_TagSet); + } else { path.emplace_back(XmlTagName::k_Unknown); @@ -5854,6 +6042,13 @@ namespace Azure { namespace Storage { namespace Blobs { = ObjectReplicationSourcePropertiesFromXml(reader); path.pop_back(); } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Tags + && path[1] == XmlTagName::k_TagSet) + { + ret.Details.Tags = TagsFromXml(reader); + path.pop_back(); + } } else if (node.Type == _internal::XmlNodeType::Text) { @@ -5972,6 +6167,25 @@ namespace Azure { namespace Storage { namespace Blobs { { ret.Details.IsAccessTierInferred = node.Value == "true"; } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_AccessTierChangeTime) + { + ret.Details.AccessTierChangedOn + = Azure::DateTime::Parse(node.Value, Azure::DateTime::DateFormat::Rfc1123); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_ArchiveStatus) + { + ret.Details.ArchiveStatus = ArchiveStatus(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_RehydratePriority) + { + ret.Details.RehydratePriority = RehydratePriority(node.Value); + } else if ( path.size() == 2 && path[0] == XmlTagName::k_Properties && path[1] == XmlTagName::k_LeaseStatus) @@ -5998,10 +6212,16 @@ namespace Azure { namespace Storage { namespace Blobs { } else if ( path.size() == 2 && path[0] == XmlTagName::k_Properties - && path[1] == XmlTagName::k_EncryptionKeySHA256) + && path[1] == XmlTagName::k_CustomerProvidedKeySha256) { ret.Details.EncryptionKeySha256 = Azure::Core::Convert::Base64Decode(node.Value); } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_EncryptionScope) + { + ret.Details.EncryptionScope = node.Value; + } else if ( path.size() == 2 && path[0] == XmlTagName::k_Properties && path[1] == XmlTagName::k_Sealed) @@ -6014,6 +6234,74 @@ namespace Azure { namespace Storage { namespace Blobs { { ret.Details.SequenceNumber = std::stoll(node.Value); } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyId) + { + ret.Details.CopyId = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyStatus) + { + ret.Details.CopyStatus = CopyStatus(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopySource) + { + ret.Details.CopySource = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyProgress) + { + ret.Details.CopyProgress = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyCompletionTime) + { + ret.Details.CopyCompletedOn + = Azure::DateTime::Parse(node.Value, Azure::DateTime::DateFormat::Rfc1123); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyStatusDescription) + { + ret.Details.CopyStatusDescription = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_IncrementalCopy) + { + ret.Details.IsIncrementalCopy = node.Value == "true"; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_CopyDestinationSnapshot) + { + ret.Details.IncrementalCopyDestinationSnapshot = node.Value; + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_DeletedTime) + { + ret.Details.DeletedOn + = Azure::DateTime::Parse(node.Value, Azure::DateTime::DateFormat::Rfc1123); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_RemainingRetentionDays) + { + ret.Details.RemainingRetentionDays = std::stoi(node.Value); + } + else if ( + path.size() == 2 && path[0] == XmlTagName::k_Properties + && path[1] == XmlTagName::k_TagCount) + { + ret.Details.TagCount = std::stoi(node.Value); + } } } return ret; @@ -6108,6 +6396,56 @@ namespace Azure { namespace Storage { namespace Blobs { return ret; } + static std::map TagsFromXml(_internal::XmlReader& reader) + { + std::map ret; + int depth = 0; + std::string key; + bool is_key = false; + bool is_value = false; + while (true) + { + auto node = reader.Read(); + if (node.Type == _internal::XmlNodeType::End) + { + break; + } + else if (node.Type == _internal::XmlNodeType::StartTag) + { + ++depth; + if (node.Name == "Key") + { + is_key = true; + } + else if (node.Name == "Value") + { + is_value = true; + } + } + else if (node.Type == _internal::XmlNodeType::EndTag) + { + if (depth-- == 0) + { + break; + } + } + if (depth == 2 && node.Type == _internal::XmlNodeType::Text) + { + if (is_key) + { + key = node.Value; + is_key = false; + } + else if (is_value) + { + ret.emplace(std::move(key), node.Value); + is_value = false; + } + } + } + return ret; + } + static std::vector ObjectReplicationSourcePropertiesFromXml( _internal::XmlReader& reader) { @@ -6727,6 +7065,9 @@ namespace Azure { namespace Storage { namespace Blobs { { throw StorageException::CreateFromResponse(std::move(pHttpResponse)); } + response.ETag = Azure::ETag(httpResponse.GetHeaders().at("etag")); + response.LastModified = Azure::DateTime::Parse( + httpResponse.GetHeaders().at("last-modified"), Azure::DateTime::DateFormat::Rfc1123); return Azure::Response( std::move(response), std::move(pHttpResponse)); } @@ -7294,6 +7635,27 @@ namespace Azure { namespace Storage { namespace Blobs { response.ETag = Azure::ETag(httpResponse.GetHeaders().at("etag")); response.LastModified = Azure::DateTime::Parse( httpResponse.GetHeaders().at("last-modified"), Azure::DateTime::DateFormat::Rfc1123); + auto x_ms_blob_sequence_number__iterator + = httpResponse.GetHeaders().find("x-ms-blob-sequence-number"); + if (x_ms_blob_sequence_number__iterator != httpResponse.GetHeaders().end()) + { + response.SequenceNumber = std::stoll(x_ms_blob_sequence_number__iterator->second); + } + response.IsServerEncrypted + = httpResponse.GetHeaders().at("x-ms-request-server-encrypted") == "true"; + auto x_ms_encryption_key_sha256__iterator + = httpResponse.GetHeaders().find("x-ms-encryption-key-sha256"); + if (x_ms_encryption_key_sha256__iterator != httpResponse.GetHeaders().end()) + { + response.EncryptionKeySha256 + = Azure::Core::Convert::Base64Decode(x_ms_encryption_key_sha256__iterator->second); + } + auto x_ms_encryption_scope__iterator + = httpResponse.GetHeaders().find("x-ms-encryption-scope"); + if (x_ms_encryption_scope__iterator != httpResponse.GetHeaders().end()) + { + response.EncryptionScope = x_ms_encryption_scope__iterator->second; + } return Azure::Response( std::move(response), std::move(pHttpResponse)); } @@ -7303,6 +7665,7 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Nullable Timeout; Models::AccessTier AccessTier; Azure::Nullable RehydratePriority; + Azure::Nullable LeaseId; Azure::Nullable IfTags; }; // struct SetBlobAccessTierOptions @@ -7326,6 +7689,10 @@ namespace Azure { namespace Storage { namespace Blobs { request.SetHeader( "x-ms-rehydrate-priority", options.RehydratePriority.Value().ToString()); } + if (options.LeaseId.HasValue()) + { + request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); + } if (options.IfTags.HasValue()) { request.SetHeader("x-ms-if-tags", options.IfTags.Value()); @@ -7366,9 +7733,9 @@ namespace Azure { namespace Storage { namespace Blobs { { Azure::Nullable Timeout; Storage::Metadata Metadata; + std::map Tags; std::string SourceUri; Azure::Nullable LeaseId; - Azure::Nullable SourceLeaseId; Azure::Nullable AccessTier; Azure::Nullable RehydratePriority; Azure::Nullable IfModifiedSince; @@ -7381,6 +7748,7 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::ETag SourceIfMatch; Azure::ETag SourceIfNoneMatch; Azure::Nullable SourceIfTags; + Azure::Nullable SourceLeaseId; Azure::Nullable ShouldSealDestination; }; // struct StartBlobCopyFromUriOptions @@ -7403,15 +7771,25 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.Tags.empty()) + { + std::string blobTagsValue; + for (const auto& tag : options.Tags) + { + if (!blobTagsValue.empty()) + { + blobTagsValue += "&"; + } + blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "=" + + _internal::UrlEncodeQueryParameter(tag.second); + } + request.SetHeader("x-ms-tags", std::move(blobTagsValue)); + } request.SetHeader("x-ms-copy-source", options.SourceUri); if (options.LeaseId.HasValue()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); } - if (options.SourceLeaseId.HasValue()) - { - request.SetHeader("x-ms-source-lease-id", options.SourceLeaseId.Value()); - } if (options.AccessTier.HasValue()) { request.SetHeader("x-ms-access-tier", options.AccessTier.Value().ToString()); @@ -7476,6 +7854,10 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-source-if-tags", options.SourceIfTags.Value()); } + if (options.SourceLeaseId.HasValue()) + { + request.SetHeader("x-ms-source-lease-id", options.SourceLeaseId.Value()); + } auto pHttpResponse = pipeline.Send(request, context); Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse; Models::_detail::StartBlobCopyFromUriResult response; @@ -8047,12 +8429,6 @@ namespace Azure { namespace Storage { namespace Blobs { response.ETag = Azure::ETag(httpResponse.GetHeaders().at("etag")); response.LastModified = Azure::DateTime::Parse( httpResponse.GetHeaders().at("last-modified"), Azure::DateTime::DateFormat::Rfc1123); - auto x_ms_blob_sequence_number__iterator - = httpResponse.GetHeaders().find("x-ms-blob-sequence-number"); - if (x_ms_blob_sequence_number__iterator != httpResponse.GetHeaders().end()) - { - response.SequenceNumber = std::stoll(x_ms_blob_sequence_number__iterator->second); - } return Azure::Response( std::move(response), std::move(pHttpResponse)); } @@ -8271,6 +8647,7 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Nullable TransactionalContentHash; BlobHttpHeaders HttpHeaders; Storage::Metadata Metadata; + std::map Tags; Azure::Nullable LeaseId; Azure::Nullable AccessTier; Azure::Nullable EncryptionKey; @@ -8368,6 +8745,20 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.Tags.empty()) + { + std::string blobTagsValue; + for (const auto& tag : options.Tags) + { + if (!blobTagsValue.empty()) + { + blobTagsValue += "&"; + } + blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "=" + + _internal::UrlEncodeQueryParameter(tag.second); + } + request.SetHeader("x-ms-tags", std::move(blobTagsValue)); + } if (options.LeaseId.HasValue()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -8741,6 +9132,7 @@ namespace Azure { namespace Storage { namespace Blobs { std::vector> BlockList; BlobHttpHeaders HttpHeaders; Storage::Metadata Metadata; + std::map Tags; Azure::Nullable LeaseId; Azure::Nullable EncryptionKey; Azure::Nullable> EncryptionKeySha256; @@ -8811,6 +9203,20 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.Tags.empty()) + { + std::string blobTagsValue; + for (const auto& tag : options.Tags) + { + if (!blobTagsValue.empty()) + { + blobTagsValue += "&"; + } + blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "=" + + _internal::UrlEncodeQueryParameter(tag.second); + } + request.SetHeader("x-ms-tags", std::move(blobTagsValue)); + } if (options.LeaseId.HasValue()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -9122,6 +9528,7 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::ETag IfMatch; Azure::ETag IfNoneMatch; Azure::Nullable IfTags; + std::map Tags; }; // struct CreatePageBlobOptions static Azure::Response Create( @@ -9170,6 +9577,20 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.Tags.empty()) + { + std::string blobTagsValue; + for (const auto& tag : options.Tags) + { + if (!blobTagsValue.empty()) + { + blobTagsValue += "&"; + } + blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "=" + + _internal::UrlEncodeQueryParameter(tag.second); + } + request.SetHeader("x-ms-tags", std::move(blobTagsValue)); + } if (options.LeaseId.HasValue()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); @@ -9467,6 +9888,10 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::ETag IfMatch; Azure::ETag IfNoneMatch; Azure::Nullable IfTags; + Azure::Nullable SourceIfModifiedSince; + Azure::Nullable SourceIfUnmodifiedSince; + Azure::ETag SourceIfMatch; + Azure::ETag SourceIfNoneMatch; }; // struct UploadPageBlobPagesFromUriOptions static Azure::Response UploadPagesFromUri( @@ -9587,6 +10012,28 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-if-tags", options.IfTags.Value()); } + if (options.SourceIfModifiedSince.HasValue()) + { + request.SetHeader( + "x-ms-source-if-modified-since", + options.SourceIfModifiedSince.Value().ToString( + Azure::DateTime::DateFormat::Rfc1123)); + } + if (options.SourceIfUnmodifiedSince.HasValue()) + { + request.SetHeader( + "x-ms-source-if-unmodified-since", + options.SourceIfUnmodifiedSince.Value().ToString( + Azure::DateTime::DateFormat::Rfc1123)); + } + if (options.SourceIfMatch.HasValue() && !options.SourceIfMatch.ToString().empty()) + { + request.SetHeader("x-ms-source-if-match", options.SourceIfMatch.ToString()); + } + if (options.SourceIfNoneMatch.HasValue() && !options.SourceIfNoneMatch.ToString().empty()) + { + request.SetHeader("x-ms-source-if-none-match", options.SourceIfNoneMatch.ToString()); + } auto pHttpResponse = pipeline.Send(request, context); Azure::Core::Http::RawResponse& httpResponse = *pHttpResponse; UploadPagesFromUriResult response; @@ -10247,6 +10694,7 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Nullable Timeout; BlobHttpHeaders HttpHeaders; Storage::Metadata Metadata; + std::map Tags; Azure::Nullable LeaseId; Azure::Nullable EncryptionKey; Azure::Nullable> EncryptionKeySha256; @@ -10305,6 +10753,20 @@ namespace Azure { namespace Storage { namespace Blobs { { request.SetHeader("x-ms-meta-" + pair.first, pair.second); } + if (!options.Tags.empty()) + { + std::string blobTagsValue; + for (const auto& tag : options.Tags) + { + if (!blobTagsValue.empty()) + { + blobTagsValue += "&"; + } + blobTagsValue += _internal::UrlEncodeQueryParameter(tag.first) + "=" + + _internal::UrlEncodeQueryParameter(tag.second); + } + request.SetHeader("x-ms-tags", std::move(blobTagsValue)); + } if (options.LeaseId.HasValue()) { request.SetHeader("x-ms-lease-id", options.LeaseId.Value()); diff --git a/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp index b1fd22bc2..7995202f2 100644 --- a/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp @@ -79,6 +79,7 @@ namespace Azure { namespace Storage { namespace Blobs { _detail::BlobRestClient::AppendBlob::CreateAppendBlobOptions protocolLayerOptions; protocolLayerOptions.HttpHeaders = options.HttpHeaders; protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tags = options.Tags; protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp index 57112b514..8456ab960 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp @@ -525,6 +525,8 @@ namespace Azure { namespace Storage { namespace Blobs { _detail::BlobRestClient::Blob::SetBlobAccessTierOptions protocolLayerOptions; protocolLayerOptions.AccessTier = tier; protocolLayerOptions.RehydratePriority = options.RehydratePriority; + protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; + protocolLayerOptions.IfTags = options.AccessConditions.TagConditions; return _detail::BlobRestClient::Blob::SetAccessTier( *m_pipeline, m_blobUrl, protocolLayerOptions, context); } @@ -536,6 +538,7 @@ namespace Azure { namespace Storage { namespace Blobs { { _detail::BlobRestClient::Blob::StartBlobCopyFromUriOptions protocolLayerOptions; protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tags = options.Tags; protocolLayerOptions.SourceUri = sourceUri; protocolLayerOptions.AccessTier = options.AccessTier; protocolLayerOptions.RehydratePriority = options.RehydratePriority; @@ -545,12 +548,12 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch; protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; protocolLayerOptions.IfTags = options.AccessConditions.TagConditions; - protocolLayerOptions.SourceLeaseId = options.SourceAccessConditions.LeaseId; protocolLayerOptions.SourceIfModifiedSince = options.SourceAccessConditions.IfModifiedSince; protocolLayerOptions.SourceIfUnmodifiedSince = options.SourceAccessConditions.IfUnmodifiedSince; protocolLayerOptions.SourceIfMatch = options.SourceAccessConditions.IfMatch; protocolLayerOptions.SourceIfNoneMatch = options.SourceAccessConditions.IfNoneMatch; protocolLayerOptions.ShouldSealDestination = options.ShouldSealDestination; + protocolLayerOptions.SourceLeaseId = options.SourceAccessConditions.LeaseId; protocolLayerOptions.SourceIfTags = options.SourceAccessConditions.TagConditions; auto response = _detail::BlobRestClient::Blob::StartCopyFromUri( diff --git a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp index 0980b3d32..59d6dd62c 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp @@ -261,6 +261,10 @@ namespace Azure { namespace Storage { namespace Blobs { { i.Details.IsSealed = false; } + if (i.Details.CopyStatus.HasValue() && !i.Details.IsIncrementalCopy.HasValue()) + { + i.Details.IsIncrementalCopy = false; + } } ListBlobsPagedResponse pagedResponse; @@ -310,6 +314,10 @@ namespace Azure { namespace Storage { namespace Blobs { { i.Details.IsSealed = false; } + if (i.Details.CopyStatus.HasValue() && !i.Details.IsIncrementalCopy.HasValue()) + { + i.Details.IsIncrementalCopy = false; + } } ListBlobsByHierarchyPagedResponse pagedResponse; diff --git a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp index cdbb9134a..c484c5097 100644 --- a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp @@ -98,6 +98,7 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.TransactionalContentHash = options.TransactionalContentHash; protocolLayerOptions.HttpHeaders = options.HttpHeaders; protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tags = options.Tags; protocolLayerOptions.AccessTier = options.AccessTier; protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; @@ -138,6 +139,7 @@ namespace Azure { namespace Storage { namespace Blobs { UploadBlockBlobOptions uploadBlockBlobOptions; uploadBlockBlobOptions.HttpHeaders = options.HttpHeaders; uploadBlockBlobOptions.Metadata = options.Metadata; + uploadBlockBlobOptions.Tags = options.Tags; uploadBlockBlobOptions.AccessTier = options.AccessTier; return Upload(contentStream, uploadBlockBlobOptions, context); } @@ -187,6 +189,7 @@ namespace Azure { namespace Storage { namespace Blobs { CommitBlockListOptions commitBlockListOptions; commitBlockListOptions.HttpHeaders = options.HttpHeaders; commitBlockListOptions.Metadata = options.Metadata; + commitBlockListOptions.Tags = options.Tags; commitBlockListOptions.AccessTier = options.AccessTier; auto commitBlockListResponse = CommitBlockList(blockIds, commitBlockListOptions, context); @@ -219,6 +222,7 @@ namespace Azure { namespace Storage { namespace Blobs { UploadBlockBlobOptions uploadBlockBlobOptions; uploadBlockBlobOptions.HttpHeaders = options.HttpHeaders; uploadBlockBlobOptions.Metadata = options.Metadata; + uploadBlockBlobOptions.Tags = options.Tags; uploadBlockBlobOptions.AccessTier = options.AccessTier; return Upload(contentStream, uploadBlockBlobOptions, context); } @@ -276,6 +280,7 @@ namespace Azure { namespace Storage { namespace Blobs { CommitBlockListOptions commitBlockListOptions; commitBlockListOptions.HttpHeaders = options.HttpHeaders; commitBlockListOptions.Metadata = options.Metadata; + commitBlockListOptions.Tags = options.Tags; commitBlockListOptions.AccessTier = options.AccessTier; auto commitBlockListResponse = CommitBlockList(blockIds, commitBlockListOptions, context); @@ -351,6 +356,7 @@ namespace Azure { namespace Storage { namespace Blobs { } protocolLayerOptions.HttpHeaders = options.HttpHeaders; protocolLayerOptions.Metadata = options.Metadata; + protocolLayerOptions.Tags = options.Tags; protocolLayerOptions.AccessTier = options.AccessTier; protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; diff --git a/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp index 370d3a69c..9c60c6d10 100644 --- a/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp @@ -86,6 +86,7 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.HttpHeaders = options.HttpHeaders; protocolLayerOptions.Metadata = options.Metadata; protocolLayerOptions.AccessTier = options.AccessTier; + protocolLayerOptions.Tags = options.Tags; protocolLayerOptions.LeaseId = options.AccessConditions.LeaseId; protocolLayerOptions.IfModifiedSince = options.AccessConditions.IfModifiedSince; protocolLayerOptions.IfUnmodifiedSince = options.AccessConditions.IfUnmodifiedSince; @@ -174,6 +175,10 @@ namespace Azure { namespace Storage { namespace Blobs { protocolLayerOptions.IfMatch = options.AccessConditions.IfMatch; protocolLayerOptions.IfNoneMatch = options.AccessConditions.IfNoneMatch; protocolLayerOptions.IfTags = options.AccessConditions.TagConditions; + protocolLayerOptions.SourceIfModifiedSince = options.SourceAccessConditions.IfModifiedSince; + protocolLayerOptions.SourceIfUnmodifiedSince = options.SourceAccessConditions.IfUnmodifiedSince; + protocolLayerOptions.SourceIfMatch = options.SourceAccessConditions.IfMatch; + protocolLayerOptions.SourceIfNoneMatch = options.SourceAccessConditions.IfNoneMatch; if (m_customerProvidedKey.HasValue()) { protocolLayerOptions.EncryptionKey = m_customerProvidedKey.Value().Key; diff --git a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp index 12d3ede8c..2ee49e123 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/append_blob_client_test.cpp @@ -93,6 +93,19 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(appendBlobClient.Delete(), StorageException); } + TEST_F(AppendBlobClientTest, CreateWithTags) + { + auto appendBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + Blobs::CreateAppendBlobOptions options; + options.Tags["key1"] = "value1"; + options.Tags["key2"] = "value2"; + options.Tags["key3 +-./:=_"] = "v1 +-./:=_"; + appendBlobClient.Create(options); + + EXPECT_EQ(appendBlobClient.GetTags().Value, options.Tags); + } + TEST_F(AppendBlobClientTest, AccessConditionLastModifiedTime) { auto appendBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( @@ -201,31 +214,15 @@ namespace Azure { namespace Storage { namespace Test { { auto sourceBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( StandardStorageConnectionString(), m_containerName, RandomString()); - sourceBlobClient.Create(); - Blobs::BlobLeaseClient sourceLeaseClient( - sourceBlobClient, Blobs::BlobLeaseClient::CreateUniqueLeaseId()); - auto leaseResponse = sourceLeaseClient.Acquire(Blobs::BlobLeaseClient::InfiniteLeaseDuration); - std::string leaseId = leaseResponse.Value.LeaseId; - Azure::ETag eTag = leaseResponse.Value.ETag; - auto lastModifiedTime = leaseResponse.Value.LastModified; + auto createResponse = sourceBlobClient.Create(); + Azure::ETag eTag = createResponse.Value.ETag; + auto lastModifiedTime = createResponse.Value.LastModified; auto timeBeforeStr = lastModifiedTime - std::chrono::seconds(1); auto timeAfterStr = lastModifiedTime + std::chrono::seconds(1); auto destBlobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( StandardStorageConnectionString(), m_containerName, RandomString()); - { - Blobs::StartBlobCopyFromUriOptions options; - options.SourceAccessConditions.LeaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId(); - /* - don't know why, the copy operation also succeeds even if the lease ID doesn't match. - EXPECT_THROW( - destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options), StorageException); - */ - options.SourceAccessConditions.LeaseId = leaseId; - EXPECT_NO_THROW(destBlobClient.StartCopyFromUri(sourceBlobClient.GetUrl(), options)); - } - sourceLeaseClient.Break(); { Blobs::StartBlobCopyFromUriOptions options; options.SourceAccessConditions.IfMatch = eTag; @@ -277,20 +274,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(getPropertiesResult.Value.IsSealed.HasValue()); EXPECT_FALSE(getPropertiesResult.Value.IsSealed.Value()); - Azure::Storage::Blobs::ListBlobsOptions options; - options.Prefix = blobName; - for (auto pageResponse = m_blobContainerClient->ListBlobs(options); pageResponse.HasPage(); - pageResponse.MoveToNextPage()) - { - for (const auto& blob : pageResponse.Blobs) - { - if (blob.Name == blobName) - { - EXPECT_TRUE(blob.Details.IsSealed.HasValue()); - EXPECT_FALSE(blob.Details.IsSealed.Value()); - } - } - } + auto blobItem = GetBlobItem(blobName); + EXPECT_TRUE(blobItem.Details.IsSealed.HasValue()); + EXPECT_FALSE(blobItem.Details.IsSealed.Value()); Blobs::SealAppendBlobOptions sealOptions; sealOptions.AccessConditions.IfAppendPositionEqual = m_blobContent.size() + 1; @@ -310,18 +296,9 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(getPropertiesResult.Value.IsSealed.HasValue()); EXPECT_TRUE(getPropertiesResult.Value.IsSealed.Value()); - for (auto pageResponse = m_blobContainerClient->ListBlobs(options); pageResponse.HasPage(); - pageResponse.MoveToNextPage()) - { - for (const auto& blob : pageResponse.Blobs) - { - if (blob.Name == blobName) - { - EXPECT_TRUE(blob.Details.IsSealed.HasValue()); - EXPECT_TRUE(blob.Details.IsSealed.Value()); - } - } - } + blobItem = GetBlobItem(blobName); + EXPECT_TRUE(blobItem.Details.IsSealed.HasValue()); + EXPECT_TRUE(blobItem.Details.IsSealed.Value()); auto blobClient2 = m_blobContainerClient->GetAppendBlobClient(RandomString()); diff --git a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp index 1c867011d..f4f343d3c 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.cpp @@ -51,6 +51,27 @@ namespace Azure { namespace Storage { namespace Test { *_internal::ParseConnectionString(StandardStorageConnectionString()).KeyCredential); } + Blobs::Models::BlobItem BlobContainerClientTest::GetBlobItem( + const std::string& blobName, + Blobs::Models::ListBlobsIncludeFlags include) + { + Blobs::ListBlobsOptions options; + options.Prefix = blobName; + options.Include = include; + for (auto page = m_blobContainerClient->ListBlobs(options); page.HasPage(); + page.MoveToNextPage()) + { + for (auto& blob : page.Blobs) + { + if (blob.Name == blobName) + { + return std::move(blob); + } + } + } + std::abort(); + } + TEST_F(BlobContainerClientTest, CreateDelete) { auto container_client = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( @@ -477,6 +498,23 @@ namespace Azure { namespace Storage { namespace Test { createOptions.PreventEncryptionScopeOverride.Value()); auto appendBlobClient = containerClient.GetAppendBlobClient(blobName); auto blobContentInfo = appendBlobClient.Create(); + { + Blobs::ListBlobsOptions listOptions; + listOptions.Prefix = blobName; + for (auto page = containerClient.ListBlobs(listOptions); page.HasPage(); + page.MoveToNextPage()) + { + for (auto& blob : page.Blobs) + { + if (blob.Name == blobName) + { + EXPECT_TRUE(blob.Details.IsServerEncrypted); + EXPECT_TRUE(blob.Details.EncryptionScope.HasValue()); + EXPECT_EQ(blob.Details.EncryptionScope.Value(), TestEncryptionScope); + } + } + } + } appendBlobClient.Delete(); EXPECT_TRUE(blobContentInfo.Value.EncryptionScope.HasValue()); EXPECT_EQ(blobContentInfo.Value.EncryptionScope.Value(), TestEncryptionScope); @@ -498,6 +536,10 @@ namespace Azure { namespace Storage { namespace Test { auto blobContentInfo = appendBlobClient.Create(); EXPECT_TRUE(blobContentInfo.Value.EncryptionScope.HasValue()); EXPECT_EQ(blobContentInfo.Value.EncryptionScope.Value(), TestEncryptionScope); + auto setMetadataRes = appendBlobClient.SetMetadata({}); + EXPECT_TRUE(setMetadataRes.Value.IsServerEncrypted); + ASSERT_TRUE(setMetadataRes.Value.EncryptionScope.HasValue()); + EXPECT_EQ(setMetadataRes.Value.EncryptionScope.Value(), TestEncryptionScope); auto properties = appendBlobClient.GetProperties().Value; EXPECT_TRUE(properties.EncryptionScope.HasValue()); EXPECT_EQ(properties.EncryptionScope.Value(), TestEncryptionScope); @@ -569,13 +611,24 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(blobContentInfo.EncryptionKeySha256.HasValue()); EXPECT_EQ( blobContentInfo.EncryptionKeySha256.Value(), options.CustomerProvidedKey.Value().KeyHash); + auto blobItem = GetBlobItem(appendBlobName); + EXPECT_TRUE(blobItem.Details.IsServerEncrypted); + EXPECT_TRUE(blobItem.Details.EncryptionKeySha256.HasValue()); + EXPECT_EQ( + blobItem.Details.EncryptionKeySha256.Value(), + options.CustomerProvidedKey.Value().KeyHash); bodyStream.Rewind(); EXPECT_NO_THROW(appendBlob.AppendBlock(bodyStream)); EXPECT_NO_THROW(appendBlob.AppendBlockFromUri(copySourceBlob.GetUrl() + GetSas())); EXPECT_NO_THROW(appendBlob.Download()); EXPECT_NO_THROW(appendBlob.GetProperties()); - EXPECT_NO_THROW(appendBlob.SetMetadata({})); + auto setMetadataRes = appendBlob.SetMetadata({}); + EXPECT_TRUE(setMetadataRes.Value.IsServerEncrypted); + ASSERT_TRUE(setMetadataRes.Value.EncryptionKeySha256.HasValue()); + EXPECT_EQ( + setMetadataRes.Value.EncryptionKeySha256.Value(), + options.CustomerProvidedKey.Value().KeyHash); EXPECT_NO_THROW(appendBlob.CreateSnapshot()); auto appendBlobClientWithoutEncryptionKey @@ -681,16 +734,47 @@ namespace Azure { namespace Storage { namespace Test { StandardStorageConnectionString(), LowercaseRandomString()); containerClient.Create(); - std::string leaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId(); + const std::string leaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId(); + const std::string dummyLeaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId(); Blobs::BlobLeaseClient leaseClient(containerClient, leaseId); leaseClient.Acquire(std::chrono::seconds(30)); - EXPECT_THROW(containerClient.Delete(), StorageException); - Blobs::DeleteBlobContainerOptions options; - options.AccessConditions.LeaseId = leaseId; - EXPECT_NO_THROW(containerClient.Delete(options)); + { + Blobs::GetBlobContainerPropertiesOptions options; + options.AccessConditions.LeaseId = dummyLeaseId; + EXPECT_THROW(containerClient.GetProperties(options), StorageException); + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.GetProperties(options)); + } + { + Blobs::SetBlobContainerMetadataOptions options; + options.AccessConditions.LeaseId = dummyLeaseId; + EXPECT_THROW(containerClient.SetMetadata({}, options), StorageException); + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.SetMetadata({}, options)); + } + { + Blobs::GetBlobContainerAccessPolicyOptions options; + options.AccessConditions.LeaseId = dummyLeaseId; + EXPECT_THROW(containerClient.GetAccessPolicy(options), StorageException); + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.GetAccessPolicy(options)); + } + { + Blobs::SetBlobContainerAccessPolicyOptions options; + options.AccessConditions.LeaseId = dummyLeaseId; + EXPECT_THROW(containerClient.SetAccessPolicy(options), StorageException); + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.SetAccessPolicy(options)); + } + { + EXPECT_THROW(containerClient.Delete(), StorageException); + Blobs::DeleteBlobContainerOptions options; + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(containerClient.Delete(options)); + } } - TEST_F(BlobContainerClientTest, DISABLED_Tags) + TEST_F(BlobContainerClientTest, Tags) { std::string blobName = RandomString(); auto blobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( @@ -710,6 +794,8 @@ namespace Azure { namespace Storage { namespace Test { std::string v2 = RandomString(); std::string c3 = "k" + RandomString(); std::string v3 = RandomString(); + std::string c4 = "key3 +-./:=_"; + std::string v4 = "v1 +-./:=_"; tags[c1] = v1; tags[c2] = v2; tags[c3] = v3; @@ -722,11 +808,16 @@ namespace Azure { namespace Storage { namespace Test { properties = blobClient.GetProperties().Value; EXPECT_TRUE(properties.TagCount.HasValue()); - EXPECT_EQ(properties.TagCount.Value(), static_cast(tags.size())); + EXPECT_EQ(properties.TagCount.Value(), static_cast(tags.size())); downloadRet = blobClient.Download(); - EXPECT_TRUE(downloadRet.Value.Details.TagCount.HasValue()); - EXPECT_EQ(downloadRet.Value.Details.TagCount.Value(), static_cast(tags.size())); + ASSERT_TRUE(downloadRet.Value.Details.TagCount.HasValue()); + EXPECT_EQ(downloadRet.Value.Details.TagCount.Value(), static_cast(tags.size())); + + auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Tags); + ASSERT_TRUE(blobItem.Details.TagCount.HasValue()); + EXPECT_EQ(blobItem.Details.TagCount.Value(), static_cast(tags.size())); + EXPECT_EQ(blobItem.Details.Tags, tags); auto blobServiceClient = Azure::Storage::Blobs::BlobServiceClient::CreateFromConnectionString( StandardStorageConnectionString()); @@ -762,7 +853,7 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(findResults[0].BlobContainerName, m_containerName); } - TEST_F(BlobContainerClientTest, DISABLED_AccessConditionTags) + TEST_F(BlobContainerClientTest, AccessConditionTags) { std::map tags; std::string c1 = "k" + RandomString(); @@ -960,6 +1051,15 @@ namespace Azure { namespace Storage { namespace Test { blockBlobClient.UploadFrom(contentData.data(), contentData.size()); blockBlobClient.SetTags(tags); + { + Blobs::SetBlobAccessTierOptions options; + options.AccessConditions.TagConditions = successWhereExpression; + EXPECT_NO_THROW(blockBlobClient.SetAccessTier(Blobs::Models::AccessTier::Hot, options)); + options.AccessConditions.TagConditions = failWhereExpression; + EXPECT_THROW( + blockBlobClient.SetAccessTier(Blobs::Models::AccessTier::Hot, options), StorageException); + } + { Blobs::UploadBlockBlobOptions options; options.AccessConditions.TagConditions = failWhereExpression; diff --git a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.hpp b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.hpp index 7671e7edf..ce1008c5d 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.hpp +++ b/sdk/storage/azure-storage-blobs/test/ut/blob_container_client_test.hpp @@ -13,6 +13,9 @@ namespace Azure { namespace Storage { namespace Test { static void TearDownTestSuite(); static std::string GetSas(); + static Blobs::Models::BlobItem GetBlobItem( + const std::string& blobName, + Blobs::Models::ListBlobsIncludeFlags include = Blobs::Models::ListBlobsIncludeFlags::None); static std::shared_ptr m_blobContainerClient; static std::string m_containerName; 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 ae3d8ab88..6784e06ec 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 @@ -105,19 +105,19 @@ namespace Azure { namespace Storage { namespace Test { blobClient0.Delete(options); }; - // auto verify_blob_tags = [&](const std::string& sas) { - // blobClient0.Create(); - // std::map tags = {{"tag_key1", "tag_value1"}}; - // blobClient0.SetTags(tags); - // auto blobClient = Blobs::AppendBlobClient(blobUrl + sas); - // EXPECT_NO_THROW(blobClient.GetTags()); - // blobClient0.Delete(); - //}; + auto verify_blob_tags = [&](const std::string& sas) { + blobClient0.Create(); + std::map tags = {{"tag_key1", "tag_value1"}}; + blobClient0.SetTags(tags); + auto blobClient = Blobs::AppendBlobClient(blobUrl + sas); + EXPECT_NO_THROW(blobClient.GetTags()); + blobClient0.Delete(); + }; - // auto verify_blob_filter = [&](const std::string& sas) { - // auto serviceClient = Blobs::BlobServiceClient(serviceUrl + sas); - // EXPECT_NO_THROW(serviceClient.FindBlobsByTags("\"tag_key1\" = 'tag_value1'")); - //}; + auto verify_blob_filter = [&](const std::string& sas) { + auto serviceClient = Blobs::BlobServiceClient(serviceUrl + sas); + EXPECT_NO_THROW(serviceClient.FindBlobsByTags("\"tag_key1\" = 'tag_value1'")); + }; for (auto permissions : { Sas::AccountSasPermissions::All, @@ -161,11 +161,11 @@ namespace Azure { namespace Storage { namespace Test { } if ((permissions & Sas::AccountSasPermissions::Tags) == Sas::AccountSasPermissions::Tags) { - // verify_blob_tags(sasToken); + verify_blob_tags(sasToken); } if ((permissions & Sas::AccountSasPermissions::Filter) == Sas::AccountSasPermissions::Filter) { - // verify_blob_filter(sasToken); + verify_blob_filter(sasToken); } } @@ -210,8 +210,8 @@ namespace Azure { namespace Storage { namespace Test { } if ((permissions & Sas::BlobSasPermissions::Tags) == Sas::BlobSasPermissions::Tags) { - // verify_blob_tags(sasToken); - // verify_blob_tags(sasToken2); + verify_blob_tags(sasToken); + verify_blob_tags(sasToken2); } } @@ -322,8 +322,8 @@ namespace Azure { namespace Storage { namespace Test { if ((permissions & Sas::BlobContainerSasPermissions::Tags) == Sas::BlobContainerSasPermissions::Tags) { - // verify_blob_tags(sasToken); - // verify_blob_tags(sasToken2); + verify_blob_tags(sasToken); + verify_blob_tags(sasToken2); } } 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 f03996c8f..f56be2e50 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 @@ -77,6 +77,32 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(blockBlobClient.Delete(), StorageException); } + TEST_F(BlockBlobClientTest, SoftDelete) + { + const std::string blobName = RandomString(); + std::vector emptyContent; + auto blockBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, blobName); + auto blobContent = Azure::Core::IO::MemoryBodyStream(emptyContent.data(), emptyContent.size()); + blockBlobClient.Upload(blobContent); + + auto blobItem = GetBlobItem(blobName); + EXPECT_FALSE(blobItem.IsDeleted); + EXPECT_FALSE(blobItem.Details.DeletedOn.HasValue()); + EXPECT_FALSE(blobItem.Details.RemainingRetentionDays.HasValue()); + + blockBlobClient.Delete(); + + /* + // Soft delete doesn't work in storage account with versioning enabled. + blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Deleted); + EXPECT_TRUE(blobItem.IsDeleted); + ASSERT_TRUE(blobItem.Details.DeletedOn.HasValue()); + EXPECT_TRUE(IsValidTime(blobItem.Details.DeletedOn.Value())); + EXPECT_TRUE(blobItem.Details.RemainingRetentionDays.HasValue()); + */ + } + TEST_F(BlockBlobClientTest, UploadDownload) { auto res = m_blockBlobClient->Download(); @@ -108,6 +134,49 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(res.Value.BlobSize, static_cast(m_blobContent.size())); } + TEST_F(BlockBlobClientTest, UploadWithTags) + { + auto blockBlobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + std::map tags; + tags["key1"] = "value1"; + tags["key2"] = "value2"; + tags["key3 +-./:=_"] = "v1 +-./:=_"; + + std::vector blobContent(100, 'a'); + { + Blobs::UploadBlockBlobOptions options; + options.Tags = tags; + auto stream = Azure::Core::IO::MemoryBodyStream(blobContent.data(), blobContent.size()); + blockBlobClient.Upload(stream, options); + EXPECT_EQ(blockBlobClient.GetTags().Value, tags); + blockBlobClient.Delete(); + } + + { + Blobs::UploadBlockBlobFromOptions options; + options.TransferOptions.SingleUploadThreshold = 0; + options.TransferOptions.ChunkSize = blobContent.size() / 2; + options.Tags = tags; + + { + blockBlobClient.UploadFrom(blobContent.data(), blobContent.size(), options); + EXPECT_EQ(blockBlobClient.GetTags().Value, tags); + blockBlobClient.Delete(); + } + { + const std::string tempFilename = RandomString(); + { + Azure::Storage::_internal::FileWriter fileWriter(tempFilename); + fileWriter.Write(blobContent.data(), blobContent.size(), 0); + } + blockBlobClient.UploadFrom(tempFilename, options); + EXPECT_EQ(blockBlobClient.GetTags().Value, tags); + blockBlobClient.Delete(); + } + } + } + TEST_F(BlockBlobClientTest, DownloadTransactionalHash) { const std::vector dataPart1(static_cast(4_MB + 1), 'a'); @@ -204,24 +273,7 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(IsValidTime(res.Value.LastAccessedOn.Value())); } { - Azure::DateTime lastAccessedOn; - - Azure::Storage::Blobs::ListBlobsOptions options; - options.Prefix = m_blobName; - for (auto pageResult = m_blobContainerClient->ListBlobs(options); pageResult.HasPage(); - pageResult.MoveToNextPage()) - { - for (const auto& blob : pageResult.Blobs) - { - if (blob.Name == m_blobName) - { - lastAccessedOn = blob.Details.LastAccessedOn.Value(); - break; - } - } - } - - EXPECT_TRUE(IsValidTime(lastAccessedOn)); + EXPECT_TRUE(IsValidTime(GetBlobItem(m_blobName).Details.LastAccessedOn.Value())); } } @@ -256,36 +308,52 @@ namespace Azure { namespace Storage { namespace Test { TEST_F(BlockBlobClientTest, CopyFromUri) { - auto blobClient = m_blobContainerClient->GetBlobClient(RandomString()); + const std::string blobName = RandomString(); + auto blobClient = m_blobContainerClient->GetBlobClient(blobName); auto res = blobClient.StartCopyFromUri(m_blockBlobClient->GetUrl()); EXPECT_EQ(res.GetRawResponse().GetStatusCode(), Azure::Core::Http::HttpStatusCode::Accepted); + res.PollUntilDone(std::chrono::seconds(1)); auto properties = blobClient.GetProperties().Value; + EXPECT_FALSE(properties.CopyId.Value().empty()); EXPECT_FALSE(properties.CopySource.Value().empty()); EXPECT_TRUE( - properties.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Pending - || properties.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success); + properties.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success); EXPECT_FALSE(properties.CopyProgress.Value().empty()); - if (properties.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success) - { - EXPECT_TRUE(IsValidTime(properties.CopyCompletedOn.Value())); - } + EXPECT_TRUE(IsValidTime(properties.CopyCompletedOn.Value())); ASSERT_TRUE(properties.IsIncrementalCopy.HasValue()); EXPECT_FALSE(properties.IsIncrementalCopy.Value()); EXPECT_FALSE(properties.IncrementalCopyDestinationSnapshot.HasValue()); auto downloadResult = blobClient.Download(); + EXPECT_FALSE(downloadResult.Value.Details.CopyId.Value().empty()); EXPECT_FALSE(downloadResult.Value.Details.CopySource.Value().empty()); EXPECT_TRUE( downloadResult.Value.Details.CopyStatus.Value() - == Azure::Storage::Blobs::Models::CopyStatus::Pending - || downloadResult.Value.Details.CopyStatus.Value() - == Azure::Storage::Blobs::Models::CopyStatus::Success); + == Azure::Storage::Blobs::Models::CopyStatus::Success); EXPECT_FALSE(downloadResult.Value.Details.CopyProgress.Value().empty()); - if (downloadResult.Value.Details.CopyStatus.Value() - == Azure::Storage::Blobs::Models::CopyStatus::Success) - { - EXPECT_TRUE(IsValidTime(downloadResult.Value.Details.CopyCompletedOn.Value())); - } + EXPECT_TRUE(IsValidTime(downloadResult.Value.Details.CopyCompletedOn.Value())); + + auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Copy); + EXPECT_FALSE(blobItem.Details.CopyId.Value().empty()); + EXPECT_FALSE(blobItem.Details.CopySource.Value().empty()); + EXPECT_TRUE( + blobItem.Details.CopyStatus.Value() == Azure::Storage::Blobs::Models::CopyStatus::Success); + EXPECT_FALSE(blobItem.Details.CopyProgress.Value().empty()); + EXPECT_TRUE(IsValidTime(blobItem.Details.CopyCompletedOn.Value())); + ASSERT_TRUE(blobItem.Details.IsIncrementalCopy.HasValue()); + EXPECT_FALSE(blobItem.Details.IsIncrementalCopy.Value()); + EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue()); + } + + TEST_F(BlockBlobClientTest, CopyWithTags) + { + auto blobClient = m_blobContainerClient->GetBlockBlobClient(RandomString()); + Blobs::StartBlobCopyFromUriOptions options; + options.Tags["key1"] = "value1"; + options.Tags["key2"] = "value2"; + options.Tags["key3 +-./:=_"] = "v1 +-./:=_"; + blobClient.StartCopyFromUri(m_blockBlobClient->GetUrl(), options); + EXPECT_EQ(blobClient.GetTags().Value, options.Tags); } TEST_F(BlockBlobClientTest, SnapShotVersions) @@ -373,28 +441,16 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_FALSE(downloadResponse.Value.Details.IsCurrentVersion.Value()); EXPECT_EQ(version1, downloadResponse.Value.Details.VersionId.Value()); - Azure::Storage::Blobs::ListBlobsOptions options; - options.Prefix = blobName; - options.Include = Blobs::Models::ListBlobsIncludeFlags::Versions; - for (auto pageResult = m_blobContainerClient->ListBlobs(options); pageResult.HasPage(); - pageResult.MoveToNextPage()) + auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Versions); + ASSERT_TRUE(blobItem.VersionId.HasValue()); + ASSERT_TRUE(blobItem.IsCurrentVersion.HasValue()); + if (blobItem.VersionId.Value() == latestVersion) { - for (const auto& blob : pageResult.Blobs) - { - if (blob.Name == blobName) - { - ASSERT_TRUE(blob.VersionId.HasValue()); - ASSERT_TRUE(blob.IsCurrentVersion.HasValue()); - if (blob.VersionId.Value() == latestVersion) - { - EXPECT_TRUE(blob.IsCurrentVersion.Value()); - } - else - { - EXPECT_FALSE(blob.IsCurrentVersion.Value()); - } - } - } + EXPECT_TRUE(blobItem.IsCurrentVersion.Value()); + } + else + { + EXPECT_FALSE(blobItem.IsCurrentVersion.Value()); } } @@ -1023,21 +1079,11 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(properties.IsAccessTierInferred.Value()); EXPECT_FALSE(properties.AccessTierChangedOn.HasValue()); - Azure::Storage::Blobs::ListBlobsOptions options; - options.Prefix = blobName; - for (auto pageResult = m_blobContainerClient->ListBlobs(options); pageResult.HasPage(); - pageResult.MoveToNextPage()) - { - for (const auto& blob : pageResult.Blobs) - { - if (blob.Name == blobName) - { - ASSERT_TRUE(blob.Details.AccessTier.HasValue()); - ASSERT_TRUE(blob.Details.IsAccessTierInferred.HasValue()); - EXPECT_TRUE(blob.Details.IsAccessTierInferred.Value()); - } - } - } + auto blobItem = GetBlobItem(blobName); + ASSERT_TRUE(blobItem.Details.AccessTier.HasValue()); + ASSERT_TRUE(blobItem.Details.IsAccessTierInferred.HasValue()); + EXPECT_TRUE(blobItem.Details.IsAccessTierInferred.Value()); + EXPECT_FALSE(blobItem.Details.AccessTierChangedOn.HasValue()); // choose a different tier auto targetTier = properties.AccessTier.Value() == Blobs::Models::AccessTier::Hot @@ -1051,19 +1097,48 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_FALSE(properties.IsAccessTierInferred.Value()); EXPECT_TRUE(properties.AccessTierChangedOn.HasValue()); - for (auto pageResult = m_blobContainerClient->ListBlobs(options); pageResult.HasPage(); - pageResult.MoveToNextPage()) - { - for (const auto& blob : pageResult.Blobs) - { - if (blob.Name == blobName) - { - ASSERT_TRUE(blob.Details.AccessTier.HasValue()); - ASSERT_TRUE(blob.Details.IsAccessTierInferred.HasValue()); - EXPECT_FALSE(blob.Details.IsAccessTierInferred.Value()); - } - } - } + blobItem = GetBlobItem(blobName); + ASSERT_TRUE(blobItem.Details.AccessTier.HasValue()); + ASSERT_TRUE(blobItem.Details.IsAccessTierInferred.HasValue()); + EXPECT_FALSE(blobItem.Details.IsAccessTierInferred.Value()); + EXPECT_TRUE(blobItem.Details.AccessTierChangedOn.HasValue()); + + // set to archive, then rehydrate + blobClient.SetAccessTier(Blobs::Models::AccessTier::Archive); + blobClient.SetAccessTier(Blobs::Models::AccessTier::Hot); + properties = blobClient.GetProperties().Value; + ASSERT_TRUE(properties.ArchiveStatus.HasValue()); + EXPECT_EQ( + properties.ArchiveStatus.Value(), Blobs::Models::ArchiveStatus::RehydratePendingToHot); + ASSERT_TRUE(properties.RehydratePriority.HasValue()); + EXPECT_EQ(properties.RehydratePriority.Value(), Blobs::Models::RehydratePriority::Standard); + + blobItem = GetBlobItem(blobName); + ASSERT_TRUE(blobItem.Details.ArchiveStatus.HasValue()); + EXPECT_EQ( + blobItem.Details.ArchiveStatus.Value(), + Blobs::Models::ArchiveStatus::RehydratePendingToHot); + ASSERT_TRUE(blobItem.Details.RehydratePriority.HasValue()); + EXPECT_EQ( + blobItem.Details.RehydratePriority.Value(), Blobs::Models::RehydratePriority::Standard); + } + + TEST_F(BlockBlobClientTest, SetTierWithLeaseId) + { + std::vector emptyContent; + auto blobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + blobClient.UploadFrom(emptyContent.data(), emptyContent.size()); + + const std::string leaseId = Blobs::BlobLeaseClient::CreateUniqueLeaseId(); + Blobs::BlobLeaseClient leaseClient(blobClient, leaseId); + leaseClient.Acquire(std::chrono::seconds(30)); + + EXPECT_THROW(blobClient.SetAccessTier(Blobs::Models::AccessTier::Cool), StorageException); + + Blobs::SetBlobAccessTierOptions options; + options.AccessConditions.LeaseId = leaseId; + EXPECT_NO_THROW(blobClient.SetAccessTier(Blobs::Models::AccessTier::Cool, options)); } }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp index f438def48..50d835a60 100644 --- a/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/ut/page_blob_client_test.cpp @@ -62,6 +62,19 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(pageBlobClient.Delete(), StorageException); } + TEST_F(PageBlobClientTest, CreateWithTags) + { + auto pageBlobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + Blobs::CreatePageBlobOptions options; + options.Tags["key1"] = "value1"; + options.Tags["key2"] = "value2"; + options.Tags["key3 +-./:=_"] = "v1 +-./:=_"; + pageBlobClient.Create(512, options); + + EXPECT_EQ(pageBlobClient.GetTags().Value, options.Tags); + } + TEST_F(PageBlobClientTest, Resize) { auto pageBlobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( @@ -162,8 +175,9 @@ namespace Azure { namespace Storage { namespace Test { TEST_F(PageBlobClientTest, StartCopyIncremental) { + const std::string blobName = RandomString(); auto pageBlobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( - StandardStorageConnectionString(), m_containerName, RandomString()); + StandardStorageConnectionString(), m_containerName, blobName); std::string snapshot = m_pageBlobClient->CreateSnapshot().Value.Snapshot; Azure::Core::Url sourceUri(m_pageBlobClient->WithSnapshot(snapshot).GetUrl()); auto copyInfo = pageBlobClient.StartCopyIncremental(AppendQueryParameters(sourceUri, GetSas())); @@ -184,6 +198,12 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(IsValidTime(getPropertiesResult.Value.CopyCompletedOn.Value())); ASSERT_TRUE(getPropertiesResult.Value.CopyProgress.HasValue()); EXPECT_FALSE(getPropertiesResult.Value.CopyProgress.Value().empty()); + + auto blobItem = GetBlobItem(blobName, Blobs::Models::ListBlobsIncludeFlags::Copy); + ASSERT_TRUE(blobItem.Details.IsIncrementalCopy.HasValue()); + EXPECT_TRUE(blobItem.Details.IsIncrementalCopy.Value()); + ASSERT_TRUE(blobItem.Details.IncrementalCopyDestinationSnapshot.HasValue()); + EXPECT_FALSE(blobItem.Details.IncrementalCopyDestinationSnapshot.Value().empty()); } TEST_F(PageBlobClientTest, Lease) @@ -329,4 +349,56 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_EQ(downloadStream->ReadToEnd(Azure::Core::Context()), m_blobContent); } + TEST_F(PageBlobClientTest, SourceBlobAccessConditions) + { + auto sourceBlobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + + const std::string url = sourceBlobClient.GetUrl() + GetSas(); + + const int64_t blobSize = 512; + auto createResponse = sourceBlobClient.Create(blobSize); + Azure::ETag eTag = createResponse.Value.ETag; + auto lastModifiedTime = createResponse.Value.LastModified; + auto timeBeforeStr = lastModifiedTime - std::chrono::seconds(1); + auto timeAfterStr = lastModifiedTime + std::chrono::seconds(1); + + auto destBlobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + destBlobClient.Create(blobSize); + + { + Blobs::UploadPagesFromUriOptions options; + options.SourceAccessConditions.IfMatch = eTag; + EXPECT_NO_THROW(destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options)); + options.SourceAccessConditions.IfMatch = DummyETag; + EXPECT_THROW( + destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options), StorageException); + } + { + Blobs::UploadPagesFromUriOptions options; + options.SourceAccessConditions.IfNoneMatch = DummyETag; + EXPECT_NO_THROW(destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options)); + options.SourceAccessConditions.IfNoneMatch = eTag; + EXPECT_THROW( + destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options), StorageException); + } + { + Blobs::UploadPagesFromUriOptions options; + options.SourceAccessConditions.IfModifiedSince = timeBeforeStr; + EXPECT_NO_THROW(destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options)); + options.SourceAccessConditions.IfModifiedSince = timeAfterStr; + EXPECT_THROW( + destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options), StorageException); + } + { + Blobs::UploadPagesFromUriOptions options; + options.SourceAccessConditions.IfUnmodifiedSince = timeAfterStr; + EXPECT_NO_THROW(destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options)); + options.SourceAccessConditions.IfUnmodifiedSince = timeBeforeStr; + EXPECT_THROW( + destBlobClient.UploadPagesFromUri(0, url, {0, blobSize}, options), StorageException); + } + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md index 76725dc56..803934993 100644 --- a/sdk/storage/azure-storage-files-datalake/CHANGELOG.md +++ b/sdk/storage/azure-storage-files-datalake/CHANGELOG.md @@ -12,6 +12,10 @@ ## 12.0.1 (2021-07-07) +### New Features + +- Added `ETag` and `LastModified` into `ScheduleFileDeletionResult`. + ### Bug Fixes - Fixed a bug where transactional MD5 hash was treated as blob MD5 hash when downloading partial blob. diff --git a/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp index b4e298eda..7f44c37ef 100644 --- a/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp +++ b/sdk/storage/azure-storage-files-datalake/test/datalake_file_client_test.cpp @@ -333,9 +333,11 @@ namespace Azure { namespace Storage { namespace Test { { { auto client = m_fileSystemClient->GetFileClient(RandomString()); - EXPECT_NO_THROW(client.Create()); - EXPECT_NO_THROW( - client.ScheduleDeletion(Files::DataLake::ScheduleFileExpiryOriginType::NeverExpire)); + auto createResponse = client.Create(); + auto scheduleDeletionResponse + = client.ScheduleDeletion(Files::DataLake::ScheduleFileExpiryOriginType::NeverExpire); + EXPECT_EQ(scheduleDeletionResponse.Value.ETag, createResponse.Value.ETag); + EXPECT_EQ(scheduleDeletionResponse.Value.LastModified, createResponse.Value.LastModified); } { auto client = m_fileSystemClient->GetFileClient(RandomString());