github.com/jfrog/jfrog-cli-core/v2@v2.52.0/artifactory/utils/storageinfo.go (about)

     1  package utils
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/jfrog/gofrog/datastructures"
    11  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    12  	"github.com/jfrog/jfrog-client-go/artifactory"
    13  	"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    14  	clientUtils "github.com/jfrog/jfrog-client-go/utils"
    15  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    16  	"github.com/jfrog/jfrog-client-go/utils/io/httputils"
    17  )
    18  
    19  const (
    20  	serviceManagerRetriesPerRequest                  = 3
    21  	serviceManagerRetriesWaitPerRequestMilliSecs int = 1000
    22  	storageInfoRepoMissingError                      = "one or more of the requested repositories were not found"
    23  )
    24  
    25  var getRepoSummaryPollingTimeout = 10 * time.Minute
    26  var getRepoSummaryPollingInterval = 10 * time.Second
    27  
    28  type StorageInfoManager struct {
    29  	serviceManager artifactory.ArtifactoryServicesManager
    30  }
    31  
    32  func NewStorageInfoManager(ctx context.Context, serverDetails *config.ServerDetails) (*StorageInfoManager, error) {
    33  	serviceManager, err := CreateServiceManagerWithContext(ctx, serverDetails, false, 0, serviceManagerRetriesPerRequest, serviceManagerRetriesWaitPerRequestMilliSecs, time.Minute)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	return &StorageInfoManager{serviceManager: serviceManager}, nil
    38  }
    39  
    40  func (sim *StorageInfoManager) GetServiceManager() artifactory.ArtifactoryServicesManager {
    41  	return sim.serviceManager
    42  }
    43  
    44  // Start calculating storage info in Artifactory
    45  func (sim *StorageInfoManager) CalculateStorageInfo() error {
    46  	return sim.serviceManager.CalculateStorageInfo()
    47  }
    48  
    49  // Get storage info from Artifactory
    50  func (sim *StorageInfoManager) GetStorageInfo() (*utils.StorageInfo, error) {
    51  	return sim.serviceManager.GetStorageInfo()
    52  }
    53  
    54  // Get Service Id from Artifactory
    55  func (sim *StorageInfoManager) GetServiceId() (string, error) {
    56  	return sim.serviceManager.GetServiceId()
    57  }
    58  
    59  // Get repository summary from the storage info.
    60  // This method must be called after CalculateStorageInfo.
    61  // repoKey - The repository key
    62  func (sim *StorageInfoManager) GetRepoSummary(repoKey string) (*utils.RepositorySummary, error) {
    63  	var retVal *utils.RepositorySummary
    64  	pollingExecutor := &httputils.PollingExecutor{
    65  		Timeout:         getRepoSummaryPollingTimeout,
    66  		PollingInterval: getRepoSummaryPollingInterval,
    67  		MsgPrefix:       "Waiting for storage info calculation completion",
    68  		PollingAction: func() (shouldStop bool, responseBody []byte, err error) {
    69  			storageInfo, err := sim.GetStorageInfo()
    70  			if err != nil {
    71  				return true, []byte{}, err
    72  			}
    73  
    74  			for i, repoSummary := range storageInfo.RepositoriesSummaryList {
    75  				if repoSummary.RepoKey == repoKey {
    76  					retVal = &storageInfo.RepositoriesSummaryList[i]
    77  					return true, []byte{}, nil
    78  				}
    79  			}
    80  			return false, []byte{}, nil
    81  		},
    82  	}
    83  	_, err := pollingExecutor.Execute()
    84  	if retVal == nil && (err == nil || errors.As(err, &clientUtils.RetryExecutorTimeoutError{})) {
    85  		return nil, errorutils.CheckErrorf("could not find repository '%s' in the repositories summary", repoKey)
    86  	}
    87  	return retVal, err
    88  }
    89  
    90  // GetReposTotalSizeAndFiles gets the total size (bytes) and files of all passed repositories.
    91  // This method must be called after CalculateStorageInfo.
    92  // The result of this function might not be accurate!
    93  func (sim *StorageInfoManager) GetReposTotalSizeAndFiles(repoKeys ...string) (totalSize, totalFiles int64, err error) {
    94  	reposCounted := 0
    95  	reposSet := datastructures.MakeSet[string]()
    96  	for _, repoKey := range repoKeys {
    97  		reposSet.Add(repoKey)
    98  	}
    99  	pollingExecutor := &httputils.PollingExecutor{
   100  		Timeout:         getRepoSummaryPollingTimeout,
   101  		PollingInterval: getRepoSummaryPollingInterval,
   102  		MsgPrefix:       "Waiting for storage info calculation completion",
   103  		PollingAction: func() (shouldStop bool, responseBody []byte, err error) {
   104  			// Reset counters between polling attempts.
   105  			totalSize = 0
   106  			reposCounted = 0
   107  			totalFiles = 0
   108  
   109  			storageInfo, err := sim.GetStorageInfo()
   110  			if err != nil {
   111  				return true, nil, err
   112  			}
   113  			for i, repoSummary := range storageInfo.RepositoriesSummaryList {
   114  				if reposSet.Exists(repoSummary.RepoKey) {
   115  					reposCounted++
   116  					sizeToAdd, err := GetUsedSpaceInBytes(&storageInfo.RepositoriesSummaryList[i])
   117  					if err != nil {
   118  						return true, nil, err
   119  					}
   120  					totalSize += sizeToAdd
   121  
   122  					filesToAdd, err := GetFilesCountFromRepositorySummary(&storageInfo.RepositoriesSummaryList[i])
   123  					if err != nil {
   124  						return true, nil, err
   125  					}
   126  					totalFiles += filesToAdd
   127  				}
   128  			}
   129  			return reposCounted == len(repoKeys), nil, nil
   130  		},
   131  	}
   132  	_, err = pollingExecutor.Execute()
   133  	if reposCounted < len(repoKeys) && (err == nil || errors.As(err, &clientUtils.RetryExecutorTimeoutError{})) {
   134  		return totalSize, totalFiles, errorutils.CheckErrorf(storageInfoRepoMissingError)
   135  	}
   136  	return totalSize, totalFiles, err
   137  }
   138  
   139  func GetFilesCountFromRepositorySummary(repoSummary *utils.RepositorySummary) (int64, error) {
   140  	files, err := repoSummary.FilesCount.Int64()
   141  	return files, errorutils.CheckError(err)
   142  }
   143  
   144  func GetUsedSpaceInBytes(repoSummary *utils.RepositorySummary) (int64, error) {
   145  	if repoSummary.UsedSpaceInBytes.String() != "" {
   146  		size, err := repoSummary.UsedSpaceInBytes.Int64()
   147  		return size, errorutils.CheckError(err)
   148  	}
   149  
   150  	return convertStorageSizeStringToBytes(repoSummary.UsedSpace)
   151  }
   152  
   153  func convertStorageSizeStringToBytes(sizeStr string) (int64, error) {
   154  	usedSpaceParts := strings.Fields(sizeStr)
   155  	if len(usedSpaceParts) != 2 {
   156  		return 0, errorutils.CheckErrorf("could not parse size string '%s'", sizeStr)
   157  	}
   158  	// The ReplaceAll removes ',' from the number, for example: 1,004.64 -> 1004.64
   159  	sizeInUnit, err := strconv.ParseFloat(strings.ReplaceAll(usedSpaceParts[0], ",", ""), 64)
   160  	if err != nil {
   161  		return 0, errorutils.CheckError(err)
   162  	}
   163  	var sizeInBytes float64
   164  
   165  	switch usedSpaceParts[1] {
   166  	case "bytes":
   167  		sizeInBytes = sizeInUnit
   168  	case "KB":
   169  		sizeInBytes = sizeInUnit * float64(utils.SizeKib)
   170  	case "MB":
   171  		sizeInBytes = sizeInUnit * float64(utils.SizeMiB)
   172  	case "GB":
   173  		sizeInBytes = sizeInUnit * float64(utils.SizeGiB)
   174  	case "TB":
   175  		sizeInBytes = sizeInUnit * float64(utils.SizeTiB)
   176  	default:
   177  		return 0, errorutils.CheckErrorf("could not parse size string '%s'", sizeStr)
   178  	}
   179  	return int64(sizeInBytes), nil
   180  }