diff --git a/commands.go b/commands.go index e653eb2fe..24b44b545 100644 --- a/commands.go +++ b/commands.go @@ -89,15 +89,14 @@ func Exec(command FrogbotCommand, commandName string) (err error) { } // Send a usage report - usageReportSent := make(chan error) - go utils.ReportUsage(commandName, frogbotDetails.ServerDetails, usageReportSent) + waitForUsageResponse := utils.ReportUsageOnCommand(commandName, frogbotDetails.ServerDetails, frogbotDetails.Repositories) // Invoke the command interface log.Info(fmt.Sprintf("Running Frogbot %q command", commandName)) err = command.Run(frogbotDetails.Repositories, frogbotDetails.GitClient) - // Wait for a signal, letting us know that the usage reporting is done. - <-usageReportSent + // Wait for usage reporting to finish. + waitForUsageResponse() if err == nil { log.Info(fmt.Sprintf("Frogbot %q command finished successfully", commandName)) diff --git a/go.mod b/go.mod index 7c0550124..aa1db34f7 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,11 @@ require ( github.com/go-git/go-git/v5 v5.8.1 github.com/golang/mock v1.6.0 github.com/google/go-github/v45 v45.2.0 - github.com/jfrog/build-info-go v1.9.8 + github.com/jfrog/build-info-go v1.9.9 github.com/jfrog/froggit-go v1.13.4 github.com/jfrog/gofrog v1.3.0 - github.com/jfrog/jfrog-cli-core/v2 v2.41.2 - github.com/jfrog/jfrog-client-go v1.31.5 + github.com/jfrog/jfrog-cli-core/v2 v2.41.4 + github.com/jfrog/jfrog-client-go v1.31.6 github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.25.7 diff --git a/go.sum b/go.sum index ae521ea94..7d43302b9 100644 --- a/go.sum +++ b/go.sum @@ -875,16 +875,16 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= -github.com/jfrog/build-info-go v1.9.8 h1:D8/ga+YgQpqp/CJj2zteS4/twmSy8zvm1v9lCd2Kv1M= -github.com/jfrog/build-info-go v1.9.8/go.mod h1:t31QRpH5xUJKw8XkQlAA+Aq7aanyS1rrzpcK8xSNVts= +github.com/jfrog/build-info-go v1.9.9 h1:YMA9okHawBNL8SrCWzqULSf5M4W+YnWyUhmkWSjoXEE= +github.com/jfrog/build-info-go v1.9.9/go.mod h1:t31QRpH5xUJKw8XkQlAA+Aq7aanyS1rrzpcK8xSNVts= github.com/jfrog/froggit-go v1.13.4 h1:+pHq3iNkKFvojXCJ74sDV+UsV4Thsi03dsu36jkS7Rc= github.com/jfrog/froggit-go v1.13.4/go.mod h1:0jRAaZZusaFFnITosmx6CA60SKryuoaCasJyUrP/c1s= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= -github.com/jfrog/jfrog-cli-core/v2 v2.41.2 h1:Gnp93JcDAnHHCN3SHqam2K/S9yJcytS4q+MQd6vv9Ck= -github.com/jfrog/jfrog-cli-core/v2 v2.41.2/go.mod h1:YqB9rEJF1P7uGLIPUvF5qdDDf1zM5f4DneIQNkqyAfs= -github.com/jfrog/jfrog-client-go v1.31.5 h1:dYVgIJzMwX+EU9GEELKPSHFLyfW6UrrjZWMEZtAyx6A= -github.com/jfrog/jfrog-client-go v1.31.5/go.mod h1:icb00ZJN/mMMNkQduHDkzpqsXH9Flwi3f3COYexq3Nc= +github.com/jfrog/jfrog-cli-core/v2 v2.41.4 h1:+V35NN+UaKl6ZFSjAyZFZ4VijCgsORnGsHug02DROdE= +github.com/jfrog/jfrog-cli-core/v2 v2.41.4/go.mod h1:Mi3WFUzG2CU6tlLpGsMNRaKkhH/tIMuci4tjnPZ9S3M= +github.com/jfrog/jfrog-client-go v1.31.6 h1:uWuyT4BDm9s5ES6oDTBny9Gl6yf8iKFjcbmHSHQZrDc= +github.com/jfrog/jfrog-client-go v1.31.6/go.mod h1:icb00ZJN/mMMNkQduHDkzpqsXH9Flwi3f3COYexq3Nc= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= diff --git a/utils/utils.go b/utils/utils.go index b7a0540ab..c39d0c475 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,23 +6,23 @@ import ( "encoding/hex" "errors" "fmt" + "os" + "regexp" + "sort" + "strings" + "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/froggit-go/vcsutils" "github.com/jfrog/gofrog/version" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/utils/usage" audit "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/generic" "github.com/jfrog/jfrog-cli-core/v2/xray/formats" xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" - "github.com/jfrog/jfrog-client-go/artifactory/usage" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/jfrog/jfrog-client-go/utils/log" - "os" - "regexp" - "sort" - "strings" ) const ( @@ -148,29 +148,35 @@ func Chdir(dir string) (cbk func() error, err error) { return func() error { return os.Chdir(wd) }, err } -func ReportUsage(commandName string, serverDetails *config.ServerDetails, usageReportSent chan<- error) { - var err error - defer func() { - // The usage reporting is meant to run asynchronously, so that the actual action isn't delayed. - // It is however important to the application to not exit before the reporting is finished. That is, in case the reporting takes longer than the action. - usageReportSent <- err - }() - if serverDetails.ArtifactoryUrl == "" { - return - } - log.Debug(usage.ReportUsagePrefix, "Sending info...") - serviceManager, err := utils.CreateServiceManager(serverDetails, -1, 0, false) +func ReportUsageOnCommand(commandName string, serverDetails *config.ServerDetails, repositories RepoAggregator) func() { + reporter := usage.NewUsageReporter(productId, serverDetails) + reports, err := convertToUsageReports(commandName, repositories) if err != nil { - log.Debug(usage.ReportUsagePrefix, err.Error()) - return + log.Debug(usage.ReportUsagePrefix, "Could not create usage data to report") } - err = usage.SendReportUsage(productId, commandName, serviceManager) - if err != nil { - log.Debug(err.Error()) - return + reporter.Report(reports...) + return func() { + if err = reporter.WaitForResponses(); err != nil { + log.Debug(err.Error()) + } } } +func convertToUsageReports(commandName string, repositories RepoAggregator) (reports []usage.ReportFeature, err error) { + for _, repository := range repositories { + // Report one entry for each repository as client + if clientId, e := Md5Hash(repository.RepoName); e != nil { + err = errors.Join(err, e) + } else { + reports = append(reports, usage.ReportFeature{ + FeatureId: commandName, + ClientId: clientId, + }) + } + } + return +} + func Md5Hash(values ...string) (string, error) { hash := crypto.MD5.New() for _, ob := range values { diff --git a/utils/utils_test.go b/utils/utils_test.go index a7cf1b192..a647b06b1 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,18 +1,15 @@ package utils import ( - "bytes" - "fmt" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/xray/formats" - "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" "os" "path" "path/filepath" "testing" + + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/xray/formats" + "github.com/stretchr/testify/assert" ) func TestChdir(t *testing.T) { @@ -44,48 +41,29 @@ func TestChdirErr(t *testing.T) { assert.Equal(t, originCwd, cwd) } -func TestReportUsage(t *testing.T) { - const commandName = "test-command" - server := httptest.NewServer(createUsageHandler(t, commandName)) - defer server.Close() - - serverDetails := &config.ServerDetails{ArtifactoryUrl: server.URL + "/"} - channel := make(chan error) - go ReportUsage(commandName, serverDetails, channel) - assert.NoError(t, <-channel) +func getDummyRepoNames() []string { + return []string{"repository1", "repository2"} } -func TestReportUsageError(t *testing.T) { - channel := make(chan error) - go ReportUsage("", &config.ServerDetails{}, channel) - assert.NoError(t, <-channel) - - channel = make(chan error) - go ReportUsage("", &config.ServerDetails{ArtifactoryUrl: "http://httpbin.org/status/404"}, channel) - assert.Error(t, <-channel) +func getDummyRepo() RepoAggregator { + repos := RepoAggregator{} + names := getDummyRepoNames() + for _, name := range names { + repos = append(repos, Repository{Params: Params{Git: Git{RepoName: name}}}) + } + return repos } -// Create HTTP handler to mock an Artifactory server suitable for report usage requests -func createUsageHandler(t *testing.T, commandName string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/api/system/version" { - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(`{"version":"6.9.0"}`)) - assert.NoError(t, err) - return - } - if r.RequestURI == "/api/system/usage" { - // Check request - buf := new(bytes.Buffer) - _, err := buf.ReadFrom(r.Body) - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf(`{"productId":"%s","features":[{"featureId":"%s"}]}`, productId, commandName), buf.String()) - - // Send response OK - w.WriteHeader(http.StatusOK) - _, err = w.Write([]byte("{}")) - assert.NoError(t, err) - } +func TestConvertToUsageReports(t *testing.T) { + const commandName = "test-command" + repoNames := getDummyRepoNames() + repo := getDummyRepo() + features, err := convertToUsageReports(commandName, repo) + assert.NoError(t, err) + assert.Len(t, features, 2) + for _, feature := range features { + assert.Equal(t, commandName, feature.FeatureId) + assert.NotContains(t, repoNames, feature.ClientId) } }