From 485ec6918053c01176a481aee1c61a2f650ee59b Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 00:52:03 +0530 Subject: [PATCH 01/13] added stats command --- utils/stats/stats.go | 597 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 utils/stats/stats.go diff --git a/utils/stats/stats.go b/utils/stats/stats.go new file mode 100644 index 000000000..c8672ba48 --- /dev/null +++ b/utils/stats/stats.go @@ -0,0 +1,597 @@ +package coreStats + +import ( + "encoding/json" + "fmt" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" + "github.com/jfrog/jfrog-client-go/utils/log" + clientStats "github.com/jfrog/jfrog-client-go/utils/stats" + "os" +) + +type ArtifactoryInfo struct { + BinariesSummary BinariesSummary `json:"binariesSummary"` + FileStoreSummary FileStoreSummary `json:"fileStoreSummary"` + RepositoriesSummaryList []RepositorySummary `json:"repositoriesSummaryList"` +} + +type BinariesSummary struct { + BinariesCount string `json:"binariesCount"` + BinariesSize string `json:"binariesSize"` + ArtifactsCount string `json:"artifactsCount"` + ArtifactsSize string `json:"artifactsSize"` +} + +type FileStoreSummary struct { + StorageType string `json:"storageType"` + StorageDirectory string `json:"storageDirectory"` +} + +type RepositorySummary struct { + RepoKey string `json:"repoKey"` + RepoType string `json:"repoType"` + PackageType string `json:"packageType"` +} + +type XrayPolicy struct { + Name string `json:"name"` + Type string `json:"type"` + Author string `json:"author"` + Rules []Rule `json:"rules"` + Created string `json:"created"` + Modified string `json:"modified"` +} + +type Rule struct { + Name string `json:"name"` + Priority int `json:"priority"` + Actions struct{} `json:"actions"` + Criteria Criteria `json:"criteria"` +} + +type Criteria struct { + MinSeverity string `json:"min_severity"` +} + +type XrayWatch struct { + GeneralData GeneralData `json:"general_data"` + ProjectResources ProjectResources `json:"project_resources"` +} + +type GeneralData struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type ProjectResources struct { + Resources []Resource `json:"resources"` +} + +type Resource struct { + Type string `json:"type"` + Name string `json:"name"` + BinMgrID string `json:"bin_mgr_id"` +} + +type Project struct { + DisplayName string `json:"display_name"` + Description string `json:"description"` + AdminPrivileges AdminPrivileges `json:"admin_privileges"` + ProjectKey string `json:"project_key"` +} + +type AdminPrivileges struct { + ManageMembers bool `json:"manage_members"` + ManageResources bool `json:"manage_resources"` + IndexResources bool `json:"index_resources"` +} + +type JPD struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"base_url"` + Status Status `json:"status"` + Local bool `json:"local"` + Services []Service `json:"services"` + Licenses []License `json:"licenses"` +} + +type Status struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type Service struct { + Type string `json:"type"` + Status Status `json:"status"` +} + +type License struct { + Type string `json:"type"` + Expired bool `json:"expired"` + LicensedTo string `json:"licensed_to"` +} + +type ReleaseBundleResponse struct { + ReleaseBundles []ReleaseBundleInfo `json:"release_bundles"` +} + +type ReleaseBundleInfo struct { + RepositoryKey string `json:"repository_key"` + ReleaseBundleName string `json:"release_bundle_name"` + ProjectKey string `json:"project_key"` +} + +type RepositoryDetails struct { + Key string `json:"key"` + Type string `json:"type"` + PackageType string `json:"packageType"` +} + +type GenericResultsWriter struct { + data interface{} + format string +} + +func NewGenericResultsWriter(data interface{}, format string) *GenericResultsWriter { + return &GenericResultsWriter{ + data: data, + format: format, + } +} + +type StatsFunc func(client *httpclient.HttpClient, sd *config.ServerDetails, hd httputils.HttpClientDetails) (interface{}, error) + +func getCommandMap() map[string]StatsFunc { + return map[string]StatsFunc{ + "rt": GetArtifactoryStats, + "rpr": GetRepositoriesStats, + "xrp": GetXrayPolicies, + "xrw": GetXrayWatches, + "pr": GetProjectsStats, + "rb": GetReleaseBundlesStats, + "jpd": GetJPDsStats, + } +} + +func GetStats(outputFormat string, product string, accessToken string) error { + serverDetails, err := config.GetDefaultServerConf() + if err != nil { + return err + } + + httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken} + if accessToken != "" { + httpClientDetails.AccessToken = accessToken + } + + httpClientDetails.SetContentTypeApplicationJson() + client, err := httpclient.ClientBuilder().Build() + if err != nil { + return err + } + + commandMap := getCommandMap() + + commandFunc, ok := commandMap[product] + + if product != "" { + if !ok { + err = fmt.Errorf("unknown product: %s", product) + return err + } else { + _ = GetStatsForProduct(commandFunc, product, outputFormat, client, serverDetails, httpClientDetails) + } + } else { + for productName, commandAPI := range commandMap { + _ = GetStatsForProduct(commandAPI, productName, outputFormat, client, serverDetails, httpClientDetails) + } + } + + return nil +} + +func GetStatsForProduct(commandAPI StatsFunc, productName string, outputFormat string, client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) error { + body, err := commandAPI(client, serverDetails, httpClientDetails) + if err != nil { + err = NewGenericResultsWriter(err, outputFormat).Print() + if err != nil { + log.Error(productName, " : ", err) + return err + } + } else { + err := NewGenericResultsWriter(body, outputFormat).Print() + if err != nil { + log.Error(productName, " : ", err) + return err + } + } + return nil +} + +func (rw *GenericResultsWriter) Print() error { + switch rw.format { + case "json", "simplejson": + return rw.printJson() + case "table": + return rw.printTable() + default: + return rw.printSimple() + } +} + +func (rw *GenericResultsWriter) printJson() error { + if rw.data == nil { + return nil + } + + jsonBytes, err := json.MarshalIndent(rw.data, "", " ") + if err != nil { + return err + } + result := string(jsonBytes) + if len(result) <= 2 { + msg := "" + switch rw.data.(type) { + case *ArtifactoryInfo: + msg = "Artifacts: No Artifacts Available" + break + case *[]RepositoryDetails: + msg = "Repositories: No Repository Available" + break + case *[]XrayPolicy: + msg = "Policies: No Xray Policy Available" + break + case *[]XrayWatch: + msg = "Watches: No Xray Watch Available" + break + case *[]Project: + msg = "Projects: No Project Available" + break + case *[]JPD: + msg = "JPDs: No JPD Available" + break + case *ReleaseBundleResponse: + msg = "Release Bundles: No Release Bundle Info Available" + break + } + jsonBytes, err = json.MarshalIndent(msg, "", " ") + if err != nil { + return err + } + result = string(jsonBytes) + } + fmt.Println(result) + return nil +} + +func (rw *GenericResultsWriter) printTable() error { + if rw.data == nil { + return nil + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetStyle(table.StyleLight) + + switch v := rw.data.(type) { + case *ArtifactoryInfo: + printArtifactoryStatsTable(t, v) + case *[]XrayPolicy: + printXrayPoliciesTable(t, v) + case *[]XrayWatch: + printXrayWatchesTable(t, v) + case *[]Project: + printProjectsTable(t, v) + case *[]JPD: + printJPDsTable(t, v) + case *ReleaseBundleResponse: + printReleaseBundlesTable(t, v) + case *[]RepositoryDetails: + printRepositoriesTable(t, v) + default: + if apiErr, ok := v.(*clientStats.APIError); ok { + printErrorTable(t, apiErr) + } else { + t.AppendRow(table.Row{"An unexpected error occurred: " + v.(error).Error()}) + } + } + t.Render() + return nil +} + +func printArtifactoryStatsTable(t table.Writer, stats *ArtifactoryInfo) { + t.AppendHeader(table.Row{"ARTIFACTS METRIC", "COUNT"}) + t.AppendRows([]table.Row{ + {"Total No of Artifacts", stats.BinariesSummary.ArtifactsCount}, + {"Total Binaries Size:", stats.BinariesSummary.BinariesSize}, + {"Total Storage Used: ", stats.BinariesSummary.ArtifactsSize}, + {"Storage Type: ", stats.FileStoreSummary.StorageType}, + }) + t.Render() + t.ResetRows() + t.ResetHeaders() +} + +func printXrayPoliciesTable(t table.Writer, policies *[]XrayPolicy) { + if len(*policies) == 0 { + t.AppendRow(table.Row{"No Policy Available"}) + return + } + t.AppendHeader(table.Row{"Policy Name", "Type", "Author"}) + for _, policy := range *policies { + t.AppendRow(table.Row{policy.Name, policy.Type, policy.Author}) + } +} + +func printXrayWatchesTable(t table.Writer, watches *[]XrayWatch) { + if len(*watches) == 0 { + t.AppendRow(table.Row{"No Watches Available"}) + return + } + t.AppendHeader(table.Row{"Watch Name"}) + for _, watch := range *watches { + t.AppendRow(table.Row{watch.GeneralData.Name}) + } +} + +func printProjectsTable(t table.Writer, projects *[]Project) { + if len(*projects) == 0 { + t.AppendRow(table.Row{"No Projects Available"}) + return + } + t.AppendHeader(table.Row{"Project Key", "Display Name"}) + for _, project := range *projects { + t.AppendRow(table.Row{project.ProjectKey, project.DisplayName}) + } +} + +func printJPDsTable(t table.Writer, jpdList *[]JPD) { + if len(*jpdList) == 0 { + t.AppendRow(table.Row{"No JPDs Available"}) + return + } + t.AppendHeader(table.Row{"JPD Name", "URL", "Status"}) + for _, jpd := range *jpdList { + t.AppendRow(table.Row{jpd.Name, jpd.URL, jpd.Status.Code}) + } +} + +func printErrorTable(t table.Writer, apiError *clientStats.APIError) { + t.AppendHeader(table.Row{"Product", "Status", "Text", "Suggestion"}) + t.AppendRow(table.Row{apiError.Product, apiError.StatusCode, apiError.StatusText, apiError.Suggestion}) +} + +func (rw *GenericResultsWriter) printSimple() error { + if rw.data == nil { + return nil + } + + switch v := rw.data.(type) { + case *ArtifactoryInfo: + printArtifactoryStats(v) + case *[]XrayPolicy: + printXrayPoliciesStats(v) + case *[]XrayWatch: + printXrayWatchesStats(v) + case *[]Project: + printProjectsStats(v) + case *[]JPD: + printJPDsStats(v) + case *ReleaseBundleResponse: + printReleaseBundlesSimple(v) + case *[]RepositoryDetails: + printRepositoriesSimple(v) + default: + printErrorMessage(rw.data.(*clientStats.APIError)) + } + + return nil +} + +func getRepositoryCounts(repos *[]RepositoryDetails) map[string]int { + counts := make(map[string]int) + for _, repo := range *repos { + counts[repo.Type]++ + } + return counts +} + +func printRepositoriesTable(t table.Writer, repos *[]RepositoryDetails) { + if len(*repos) == 0 { + t.AppendRow(table.Row{"No Repositories Available"}) + return + } + counts := getRepositoryCounts(repos) + t.AppendHeader(table.Row{"Repository Type", "Count"}) + for repoType, count := range counts { + t.AppendRow(table.Row{repoType, count}) + } +} + +func printRepositoriesSimple(repos *[]RepositoryDetails) { + log.Output("--- Repositories Summary by Type ---") + if len(*repos) == 0 { + log.Output("No Repositories Available") + return + } + counts := getRepositoryCounts(repos) + for repoType, count := range counts { + log.Output("- ", repoType, ": ", count) + } + log.Output() +} + +func printReleaseBundlesTable(t table.Writer, rbResponse *ReleaseBundleResponse) { + if len(rbResponse.ReleaseBundles) == 0 { + t.AppendRow(table.Row{"No Release Bundles Available"}) + return + } + t.AppendHeader(table.Row{"Release Bundle Name", "Project Key", "Repository Key"}) + for _, rb := range rbResponse.ReleaseBundles { + t.AppendRow(table.Row{rb.ReleaseBundleName, rb.ProjectKey, rb.RepositoryKey}) + } +} + +func printReleaseBundlesSimple(rbResponse *ReleaseBundleResponse) { + log.Output("--- Available Release Bundles ---") + if len(rbResponse.ReleaseBundles) == 0 { + log.Output("No Release Bundles Available") + return + } + for _, rb := range rbResponse.ReleaseBundles { + log.Output("- ", rb.ReleaseBundleName) + } + log.Output() +} + +func printArtifactoryStats(stats *ArtifactoryInfo) { + log.Output("--- Artifactory Statistics Summary ---") + log.Output("Total No of Artifacts: ", stats.BinariesSummary.ArtifactsCount) + log.Output("Total Binaries Size: ", stats.BinariesSummary.BinariesSize) + log.Output("Total Storage Used: ", stats.BinariesSummary.ArtifactsSize) + log.Output("Storage Type: ", stats.FileStoreSummary.StorageType) + log.Output() +} + +func printXrayPoliciesStats(policies *[]XrayPolicy) { + log.Output("--- Xray Policies ---") + for _, policy := range *policies { + log.Output("- ", policy.Name) + } + if len(*policies) == 0 { + log.Output("No Xray Policies Available") + } + log.Output() + return +} + +func printXrayWatchesStats(watches *[]XrayWatch) { + log.Output("--- Enforced Xray Watches ---") + for _, watch := range *watches { + log.Output("- ", watch.GeneralData.Name) + } + if len(*watches) == 0 { + log.Output("No Xray Watches Available") + } + log.Output() + return +} + +func printProjectsStats(projects *[]Project) { + log.Output("--- Available Projects ---") + for _, project := range *projects { + log.Output("- ", project.DisplayName) + } + if len(*projects) == 0 { + log.Output("No Projects Available") + } + log.Output() + return +} + +func printJPDsStats(jpdList *[]JPD) { + log.Output("--- Available JPDs ---") + for _, jpd := range *jpdList { + log.Output("- ", jpd.Name) + } + if len(*jpdList) == 0 { + log.Output("No JPDs Info Available") + } + log.Output() + return +} + +func printErrorMessage(apiError *clientStats.APIError) { + log.Output("---", apiError.Product, "---") + log.Output("StatusCode - ", apiError.StatusCode) + log.Output("StatusText - ", apiError.StatusText) + log.Output("Suggestion - ", apiError.Suggestion) +} + +func GetArtifactoryStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetArtifactoryStats(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var stats ArtifactoryInfo + if err := json.Unmarshal(body, &stats); err != nil { + return nil, fmt.Errorf("error parsing Artifactory JSON: %w", err) + } + return &stats, nil +} + +func GetRepositoriesStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetRepositoriesStats(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var repos []RepositoryDetails + if err := json.Unmarshal(body, &repos); err != nil { + return nil, fmt.Errorf("error parsing repositories JSON: %w", err) + } + return &repos, nil +} + +func GetXrayPolicies(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetXrayPolicies(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var policies []XrayPolicy + if err := json.Unmarshal(body, &policies); err != nil { + return nil, fmt.Errorf("error parsing policies JSON: %w", err) + } + return &policies, nil +} + +func GetXrayWatches(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetXrayWatches(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var watches []XrayWatch + if err := json.Unmarshal(body, &watches); err != nil { + return nil, fmt.Errorf("error parsing Watches JSON: %w", err) + } + return &watches, nil +} + +func GetProjectsStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetProjectsStats(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var projects []Project + if err := json.Unmarshal(body, &projects); err != nil { + return nil, fmt.Errorf("error parsing Projects JSON: %w", err) + } + return &projects, nil +} + +func GetJPDsStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetJPDsStats(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var jpdList []JPD + if err := json.Unmarshal(body, &jpdList); err != nil { + return nil, fmt.Errorf("error parsing JPDs JSON: %w", err) + } + return &jpdList, nil +} + +func GetReleaseBundlesStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetReleaseBundlesStats(client, serverDetails, httpClientDetails) + if err != nil { + return nil, err + } + var releaseBundles ReleaseBundleResponse + if err := json.Unmarshal(body, &releaseBundles); err != nil { + return nil, fmt.Errorf("error parsing ReleaseBundles JSON: %w", err) + } + return &releaseBundles, nil +} From ba78f575b410b7113e6932d14792da8565d31afd Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 00:58:34 +0530 Subject: [PATCH 02/13] Temp go.mod and go.sum file --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 74300134f..30afa3e8c 100644 --- a/go.mod +++ b/go.mod @@ -112,7 +112,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20250717041744-d3ea4d99f4e7 +replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20250611113558-c1a092f216fd diff --git a/go.sum b/go.sum index ff2d868d8..c543c0f5c 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,6 @@ github.com/jfrog/build-info-go v1.10.17 h1:wnVd9KkyFGQgNL+oU1wXyJB7/Ui9O/MqUnNKU github.com/jfrog/build-info-go v1.10.17/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= -github.com/jfrog/jfrog-client-go v1.54.7 h1:S1geo9T5ZCAb7EkXSv+NJ0K8+yhDsxlrybHTosCilIg= -github.com/jfrog/jfrog-client-go v1.54.7/go.mod h1:cOy7Pn34bGtjp0eWHADTRJG5Er0qVnJIz04u+NGEpcQ= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -164,6 +162,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f h1:hIpX+BvmNqysbDO1YdbjOTU3rr14QTz1Ev3qyxTpcJ0= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f/go.mod h1:IwGSt21D6sbJVptrX8SxZNTthPXe+KlaIRRtv80Pqp8= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= From 44afeb03fef72d36140c12b59dd110cb00193136 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 12:31:24 +0530 Subject: [PATCH 03/13] Temp go.mod and go.sum file --- go.mod | 3 ++- go.sum | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 30afa3e8c..5006d413d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jfrog/jfrog-cli-core/v2 +module github.com/naveenku-jfrog/jfrog-cli-core go 1.24.6 @@ -15,6 +15,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.6.8 github.com/jfrog/build-info-go v1.10.17 github.com/jfrog/gofrog v1.7.6 + github.com/jfrog/jfrog-cli-core/v2 v2.59.7 github.com/jfrog/jfrog-client-go v1.54.7 github.com/magiconair/properties v1.8.10 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index c543c0f5c..7fa9c149c 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/jfrog/build-info-go v1.10.17 h1:wnVd9KkyFGQgNL+oU1wXyJB7/Ui9O/MqUnNKU github.com/jfrog/build-info-go v1.10.17/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= +github.com/jfrog/jfrog-cli-core/v2 v2.59.7 h1:qn4HVqP5L9GQ5py0ZeJzXkc3UXq4cHT20AJnwcco7ys= +github.com/jfrog/jfrog-cli-core/v2 v2.59.7/go.mod h1:W6o+7kLLy7GEWhCN9I2vzjGRGeeOZzsJ0uMaKCyr1CI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= From 3e0785016fdf046200793a132bbea84ed327022c Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 12:40:40 +0530 Subject: [PATCH 04/13] Temp go.mod and go.sum file --- go.mod | 3 +-- go.sum | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 5006d413d..30afa3e8c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/naveenku-jfrog/jfrog-cli-core +module github.com/jfrog/jfrog-cli-core/v2 go 1.24.6 @@ -15,7 +15,6 @@ require ( github.com/jedib0t/go-pretty/v6 v6.6.8 github.com/jfrog/build-info-go v1.10.17 github.com/jfrog/gofrog v1.7.6 - github.com/jfrog/jfrog-cli-core/v2 v2.59.7 github.com/jfrog/jfrog-client-go v1.54.7 github.com/magiconair/properties v1.8.10 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index 7fa9c149c..c543c0f5c 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,6 @@ github.com/jfrog/build-info-go v1.10.17 h1:wnVd9KkyFGQgNL+oU1wXyJB7/Ui9O/MqUnNKU github.com/jfrog/build-info-go v1.10.17/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= -github.com/jfrog/jfrog-cli-core/v2 v2.59.7 h1:qn4HVqP5L9GQ5py0ZeJzXkc3UXq4cHT20AJnwcco7ys= -github.com/jfrog/jfrog-cli-core/v2 v2.59.7/go.mod h1:W6o+7kLLy7GEWhCN9I2vzjGRGeeOZzsJ0uMaKCyr1CI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= From 6b1305a7aa1072f66eae76487c107e27f15097f1 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 13:08:43 +0530 Subject: [PATCH 05/13] Temp go.mod and go.sum file --- go.mod | 3 ++- go.sum | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 30afa3e8c..740116501 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/jfrog/jfrog-cli-core/v2 +module github.com/naveenku-jfrog/jfrog-cli-core/v2 go 1.24.6 @@ -15,6 +15,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.6.8 github.com/jfrog/build-info-go v1.10.17 github.com/jfrog/gofrog v1.7.6 + github.com/jfrog/jfrog-cli-core/v2 v2.59.7 github.com/jfrog/jfrog-client-go v1.54.7 github.com/magiconair/properties v1.8.10 github.com/manifoldco/promptui v0.9.0 diff --git a/go.sum b/go.sum index c543c0f5c..7fa9c149c 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/jfrog/build-info-go v1.10.17 h1:wnVd9KkyFGQgNL+oU1wXyJB7/Ui9O/MqUnNKU github.com/jfrog/build-info-go v1.10.17/go.mod h1:szdz9+WzB7+7PGnILLUgyY+OF5qD5geBT7UGNIxibyw= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= +github.com/jfrog/jfrog-cli-core/v2 v2.59.7 h1:qn4HVqP5L9GQ5py0ZeJzXkc3UXq4cHT20AJnwcco7ys= +github.com/jfrog/jfrog-cli-core/v2 v2.59.7/go.mod h1:W6o+7kLLy7GEWhCN9I2vzjGRGeeOZzsJ0uMaKCyr1CI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= From ada0c90f616e885b92338f90f2bf70dec34c5555 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 13:29:27 +0530 Subject: [PATCH 06/13] Addressing lint warnings --- utils/stats/stats.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index c8672ba48..225521ed7 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -238,25 +238,18 @@ func (rw *GenericResultsWriter) printJson() error { switch rw.data.(type) { case *ArtifactoryInfo: msg = "Artifacts: No Artifacts Available" - break case *[]RepositoryDetails: msg = "Repositories: No Repository Available" - break case *[]XrayPolicy: msg = "Policies: No Xray Policy Available" - break case *[]XrayWatch: msg = "Watches: No Xray Watch Available" - break case *[]Project: msg = "Projects: No Project Available" - break case *[]JPD: msg = "JPDs: No JPD Available" - break case *ReleaseBundleResponse: msg = "Release Bundles: No Release Bundle Info Available" - break } jsonBytes, err = json.MarshalIndent(msg, "", " ") if err != nil { @@ -296,7 +289,7 @@ func (rw *GenericResultsWriter) printTable() error { if apiErr, ok := v.(*clientStats.APIError); ok { printErrorTable(t, apiErr) } else { - t.AppendRow(table.Row{"An unexpected error occurred: " + v.(error).Error()}) + log.Warn("Table format is not supported for this unknown data type.") } } t.Render() @@ -386,7 +379,11 @@ func (rw *GenericResultsWriter) printSimple() error { case *[]RepositoryDetails: printRepositoriesSimple(v) default: - printErrorMessage(rw.data.(*clientStats.APIError)) + if apiErr, ok := rw.data.(*clientStats.APIError); ok { + printErrorMessage(apiErr) + } else { + log.Warn("An unexpected data type was received and cannot be printed as a detailed error.") + } } return nil @@ -466,7 +463,6 @@ func printXrayPoliciesStats(policies *[]XrayPolicy) { log.Output("No Xray Policies Available") } log.Output() - return } func printXrayWatchesStats(watches *[]XrayWatch) { @@ -478,7 +474,6 @@ func printXrayWatchesStats(watches *[]XrayWatch) { log.Output("No Xray Watches Available") } log.Output() - return } func printProjectsStats(projects *[]Project) { @@ -490,7 +485,6 @@ func printProjectsStats(projects *[]Project) { log.Output("No Projects Available") } log.Output() - return } func printJPDsStats(jpdList *[]JPD) { @@ -502,7 +496,6 @@ func printJPDsStats(jpdList *[]JPD) { log.Output("No JPDs Info Available") } log.Output() - return } func printErrorMessage(apiError *clientStats.APIError) { From 720bad6c3db3144e0de3c8dae8e9ee90a2cea151 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 19:47:51 +0530 Subject: [PATCH 07/13] Improving Console text messages --- utils/stats/stats.go | 81 ++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 225521ed7..ca455f779 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -268,7 +268,7 @@ func (rw *GenericResultsWriter) printTable() error { t := table.NewWriter() t.SetOutputMirror(os.Stdout) - t.SetStyle(table.StyleLight) + t.SetStyle(table.StyleDouble) switch v := rw.data.(type) { case *ArtifactoryInfo: @@ -293,6 +293,7 @@ func (rw *GenericResultsWriter) printTable() error { } } t.Render() + log.Output() return nil } @@ -413,6 +414,7 @@ func printRepositoriesSimple(repos *[]RepositoryDetails) { log.Output("--- Repositories Summary by Type ---") if len(*repos) == 0 { log.Output("No Repositories Available") + log.Output() return } counts := getRepositoryCounts(repos) @@ -437,65 +439,100 @@ func printReleaseBundlesSimple(rbResponse *ReleaseBundleResponse) { log.Output("--- Available Release Bundles ---") if len(rbResponse.ReleaseBundles) == 0 { log.Output("No Release Bundles Available") + log.Output() return } - for _, rb := range rbResponse.ReleaseBundles { - log.Output("- ", rb.ReleaseBundleName) + for index, rb := range rbResponse.ReleaseBundles { + log.Output("ReleaseBundle: ", index+1) + log.Output("ReleaseBundleName: ", rb.ReleaseBundleName) + log.Output("RepositoryKey: ", rb.RepositoryKey) + log.Output("ProjectKey:", rb.ProjectKey) + log.Output() } - log.Output() } func printArtifactoryStats(stats *ArtifactoryInfo) { log.Output("--- Artifactory Statistics Summary ---") - log.Output("Total No of Artifacts: ", stats.BinariesSummary.ArtifactsCount) + log.Output("Total No of Binaries: ", stats.BinariesSummary.BinariesCount) log.Output("Total Binaries Size: ", stats.BinariesSummary.BinariesSize) - log.Output("Total Storage Used: ", stats.BinariesSummary.ArtifactsSize) + log.Output("Total No of Artifacts: ", stats.BinariesSummary.ArtifactsCount) + log.Output("Total Artifacts Size: ", stats.BinariesSummary.ArtifactsSize) log.Output("Storage Type: ", stats.FileStoreSummary.StorageType) log.Output() } func printXrayPoliciesStats(policies *[]XrayPolicy) { log.Output("--- Xray Policies ---") - for _, policy := range *policies { - log.Output("- ", policy.Name) - } if len(*policies) == 0 { log.Output("No Xray Policies Available") + log.Output() + return + } + for index, policy := range *policies { + log.Output("Policy: ", index+1) + log.Output("Name: ", policy.Name) + log.Output("Type: ", policy.Type) + log.Output("Author: ", policy.Author) + log.Output("Created: ", policy.Created) + log.Output("Modified: ", policy.Modified) + log.Output() } - log.Output() } func printXrayWatchesStats(watches *[]XrayWatch) { log.Output("--- Enforced Xray Watches ---") - for _, watch := range *watches { - log.Output("- ", watch.GeneralData.Name) - } if len(*watches) == 0 { log.Output("No Xray Watches Available") + log.Output() + return + } + for _, watch := range *watches { + log.Output("Name: ", watch.GeneralData.Name) + for _, resource := range watch.ProjectResources.Resources { + log.Output("Name:", resource.Name) + log.Output("Type:", resource.Type) + log.Output("BinMgrID:", resource.BinMgrID) + } + log.Output("") } - log.Output() } func printProjectsStats(projects *[]Project) { log.Output("--- Available Projects ---") - for _, project := range *projects { - log.Output("- ", project.DisplayName) - } if len(*projects) == 0 { log.Output("No Projects Available") + log.Output() + return + } + for index, project := range *projects { + log.Output("Project: ", index+1) + log.Output("Name: ", project.DisplayName) + log.Output("Key: ", project.ProjectKey) + if project.Description != "" { + log.Output("Description: ", project.Description) + } else { + log.Output("Description: NA") + } + log.Output() } - log.Output() } func printJPDsStats(jpdList *[]JPD) { log.Output("--- Available JPDs ---") - for _, jpd := range *jpdList { - log.Output("- ", jpd.Name) - } if len(*jpdList) == 0 { log.Output("No JPDs Info Available") + log.Output() + return + } + for index, jpd := range *jpdList { + log.Output("JPD: ", index+1) + log.Output("Name: ", jpd.Name) + log.Output("URL: ", jpd.URL) + log.Output("Status: ", jpd.Status.Code) + log.Output("Detailed Status: ", jpd.Status.Message) + log.Output("Local: ", jpd.Local) + log.Output() } - log.Output() } func printErrorMessage(apiError *clientStats.APIError) { From 9112888ab696dd94106b525ba7471b8035b03736 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 20:32:35 +0530 Subject: [PATCH 08/13] Removing dependency of jfrog-client-go on this core repo --- go.mod | 2 +- go.sum | 4 ++-- utils/stats/stats.go | 40 +++++++++++++++++++++------------------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 740116501..acbdaa7d3 100644 --- a/go.mod +++ b/go.mod @@ -113,7 +113,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f +replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20250611113558-c1a092f216fd diff --git a/go.sum b/go.sum index 7fa9c149c..e49e394c6 100644 --- a/go.sum +++ b/go.sum @@ -164,8 +164,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f h1:hIpX+BvmNqysbDO1YdbjOTU3rr14QTz1Ev3qyxTpcJ0= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250913192322-c923c7327b4f/go.mod h1:IwGSt21D6sbJVptrX8SxZNTthPXe+KlaIRRtv80Pqp8= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716 h1:5SKtAYuGvLVoNd40w5aXXBHSZHNVXiAQkGnYQFRN4Lg= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716/go.mod h1:IwGSt21D6sbJVptrX8SxZNTthPXe+KlaIRRtv80Pqp8= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= diff --git a/utils/stats/stats.go b/utils/stats/stats.go index ca455f779..7a73bbee7 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -143,7 +143,7 @@ func NewGenericResultsWriter(data interface{}, format string) *GenericResultsWri } } -type StatsFunc func(client *httpclient.HttpClient, sd *config.ServerDetails, hd httputils.HttpClientDetails) (interface{}, error) +type StatsFunc func(client *httpclient.HttpClient, artifactoryUrl string, hd httputils.HttpClientDetails) (interface{}, error) func getCommandMap() map[string]StatsFunc { return map[string]StatsFunc{ @@ -178,24 +178,26 @@ func GetStats(outputFormat string, product string, accessToken string) error { commandFunc, ok := commandMap[product] + serverUrl := serverDetails.GetUrl() + if product != "" { if !ok { err = fmt.Errorf("unknown product: %s", product) return err } else { - _ = GetStatsForProduct(commandFunc, product, outputFormat, client, serverDetails, httpClientDetails) + _ = GetStatsForProduct(commandFunc, product, outputFormat, client, serverUrl, httpClientDetails) } } else { for productName, commandAPI := range commandMap { - _ = GetStatsForProduct(commandAPI, productName, outputFormat, client, serverDetails, httpClientDetails) + _ = GetStatsForProduct(commandAPI, productName, outputFormat, client, serverUrl, httpClientDetails) } } return nil } -func GetStatsForProduct(commandAPI StatsFunc, productName string, outputFormat string, client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) error { - body, err := commandAPI(client, serverDetails, httpClientDetails) +func GetStatsForProduct(commandAPI StatsFunc, productName string, outputFormat string, client *httpclient.HttpClient, artifactoryUrl string, httpClientDetails httputils.HttpClientDetails) error { + body, err := commandAPI(client, artifactoryUrl, httpClientDetails) if err != nil { err = NewGenericResultsWriter(err, outputFormat).Print() if err != nil { @@ -542,8 +544,8 @@ func printErrorMessage(apiError *clientStats.APIError) { log.Output("Suggestion - ", apiError.Suggestion) } -func GetArtifactoryStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetArtifactoryStats(client, serverDetails, httpClientDetails) +func GetArtifactoryStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetArtifactoryStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -554,8 +556,8 @@ func GetArtifactoryStats(client *httpclient.HttpClient, serverDetails *config.Se return &stats, nil } -func GetRepositoriesStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetRepositoriesStats(client, serverDetails, httpClientDetails) +func GetRepositoriesStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetRepositoriesStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -566,8 +568,8 @@ func GetRepositoriesStats(client *httpclient.HttpClient, serverDetails *config.S return &repos, nil } -func GetXrayPolicies(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetXrayPolicies(client, serverDetails, httpClientDetails) +func GetXrayPolicies(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetXrayPolicies(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -578,8 +580,8 @@ func GetXrayPolicies(client *httpclient.HttpClient, serverDetails *config.Server return &policies, nil } -func GetXrayWatches(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetXrayWatches(client, serverDetails, httpClientDetails) +func GetXrayWatches(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetXrayWatches(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -590,8 +592,8 @@ func GetXrayWatches(client *httpclient.HttpClient, serverDetails *config.ServerD return &watches, nil } -func GetProjectsStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetProjectsStats(client, serverDetails, httpClientDetails) +func GetProjectsStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetProjectsStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -602,8 +604,8 @@ func GetProjectsStats(client *httpclient.HttpClient, serverDetails *config.Serve return &projects, nil } -func GetJPDsStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetJPDsStats(client, serverDetails, httpClientDetails) +func GetJPDsStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetJPDsStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } @@ -614,8 +616,8 @@ func GetJPDsStats(client *httpclient.HttpClient, serverDetails *config.ServerDet return &jpdList, nil } -func GetReleaseBundlesStats(client *httpclient.HttpClient, serverDetails *config.ServerDetails, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetReleaseBundlesStats(client, serverDetails, httpClientDetails) +func GetReleaseBundlesStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { + body, err := clientStats.GetReleaseBundlesStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } From 73461b2631bcb7a314b6675bf965fa04d117d88f Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 14 Sep 2025 23:16:33 +0530 Subject: [PATCH 09/13] Added Unit Tests for all products API --- utils/stats/stats_test.go | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 utils/stats/stats_test.go diff --git a/utils/stats/stats_test.go b/utils/stats/stats_test.go new file mode 100644 index 000000000..f014d0d4d --- /dev/null +++ b/utils/stats/stats_test.go @@ -0,0 +1,48 @@ +package coreStats + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "testing" + + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" + clientStats "github.com/jfrog/jfrog-client-go/utils/stats" + "github.com/stretchr/testify/assert" +) + +type statsTestFunc func(client *httpclient.HttpClient, artifactoryUrl string, hd httputils.HttpClientDetails) ([]byte, error) + +func setupTestClient(t *testing.T) (*httpclient.HttpClient, httputils.HttpClientDetails, string) { + serverDetails, err := config.GetDefaultServerConf() + assert.NoError(t, err) + + httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken} + httpClientDetails.SetContentTypeApplicationJson() + client, err := httpclient.ClientBuilder().Build() + assert.NoError(t, err) + return client, httpClientDetails, serverDetails.GetUrl() +} + +func TestAllProductAPIs(t *testing.T) { + productFunctions := map[string]statsTestFunc{ + "Artifactory": clientStats.GetArtifactoryStats, + "Repositories": clientStats.GetRepositoriesStats, + "XrayPolicies": clientStats.GetXrayPolicies, + "XrayWatches": clientStats.GetXrayWatches, + "Projects": clientStats.GetProjectsStats, + "JPDs": clientStats.GetJPDsStats, + "ReleaseBundles": clientStats.GetReleaseBundlesStats, + } + + for product, getFunc := range productFunctions { + t.Run(product, func(t *testing.T) { + t.Run(product, func(t *testing.T) { + client, httpClientDetails, baseUrl := setupTestClient(t) + _, err := getFunc(client, baseUrl, httpClientDetails) + if err != nil { + assert.NoError(t, err) + } + }) + }) + } +} From 7f4aca28ee5a989cac8e52c55c274cc986daf089 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 15 Sep 2025 01:32:26 +0530 Subject: [PATCH 10/13] Added Admin validation and unit tests --- go.mod | 4 +- go.sum | 4 +- utils/stats/stats.go | 24 +++++++++-- utils/stats/stats_test.go | 87 +++++++++++++++++++++++++++++++++++---- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index acbdaa7d3..f3a8e0b46 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/chzyer/readline v1.5.1 github.com/forPelevin/gomoji v1.3.1 github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/google/uuid v1.6.0 github.com/gookit/color v1.6.0 github.com/jedib0t/go-pretty/v6 v6.6.8 @@ -55,7 +56,6 @@ require ( github.com/go-logr/zapr v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -113,7 +113,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716 +replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20250611113558-c1a092f216fd diff --git a/go.sum b/go.sum index e49e394c6..84f82485f 100644 --- a/go.sum +++ b/go.sum @@ -164,8 +164,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716 h1:5SKtAYuGvLVoNd40w5aXXBHSZHNVXiAQkGnYQFRN4Lg= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914145954-f52481d08716/go.mod h1:IwGSt21D6sbJVptrX8SxZNTthPXe+KlaIRRtv80Pqp8= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4 h1:vCNBQI2bdIN9T5gFXXM4PWTfBgnV5AZ22HhtqIHcng4= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4/go.mod h1:9/MZzdo3SYYwhXrWPc6O8f75nL9kKyoNaF1ti0Q0nmM= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 7a73bbee7..13e628684 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -10,6 +10,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" clientStats "github.com/jfrog/jfrog-client-go/utils/stats" "os" + "os/exec" ) type ArtifactoryInfo struct { @@ -157,8 +158,17 @@ func getCommandMap() map[string]StatsFunc { } } -func GetStats(outputFormat string, product string, accessToken string) error { - serverDetails, err := config.GetDefaultServerConf() +func RunJfrogPing(serverID string) error { + cmd := exec.Command("jf", "rt", "ping", "--server-id="+serverID) + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("command 'jf rt ping' failed: %w\nOutput: %s", err, string(output)) + } + return nil +} + +func GetStats(outputFormat string, product string, accessToken string, serverId string) error { + serverDetails, err := config.GetSpecificConfig(serverId, true, false) if err != nil { return err } @@ -180,6 +190,15 @@ func GetStats(outputFormat string, product string, accessToken string) error { serverUrl := serverDetails.GetUrl() + if httpClientDetails.AccessToken == "" { + _ = RunJfrogPing(serverDetails.ServerId) + } + + serverDetails, err = config.GetSpecificConfig(serverId, true, false) + if err != nil { + return err + } + if product != "" { if !ok { err = fmt.Errorf("unknown product: %s", product) @@ -388,7 +407,6 @@ func (rw *GenericResultsWriter) printSimple() error { log.Warn("An unexpected data type was received and cannot be printed as a detailed error.") } } - return nil } diff --git a/utils/stats/stats_test.go b/utils/stats/stats_test.go index f014d0d4d..fdcbfef1d 100644 --- a/utils/stats/stats_test.go +++ b/utils/stats/stats_test.go @@ -1,6 +1,8 @@ package coreStats import ( + "fmt" + "github.com/golang-jwt/jwt/v4" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "testing" @@ -12,15 +14,67 @@ import ( type statsTestFunc func(client *httpclient.HttpClient, artifactoryUrl string, hd httputils.HttpClientDetails) ([]byte, error) -func setupTestClient(t *testing.T) (*httpclient.HttpClient, httputils.HttpClientDetails, string) { +func GetTokenID(tokenString string) (string, error) { + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return "", fmt.Errorf("failed to parse token: %w", err) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return "", fmt.Errorf("failed to read token claims") + } + + tokenID, ok := claims["jti"].(string) + if !ok { + return "", fmt.Errorf("token does not contain a valid 'jti' (Token ID) claim") + } + + return tokenID, nil +} + +func IsAdminToken(client *httpclient.HttpClient, baseUrl string, tokenString string, httpClientDetails httputils.HttpClientDetails) bool { + tokenID, err := GetTokenID(tokenString) + if err != nil { + return false + } + isAdmin, err := clientStats.IsAdminToken(client, baseUrl, tokenID, httpClientDetails) + if err != nil { + return false + } + return isAdmin +} + +func setupTestClient(t *testing.T) (*httpclient.HttpClient, httputils.HttpClientDetails, *config.ServerDetails, bool) { serverDetails, err := config.GetDefaultServerConf() - assert.NoError(t, err) + if err != nil { + assert.NoError(t, err) + } httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken} + httpClientDetails.SetContentTypeApplicationJson() client, err := httpclient.ClientBuilder().Build() + if err != nil { + assert.NoError(t, err) + } + + serverUrl := serverDetails.GetUrl() + + if httpClientDetails.AccessToken == "" { + _ = RunJfrogPing(serverDetails.ServerId) + } + + serverDetails, err = config.GetDefaultServerConf() + if err != nil { + assert.NoError(t, err) + } + + isAdminToken := IsAdminToken(client, serverUrl, serverDetails.AccessToken, httpClientDetails) + + fmt.Println("Admin token : ", isAdminToken) assert.NoError(t, err) - return client, httpClientDetails, serverDetails.GetUrl() + return client, httpClientDetails, serverDetails, isAdminToken } func TestAllProductAPIs(t *testing.T) { @@ -33,14 +87,31 @@ func TestAllProductAPIs(t *testing.T) { "JPDs": clientStats.GetJPDsStats, "ReleaseBundles": clientStats.GetReleaseBundlesStats, } - + testCasesMap := map[string]bool{ + "Artifactory": false, + "Repositories": false, + "XrayPolicies": true, + "XrayWatches": true, + "Projects": true, + "JPDs": true, + "ReleaseBundles": false, + } for product, getFunc := range productFunctions { t.Run(product, func(t *testing.T) { t.Run(product, func(t *testing.T) { - client, httpClientDetails, baseUrl := setupTestClient(t) - _, err := getFunc(client, baseUrl, httpClientDetails) - if err != nil { - assert.NoError(t, err) + client, httpClientDetails, server, isAdmin := setupTestClient(t) + if isAdmin { + _, err := getFunc(client, server.GetUrl(), httpClientDetails) + if err != nil { + assert.NoError(t, err) + } + } else { + _, err := getFunc(client, server.GetUrl(), httpClientDetails) + if err != nil { + if !testCasesMap[product] { + assert.NoError(t, err) + } + } } }) }) From 5faddd7f72370f01a08b6ef975eeb412830769a9 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 15 Sep 2025 01:33:32 +0530 Subject: [PATCH 11/13] Added Admin validation and unit tests --- utils/stats/stats_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/stats/stats_test.go b/utils/stats/stats_test.go index fdcbfef1d..85fc7f8b1 100644 --- a/utils/stats/stats_test.go +++ b/utils/stats/stats_test.go @@ -72,7 +72,6 @@ func setupTestClient(t *testing.T) (*httpclient.HttpClient, httputils.HttpClient isAdminToken := IsAdminToken(client, serverUrl, serverDetails.AccessToken, httpClientDetails) - fmt.Println("Admin token : ", isAdminToken) assert.NoError(t, err) return client, httpClientDetails, serverDetails, isAdminToken } From 8c22b05929293c49d7421b4708b308fc4d700187 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 15 Sep 2025 01:40:30 +0530 Subject: [PATCH 12/13] Added Admin validation and unit tests --- utils/stats/stats.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 13e628684..5a5093ebc 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -198,6 +198,7 @@ func GetStats(outputFormat string, product string, accessToken string, serverId if err != nil { return err } + httpClientDetails.AccessToken = serverDetails.AccessToken if product != "" { if !ok { From 3b51904b9be09304493816609e292a3c1a7e391f Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 15 Sep 2025 21:21:34 +0530 Subject: [PATCH 13/13] Removed Xray stats changes and moved test to jfrog-cli --- go.mod | 18 +- go.sum | 92 ++++++- utils/stats/stats.go | 510 ++++++++++++++++++++------------------ utils/stats/stats_test.go | 118 --------- 4 files changed, 355 insertions(+), 383 deletions(-) delete mode 100644 utils/stats/stats_test.go diff --git a/go.mod b/go.mod index f3a8e0b46..8458c3c4f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/chzyer/readline v1.5.1 github.com/forPelevin/gomoji v1.3.1 github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 - github.com/golang-jwt/jwt/v4 v4.5.2 github.com/google/uuid v1.6.0 github.com/gookit/color v1.6.0 github.com/jedib0t/go-pretty/v6 v6.6.8 @@ -21,18 +20,22 @@ require ( github.com/magiconair/properties v1.8.10 github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c + github.com/pterm/pterm v0.12.81 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.11.1 github.com/urfave/cli v1.22.17 github.com/vbauerster/mpb/v8 v8.10.2 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 - golang.org/x/sync v0.15.0 - golang.org/x/term v0.32.0 - golang.org/x/text v0.26.0 + golang.org/x/sync v0.17.0 + golang.org/x/term v0.35.0 + golang.org/x/text v0.29.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + atomicgo.dev/cursor v0.2.0 // indirect + atomicgo.dev/keyboard v0.2.9 // indirect + atomicgo.dev/schedule v0.1.0 // indirect dario.cat/mergo v1.0.2 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/CycloneDX/cyclonedx-go v0.9.2 // indirect @@ -42,6 +45,7 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect + github.com/containerd/console v1.0.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -56,6 +60,7 @@ require ( github.com/go-logr/zapr v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -66,6 +71,7 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -99,7 +105,7 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sys v0.36.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -113,7 +119,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4 +replace github.com/jfrog/jfrog-client-go => github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250915154721-774db72c9f8c // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20250611113558-c1a092f216fd diff --git a/go.sum b/go.sum index 84f82485f..827df5638 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,26 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= +atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= +atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= +atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= +atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo= github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -21,6 +38,7 @@ github.com/apache/camel-k/v2 v2.7.0 h1:QXg5tbUbnv8CYdAJi+RbNAyxZhylkEZF5oHh6nghS github.com/apache/camel-k/v2 v2.7.0/go.mod h1:2q7XQaYLF0WNqCmghe03P9jc/RlFLGwqgrf19EUpnsc= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -38,6 +56,9 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= @@ -102,6 +123,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -126,6 +149,9 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= @@ -137,6 +163,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= @@ -153,6 +181,7 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= @@ -164,8 +193,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4 h1:vCNBQI2bdIN9T5gFXXM4PWTfBgnV5AZ22HhtqIHcng4= -github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250914195938-7cc50dab62f4/go.mod h1:9/MZzdo3SYYwhXrWPc6O8f75nL9kKyoNaF1ti0Q0nmM= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250915154721-774db72c9f8c h1:eclR3Bn3q+EKrV5p55DaezhFTVGjSws3BEDyNxWbM/o= +github.com/naveenku-jfrog/jfrog-client-go v1.54.2-0.20250915154721-774db72c9f8c/go.mod h1:cOy7Pn34bGtjp0eWHADTRJG5Er0qVnJIz04u+NGEpcQ= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= @@ -187,6 +216,15 @@ github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiK github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA= +github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -196,6 +234,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -220,6 +259,8 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -250,12 +291,14 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -265,6 +308,7 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= @@ -272,18 +316,25 @@ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -298,28 +349,43 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -333,9 +399,11 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 5a5093ebc..a29a444b3 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -3,20 +3,23 @@ package coreStats import ( "encoding/json" "fmt" - "github.com/jedib0t/go-pretty/v6/table" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/http/httpclient" "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" clientStats "github.com/jfrog/jfrog-client-go/utils/stats" - "os" - "os/exec" + "github.com/pterm/pterm" + "strings" ) +const displayLimit = 5 + type ArtifactoryInfo struct { BinariesSummary BinariesSummary `json:"binariesSummary"` FileStoreSummary FileStoreSummary `json:"fileStoreSummary"` RepositoriesSummaryList []RepositorySummary `json:"repositoriesSummaryList"` + ProjectsCount int `json:"-"` + RepositoriesDetails []RepositoryDetails `json:"-"` } type BinariesSummary struct { @@ -148,32 +151,27 @@ type StatsFunc func(client *httpclient.HttpClient, artifactoryUrl string, hd htt func getCommandMap() map[string]StatsFunc { return map[string]StatsFunc{ - "rt": GetArtifactoryStats, - "rpr": GetRepositoriesStats, - "xrp": GetXrayPolicies, - "xrw": GetXrayWatches, - "pr": GetProjectsStats, "rb": GetReleaseBundlesStats, "jpd": GetJPDsStats, + "rt": GetArtifactoryStats, + "pr": GetProjectsStats, } } -func RunJfrogPing(serverID string) error { - cmd := exec.Command("jf", "rt", "ping", "--server-id="+serverID) - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("command 'jf rt ping' failed: %w\nOutput: %s", err, string(output)) - } - return nil +var needAdminTokenMap = map[string]bool{ + "PROJECTS": true, + "JPD": true, } +var needAdminToken = false + func GetStats(outputFormat string, product string, accessToken string, serverId string) error { serverDetails, err := config.GetSpecificConfig(serverId, true, false) if err != nil { return err } - httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken} + httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken, User: serverDetails.User, Password: serverDetails.Password} if accessToken != "" { httpClientDetails.AccessToken = accessToken } @@ -186,52 +184,65 @@ func GetStats(outputFormat string, product string, accessToken string, serverId commandMap := getCommandMap() - commandFunc, ok := commandMap[product] - serverUrl := serverDetails.GetUrl() - if httpClientDetails.AccessToken == "" { - _ = RunJfrogPing(serverDetails.ServerId) - } + var allResults []interface{} - serverDetails, err = config.GetSpecificConfig(serverId, true, false) - if err != nil { - return err - } - httpClientDetails.AccessToken = serverDetails.AccessToken + projectsCount := 0 + + productOrder := []string{"rt", "jpd", "pr", "rb"} if product != "" { - if !ok { - err = fmt.Errorf("unknown product: %s", product) - return err + if commandFunc, ok := commandMap[product]; ok { + results, err := GetStatsForProduct(commandFunc, client, serverUrl, httpClientDetails) + if err != nil { + allResults = append(allResults, err) + } else { + allResults = append(allResults, results) + } } else { - _ = GetStatsForProduct(commandFunc, product, outputFormat, client, serverUrl, httpClientDetails) + return fmt.Errorf("unknown product: %s", product) } } else { - for productName, commandAPI := range commandMap { - _ = GetStatsForProduct(commandAPI, productName, outputFormat, client, serverUrl, httpClientDetails) + for _, productName := range productOrder { + if commandFunc, ok := commandMap[productName]; ok { + results, err := GetStatsForProduct(commandFunc, client, serverUrl, httpClientDetails) + if productName == "pr" && results != nil { + projects := results.(*[]Project) + projectsCount = len(*projects) + } else if productName == "rt" && results != nil { + artifactoryInfo := results.(*ArtifactoryInfo) + artifactoryInfo.ProjectsCount = projectsCount + allResults = append(allResults, artifactoryInfo) + continue + } + if err != nil { + allResults = append(allResults, err) + } else { + allResults = append(allResults, results) + } + } } } + return printAllResults(allResults, outputFormat) +} +func printAllResults(results []interface{}, outputFormat string) error { + for _, result := range results { + err := NewGenericResultsWriter(result, outputFormat).Print() + if err != nil { + log.Error("Failed to print result:", err) + } + } return nil } -func GetStatsForProduct(commandAPI StatsFunc, productName string, outputFormat string, client *httpclient.HttpClient, artifactoryUrl string, httpClientDetails httputils.HttpClientDetails) error { +func GetStatsForProduct(commandAPI StatsFunc, client *httpclient.HttpClient, artifactoryUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { body, err := commandAPI(client, artifactoryUrl, httpClientDetails) if err != nil { - err = NewGenericResultsWriter(err, outputFormat).Print() - if err != nil { - log.Error(productName, " : ", err) - return err - } - } else { - err := NewGenericResultsWriter(body, outputFormat).Print() - if err != nil { - log.Error(productName, " : ", err) - return err - } + return nil, err } - return nil + return body, nil } func (rw *GenericResultsWriter) Print() error { @@ -239,7 +250,7 @@ func (rw *GenericResultsWriter) Print() error { case "json", "simplejson": return rw.printJson() case "table": - return rw.printTable() + return rw.printDashboard() default: return rw.printSimple() } @@ -260,12 +271,6 @@ func (rw *GenericResultsWriter) printJson() error { switch rw.data.(type) { case *ArtifactoryInfo: msg = "Artifacts: No Artifacts Available" - case *[]RepositoryDetails: - msg = "Repositories: No Repository Available" - case *[]XrayPolicy: - msg = "Policies: No Xray Policy Available" - case *[]XrayWatch: - msg = "Watches: No Xray Watch Available" case *[]Project: msg = "Projects: No Project Available" case *[]JPD: @@ -283,102 +288,181 @@ func (rw *GenericResultsWriter) printJson() error { return nil } -func (rw *GenericResultsWriter) printTable() error { +func (rw *GenericResultsWriter) printDashboard() error { if rw.data == nil { return nil } - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) - t.SetStyle(table.StyleDouble) - switch v := rw.data.(type) { case *ArtifactoryInfo: - printArtifactoryStatsTable(t, v) - case *[]XrayPolicy: - printXrayPoliciesTable(t, v) - case *[]XrayWatch: - printXrayWatchesTable(t, v) + printArtifactoryDashboard(v) case *[]Project: - printProjectsTable(t, v) + printProjectsDashboard(v) case *[]JPD: - printJPDsTable(t, v) + printJPDsDashboard(v) case *ReleaseBundleResponse: - printReleaseBundlesTable(t, v) - case *[]RepositoryDetails: - printRepositoriesTable(t, v) - default: - if apiErr, ok := v.(*clientStats.APIError); ok { - printErrorTable(t, apiErr) - } else { - log.Warn("Table format is not supported for this unknown data type.") - } + printReleaseBundlesDashboard(v) + case *clientStats.APIError: + printErrorDashboard(v) + case *clientStats.GenericError: + printGenericErrorDashboard(v) } - t.Render() - log.Output() return nil } -func printArtifactoryStatsTable(t table.Writer, stats *ArtifactoryInfo) { - t.AppendHeader(table.Row{"ARTIFACTS METRIC", "COUNT"}) - t.AppendRows([]table.Row{ - {"Total No of Artifacts", stats.BinariesSummary.ArtifactsCount}, - {"Total Binaries Size:", stats.BinariesSummary.BinariesSize}, - {"Total Storage Used: ", stats.BinariesSummary.ArtifactsSize}, - {"Storage Type: ", stats.FileStoreSummary.StorageType}, - }) - t.Render() - t.ResetRows() - t.ResetHeaders() +func printArtifactoryDashboard(stats *ArtifactoryInfo) { + pterm.Println("📦 Artifactory Summary") + projectCount := pterm.LightCyan(stats.ProjectsCount) + if stats.BinariesSummary.BinariesCount == string("0") && needAdminToken { + projectCount = pterm.LightRed("No Admin Token") + } + + summaryTableData := pterm.TableData{ + {"Metric", "Value"}, + {"Total Projects", projectCount}, + {"Total Binaries", pterm.LightCyan(stats.BinariesSummary.BinariesCount)}, + {"Total Binaries Size", pterm.LightCyan(stats.BinariesSummary.BinariesSize)}, + {"Total Artifacts ", pterm.LightCyan(stats.BinariesSummary.ArtifactsCount)}, + {"Total Artifacts Size", pterm.LightCyan(stats.BinariesSummary.ArtifactsSize)}, + {"Storage Type", pterm.LightCyan(stats.FileStoreSummary.StorageType)}, + } + pterm.DefaultTable.WithHasHeader().WithData(summaryTableData).Render() + + repoTypeCounts := make(map[string]int) + + for _, repo := range stats.RepositoriesSummaryList { + if repo.RepoKey != "TOTAL" && repo.RepoType != "NA" { + repoTypeCounts[repo.RepoType]++ + } + } + + breakdownData := pterm.TableData{{"Repository Type", "Count"}} + for repoType, count := range repoTypeCounts { + breakdownData = append(breakdownData, []string{pterm.LightMagenta(repoType), pterm.LightGreen(fmt.Sprintf("%d", count))}) + } + pterm.DefaultTable.WithHasHeader().WithData(breakdownData).Render() } -func printXrayPoliciesTable(t table.Writer, policies *[]XrayPolicy) { - if len(*policies) == 0 { - t.AppendRow(table.Row{"No Policy Available"}) +func printProjectsDashboard(projects *[]Project) { + pterm.Println("📁 Projects") + if len(*projects) == 0 { + pterm.Warning.Println("No Projects found.") return } - t.AppendHeader(table.Row{"Policy Name", "Type", "Author"}) - for _, policy := range *policies { - t.AppendRow(table.Row{policy.Name, policy.Type, policy.Author}) + loopRange := len(*projects) + if loopRange > displayLimit { + loopRange = displayLimit } + actualProjectsCount := len(*projects) + + tableData := pterm.TableData{{"Project Key", "Display Name"}} + for i := 0; i < loopRange; i++ { + project := (*projects)[i] + tableData = append(tableData, []string{pterm.LightBlue(project.ProjectKey), project.DisplayName}) + } + + tableString, _ := pterm.DefaultTable.WithHasHeader().WithData(tableData).Srender() + trimmedTable := strings.TrimSuffix(tableString, "\n") + + pterm.Print(trimmedTable) + if actualProjectsCount > displayLimit { + pterm.Println(pterm.Yellow(fmt.Sprintf("\n...and %d more projects.", actualProjectsCount-displayLimit))) + } + pterm.Print("\n") } -func printXrayWatchesTable(t table.Writer, watches *[]XrayWatch) { - if len(*watches) == 0 { - t.AppendRow(table.Row{"No Watches Available"}) +func printJPDsDashboard(jpdList *[]JPD) { + pterm.Println("🛰️ JFrog Platform Deployments (JPDs)") + if jpdList == nil || len(*jpdList) == 0 { + pterm.Warning.Println("No JPDs found.") + pterm.Println() return } - t.AppendHeader(table.Row{"Watch Name"}) - for _, watch := range *watches { - t.AppendRow(table.Row{watch.GeneralData.Name}) + + loopRange := len(*jpdList) + if loopRange > displayLimit { + loopRange = displayLimit + } + actualCount := len(*jpdList) + + tableData := pterm.TableData{{"Name", "URL", "Status"}} + for i := 0; i < loopRange; i++ { + jpd := (*jpdList)[i] + var status string + if jpd.Status.Code == "ONLINE" { + status = pterm.LightGreen(jpd.Status.Code) + } else { + status = pterm.LightRed(jpd.Status.Code) + } + tableData = append(tableData, []string{pterm.LightCyan(jpd.Name), jpd.URL, status}) + } + + tableString, _ := pterm.DefaultTable.WithHasHeader().WithData(tableData).Srender() + pterm.Print(strings.TrimSuffix(tableString, "\n")) + + if actualCount > displayLimit { + pterm.Print(pterm.Yellow(fmt.Sprintf("\n...and %d more JPDs.\n", actualCount-displayLimit))) } + pterm.Print("\n\n") } -func printProjectsTable(t table.Writer, projects *[]Project) { - if len(*projects) == 0 { - t.AppendRow(table.Row{"No Projects Available"}) +func printReleaseBundlesDashboard(rbResponse *ReleaseBundleResponse) { + pterm.Println("🚀 Release Bundles") + if rbResponse == nil || len(rbResponse.ReleaseBundles) == 0 { + pterm.Warning.Println("No Release Bundles found.") + pterm.Println() return } - t.AppendHeader(table.Row{"Project Key", "Display Name"}) - for _, project := range *projects { - t.AppendRow(table.Row{project.ProjectKey, project.DisplayName}) + + loopRange := len(rbResponse.ReleaseBundles) + if loopRange > displayLimit { + loopRange = displayLimit + } + actualCount := len(rbResponse.ReleaseBundles) + + tableData := pterm.TableData{{"Release Bundle Name", "Project Key", "Repository Key"}} + for i := 0; i < loopRange; i++ { + rb := rbResponse.ReleaseBundles[i] + tableData = append(tableData, []string{ + pterm.LightGreen(rb.ReleaseBundleName), + rb.ProjectKey, + pterm.LightYellow(rb.RepositoryKey), + }) } + + tableString, _ := pterm.DefaultTable.WithHasHeader().WithData(tableData).Srender() + pterm.Print(strings.TrimSuffix(tableString, "\n")) + + if actualCount > displayLimit { + pterm.Print(pterm.Yellow(fmt.Sprintf("\n...and %d more release bundles.\n", actualCount-displayLimit))) + } + pterm.Print("\n\n") } -func printJPDsTable(t table.Writer, jpdList *[]JPD) { - if len(*jpdList) == 0 { - t.AppendRow(table.Row{"No JPDs Available"}) - return +func printErrorDashboard(apiError *clientStats.APIError) { + _, ok := needAdminTokenMap[apiError.Product] + Suggestion := "" + if apiError.StatusCode >= 400 && apiError.StatusCode < 500 && ok { + Suggestion = "Need Admin Token" + needAdminToken = true + } else { + Suggestion = apiError.Suggestion } - t.AppendHeader(table.Row{"JPD Name", "URL", "Status"}) - for _, jpd := range *jpdList { - t.AppendRow(table.Row{jpd.Name, jpd.URL, jpd.Status.Code}) + + tableData := pterm.TableData{ + {"Product: ", apiError.Product}, + {"Status Code", pterm.LightRed(fmt.Sprintf("%d", apiError.StatusCode))}, + {"Status", pterm.LightRed(apiError.StatusText)}, + {"Suggestion", pterm.LightYellow(Suggestion)}, } + pterm.DefaultTable.WithHasHeader().WithData(tableData).Render() } -func printErrorTable(t table.Writer, apiError *clientStats.APIError) { - t.AppendHeader(table.Row{"Product", "Status", "Text", "Suggestion"}) - t.AppendRow(table.Row{apiError.Product, apiError.StatusCode, apiError.StatusText, apiError.Suggestion}) +func printGenericErrorDashboard(err *clientStats.GenericError) { + tableData := pterm.TableData{ + {err.Product, err.Err.Error()}, + } + pterm.DefaultTable.WithBoxed(true).WithData(tableData).Render() } func (rw *GenericResultsWriter) printSimple() error { @@ -389,91 +473,53 @@ func (rw *GenericResultsWriter) printSimple() error { switch v := rw.data.(type) { case *ArtifactoryInfo: printArtifactoryStats(v) - case *[]XrayPolicy: - printXrayPoliciesStats(v) - case *[]XrayWatch: - printXrayWatchesStats(v) case *[]Project: printProjectsStats(v) case *[]JPD: printJPDsStats(v) case *ReleaseBundleResponse: printReleaseBundlesSimple(v) - case *[]RepositoryDetails: - printRepositoriesSimple(v) - default: - if apiErr, ok := rw.data.(*clientStats.APIError); ok { - printErrorMessage(apiErr) - } else { - log.Warn("An unexpected data type was received and cannot be printed as a detailed error.") - } + case *clientStats.APIError: + printErrorMessage(v) + case *clientStats.GenericError: + printGenericError(v) } return nil } -func getRepositoryCounts(repos *[]RepositoryDetails) map[string]int { - counts := make(map[string]int) - for _, repo := range *repos { - counts[repo.Type]++ - } - return counts -} - -func printRepositoriesTable(t table.Writer, repos *[]RepositoryDetails) { - if len(*repos) == 0 { - t.AppendRow(table.Row{"No Repositories Available"}) - return - } - counts := getRepositoryCounts(repos) - t.AppendHeader(table.Row{"Repository Type", "Count"}) - for repoType, count := range counts { - t.AppendRow(table.Row{repoType, count}) - } -} - -func printRepositoriesSimple(repos *[]RepositoryDetails) { - log.Output("--- Repositories Summary by Type ---") - if len(*repos) == 0 { - log.Output("No Repositories Available") - log.Output() - return - } - counts := getRepositoryCounts(repos) - for repoType, count := range counts { - log.Output("- ", repoType, ": ", count) - } - log.Output() -} - -func printReleaseBundlesTable(t table.Writer, rbResponse *ReleaseBundleResponse) { - if len(rbResponse.ReleaseBundles) == 0 { - t.AppendRow(table.Row{"No Release Bundles Available"}) - return - } - t.AppendHeader(table.Row{"Release Bundle Name", "Project Key", "Repository Key"}) - for _, rb := range rbResponse.ReleaseBundles { - t.AppendRow(table.Row{rb.ReleaseBundleName, rb.ProjectKey, rb.RepositoryKey}) - } -} - func printReleaseBundlesSimple(rbResponse *ReleaseBundleResponse) { - log.Output("--- Available Release Bundles ---") + log.Output("--- Release Bundles ---") if len(rbResponse.ReleaseBundles) == 0 { log.Output("No Release Bundles Available") log.Output() return } - for index, rb := range rbResponse.ReleaseBundles { - log.Output("ReleaseBundle: ", index+1) + loopRange := len(rbResponse.ReleaseBundles) + if loopRange > displayLimit { + loopRange = displayLimit + } + actualProjectsCount := len(rbResponse.ReleaseBundles) + for i := 0; i < loopRange; i++ { + rb := rbResponse.ReleaseBundles[i] + log.Output("ReleaseBundle: ", i+1) log.Output("ReleaseBundleName: ", rb.ReleaseBundleName) log.Output("RepositoryKey: ", rb.RepositoryKey) log.Output("ProjectKey:", rb.ProjectKey) log.Output() } + if actualProjectsCount > displayLimit { + log.Output(pterm.Yellow(fmt.Sprintf("...and %d more release bundles, try JSON format", actualProjectsCount-displayLimit))) + } + log.Output() } func printArtifactoryStats(stats *ArtifactoryInfo) { - log.Output("--- Artifactory Statistics Summary ---") + projectCount := pterm.Normal(stats.ProjectsCount) + if stats.ProjectsCount == 0 && needAdminToken { + projectCount = pterm.Normal("No Admin Token") + } + log.Output("--- Artifactory Statistics ---") + log.Output("Total Projects: ", projectCount) log.Output("Total No of Binaries: ", stats.BinariesSummary.BinariesCount) log.Output("Total Binaries Size: ", stats.BinariesSummary.BinariesSize) log.Output("Total No of Artifacts: ", stats.BinariesSummary.ArtifactsCount) @@ -482,42 +528,6 @@ func printArtifactoryStats(stats *ArtifactoryInfo) { log.Output() } -func printXrayPoliciesStats(policies *[]XrayPolicy) { - log.Output("--- Xray Policies ---") - if len(*policies) == 0 { - log.Output("No Xray Policies Available") - log.Output() - return - } - for index, policy := range *policies { - log.Output("Policy: ", index+1) - log.Output("Name: ", policy.Name) - log.Output("Type: ", policy.Type) - log.Output("Author: ", policy.Author) - log.Output("Created: ", policy.Created) - log.Output("Modified: ", policy.Modified) - log.Output() - } -} - -func printXrayWatchesStats(watches *[]XrayWatch) { - log.Output("--- Enforced Xray Watches ---") - if len(*watches) == 0 { - log.Output("No Xray Watches Available") - log.Output() - return - } - for _, watch := range *watches { - log.Output("Name: ", watch.GeneralData.Name) - for _, resource := range watch.ProjectResources.Resources { - log.Output("Name:", resource.Name) - log.Output("Type:", resource.Type) - log.Output("BinMgrID:", resource.BinMgrID) - } - log.Output("") - } -} - func printProjectsStats(projects *[]Project) { log.Output("--- Available Projects ---") if len(*projects) == 0 { @@ -525,8 +535,14 @@ func printProjectsStats(projects *[]Project) { log.Output() return } - for index, project := range *projects { - log.Output("Project: ", index+1) + loopRange := len(*projects) + if loopRange > displayLimit { + loopRange = displayLimit + } + actualProjectsCount := len(*projects) + for i := 0; i < loopRange; i++ { + project := (*projects)[i] + log.Output("Project: ", i+1) log.Output("Name: ", project.DisplayName) log.Output("Key: ", project.ProjectKey) if project.Description != "" { @@ -536,6 +552,10 @@ func printProjectsStats(projects *[]Project) { } log.Output() } + if actualProjectsCount > displayLimit { + log.Output(pterm.Yellow(fmt.Sprintf("...and %d more projects, try JSON format", actualProjectsCount-displayLimit))) + } + log.Output() } func printJPDsStats(jpdList *[]JPD) { @@ -545,8 +565,14 @@ func printJPDsStats(jpdList *[]JPD) { log.Output() return } - for index, jpd := range *jpdList { - log.Output("JPD: ", index+1) + loopRange := len(*jpdList) + if loopRange > displayLimit { + loopRange = displayLimit + } + actualProjectsCount := len(*jpdList) + for i := 0; i < loopRange; i++ { + jpd := (*jpdList)[i] + log.Output("JPD: ", i+1) log.Output("Name: ", jpd.Name) log.Output("URL: ", jpd.URL) log.Output("Status: ", jpd.Status.Code) @@ -554,13 +580,31 @@ func printJPDsStats(jpdList *[]JPD) { log.Output("Local: ", jpd.Local) log.Output() } + if actualProjectsCount > displayLimit { + log.Output(pterm.Yellow(fmt.Sprintf("...and %d more JPDs, try JSON format", actualProjectsCount-displayLimit))) + } } func printErrorMessage(apiError *clientStats.APIError) { + _, ok := needAdminTokenMap[apiError.Product] + Suggestion := "" + if apiError.StatusCode >= 400 && apiError.StatusCode < 500 && ok { + Suggestion = "Need Admin Token" + needAdminToken = true + } else { + Suggestion = apiError.Suggestion + } log.Output("---", apiError.Product, "---") log.Output("StatusCode - ", apiError.StatusCode) log.Output("StatusText - ", apiError.StatusText) - log.Output("Suggestion - ", apiError.Suggestion) + log.Output("Suggestion - ", Suggestion) + log.Output() +} + +func printGenericError(err *clientStats.GenericError) { + log.Output("---", err.Product, "---") + log.Output("Error: ", err.Err.Error()) + log.Output() } func GetArtifactoryStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { @@ -572,43 +616,15 @@ func GetArtifactoryStats(client *httpclient.HttpClient, serverUrl string, httpCl if err := json.Unmarshal(body, &stats); err != nil { return nil, fmt.Errorf("error parsing Artifactory JSON: %w", err) } - return &stats, nil -} -func GetRepositoriesStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetRepositoriesStats(client, serverUrl, httpClientDetails) + body, err = clientStats.GetRepositoriesStats(client, serverUrl, httpClientDetails) if err != nil { return nil, err } - var repos []RepositoryDetails - if err := json.Unmarshal(body, &repos); err != nil { + if err := json.Unmarshal(body, &stats.RepositoriesDetails); err != nil { return nil, fmt.Errorf("error parsing repositories JSON: %w", err) } - return &repos, nil -} - -func GetXrayPolicies(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetXrayPolicies(client, serverUrl, httpClientDetails) - if err != nil { - return nil, err - } - var policies []XrayPolicy - if err := json.Unmarshal(body, &policies); err != nil { - return nil, fmt.Errorf("error parsing policies JSON: %w", err) - } - return &policies, nil -} - -func GetXrayWatches(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { - body, err := clientStats.GetXrayWatches(client, serverUrl, httpClientDetails) - if err != nil { - return nil, err - } - var watches []XrayWatch - if err := json.Unmarshal(body, &watches); err != nil { - return nil, fmt.Errorf("error parsing Watches JSON: %w", err) - } - return &watches, nil + return &stats, nil } func GetProjectsStats(client *httpclient.HttpClient, serverUrl string, httpClientDetails httputils.HttpClientDetails) (interface{}, error) { diff --git a/utils/stats/stats_test.go b/utils/stats/stats_test.go deleted file mode 100644 index 85fc7f8b1..000000000 --- a/utils/stats/stats_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package coreStats - -import ( - "fmt" - "github.com/golang-jwt/jwt/v4" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "testing" - - "github.com/jfrog/jfrog-client-go/http/httpclient" - "github.com/jfrog/jfrog-client-go/utils/io/httputils" - clientStats "github.com/jfrog/jfrog-client-go/utils/stats" - "github.com/stretchr/testify/assert" -) - -type statsTestFunc func(client *httpclient.HttpClient, artifactoryUrl string, hd httputils.HttpClientDetails) ([]byte, error) - -func GetTokenID(tokenString string) (string, error) { - token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) - if err != nil { - return "", fmt.Errorf("failed to parse token: %w", err) - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return "", fmt.Errorf("failed to read token claims") - } - - tokenID, ok := claims["jti"].(string) - if !ok { - return "", fmt.Errorf("token does not contain a valid 'jti' (Token ID) claim") - } - - return tokenID, nil -} - -func IsAdminToken(client *httpclient.HttpClient, baseUrl string, tokenString string, httpClientDetails httputils.HttpClientDetails) bool { - tokenID, err := GetTokenID(tokenString) - if err != nil { - return false - } - isAdmin, err := clientStats.IsAdminToken(client, baseUrl, tokenID, httpClientDetails) - if err != nil { - return false - } - return isAdmin -} - -func setupTestClient(t *testing.T) (*httpclient.HttpClient, httputils.HttpClientDetails, *config.ServerDetails, bool) { - serverDetails, err := config.GetDefaultServerConf() - if err != nil { - assert.NoError(t, err) - } - - httpClientDetails := httputils.HttpClientDetails{AccessToken: serverDetails.AccessToken} - - httpClientDetails.SetContentTypeApplicationJson() - client, err := httpclient.ClientBuilder().Build() - if err != nil { - assert.NoError(t, err) - } - - serverUrl := serverDetails.GetUrl() - - if httpClientDetails.AccessToken == "" { - _ = RunJfrogPing(serverDetails.ServerId) - } - - serverDetails, err = config.GetDefaultServerConf() - if err != nil { - assert.NoError(t, err) - } - - isAdminToken := IsAdminToken(client, serverUrl, serverDetails.AccessToken, httpClientDetails) - - assert.NoError(t, err) - return client, httpClientDetails, serverDetails, isAdminToken -} - -func TestAllProductAPIs(t *testing.T) { - productFunctions := map[string]statsTestFunc{ - "Artifactory": clientStats.GetArtifactoryStats, - "Repositories": clientStats.GetRepositoriesStats, - "XrayPolicies": clientStats.GetXrayPolicies, - "XrayWatches": clientStats.GetXrayWatches, - "Projects": clientStats.GetProjectsStats, - "JPDs": clientStats.GetJPDsStats, - "ReleaseBundles": clientStats.GetReleaseBundlesStats, - } - testCasesMap := map[string]bool{ - "Artifactory": false, - "Repositories": false, - "XrayPolicies": true, - "XrayWatches": true, - "Projects": true, - "JPDs": true, - "ReleaseBundles": false, - } - for product, getFunc := range productFunctions { - t.Run(product, func(t *testing.T) { - t.Run(product, func(t *testing.T) { - client, httpClientDetails, server, isAdmin := setupTestClient(t) - if isAdmin { - _, err := getFunc(client, server.GetUrl(), httpClientDetails) - if err != nil { - assert.NoError(t, err) - } - } else { - _, err := getFunc(client, server.GetUrl(), httpClientDetails) - if err != nil { - if !testCasesMap[product] { - assert.NoError(t, err) - } - } - } - }) - }) - } -}