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 }