Skip to content

Commit

Permalink
Container soft delete (#19136)
Browse files Browse the repository at this point in the history
Adding code for container soft delete
  • Loading branch information
souravgupta-msft authored Sep 22, 2022
1 parent fedb429 commit 81279eb
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 0 deletions.
17 changes: 17 additions & 0 deletions sdk/storage/azblob/container/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,23 @@ func (c *Client) Delete(ctx context.Context, options *DeleteOptions) (DeleteResp
return resp, err
}

// Restore operation restore the contents and properties of a soft deleted container to a specified container.
// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/restore-container.
func (c *Client) Restore(ctx context.Context, deletedContainerVersion string, options *RestoreOptions) (RestoreResponse, error) {
urlParts, err := blob.ParseURL(c.URL())
if err != nil {
return RestoreResponse{}, err
}

opts := &generated.ContainerClientRestoreOptions{
DeletedContainerName: &urlParts.ContainerName,
DeletedContainerVersion: &deletedContainerVersion,
}
resp, err := c.generated().Restore(ctx, opts)

return resp, err
}

// GetProperties returns the container's properties.
// For more information, see https://docs.microsoft.com/rest/api/storageservices/get-container-metadata.
func (c *Client) GetProperties(ctx context.Context, o *GetPropertiesOptions) (GetPropertiesResponse, error) {
Expand Down
7 changes: 7 additions & 0 deletions sdk/storage/azblob/container/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ func (o *DeleteOptions) format() (*generated.ContainerClientDeleteOptions, *gene

// ---------------------------------------------------------------------------------------------------------------------

// RestoreOptions contains the optional parameters for the Client.Restore method.
type RestoreOptions struct {
// placeholder for future options
}

// ---------------------------------------------------------------------------------------------------------------------

// GetPropertiesOptions contains the optional parameters for the ContainerClient.GetProperties method.
type GetPropertiesOptions struct {
LeaseAccessConditions *LeaseAccessConditions
Expand Down
3 changes: 3 additions & 0 deletions sdk/storage/azblob/container/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ type CreateResponse = generated.ContainerClientCreateResponse
// DeleteResponse contains the response from method Client.Delete.
type DeleteResponse = generated.ContainerClientDeleteResponse

// RestoreResponse contains the response from method Client.Restore.
type RestoreResponse = generated.ContainerClientRestoreResponse

// GetPropertiesResponse contains the response from method Client.GetProperties.
type GetPropertiesResponse = generated.ContainerClientGetPropertiesResponse

Expand Down
8 changes: 8 additions & 0 deletions sdk/storage/azblob/service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ func (s *Client) DeleteContainer(ctx context.Context, containerName string, opti
return containerDeleteResp, err
}

// RestoreContainer restores soft-deleted container
// Operation will only be successful if used within the specified number of days set in the delete retention policy
func (s *Client) RestoreContainer(ctx context.Context, deletedContainerName string, deletedContainerVersion string, options *RestoreContainerOptions) (RestoreContainerResponse, error) {
containerClient := s.NewContainerClient(deletedContainerName)
containerRestoreResp, err := containerClient.Restore(ctx, deletedContainerVersion, options)
return containerRestoreResp, err
}

// GetAccountInfo provides account level information
func (s *Client) GetAccountInfo(ctx context.Context, o *GetAccountInfoOptions) (GetAccountInfoResponse, error) {
getAccountInfoOptions := o.format()
Expand Down
61 changes: 61 additions & 0 deletions sdk/storage/azblob/service/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,64 @@ func (s *ServiceUnrecordedTestsSuite) TestSASContainerClient2() {
//_, err = containerClient2.Create(ctx, nil)
//_require.Nil(err)
}

// make sure that container soft delete is enabled
// TODO: convert this test to recorded
func (s *ServiceUnrecordedTestsSuite) TestContainerRestore() {
_require := require.New(s.T())
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil)
_require.NoError(err)

testName := s.T().Name()
containerName := testcommon.GenerateContainerName(testName)

_, err = svcClient.CreateContainer(context.Background(), containerName, nil)
_require.Nil(err)

_, err = svcClient.DeleteContainer(context.Background(), containerName, nil)
_require.Nil(err)

prefix := testcommon.ContainerPrefix
listOptions := service.ListContainersOptions{Prefix: &prefix, Include: service.ListContainersInclude{Metadata: true, Deleted: true}}
pager := svcClient.NewListContainersPager(&listOptions)

contRestored := false
for pager.More() {
resp, err := pager.NextPage(context.Background())
_require.Nil(err)
for _, cont := range resp.ContainerItems {
_require.NotNil(cont.Name)

if *cont.Deleted && *cont.Name == containerName {
contRestored = true
_, err = svcClient.RestoreContainer(context.Background(), containerName, *cont.Version, nil)
_require.Nil(err)
break
}
}
if contRestored {
break
}
}

_require.Equal(contRestored, true)

_, err = svcClient.DeleteContainer(context.Background(), containerName, nil)
_require.Nil(err)
}

// TODO: convert this test to recorded
func (s *ServiceUnrecordedTestsSuite) TestContainerRestoreFailures() {
_require := require.New(s.T())
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil)
_require.NoError(err)

testName := s.T().Name()
containerName := testcommon.GenerateContainerName(testName)

_, err = svcClient.RestoreContainer(context.Background(), containerName, "", nil)
testcommon.ValidateBlobErrorCode(_require, err, bloberror.MissingRequiredHeader)

_, err = svcClient.RestoreContainer(context.Background(), "", "", &service.RestoreContainerOptions{})
testcommon.ValidateBlobErrorCode(_require, err, bloberror.MissingRequiredHeader)
}
34 changes: 34 additions & 0 deletions sdk/storage/azblob/service/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,40 @@ func Example_service_Client_DeleteContainer() {
handleError(err)
}

func Example_service_Client_RestoreContainer() {
accountName, ok := os.LookupEnv("AZURE_STORAGE_ACCOUNT_NAME")
if !ok {
panic("AZURE_STORAGE_ACCOUNT_NAME could not be found")
}
serviceURL := fmt.Sprintf("https://%s.blob.core.windows.net/", accountName)

cred, err := azidentity.NewDefaultAzureCredential(nil)
handleError(err)
serviceClient, err := service.NewClient(serviceURL, cred, nil)
handleError(err)

listOptions := service.ListContainersOptions{
Include: service.ListContainersInclude{
Metadata: true, // Include Metadata
Deleted: true, // Include deleted containers in the result as well
},
}
pager := serviceClient.NewListContainersPager(&listOptions)

for pager.More() {
resp, err := pager.NextPage(context.TODO())
if err != nil {
log.Fatal(err)
}
for _, cont := range resp.ContainerItems {
if *cont.Deleted {
_, err = serviceClient.RestoreContainer(context.TODO(), *cont.Name, *cont.Version, nil)
handleError(err)
}
}
}
}

func Example_service_Client_ListContainers() {
accountName, ok := os.LookupEnv("AZURE_STORAGE_ACCOUNT_NAME")
if !ok {
Expand Down
3 changes: 3 additions & 0 deletions sdk/storage/azblob/service/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type CreateContainerOptions = container.CreateOptions
// DeleteContainerOptions contains the optional parameters for the container.Client.Delete method.
type DeleteContainerOptions = container.DeleteOptions

// RestoreContainerOptions contains the optional parameters for the container.Client.Restore method.
type RestoreContainerOptions = container.RestoreOptions

// CorsRule - CORS is an HTTP feature that enables a web application running under one domain to access resources in another
// domain. Web browsers implement a security restriction known as same-origin policy that
// prevents a web page from calling APIs in a different domain; CORS provides a secure way to allow one domain (the origin
Expand Down
3 changes: 3 additions & 0 deletions sdk/storage/azblob/service/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ type CreateContainerResponse = generated.ContainerClientCreateResponse
// DeleteContainerResponse contains the response from method container.Client.Delete
type DeleteContainerResponse = generated.ContainerClientDeleteResponse

// RestoreContainerResponse contains the response from method container.Client.Restore
type RestoreContainerResponse = generated.ContainerClientRestoreResponse

// GetAccountInfoResponse contains the response from method Client.GetAccountInfo.
type GetAccountInfoResponse = generated.ServiceClientGetAccountInfoResponse

Expand Down

0 comments on commit 81279eb

Please sign in to comment.