Repository: kopia/kopia
Pull request #3405 (2023-10-25 22:27:25)

feat(providers): Add `ExtendBlobRetention` support for Azure
Adds `ExtendBlobRetention` code for Azure and fixes a couple typos I saw. 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`174f614`)](https://app.codecov.io/gh/kopia/kopia/commit/174f6141e133f487e54ba4cdede830636368aec2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) 75.80% compared to head [(`1a6f935`)](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) 75.81%.
> Report is 10 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #3405   +/-   ##
=======================================
  Coverage   75.80%   75.81%           
=======================================
  Files         465      465           
  Lines       37165    37166    +1     
=======================================
+ Hits        28174    28178    +4     
+ Misses       7059     7058    -1     
+ Partials     1932     1930    -2     
```


| [Files](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia) | Coverage Δ | |
|---|---|---|
| [repo/blob/storage.go](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9ibG9iL3N0b3JhZ2UuZ28=) | `93.87% <ø> (ø)` | |
| [repo/maintenance/content\_rewrite.go](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia#diff-cmVwby9tYWludGVuYW5jZS9jb250ZW50X3Jld3JpdGUuZ28=) | `79.48% <ø> (ø)` | |

... and [7 files with indirect coverage changes](https://app.codecov.io/gh/kopia/kopia/pull/3405/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/kopia/kopia/pull/3405?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kopia).

julio-lopez: @KastenMike also, thanks again for breaking things into smaller pieces to facilitate reviewing.

Patch:
diff --git a/repo/blob/azure/azure_storage.go b/repo/blob/azure/azure_storage.go
index 374fd2b9..d9cd13cf 100644
--- a/repo/blob/azure/azure_storage.go
+++ b/repo/blob/azure/azure_storage.go
@@ -165,6 +165,24 @@ func (az *azStorage) DeleteBlob(ctx context.Context, b blob.ID) error {
 	return err
 }
 
+// ExtendBlobRetention extends a blob retention period.
+func (az *azStorage) ExtendBlobRetention(ctx context.Context, b blob.ID, opts blob.ExtendOptions) error {
+	retainUntilDate := clock.Now().Add(opts.RetentionPeriod).UTC()
+	mode := azblobblob.ImmutabilityPolicySetting(blob.Locked) // overwrite the S3 values
+
+	_, err := az.service.ServiceClient().
+		NewContainerClient(az.Container).
+		NewBlobClient(az.getObjectNameString(b)).
+		SetImmutabilityPolicy(ctx, retainUntilDate, &azblobblob.SetImmutabilityPolicyOptions{
+			Mode: &mode,
+		})
+	if err != nil {
+		return errors.Wrap(err, "unable to extend retention period")
+	}
+
+	return nil
+}
+
 func (az *azStorage) getObjectNameString(b blob.ID) string {
 	return az.Prefix + string(b)
 }
diff --git a/repo/blob/storage.go b/repo/blob/storage.go
index 68483ca5..3aa030ef 100644
--- a/repo/blob/storage.go
+++ b/repo/blob/storage.go
@@ -7,6 +7,7 @@
 	"sync"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
 	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 
@@ -107,6 +108,9 @@ type Reader interface {
 
 	// Compliance - compliance mode.
 	Compliance RetentionMode = "COMPLIANCE"
+
+	// Locked - Locked policy mode for Azure.
+	Locked RetentionMode = RetentionMode(blob.ImmutabilityPolicyModeLocked)
 )
 
 func (r RetentionMode) String() string {
@@ -142,7 +146,7 @@ type ExtendOptions struct {
 // common functions that are mostly provider independent and have a sensible
 // default.
 //
-// Storage providers should imbed this struct and override functions that they
+// Storage providers should embed this struct and override functions that they
 // have different return values for.
 type DefaultProviderImplementation struct{}
 
diff --git a/repo/maintenance/content_rewrite.go b/repo/maintenance/content_rewrite.go
index 7f4397b1..77f17737 100644
--- a/repo/maintenance/content_rewrite.go
+++ b/repo/maintenance/content_rewrite.go
@@ -36,7 +36,7 @@ type contentInfoOrError struct {
 }
 
 // RewriteContents rewrites contents according to provided criteria and creates new
-// blobs and index entries to point at the.
+// blobs and index entries to point at them.
 func RewriteContents(ctx context.Context, rep repo.DirectRepositoryWriter, opt *RewriteContentsOptions, safety SafetyParameters) error {
 	if opt == nil {
 		return errors.Errorf("missing options")


Test patch:
diff --git a/repo/blob/s3/s3_storage_test.go b/repo/blob/s3/s3_storage_test.go
index 1636e2ac..ebefd771 100644
--- a/repo/blob/s3/s3_storage_test.go
+++ b/repo/blob/s3/s3_storage_test.go
@@ -119,7 +119,7 @@ func getProviderOptions(tb testing.TB, envName string) *Options {
 	}
 
 	if o.Prefix != "" {
-		tb.Fatalf("options providd in '%v' must not specify a prefix", envName)
+		tb.Fatalf("options provided in '%v' must not specify a prefix", envName)
 	}
 
 	return &o