Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a different message when writing tags fails due to permissions issues, add test #2793

Merged
merged 7 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,7 @@ func (cca *CookedCopyCmdArgs) ReportProgressOrExit(lcm common.LifecycleMgr) (tot

if jobDone {
exitCode := cca.getSuccessExitCode()
if summary.TransfersFailed > 0 {
if summary.TransfersFailed > 0 || summary.JobStatus == common.EJobStatus.Cancelled() || summary.JobStatus == common.EJobStatus.Cancelling() {
exitCode = common.EExitCode.Error()
}

Expand Down Expand Up @@ -2120,7 +2120,7 @@ func init() {
cpCmd.PersistentFlags().BoolVar(&raw.s2sSourceChangeValidation, "s2s-detect-source-changed", false, "False by default. Detect if the source file/blob changes while it is being read. This parameter only applies to service to service copies, because the corresponding check is permanently enabled for uploads and downloads.")
cpCmd.PersistentFlags().StringVar(&raw.s2sInvalidMetadataHandleOption, "s2s-handle-invalid-metadata", common.DefaultInvalidMetadataHandleOption.String(), "Specifies how invalid metadata keys are handled. Available options: ExcludeIfInvalid, FailIfInvalid, RenameIfInvalid (default 'ExcludeIfInvalid').")
cpCmd.PersistentFlags().StringVar(&raw.listOfVersionIDs, "list-of-versions", "", "Specifies a path to a text file where each version id is listed on a separate line. Ensure that the source must point to a single blob and all the version ids specified in the file using this flag must belong to the source blob only. AzCopy will download the specified versions in the destination folder provided.")
cpCmd.PersistentFlags().StringVar(&raw.blobTags, "blob-tags", "", "Set tags on blobs to categorize data in your storage account. Multiple blob tags should be separated by ';', i.e. 'foo=bar;some=thing'.")
cpCmd.PersistentFlags().StringVar(&raw.blobTags, "blob-tags", "", "Set tags on blobs to categorize data in your storage account. Multiple blob tags should be separated by '&', i.e. 'foo=bar&some=thing'.")
cpCmd.PersistentFlags().BoolVar(&raw.s2sPreserveBlobTags, "s2s-preserve-blob-tags", false, "False by default. Preserve blob tags during service to service transfer from one blob storage to another.")
cpCmd.PersistentFlags().BoolVar(&raw.includeDirectoryStubs, "include-directory-stub", false, "False by default to ignore directory stubs. Directory stubs are blobs with metadata 'hdi_isfolder:true'. Setting value to true will preserve directory stubs during transfers. Including this flag with no value defaults to true (e.g, azcopy copy --include-directory-stub is the same as azcopy copy --include-directory-stub=true).")
cpCmd.PersistentFlags().BoolVar(&raw.disableAutoDecoding, "disable-auto-decoding", false, "False by default to enable automatic decoding of illegal chars on Windows. Can be set to true to disable automatic decoding.")
Expand Down
2 changes: 1 addition & 1 deletion cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ func (cca *cookedSyncCmdArgs) ReportProgressOrExit(lcm common.LifecycleMgr) (tot

if jobDone {
exitCode := common.EExitCode.Success()
if summary.TransfersFailed > 0 {
if summary.TransfersFailed > 0 || summary.JobStatus == common.EJobStatus.Cancelled() || summary.JobStatus == common.EJobStatus.Cancelling() {
exitCode = common.EExitCode.Error()
}

Expand Down
74 changes: 74 additions & 0 deletions e2etest/zt_newe2e_basic_functionality_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package e2etest

import (
blobsas "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"github.com/Azure/azure-storage-azcopy/v10/common"
"strconv"
"time"
Expand Down Expand Up @@ -410,3 +411,76 @@ func (s *BasicFunctionalitySuite) Scenario_Copy_EmptySASErrorCodes(svm *Scenario
// Validate that the stdout contains these error URLs
ValidateContainsError(svm, stdout, []string{"https://aka.ms/AzCopyError/NoAuthenticationInformation", "https://aka.ms/AzCopyError/ResourceNotFound"})
}

func (s *BasicFunctionalitySuite) Scenario_TagsPermission(svm *ScenarioVariationManager) {
objectType := ResolveVariation(svm, []common.EntityType{common.EEntityType.File(), common.EEntityType.Folder(), common.EEntityType.Symlink()})
srcLoc := ResolveVariation(svm, []common.Location{common.ELocation.Local(), common.ELocation.Blob()})

// Local resource manager doesn't have symlink abilities yet, and the same codepath is hit.
if objectType == common.EEntityType.Symlink() && srcLoc == common.ELocation.Local() {
svm.InvalidateScenario()
return
}

srcObj := CreateResource[ObjectResourceManager](svm, GetRootResource(svm, srcLoc), ResourceDefinitionObject{
Body: common.Iff(objectType == common.EEntityType.File(), NewZeroObjectContentContainer(1024*1024*5), nil),
ObjectProperties: ObjectProperties{
EntityType: objectType,
},
})
dstCt := CreateResource[ContainerResourceManager](svm, GetRootResource(svm, common.ELocation.Blob()), ResourceDefinitionContainer{})

svm.InsertVariationSeparator("Blob")

multiBlock := "single-block"
var blobType common.BlobType
if objectType == common.EEntityType.File() {
svm.InsertVariationSeparator("-")
blobType = ResolveVariation(svm, []common.BlobType{common.EBlobType.BlockBlob(), common.EBlobType.PageBlob(), common.EBlobType.AppendBlob()})

if blobType == common.EBlobType.BlockBlob() {
svm.InsertVariationSeparator("-")
multiBlock = ResolveVariation(svm, []string{"single-block", "multi-block"})
}
}

stdOut, _ := RunAzCopy(
svm,
AzCopyCommand{
Verb: AzCopyVerbCopy,
Targets: []ResourceManager{
srcObj,
AzCopyTarget{dstCt, EExplicitCredentialType.SASToken(), CreateAzCopyTargetOptions{
SASTokenOptions: GenericServiceSignatureValues{
ContainerName: dstCt.ContainerName(),
Permissions: PtrOf(blobsas.ContainerPermissions{
Read: true,
Add: true,
Create: true,
Write: true,
Tag: false,
}).String(),
},
}},
},
Flags: CopyFlags{
BlobTags: common.Metadata{
"foo": PtrOf("bar"),
"alpha": PtrOf("beta"),
},
CopySyncCommonFlags: CopySyncCommonFlags{
BlockSizeMB: common.Iff(objectType == common.EEntityType.File() && multiBlock != "single-block",
PtrOf(0.5),
nil),
Recursive: pointerTo(true),
IncludeDirectoryStubs: pointerTo(true),
},
BlobType: &blobType,
PreserveSymlinks: pointerTo(true),
},
ShouldFail: true,
},
)

ValidateErrorOutput(svm, stdOut, "Authorization failed during an attempt to set tags, please ensure you have the appropriate Tags permission")
}
7 changes: 6 additions & 1 deletion ste/mgr-JobPartTransferMgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,12 @@ func (jptm *jobPartTransferMgr) failActiveTransfer(typ transferErrorCode, descri
!jptm.jobPartMgr.(*jobPartMgr).jobMgr.IsDaemon() {
// quit right away, since without proper authentication no work can be done
// display a clear message
common.GetLifecycleMgr().Info(fmt.Sprintf("Authentication failed, it is either not correct, or expired, or does not have the correct permission %s", err.Error()))
if strings.Contains(descriptionOfWhereErrorOccurred, "tags") {
common.GetLifecycleMgr().Info(fmt.Sprintf("Authorization failed during an attempt to set tags, please ensure you have the appropriate Tags permission %s", err.Error()))
} else {
common.GetLifecycleMgr().Info(fmt.Sprintf("Authentication failed, it is either not correct, or expired, or does not have the correct permission %s", err.Error()))
}

// and use the normal cancelling mechanism so that we can exit in a clean and controlled way
jptm.jobPartMgr.(*jobPartMgr).jobMgr.CancelPauseJobOrder(common.EJobStatus.Cancelling())
// TODO: this results in the final job output line being: Final Job Status: Cancelled
Expand Down
4 changes: 2 additions & 2 deletions ste/sender-appendBlob.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,15 @@ func (s *appendBlobSenderBase) Prologue(ps common.PrologueState) (destinationMod
CPKScopeInfo: s.jptm.CpkScopeInfo(),
})
if err != nil {
s.jptm.FailActiveSend("Creating blob", err)
s.jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Creating blob (with tags)", "Creating blob"), err)
return
}
destinationModified = true

if setTags {
_, err = s.destAppendBlobClient.SetTags(s.jptm.Context(), s.blobTagsToApply, nil)
if err != nil {
s.jptm.Log(common.LogWarning, err.Error())
s.jptm.FailActiveSend("Set blob tags", err)
}
}
return
Expand Down
20 changes: 18 additions & 2 deletions ste/sender-blobFolders.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,17 +199,33 @@ func (b *blobFolderSender) EnsureFolderExists() error {
}

err = t.CreateFolder(b.DirUrlToString(), func() error {
blobTags := b.blobTagsToApply
setTags := separateSetTagsRequired(blobTags)
if setTags || len(blobTags) == 0 {
blobTags = nil
}

// It doesn't make sense to use a special access tier for a blob folder, the blob will be 0 bytes.
_, err := b.destinationClient.Upload(b.jptm.Context(), streaming.NopCloser(bytes.NewReader(nil)),
&blockblob.UploadOptions{
HTTPHeaders: &b.headersToApply,
Metadata: b.metadataToApply,
Tags: b.blobTagsToApply,
Tags: blobTags,
CPKInfo: b.jptm.CpkInfo(),
CPKScopeInfo: b.jptm.CpkScopeInfo(),
})
if err != nil {
b.jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Upload folder (with tags)", "Upload folder"), err)
}

return err
if setTags {
if _, err := b.destinationClient.SetTags(b.jptm.Context(), b.blobTagsToApply, nil); err != nil {
b.jptm.FailActiveSend("Set tags", err)
return nil
}
}

return nil
})

if err != nil {
Expand Down
21 changes: 19 additions & 2 deletions ste/sender-blobSymlinks.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,33 @@ func (s *blobSymlinkSender) SendSymlink(linkData string) error {
}
s.metadataToApply["is_symlink"] = to.Ptr("true")

blobTags := s.blobTagsToApply
setTags := separateSetTagsRequired(blobTags)
if setTags || len(blobTags) == 0 {
blobTags = nil
}

_, err = s.destinationClient.Upload(s.jptm.Context(), streaming.NopCloser(strings.NewReader(linkData)),
&blockblob.UploadOptions{
HTTPHeaders: &s.headersToApply,
Metadata: s.metadataToApply,
Tier: s.destBlobTier,
Tags: s.blobTagsToApply,
Tags: blobTags,
CPKInfo: s.jptm.CpkInfo(),
CPKScopeInfo: s.jptm.CpkScopeInfo(),
})
return err
if err != nil {
adreed-msft marked this conversation as resolved.
Show resolved Hide resolved
s.jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Upload symlink (with tags)", "Upload symlink"), err)
adreed-msft marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

if setTags {
if _, err := s.destinationClient.SetTags(s.jptm.Context(), s.blobTagsToApply, nil); err != nil {
s.jptm.FailActiveSend("Set tags", err)
return nil
}
}
return nil
}

// ===== Implement sender so that it can be returned in newBlobUploader. =====
Expand Down
4 changes: 2 additions & 2 deletions ste/sender-blockBlob.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,13 @@ func (s *blockBlobSenderBase) Epilogue() {
CPKScopeInfo: s.jptm.CpkScopeInfo(),
})
if err != nil {
jptm.FailActiveSend("Committing block list", err)
jptm.FailActiveSend(common.Iff(blobTags != nil, "Committing block list (with tags)", "Committing block list"), err)
return
}

if setTags {
if _, err := s.destBlockBlobClient.SetTags(jptm.Context(), s.blobTagsToApply, nil); err != nil {
s.jptm.Log(common.LogWarning, err.Error())
jptm.FailActiveSend("Setting tags", err)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions ste/sender-blockBlobFromLocal.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@ func (u *blockBlobUploader) generatePutWholeBlob(id common.ChunkID, reader commo

// if the put blob is a failure, update the transfer status to failed
if err != nil {
jptm.FailActiveUpload("Uploading blob", err)
jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Committing block list (with tags)", "Committing block list"), err)
return
}

atomic.AddInt32(&u.atomicChunksWritten, 1)

if setTags {
if _, err := u.destBlockBlobClient.SetTags(jptm.Context(), u.blobTagsToApply, nil); err != nil {
u.jptm.Log(common.LogWarning, err.Error())
jptm.FailActiveSend("Set blob tags", err)
}
}
})
Expand Down
4 changes: 2 additions & 2 deletions ste/sender-blockBlobFromURL.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ func (c *urlToBlockBlobCopier) generateStartPutBlobFromURL(id common.ChunkID, bl
})

if err != nil {
c.jptm.FailActiveSend("Put Blob from URL", err)
c.jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Committing block list (with tags)", "Committing block list"), err)
return
}

atomic.AddInt32(&c.atomicChunksWritten, 1)

if setTags {
if _, err := c.destBlockBlobClient.SetTags(c.jptm.Context(), c.blobTagsToApply, nil); err != nil {
c.jptm.Log(common.LogWarning, err.Error())
c.jptm.FailActiveSend("Set blob tags", err)
}
}
})
Expand Down
4 changes: 2 additions & 2 deletions ste/sender-pageBlob.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,15 +260,15 @@ func (s *pageBlobSenderBase) Prologue(ps common.PrologueState) (destinationModif
CPKScopeInfo: s.jptm.CpkScopeInfo(),
})
if err != nil {
s.jptm.FailActiveSend("Creating blob", err)
s.jptm.FailActiveSend(common.Iff(len(blobTags) > 0, "Creating blob (with tags)", "Creating blob"), err)
return
}

destinationModified = true

if setTags {
if _, err := s.destPageBlobClient.SetTags(s.jptm.Context(), s.blobTagsToApply, nil); err != nil {
s.jptm.Log(common.LogWarning, err.Error())
s.jptm.FailActiveSend("Set blob tags", err)
}
}

Expand Down
Loading