From 689cd533d8e8e89aa76331cdf35a14ec2144d6dd Mon Sep 17 00:00:00 2001 From: sfali Date: Tue, 10 Sep 2024 22:25:39 -0400 Subject: [PATCH] Delete Container support #3253 1. Request builder for delete container 2. Implementation of delete container and Scala and Java API 3. Tests for delete container 4. Documentation --- .../storage/impl/AzureStorageStream.scala | 8 +++ .../azure/storage/javadsl/BlobService.scala | 12 +++++ .../storage/requests/DeleteContainer.scala | 54 +++++++++++++++++++ .../azure/storage/scaladsl/BlobService.scala | 12 +++++ .../test/java/docs/javadsl/StorageTest.java | 17 ++++++ .../scaladsl/AzuriteIntegrationSpec.scala | 10 ---- .../scaladsl/StorageIntegrationSpec.scala | 40 +++++++++++--- .../scaladsl/StorageWireMockBase.scala | 10 ++++ .../scala/docs/scaladsl/StorageSpec.scala | 39 +++++++++++++- docs/src/main/paradox/azure-storage.md | 11 ++++ 10 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/requests/DeleteContainer.scala diff --git a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/impl/AzureStorageStream.scala b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/impl/AzureStorageStream.scala index 7a430e800b..bace04b460 100644 --- a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/impl/AzureStorageStream.scala +++ b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/impl/AzureStorageStream.scala @@ -30,6 +30,7 @@ import akka.stream.alpakka.azure.storage.requests.{ ClearFileRange, CreateContainer, CreateFile, + DeleteContainer, GetProperties, RequestBuilder, UpdateFileRange @@ -132,6 +133,13 @@ object AzureStorageStream { objectPath = objectPath, requestBuilder = requestBuilder) + private[storage] def deleteContainer(objectPath: String, + requestBuilder: DeleteContainer): Source[Option[ObjectMetadata], NotUsed] = + handleRequest(successCode = Accepted, + storageType = BlobType, + objectPath = objectPath, + requestBuilder = requestBuilder) + /** * Common function to handle all requests where we don't expect response body. * diff --git a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/javadsl/BlobService.scala b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/javadsl/BlobService.scala index bd1ce927c9..266c1a4612 100644 --- a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/javadsl/BlobService.scala +++ b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/javadsl/BlobService.scala @@ -12,6 +12,7 @@ import akka.http.scaladsl.model.HttpEntity import akka.stream.alpakka.azure.storage.impl.AzureStorageStream import akka.stream.alpakka.azure.storage.requests.{ CreateContainer, + DeleteContainer, DeleteFile, GetBlob, GetProperties, @@ -133,4 +134,15 @@ object BlobService { */ def createContainer(objectPath: String, requestBuilder: CreateContainer): Source[Optional[ObjectMetadata], NotUsed] = AzureStorageStream.createContainer(objectPath, requestBuilder).map(opt => Optional.ofNullable(opt.orNull)).asJava + + /** + * Delete container. + * + * @param objectPath name of the container + * @param requestBuilder builder to build deleteContainer request + * @return A [[akka.stream.scaladsl.Source Source]] containing an [[scala.Option]] of + * [[akka.stream.alpakka.azure.storage.ObjectMetadata]], will be [[scala.None]] in case the object does not exist + */ + def deleteContainer(objectPath: String, requestBuilder: DeleteContainer): Source[Optional[ObjectMetadata], NotUsed] = + AzureStorageStream.deleteContainer(objectPath, requestBuilder).map(opt => Optional.ofNullable(opt.orNull)).asJava } diff --git a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/requests/DeleteContainer.scala b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/requests/DeleteContainer.scala new file mode 100644 index 0000000000..4bd931124c --- /dev/null +++ b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/requests/DeleteContainer.scala @@ -0,0 +1,54 @@ +/* + * Copyright (C) since 2016 Lightbend Inc. + */ + +package akka.stream.alpakka +package azure +package storage +package requests + +import akka.http.scaladsl.model.{HttpHeader, HttpMethod, HttpMethods} +import akka.stream.alpakka.azure.storage.headers.ServerSideEncryption +import akka.stream.alpakka.azure.storage.impl.StorageHeaders + +final class DeleteContainer(val leaseId: Option[String] = None, + override val sse: Option[ServerSideEncryption] = None, + override val additionalHeaders: Seq[HttpHeader] = Seq.empty) + extends RequestBuilder { + + override protected val method: HttpMethod = HttpMethods.DELETE + + override protected val queryParams: Map[String, String] = super.queryParams ++ Map("restype" -> "container") + + def withLeaseId(leaseId: String): DeleteContainer = copy(leaseId = Option(leaseId)) + + override def withServerSideEncryption(sse: ServerSideEncryption): DeleteContainer = copy(sse = Option(sse)) + + override def addHeader(httpHeader: HttpHeader): DeleteContainer = + copy(additionalHeaders = additionalHeaders :+ httpHeader) + + override protected def getHeaders: Seq[HttpHeader] = + StorageHeaders() + .witServerSideEncryption(sse) + .withLeaseIdHeader(leaseId) + .withAdditionalHeaders(additionalHeaders) + .headers + + private def copy(leaseId: Option[String] = leaseId, + sse: Option[ServerSideEncryption] = sse, + additionalHeaders: Seq[HttpHeader] = additionalHeaders) = + new DeleteContainer(leaseId = leaseId, sse = sse, additionalHeaders = additionalHeaders) +} + +object DeleteContainer { + + /* + * Scala API + */ + def apply(): DeleteContainer = new DeleteContainer() + + /* + * Java API + */ + def create(): DeleteContainer = DeleteContainer() +} diff --git a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/scaladsl/BlobService.scala b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/scaladsl/BlobService.scala index e4b508a848..8eceac1939 100644 --- a/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/scaladsl/BlobService.scala +++ b/azure-storage/src/main/scala/akka/stream/alpakka/azure/storage/scaladsl/BlobService.scala @@ -13,6 +13,7 @@ import akka.stream.alpakka.azure.storage.impl.AzureStorageStream import akka.stream.alpakka.azure.storage.requests.{ CreateContainer, DeleteBlob, + DeleteContainer, GetBlob, GetProperties, PutAppendBlock, @@ -110,4 +111,15 @@ object BlobService { */ def createContainer(objectPath: String, requestBuilder: CreateContainer): Source[Option[ObjectMetadata], NotUsed] = AzureStorageStream.createContainer(objectPath, requestBuilder) + + /** + * Delete container. + * + * @param objectPath name of the container + * @param requestBuilder builder to build deleteContainer request + * @return A [[akka.stream.scaladsl.Source Source]] containing an [[scala.Option]] of + * [[akka.stream.alpakka.azure.storage.ObjectMetadata]], will be [[scala.None]] in case the object does not exist + */ + def deleteContainer(objectPath: String, requestBuilder: DeleteContainer): Source[Option[ObjectMetadata], NotUsed] = + AzureStorageStream.deleteContainer(objectPath, requestBuilder) } diff --git a/azure-storage/src/test/java/docs/javadsl/StorageTest.java b/azure-storage/src/test/java/docs/javadsl/StorageTest.java index dba0e62e33..29cd3bfcc9 100644 --- a/azure-storage/src/test/java/docs/javadsl/StorageTest.java +++ b/azure-storage/src/test/java/docs/javadsl/StorageTest.java @@ -14,6 +14,7 @@ import akka.stream.alpakka.azure.storage.requests.ClearFileRange; import akka.stream.alpakka.azure.storage.requests.CreateContainer; import akka.stream.alpakka.azure.storage.requests.CreateFile; +import akka.stream.alpakka.azure.storage.requests.DeleteContainer; import akka.stream.alpakka.azure.storage.requests.DeleteFile; import akka.stream.alpakka.azure.storage.requests.GetBlob; import akka.stream.alpakka.azure.storage.requests.GetFile; @@ -82,6 +83,22 @@ public void createContainer() throws Exception { Assert.assertEquals(objectMetadata.getETag().get(), ETagRawValue()); } + @Test + public void deleteContainer() throws Exception { + mockDeleteContainer(); + + //#delete-container + final Source, NotUsed> source = BlobService.deleteContainer(containerName(), DeleteContainer.create()); + + final CompletionStage> optionalCompletionStage = source.runWith(Sink.head(), system); + //#delete-container + + final var optionalObjectMetadata = optionalCompletionStage.toCompletableFuture().get(); + Assert.assertTrue(optionalObjectMetadata.isPresent()); + final var objectMetadata = optionalObjectMetadata.get(); + Assert.assertEquals(objectMetadata.getContentLength(), 0L); + } + // TODO: There are couple of issues, firstly there are two `Content-Length` headers being added, one by `putBlob` // function and secondly by, most likely, by WireMock. Need to to figure out how to tell WireMock not to add `Content-Length` diff --git a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/AzuriteIntegrationSpec.scala b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/AzuriteIntegrationSpec.scala index f096630656..2ffff35c23 100644 --- a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/AzuriteIntegrationSpec.scala +++ b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/AzuriteIntegrationSpec.scala @@ -12,9 +12,6 @@ import akka.stream.Attributes import com.dimafeng.testcontainers.ForAllTestContainer import org.scalatest.Ignore -import scala.concurrent.duration._ -import scala.concurrent.Await - // TODO: investigate how Azurite works, it is not even working with pure Java API // `putBlob` operations fails with "Premature end of file." error with BadRequest. @Ignore @@ -27,12 +24,5 @@ class AzuriteIntegrationSpec extends StorageIntegrationSpec with ForAllTestConta protected lazy val blobSettings: StorageSettings = StorageExt(system).settings("azurite").withEndPointUrl(container.getBlobHostAddress) - override protected def beforeAll(): Unit = { - super.beforeAll() - - val eventualDone = createContainer(defaultContainerName) - Await.result(eventualDone, 10.seconds) - } - override protected def getDefaultAttributes: Attributes = StorageAttributes.settings(blobSettings) } diff --git a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageIntegrationSpec.scala b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageIntegrationSpec.scala index 1c23463937..b8e2243334 100644 --- a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageIntegrationSpec.scala +++ b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageIntegrationSpec.scala @@ -13,7 +13,14 @@ import akka.http.scaladsl.Http import akka.http.scaladsl.model.ContentTypes import akka.http.scaladsl.model.headers.ByteRange import akka.stream.Attributes -import akka.stream.alpakka.azure.storage.requests.{CreateContainer, DeleteBlob, GetBlob, GetProperties, PutBlockBlob} +import akka.stream.alpakka.azure.storage.requests.{ + CreateContainer, + DeleteBlob, + DeleteContainer, + GetBlob, + GetProperties, + PutBlockBlob +} import akka.stream.alpakka.testkit.scaladsl.LogCapturing import akka.stream.scaladsl.{Flow, Framing, Keep, Sink, Source} import akka.testkit.TestKit @@ -56,6 +63,19 @@ trait StorageIntegrationSpec protected def getDefaultAttributes: Attributes = StorageAttributes.settings(StorageSettings()) "BlobService" should { + "create container" in { + val maybeObjectMetadata = + BlobService + .createContainer(objectPath = defaultContainerName, requestBuilder = CreateContainer()) + .withAttributes(getDefaultAttributes) + .runWith(Sink.head) + .futureValue + + maybeObjectMetadata shouldBe defined + val objectMetadata = maybeObjectMetadata.get + objectMetadata.contentLength shouldBe 0L + } + "put blob" in { val maybeObjectMetadata = BlobService @@ -142,13 +162,19 @@ trait StorageIntegrationSpec maybeObjectMetadata shouldBe empty } - } - protected def createContainer(containerName: String): Future[Done] = { - BlobService - .createContainer(containerName, CreateContainer()) - .withAttributes(getDefaultAttributes) - .runWith(Sink.ignore) + "delete container" in { + val maybeObjectMetadata = + BlobService + .deleteContainer(objectPath = defaultContainerName, requestBuilder = DeleteContainer()) + .withAttributes(getDefaultAttributes) + .runWith(Sink.head) + .futureValue + + maybeObjectMetadata shouldBe defined + val objectMetadata = maybeObjectMetadata.get + objectMetadata.contentLength shouldBe 0L + } } protected def calculateDigest(text: String): String = { diff --git a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageWireMockBase.scala b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageWireMockBase.scala index 416f0226da..7959d8ddbc 100644 --- a/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageWireMockBase.scala +++ b/azure-storage/src/test/scala/akka/stream/alpakka/azure/storage/scaladsl/StorageWireMockBase.scala @@ -62,6 +62,16 @@ abstract class StorageWireMockBase(_system: ActorSystem, val _wireMockServer: Wi ) ) + protected def mockDeleteContainer(): StubMapping = + mock.register( + delete(urlEqualTo(s"/$AccountName/$containerName?restype=container")) + .willReturn( + aResponse() + .withStatus(202) + .withHeader(`Content-Length`.name, "0") + ) + ) + protected def mockPutBlockBlob(): StubMapping = mock.register( put(urlEqualTo(s"/$AccountName/$containerName/$blobName")) diff --git a/azure-storage/src/test/scala/docs/scaladsl/StorageSpec.scala b/azure-storage/src/test/scala/docs/scaladsl/StorageSpec.scala index d80888b839..a3ac4eb5cc 100644 --- a/azure-storage/src/test/scala/docs/scaladsl/StorageSpec.scala +++ b/azure-storage/src/test/scala/docs/scaladsl/StorageSpec.scala @@ -8,7 +8,6 @@ import akka.NotUsed import akka.http.scaladsl.model.{ContentTypes, StatusCodes} import akka.stream.alpakka.azure.storage.StorageException import akka.stream.alpakka.azure.storage.headers.ServerSideEncryption -import akka.stream.alpakka.azure.storage.requests._ import akka.stream.alpakka.azure.storage.scaladsl.StorageWireMockBase import akka.stream.alpakka.azure.storage.scaladsl.StorageWireMockBase.ETagRawValue import akka.stream.alpakka.testkit.scaladsl.LogCapturing @@ -44,6 +43,7 @@ class StorageSpec //#create-container import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.CreateContainer val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.createContainer(containerName, CreateContainer()) @@ -58,6 +58,26 @@ class StorageSpec objectMetadata.eTag shouldBe Some(ETagRawValue) } + "delete container" in { + mockDeleteContainer() + + //#delete-container + import akka.stream.alpakka.azure.storage.scaladsl.BlobService + import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.DeleteContainer + + val source: Source[Option[ObjectMetadata], NotUsed] = + BlobService.deleteContainer(containerName, DeleteContainer()) + + val eventualMaybeMetadata: Future[Option[ObjectMetadata]] = source.runWith(Sink.head) + //#delete-container + + val maybeObjectMetadata = eventualMaybeMetadata.futureValue + maybeObjectMetadata shouldBe defined + val objectMetadata = maybeObjectMetadata.get + objectMetadata.contentLength shouldBe 0L + } + // TODO: There are couple of issues, firstly there are two `Content-Length` headers being added, one by `putBlob` // function and secondly by, most likely, by WireMock. Need to to figure out how to tell WireMock not to add `Content-Length` // header, secondly once that resolve then we get `akka.http.scaladsl.model.EntityStreamException`. @@ -67,6 +87,7 @@ class StorageSpec //#put-block-blob import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.PutBlockBlob val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.putBlockBlob( @@ -94,6 +115,7 @@ class StorageSpec //#put-page-blob import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.PutPageBlock val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.putPageBlock( @@ -120,6 +142,7 @@ class StorageSpec //#put-append-blob import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.PutAppendBlock val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.putAppendBlock(objectPath = s"$containerName/$blobName", @@ -141,6 +164,7 @@ class StorageSpec //#get-blob import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", GetBlob()) @@ -156,6 +180,7 @@ class StorageSpec import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", GetBlob().withVersionId("versionId")) @@ -170,6 +195,7 @@ class StorageSpec import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", GetBlob().withLeaseId("leaseId")) @@ -184,6 +210,7 @@ class StorageSpec import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", @@ -199,6 +226,7 @@ class StorageSpec import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", GetBlob()) @@ -221,6 +249,7 @@ class StorageSpec //#get-blob-range import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetBlob val source: Source[ByteString, Future[ObjectMetadata]] = BlobService.getBlob(objectPath = s"$containerName/$blobName", requestBuilder = GetBlob().withRange(subRange)) @@ -237,6 +266,7 @@ class StorageSpec //#get-blob-properties import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetProperties val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.getProperties(objectPath = s"$containerName/$blobName", GetProperties()) @@ -258,6 +288,7 @@ class StorageSpec //#delete-blob import akka.stream.alpakka.azure.storage.scaladsl.BlobService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.DeleteBlob val source: Source[Option[ObjectMetadata], NotUsed] = BlobService.deleteBlob(objectPath = s"$containerName/$blobName", DeleteBlob()) @@ -281,6 +312,7 @@ class StorageSpec //#create-file import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.CreateFile val source: Source[Option[ObjectMetadata], NotUsed] = FileService.createFile(objectPath = s"$containerName/$blobName", @@ -305,6 +337,7 @@ class StorageSpec //#update-range import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.UpdateFileRange val source: Source[Option[ObjectMetadata], NotUsed] = FileService.updateRange( @@ -329,6 +362,7 @@ class StorageSpec //#get-file import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetFile val source: Source[ByteString, Future[ObjectMetadata]] = FileService.getFile(objectPath = s"$containerName/$blobName", GetFile()) @@ -345,6 +379,7 @@ class StorageSpec //#get-file-properties import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.GetProperties val source: Source[Option[ObjectMetadata], NotUsed] = FileService.getProperties(objectPath = s"$containerName/$blobName", GetProperties()) @@ -369,6 +404,7 @@ class StorageSpec //#clear-range import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.ClearFileRange val source: Source[Option[ObjectMetadata], NotUsed] = FileService.clearRange(objectPath = s"$containerName/$blobName", requestBuilder = ClearFileRange(subRange)) @@ -389,6 +425,7 @@ class StorageSpec //#delete-file import akka.stream.alpakka.azure.storage.scaladsl.FileService import akka.stream.alpakka.azure.storage.ObjectMetadata + import akka.stream.alpakka.azure.storage.requests.DeleteFile val source: Source[Option[ObjectMetadata], NotUsed] = FileService.deleteFile(objectPath = s"$containerName/$blobName", DeleteFile()) diff --git a/docs/src/main/paradox/azure-storage.md b/docs/src/main/paradox/azure-storage.md index 913405f026..365d631dd2 100644 --- a/docs/src/main/paradox/azure-storage.md +++ b/docs/src/main/paradox/azure-storage.md @@ -125,6 +125,17 @@ Scala Java : @@snip [snip](/azure-storage/src/test/java/docs/javadsl/StorageTest.java) { #create-container } +### Create Container + +The [`Delete Container`](https://learn.microsoft.com/en-us/rest/api/storageservices/delete-container) operation creates existing container under the specified account. + +Scala +: @@snip [snip](/azure-storage/src/test/scala/docs/scaladsl/StorageSpec.scala) { #delete-container } + +Java +: @@snip [snip](/azure-storage/src/test/java/docs/javadsl/StorageTest.java) { #delete-container } + + ### Put Block Blob The [`Put Block Blob`](https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob) operation creates a new block or updates the content of an existing block blob.