From 135ac718960e533b6f186a2e61738b3bac833967 Mon Sep 17 00:00:00 2001 From: attiasas Date: Sun, 6 Aug 2023 16:58:07 +0300 Subject: [PATCH 01/17] Add reporting usage methods --- tests/artifactoryupload_test.go | 2 +- xray/usage/reportusage.go | 155 ++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 xray/usage/reportusage.go diff --git a/tests/artifactoryupload_test.go b/tests/artifactoryupload_test.go index 1795cba13..c8df9435b 100644 --- a/tests/artifactoryupload_test.go +++ b/tests/artifactoryupload_test.go @@ -368,7 +368,7 @@ func archiveUpload(t *testing.T) { } // Check for timezone offset for each file in the zip - r, err := zip.OpenReader(downloadTarget+"test.zip") + r, err := zip.OpenReader(downloadTarget + "test.zip") assert.NoError(t, err) defer func() { assert.NoError(t, r.Close()) }() _, sysTimezoneOffset := time.Now().Zone() diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go new file mode 100644 index 000000000..057fe73d1 --- /dev/null +++ b/xray/usage/reportusage.go @@ -0,0 +1,155 @@ +package usage + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + versionutil "github.com/jfrog/gofrog/version" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/jfrog/jfrog-client-go/xray" +) + +const ( + minXrayVersion = "3.80.0" + + xrayUsageApiPath = "api/v1/usage/events/send" + ecosystemUsageApiUrl = "http://ecosystem-services.jfrog.info/api/usage/report" + ReportUsagePrefix = "Usage Report: " +) + +type ReportUsageAttribute struct { + AttributeName string + AttributeValue string +} + +func (rua *ReportUsageAttribute) isEmpty() bool { + return rua.AttributeName == "" +} + +type ReportXrayEventData struct { + ProductId string `json:"product_name"` + EventId string `json:"event_name"` + Origin string `json:"origin,omitempty"` + Attributes map[string]string `json:"data,omitempty"` +} + +func SendXrayReportUsage(productId, commandName string, serviceManager xray.XrayServicesManager, attributes ...ReportUsageAttribute) error { + config := serviceManager.Config() + if config == nil { + return errorutils.CheckErrorf(ReportUsagePrefix + "Expected full config, but no configuration exists.") + } + xrDetails := config.GetServiceDetails() + if xrDetails == nil { + return errorutils.CheckErrorf(ReportUsagePrefix + "Xray details not configured.") + } + xrayVersion, err := xrDetails.GetVersion() + if err != nil { + return errors.New(ReportUsagePrefix + "Couldn't get Xray version. Error: " + err.Error()) + } + if !isVersionCompatible(xrayVersion) { + log.Debug(fmt.Sprintf(ReportUsagePrefix+"Expected Xray version %s or above, got %s", minXrayVersion, xrayVersion)) + return nil + } + + url, err := utils.BuildArtifactoryUrl(xrDetails.GetUrl(), xrayUsageApiPath, make(map[string]string)) + if err != nil { + return errors.New(ReportUsagePrefix + err.Error()) + } + clientDetails := xrDetails.CreateHttpClientDetails() + + bodyContent, err := reportUsageXrayToJson(productId, commandName, attributes...) + if err != nil { + return errors.New(ReportUsagePrefix + err.Error()) + } + utils.AddHeader("Content-Type", "application/json", &clientDetails.Headers) + resp, body, err := serviceManager.Client().SendPost(url, bodyContent, &clientDetails) + if err != nil { + return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) + } + + err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) + if err != nil { + return err + } + + log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Xray response:", resp.Status) + return nil +} + +// Returns an error if the Xray version is not compatible to run usage api +func isVersionCompatible(xrayVersion string) bool { + // API exists from Xray version 3.80.0 and above: + version := versionutil.NewVersion(xrayVersion) + return version.AtLeast(minXrayVersion) +} + +func reportUsageXrayToJson(productId, commandName string, attributes ...ReportUsageAttribute) ([]byte, error) { + reportInfo := ReportXrayEventData{ProductId: productId, EventId: getExpectedEventName(productId, commandName), Origin: "API"} + if len(attributes) > 0 { + reportInfo.Attributes = make(map[string]string, len(attributes)) + for _, attribute := range attributes { + if !attribute.isEmpty() { + reportInfo.Attributes[attribute.AttributeName] = attribute.AttributeValue + } + } + } + bodyContent, err := json.Marshal(reportInfo) + return bodyContent, errorutils.CheckError(err) +} + +func getExpectedEventName(productId, commandName string) string { + return "server_" + productId + "_" + commandName +} + +type ReportEcosystemUsageData struct { + ProductId string `json:"productId"` + AccountId string `json:"accountId"` + Features []string `json:"features"` + ClientId string `json:"clientId,omitempty"` +} + +func SendEcosystemReportUsage(productId, accountId, clientId string, features ...string) error { + reportInfo := ReportEcosystemUsageData{ProductId: productId, AccountId: accountId, ClientId: clientId, Features: []string{}} + for _, feature := range features { + if feature != "" { + reportInfo.Features = append(reportInfo.Features, feature) + } + } + if len(reportInfo.Features) <= 0 { + return errorutils.CheckErrorf(ReportUsagePrefix + "Expected at least one feature to report usage on.") + } + + bodyContent, err := json.Marshal(reportInfo) + if err = errorutils.CheckError(err); err != nil { + return errors.New(ReportUsagePrefix + err.Error()) + } + + resp, body, err := sendRequestToEcosystemService(bodyContent) + if err != nil { + return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) + } + + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted); err != nil { + return err + } + + log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Xray response:", resp.Status) + return nil +} + +func sendRequestToEcosystemService(content []byte) (resp *http.Response, respBody []byte, err error) { + var client *httpclient.HttpClient + if client, err = httpclient.ClientBuilder().Build(); err != nil { + return + } + + details := httputils.HttpClientDetails{} + utils.AddHeader("Content-Type", "application/json", &details.Headers) + return client.SendPost(ecosystemUsageApiUrl, content, details, "") +} From 8c6459dd647468d4b4113d55b3030156db994d6f Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 8 Aug 2023 15:09:07 +0300 Subject: [PATCH 02/17] Add tests --- xray/usage/reportusage.go | 54 +++++++++----- xray/usage/reportusage_test.go | 132 +++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 18 deletions(-) create mode 100644 xray/usage/reportusage_test.go diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 057fe73d1..2d3e88a1a 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -63,7 +63,7 @@ func SendXrayReportUsage(productId, commandName string, serviceManager xray.Xray } clientDetails := xrDetails.CreateHttpClientDetails() - bodyContent, err := reportUsageXrayToJson(productId, commandName, attributes...) + bodyContent, err := reportUsageXrayToJson(CreateUsageEvents(productId, commandName, attributes...)) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } @@ -89,17 +89,22 @@ func isVersionCompatible(xrayVersion string) bool { return version.AtLeast(minXrayVersion) } -func reportUsageXrayToJson(productId, commandName string, attributes ...ReportUsageAttribute) ([]byte, error) { - reportInfo := ReportXrayEventData{ProductId: productId, EventId: getExpectedEventName(productId, commandName), Origin: "API"} - if len(attributes) > 0 { - reportInfo.Attributes = make(map[string]string, len(attributes)) - for _, attribute := range attributes { +func CreateUsageEvents(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { + reportInfo := ReportXrayEventData{ProductId: productId, EventId: getExpectedEventName(productId, featureId), Origin: "API"} + + if len(additionalAttributes) > 0 { + reportInfo.Attributes = make(map[string]string, len(additionalAttributes)) + for _, attribute := range additionalAttributes { if !attribute.isEmpty() { reportInfo.Attributes[attribute.AttributeName] = attribute.AttributeValue } } } - bodyContent, err := json.Marshal(reportInfo) + return reportInfo +} + +func reportUsageXrayToJson(events ...ReportXrayEventData) ([]byte, error) { + bodyContent, err := json.Marshal(events) return bodyContent, errorutils.CheckError(err) } @@ -110,23 +115,18 @@ func getExpectedEventName(productId, commandName string) string { type ReportEcosystemUsageData struct { ProductId string `json:"productId"` AccountId string `json:"accountId"` - Features []string `json:"features"` ClientId string `json:"clientId,omitempty"` + Features []string `json:"features"` } func SendEcosystemReportUsage(productId, accountId, clientId string, features ...string) error { - reportInfo := ReportEcosystemUsageData{ProductId: productId, AccountId: accountId, ClientId: clientId, Features: []string{}} - for _, feature := range features { - if feature != "" { - reportInfo.Features = append(reportInfo.Features, feature) - } - } - if len(reportInfo.Features) <= 0 { - return errorutils.CheckErrorf(ReportUsagePrefix + "Expected at least one feature to report usage on.") + reportInfo, err := CreateUsageData(productId, accountId, clientId, features...) + if err != nil { + return errorutils.CheckErrorf(ReportUsagePrefix + err.Error()) } - bodyContent, err := json.Marshal(reportInfo) - if err = errorutils.CheckError(err); err != nil { + bodyContent, err := reportUsageEcosystemToJson(reportInfo) + if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } @@ -143,6 +143,24 @@ func SendEcosystemReportUsage(productId, accountId, clientId string, features .. return nil } +func CreateUsageData(productId, accountId, clientId string, features ...string) (reportInfo ReportEcosystemUsageData, err error) { + reportInfo = ReportEcosystemUsageData{ProductId: productId, AccountId: accountId, ClientId: clientId, Features: []string{}} + for _, feature := range features { + if feature != "" { + reportInfo.Features = append(reportInfo.Features, feature) + } + } + if len(reportInfo.Features) == 0 { + err = fmt.Errorf("Expected at least one feature to report usage on.") + } + return +} + +func reportUsageEcosystemToJson(event ReportEcosystemUsageData) ([]byte, error) { + bodyContent, err := json.Marshal(event) + return bodyContent, errorutils.CheckError(err) +} + func sendRequestToEcosystemService(content []byte) (resp *http.Response, respBody []byte, err error) { var client *httpclient.HttpClient if client, err = httpclient.ClientBuilder().Build(); err != nil { diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go new file mode 100644 index 000000000..5da1a1623 --- /dev/null +++ b/xray/usage/reportusage_test.go @@ -0,0 +1,132 @@ +package usage + +import ( + "fmt" + "strings" + "testing" + + "github.com/jfrog/jfrog-client-go/utils" + "github.com/stretchr/testify/assert" +) + +func TestIsXrayVersionCompatible(t *testing.T) { + tests := []struct { + xrayVersion string + expectedResult bool + }{ + {"1.2.0", false}, + {"2.9.0", false}, + {"2.0.0", false}, + {"3.79.3", false}, + {"3.80.0", true}, + {utils.Development, true}, + {"3.81.2", true}, + {"4.15.2", true}, + } + for _, test := range tests { + t.Run(test.xrayVersion, func(t *testing.T) { + result := isVersionCompatible(test.xrayVersion) + if test.expectedResult != result { + t.Error(fmt.Errorf("expected %t, got %t", test.expectedResult, result)) + } + }) + } +} + +func TestXrayReportUsageJson(t *testing.T) { + type reportUsageTestCase struct { + productId string + EventId string + Attributes []ReportUsageAttribute + } + jsonPatterns := []string{ + `[{"product_name":"%s","event_name":"%s","origin":"API"}]`, + `[{"product_name":"%s","event_name":"%s","origin":"API","data":{"%s":"%s"}}]`, + `[{"product_name":"%s","event_name":"%s","origin":"API","data":{"%s":"%s","%s":"%s"}}]`, + } + + cases := []reportUsageTestCase{ + {"jfrog-cli-go", "generic_audit", []ReportUsageAttribute{}}, + {"frogbot", "scan_pull_request", []ReportUsageAttribute{{AttributeName: "clientId", AttributeValue: "repo1"}}}, + {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"}, {AttributeName: "clientId", AttributeValue: "user-hash"}}}, + } + + for _, test := range cases { + // Create the expected json + expectedResult := "" + switch { + case len(test.Attributes) == 1: + expectedResult = fmt.Sprintf(jsonPatterns[1], test.productId, getExpectedEventName(test.productId, test.EventId), test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue) + case len(test.Attributes) == 2: + expectedResult = fmt.Sprintf(jsonPatterns[2], test.productId, getExpectedEventName(test.productId, test.EventId), test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue) + default: + expectedResult = fmt.Sprintf(jsonPatterns[0], test.productId, getExpectedEventName(test.productId, test.EventId)) + } + // Run test + t.Run(test.EventId, func(t *testing.T) { + body, err := reportUsageXrayToJson(CreateUsageEvents(test.productId, test.EventId, test.Attributes...)) + assert.NoError(t, err) + assert.Equal(t, expectedResult, string(body)) + }) + } +} + +func TestEcosystemReportUsageJson(t *testing.T) { + type reportUsageTestCase struct { + ProductId string + AccountId string + ClientId string + Features []string + } + jsonPatterns := []string{ + `{"productId":"%s","accountId":"%s","features":[]}`, + `{"productId":"%s","accountId":"%s","features":["%s"]}`, + `{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s"]}`, + `{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s","%s"]}`, + } + + cases := []reportUsageTestCase{ + {"jfrog-cli-go", "platform.jfrog.io", "", []string{}}, + {"jfrog-cli-go", "platform.jfrog.io", "", []string{"generic_audit"}}, + {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request"}}, + {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request", "npm-dep"}}, + } + + // Create the expected json + for _, test := range cases { + // Create the expected json + expectedResult := "" + switch { + case len(test.Features) == 1: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(jsonPatterns[2], test.ProductId, test.AccountId, test.ClientId, test.Features[0]) + } else { + expectedResult = fmt.Sprintf(jsonPatterns[1], test.ProductId, test.AccountId, test.Features[0]) + } + case len(test.Features) == 2: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(jsonPatterns[3], test.ProductId, test.AccountId, test.ClientId, test.Features[0], test.Features[1]) + } else { + expectedResult = fmt.Sprintf(jsonPatterns[3], test.ProductId, test.AccountId, test.Features[0], test.Features[1]) + } + default: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(jsonPatterns[0], test.ProductId, test.AccountId, test.ClientId) + } else { + expectedResult = fmt.Sprintf(jsonPatterns[0], test.ProductId, test.AccountId) + } + } + // Run test + t.Run(strings.Join(test.Features, ","), func(t *testing.T) { + if data, err := CreateUsageData(test.ProductId, test.AccountId, test.ClientId, test.Features...); len(test.Features) > 0 { + assert.NoError(t, err) + body, err := reportUsageEcosystemToJson(data) + assert.NoError(t, err) + assert.Equal(t, expectedResult, string(body)) + } else { + assert.Error(t, err) + } + + }) + } +} From dd465d9d769c9c3cb74f40f04ac726edf332ca1f Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 8 Aug 2023 16:09:57 +0300 Subject: [PATCH 03/17] add option for multiple data at one request --- xray/usage/reportusage.go | 6 +++--- xray/usage/reportusage_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 2d3e88a1a..c10f286b9 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -151,13 +151,13 @@ func CreateUsageData(productId, accountId, clientId string, features ...string) } } if len(reportInfo.Features) == 0 { - err = fmt.Errorf("Expected at least one feature to report usage on.") + err = fmt.Errorf("expected at least one feature to report usage on") } return } -func reportUsageEcosystemToJson(event ReportEcosystemUsageData) ([]byte, error) { - bodyContent, err := json.Marshal(event) +func reportUsageEcosystemToJson(events ...ReportEcosystemUsageData) ([]byte, error) { + bodyContent, err := json.Marshal(events) return bodyContent, errorutils.CheckError(err) } diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index 5da1a1623..a9f5e96e5 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -79,10 +79,10 @@ func TestEcosystemReportUsageJson(t *testing.T) { Features []string } jsonPatterns := []string{ - `{"productId":"%s","accountId":"%s","features":[]}`, - `{"productId":"%s","accountId":"%s","features":["%s"]}`, - `{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s"]}`, - `{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s","%s"]}`, + `[{"productId":"%s","accountId":"%s","features":[]}]`, + `[{"productId":"%s","accountId":"%s","features":["%s"]}]`, + `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s"]}]`, + `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s","%s"]}]`, } cases := []reportUsageTestCase{ From cce2a8e483947af6db259731b552e4c595fc4781 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 8 Aug 2023 16:59:23 +0300 Subject: [PATCH 04/17] change method to send multiple reports once --- xray/usage/reportusage.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index c10f286b9..397226f2a 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -39,7 +39,10 @@ type ReportXrayEventData struct { Attributes map[string]string `json:"data,omitempty"` } -func SendXrayReportUsage(productId, commandName string, serviceManager xray.XrayServicesManager, attributes ...ReportUsageAttribute) error { +func SendXrayReportUsage(serviceManager xray.XrayServicesManager, events ...ReportXrayEventData) error { + if len(events) == 0 { + return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") + } config := serviceManager.Config() if config == nil { return errorutils.CheckErrorf(ReportUsagePrefix + "Expected full config, but no configuration exists.") @@ -63,7 +66,7 @@ func SendXrayReportUsage(productId, commandName string, serviceManager xray.Xray } clientDetails := xrDetails.CreateHttpClientDetails() - bodyContent, err := reportUsageXrayToJson(CreateUsageEvents(productId, commandName, attributes...)) + bodyContent, err := reportUsageXrayToJson(events...) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } @@ -119,27 +122,25 @@ type ReportEcosystemUsageData struct { Features []string `json:"features"` } -func SendEcosystemReportUsage(productId, accountId, clientId string, features ...string) error { - reportInfo, err := CreateUsageData(productId, accountId, clientId, features...) - if err != nil { - return errorutils.CheckErrorf(ReportUsagePrefix + err.Error()) +func SendEcosystemReportUsage(events ...ReportEcosystemUsageData) error { + if len(events) == 0 { + return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") } - - bodyContent, err := reportUsageEcosystemToJson(reportInfo) + bodyContent, err := reportUsageEcosystemToJson(events...) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } resp, body, err := sendRequestToEcosystemService(bodyContent) if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) + return errors.New(ReportUsagePrefix + "Couldn't send usage info to Ecosystem. Error: " + err.Error()) } if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted); err != nil { return err } - log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Xray response:", resp.Status) + log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Ecosystem response:", resp.Status) return nil } From 1d7efd57d1cbdd0420abf0bb597c12115d7742b6 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 9 Aug 2023 14:25:37 +0300 Subject: [PATCH 05/17] rename methods --- xray/usage/reportusage.go | 16 ++++++++-------- xray/usage/reportusage_test.go | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 397226f2a..81b6c3482 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -39,7 +39,7 @@ type ReportXrayEventData struct { Attributes map[string]string `json:"data,omitempty"` } -func SendXrayReportUsage(serviceManager xray.XrayServicesManager, events ...ReportXrayEventData) error { +func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...ReportXrayEventData) error { if len(events) == 0 { return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") } @@ -66,7 +66,7 @@ func SendXrayReportUsage(serviceManager xray.XrayServicesManager, events ...Repo } clientDetails := xrDetails.CreateHttpClientDetails() - bodyContent, err := reportUsageXrayToJson(events...) + bodyContent, err := xrayUsageEventsToJson(events...) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } @@ -92,7 +92,7 @@ func isVersionCompatible(xrayVersion string) bool { return version.AtLeast(minXrayVersion) } -func CreateUsageEvents(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { +func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { reportInfo := ReportXrayEventData{ProductId: productId, EventId: getExpectedEventName(productId, featureId), Origin: "API"} if len(additionalAttributes) > 0 { @@ -106,7 +106,7 @@ func CreateUsageEvents(productId, featureId string, additionalAttributes ...Repo return reportInfo } -func reportUsageXrayToJson(events ...ReportXrayEventData) ([]byte, error) { +func xrayUsageEventsToJson(events ...ReportXrayEventData) ([]byte, error) { bodyContent, err := json.Marshal(events) return bodyContent, errorutils.CheckError(err) } @@ -122,11 +122,11 @@ type ReportEcosystemUsageData struct { Features []string `json:"features"` } -func SendEcosystemReportUsage(events ...ReportEcosystemUsageData) error { - if len(events) == 0 { +func SendEcosystemUsageReports(reports ...ReportEcosystemUsageData) error { + if len(reports) == 0 { return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") } - bodyContent, err := reportUsageEcosystemToJson(events...) + bodyContent, err := ecosystemUsageReportsToJson(reports...) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } @@ -157,7 +157,7 @@ func CreateUsageData(productId, accountId, clientId string, features ...string) return } -func reportUsageEcosystemToJson(events ...ReportEcosystemUsageData) ([]byte, error) { +func ecosystemUsageReportsToJson(events ...ReportEcosystemUsageData) ([]byte, error) { bodyContent, err := json.Marshal(events) return bodyContent, errorutils.CheckError(err) } diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index a9f5e96e5..67d90a71a 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -33,7 +33,7 @@ func TestIsXrayVersionCompatible(t *testing.T) { } } -func TestXrayReportUsageJson(t *testing.T) { +func TestXrayUsageEventToJson(t *testing.T) { type reportUsageTestCase struct { productId string EventId string @@ -64,14 +64,14 @@ func TestXrayReportUsageJson(t *testing.T) { } // Run test t.Run(test.EventId, func(t *testing.T) { - body, err := reportUsageXrayToJson(CreateUsageEvents(test.productId, test.EventId, test.Attributes...)) + body, err := xrayUsageEventsToJson(CreateUsageEvent(test.productId, test.EventId, test.Attributes...)) assert.NoError(t, err) assert.Equal(t, expectedResult, string(body)) }) } } -func TestEcosystemReportUsageJson(t *testing.T) { +func TestEcosystemReportUsageToJson(t *testing.T) { type reportUsageTestCase struct { ProductId string AccountId string @@ -120,7 +120,7 @@ func TestEcosystemReportUsageJson(t *testing.T) { t.Run(strings.Join(test.Features, ","), func(t *testing.T) { if data, err := CreateUsageData(test.ProductId, test.AccountId, test.ClientId, test.Features...); len(test.Features) > 0 { assert.NoError(t, err) - body, err := reportUsageEcosystemToJson(data) + body, err := ecosystemUsageReportsToJson(data) assert.NoError(t, err) assert.Equal(t, expectedResult, string(body)) } else { From 3786af798944a97e9773f80e60d13f5085ebfe35 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 14 Aug 2023 12:51:23 +0300 Subject: [PATCH 06/17] change url --- xray/usage/reportusage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 81b6c3482..b03da159a 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -19,7 +19,7 @@ const ( minXrayVersion = "3.80.0" xrayUsageApiPath = "api/v1/usage/events/send" - ecosystemUsageApiUrl = "http://ecosystem-services.jfrog.info/api/usage/report" + ecosystemUsageApiUrl = "http://usage-ecosystem.jfrog.info/api/usage/report" ReportUsagePrefix = "Usage Report: " ) @@ -33,10 +33,10 @@ func (rua *ReportUsageAttribute) isEmpty() bool { } type ReportXrayEventData struct { + Attributes map[string]string `json:"data,omitempty"` ProductId string `json:"product_name"` EventId string `json:"event_name"` Origin string `json:"origin,omitempty"` - Attributes map[string]string `json:"data,omitempty"` } func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...ReportXrayEventData) error { From 8e71f891e5199dd677123ec52143b0d0964a4345 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 15 Aug 2023 09:15:07 +0300 Subject: [PATCH 07/17] fix tests --- xray/usage/reportusage_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index 67d90a71a..6b3496a61 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -41,14 +41,14 @@ func TestXrayUsageEventToJson(t *testing.T) { } jsonPatterns := []string{ `[{"product_name":"%s","event_name":"%s","origin":"API"}]`, - `[{"product_name":"%s","event_name":"%s","origin":"API","data":{"%s":"%s"}}]`, - `[{"product_name":"%s","event_name":"%s","origin":"API","data":{"%s":"%s","%s":"%s"}}]`, + `[{"data":{"%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API"}]`, + `[{"data":{"%s":"%s","%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API"}]`, } cases := []reportUsageTestCase{ {"jfrog-cli-go", "generic_audit", []ReportUsageAttribute{}}, {"frogbot", "scan_pull_request", []ReportUsageAttribute{{AttributeName: "clientId", AttributeValue: "repo1"}}}, - {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"}, {AttributeName: "clientId", AttributeValue: "user-hash"}}}, + {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"},{AttributeName: "clientId", AttributeValue: "user-hash"}}}, } for _, test := range cases { @@ -56,9 +56,9 @@ func TestXrayUsageEventToJson(t *testing.T) { expectedResult := "" switch { case len(test.Attributes) == 1: - expectedResult = fmt.Sprintf(jsonPatterns[1], test.productId, getExpectedEventName(test.productId, test.EventId), test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue) + expectedResult = fmt.Sprintf(jsonPatterns[1], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.productId, getExpectedEventName(test.productId, test.EventId)) case len(test.Attributes) == 2: - expectedResult = fmt.Sprintf(jsonPatterns[2], test.productId, getExpectedEventName(test.productId, test.EventId), test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue) + expectedResult = fmt.Sprintf(jsonPatterns[2], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue, test.productId, getExpectedEventName(test.productId, test.EventId)) default: expectedResult = fmt.Sprintf(jsonPatterns[0], test.productId, getExpectedEventName(test.productId, test.EventId)) } From be09ac8b8e1ebff003ce55c17358c933d4bb2a7a Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 15 Aug 2023 16:35:08 +0300 Subject: [PATCH 08/17] expose event name method --- xray/usage/reportusage.go | 2 +- xray/usage/reportusage_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index b03da159a..24258ed4f 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -111,7 +111,7 @@ func xrayUsageEventsToJson(events ...ReportXrayEventData) ([]byte, error) { return bodyContent, errorutils.CheckError(err) } -func getExpectedEventName(productId, commandName string) string { +func GetExpectedEventName(productId, commandName string) string { return "server_" + productId + "_" + commandName } diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index 6b3496a61..66c1bd10c 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -48,7 +48,7 @@ func TestXrayUsageEventToJson(t *testing.T) { cases := []reportUsageTestCase{ {"jfrog-cli-go", "generic_audit", []ReportUsageAttribute{}}, {"frogbot", "scan_pull_request", []ReportUsageAttribute{{AttributeName: "clientId", AttributeValue: "repo1"}}}, - {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"},{AttributeName: "clientId", AttributeValue: "user-hash"}}}, + {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"}, {AttributeName: "clientId", AttributeValue: "user-hash"}}}, } for _, test := range cases { From 1ebbfce51d8b8a06af0402fb955e53102fec6b72 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 15 Aug 2023 16:36:33 +0300 Subject: [PATCH 09/17] update use --- xray/usage/reportusage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 24258ed4f..ca4915c27 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -93,7 +93,7 @@ func isVersionCompatible(xrayVersion string) bool { } func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { - reportInfo := ReportXrayEventData{ProductId: productId, EventId: getExpectedEventName(productId, featureId), Origin: "API"} + reportInfo := ReportXrayEventData{ProductId: productId, EventId: GetExpectedEventName(productId, featureId), Origin: "API"} if len(additionalAttributes) > 0 { reportInfo.Attributes = make(map[string]string, len(additionalAttributes)) From a24f40115a7812f9efee8203d22556340afb0921 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 15 Aug 2023 16:46:22 +0300 Subject: [PATCH 10/17] fix tests --- xray/usage/reportusage.go | 4 ++-- xray/usage/reportusage_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index ca4915c27..f15c845e5 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -93,7 +93,7 @@ func isVersionCompatible(xrayVersion string) bool { } func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { - reportInfo := ReportXrayEventData{ProductId: productId, EventId: GetExpectedEventName(productId, featureId), Origin: "API"} + reportInfo := ReportXrayEventData{ProductId: productId, EventId: GetExpectedXrayEventName(productId, featureId), Origin: "API"} if len(additionalAttributes) > 0 { reportInfo.Attributes = make(map[string]string, len(additionalAttributes)) @@ -111,7 +111,7 @@ func xrayUsageEventsToJson(events ...ReportXrayEventData) ([]byte, error) { return bodyContent, errorutils.CheckError(err) } -func GetExpectedEventName(productId, commandName string) string { +func GetExpectedXrayEventName(productId, commandName string) string { return "server_" + productId + "_" + commandName } diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index 66c1bd10c..41fd3fac4 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -56,11 +56,11 @@ func TestXrayUsageEventToJson(t *testing.T) { expectedResult := "" switch { case len(test.Attributes) == 1: - expectedResult = fmt.Sprintf(jsonPatterns[1], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.productId, getExpectedEventName(test.productId, test.EventId)) + expectedResult = fmt.Sprintf(jsonPatterns[1], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) case len(test.Attributes) == 2: - expectedResult = fmt.Sprintf(jsonPatterns[2], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue, test.productId, getExpectedEventName(test.productId, test.EventId)) + expectedResult = fmt.Sprintf(jsonPatterns[2], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) default: - expectedResult = fmt.Sprintf(jsonPatterns[0], test.productId, getExpectedEventName(test.productId, test.EventId)) + expectedResult = fmt.Sprintf(jsonPatterns[0], test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) } // Run test t.Run(test.EventId, func(t *testing.T) { From 71b6b96990528aebd8547283251dfb795ee11209 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 15 Aug 2023 17:15:45 +0300 Subject: [PATCH 11/17] change to https --- xray/usage/reportusage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index f15c845e5..3eba53ccf 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -19,7 +19,7 @@ const ( minXrayVersion = "3.80.0" xrayUsageApiPath = "api/v1/usage/events/send" - ecosystemUsageApiUrl = "http://usage-ecosystem.jfrog.info/api/usage/report" + ecosystemUsageApiUrl = "https://usage-ecosystem.jfrog.info/api/usage/report" ReportUsagePrefix = "Usage Report: " ) From 18ed431c8b5f66ce98c8295a28d2739ce882bfc0 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 16 Aug 2023 10:15:33 +0300 Subject: [PATCH 12/17] expose the ecosystem base url --- xray/usage/reportusage.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 3eba53ccf..c74eb7ae5 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -18,9 +18,12 @@ import ( const ( minXrayVersion = "3.80.0" - xrayUsageApiPath = "api/v1/usage/events/send" - ecosystemUsageApiUrl = "https://usage-ecosystem.jfrog.info/api/usage/report" - ReportUsagePrefix = "Usage Report: " + xrayUsageApiPath = "api/v1/usage/events/send" + + EcosystemUsageBaseUrl = "https://usage-ecosystem.jfrog.info/" + ecosystemUsageApiPath = "api/usage/report" + + ReportUsagePrefix = "Usage Report: " ) type ReportUsageAttribute struct { @@ -170,5 +173,5 @@ func sendRequestToEcosystemService(content []byte) (resp *http.Response, respBod details := httputils.HttpClientDetails{} utils.AddHeader("Content-Type", "application/json", &details.Headers) - return client.SendPost(ecosystemUsageApiUrl, content, details, "") + return client.SendPost(EcosystemUsageBaseUrl+ecosystemUsageApiPath, content, details, "") } From 4ec0339e2b6e96272f73df1c5db1a7e19323d8b0 Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 22 Aug 2023 16:19:01 +0300 Subject: [PATCH 13/17] review changes --- access/services/login.go | 16 ++- artifactory/services/delete.go | 3 +- artifactory/services/discardBuilds.go | 10 +- artifactory/services/distribute.go | 7 +- artifactory/services/dockerpromote.go | 7 +- artifactory/services/download.go | 2 +- artifactory/services/go/publish.go | 11 +- artifactory/services/movecopy.go | 4 +- artifactory/services/ping.go | 4 +- artifactory/services/promote.go | 3 +- artifactory/services/props.go | 4 +- artifactory/services/readfile.go | 4 +- artifactory/services/security.go | 7 +- artifactory/services/storage.go | 12 +- artifactory/services/upload.go | 23 ++-- .../services/utils/artifactoryutils.go | 23 +--- artifactory/services/xrayscan.go | 2 +- artifactory/usage/reportusage.go | 88 +++++++----- artifactory/usage/reportusage_test.go | 8 +- lifecycle/services/delete.go | 4 +- lifecycle/services/operation.go | 3 +- lifecycle/services/status.go | 4 +- utils/usage/reportusage.go | 68 ++++++++++ utils/usage/reportusage_test.go | 64 +++++++++ utils/utils.go | 40 ++++++ xray/usage/reportusage.go | 128 ++++-------------- xray/usage/reportusage_test.go | 126 +++++------------ 27 files changed, 360 insertions(+), 315 deletions(-) create mode 100644 utils/usage/reportusage.go create mode 100644 utils/usage/reportusage_test.go diff --git a/access/services/login.go b/access/services/login.go index 7a6ce32b3..5dd9fdbf1 100644 --- a/access/services/login.go +++ b/access/services/login.go @@ -2,14 +2,16 @@ package services import ( "encoding/json" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "net/http" + "path" + "time" + + artifactoryutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" - "net/http" - "path" - "time" ) const ( @@ -37,7 +39,7 @@ func NewLoginService(client *jfroghttpclient.JfrogHttpClient) *LoginService { func (ls *LoginService) SendLoginAuthenticationRequest(uuid string) error { restAPI := path.Join(baseClientLoginApi, requestApi) - fullUrl, err := utils.BuildArtifactoryUrl(ls.ServiceDetails.GetUrl(), restAPI, make(map[string]string)) + fullUrl, err := utils.BuildUrl(ls.ServiceDetails.GetUrl(), restAPI, make(map[string]string)) if err != nil { return err } @@ -49,7 +51,7 @@ func (ls *LoginService) SendLoginAuthenticationRequest(uuid string) error { return errorutils.CheckError(err) } httpClientsDetails := ls.ServiceDetails.CreateHttpClientDetails() - utils.SetContentType("application/json", &httpClientsDetails.Headers) + artifactoryutils.SetContentType("application/json", &httpClientsDetails.Headers) resp, body, err := ls.client.SendPost(fullUrl, requestContent, &httpClientsDetails) if err != nil { return err @@ -89,7 +91,7 @@ func (ls *LoginService) GetLoginAuthenticationToken(uuid string) (token auth.Com func (ls *LoginService) getLoginAuthenticationToken(uuid string) (resp *http.Response, body []byte, err error) { restAPI := path.Join(baseClientLoginApi, tokenApi, uuid) - fullUrl, err := utils.BuildArtifactoryUrl(ls.ServiceDetails.GetUrl(), restAPI, make(map[string]string)) + fullUrl, err := utils.BuildUrl(ls.ServiceDetails.GetUrl(), restAPI, make(map[string]string)) if err != nil { return } diff --git a/artifactory/services/delete.go b/artifactory/services/delete.go index 58c1ec3b4..887eb264e 100644 --- a/artifactory/services/delete.go +++ b/artifactory/services/delete.go @@ -10,6 +10,7 @@ import ( "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" clientutils "github.com/jfrog/jfrog-client-go/utils" + urlutil "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/content" "github.com/jfrog/jfrog-client-go/utils/log" @@ -98,7 +99,7 @@ func (ds *DeleteService) createFileHandlerFunc(result *utils.Result) fileDeleteH return func(threadId int) error { result.TotalCount[threadId]++ logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, ds.DryRun) - deletePath, e := utils.BuildArtifactoryUrl(ds.GetArtifactoryDetails().GetUrl(), resultItem.GetItemRelativePath(), make(map[string]string)) + deletePath, e := urlutil.BuildUrl(ds.GetArtifactoryDetails().GetUrl(), resultItem.GetItemRelativePath(), make(map[string]string)) if e != nil { return e } diff --git a/artifactory/services/discardBuilds.go b/artifactory/services/discardBuilds.go index 6316fba17..7a4085d3a 100644 --- a/artifactory/services/discardBuilds.go +++ b/artifactory/services/discardBuilds.go @@ -2,16 +2,18 @@ package services import ( "encoding/json" - buildinfo "github.com/jfrog/build-info-go/entities" "net/http" "path" "strconv" "strings" "time" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + buildinfo "github.com/jfrog/build-info-go/entities" + + artifactoryutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -30,7 +32,7 @@ func (ds *DiscardBuildsService) DiscardBuilds(params DiscardBuildsParams) error discardUrl := ds.ArtDetails.GetUrl() restApi := path.Join("api/build/retention/", params.GetBuildName()) - requestFullUrl, err := utils.BuildArtifactoryUrl(discardUrl, restApi, make(map[string]string)) + requestFullUrl, err := utils.BuildUrl(discardUrl, restApi, make(map[string]string)) if err != nil { return err } @@ -63,7 +65,7 @@ func (ds *DiscardBuildsService) DiscardBuilds(params DiscardBuildsParams) error } httpClientsDetails := ds.getArtifactoryDetails().CreateHttpClientDetails() - utils.SetContentType("application/json", &httpClientsDetails.Headers) + artifactoryutils.SetContentType("application/json", &httpClientsDetails.Headers) resp, body, err := ds.client.SendPost(requestFullUrl, requestContent, &httpClientsDetails) if err != nil { diff --git a/artifactory/services/distribute.go b/artifactory/services/distribute.go index 83e2c89e5..e0dd48d6b 100644 --- a/artifactory/services/distribute.go +++ b/artifactory/services/distribute.go @@ -6,9 +6,10 @@ import ( "path" "strings" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + artifactoryutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -41,7 +42,7 @@ func (ds *DistributeService) BuildDistribute(params BuildDistributionParams) err distributeUrl := ds.ArtDetails.GetUrl() restApi := path.Join("api/build/distribute/", params.GetBuildName(), params.GetBuildNumber()) - requestFullUrl, err := utils.BuildArtifactoryUrl(distributeUrl, restApi, make(map[string]string)) + requestFullUrl, err := utils.BuildUrl(distributeUrl, restApi, make(map[string]string)) if err != nil { return err } @@ -70,7 +71,7 @@ func (ds *DistributeService) BuildDistribute(params BuildDistributionParams) err } httpClientsDetails := ds.getArtifactoryDetails().CreateHttpClientDetails() - utils.SetContentType("application/json", &httpClientsDetails.Headers) + artifactoryutils.SetContentType("application/json", &httpClientsDetails.Headers) resp, body, err := ds.client.SendPost(requestFullUrl, requestContent, &httpClientsDetails) if err != nil { diff --git a/artifactory/services/dockerpromote.go b/artifactory/services/dockerpromote.go index e1f7d359e..e51a573bd 100644 --- a/artifactory/services/dockerpromote.go +++ b/artifactory/services/dockerpromote.go @@ -5,9 +5,10 @@ import ( "net/http" "path" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + artifactoryutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -36,7 +37,7 @@ func (ps *DockerPromoteService) IsDryRun() bool { func (ps *DockerPromoteService) PromoteDocker(params DockerPromoteParams) error { // Create URL restApi := path.Join("api/docker", params.SourceRepo, "v2", "promote") - url, err := utils.BuildArtifactoryUrl(ps.GetArtifactoryDetails().GetUrl(), restApi, nil) + url, err := utils.BuildUrl(ps.GetArtifactoryDetails().GetUrl(), restApi, nil) if err != nil { return err } @@ -57,7 +58,7 @@ func (ps *DockerPromoteService) PromoteDocker(params DockerPromoteParams) error // Send POST request httpClientsDetails := ps.GetArtifactoryDetails().CreateHttpClientDetails() - utils.SetContentType("application/json", &httpClientsDetails.Headers) + artifactoryutils.SetContentType("application/json", &httpClientsDetails.Headers) resp, body, err := ps.client.SendPost(url, requestContent, &httpClientsDetails) if err != nil { return err diff --git a/artifactory/services/download.go b/artifactory/services/download.go index 941c7234c..c7e76df71 100644 --- a/artifactory/services/download.go +++ b/artifactory/services/download.go @@ -502,7 +502,7 @@ func (ds *DownloadService) createFileHandlerFunc(downloadParams DownloadParams, return func(downloadData DownloadData) parallel.TaskFunc { return func(threadId int) error { logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, ds.DryRun) - downloadPath, e := utils.BuildArtifactoryUrl(ds.GetArtifactoryDetails().GetUrl(), downloadData.Dependency.GetItemRelativePath(), make(map[string]string)) + downloadPath, e := clientutils.BuildUrl(ds.GetArtifactoryDetails().GetUrl(), downloadData.Dependency.GetItemRelativePath(), make(map[string]string)) if e != nil { return e } diff --git a/artifactory/services/go/publish.go b/artifactory/services/go/publish.go index 3e89c94cf..63426812d 100644 --- a/artifactory/services/go/publish.go +++ b/artifactory/services/go/publish.go @@ -8,9 +8,10 @@ import ( "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + artifactoryutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/content" @@ -37,8 +38,8 @@ func (gpc *GoPublishCommand) verifyCompatibleVersion(artifactoryVersion string) return nil } -func (gpc *GoPublishCommand) PublishPackage(params GoParams, client *jfroghttpclient.JfrogHttpClient, artDetails auth.ServiceDetails) (*utils.OperationSummary, error) { - goApiUrl, err := utils.BuildArtifactoryUrl(artDetails.GetUrl(), "api/go/", make(map[string]string)) +func (gpc *GoPublishCommand) PublishPackage(params GoParams, client *jfroghttpclient.JfrogHttpClient, artDetails auth.ServiceDetails) (*artifactoryutils.OperationSummary, error) { + goApiUrl, err := utils.BuildUrl(artDetails.GetUrl(), "api/go/", make(map[string]string)) if err != nil { return nil, err } @@ -72,7 +73,7 @@ func (gpc *GoPublishCommand) PublishPackage(params GoParams, client *jfroghttpcl return nil, err } - return &utils.OperationSummary{TotalSucceeded: totalSucceed, TotalFailed: totalFailed, TransferDetailsReader: content.NewContentReader(fileTransferDetailsTempFile, "files")}, nil + return &artifactoryutils.OperationSummary{TotalSucceeded: totalSucceed, TotalFailed: totalFailed, TransferDetailsReader: content.NewContentReader(fileTransferDetailsTempFile, "files")}, nil } func (gpc *GoPublishCommand) uploadFile(params GoParams, filePath string, moduleId, ext, goApiUrl string, filesDetails *[]clientutils.FileTransferDetails, pwa *GoPublishCommand) (success, failed int, err error) { @@ -108,7 +109,7 @@ func (gpc *GoPublishCommand) upload(localPath, pathInArtifactory, version, props if err != nil { return nil, err } - utils.AddChecksumHeaders(gpc.clientDetails.Headers, details) + artifactoryutils.AddChecksumHeaders(gpc.clientDetails.Headers, details) resp, body, err := gpc.client.UploadFile(localPath, goApiUrl, "", &gpc.clientDetails, nil) if err != nil { return nil, err diff --git a/artifactory/services/movecopy.go b/artifactory/services/movecopy.go index ed7a27bd9..1a9112566 100644 --- a/artifactory/services/movecopy.go +++ b/artifactory/services/movecopy.go @@ -237,7 +237,7 @@ func (mc *MoveCopyService) moveOrCopyFile(sourcePath, destPath, logMsgPrefix str } else { log.Info(logMsgPrefix + message) } - requestFullUrl, err := utils.BuildArtifactoryUrl(moveUrl, restApi, params) + requestFullUrl, err := clientutils.BuildUrl(moveUrl, restApi, params) if err != nil { return false, err } @@ -268,7 +268,7 @@ func (mc *MoveCopyService) createPathForMoveAction(destPath, logMsgPrefix string func (mc *MoveCopyService) createPathInArtifactory(destPath, logMsgPrefix string) (bool, error) { rtUrl := mc.GetArtifactoryDetails().GetUrl() - requestFullUrl, err := utils.BuildArtifactoryUrl(rtUrl, destPath, map[string]string{}) + requestFullUrl, err := clientutils.BuildUrl(rtUrl, destPath, map[string]string{}) if err != nil { return false, err } diff --git a/artifactory/services/ping.go b/artifactory/services/ping.go index dc2b5e3a7..45a798d18 100644 --- a/artifactory/services/ping.go +++ b/artifactory/services/ping.go @@ -3,9 +3,9 @@ package services import ( "net/http" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -32,7 +32,7 @@ func (ps *PingService) IsDryRun() bool { } func (ps *PingService) Ping() ([]byte, error) { - url, err := utils.BuildArtifactoryUrl(ps.GetArtifactoryDetails().GetUrl(), "api/system/ping", nil) + url, err := utils.BuildUrl(ps.GetArtifactoryDetails().GetUrl(), "api/system/ping", nil) if err != nil { return nil, err } diff --git a/artifactory/services/promote.go b/artifactory/services/promote.go index 794f0d7d2..a56c297f1 100644 --- a/artifactory/services/promote.go +++ b/artifactory/services/promote.go @@ -8,6 +8,7 @@ import ( "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) @@ -41,7 +42,7 @@ func (ps *PromoteService) BuildPromote(promotionParams PromotionParams) error { queryParams["project"] = promotionParams.ProjectKey } - requestFullUrl, err := utils.BuildArtifactoryUrl(promoteUrl, restApi, queryParams) + requestFullUrl, err := clientutils.BuildUrl(promoteUrl, restApi, queryParams) if err != nil { return err } diff --git a/artifactory/services/props.go b/artifactory/services/props.go index c716f865c..54c94b190 100644 --- a/artifactory/services/props.go +++ b/artifactory/services/props.go @@ -108,7 +108,7 @@ func (ps *PropsService) performRequest(propsParams PropsParams, isDelete bool) ( logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, ps.IsDryRun()) restAPI := path.Join("api", "storage", relativePath) - setPropertiesURL, err := utils.BuildArtifactoryUrl(ps.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) + setPropertiesURL, err := clientutils.BuildUrl(ps.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) if err != nil { return err } @@ -166,7 +166,7 @@ func NewPropsParams() PropsParams { func (ps *PropsService) GetItemProperties(relativePath string) (*utils.ItemProperties, error) { restAPI := path.Join("api", "storage", path.Clean(relativePath)) - propertiesURL, err := utils.BuildArtifactoryUrl(ps.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) + propertiesURL, err := clientutils.BuildUrl(ps.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) if err != nil { return nil, err } diff --git a/artifactory/services/readfile.go b/artifactory/services/readfile.go index 413890f66..75015304e 100644 --- a/artifactory/services/readfile.go +++ b/artifactory/services/readfile.go @@ -4,9 +4,9 @@ import ( "io" "net/http" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" ) @@ -39,7 +39,7 @@ func (ds *ReadFileService) SetDryRun(isDryRun bool) { } func (ds *ReadFileService) ReadRemoteFile(downloadPath string) (io.ReadCloser, error) { - readPath, err := utils.BuildArtifactoryUrl(ds.GetArtifactoryDetails().GetUrl(), downloadPath, make(map[string]string)) + readPath, err := utils.BuildUrl(ds.GetArtifactoryDetails().GetUrl(), downloadPath, make(map[string]string)) if err != nil { return nil, err } diff --git a/artifactory/services/security.go b/artifactory/services/security.go index bc837e248..78153ec33 100644 --- a/artifactory/services/security.go +++ b/artifactory/services/security.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" clientutils "github.com/jfrog/jfrog-client-go/utils" @@ -34,7 +33,7 @@ func (ss *SecurityService) getArtifactoryDetails() auth.ServiceDetails { // Create an API key for the current user. Returns an error if API key already exists - use regenerate API key instead. func (ss *SecurityService) CreateAPIKey() (string, error) { httpClientDetails := ss.ArtDetails.CreateHttpClientDetails() - reqURL, err := utils.BuildArtifactoryUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) + reqURL, err := clientutils.BuildUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) if err != nil { return "", err } @@ -55,7 +54,7 @@ func (ss *SecurityService) CreateAPIKey() (string, error) { func (ss *SecurityService) RegenerateAPIKey() (string, error) { httpClientDetails := ss.ArtDetails.CreateHttpClientDetails() - reqURL, err := utils.BuildArtifactoryUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) + reqURL, err := clientutils.BuildUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) if err != nil { return "", err } @@ -75,7 +74,7 @@ func (ss *SecurityService) RegenerateAPIKey() (string, error) { // Returns empty string if API Key wasn't generated. func (ss *SecurityService) GetAPIKey() (string, error) { httpClientDetails := ss.ArtDetails.CreateHttpClientDetails() - reqURL, err := utils.BuildArtifactoryUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) + reqURL, err := clientutils.BuildUrl(ss.ArtDetails.GetUrl(), APIKeyPath, nil) if err != nil { return "", err } diff --git a/artifactory/services/storage.go b/artifactory/services/storage.go index 97489e0fe..51c4453c8 100644 --- a/artifactory/services/storage.go +++ b/artifactory/services/storage.go @@ -2,14 +2,16 @@ package services import ( "encoding/json" + "net/http" + "path" + "strconv" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" - "net/http" - "path" - "strconv" ) type StorageService struct { @@ -34,7 +36,7 @@ func (s *StorageService) GetJfrogHttpClient() *jfroghttpclient.JfrogHttpClient { func (s *StorageService) FolderInfo(relativePath string) (*utils.FolderInfo, error) { client := s.GetJfrogHttpClient() restAPI := path.Join(StorageRestApi, path.Clean(relativePath)) - folderUrl, err := utils.BuildArtifactoryUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) + folderUrl, err := clientutils.BuildUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, make(map[string]string)) if err != nil { return nil, err } @@ -69,7 +71,7 @@ func (s *StorageService) FileList(relativePath string, optionalParams utils.File params["depth"] = strconv.Itoa(optionalParams.Depth) } - folderUrl, err := utils.BuildArtifactoryUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, params) + folderUrl, err := clientutils.BuildUrl(s.GetArtifactoryDetails().GetUrl(), restAPI, params) if err != nil { return nil, err } diff --git a/artifactory/services/upload.go b/artifactory/services/upload.go index 118267609..0abf34585 100644 --- a/artifactory/services/upload.go +++ b/artifactory/services/upload.go @@ -4,6 +4,16 @@ import ( "archive/zip" "errors" "fmt" + "io" + "net/http" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "github.com/jfrog/build-info-go/entities" biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/gofrog/parallel" @@ -18,15 +28,6 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "sync" ) type UploadService struct { @@ -742,7 +743,7 @@ func (us *UploadService) postUpload(uploadResult *utils.Result, threadId int, ar } func (us *UploadService) createFolderInArtifactory(artifact UploadData) error { - url, err := utils.BuildArtifactoryUrl(us.ArtDetails.GetUrl(), artifact.Artifact.TargetPath, make(map[string]string)) + url, err := clientutils.BuildUrl(us.ArtDetails.GetUrl(), artifact.Artifact.TargetPath, make(map[string]string)) url = clientutils.AddTrailingSlashIfNeeded(url) if err != nil { return err @@ -912,7 +913,7 @@ func (us *UploadService) addFileToZip(artifact *clientutils.Artifact, progressPr } func buildUploadUrls(artifactoryUrl, targetPath, buildProps, debianConfig string, targetProps *utils.Properties) (targetUrlWithProps string, err error) { - targetUrl, err := utils.BuildArtifactoryUrl(artifactoryUrl, targetPath, make(map[string]string)) + targetUrl, err := clientutils.BuildUrl(artifactoryUrl, targetPath, make(map[string]string)) if err != nil { return } diff --git a/artifactory/services/utils/artifactoryutils.go b/artifactory/services/utils/artifactoryutils.go index 9c51b8188..f89c3805d 100644 --- a/artifactory/services/utils/artifactoryutils.go +++ b/artifactory/services/utils/artifactoryutils.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net/http" - "net/url" "path" "strings" "sync" @@ -97,26 +96,6 @@ func AddHeader(headerName, headerValue string, headers *map[string]string) { (*headers)[headerName] = headerValue } -// Builds a URL for Artifactory requests. -// Pay attention: semicolons are escaped! -func BuildArtifactoryUrl(baseUrl, path string, params map[string]string) (string, error) { - u := url.URL{Path: path} - parsedUrl, err := url.Parse(baseUrl + u.String()) - err = errorutils.CheckError(err) - if err != nil { - return "", err - } - q := parsedUrl.Query() - for k, v := range params { - q.Set(k, v) - } - parsedUrl.RawQuery = q.Encode() - - // Semicolons are reserved as separators in some Artifactory APIs, so they'd better be encoded when used for other purposes - encodedUrl := strings.ReplaceAll(parsedUrl.String(), ";", url.QueryEscape(";")) - return encodedUrl, nil -} - func IsWildcardPattern(pattern string) bool { return strings.Contains(pattern, "*") || strings.HasSuffix(pattern, "/") || !strings.Contains(pattern, "/") } @@ -580,7 +559,7 @@ func GetBuildInfo(buildName, buildNumber, projectKey string, flags CommonConf) ( queryParams["project"] = projectKey } - requestFullUrl, err := BuildArtifactoryUrl(flags.GetArtifactoryDetails().GetUrl(), restApi, queryParams) + requestFullUrl, err := utils.BuildUrl(flags.GetArtifactoryDetails().GetUrl(), restApi, queryParams) if err != nil { return nil, false, err } diff --git a/artifactory/services/xrayscan.go b/artifactory/services/xrayscan.go index b2e6a897f..99542adbe 100644 --- a/artifactory/services/xrayscan.go +++ b/artifactory/services/xrayscan.go @@ -38,7 +38,7 @@ func NewXrayScanService(client *jfroghttpclient.JfrogHttpClient) *XrayScanServic // Deprecated legacy scan build. The new build scan command is in "/xray/commands/scan/buildscan" func (ps *XrayScanService) ScanBuild(scanParams XrayScanParams) ([]byte, error) { url := ps.ArtDetails.GetUrl() - requestFullUrl, err := utils.BuildArtifactoryUrl(url, apiUri, make(map[string]string)) + requestFullUrl, err := clientutils.BuildUrl(url, apiUri, make(map[string]string)) if err != nil { return []byte{}, err } diff --git a/artifactory/usage/reportusage.go b/artifactory/usage/reportusage.go index 7c9b5882f..08f2b0310 100644 --- a/artifactory/usage/reportusage.go +++ b/artifactory/usage/reportusage.go @@ -3,13 +3,14 @@ package usage import ( "encoding/json" "errors" - "fmt" - versionutil "github.com/jfrog/gofrog/version" + "net/http" + "github.com/jfrog/jfrog-client-go/artifactory" "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" - "net/http" ) const minArtifactoryVersion = "6.9.0" @@ -24,58 +25,82 @@ func (rua *ReportUsageAttribute) isEmpty() bool { return rua.AttributeName == "" } -func SendReportUsage(productId, commandName string, serviceManager artifactory.ArtifactoryServicesManager, attributes ...ReportUsageAttribute) error { +func validateAndGetUsageServerInfo(serviceManager artifactory.ArtifactoryServicesManager) (url string, clientDetails httputils.HttpClientDetails, err error) { config := serviceManager.GetConfig() if config == nil { - return errorutils.CheckErrorf(ReportUsagePrefix + "Expected full config, but no configuration exists.") + err = errorutils.CheckErrorf("Expected full config, but no configuration exists.") + return } rtDetails := config.GetServiceDetails() if rtDetails == nil { - return errorutils.CheckErrorf(ReportUsagePrefix + "Artifactory details not configured.") + err = errorutils.CheckErrorf("Artifactory details not configured.") + return } - url, err := utils.BuildArtifactoryUrl(rtDetails.GetUrl(), "api/system/usage", make(map[string]string)) - if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) - } - clientDetails := rtDetails.CreateHttpClientDetails() // Check Artifactory version artifactoryVersion, err := rtDetails.GetVersion() if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't get Artifactory version. Error: " + err.Error()) + err = errors.New("Couldn't get Artifactory version. Error: " + err.Error()) + return } - if !isVersionCompatible(artifactoryVersion) { - log.Debug(fmt.Sprintf(ReportUsagePrefix+"Expected Artifactory version %s or above, got %s", minArtifactoryVersion, artifactoryVersion)) - return nil + if e := clientutils.ValidateMinimumVersion(clientutils.Artifactory, minArtifactoryVersion, artifactoryVersion); e != nil { + log.Debug(ReportUsagePrefix, e.Error()) + return } - - bodyContent, err := reportUsageToJson(productId, commandName, attributes...) + url, err = clientutils.BuildUrl(rtDetails.GetUrl(), "api/system/usage", make(map[string]string)) if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) + err = errors.New(ReportUsagePrefix + err.Error()) + return } + clientDetails = rtDetails.CreateHttpClientDetails() + return +} + +func sendReport(url string, serviceManager artifactory.ArtifactoryServicesManager, clientDetails httputils.HttpClientDetails, bodyContent []byte) error { utils.AddHeader("Content-Type", "application/json", &clientDetails.Headers) resp, body, err := serviceManager.Client().SendPost(url, bodyContent, &clientDetails) if err != nil { return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) } - err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) if err != nil { return err } - - log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Artifactory response:", resp.Status) + log.Debug(ReportUsagePrefix, "Usage info sent successfully.", "Artifactory response:", resp.Status) return nil } -// Returns an error if the Artifactory version is not compatible -func isVersionCompatible(artifactoryVersion string) bool { - // API exists from Artifactory version 6.9.0 and above: - version := versionutil.NewVersion(artifactoryVersion) - return version.AtLeast(minArtifactoryVersion) +func ReportUsageToArtifactory(productId string, serviceManager artifactory.ArtifactoryServicesManager, features ...Feature) error { + url, clientDetails, err := validateAndGetUsageServerInfo(serviceManager) + if err != nil || url == "" { + return err + } + bodyContent, err := usageFeaturesToJson(productId, features...) + if err != nil { + return errors.New(ReportUsagePrefix + err.Error()) + } + return sendReport(url, serviceManager, clientDetails, bodyContent) +} + +func SendReportUsage(productId, commandName string, serviceManager artifactory.ArtifactoryServicesManager, attributes ...ReportUsageAttribute) error { + url, clientDetails, err := validateAndGetUsageServerInfo(serviceManager) + if err != nil || url == "" { + return err + } + bodyContent, err := reportUsageToJson(productId, commandName, attributes...) + if err != nil { + return errors.New(ReportUsagePrefix + err.Error()) + } + return sendReport(url, serviceManager, clientDetails, bodyContent) +} + +func usageFeaturesToJson(productId string, features ...Feature) ([]byte, error) { + params := reportUsageParams{ProductId: productId, Features: features} + bodyContent, err := json.Marshal(params) + return bodyContent, errorutils.CheckError(err) } func reportUsageToJson(productId, commandName string, attributes ...ReportUsageAttribute) ([]byte, error) { - featureInfo := feature{FeatureId: commandName} + featureInfo := Feature{FeatureId: commandName} if len(attributes) > 0 { featureInfo.Attributes = make(map[string]string, len(attributes)) for _, attribute := range attributes { @@ -84,17 +109,16 @@ func reportUsageToJson(productId, commandName string, attributes ...ReportUsageA } } } - params := reportUsageParams{ProductId: productId, Features: []feature{featureInfo}} - bodyContent, err := json.Marshal(params) - return bodyContent, errorutils.CheckError(err) + return usageFeaturesToJson(productId, featureInfo) } type reportUsageParams struct { ProductId string `json:"productId"` - Features []feature `json:"features,omitempty"` + Features []Feature `json:"features,omitempty"` } -type feature struct { +type Feature struct { FeatureId string `json:"featureId,omitempty"` Attributes map[string]string `json:"attributes,omitempty"` + ClientId string `json:"uniqueClientId,omitempty"` } diff --git a/artifactory/usage/reportusage_test.go b/artifactory/usage/reportusage_test.go index 211af4a3a..f77742670 100644 --- a/artifactory/usage/reportusage_test.go +++ b/artifactory/usage/reportusage_test.go @@ -24,9 +24,11 @@ func TestIsVersionCompatible(t *testing.T) { } for _, test := range tests { t.Run(test.artifactoryVersion, func(t *testing.T) { - result := isVersionCompatible(test.artifactoryVersion) - if test.expectedResult != result { - t.Error(fmt.Errorf("expected %t, got %t", test.expectedResult, result)) + err := utils.ValidateMinimumVersion(utils.Xray, test.artifactoryVersion, minArtifactoryVersion) + if test.expectedResult { + assert.NoError(t, err) + } else { + assert.Error(t, err) } }) } diff --git a/lifecycle/services/delete.go b/lifecycle/services/delete.go index 0910712ee..1596c949d 100644 --- a/lifecycle/services/delete.go +++ b/lifecycle/services/delete.go @@ -1,7 +1,7 @@ package services import ( - rtUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "net/http" "path" @@ -12,7 +12,7 @@ func (rbs *ReleaseBundlesService) DeleteReleaseBundle(rbDetails ReleaseBundleDet queryParams := getProjectQueryParam(params.ProjectKey) queryParams[async] = strconv.FormatBool(params.Async) restApi := path.Join(releaseBundleBaseApi, records, rbDetails.ReleaseBundleName, rbDetails.ReleaseBundleVersion) - requestFullUrl, err := rtUtils.BuildArtifactoryUrl(rbs.GetLifecycleDetails().GetUrl(), restApi, queryParams) + requestFullUrl, err := utils.BuildUrl(rbs.GetLifecycleDetails().GetUrl(), restApi, queryParams) if err != nil { return err } diff --git a/lifecycle/services/operation.go b/lifecycle/services/operation.go index 539c47e3f..3d44f6db6 100644 --- a/lifecycle/services/operation.go +++ b/lifecycle/services/operation.go @@ -5,6 +5,7 @@ import ( rtUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "net/http" @@ -36,7 +37,7 @@ type ReleaseBundleOperation interface { func (rbs *ReleaseBundlesService) doOperation(operation ReleaseBundleOperation) ([]byte, error) { queryParams := getProjectQueryParam(operation.getOperationParams().ProjectKey) queryParams[async] = strconv.FormatBool(operation.getOperationParams().Async) - requestFullUrl, err := rtUtils.BuildArtifactoryUrl(rbs.GetLifecycleDetails().GetUrl(), operation.getOperationRestApi(), queryParams) + requestFullUrl, err := utils.BuildUrl(rbs.GetLifecycleDetails().GetUrl(), operation.getOperationRestApi(), queryParams) if err != nil { return []byte{}, err } diff --git a/lifecycle/services/status.go b/lifecycle/services/status.go index ba696f3ce..35364a79a 100644 --- a/lifecycle/services/status.go +++ b/lifecycle/services/status.go @@ -3,7 +3,7 @@ package services import ( "encoding/json" "fmt" - rtUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" "net/http" @@ -52,7 +52,7 @@ func (rbs *ReleaseBundlesService) getReleaseBundleOperationStatus(restApi string } func (rbs *ReleaseBundlesService) getReleaseBundleStatus(restApi string, projectKey string) (statusResp ReleaseBundleStatusResponse, body []byte, err error) { - requestFullUrl, err := rtUtils.BuildArtifactoryUrl(rbs.GetLifecycleDetails().GetUrl(), restApi, getProjectQueryParam(projectKey)) + requestFullUrl, err := utils.BuildUrl(rbs.GetLifecycleDetails().GetUrl(), restApi, getProjectQueryParam(projectKey)) if err != nil { return } diff --git a/utils/usage/reportusage.go b/utils/usage/reportusage.go new file mode 100644 index 000000000..bdda90841 --- /dev/null +++ b/utils/usage/reportusage.go @@ -0,0 +1,68 @@ +package usage + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/httputils" +) + +const ( + ecosystemUsageApiPath = "https://usage-ecosystem.jfrog.io/api/usage/report" +) + +type ReportEcosystemUsageData struct { + ProductId string `json:"productId"` + AccountId string `json:"accountId"` + ClientId string `json:"clientId,omitempty"` + Features []string `json:"features"` +} + +func SendEcosystemUsageReports(reports ...ReportEcosystemUsageData) error { + if len(reports) == 0 { + return errorutils.CheckErrorf("Nothing to send.") + } + bodyContent, err := json.Marshal(reports) + if err != nil { + return errorutils.CheckError(err) + } + if err != nil { + return err + } + resp, body, err := sendRequestToEcosystemService(bodyContent) + if err != nil { + return errors.New("Couldn't send usage info to Ecosystem. Error: " + err.Error()) + } + + if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted); err != nil { + return err + } + return nil +} + +func CreateUsageData(productId, accountId, clientId string, features ...string) (reportInfo ReportEcosystemUsageData, err error) { + reportInfo = ReportEcosystemUsageData{ProductId: productId, AccountId: accountId, ClientId: clientId, Features: []string{}} + for _, feature := range features { + if feature != "" { + reportInfo.Features = append(reportInfo.Features, feature) + } + } + if len(reportInfo.Features) == 0 { + err = errorutils.CheckErrorf("expected at least one feature to report usage on") + } + return +} + +func sendRequestToEcosystemService(content []byte) (resp *http.Response, respBody []byte, err error) { + var client *httpclient.HttpClient + if client, err = httpclient.ClientBuilder().Build(); err != nil { + return + } + details := httputils.HttpClientDetails{} + utils.AddHeader("Content-Type", "application/json", &details.Headers) + return client.SendPost(ecosystemUsageApiPath, content, details, "Ecosystem-Usage") +} diff --git a/utils/usage/reportusage_test.go b/utils/usage/reportusage_test.go new file mode 100644 index 000000000..f0a71b4c6 --- /dev/null +++ b/utils/usage/reportusage_test.go @@ -0,0 +1,64 @@ +package usage + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +type reportUsageTestCase struct { + ProductId string + AccountId string + ClientId string + Features []string + JsonPattern string +} + +var cases = []reportUsageTestCase{ + {"jfrog-cli-go", "platform.jfrog.io", "", []string{}, `[{"productId":"%s","accountId":"%s","features":[]}]`}, + {"jfrog-cli-go", "platform.jfrog.io", "", []string{"generic_audit"}, `[{"productId":"%s","accountId":"%s","features":["%s"]}]`}, + {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request"}, `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s"]}]`}, + {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request", "npm-dep"}, `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s","%s"]}]`}, +} + +func TestEcosystemReportUsageToJson(t *testing.T) { + // Create the expected json + for _, test := range cases { + // Create the expected json + var expectedResult string + switch len(test.Features) { + case 1: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId, test.ClientId, test.Features[0]) + } else { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId, test.Features[0]) + } + case 2: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId, test.ClientId, test.Features[0], test.Features[1]) + } else { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId, test.Features[0], test.Features[1]) + } + default: + if test.ClientId != "" { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId, test.ClientId) + } else { + expectedResult = fmt.Sprintf(test.JsonPattern, test.ProductId, test.AccountId) + } + } + // Run test + t.Run("Features: "+strings.Join(test.Features, ","), func(t *testing.T) { + if data, err := CreateUsageData(test.ProductId, test.AccountId, test.ClientId, test.Features...); len(test.Features) > 0 { + assert.NoError(t, err) + body, err := json.Marshal([]ReportEcosystemUsageData{data}) + assert.NoError(t, err) + assert.Equal(t, expectedResult, string(body)) + } else { + assert.ErrorContains(t, err, "expected at least one feature to report usage on") + } + }) + } +} diff --git a/utils/utils.go b/utils/utils.go index ebd6488c1..0a8a2134a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,6 +16,7 @@ import ( "github.com/jfrog/build-info-go/entities" "github.com/jfrog/gofrog/stringutils" + "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" @@ -29,6 +30,18 @@ const ( Version = "1.31.4" ) +type MinVersionProduct string + +const ( + Artifactory MinVersionProduct = "JFrog Artifactory" + Xray MinVersionProduct = "JFrog Xray" + DataTransfer MinVersionProduct = "Data Transfer" + DockerApi MinVersionProduct = "Docker API" + Projects MinVersionProduct = "JFrog Projects" + + MinimumVersionMsg = "You are using %s version %s, while this operation requires version %s or higher." +) + // In order to limit the number of items loaded from a reader into the memory, we use a buffers with this size limit. var ( MaxBufferSize = 50000 @@ -52,6 +65,13 @@ func getDefaultUserAgent() string { return fmt.Sprintf("%s/%s", Agent, getVersion()) } +func ValidateMinimumVersion(product MinVersionProduct, currentVersion, minimumVersion string) error { + if !version.NewVersion(currentVersion).AtLeast(minimumVersion) { + return errorutils.CheckErrorf(MinimumVersionMsg, product, currentVersion, minimumVersion) + } + return nil +} + // Get the local root path, from which to start collecting artifacts to be used for: // 1. Uploaded to Artifactory, // 2. Adding to the local build-info, to be later published to Artifactory. @@ -210,6 +230,26 @@ func cleanPath(path string) string { return path } +// Builds a URL for Artifactory/Xray requests. +// Pay attention: semicolons are escaped! +func BuildUrl(baseUrl, path string, params map[string]string) (string, error) { + u := url.URL{Path: path} + parsedUrl, err := url.Parse(baseUrl + u.String()) + err = errorutils.CheckError(err) + if err != nil { + return "", err + } + q := parsedUrl.Query() + for k, v := range params { + q.Set(k, v) + } + parsedUrl.RawQuery = q.Encode() + + // Semicolons are reserved as separators in some Artifactory APIs, so they'd better be encoded when used for other purposes + encodedUrl := strings.ReplaceAll(parsedUrl.String(), ";", url.QueryEscape(";")) + return encodedUrl, nil +} + // BuildTargetPath Replaces matched regular expression from path to corresponding placeholder {i} at target. // Example 1: // diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index c74eb7ae5..0a01c3d6b 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -3,27 +3,19 @@ package usage import ( "encoding/json" "errors" - "fmt" "net/http" - versionutil "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-client-go/artifactory/services/utils" - "github.com/jfrog/jfrog-client-go/http/httpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/io/httputils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" ) const ( - minXrayVersion = "3.80.0" - - xrayUsageApiPath = "api/v1/usage/events/send" - - EcosystemUsageBaseUrl = "https://usage-ecosystem.jfrog.info/" - ecosystemUsageApiPath = "api/usage/report" - - ReportUsagePrefix = "Usage Report: " + minXrayVersion = "3.81.4" + xrayUsageApiPath = "api/v1/usage/events/send" + ReportUsagePrefix = "Usage Report:" ) type ReportUsageAttribute struct { @@ -44,33 +36,32 @@ type ReportXrayEventData struct { func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...ReportXrayEventData) error { if len(events) == 0 { - return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") + return errorutils.CheckErrorf("Nothing to send.") } config := serviceManager.Config() if config == nil { - return errorutils.CheckErrorf(ReportUsagePrefix + "Expected full config, but no configuration exists.") + return errorutils.CheckErrorf("Expected full config, but no configuration exists.") } xrDetails := config.GetServiceDetails() if xrDetails == nil { - return errorutils.CheckErrorf(ReportUsagePrefix + "Xray details not configured.") + return errorutils.CheckErrorf("Xray details not configured.") } xrayVersion, err := xrDetails.GetVersion() if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't get Xray version. Error: " + err.Error()) + return errors.New("Couldn't get Xray version. Error: " + err.Error()) } - if !isVersionCompatible(xrayVersion) { - log.Debug(fmt.Sprintf(ReportUsagePrefix+"Expected Xray version %s or above, got %s", minXrayVersion, xrayVersion)) + if e := clientutils.ValidateMinimumVersion(clientutils.Xray, minXrayVersion, xrayVersion); e != nil { + log.Debug(ReportUsagePrefix, e.Error()) return nil } - - url, err := utils.BuildArtifactoryUrl(xrDetails.GetUrl(), xrayUsageApiPath, make(map[string]string)) + url, err := clientutils.BuildUrl(xrDetails.GetUrl(), xrayUsageApiPath, make(map[string]string)) if err != nil { return errors.New(ReportUsagePrefix + err.Error()) } clientDetails := xrDetails.CreateHttpClientDetails() - bodyContent, err := xrayUsageEventsToJson(events...) - if err != nil { + bodyContent, err := json.Marshal(events) + if errorutils.CheckError(err) != nil { return errors.New(ReportUsagePrefix + err.Error()) } utils.AddHeader("Content-Type", "application/json", &clientDetails.Headers) @@ -78,100 +69,27 @@ func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...Repo if err != nil { return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) } - err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) if err != nil { return err } - - log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Xray response:", resp.Status) return nil } -// Returns an error if the Xray version is not compatible to run usage api -func isVersionCompatible(xrayVersion string) bool { - // API exists from Xray version 3.80.0 and above: - version := versionutil.NewVersion(xrayVersion) - return version.AtLeast(minXrayVersion) -} - -func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) ReportXrayEventData { - reportInfo := ReportXrayEventData{ProductId: productId, EventId: GetExpectedXrayEventName(productId, featureId), Origin: "API"} - - if len(additionalAttributes) > 0 { - reportInfo.Attributes = make(map[string]string, len(additionalAttributes)) - for _, attribute := range additionalAttributes { - if !attribute.isEmpty() { - reportInfo.Attributes[attribute.AttributeName] = attribute.AttributeValue - } - } - } - return reportInfo -} - -func xrayUsageEventsToJson(events ...ReportXrayEventData) ([]byte, error) { - bodyContent, err := json.Marshal(events) - return bodyContent, errorutils.CheckError(err) -} - -func GetExpectedXrayEventName(productId, commandName string) string { - return "server_" + productId + "_" + commandName -} - -type ReportEcosystemUsageData struct { - ProductId string `json:"productId"` - AccountId string `json:"accountId"` - ClientId string `json:"clientId,omitempty"` - Features []string `json:"features"` -} - -func SendEcosystemUsageReports(reports ...ReportEcosystemUsageData) error { - if len(reports) == 0 { - return errorutils.CheckErrorf(ReportUsagePrefix + "Nothing to send.") - } - bodyContent, err := ecosystemUsageReportsToJson(reports...) - if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) - } - - resp, body, err := sendRequestToEcosystemService(bodyContent) - if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't send usage info to Ecosystem. Error: " + err.Error()) - } - - if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted); err != nil { - return err +func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) (event ReportXrayEventData) { + event = ReportXrayEventData{ProductId: productId, EventId: GetExpectedXrayEventName(productId, featureId), Origin: "API_CLI"} + if len(additionalAttributes) == 0 { + return } - - log.Debug(ReportUsagePrefix+"Usage info sent successfully.", "Ecosystem response:", resp.Status) - return nil -} - -func CreateUsageData(productId, accountId, clientId string, features ...string) (reportInfo ReportEcosystemUsageData, err error) { - reportInfo = ReportEcosystemUsageData{ProductId: productId, AccountId: accountId, ClientId: clientId, Features: []string{}} - for _, feature := range features { - if feature != "" { - reportInfo.Features = append(reportInfo.Features, feature) + event.Attributes = make(map[string]string, len(additionalAttributes)) + for _, attribute := range additionalAttributes { + if !attribute.isEmpty() { + event.Attributes[attribute.AttributeName] = attribute.AttributeValue } } - if len(reportInfo.Features) == 0 { - err = fmt.Errorf("expected at least one feature to report usage on") - } return } -func ecosystemUsageReportsToJson(events ...ReportEcosystemUsageData) ([]byte, error) { - bodyContent, err := json.Marshal(events) - return bodyContent, errorutils.CheckError(err) -} - -func sendRequestToEcosystemService(content []byte) (resp *http.Response, respBody []byte, err error) { - var client *httpclient.HttpClient - if client, err = httpclient.ClientBuilder().Build(); err != nil { - return - } - - details := httputils.HttpClientDetails{} - utils.AddHeader("Content-Type", "application/json", &details.Headers) - return client.SendPost(EcosystemUsageBaseUrl+ecosystemUsageApiPath, content, details, "") +func GetExpectedXrayEventName(productId, commandName string) string { + return "server_" + productId + "_" + commandName } diff --git a/xray/usage/reportusage_test.go b/xray/usage/reportusage_test.go index 41fd3fac4..fb1d269cc 100644 --- a/xray/usage/reportusage_test.go +++ b/xray/usage/reportusage_test.go @@ -1,8 +1,8 @@ package usage import ( + "encoding/json" "fmt" - "strings" "testing" "github.com/jfrog/jfrog-client-go/utils" @@ -11,122 +11,60 @@ import ( func TestIsXrayVersionCompatible(t *testing.T) { tests := []struct { - xrayVersion string - expectedResult bool + xrayVersion string + compatible bool }{ {"1.2.0", false}, {"2.9.0", false}, {"2.0.0", false}, - {"3.79.3", false}, - {"3.80.0", true}, + {"3.80.3", false}, + {"3.81.4", true}, {utils.Development, true}, - {"3.81.2", true}, + {"3.83.2", true}, {"4.15.2", true}, } for _, test := range tests { t.Run(test.xrayVersion, func(t *testing.T) { - result := isVersionCompatible(test.xrayVersion) - if test.expectedResult != result { - t.Error(fmt.Errorf("expected %t, got %t", test.expectedResult, result)) + err := utils.ValidateMinimumVersion(utils.Xray, test.xrayVersion, minXrayVersion) + if test.compatible { + assert.NoError(t, err) + } else { + assert.Error(t, err) } }) } } -func TestXrayUsageEventToJson(t *testing.T) { - type reportUsageTestCase struct { - productId string - EventId string - Attributes []ReportUsageAttribute - } - jsonPatterns := []string{ - `[{"product_name":"%s","event_name":"%s","origin":"API"}]`, - `[{"data":{"%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API"}]`, - `[{"data":{"%s":"%s","%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API"}]`, - } +type reportUsageTestCase struct { + productId string + EventId string + Attributes []ReportUsageAttribute + jsonPattern string +} - cases := []reportUsageTestCase{ - {"jfrog-cli-go", "generic_audit", []ReportUsageAttribute{}}, - {"frogbot", "scan_pull_request", []ReportUsageAttribute{{AttributeName: "clientId", AttributeValue: "repo1"}}}, - {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"}, {AttributeName: "clientId", AttributeValue: "user-hash"}}}, - } +var reportCases = []reportUsageTestCase{ + {"jfrog-cli-go", "generic_audit", []ReportUsageAttribute{}, `[{"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`}, + {"frogbot", "scan_pull_request", []ReportUsageAttribute{{AttributeName: "clientId", AttributeValue: "repo1"}}, `[{"data":{"%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`}, + {"jfrog-idea-plugin", "ci", []ReportUsageAttribute{{AttributeName: "buildNumber", AttributeValue: "1023456"}, {AttributeName: "clientId", AttributeValue: "user-hash"}}, `[{"data":{"%s":"%s","%s":"%s"},"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`}, +} - for _, test := range cases { +func TestXrayUsageEventToJson(t *testing.T) { + for _, test := range reportCases { // Create the expected json - expectedResult := "" - switch { - case len(test.Attributes) == 1: - expectedResult = fmt.Sprintf(jsonPatterns[1], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) - case len(test.Attributes) == 2: - expectedResult = fmt.Sprintf(jsonPatterns[2], test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) + var expectedResult string + switch len(test.Attributes) { + case 1: + expectedResult = fmt.Sprintf(test.jsonPattern, test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) + case 2: + expectedResult = fmt.Sprintf(test.jsonPattern, test.Attributes[0].AttributeName, test.Attributes[0].AttributeValue, test.Attributes[1].AttributeName, test.Attributes[1].AttributeValue, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) default: - expectedResult = fmt.Sprintf(jsonPatterns[0], test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) + expectedResult = fmt.Sprintf(test.jsonPattern, test.productId, GetExpectedXrayEventName(test.productId, test.EventId)) } // Run test t.Run(test.EventId, func(t *testing.T) { - body, err := xrayUsageEventsToJson(CreateUsageEvent(test.productId, test.EventId, test.Attributes...)) + body, err := json.Marshal([]ReportXrayEventData{CreateUsageEvent(test.productId, test.EventId, test.Attributes...)}) assert.NoError(t, err) assert.Equal(t, expectedResult, string(body)) }) } } - -func TestEcosystemReportUsageToJson(t *testing.T) { - type reportUsageTestCase struct { - ProductId string - AccountId string - ClientId string - Features []string - } - jsonPatterns := []string{ - `[{"productId":"%s","accountId":"%s","features":[]}]`, - `[{"productId":"%s","accountId":"%s","features":["%s"]}]`, - `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s"]}]`, - `[{"productId":"%s","accountId":"%s","clientId":"%s","features":["%s","%s"]}]`, - } - - cases := []reportUsageTestCase{ - {"jfrog-cli-go", "platform.jfrog.io", "", []string{}}, - {"jfrog-cli-go", "platform.jfrog.io", "", []string{"generic_audit"}}, - {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request"}}, - {"frogbot", "platform.jfrog.io", "repo1", []string{"scan_pull_request", "npm-dep"}}, - } - - // Create the expected json - for _, test := range cases { - // Create the expected json - expectedResult := "" - switch { - case len(test.Features) == 1: - if test.ClientId != "" { - expectedResult = fmt.Sprintf(jsonPatterns[2], test.ProductId, test.AccountId, test.ClientId, test.Features[0]) - } else { - expectedResult = fmt.Sprintf(jsonPatterns[1], test.ProductId, test.AccountId, test.Features[0]) - } - case len(test.Features) == 2: - if test.ClientId != "" { - expectedResult = fmt.Sprintf(jsonPatterns[3], test.ProductId, test.AccountId, test.ClientId, test.Features[0], test.Features[1]) - } else { - expectedResult = fmt.Sprintf(jsonPatterns[3], test.ProductId, test.AccountId, test.Features[0], test.Features[1]) - } - default: - if test.ClientId != "" { - expectedResult = fmt.Sprintf(jsonPatterns[0], test.ProductId, test.AccountId, test.ClientId) - } else { - expectedResult = fmt.Sprintf(jsonPatterns[0], test.ProductId, test.AccountId) - } - } - // Run test - t.Run(strings.Join(test.Features, ","), func(t *testing.T) { - if data, err := CreateUsageData(test.ProductId, test.AccountId, test.ClientId, test.Features...); len(test.Features) > 0 { - assert.NoError(t, err) - body, err := ecosystemUsageReportsToJson(data) - assert.NoError(t, err) - assert.Equal(t, expectedResult, string(body)) - } else { - assert.Error(t, err) - } - - }) - } -} From c4bf12d935b59867be248eca510a63dd0967260a Mon Sep 17 00:00:00 2001 From: attiasas Date: Tue, 22 Aug 2023 17:01:10 +0300 Subject: [PATCH 14/17] remove logs if error and prefixes --- artifactory/usage/reportusage.go | 12 +++++------- xray/usage/reportusage.go | 9 ++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/artifactory/usage/reportusage.go b/artifactory/usage/reportusage.go index 08f2b0310..7d721fa17 100644 --- a/artifactory/usage/reportusage.go +++ b/artifactory/usage/reportusage.go @@ -14,7 +14,6 @@ import ( ) const minArtifactoryVersion = "6.9.0" -const ReportUsagePrefix = "Usage Report: " type ReportUsageAttribute struct { AttributeName string @@ -43,12 +42,12 @@ func validateAndGetUsageServerInfo(serviceManager artifactory.ArtifactoryService return } if e := clientutils.ValidateMinimumVersion(clientutils.Artifactory, minArtifactoryVersion, artifactoryVersion); e != nil { - log.Debug(ReportUsagePrefix, e.Error()) + log.Debug("Usage Report:", e.Error()) return } url, err = clientutils.BuildUrl(rtDetails.GetUrl(), "api/system/usage", make(map[string]string)) if err != nil { - err = errors.New(ReportUsagePrefix + err.Error()) + err = errors.New(err.Error()) return } clientDetails = rtDetails.CreateHttpClientDetails() @@ -59,13 +58,12 @@ func sendReport(url string, serviceManager artifactory.ArtifactoryServicesManage utils.AddHeader("Content-Type", "application/json", &clientDetails.Headers) resp, body, err := serviceManager.Client().SendPost(url, bodyContent, &clientDetails) if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) + return errors.New("Couldn't send usage info. Error: " + err.Error()) } err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) if err != nil { return err } - log.Debug(ReportUsagePrefix, "Usage info sent successfully.", "Artifactory response:", resp.Status) return nil } @@ -76,7 +74,7 @@ func ReportUsageToArtifactory(productId string, serviceManager artifactory.Artif } bodyContent, err := usageFeaturesToJson(productId, features...) if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) + return err } return sendReport(url, serviceManager, clientDetails, bodyContent) } @@ -88,7 +86,7 @@ func SendReportUsage(productId, commandName string, serviceManager artifactory.A } bodyContent, err := reportUsageToJson(productId, commandName, attributes...) if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) + return err } return sendReport(url, serviceManager, clientDetails, bodyContent) } diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 0a01c3d6b..8b35e2f39 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -15,7 +15,6 @@ import ( const ( minXrayVersion = "3.81.4" xrayUsageApiPath = "api/v1/usage/events/send" - ReportUsagePrefix = "Usage Report:" ) type ReportUsageAttribute struct { @@ -51,23 +50,23 @@ func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...Repo return errors.New("Couldn't get Xray version. Error: " + err.Error()) } if e := clientutils.ValidateMinimumVersion(clientutils.Xray, minXrayVersion, xrayVersion); e != nil { - log.Debug(ReportUsagePrefix, e.Error()) + log.Debug("Usage Report:", e.Error()) return nil } url, err := clientutils.BuildUrl(xrDetails.GetUrl(), xrayUsageApiPath, make(map[string]string)) if err != nil { - return errors.New(ReportUsagePrefix + err.Error()) + return errors.New(err.Error()) } clientDetails := xrDetails.CreateHttpClientDetails() bodyContent, err := json.Marshal(events) if errorutils.CheckError(err) != nil { - return errors.New(ReportUsagePrefix + err.Error()) + return errors.New(err.Error()) } utils.AddHeader("Content-Type", "application/json", &clientDetails.Headers) resp, body, err := serviceManager.Client().SendPost(url, bodyContent, &clientDetails) if err != nil { - return errors.New(ReportUsagePrefix + "Couldn't send usage info. Error: " + err.Error()) + return errors.New("Couldn't send usage info. Error: " + err.Error()) } err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) if err != nil { From 8dc6b7b748316ba1e476dc23dcad0d4930ba7164 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 23 Aug 2023 14:02:52 +0300 Subject: [PATCH 15/17] format --- xray/usage/reportusage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 8b35e2f39..7079b81b3 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -13,8 +13,8 @@ import ( ) const ( - minXrayVersion = "3.81.4" - xrayUsageApiPath = "api/v1/usage/events/send" + minXrayVersion = "3.81.4" + xrayUsageApiPath = "api/v1/usage/events/send" ) type ReportUsageAttribute struct { From fddd2341af073d6dcc1bc7ac6eecbd4a26d98678 Mon Sep 17 00:00:00 2001 From: attiasas Date: Wed, 23 Aug 2023 16:58:11 +0300 Subject: [PATCH 16/17] fix minimum version check --- artifactory/usage/reportusage.go | 2 +- xray/usage/reportusage.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artifactory/usage/reportusage.go b/artifactory/usage/reportusage.go index 7d721fa17..4f0839369 100644 --- a/artifactory/usage/reportusage.go +++ b/artifactory/usage/reportusage.go @@ -41,7 +41,7 @@ func validateAndGetUsageServerInfo(serviceManager artifactory.ArtifactoryService err = errors.New("Couldn't get Artifactory version. Error: " + err.Error()) return } - if e := clientutils.ValidateMinimumVersion(clientutils.Artifactory, minArtifactoryVersion, artifactoryVersion); e != nil { + if e := clientutils.ValidateMinimumVersion(clientutils.Artifactory, artifactoryVersion, minArtifactoryVersion); e != nil { log.Debug("Usage Report:", e.Error()) return } diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index 7079b81b3..dfcb6e836 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -49,7 +49,7 @@ func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...Repo if err != nil { return errors.New("Couldn't get Xray version. Error: " + err.Error()) } - if e := clientutils.ValidateMinimumVersion(clientutils.Xray, minXrayVersion, xrayVersion); e != nil { + if e := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, minXrayVersion); e != nil { log.Debug("Usage Report:", e.Error()) return nil } From a72dae35f1624537a3919ac0ea38b9408f5f45e2 Mon Sep 17 00:00:00 2001 From: attiasas Date: Mon, 28 Aug 2023 14:58:56 +0300 Subject: [PATCH 17/17] review changes --- artifactory/usage/reportusage_test.go | 2 +- utils/usage/reportusage.go | 9 +-------- utils/utils.go | 3 +-- xray/usage/reportusage.go | 6 +----- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/artifactory/usage/reportusage_test.go b/artifactory/usage/reportusage_test.go index f77742670..acc74cfad 100644 --- a/artifactory/usage/reportusage_test.go +++ b/artifactory/usage/reportusage_test.go @@ -28,7 +28,7 @@ func TestIsVersionCompatible(t *testing.T) { if test.expectedResult { assert.NoError(t, err) } else { - assert.Error(t, err) + assert.ErrorContains(t, err, fmt.Sprintf(utils.MinimumVersionMsg, utils.Xray, test.artifactoryVersion, minArtifactoryVersion)) } }) } diff --git a/utils/usage/reportusage.go b/utils/usage/reportusage.go index bdda90841..765486f8c 100644 --- a/utils/usage/reportusage.go +++ b/utils/usage/reportusage.go @@ -30,18 +30,11 @@ func SendEcosystemUsageReports(reports ...ReportEcosystemUsageData) error { if err != nil { return errorutils.CheckError(err) } - if err != nil { - return err - } resp, body, err := sendRequestToEcosystemService(bodyContent) if err != nil { return errors.New("Couldn't send usage info to Ecosystem. Error: " + err.Error()) } - - if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted); err != nil { - return err - } - return nil + return errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) } func CreateUsageData(productId, accountId, clientId string, features ...string) (reportInfo ReportEcosystemUsageData, err error) { diff --git a/utils/utils.go b/utils/utils.go index 2cfae039b..152eb8a6a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -235,8 +235,7 @@ func cleanPath(path string) string { func BuildUrl(baseUrl, path string, params map[string]string) (string, error) { u := url.URL{Path: path} parsedUrl, err := url.Parse(baseUrl + u.String()) - err = errorutils.CheckError(err) - if err != nil { + if err = errorutils.CheckError(err); err != nil { return "", err } q := parsedUrl.Query() diff --git a/xray/usage/reportusage.go b/xray/usage/reportusage.go index dfcb6e836..365486c91 100644 --- a/xray/usage/reportusage.go +++ b/xray/usage/reportusage.go @@ -68,11 +68,7 @@ func SendXrayUsageEvents(serviceManager xray.XrayServicesManager, events ...Repo if err != nil { return errors.New("Couldn't send usage info. Error: " + err.Error()) } - err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) - if err != nil { - return err - } - return nil + return errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusAccepted) } func CreateUsageEvent(productId, featureId string, additionalAttributes ...ReportUsageAttribute) (event ReportXrayEventData) {