github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/transferfiles/transfer.go (about)

     1  package transferfiles
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"os/signal"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  
    15  	"github.com/jfrog/gofrog/version"
    16  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferfiles/state"
    17  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils/precheckrunner"
    18  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
    19  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    20  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    21  	usageReporter "github.com/jfrog/jfrog-cli-core/v2/utils/usage"
    22  	serviceUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    23  	"github.com/jfrog/jfrog-client-go/artifactory/usage"
    24  	clientutils "github.com/jfrog/jfrog-client-go/utils"
    25  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    26  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    27  	"github.com/jfrog/jfrog-client-go/utils/log"
    28  	"golang.org/x/exp/slices"
    29  )
    30  
    31  const (
    32  	// Size of the channel where the transfer's go routines write the transfer errors
    33  	fileWritersChannelSize       = 500000
    34  	retries                      = 600
    35  	retriesWaitMilliSecs         = 5000
    36  	dataTransferPluginMinVersion = "1.7.0"
    37  	disableDistinctAqlMinVersion = "7.37"
    38  )
    39  
    40  type TransferFilesCommand struct {
    41  	context                   context.Context
    42  	cancelFunc                context.CancelFunc
    43  	sourceServerDetails       *config.ServerDetails
    44  	targetServerDetails       *config.ServerDetails
    45  	sourceStorageInfoManager  *utils.StorageInfoManager
    46  	targetStorageInfoManager  *utils.StorageInfoManager
    47  	checkExistenceInFilestore bool
    48  	progressbar               *TransferProgressMng
    49  	includeReposPatterns      []string
    50  	excludeReposPatterns      []string
    51  	ignoreState               bool
    52  	proxyKey                  string
    53  	status                    bool
    54  	stop                      bool
    55  	stopSignal                chan os.Signal
    56  	stateManager              *state.TransferStateManager
    57  	preChecks                 bool
    58  	locallyGeneratedFilter    *locallyGeneratedFilter
    59  	// Optimization in Artifactory version 7.37 and above enables the exclusion of setting DISTINCT in SQL queries
    60  	disabledDistinctiveAql bool
    61  }
    62  
    63  func NewTransferFilesCommand(sourceServer, targetServer *config.ServerDetails) (*TransferFilesCommand, error) {
    64  	stateManager, err := state.NewTransferStateManager(false)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	ctx, cancelFunc := context.WithCancel(context.Background())
    69  	return &TransferFilesCommand{
    70  		context:             ctx,
    71  		cancelFunc:          cancelFunc,
    72  		sourceServerDetails: sourceServer,
    73  		targetServerDetails: targetServer,
    74  		stateManager:        stateManager,
    75  		stopSignal:          make(chan os.Signal, 1),
    76  	}, nil
    77  }
    78  
    79  func (tdc *TransferFilesCommand) CommandName() string {
    80  	return "rt_transfer_files"
    81  }
    82  
    83  func (tdc *TransferFilesCommand) SetFilestore(filestore bool) {
    84  	tdc.checkExistenceInFilestore = filestore
    85  }
    86  
    87  func (tdc *TransferFilesCommand) SetIncludeReposPatterns(includeReposPatterns []string) {
    88  	tdc.includeReposPatterns = includeReposPatterns
    89  }
    90  
    91  func (tdc *TransferFilesCommand) SetExcludeReposPatterns(excludeReposPatterns []string) {
    92  	tdc.excludeReposPatterns = excludeReposPatterns
    93  }
    94  
    95  func (tdc *TransferFilesCommand) SetIgnoreState(ignoreState bool) {
    96  	tdc.ignoreState = ignoreState
    97  }
    98  
    99  func (tdc *TransferFilesCommand) SetProxyKey(proxyKey string) {
   100  	tdc.proxyKey = proxyKey
   101  }
   102  
   103  func (tdc *TransferFilesCommand) SetStatus(status bool) {
   104  	tdc.status = status
   105  }
   106  
   107  func (tdc *TransferFilesCommand) SetStop(stop bool) {
   108  	tdc.stop = stop
   109  }
   110  
   111  func (tdc *TransferFilesCommand) SetPreChecks(check bool) {
   112  	tdc.preChecks = check
   113  }
   114  
   115  func (tdc *TransferFilesCommand) Run() (err error) {
   116  	if tdc.status {
   117  		return ShowStatus()
   118  	}
   119  	if tdc.stop {
   120  		return tdc.signalStop()
   121  	}
   122  	if err = tdc.stateManager.TryLockTransferStateManager(); err != nil {
   123  		return err
   124  	}
   125  	defer func() {
   126  		err = errors.Join(err, tdc.stateManager.UnlockTransferStateManager())
   127  	}()
   128  	if _, err = tdc.stateManager.InitStartTimestamp(); err != nil {
   129  		return err
   130  	}
   131  
   132  	srcUpService, err := createSrcRtUserPluginServiceManager(tdc.context, tdc.sourceServerDetails)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	if err = getAndValidateDataTransferPlugin(srcUpService); err != nil {
   138  		return err
   139  	}
   140  
   141  	if err = tdc.verifySourceTargetConnectivity(srcUpService); err != nil {
   142  		return err
   143  	}
   144  
   145  	if err = tdc.initDistinctAql(); err != nil {
   146  		return err
   147  	}
   148  
   149  	if tdc.preChecks {
   150  		if runner, err := tdc.NewTransferDataPreChecksRunner(); err != nil {
   151  			return err
   152  		} else {
   153  			return runner.Run(tdc.context, tdc.sourceServerDetails)
   154  		}
   155  	}
   156  
   157  	if err = tdc.initTransferDir(); err != nil {
   158  		return err
   159  	}
   160  
   161  	if err = assertSupportedTransferDirStructure(); err != nil {
   162  		return err
   163  	}
   164  
   165  	if err = tdc.initStorageInfoManagers(); err != nil {
   166  		return err
   167  	}
   168  
   169  	sourceLocalRepos, sourceBuildInfoRepos, err := tdc.getAllLocalRepos(tdc.sourceServerDetails, tdc.sourceStorageInfoManager)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	allSourceLocalRepos := append(slices.Clone(sourceLocalRepos), sourceBuildInfoRepos...)
   174  	targetLocalRepos, targetBuildInfoRepos, err := tdc.getAllLocalRepos(tdc.targetServerDetails, tdc.targetStorageInfoManager)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	if err = tdc.initLocallyGeneratedFilter(); err != nil {
   180  		return err
   181  	}
   182  
   183  	// Handle interruptions
   184  	finishStopping, newPhase := tdc.handleStop(srcUpService)
   185  	defer finishStopping()
   186  
   187  	if err = tdc.removeOldFilesIfNeeded(allSourceLocalRepos); err != nil {
   188  		return err
   189  	}
   190  
   191  	if err = tdc.initStateManager(allSourceLocalRepos, sourceBuildInfoRepos); err != nil {
   192  		return err
   193  	}
   194  
   195  	// Init and Set progress bar with the length of the source local and build info repositories
   196  	err = initTransferProgressMng(allSourceLocalRepos, tdc, 0)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	unsetTempDir, err := initTempDir()
   202  	if err != nil {
   203  		return err
   204  	}
   205  	defer unsetTempDir()
   206  
   207  	go tdc.reportTransferFilesUsage()
   208  
   209  	// Transfer local repositories
   210  	if err := tdc.transferRepos(sourceLocalRepos, targetLocalRepos, false, newPhase, srcUpService); err != nil {
   211  		return tdc.cleanup(err, sourceLocalRepos)
   212  	}
   213  
   214  	// Transfer build-info repositories
   215  	if err := tdc.transferRepos(sourceBuildInfoRepos, targetBuildInfoRepos, true, newPhase, srcUpService); err != nil {
   216  		return tdc.cleanup(err, allSourceLocalRepos)
   217  	}
   218  
   219  	// Close progressBar and create CSV errors summary file
   220  	return tdc.cleanup(err, allSourceLocalRepos)
   221  }
   222  
   223  func (tdc *TransferFilesCommand) initStateManager(allSourceLocalRepos, sourceBuildInfoRepos []string) error {
   224  	_, totalBiFiles, err := tdc.sourceStorageInfoManager.GetReposTotalSizeAndFiles(sourceBuildInfoRepos...)
   225  	if err != nil {
   226  		return err
   227  	}
   228  	totalSizeBytes, totalFiles, err := tdc.sourceStorageInfoManager.GetReposTotalSizeAndFiles(allSourceLocalRepos...)
   229  	if err != nil {
   230  		return err
   231  	}
   232  	// Init State Manager's fields values
   233  	tdc.stateManager.OverallTransfer.TotalSizeBytes = totalSizeBytes
   234  	tdc.stateManager.OverallTransfer.TotalUnits = totalFiles
   235  	tdc.stateManager.TotalRepositories.TotalUnits = int64(len(allSourceLocalRepos))
   236  	tdc.stateManager.OverallBiFiles.TotalUnits = totalBiFiles
   237  	tdc.stateManager.TimeEstimationManager.CurrentTotalTransferredBytes = 0
   238  	if !tdc.ignoreState {
   239  		numberInitialErrors, e := getRetryErrorCount(allSourceLocalRepos)
   240  		if e != nil {
   241  			return e
   242  		}
   243  		tdc.stateManager.TransferFailures = uint64(numberInitialErrors)
   244  
   245  		numberInitialDelays, e := getDelayedFilesCount(allSourceLocalRepos)
   246  		if e != nil {
   247  			return e
   248  		}
   249  		tdc.stateManager.DelayedFiles = uint64(numberInitialDelays)
   250  	} else {
   251  		tdc.stateManager.VisitedFolders = 0
   252  		tdc.stateManager.TransferFailures = 0
   253  		tdc.stateManager.DelayedFiles = 0
   254  	}
   255  	return nil
   256  }
   257  
   258  func (tdc *TransferFilesCommand) reportTransferFilesUsage() {
   259  	log.Debug(usageReporter.ReportUsagePrefix, "Sending Transfer Files info...")
   260  	sourceStorageInfo, err := tdc.sourceStorageInfoManager.GetStorageInfo()
   261  	if err != nil {
   262  		log.Debug(err.Error())
   263  		return
   264  	}
   265  	sourceServiceId, err := tdc.sourceStorageInfoManager.GetServiceId()
   266  	if err != nil {
   267  		log.Debug(err.Error())
   268  		return
   269  	}
   270  
   271  	reportUsageAttributes := []usage.ReportUsageAttribute{
   272  		{
   273  			AttributeName:  "sourceServiceId",
   274  			AttributeValue: sourceServiceId,
   275  		},
   276  		{
   277  			AttributeName:  "sourceStorageSize",
   278  			AttributeValue: sourceStorageInfo.BinariesSize,
   279  		},
   280  	}
   281  	err = usage.SendReportUsage(coreutils.GetCliUserAgent(), tdc.CommandName(), tdc.targetStorageInfoManager.GetServiceManager(), reportUsageAttributes...)
   282  	if err != nil {
   283  		log.Debug(err.Error())
   284  	}
   285  }
   286  
   287  func (tdc *TransferFilesCommand) initStorageInfoManagers() error {
   288  	// Init source storage info manager
   289  	storageInfoManager, err := utils.NewStorageInfoManager(tdc.context, tdc.sourceServerDetails)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	tdc.sourceStorageInfoManager = storageInfoManager
   294  	if err := storageInfoManager.CalculateStorageInfo(); err != nil {
   295  		return err
   296  	}
   297  
   298  	// Init target storage info manager
   299  	storageInfoManager, err = utils.NewStorageInfoManager(tdc.context, tdc.targetServerDetails)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	tdc.targetStorageInfoManager = storageInfoManager
   304  	return storageInfoManager.CalculateStorageInfo()
   305  }
   306  
   307  func (tdc *TransferFilesCommand) initDistinctAql() error {
   308  	// Init source storage services manager
   309  	servicesManager, err := createTransferServiceManager(tdc.context, tdc.sourceServerDetails)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	// Getting source Artifactory version
   315  	sourceArtifactoryVersion, err := servicesManager.GetVersion()
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	// If version is at least 7.37, add .distinct(false) to AQL queries
   321  	if version.NewVersion(sourceArtifactoryVersion).AtLeast(disableDistinctAqlMinVersion) {
   322  		tdc.disabledDistinctiveAql = true
   323  		log.Debug(fmt.Sprintf("The source Artifactory version is above %s (%s). Adding .distinct(false) to AQL requests.",
   324  			disableDistinctAqlMinVersion, sourceArtifactoryVersion))
   325  	}
   326  	return nil
   327  }
   328  
   329  // Creates the Pre-checks runner for the data transfer command
   330  func (tdc *TransferFilesCommand) NewTransferDataPreChecksRunner() (runner *precheckrunner.PreCheckRunner, err error) {
   331  	// Get relevant repos
   332  	serviceManager, err := createTransferServiceManager(tdc.context, tdc.sourceServerDetails)
   333  	if err != nil {
   334  		return
   335  	}
   336  	localRepos, err := utils.GetFilteredRepositoriesByNameAndType(serviceManager, tdc.includeReposPatterns, tdc.excludeReposPatterns, utils.Local)
   337  	if err != nil {
   338  		return
   339  	}
   340  	federatedRepos, err := utils.GetFilteredRepositoriesByNameAndType(serviceManager, tdc.includeReposPatterns, tdc.excludeReposPatterns, utils.Federated)
   341  	if err != nil {
   342  		return
   343  	}
   344  
   345  	runner = precheckrunner.NewPreChecksRunner()
   346  
   347  	// Add pre checks here
   348  	runner.AddCheck(NewLongPropertyCheck(append(localRepos, federatedRepos...), tdc.disabledDistinctiveAql))
   349  
   350  	return
   351  }
   352  
   353  func (tdc *TransferFilesCommand) transferRepos(sourceRepos []string, targetRepos []string,
   354  	buildInfoRepo bool, newPhase *transferPhase, srcUpService *srcUserPluginService) error {
   355  	for _, repoKey := range sourceRepos {
   356  		if tdc.shouldStop() {
   357  			return nil
   358  		}
   359  		err := tdc.transferSingleRepo(repoKey, targetRepos, buildInfoRepo, newPhase, srcUpService)
   360  		if err != nil {
   361  			return err
   362  		}
   363  	}
   364  	return nil
   365  }
   366  
   367  func (tdc *TransferFilesCommand) transferSingleRepo(sourceRepoKey string, targetRepos []string,
   368  	buildInfoRepo bool, newPhase *transferPhase, srcUpService *srcUserPluginService) (err error) {
   369  	if !slices.Contains(targetRepos, sourceRepoKey) {
   370  		log.Error("repository '" + sourceRepoKey + "' does not exist in target. Skipping...")
   371  		return
   372  	}
   373  
   374  	repoSummary, err := tdc.sourceStorageInfoManager.GetRepoSummary(sourceRepoKey)
   375  	if err != nil {
   376  		log.Error(err.Error() + ". Skipping...")
   377  		return nil
   378  	}
   379  
   380  	if tdc.progressbar != nil {
   381  		tdc.progressbar.NewRepository(sourceRepoKey)
   382  	}
   383  
   384  	if err = tdc.updateRepoState(repoSummary, buildInfoRepo); err != nil {
   385  		return
   386  	}
   387  
   388  	restoreFunc, err := tdc.handleMaxUniqueSnapshots(repoSummary)
   389  	if err != nil {
   390  		return
   391  	}
   392  	defer func() {
   393  		err = errors.Join(err, restoreFunc())
   394  	}()
   395  
   396  	if err = tdc.initCurThreads(buildInfoRepo); err != nil {
   397  		return
   398  	}
   399  	minChecksumDeploySize, err := utils.GetMinChecksumDeploySize()
   400  	if err != nil {
   401  		return
   402  	}
   403  	for currentPhaseId := 0; currentPhaseId < NumberOfPhases; currentPhaseId++ {
   404  		if tdc.shouldStop() {
   405  			return
   406  		}
   407  		// Ensure the data structure which stores the upload tasks on Artifactory's side is wiped clean,
   408  		// in case some requests to delete handles tasks sent by JFrog CLI did not reach Artifactory.
   409  		err = stopTransferInArtifactory(tdc.sourceServerDetails, srcUpService)
   410  		if err != nil {
   411  			log.Error(err)
   412  		}
   413  		*newPhase = createTransferPhase(currentPhaseId)
   414  		if err = tdc.stateManager.SetRepoPhase(currentPhaseId); err != nil {
   415  			return
   416  		}
   417  		if err = tdc.startPhase(newPhase, sourceRepoKey, buildInfoRepo, *repoSummary, srcUpService, minChecksumDeploySize); err != nil {
   418  			return
   419  		}
   420  	}
   421  	return tdc.stateManager.IncRepositoriesTransferred()
   422  }
   423  
   424  func (tdc *TransferFilesCommand) updateRepoState(repoSummary *serviceUtils.RepositorySummary, buildInfoRepo bool) error {
   425  	filesCount, err := utils.GetFilesCountFromRepositorySummary(repoSummary)
   426  	if err != nil {
   427  		return err
   428  	}
   429  
   430  	usedSpaceInBytes, err := utils.GetUsedSpaceInBytes(repoSummary)
   431  	if err != nil {
   432  		return err
   433  	}
   434  
   435  	return tdc.stateManager.SetRepoState(repoSummary.RepoKey, usedSpaceInBytes, filesCount, buildInfoRepo, tdc.ignoreState)
   436  }
   437  
   438  func (tdc *TransferFilesCommand) initTransferDir() error {
   439  	transferDir, err := coreutils.GetJfrogTransferDir()
   440  	if err != nil {
   441  		return err
   442  	}
   443  	exist, err := fileutils.IsDirExists(transferDir, false)
   444  	if err != nil {
   445  		return err
   446  	}
   447  	if exist {
   448  		// Remove ~/.jfrog/transfer/stop if exist
   449  		if err = os.RemoveAll(filepath.Join(transferDir, StopFileName)); err != nil {
   450  			return errorutils.CheckError(err)
   451  		}
   452  	}
   453  	return errorutils.CheckError(os.MkdirAll(transferDir, 0777))
   454  }
   455  
   456  func (tdc *TransferFilesCommand) removeOldFilesIfNeeded(repos []string) error {
   457  	// If we ignore the old state, we need to remove all the old unused files so the process can start clean
   458  	if tdc.ignoreState {
   459  		errFiles, err := getErrorsFiles(repos, true)
   460  		if err != nil {
   461  			return err
   462  		}
   463  		for _, file := range errFiles {
   464  			err = os.Remove(file)
   465  			if err != nil {
   466  				return errorutils.CheckError(err)
   467  			}
   468  		}
   469  		delayFiles, err := getDelayFiles(repos)
   470  		if err != nil {
   471  			return err
   472  		}
   473  		for _, file := range delayFiles {
   474  			err = os.Remove(file)
   475  			if err != nil {
   476  				return errorutils.CheckError(err)
   477  			}
   478  		}
   479  	}
   480  	return nil
   481  }
   482  
   483  func (tdc *TransferFilesCommand) startPhase(newPhase *transferPhase, repo string, buildInfoRepo bool, repoSummary serviceUtils.RepositorySummary, srcUpService *srcUserPluginService, minChecksumDeploySize int64) error {
   484  	tdc.initNewPhase(*newPhase, srcUpService, repoSummary, repo, buildInfoRepo, minChecksumDeploySize)
   485  	skip, err := (*newPhase).shouldSkipPhase()
   486  	if err != nil || skip {
   487  		return err
   488  	}
   489  	err = (*newPhase).phaseStarted()
   490  	if err != nil {
   491  		return err
   492  	}
   493  	err = (*newPhase).initProgressBar()
   494  	if err != nil {
   495  		return err
   496  	}
   497  	if tdc.disabledDistinctiveAql {
   498  		(*newPhase).setDisabledDistinctiveAql()
   499  	}
   500  	printPhaseChange("Running '" + (*newPhase).getPhaseName() + "' for repo '" + repo + "'...")
   501  	err = (*newPhase).run()
   502  	if err != nil {
   503  		return err
   504  	}
   505  	printPhaseChange("Done running '" + (*newPhase).getPhaseName() + "' for repo '" + repo + "'.")
   506  	return (*newPhase).phaseDone()
   507  }
   508  
   509  // Handle interrupted signal.
   510  // shouldStop - Pointer to boolean variable, if the process gets interrupted shouldStop will be set to true
   511  // newPhase - The current running phase
   512  // srcUpService - Source plugin service
   513  func (tdc *TransferFilesCommand) handleStop(srcUpService *srcUserPluginService) (func(), *transferPhase) {
   514  	var newPhase transferPhase
   515  	finishStop := make(chan bool)
   516  	signal.Notify(tdc.stopSignal, os.Interrupt, syscall.SIGTERM)
   517  	go func() {
   518  		defer close(finishStop)
   519  		// Wait for the stop signal or close(stopSignal) to happen
   520  		if <-tdc.stopSignal == nil {
   521  			// The stopSignal channel is closed
   522  			return
   523  		}
   524  		// Before interrupting the process, do a thread dump
   525  		if err := doThreadDump(); err != nil {
   526  			log.Error(err)
   527  		}
   528  		tdc.cancelFunc()
   529  		if newPhase != nil {
   530  			newPhase.StopGracefully()
   531  		}
   532  		log.Info("Gracefully stopping files transfer...")
   533  		err := stopTransferInArtifactory(tdc.sourceServerDetails, srcUpService)
   534  		if err != nil {
   535  			log.Error(err)
   536  		}
   537  	}()
   538  
   539  	// Return a cleanup function that closes the stopSignal channel and wait for close if needed
   540  	return func() {
   541  		// Close the stop signal channel
   542  		close(tdc.stopSignal)
   543  		if tdc.shouldStop() {
   544  			// If we should stop, wait for stop to happen
   545  			<-finishStop
   546  		}
   547  	}, &newPhase
   548  }
   549  
   550  func (tdc *TransferFilesCommand) initNewPhase(newPhase transferPhase, srcUpService *srcUserPluginService, repoSummary serviceUtils.RepositorySummary, repoKey string, buildInfoRepo bool, minChecksumDeploySize int64) {
   551  	newPhase.setContext(tdc.context)
   552  	newPhase.setRepoKey(repoKey)
   553  	newPhase.setCheckExistenceInFilestore(tdc.checkExistenceInFilestore)
   554  	newPhase.setSourceDetails(tdc.sourceServerDetails)
   555  	newPhase.setTargetDetails(tdc.targetServerDetails)
   556  	newPhase.setSrcUserPluginService(srcUpService)
   557  	newPhase.setRepoSummary(repoSummary)
   558  	newPhase.setProgressBar(tdc.progressbar)
   559  	newPhase.setProxyKey(tdc.proxyKey)
   560  	newPhase.setStateManager(tdc.stateManager)
   561  	newPhase.setBuildInfo(buildInfoRepo)
   562  	newPhase.setPackageType(repoSummary.PackageType)
   563  	newPhase.setLocallyGeneratedFilter(tdc.locallyGeneratedFilter)
   564  	newPhase.setStopSignal(tdc.stopSignal)
   565  	newPhase.setMinCheckSumDeploySize(minChecksumDeploySize)
   566  }
   567  
   568  // Get all local and build-info repositories of the input server
   569  // serverDetails      - Source or target server details
   570  // storageInfoManager - Source or target storage info manager
   571  func (tdc *TransferFilesCommand) getAllLocalRepos(serverDetails *config.ServerDetails, storageInfoManager *utils.StorageInfoManager) ([]string, []string, error) {
   572  	serviceManager, err := createTransferServiceManager(tdc.context, serverDetails)
   573  	if err != nil {
   574  		return []string{}, []string{}, err
   575  	}
   576  	excludeRepoPatternsWithBuildInfo := tdc.excludeReposPatterns
   577  	excludeRepoPatternsWithBuildInfo = append(excludeRepoPatternsWithBuildInfo, "*-build-info")
   578  	localRepos, err := utils.GetFilteredRepositoriesByNameAndType(serviceManager, tdc.includeReposPatterns, excludeRepoPatternsWithBuildInfo, utils.Local)
   579  	if err != nil {
   580  		return []string{}, []string{}, err
   581  	}
   582  	federatedRepos, err := utils.GetFilteredRepositoriesByNameAndType(serviceManager, tdc.includeReposPatterns, excludeRepoPatternsWithBuildInfo, utils.Federated)
   583  	if err != nil {
   584  		return []string{}, []string{}, err
   585  	}
   586  
   587  	storageInfo, err := storageInfoManager.GetStorageInfo()
   588  	if err != nil {
   589  		return []string{}, []string{}, err
   590  	}
   591  
   592  	buildInfoRepoKeys, err := utils.GetFilteredBuildInfoRepositories(storageInfo, tdc.includeReposPatterns, tdc.excludeReposPatterns)
   593  	if err != nil {
   594  		return []string{}, []string{}, err
   595  	}
   596  
   597  	return append(localRepos, federatedRepos...), buildInfoRepoKeys, err
   598  }
   599  
   600  func (tdc *TransferFilesCommand) initCurThreads(buildInfoRepo bool) error {
   601  	// Use default threads if settings file doesn't exist or an error occurred.
   602  	curChunkUploaderThreads = utils.DefaultThreads
   603  	curChunkBuilderThreads = utils.DefaultThreads
   604  	settings, err := utils.LoadTransferSettings()
   605  	if err != nil {
   606  		return err
   607  	}
   608  	if settings != nil {
   609  		curChunkBuilderThreads, curChunkUploaderThreads = settings.CalcNumberOfThreads(buildInfoRepo)
   610  		if buildInfoRepo && curChunkUploaderThreads < settings.ThreadsNumber {
   611  			log.Info("Build info transferring - using reduced number of threads")
   612  		}
   613  	}
   614  
   615  	log.Info("Running with maximum", strconv.Itoa(curChunkUploaderThreads), "working threads...")
   616  	return nil
   617  }
   618  
   619  func (tdc *TransferFilesCommand) initLocallyGeneratedFilter() error {
   620  	servicesManager, err := createTransferServiceManager(tdc.context, tdc.targetServerDetails)
   621  	if err != nil {
   622  		return err
   623  	}
   624  	targetArtifactoryVersion, err := servicesManager.GetVersion()
   625  	if err != nil {
   626  		return err
   627  	}
   628  	tdc.locallyGeneratedFilter = NewLocallyGenerated(tdc.context, servicesManager, targetArtifactoryVersion)
   629  	return err
   630  }
   631  
   632  func printPhaseChange(message string) {
   633  	log.Info("========== " + message + " ==========")
   634  }
   635  
   636  // If an error occurred cleanup will:
   637  // 1. Close progressBar
   638  // 2. Create CSV errors summary file
   639  func (tdc *TransferFilesCommand) cleanup(originalErr error, sourceRepos []string) (err error) {
   640  	err = originalErr
   641  	// Quit progress bar (before printing logs)
   642  	if tdc.progressbar != nil {
   643  		err = errors.Join(err, tdc.progressbar.Quit())
   644  	}
   645  	// Transferring finished successfully
   646  	if originalErr == nil {
   647  		log.Info("Files transfer is complete!")
   648  	}
   649  	if tdc.stateManager.CurrentRepo.Name != "" {
   650  		e := tdc.stateManager.SaveStateAndSnapshots()
   651  		if e != nil {
   652  			log.Error("Couldn't save transfer state", e)
   653  			if err == nil {
   654  				err = e
   655  			}
   656  		}
   657  	}
   658  
   659  	csvErrorsFile, e := createErrorsCsvSummary(sourceRepos, tdc.stateManager.GetStartTimestamp())
   660  	if e != nil {
   661  		log.Error("Couldn't create the errors CSV file", e)
   662  		if err == nil {
   663  			err = e
   664  		}
   665  	}
   666  
   667  	if csvErrorsFile != "" {
   668  		log.Info(fmt.Sprintf("Errors occurred during the transfer. Check the errors summary CSV file in: %s", csvErrorsFile))
   669  	}
   670  	return
   671  }
   672  
   673  // handleMaxUniqueSnapshots handles special cases regarding the Max Unique Snapshots/Tags setting of repositories of
   674  // these package types: Maven, Gradle, NuGet, Ivy, SBT and Docker.
   675  // TL;DR: we might have repositories in the source with more snapshots than the maximum (in Max Unique Snapshots/Tags),
   676  // so we turn it off at the beginning of the transfer and turn it back on at the end.
   677  //
   678  // And in more detail:
   679  // The cleanup of old snapshots in Artifactory is triggered by uploading a new snapshot only, so we might have
   680  // repositories with more snapshots than the maximum (by setting the Max Unique Snapshots/Tags on a repository with more
   681  // snapshots than the maximum without uploading a new snapshot afterwards).
   682  // In such repositories, the transfer process uploads the snapshots to the target instance and triggers the cleanup, so
   683  // eventually the repository in the target might have fewer snapshots than in the source.
   684  // To handle this, we turn off the Max Unique Snapshots/Tags setting (by setting it 0) at the beginning of the transfer
   685  // of the repository, and copy it from the source at the end.
   686  func (tdc *TransferFilesCommand) handleMaxUniqueSnapshots(repoSummary *serviceUtils.RepositorySummary) (restoreFunc func() error, err error) {
   687  	// Get the source repository's max unique snapshots setting
   688  	srcMaxUniqueSnapshots, err := getMaxUniqueSnapshots(tdc.context, tdc.sourceServerDetails, repoSummary)
   689  	if err != nil {
   690  		return
   691  	}
   692  
   693  	// If it's a Maven, Gradle, NuGet, Ivy, SBT or Docker repository, update its max unique snapshots setting to 0.
   694  	// srcMaxUniqueSnapshots == -1 means it's a repository of another package type.
   695  	if srcMaxUniqueSnapshots != -1 {
   696  		err = updateMaxUniqueSnapshots(tdc.context, tdc.targetServerDetails, repoSummary, 0)
   697  		if err != nil {
   698  			return
   699  		}
   700  	}
   701  
   702  	restoreFunc = func() (err error) {
   703  		// Update the target repository's max unique snapshots setting to be the same as in the source, only if it's not 0.
   704  		if srcMaxUniqueSnapshots > 0 {
   705  			err = updateMaxUniqueSnapshots(tdc.context, tdc.targetServerDetails, repoSummary, srcMaxUniqueSnapshots)
   706  		}
   707  		return
   708  	}
   709  	return
   710  }
   711  
   712  // Create the '~/.jfrog/transfer/stop' file to mark the transfer-file process to stop
   713  func (tdc *TransferFilesCommand) signalStop() error {
   714  	running, err := tdc.stateManager.Running()
   715  	if err != nil {
   716  		return err
   717  	}
   718  	if !running {
   719  		return errorutils.CheckErrorf("There is no active file transfer process.")
   720  	}
   721  
   722  	transferDir, err := coreutils.GetJfrogTransferDir()
   723  	if err != nil {
   724  		return err
   725  	}
   726  
   727  	exist, err := fileutils.IsFileExists(filepath.Join(transferDir, StopFileName), false)
   728  	if err != nil {
   729  		return err
   730  	}
   731  	if exist {
   732  		return errorutils.CheckErrorf("Graceful stop is already in progress. Please wait...")
   733  	}
   734  
   735  	if stopFile, err := os.Create(filepath.Join(transferDir, StopFileName)); err != nil {
   736  		return errorutils.CheckError(err)
   737  	} else if err = stopFile.Close(); err != nil {
   738  		return errorutils.CheckError(err)
   739  	}
   740  	log.Info("Gracefully stopping files transfer...")
   741  	return nil
   742  }
   743  
   744  func (tdc *TransferFilesCommand) shouldStop() bool {
   745  	return tdc.context.Err() != nil
   746  }
   747  
   748  func (tdc *TransferFilesCommand) verifySourceTargetConnectivity(srcUpService *srcUserPluginService) error {
   749  	log.Info("Verifying source to target Artifactory servers connectivity...")
   750  	targetAuth := createTargetAuth(tdc.targetServerDetails, tdc.proxyKey)
   751  	err := srcUpService.verifyConnectivityRequest(targetAuth)
   752  	if err == nil {
   753  		log.Info("Connectivity check passed!")
   754  	}
   755  	return err
   756  }
   757  
   758  func validateDataTransferPluginMinimumVersion(currentVersion string) error {
   759  	if strings.Contains(currentVersion, "SNAPSHOT") {
   760  		return nil
   761  	}
   762  	return clientutils.ValidateMinimumVersion(clientutils.DataTransfer, currentVersion, dataTransferPluginMinVersion)
   763  }
   764  
   765  // Verify connection to the source Artifactory instance, and that the user plugin is installed, responsive, and stands in the minimal version requirement.
   766  func getAndValidateDataTransferPlugin(srcUpService *srcUserPluginService) error {
   767  	verifyResponse, err := srcUpService.verifyCompatibilityRequest()
   768  	if err != nil {
   769  		errMsg := err.Error()
   770  		reason := ""
   771  		if strings.Contains(errMsg, "The execution name '") && strings.Contains(errMsg, "' could not be found") {
   772  			start := strings.Index(errMsg, "'")
   773  			missingApi := errMsg[start+1 : strings.Index(errMsg[start+1:], "'")+start+1]
   774  			reason = fmt.Sprintf(" This is because the '%s' API exposed by the plugin returns a '404 Not Found' response.", missingApi)
   775  		}
   776  		return errorutils.CheckErrorf("%s;\nIt looks like the 'data-transfer' user plugin isn't installed on the source instance."+
   777  			"%s Please refer to the documentation available at "+coreutils.JFrogHelpUrl+"jfrog-hosting-models-documentation/transfer-artifactory-configuration-and-files-to-jfrog-cloud for installation instructions",
   778  			errMsg, reason)
   779  	}
   780  
   781  	err = validateDataTransferPluginMinimumVersion(verifyResponse.Version)
   782  	if err != nil {
   783  		return err
   784  	}
   785  	log.Info("data-transfer plugin version: " + verifyResponse.Version)
   786  	return nil
   787  }
   788  
   789  // Loop on json files containing FilesErrors and collect them to one FilesErrors object.
   790  func parseErrorsFromLogFiles(logPaths []string) (allErrors FilesErrors, err error) {
   791  	for _, logPath := range logPaths {
   792  		var exists bool
   793  		exists, err = fileutils.IsFileExists(logPath, false)
   794  		if err != nil {
   795  			return
   796  		}
   797  		if !exists {
   798  			err = fmt.Errorf("log file: %s does not exist", logPath)
   799  			return
   800  		}
   801  		var content []byte
   802  		content, err = fileutils.ReadFile(logPath)
   803  		if err != nil {
   804  			return
   805  		}
   806  		fileErrors := new(FilesErrors)
   807  		err = errorutils.CheckError(json.Unmarshal(content, &fileErrors))
   808  		if err != nil {
   809  			return
   810  		}
   811  		allErrors.Errors = append(allErrors.Errors, fileErrors.Errors...)
   812  	}
   813  	return
   814  }
   815  
   816  func assertSupportedTransferDirStructure() error {
   817  	return state.VerifyTransferRunStatusVersion()
   818  }
   819  
   820  func doThreadDump() error {
   821  	log.Info("Starting thread dumping...")
   822  	threadDump, err := coreutils.NewProfiler().ThreadDump()
   823  	if err != nil {
   824  		return err
   825  	}
   826  	log.Info(threadDump)
   827  	log.Info("Thread dump ended successfully")
   828  	return nil
   829  }