Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6d4f600
Inject UOTM into methods
gapra-msft Aug 27, 2025
0d1228d
Refactored initial code of resume
gapra-msft Aug 27, 2025
5ba554e
root changes
gapra-msft Aug 27, 2025
3049e9e
Fix UT
gapra-msft Aug 29, 2025
52bea7b
Merge branch 'gapra/injectUOTM' into gapra/resumeInit2
gapra-msft Sep 2, 2025
c0c2822
LCM refactor
gapra-msft Sep 2, 2025
45e9681
add method name
gapra-msft Sep 2, 2025
87a9b52
Fix e2e test issue
gapra-msft Sep 2, 2025
bb095b0
remove error handling - sometimes it can be not present
gapra-msft Sep 3, 2025
c8d149e
change behavior of cpkinfo
gapra-msft Sep 3, 2025
c1371d2
Added supoprt for jobs resume progress tracking in library
gapra-msft Sep 3, 2025
bc96906
Moved traverser code to a new package
gapra-msft Sep 3, 2025
799ef32
Export some types
gapra-msft Sep 3, 2025
ce78780
move copy util test
gapra-msft Sep 3, 2025
9f9b142
Remove azcopy as a dependency on traverser
gapra-msft Sep 3, 2025
cdb0354
try and fix test
gapra-msft Sep 3, 2025
e51f126
make account traversers use interface
gapra-msft Sep 3, 2025
614ccc7
undo not needed change
gapra-msft Sep 3, 2025
b3c108a
Merge branch 'gapra/moveTraverser' into gapra/azcopyDependencies
gapra-msft Sep 5, 2025
d9e535b
removed cred types from cooked sync args
gapra-msft Sep 5, 2025
4dea252
fix UT
gapra-msft Sep 5, 2025
38f2af2
Merge branch 'gapra/azcopyDependencies' into gapra/syncCleanup
gapra-msft Sep 5, 2025
f1e9a26
fix incorrect cred info check for destReauthTok
gapra-msft Sep 8, 2025
c41eace
remove redundant code
gapra-msft Sep 8, 2025
1347da7
Moved client creation out of traverser init
gapra-msft Sep 8, 2025
ab61190
Moved all filter logic
gapra-msft Sep 9, 2025
e00fd6c
some more cleanup
gapra-msft Sep 10, 2025
628e056
Fix casing of method for non windows
gapra-msft Sep 10, 2025
1fc66f8
Merge branch 'gapra/moveTraverser' into gapra/azcopyDependencies
gapra-msft Sep 10, 2025
632fb35
Merge branch 'gapra/azcopyDependencies' into gapra/syncCleanup
gapra-msft Sep 10, 2025
3b01f17
Clean up client creation logic
gapra-msft Sep 11, 2025
f6713dc
move client creation to arg processing
gapra-msft Sep 11, 2025
887a26c
dryrun processing is separated from main code
gapra-msft Sep 11, 2025
7118d16
refactor dryrun processing
gapra-msft Sep 11, 2025
69cfa68
skip test
gapra-msft Sep 11, 2025
ce0d360
fix bug with dryrun method
gapra-msft Sep 11, 2025
53c1f4a
Merge branch 'gapra/libraryPhase2' into gapra/syncCleanup
gapra-msft Sep 29, 2025
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
144 changes: 144 additions & 0 deletions azcopy/remoteClientUtils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package azcopy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need the microsoft licensing header?


import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/traverser"
)

func GetSourceServiceClient(ctx context.Context,
source common.ResourceString,
loc common.Location,
trailingDot common.TrailingDotOption,
cpk common.CpkOptions,
uotm *common.UserOAuthTokenManager) (*common.ServiceClient, common.CredentialType, error) {
srcCredType, _, err := GetCredentialTypeForLocation(ctx,
loc,
source,
true,
uotm,
cpk)
if err != nil {
return nil, srcCredType, err
}
var tc azcore.TokenCredential
if srcCredType.IsAzureOAuth() {
// Get token from env var or cache.
tokenInfo, err := uotm.GetTokenInfo(ctx)
if err != nil {
return nil, srcCredType, err
}

tc, err = tokenInfo.GetTokenCredential()
if err != nil {
return nil, srcCredType, err
}
}

var srcReauthTok *common.ScopedAuthenticator
if at, ok := tc.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
srcReauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, srcReauthTok)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a condition in the client creation for if we're not using OAuth tokens?


// Create Source Client.
var azureFileSpecificOptions any
if loc == common.ELocation.File() || loc == common.ELocation.FileNFS() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loc.IsFile()

