diff --git a/sdk/storage/azure-storage-blobs/CHANGELOG.md b/sdk/storage/azure-storage-blobs/CHANGELOG.md index 7fe508e70..4ac5b1495 100644 --- a/sdk/storage/azure-storage-blobs/CHANGELOG.md +++ b/sdk/storage/azure-storage-blobs/CHANGELOG.md @@ -2,6 +2,10 @@ ## 12.0.0-beta.6 (Unreleased) +### New Features + +* `CreateIfNotExists` and `DeleteIfExists` for blob containers and blobs. + ### Breaking Changes * Rename AppendBlobAccessConditions::MaxSize to AppendBlobAccessConditions::IfMaxSizeLessThanOrEqual. diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/append_blob_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/append_blob_client.hpp index 69be522ec..007e98d93 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/append_blob_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/append_blob_client.hpp @@ -117,6 +117,17 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Response Create( const CreateAppendBlobOptions& options = CreateAppendBlobOptions()) const; + /** + * @brief Creates a new 0-length append blob. The content keeps unchanged if the blob already + * exists. + * + * @param options Optional parameters to execute this function. + * @return A CreateAppendBlobResult describing the newly created append blob. Null if the blob + * already exists. + */ + Azure::Core::Response CreateIfNotExists( + const CreateAppendBlobOptions& options = CreateAppendBlobOptions()) const; + /** * @brief Commits a new block of data, represented by the content BodyStream to the end * of the existing append blob. 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 631f019a9..94e76f9a7 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 @@ -272,6 +272,15 @@ namespace Azure { namespace Storage { namespace Blobs { Azure::Core::Response Delete( const DeleteBlobOptions& options = DeleteBlobOptions()) const; + /** + * @brief Marks the specified blob or snapshot for deletion if it exists. + * + * @param options Optional parameters to execute this function. + * @return A DeleteBlobResult on successfully deleting. Null if the blob doesn't exist. + */ + Azure::Core::Response DeleteIfExists( + const DeleteBlobOptions& options = DeleteBlobOptions()) const; + /** * @brief Restores the contents and metadata of a soft deleted blob and any associated * soft deleted snapshots. diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_container_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_container_client.hpp index 60a0acece..889095a84 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_container_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_container_client.hpp @@ -129,24 +129,44 @@ namespace Azure { namespace Storage { namespace Blobs { * @brief Creates a new container under the specified account. If the container with the * same name already exists, the operation fails. * - * @param options Optional - * parameters to execute this function. + * @param options Optional parameters to execute this function. * @return A CreateBlobContainerResult describing the newly created blob container. */ Azure::Core::Response Create( const CreateBlobContainerOptions& options = CreateBlobContainerOptions()) const; + /** + * @brief Creates a new container under the specified account. If the container with the + * same name already exists, it is not changed. + * + * @param options Optional parameters to execute this function. + * @return A CreateBlobContainerResult describing the newly created blob container if the + * container doesn't exist. Null if the container already exists. + */ + Azure::Core::Response CreateIfNotExists( + const CreateBlobContainerOptions& options = CreateBlobContainerOptions()) const; + /** * @brief Marks the specified container for deletion. The container and any blobs * contained within it are later deleted during garbage collection. * - * @param - * options Optional parameters to execute this function. + * @param options Optional parameters to execute this function. * @return A DeleteBlobContainerResult if successful. */ Azure::Core::Response Delete( const DeleteBlobContainerOptions& options = DeleteBlobContainerOptions()) const; + /** + * @brief Marks the specified container for deletion if it exists. The container and any blobs + * contained within it are later deleted during garbage collection. + * + * @param options Optional parameters to execute this function. + * @return A DeleteBlobContainerResult if the container exists. Null if the container doesn't + * exist. + */ + Azure::Core::Response DeleteIfExists( + const DeleteBlobContainerOptions& options = DeleteBlobContainerOptions()) const; + /** * @brief Restores a previously deleted container. The destionation is referenced by current * BlobContainerClient. diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/page_blob_client.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/page_blob_client.hpp index 4b7ee04d3..503132405 100644 --- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/page_blob_client.hpp +++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/page_blob_client.hpp @@ -109,12 +109,11 @@ namespace Azure { namespace Storage { namespace Blobs { PageBlobClient WithVersionId(const std::string& versionId) const; /** - * @brief Creates a new page blob of the specified size. The content of any existing + * @brief Creates a new page blob of the specified size. The content of any existing * blob is overwritten with the newly initialized page blob. * - * @param - * blobContentLength Specifies the maximum size for the page blob. The size must be aligned to a - * 512-byte boundary. + * @param blobContentLength Specifies the maximum size for the page blob. The size must be + * aligned to a 512-byte boundary. * @param options Optional parameters to execute this function. * @return A CreatePageBlobResult describing the newly created page blob. */ @@ -122,6 +121,20 @@ namespace Azure { namespace Storage { namespace Blobs { int64_t blobContentLength, const CreatePageBlobOptions& options = CreatePageBlobOptions()) const; + /** + * @brief Creates a new page blob of the specified size. The content keeps unchanged if the blob + * already exists. + * + * @param blobContentLength Specifies the maximum size for the page blob. The size must be + * aligned to a 512-byte boundary. + * @param options Optional parameters to execute this function. + * @return A CreatePageBlobResult describing the newly created page blob. Null if the blob + * already exists. + */ + Azure::Core::Response CreateIfNotExists( + int64_t blobContentLength, + const CreatePageBlobOptions& options = CreatePageBlobOptions()) const; + /** * @brief Writes content to a range of pages in a page blob, starting at offset. * diff --git a/sdk/storage/azure-storage-blobs/sample/blob_getting_started.cpp b/sdk/storage/azure-storage-blobs/sample/blob_getting_started.cpp index 1e29d5b76..c99c79834 100644 --- a/sdk/storage/azure-storage-blobs/sample/blob_getting_started.cpp +++ b/sdk/storage/azure-storage-blobs/sample/blob_getting_started.cpp @@ -17,15 +17,8 @@ void BlobsGettingStarted() auto containerClient = BlobContainerClient::CreateFromConnectionString(GetConnectionString(), containerName); - try - { - containerClient.Create(); - } - catch (std::runtime_error& e) - { - // The container may already exist - std::cout << e.what() << std::endl; - } + + containerClient.CreateIfNotExists(); BlockBlobClient blobClient = containerClient.GetBlockBlobClient(blobName); 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 8a283605b..2a180e97f 100644 --- a/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/append_blob_client.cpp @@ -96,6 +96,26 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobUrl, protocolLayerOptions); } + Azure::Core::Response AppendBlobClient::CreateIfNotExists( + const CreateAppendBlobOptions& options) const + { + auto optionsCopy = options; + optionsCopy.AccessConditions.IfNoneMatch = ETagWildcard; + try + { + return Create(optionsCopy); + } + catch (StorageException& e) + { + if (e.StatusCode == Core::Http::HttpStatusCode::Conflict + && e.ErrorCode == "BlobAlreadyExists") + { + return Azure::Core::Response(std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response AppendBlobClient::AppendBlock( Azure::Core::Http::BodyStream* content, const AppendBlockOptions& options) const diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp index c93361868..74c4898cf 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp @@ -630,6 +630,23 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobUrl, protocolLayerOptions); } + Azure::Core::Response BlobClient::DeleteIfExists( + const DeleteBlobOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.StatusCode == Core::Http::HttpStatusCode::NotFound && e.ErrorCode == "BlobNotFound") + { + return Azure::Core::Response(std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response BlobClient::Undelete( const UndeleteBlobOptions& options) const { 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 c011668f7..8219d3278 100644 --- a/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/blob_container_client.cpp @@ -151,6 +151,24 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobContainerUrl, protocolLayerOptions); } + Azure::Core::Response BlobContainerClient::CreateIfNotExists( + const CreateBlobContainerOptions& options) const + { + try + { + return Create(options); + } + catch (StorageException& e) + { + if (e.StatusCode == Core::Http::HttpStatusCode::Conflict + && e.ErrorCode == "ContainerAlreadyExists") + { + return Azure::Core::Response(std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response BlobContainerClient::Delete( const DeleteBlobContainerOptions& options) const { @@ -162,6 +180,24 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobContainerUrl, protocolLayerOptions); } + Azure::Core::Response BlobContainerClient::DeleteIfExists( + const DeleteBlobContainerOptions& options) const + { + try + { + return Delete(options); + } + catch (StorageException& e) + { + if (e.StatusCode == Core::Http::HttpStatusCode::NotFound + && e.ErrorCode == "ContainerNotFound") + { + return Azure::Core::Response(std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response BlobContainerClient::Undelete( const std::string& deletedBlobContainerName, const std::string& deletedBlobContainerVersion, 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 9071cfdcc..2b428b15a 100644 --- a/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp +++ b/sdk/storage/azure-storage-blobs/src/page_blob_client.cpp @@ -102,6 +102,27 @@ namespace Azure { namespace Storage { namespace Blobs { options.Context, *m_pipeline, m_blobUrl, protocolLayerOptions); } + Azure::Core::Response PageBlobClient::CreateIfNotExists( + int64_t blobContentLength, + const CreatePageBlobOptions& options) const + { + auto optionsCopy = options; + optionsCopy.AccessConditions.IfNoneMatch = ETagWildcard; + try + { + return Create(blobContentLength, optionsCopy); + } + catch (StorageException& e) + { + if (e.StatusCode == Core::Http::HttpStatusCode::Conflict + && e.ErrorCode == "BlobAlreadyExists") + { + return Azure::Core::Response(std::move(e.RawResponse)); + } + throw; + } + } + Azure::Core::Response PageBlobClient::UploadPages( int64_t offset, Azure::Core::Http::BodyStream* content, diff --git a/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp index 74fc67657..2de5162ac 100644 --- a/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/append_blob_client_test.cpp @@ -332,4 +332,27 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(getPropertiesResult->IsSealed.GetValue()); } + TEST_F(AppendBlobClientTest, CreateIfNotExists) + { + auto blobClient = Azure::Storage::Blobs::AppendBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + auto blobClientWithoutAuth = Azure::Storage::Blobs::AppendBlobClient(blobClient.GetUrl()); + EXPECT_THROW(blobClientWithoutAuth.CreateIfNotExists(), StorageException); + { + auto response = blobClient.CreateIfNotExists(); + EXPECT_TRUE(response.HasValue()); + } + auto blobContent + = Azure::Core::Http::MemoryBodyStream(m_blobContent.data(), m_blobContent.size()); + blobClient.AppendBlock(&blobContent); + { + auto response = blobClient.CreateIfNotExists(); + EXPECT_FALSE(response.HasValue()); + } + auto downloadStream = std::move(blobClient.Download()->BodyStream); + EXPECT_EQ( + Azure::Core::Http::BodyStream::ReadToEnd(Azure::Core::Context(), *downloadStream), + m_blobContent); + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp b/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp index 391432994..b748559c9 100644 --- a/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/blob_container_client_test.cpp @@ -70,6 +70,28 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_FALSE(res2.GetRawResponse().GetHeaders().at(Details::HttpHeaderRequestId).empty()); EXPECT_FALSE(res2.GetRawResponse().GetHeaders().at(Details::HttpHeaderDate).empty()); EXPECT_FALSE(res2.GetRawResponse().GetHeaders().at(Details::HttpHeaderXMsVersion).empty()); + + container_client = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString() + "UPPERCASE"); + EXPECT_THROW(container_client.CreateIfNotExists(), StorageException); + container_client = Azure::Storage::Blobs::BlobContainerClient::CreateFromConnectionString( + StandardStorageConnectionString(), LowercaseRandomString()); + { + auto response = container_client.DeleteIfExists(); + EXPECT_FALSE(response.HasValue()); + } + { + auto response = container_client.CreateIfNotExists(); + EXPECT_TRUE(response.HasValue()); + } + { + auto response = container_client.CreateIfNotExists(); + EXPECT_FALSE(response.HasValue()); + } + { + auto response = container_client.DeleteIfExists(); + EXPECT_TRUE(response.HasValue()); + } } TEST_F(BlobContainerClientTest, Metadata) diff --git a/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp index e14898c06..b2702b62e 100644 --- a/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/block_blob_client_test.cpp @@ -757,4 +757,34 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_TRUE(exceptionCaught); } + TEST_F(BlockBlobClientTest, DeleteIfExists) + { + auto blobClient = Azure::Storage::Blobs::BlockBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + auto blobClientWithoutAuth = Azure::Storage::Blobs::BlockBlobClient(blobClient.GetUrl()); + { + auto response = blobClient.DeleteIfExists(); + EXPECT_FALSE(response.HasValue()); + } + std::vector emptyContent; + blobClient.UploadFrom(emptyContent.data(), emptyContent.size()); + EXPECT_THROW(blobClientWithoutAuth.DeleteIfExists(), StorageException); + { + auto response = blobClient.DeleteIfExists(); + EXPECT_TRUE(response.HasValue()); + } + + blobClient.UploadFrom(emptyContent.data(), emptyContent.size()); + auto snapshot = blobClient.CreateSnapshot()->Snapshot; + auto blobClientWithSnapshot = blobClient.WithSnapshot(snapshot); + { + auto response = blobClientWithSnapshot.DeleteIfExists(); + EXPECT_TRUE(response.HasValue()); + } + { + auto response = blobClientWithSnapshot.DeleteIfExists(); + EXPECT_FALSE(response.HasValue()); + } + } + }}} // namespace Azure::Storage::Test diff --git a/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp b/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp index 59439f37b..bfabff94e 100644 --- a/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp +++ b/sdk/storage/azure-storage-blobs/test/page_blob_client_test.cpp @@ -251,4 +251,28 @@ namespace Azure { namespace Storage { namespace Test { EXPECT_THROW(pageBlobClient.UploadPages(0, &pageContent, options), StorageException); } + TEST_F(PageBlobClientTest, CreateIfNotExists) + { + auto blobClient = Azure::Storage::Blobs::PageBlobClient::CreateFromConnectionString( + StandardStorageConnectionString(), m_containerName, RandomString()); + auto blobClientWithoutAuth = Azure::Storage::Blobs::PageBlobClient(blobClient.GetUrl()); + EXPECT_THROW(blobClientWithoutAuth.CreateIfNotExists(m_blobContent.size()), StorageException); + { + auto response = blobClient.CreateIfNotExists(m_blobContent.size()); + EXPECT_TRUE(response.HasValue()); + } + + auto blobContent + = Azure::Core::Http::MemoryBodyStream(m_blobContent.data(), m_blobContent.size()); + blobClient.UploadPages(0, &blobContent); + { + auto response = blobClient.CreateIfNotExists(m_blobContent.size()); + EXPECT_FALSE(response.HasValue()); + } + auto downloadStream = std::move(blobClient.Download()->BodyStream); + EXPECT_EQ( + Azure::Core::Http::BodyStream::ReadToEnd(Azure::Core::Context(), *downloadStream), + m_blobContent); + } + }}} // namespace Azure::Storage::Test