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 }