Suggested change
if loc == common.ELocation.File() || loc == common.ELocation.FileNFS() {
if loc == common.ELocation.File() || loc == common.ELocation.FileNFS() {

azureFileSpecificOptions = &common.FileClientOptions{
AllowTrailingDot: trailingDot == common.ETrailingDotOption.Enable(),
}
}

srcServiceClient, err := common.GetServiceClientForLocation(
loc,
source,
srcCredType,
tc,
&options,
azureFileSpecificOptions,
)
if err != nil {
return nil, srcCredType, err
}
return srcServiceClient, srcCredType, nil
}

func GetDestinationServiceClient(ctx context.Context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

  • Can this be consolidated into one function to reduce code dupe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating an item for this. There are some updates I need to make with the method args so I'll clean it up at once after the core of the migration is done

destination common.ResourceString,
fromTo common.FromTo,
srcCredType common.CredentialType,
trailingDot common.TrailingDotOption,
cpk common.CpkOptions,
uotm *common.UserOAuthTokenManager) (*common.ServiceClient, common.CredentialType, error) {
dstCredType, _, err := GetCredentialTypeForLocation(ctx,
fromTo.To(),
destination,
false,
uotm,
cpk)
if err != nil {
return nil, dstCredType, err
}
var tc azcore.TokenCredential
if dstCredType.IsAzureOAuth() {
// Get token from env var or cache.
tokenInfo, err := uotm.GetTokenInfo(ctx)
if err != nil {
return nil, dstCredType, err
}

tc, err = tokenInfo.GetTokenCredential()
if err != nil {
return nil, dstCredType, err
}
}

var dstReauthTok *common.ScopedAuthenticator
if at, ok := tc.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
dstReauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

var srcTokenCred *common.ScopedToken
if fromTo.IsS2S() && srcCredType.IsAzureOAuth() {
// Get token from env var or cache.
srcTokenInfo, err := uotm.GetTokenInfo(ctx)
if err != nil {
return nil, dstCredType, err
}

sourceTc, err := srcTokenInfo.GetTokenCredential()
if err != nil {
return nil, dstCredType, err
}
srcTokenCred = common.NewScopedCredential(sourceTc, srcCredType)
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, srcTokenCred, dstReauthTok)

// Create Destination Client.
var azureFileSpecificOptions any
if fromTo.To() == common.ELocation.File() || fromTo.To() == common.ELocation.FileNFS() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fromTo.To().IsFile()

Suggested change
if fromTo.To() == common.ELocation.File() || fromTo.To() == common.ELocation.FileNFS() {
if fromTo.To() == common.ELocation.File() || fromTo.To() == common.ELocation.FileNFS() {

azureFileSpecificOptions = &common.FileClientOptions{
AllowTrailingDot: trailingDot == common.ETrailingDotOption.Enable(),
AllowSourceTrailingDot: trailingDot == common.ETrailingDotOption.Enable() && fromTo.To() == common.ELocation.File(),
}
}

dstServiceClient, err := common.GetServiceClientForLocation(
fromTo.To(),
destination,
dstCredType,
tc,
&options,
azureFileSpecificOptions,
)
if err != nil {
return nil, dstCredType, err
}
return dstServiceClient, dstCredType, nil
}
10 changes: 6 additions & 4 deletions cmd/copyEnumeratorInit.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde
t, err = traverser.InitResourceTraverser(cca.Source, cca.FromTo.From(), ctx, traverser.InitResourceTraverserOptions{
DestResourceType: &dest,

Credential: &srcCredInfo,
Client: jobPartOrder.SrcServiceClient,
CredentialType: srcCredInfo.CredentialType,

ListOfFiles: cca.ListOfFilesChannel,
ListOfVersionIDs: cca.ListOfVersionIDsChannel,
Expand Down Expand Up @@ -119,7 +120,7 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde
}

// Check if the destination is a directory to correctly decide where our files land
isDestDir := cca.isDestDirectory(cca.Destination, ctx)
isDestDir := cca.isDestDirectory(cca.Destination, jobPartOrder, ctx)
if cca.ListOfVersionIDsChannel != nil && (!(cca.FromTo == common.EFromTo.BlobLocal() || cca.FromTo == common.EFromTo.BlobTrash()) || cca.IsSourceDir || !isDestDir) {
log.Fatalf("Either source is not a blob or destination is not a local folder")
}
Expand Down Expand Up @@ -374,7 +375,7 @@ func (cca *CookedCopyCmdArgs) initEnumerator(jobPartOrder common.CopyJobPartOrde

// This is condensed down into an individual function as we don't end up reusing the destination traverser at all.
// This is just for the directory check.
func (cca *CookedCopyCmdArgs) isDestDirectory(dst common.ResourceString, ctx context.Context) bool {
func (cca *CookedCopyCmdArgs) isDestDirectory(dst common.ResourceString, jobPartOrder common.CopyJobPartOrderRequest, ctx context.Context) bool {
var err error
dstCredInfo := common.CredentialInfo{}

Expand All @@ -387,7 +388,8 @@ func (cca *CookedCopyCmdArgs) isDestDirectory(dst common.ResourceString, ctx con
}

rt, err := traverser.InitResourceTraverser(dst, cca.FromTo.To(), ctx, traverser.InitResourceTraverserOptions{
Credential: &dstCredInfo,
CredentialType: dstCredInfo.CredentialType,
Client: jobPartOrder.DstServiceClient,

ListOfVersionIDs: cca.ListOfVersionIDsChannel,

Expand Down
24 changes: 23 additions & 1 deletion cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,30 @@ func (cooked cookedListCmdArgs) handleListContainerCommand() (err error) {
// check if user wants to get version id
getVersionId := containsProperty(cooked.properties, VersionId)

var reauthTok *common.ScopedAuthenticator
if at, ok := credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser2.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
var fileClientOptions any
if cooked.location.IsFile() {
fileClientOptions = &common.FileClientOptions{AllowTrailingDot: cooked.trailingDot.IsEnabled()}
}

targetServiceClient, err := common.GetServiceClientForLocation(
cooked.location,
source,
credentialInfo.CredentialType,
credentialInfo.OAuthTokenInfo.TokenCredential,
&options,
fileClientOptions,
)

traverser, err := traverser2.InitResourceTraverser(source, cooked.location, ctx, traverser2.InitResourceTraverserOptions{
Credential: &credentialInfo,
Client: targetServiceClient,
CredentialType: credentialInfo.CredentialType,

TrailingDotOption: cooked.trailingDot,

Expand Down
58 changes: 30 additions & 28 deletions cmd/removeEnumerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,38 @@ func newRemoveEnumerator(cca *CookedCopyCmdArgs) (enumerator *traverser.CopyEnum

ctx := context.WithValue(context.TODO(), ste.ServiceAPIVersionOverride, ste.DefaultServiceApiVersion)

from := cca.FromTo.From()
if !from.SupportsTrailingDot() {
cca.trailingDot = common.ETrailingDotOption.Disable()
}

var reauthTok *common.ScopedAuthenticator
if at, ok := cca.credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
var fileClientOptions any
if cca.FromTo.From().IsFile() {
fileClientOptions = &common.FileClientOptions{AllowTrailingDot: cca.trailingDot.IsEnabled()}
}
targetServiceClient, err := common.GetServiceClientForLocation(
cca.FromTo.From(),
cca.Source,
cca.credentialInfo.CredentialType,
cca.credentialInfo.OAuthTokenInfo.TokenCredential,
&options,
fileClientOptions,
)
if err != nil {
return nil, err
}

// Include-path is handled by ListOfFilesChannel.
sourceTraverser, err = traverser.InitResourceTraverser(cca.Source, cca.FromTo.From(), ctx, traverser.InitResourceTraverserOptions{
Credential: &cca.credentialInfo,
Client: targetServiceClient,
CredentialType: cca.credentialInfo.CredentialType,

ListOfFiles: cca.ListOfFilesChannel,
ListOfVersionIDs: cca.ListOfVersionIDsChannel,
Expand Down Expand Up @@ -103,33 +132,6 @@ func newRemoveEnumerator(cca *CookedCopyCmdArgs) (enumerator *traverser.CopyEnum
}
common.LogToJobLogWithPrefix(message, common.LogInfo)

from := cca.FromTo.From()
if !from.SupportsTrailingDot() {
cca.trailingDot = common.ETrailingDotOption.Disable()
}

var reauthTok *common.ScopedAuthenticator
if at, ok := cca.credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
var fileClientOptions any
if cca.FromTo.From().IsFile() {
fileClientOptions = &common.FileClientOptions{AllowTrailingDot: cca.trailingDot.IsEnabled()}
}
targetServiceClient, err := common.GetServiceClientForLocation(
cca.FromTo.From(),
cca.Source,
cca.credentialInfo.CredentialType,
cca.credentialInfo.OAuthTokenInfo.TokenCredential,
&options,
fileClientOptions,
)
if err != nil {
return nil, err
}
transferScheduler := newRemoveTransferProcessor(cca, NumOfFilesPerDispatchJobPart, fpo, targetServiceClient)

finalize := func() error {
Expand Down
6 changes: 5 additions & 1 deletion cmd/removeProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package cmd

import (
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/jobsAdmin"
)

// extract the right info from cooked arguments and instantiate a generic copy transfer processor from it
Expand Down Expand Up @@ -53,9 +54,12 @@ func newRemoveTransferProcessor(cca *CookedCopyCmdArgs, numOfTransfersPerPart in
}
}
reportFinalPart := func() { cca.isEnumerationComplete = true }
if cca.dryrunMode {
jobsAdmin.ExecuteNewCopyJobPartOrder = getDryrunNewCopyJobPartOrder(copyJobTemplate.SourceRoot.Value, copyJobTemplate.DestinationRoot.Value, copyJobTemplate.FromTo)
}

// note that the source and destination, along with the template are given to the generic processor's constructor
// this means that given an object with a relative path, this processor already knows how to schedule the right kind of transfers
return newCopyTransferProcessor(copyJobTemplate, numOfTransfersPerPart, cca.Source, cca.Destination,
reportFirstPart, reportFinalPart, false, cca.dryrunMode)
reportFirstPart, reportFinalPart, false)
}
51 changes: 26 additions & 25 deletions cmd/setPropertiesEnumerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,34 @@ func setPropertiesEnumerator(cca *CookedCopyCmdArgs) (enumerator *traverser.Copy
return nil, errors.New("a SAS token (or S3 access key) is required as a part of the input for set-properties on File Storage")
}

var reauthTok *common.ScopedAuthenticator
if at, ok := cca.credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
var fileClientOptions any
if cca.FromTo.From().IsFile() {
fileClientOptions = &common.FileClientOptions{AllowTrailingDot: cca.trailingDot.IsEnabled()}
}

targetServiceClient, err := common.GetServiceClientForLocation(
cca.FromTo.From(),
cca.Source,
cca.credentialInfo.CredentialType,
cca.credentialInfo.OAuthTokenInfo.TokenCredential,
&options,
fileClientOptions,
)
if err != nil {
return nil, err
}

// Include-path is handled by ListOfFilesChannel.
sourceTraverser, err = traverser.InitResourceTraverser(cca.Source, cca.FromTo.From(), ctx, traverser.InitResourceTraverserOptions{
Credential: &cca.credentialInfo,
Client: targetServiceClient,
CredentialType: cca.credentialInfo.CredentialType,

ListOfFiles: cca.ListOfFilesChannel,
ListOfVersionIDs: cca.ListOfVersionIDsChannel,
Expand Down Expand Up @@ -89,30 +114,6 @@ func setPropertiesEnumerator(cca *CookedCopyCmdArgs) (enumerator *traverser.Copy
}
common.LogToJobLogWithPrefix(message, common.LogInfo)

var reauthTok *common.ScopedAuthenticator
if at, ok := cca.credentialInfo.OAuthTokenInfo.TokenCredential.(common.AuthenticateToken); ok { // We don't need two different tokens here since it gets passed in just the same either way.
// This will cause a reauth with StorageScope, which is fine, that's the original Authenticate call as it stands.
reauthTok = (*common.ScopedAuthenticator)(common.NewScopedCredential(at, common.ECredentialType.OAuthToken()))
}

options := traverser.CreateClientOptions(common.AzcopyCurrentJobLogger, nil, reauthTok)
var fileClientOptions any
if cca.FromTo.From().IsFile() {
fileClientOptions = &common.FileClientOptions{AllowTrailingDot: cca.trailingDot.IsEnabled()}
}

targetServiceClient, err := common.GetServiceClientForLocation(
cca.FromTo.From(),
cca.Source,
cca.credentialInfo.CredentialType,
cca.credentialInfo.OAuthTokenInfo.TokenCredential,
&options,
fileClientOptions,
)
if err != nil {
return nil, err
}

transferScheduler := setPropertiesTransferProcessor(cca, NumOfFilesPerDispatchJobPart, fpo, targetServiceClient)

finalize := func() error {
Expand Down
7 changes: 5 additions & 2 deletions cmd/setPropertiesProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package cmd

import (
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/jobsAdmin"
)

func setPropertiesTransferProcessor(cca *CookedCopyCmdArgs, numOfTransfersPerPart int, fpo common.FolderPropertyOption, targetServiceClient *common.ServiceClient) *copyTransferProcessor {
Expand Down Expand Up @@ -58,9 +59,11 @@ func setPropertiesTransferProcessor(cca *CookedCopyCmdArgs, numOfTransfersPerPar
}
}
reportFinalPart := func() { cca.isEnumerationComplete = true }

if cca.dryrunMode {
jobsAdmin.ExecuteNewCopyJobPartOrder = getDryrunNewCopyJobPartOrder(copyJobTemplate.SourceRoot.Value, copyJobTemplate.DestinationRoot.Value, copyJobTemplate.FromTo)
}
// note that the source and destination, along with the template are given to the generic processor's constructor
// this means that given an object with a relative path, this processor already knows how to schedule the right kind of transfers
return newCopyTransferProcessor(copyJobTemplate, numOfTransfersPerPart, cca.Source, cca.Destination,
reportFirstPart, reportFinalPart, false, cca.dryrunMode)
reportFirstPart, reportFinalPart, false)
}
Loading
Loading