Skip to content

Commit c2a2114

Browse files
Version Level WORM Support (Azure#19361)
* added vlw * added test * added more tests * fix * added methods to page and append blobs * added resource * added time as mandatory * added more tests * more tests * sort * fixed ci * fixed ci * fixed ci * test res * merged main * merged main * fixed feature support * more arm debugging * more arm debugging * more arm debugging * changelog * made expiry required
1 parent 72b362a commit c2a2114

27 files changed

+2990
-22
lines changed

sdk/storage/azblob/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Added BlobDeleteType to DeleteOptions to allow access to 'Permanent' DeleteType.
88
* Added [Set Blob Expiry API](https://learn.microsoft.com/rest/api/storageservices/set-blob-expiry).
99
* Added method `ServiceClient()` to the `azblob.Client` type, allowing access to the underlying service client.
10+
* Added support for object level immutability policy with versioning (Version Level WORM).
1011

1112
### Breaking Changes
1213

sdk/storage/azblob/appendblob/client.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"context"
1111
"io"
1212
"os"
13+
"time"
1314

1415
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1516
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
@@ -190,6 +191,24 @@ func (ab *Client) Undelete(ctx context.Context, o *blob.UndeleteOptions) (blob.U
190191
return ab.BlobClient().Undelete(ctx, o)
191192
}
192193

194+
// SetImmutabilityPolicy operation enables users to set the immutability policy on a blob.
195+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
196+
func (ab *Client) SetImmutabilityPolicy(ctx context.Context, expiryTime time.Time, options *blob.SetImmutabilityPolicyOptions) (blob.SetImmutabilityPolicyResponse, error) {
197+
return ab.BlobClient().SetImmutabilityPolicy(ctx, expiryTime, options)
198+
}
199+
200+
// DeleteImmutabilityPolicy operation enables users to delete the immutability policy on a blob.
201+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
202+
func (ab *Client) DeleteImmutabilityPolicy(ctx context.Context, options *blob.DeleteImmutabilityPolicyOptions) (blob.DeleteImmutabilityPolicyResponse, error) {
203+
return ab.BlobClient().DeleteImmutabilityPolicy(ctx, options)
204+
}
205+
206+
// SetLegalHold operation enables users to set legal hold on a blob.
207+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
208+
func (ab *Client) SetLegalHold(ctx context.Context, legalHold bool, options *blob.SetLegalHoldOptions) (blob.SetLegalHoldResponse, error) {
209+
return ab.BlobClient().SetLegalHold(ctx, legalHold, options)
210+
}
211+
193212
// SetTier operation sets the tier on a blob. The operation is allowed on a page
194213
// blob in a premium storage account and on a block blob in a blob storage account (locally
195214
// redundant storage only). A premium page blob's tier determines the allowed size, IOPS, and

sdk/storage/azblob/appendblob/client_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,104 @@ func (s *AppendBlobRecordedTestsSuite) TestBlobCreateAppendIfMatchTrue() {
641641
validateAppendBlobPut(_require, abClient)
642642
}
643643

644+
func (s *AppendBlobRecordedTestsSuite) TestAppendSetImmutabilityPolicy() {
645+
_require := require.New(s.T())
646+
testName := s.T().Name()
647+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
648+
_require.NoError(err)
649+
650+
containerName := testcommon.GenerateContainerName(testName)
651+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
652+
abName := testcommon.GenerateBlobName(testName)
653+
abClient := createNewAppendBlob(context.Background(), _require, abName, containerClient)
654+
655+
currentTime, err := time.Parse(time.UnixDate, "Fri Jun 11 20:00:00 GMT 2049")
656+
_require.Nil(err)
657+
policy := blob.ImmutabilityPolicySetting(blob.ImmutabilityPolicySettingUnlocked)
658+
_require.Nil(err)
659+
660+
setImmutabilityPolicyOptions := &blob.SetImmutabilityPolicyOptions{
661+
Mode: &policy,
662+
ModifiedAccessConditions: nil,
663+
}
664+
_, err = abClient.SetImmutabilityPolicy(context.Background(), currentTime, setImmutabilityPolicyOptions)
665+
_require.Nil(err)
666+
667+
_, err = abClient.SetLegalHold(context.Background(), false, nil)
668+
_require.Nil(err)
669+
670+
_, err = abClient.Delete(context.Background(), nil)
671+
_require.NotNil(err)
672+
673+
_, err = abClient.DeleteImmutabilityPolicy(context.Background(), nil)
674+
_require.Nil(err)
675+
676+
_, err = abClient.Delete(context.Background(), nil)
677+
_require.Nil(err)
678+
}
679+
680+
func (s *AppendBlobRecordedTestsSuite) TestAppendDeleteImmutabilityPolicy() {
681+
_require := require.New(s.T())
682+
testName := s.T().Name()
683+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
684+
_require.NoError(err)
685+
686+
containerName := testcommon.GenerateContainerName(testName)
687+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
688+
689+
abName := testcommon.GenerateBlobName(testName)
690+
abClient := createNewAppendBlob(context.Background(), _require, abName, containerClient)
691+
692+
currentTime, err := time.Parse(time.UnixDate, "Fri Jun 11 20:00:00 GMT 2049")
693+
_require.Nil(err)
694+
695+
policy := blob.ImmutabilityPolicySetting(blob.ImmutabilityPolicySettingUnlocked)
696+
_require.Nil(err)
697+
698+
setImmutabilityPolicyOptions := &blob.SetImmutabilityPolicyOptions{
699+
Mode: &policy,
700+
ModifiedAccessConditions: nil,
701+
}
702+
_, err = abClient.SetImmutabilityPolicy(context.Background(), currentTime, setImmutabilityPolicyOptions)
703+
_require.Nil(err)
704+
705+
_, err = abClient.DeleteImmutabilityPolicy(context.Background(), nil)
706+
_require.Nil(err)
707+
708+
_, err = abClient.Delete(context.Background(), nil)
709+
_require.Nil(err)
710+
}
711+
712+
func (s *AppendBlobRecordedTestsSuite) TestAppendSetLegalHold() {
713+
_require := require.New(s.T())
714+
testName := s.T().Name()
715+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
716+
_require.NoError(err)
717+
718+
containerName := testcommon.GenerateContainerName(testName)
719+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
720+
721+
abName := testcommon.GenerateBlobName(testName)
722+
abClient := createNewAppendBlob(context.Background(), _require, abName, containerClient)
723+
724+
_, err = abClient.GetProperties(context.Background(), nil)
725+
_require.Nil(err)
726+
727+
_, err = abClient.SetLegalHold(context.Background(), true, nil)
728+
_require.Nil(err)
729+
730+
// should fail since time has not passed yet
731+
_, err = abClient.Delete(context.Background(), nil)
732+
_require.NotNil(err)
733+
734+
_, err = abClient.SetLegalHold(context.Background(), false, nil)
735+
_require.Nil(err)
736+
737+
_, err = abClient.Delete(context.Background(), nil)
738+
_require.Nil(err)
739+
740+
}
741+
644742
func (s *AppendBlobRecordedTestsSuite) TestBlobCreateAppendIfMatchFalse() {
645743
_require := require.New(s.T())
646744
testName := s.T().Name()

sdk/storage/azblob/blob/client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,31 @@ func (b *Client) GetTags(ctx context.Context, options *GetTagsOptions) (GetTagsR
242242

243243
}
244244

245+
// SetImmutabilityPolicy operation enables users to set the immutability policy on a blob. Mode defaults to "Unlocked".
246+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
247+
func (b *Client) SetImmutabilityPolicy(ctx context.Context, expiryTime time.Time, options *SetImmutabilityPolicyOptions) (SetImmutabilityPolicyResponse, error) {
248+
blobSetImmutabilityPolicyOptions, modifiedAccessConditions := options.format()
249+
blobSetImmutabilityPolicyOptions.ImmutabilityPolicyExpiry = &expiryTime
250+
resp, err := b.generated().SetImmutabilityPolicy(ctx, blobSetImmutabilityPolicyOptions, modifiedAccessConditions)
251+
return resp, err
252+
}
253+
254+
// DeleteImmutabilityPolicy operation enables users to delete the immutability policy on a blob.
255+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
256+
func (b *Client) DeleteImmutabilityPolicy(ctx context.Context, options *DeleteImmutabilityPolicyOptions) (DeleteImmutabilityPolicyResponse, error) {
257+
deleteImmutabilityOptions := options.format()
258+
resp, err := b.generated().DeleteImmutabilityPolicy(ctx, deleteImmutabilityOptions)
259+
return resp, err
260+
}
261+
262+
// SetLegalHold operation enables users to set legal hold on a blob.
263+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
264+
func (b *Client) SetLegalHold(ctx context.Context, legalHold bool, options *SetLegalHoldOptions) (SetLegalHoldResponse, error) {
265+
setLegalHoldOptions := options.format()
266+
resp, err := b.generated().SetLegalHold(ctx, legalHold, setLegalHoldOptions)
267+
return resp, err
268+
}
269+
245270
// CopyFromURL synchronously copies the data at the source URL to a block blob, with sizes up to 256 MB.
246271
// For more information, see https://docs.microsoft.com/en-us/rest/api/storageservices/copy-blob-from-url.
247272
func (b *Client) CopyFromURL(ctx context.Context, copySource string, options *CopyFromURLOptions) (CopyFromURLResponse, error) {

sdk/storage/azblob/blob/client_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3432,3 +3432,102 @@ func (s *BlobRecordedTestsSuite) TestBlobSetExpiry() {
34323432
_, err = bbClient.GetProperties(context.Background(), nil)
34333433
testcommon.ValidateBlobErrorCode(_require, err, bloberror.BlobNotFound)
34343434
}
3435+
3436+
func (s *BlobRecordedTestsSuite) TestSetImmutabilityPolicy() {
3437+
_require := require.New(s.T())
3438+
testName := s.T().Name()
3439+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
3440+
_require.NoError(err)
3441+
3442+
containerName := testcommon.GenerateContainerName(testName)
3443+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
3444+
3445+
blockBlobName := testcommon.GenerateBlobName(testName)
3446+
bbClient := testcommon.CreateNewBlockBlob(context.Background(), _require, blockBlobName, containerClient)
3447+
3448+
currentTime, err := time.Parse(time.UnixDate, "Fri Jun 11 20:00:00 GMT 2049")
3449+
_require.Nil(err)
3450+
policy := blob.ImmutabilityPolicySetting(blob.ImmutabilityPolicySettingUnlocked)
3451+
_require.Nil(err)
3452+
3453+
setImmutabilityPolicyOptions := &blob.SetImmutabilityPolicyOptions{
3454+
Mode: &policy,
3455+
ModifiedAccessConditions: nil,
3456+
}
3457+
_, err = bbClient.SetImmutabilityPolicy(context.Background(), currentTime, setImmutabilityPolicyOptions)
3458+
_require.Nil(err)
3459+
3460+
_, err = bbClient.SetLegalHold(context.Background(), false, nil)
3461+
_require.Nil(err)
3462+
3463+
_, err = bbClient.Delete(context.Background(), nil)
3464+
_require.NotNil(err)
3465+
3466+
_, err = bbClient.DeleteImmutabilityPolicy(context.Background(), nil)
3467+
_require.Nil(err)
3468+
3469+
_, err = bbClient.Delete(context.Background(), nil)
3470+
_require.Nil(err)
3471+
}
3472+
3473+
func (s *BlobRecordedTestsSuite) TestDeleteImmutabilityPolicy() {
3474+
_require := require.New(s.T())
3475+
testName := s.T().Name()
3476+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
3477+
_require.NoError(err)
3478+
3479+
containerName := testcommon.GenerateContainerName(testName)
3480+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
3481+
3482+
blockBlobName := testcommon.GenerateBlobName(testName)
3483+
bbClient := testcommon.CreateNewBlockBlob(context.Background(), _require, blockBlobName, containerClient)
3484+
3485+
currentTime, err := time.Parse(time.UnixDate, "Fri Jun 11 20:00:00 GMT 2049")
3486+
_require.Nil(err)
3487+
3488+
policy := blob.ImmutabilityPolicySetting(blob.ImmutabilityPolicySettingUnlocked)
3489+
_require.Nil(err)
3490+
3491+
setImmutabilityPolicyOptions := &blob.SetImmutabilityPolicyOptions{
3492+
Mode: &policy,
3493+
ModifiedAccessConditions: nil,
3494+
}
3495+
_, err = bbClient.SetImmutabilityPolicy(context.Background(), currentTime, setImmutabilityPolicyOptions)
3496+
_require.Nil(err)
3497+
3498+
_, err = bbClient.DeleteImmutabilityPolicy(context.Background(), nil)
3499+
_require.Nil(err)
3500+
3501+
_, err = bbClient.Delete(context.Background(), nil)
3502+
_require.Nil(err)
3503+
}
3504+
3505+
func (s *BlobRecordedTestsSuite) TestSetLegalHold() {
3506+
_require := require.New(s.T())
3507+
testName := s.T().Name()
3508+
svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountImmutable, nil)
3509+
_require.NoError(err)
3510+
3511+
containerName := testcommon.GenerateContainerName(testName)
3512+
containerClient := testcommon.CreateNewContainer(context.Background(), _require, containerName, svcClient)
3513+
3514+
blockBlobName := testcommon.GenerateBlobName(testName)
3515+
bbClient := testcommon.CreateNewBlockBlob(context.Background(), _require, blockBlobName, containerClient)
3516+
3517+
_, err = bbClient.GetProperties(context.Background(), nil)
3518+
_require.Nil(err)
3519+
3520+
_, err = bbClient.SetLegalHold(context.Background(), true, nil)
3521+
_require.Nil(err)
3522+
3523+
// should fail since time has not passed yet
3524+
_, err = bbClient.Delete(context.Background(), nil)
3525+
_require.NotNil(err)
3526+
3527+
_, err = bbClient.SetLegalHold(context.Background(), false, nil)
3528+
_require.Nil(err)
3529+
3530+
_, err = bbClient.Delete(context.Background(), nil)
3531+
_require.Nil(err)
3532+
3533+
}

sdk/storage/azblob/blob/models.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,54 @@ func (o *GetTagsOptions) format() (*generated.BlobClientGetTagsOptions, *generat
480480

481481
// ---------------------------------------------------------------------------------------------------------------------
482482

483+
// SetImmutabilityPolicyOptions contains the parameter for Client.SetImmutabilityPolicy
484+
type SetImmutabilityPolicyOptions struct {
485+
// Specifies the immutability policy mode to set on the blob. Possible values to set include: "Locked", "Unlocked".
486+
// "Mutable" can only be returned by service, don't set to "Mutable". If mode is not set - it will default to Unlocked.
487+
Mode *ImmutabilityPolicySetting
488+
ModifiedAccessConditions *ModifiedAccessConditions
489+
}
490+
491+
func (o *SetImmutabilityPolicyOptions) format() (*generated.BlobClientSetImmutabilityPolicyOptions, *ModifiedAccessConditions) {
492+
if o == nil {
493+
return nil, nil
494+
}
495+
ac := &exported.BlobAccessConditions{
496+
ModifiedAccessConditions: o.ModifiedAccessConditions,
497+
}
498+
_, modifiedAccessConditions := exported.FormatBlobAccessConditions(ac)
499+
500+
options := &generated.BlobClientSetImmutabilityPolicyOptions{
501+
ImmutabilityPolicyMode: o.Mode,
502+
}
503+
504+
return options, modifiedAccessConditions
505+
}
506+
507+
// ---------------------------------------------------------------------------------------------------------------------
508+
509+
// DeleteImmutabilityPolicyOptions contains the optional parameters for the Client.DeleteImmutabilityPolicy method.
510+
type DeleteImmutabilityPolicyOptions struct {
511+
// placeholder for future options
512+
}
513+
514+
func (o *DeleteImmutabilityPolicyOptions) format() *generated.BlobClientDeleteImmutabilityPolicyOptions {
515+
return nil
516+
}
517+
518+
// ---------------------------------------------------------------------------------------------------------------------
519+
520+
// SetLegalHoldOptions contains the optional parameters for the Client.SetLegalHold method.
521+
type SetLegalHoldOptions struct {
522+
// placeholder for future options
523+
}
524+
525+
func (o *SetLegalHoldOptions) format() *generated.BlobClientSetLegalHoldOptions {
526+
return nil
527+
}
528+
529+
// ---------------------------------------------------------------------------------------------------------------------
530+
483531
// CopyFromURLOptions contains the optional parameters for the Client.CopyFromURL method.
484532
type CopyFromURLOptions struct {
485533
// Optional. Used to set blob tags in various blob operations.

sdk/storage/azblob/blob/responses.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,15 @@ type SetTagsResponse = generated.BlobClientSetTagsResponse
8888
// GetTagsResponse contains the response from method BlobClient.GetTags.
8989
type GetTagsResponse = generated.BlobClientGetTagsResponse
9090

91+
// SetImmutabilityPolicyResponse contains the response from method BlobClient.SetImmutabilityPolicy.
92+
type SetImmutabilityPolicyResponse = generated.BlobClientSetImmutabilityPolicyResponse
93+
94+
// DeleteImmutabilityPolicyResponse contains the response from method BlobClient.DeleteImmutabilityPolicyResponse.
95+
type DeleteImmutabilityPolicyResponse = generated.BlobClientDeleteImmutabilityPolicyResponse
96+
97+
// SetLegalHoldResponse contains the response from method BlobClient.SetLegalHold.
98+
type SetLegalHoldResponse = generated.BlobClientSetLegalHoldResponse
99+
91100
// CopyFromURLResponse contains the response from method BlobClient.CopyFromURL.
92101
type CopyFromURLResponse = generated.BlobClientCopyFromURLResponse
93102

sdk/storage/azblob/blockblob/client.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"io"
1515
"os"
1616
"sync"
17+
"time"
1718

1819
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1920
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
@@ -218,6 +219,9 @@ func (bb *Client) CommitBlockList(ctx context.Context, base64BlockIDs []string,
218219
Timeout: options.Timeout,
219220
TransactionalContentCRC64: options.TransactionalContentCRC64,
220221
TransactionalContentMD5: options.TransactionalContentMD5,
222+
LegalHold: options.LegalHold,
223+
ImmutabilityPolicyMode: options.ImmutabilityPolicyMode,
224+
ImmutabilityPolicyExpiry: options.ImmutabilityPolicyExpiryTime,
221225
}
222226

223227
headers = options.HTTPHeaders
@@ -255,6 +259,24 @@ func (bb *Client) Undelete(ctx context.Context, o *blob.UndeleteOptions) (blob.U
255259
return bb.BlobClient().Undelete(ctx, o)
256260
}
257261

262+
// SetImmutabilityPolicy operation enables users to set the immutability policy on a blob.
263+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
264+
func (bb *Client) SetImmutabilityPolicy(ctx context.Context, expiryTime time.Time, options *blob.SetImmutabilityPolicyOptions) (blob.SetImmutabilityPolicyResponse, error) {
265+
return bb.BlobClient().SetImmutabilityPolicy(ctx, expiryTime, options)
266+
}
267+
268+
// DeleteImmutabilityPolicy operation enables users to delete the immutability policy on a blob.
269+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
270+
func (bb *Client) DeleteImmutabilityPolicy(ctx context.Context, options *blob.DeleteImmutabilityPolicyOptions) (blob.DeleteImmutabilityPolicyResponse, error) {
271+
return bb.BlobClient().DeleteImmutabilityPolicy(ctx, options)
272+
}
273+
274+
// SetLegalHold operation enables users to set legal hold on a blob.
275+
// https://learn.microsoft.com/en-us/azure/storage/blobs/immutable-storage-overview
276+
func (bb *Client) SetLegalHold(ctx context.Context, legalHold bool, options *blob.SetLegalHoldOptions) (blob.SetLegalHoldResponse, error) {
277+
return bb.BlobClient().SetLegalHold(ctx, legalHold, options)
278+
}
279+
258280
// SetTier operation sets the tier on a blob. The operation is allowed on a page
259281
// blob in a premium storage account and on a block blob in a blob storage account (locally
260282
// redundant storage only). A premium page blob's tier determines the allowed size, IOPS, and

0 commit comments

Comments
 (0)