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

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