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 }