From 4198ab09a23d6a589c1b804ea1b0b97b067a7059 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 8 Nov 2024 00:36:04 +0530 Subject: [PATCH 01/14] add analytics --- cmd/about.go | 117 ++++++------ cmd/client.go | 66 ++++--- cmd/profile.go | 76 +++++++- cmd/query.go | 8 +- cmd/queryList.go | 7 +- cmd/role.go | 20 +- cmd/stream.go | 27 +-- cmd/tail.go | 6 +- cmd/user.go | 23 +-- cmd/version.go | 6 +- go.mod | 2 + go.sum | 3 + main.go | 4 + pkg/analytics/analytics.go | 367 +++++++++++++++++++++++++++++++++++++ pkg/config/config.go | 17 ++ pkg/http/http.go | 54 ++++++ 16 files changed, 658 insertions(+), 145 deletions(-) create mode 100644 pkg/analytics/analytics.go create mode 100644 pkg/http/http.go diff --git a/cmd/about.go b/cmd/about.go index 7271bd4..23ed022 100644 --- a/cmd/about.go +++ b/cmd/about.go @@ -15,68 +15,69 @@ package cmd -import ( - "encoding/json" - "errors" - "fmt" - "io" -) +// import ( +// "encoding/json" +// "errors" +// "fmt" +// "io" +// internalHTTP "pb/pkg/http" +// ) -// About struct -type About struct { - Version string `json:"version"` - UIVersion string `json:"uiVersion"` - Commit string `json:"commit"` - DeploymentID string `json:"deploymentId"` - UpdateAvailable bool `json:"updateAvailable"` - LatestVersion string `json:"latestVersion"` - LLMActive bool `json:"llmActive"` - LLMProvider string `json:"llmProvider"` - OIDCActive bool `json:"oidcActive"` - License string `json:"license"` - Mode string `json:"mode"` - Staging string `json:"staging"` - HotTier string `json:"hotTier"` - GRPCPort int `json:"grpcPort"` - Store Store `json:"store"` - Analytics Analytics `json:"analytics"` - QueryEngine string `json:"queryEngine"` -} +// // About struct +// type About struct { +// Version string `json:"version"` +// UIVersion string `json:"uiVersion"` +// Commit string `json:"commit"` +// DeploymentID string `json:"deploymentId"` +// UpdateAvailable bool `json:"updateAvailable"` +// LatestVersion string `json:"latestVersion"` +// LLMActive bool `json:"llmActive"` +// LLMProvider string `json:"llmProvider"` +// OIDCActive bool `json:"oidcActive"` +// License string `json:"license"` +// Mode string `json:"mode"` +// Staging string `json:"staging"` +// HotTier string `json:"hotTier"` +// GRPCPort int `json:"grpcPort"` +// Store Store `json:"store"` +// Analytics Analytics `json:"analytics"` +// QueryEngine string `json:"queryEngine"` +// } -// Store struct -type Store struct { - Type string `json:"type"` - Path string `json:"path"` -} +// // Store struct +// type Store struct { +// Type string `json:"type"` +// Path string `json:"path"` +// } -// Analytics struct -type Analytics struct { - ClarityTag string `json:"clarityTag"` -} +// // Analytics struct +// type Analytics struct { +// ClarityTag string `json:"clarityTag"` +// } -func FetchAbout(client *HTTPClient) (about About, err error) { - req, err := client.NewRequest("GET", "about", nil) - if err != nil { - return - } +// func FetchAbout(client *internalHTTP.HTTPClient) (about About, err error) { +// req, err := client.NewRequest("GET", "about", nil) +// if err != nil { +// return +// } - resp, err := client.client.Do(req) - if err != nil { - return - } +// resp, err := client.Client.Do(req) +// if err != nil { +// return +// } - bytes, err := io.ReadAll(resp.Body) - if err != nil { - return - } - defer resp.Body.Close() +// bytes, err := io.ReadAll(resp.Body) +// if err != nil { +// return +// } +// defer resp.Body.Close() - if resp.StatusCode == 200 { - err = json.Unmarshal(bytes, &about) - } else { - body := string(bytes) - body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) - err = errors.New(body) - } - return -} +// if resp.StatusCode == 200 { +// err = json.Unmarshal(bytes, &about) +// } else { +// body := string(bytes) +// body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) +// err = errors.New(body) +// } +// return +// } diff --git a/cmd/client.go b/cmd/client.go index 2675796..98f98c1 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -1,6 +1,5 @@ // Copyright (c) 2024 Parseable, Inc // -// // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or @@ -13,42 +12,41 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - package cmd -import ( - "io" - "net/http" - "net/url" - "pb/pkg/config" - "time" -) +// import ( +// "io" +// "net/http" +// "net/url" +// "pb/pkg/config" +// "time" +// ) -type HTTPClient struct { - client http.Client - profile *config.Profile -} +// type HTTPClient struct { +// client http.Client +// profile *config.Profile +// } -func DefaultClient() HTTPClient { - return HTTPClient{ - client: http.Client{ - Timeout: 60 * time.Second, - }, - profile: &DefaultProfile, - } -} +// func DefaultClient() HTTPClient { +// return HTTPClient{ +// client: http.Client{ +// Timeout: 60 * time.Second, +// }, +// profile: &DefaultProfile, +// } +// } -func (client *HTTPClient) baseAPIURL(path string) (x string) { - x, _ = url.JoinPath(client.profile.URL, "api/v1/", path) - return -} +// func (client *HTTPClient) baseAPIURL(path string) (x string) { +// x, _ = url.JoinPath(client.profile.URL, "api/v1/", path) +// return +// } -func (client *HTTPClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) { - req, err = http.NewRequest(method, client.baseAPIURL(path), body) - if err != nil { - return - } - req.SetBasicAuth(client.profile.Username, client.profile.Password) - req.Header.Add("Content-Type", "application/json") - return -} +// func (client *HTTPClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) { +// req, err = http.NewRequest(method, client.baseAPIURL(path), body) +// if err != nil { +// return +// } +// req.SetBasicAuth(client.profile.Username, client.profile.Password) +// req.Header.Add("Content-Type", "application/json") +// return +// } diff --git a/cmd/profile.go b/cmd/profile.go index a46e8b4..0c7afb7 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -24,6 +24,7 @@ import ( "pb/pkg/config" "pb/pkg/model/credential" "pb/pkg/model/defaultprofile" + "time" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -58,13 +59,20 @@ func (item *ProfileListItem) Render(highlight bool) string { // Add an output flag to specify the output format. var outputFormat string -// Initialize flags -func init() { - AddProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)") - RemoveProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)") - DefaultProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)") - ListProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)") -} +// // Initialize flags +// func init() { +// AddProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") +// RemoveProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") +// DefaultProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") +// ListProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") + +// // Add PreRun and PostRun to commands +// for _, cmd := range []*cobra.Command{AddProfileCmd, RemoveProfileCmd, DefaultProfileCmd, ListProfileCmd} { +// cmd.PreRunE = analytics.CheckAndCreateUUID +// //cmd.PostRun = sendEvent +// } +// } + func outputResult(v interface{}) error { if outputFormat == "json" { jsonData, err := json.MarshalIndent(v, "", " ") @@ -241,10 +249,18 @@ var ListProfileCmd = &cobra.Command{ Use: "list profiles", Short: "List all added profiles", Example: " pb profile list", - RunE: func(_ *cobra.Command, _ []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + // Record the start time of the command execution + startTime := time.Now() + + // Initialize a variable to capture errors + var commandError string + + // Read the configuration from file fileConfig, err := config.ReadConfigFromFile() if err != nil { - return nil + commandError = fmt.Sprintf("Error reading config: %s", err) + return nil // Proceed to PostRunE for event handling } if len(fileConfig.Profiles) != 0 { @@ -252,7 +268,10 @@ var ListProfileCmd = &cobra.Command{ } if outputFormat == "json" { - return outputResult(fileConfig.Profiles) + if err := outputResult(fileConfig.Profiles); err != nil { + commandError = fmt.Sprintf("Error outputting result: %s", err) + } + return nil // Proceed to PostRunE for event handling } row := 0 @@ -262,8 +281,45 @@ var ListProfileCmd = &cobra.Command{ row++ fmt.Println() } + + // Store the execution duration as a field for PostRunE to access + cmd.Annotations = map[string]string{ + "executionTime": time.Since(startTime).String(), + } + + // If there were no errors, return nil + if commandError == "" { + return nil + } + + // If there's an error, set it in Annotations so PostRunE can access it + cmd.Annotations["error"] = commandError + return nil }, + + // // PostRunE function to send analytics event + // PostRunE: func(cmd *cobra.Command, args []string) error { + // executionTime := cmd.Annotations["executionTime"] + // commandError := cmd.Annotations["error"] + // flags := make(map[string]string) + // cmd.Flags().VisitAll(func(flag *pflag.Flag) { + // flags[flag.Name] = flag.Value.String() + // }) + // // Call SendEvent in PostRunE + // err := analytics.SendEvent( + // cmd.Name(), + // args, + // &commandError, // Pass the error here if there was one + // executionTime, + // flags, + // ) + // if err != nil { + // fmt.Println("Error sending analytics event:", err) + // } + + // return nil + // }, } func Max(a int, b int) int { diff --git a/cmd/query.go b/cmd/query.go index a284af4..1768f9a 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -27,6 +27,8 @@ import ( //! This dependency is required by the interactive flag Do not remove // tea "github.com/charmbracelet/bubbletea" + internalHTTP "pb/pkg/http" + "github.com/spf13/cobra" ) @@ -89,7 +91,7 @@ var query = &cobra.Command{ return err } - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) return fetchData(&client, query, start, end, outputFormat) }, } @@ -101,7 +103,7 @@ var QueryCmd = func() *cobra.Command { return query }() -func fetchData(client *HTTPClient, query string, startTime, endTime, outputFormat string) (err error) { +func fetchData(client *internalHTTP.HTTPClient, query string, startTime, endTime, outputFormat string) (err error) { queryTemplate := `{ "query": "%s", "startTime": "%s", @@ -115,7 +117,7 @@ func fetchData(client *HTTPClient, query string, startTime, endTime, outputForma return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } diff --git a/cmd/queryList.go b/cmd/queryList.go index 61ac4b2..576177c 100644 --- a/cmd/queryList.go +++ b/cmd/queryList.go @@ -22,6 +22,7 @@ import ( "net/http" "os" "pb/pkg/config" + internalHTTP "pb/pkg/http" "pb/pkg/model" "strings" "time" @@ -36,7 +37,7 @@ var SavedQueryList = &cobra.Command{ Long: "\nShow the list of saved queries for active user", PreRunE: PreRunDefaultProfile, Run: func(_ *cobra.Command, _ []string) { - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) // Check if the output flag is set if outputFlag != "" { @@ -113,7 +114,7 @@ var SavedQueryList = &cobra.Command{ } // Delete a saved query from the list. -func deleteSavedQuery(client *HTTPClient, savedQueryID, title string) { +func deleteSavedQuery(client *internalHTTP.HTTPClient, savedQueryID, title string) { fmt.Printf("\nAttempting to delete '%s'", title) deleteURL := `filters/` + savedQueryID req, err := client.NewRequest("DELETE", deleteURL, nil) @@ -121,7 +122,7 @@ func deleteSavedQuery(client *HTTPClient, savedQueryID, title string) { fmt.Println("Failed to delete the saved query with error: ", err) } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } diff --git a/cmd/role.go b/cmd/role.go index 74bc8c6..6396360 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -25,6 +25,8 @@ import ( "strings" "sync" + internalHTTP "pb/pkg/http" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" @@ -71,7 +73,7 @@ var AddRoleCmd = &cobra.Command{ // check if the role already exists var roles []string - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) if err := fetchRoles(&client, &roles); err != nil { return err } @@ -129,7 +131,7 @@ var AddRoleCmd = &cobra.Command{ return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -159,13 +161,13 @@ var RemoveRoleCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "role/"+name, nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -193,7 +195,7 @@ var ListRoleCmd = &cobra.Command{ Example: " pb role list", RunE: func(cmd *cobra.Command, _ []string) error { var roles []string - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) err := fetchRoles(&client, &roles) if err != nil { return err @@ -261,13 +263,13 @@ var ListRoleCmd = &cobra.Command{ }, } -func fetchRoles(client *HTTPClient, data *[]string) error { +func fetchRoles(client *internalHTTP.HTTPClient, data *[]string) error { req, err := client.NewRequest("GET", "role", nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -291,13 +293,13 @@ func fetchRoles(client *HTTPClient, data *[]string) error { return nil } -func fetchSpecificRole(client *HTTPClient, role string) (res []RoleData, err error) { +func fetchSpecificRole(client *internalHTTP.HTTPClient, role string) (res []RoleData, err error) { req, err := client.NewRequest("GET", fmt.Sprintf("role/%s", role), nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } diff --git a/cmd/stream.go b/cmd/stream.go index 12dbdb8..bde61e6 100644 --- a/cmd/stream.go +++ b/cmd/stream.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "net/http" + internalHTTP "pb/pkg/http" "strconv" "strings" "time" @@ -112,13 +113,13 @@ var AddStreamCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("PUT", "logstream/"+name, nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -147,7 +148,7 @@ var StatStreamCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) // Fetch stats data stats, err := fetchStats(&client, name) @@ -256,13 +257,13 @@ var RemoveStreamCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "logstream/"+name, nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -289,13 +290,13 @@ var ListStreamCmd = &cobra.Command{ Short: "List all streams", Example: " pb stream list", RunE: func(cmd *cobra.Command, _ []string) error { - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("GET", "logstream", nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -361,13 +362,13 @@ func init() { ListStreamCmd.Flags().StringP("output", "o", "text", "Output format: 'text' or 'json'") } -func fetchStats(client *HTTPClient, name string) (data StreamStatsData, err error) { +func fetchStats(client *internalHTTP.HTTPClient, name string) (data StreamStatsData, err error) { req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/stats", name), nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } @@ -388,13 +389,13 @@ func fetchStats(client *HTTPClient, name string) (data StreamStatsData, err erro return } -func fetchRetention(client *HTTPClient, name string) (data StreamRetentionData, err error) { +func fetchRetention(client *internalHTTP.HTTPClient, name string) (data StreamRetentionData, err error) { req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/retention", name), nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } @@ -415,13 +416,13 @@ func fetchRetention(client *HTTPClient, name string) (data StreamRetentionData, return } -func fetchAlerts(client *HTTPClient, name string) (data AlertConfig, err error) { +func fetchAlerts(client *internalHTTP.HTTPClient, name string) (data AlertConfig, err error) { req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/alert", name), nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } diff --git a/cmd/tail.go b/cmd/tail.go index c039c03..6ccde73 100644 --- a/cmd/tail.go +++ b/cmd/tail.go @@ -22,7 +22,9 @@ import ( "encoding/base64" "encoding/json" "fmt" + "pb/pkg/analytics" "pb/pkg/config" + internalHTTP "pb/pkg/http" "github.com/apache/arrow/go/v13/arrow/array" "github.com/apache/arrow/go/v13/arrow/flight" @@ -53,8 +55,8 @@ func tail(profile config.Profile, stream string) error { }) // get grpc url for this request - httpClient := DefaultClient() - about, err := FetchAbout(&httpClient) + httpClient := internalHTTP.DefaultClient(&DefaultProfile) + about, err := analytics.FetchAbout(&httpClient) if err != nil { return err } diff --git a/cmd/user.go b/cmd/user.go index 42aefe9..43fc374 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + internalHTTP "pb/pkg/http" "strings" "sync" @@ -48,7 +49,7 @@ var addUser = &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { return err @@ -89,7 +90,7 @@ var addUser = &cobra.Command{ return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -124,13 +125,13 @@ var RemoveUserCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "user/"+name, nil) if err != nil { return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -165,7 +166,7 @@ var SetUserRoleCmd = &cobra.Command{ RunE: func(_ *cobra.Command, args []string) error { name := args[0] - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { return err @@ -206,7 +207,7 @@ var SetUserRoleCmd = &cobra.Command{ return err } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return err } @@ -233,7 +234,7 @@ var ListUserCmd = &cobra.Command{ Short: "List all users", Example: " pb user list", RunE: func(cmd *cobra.Command, _ []string) error { - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { return err @@ -324,13 +325,13 @@ var ListUserCmd = &cobra.Command{ }, } -func fetchUsers(client *HTTPClient) (res []UserData, err error) { +func fetchUsers(client *internalHTTP.HTTPClient) (res []UserData, err error) { req, err := client.NewRequest("GET", "user", nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } @@ -355,12 +356,12 @@ func fetchUsers(client *HTTPClient) (res []UserData, err error) { return } -func fetchUserRoles(client *HTTPClient, user string) (res UserRoleData, err error) { +func fetchUserRoles(client *internalHTTP.HTTPClient, user string) (res UserRoleData, err error) { req, err := client.NewRequest("GET", fmt.Sprintf("user/%s/role", user), nil) if err != nil { return } - resp, err := client.client.Do(req) + resp, err := client.Client.Do(req) if err != nil { return } diff --git a/cmd/version.go b/cmd/version.go index 9b7a266..0dd3580 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -17,6 +17,8 @@ package cmd import ( "fmt" + "pb/pkg/analytics" + internalHTTP "pb/pkg/http" "github.com/spf13/cobra" ) @@ -31,7 +33,7 @@ var VersionCmd = &cobra.Command{ // PrintVersion prints version information func PrintVersion(version, commit string) { - client := DefaultClient() + client := internalHTTP.DefaultClient(&DefaultProfile) fmt.Printf("\n%s \n", StandardStyleAlt.Render("pb version")) fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), version) @@ -40,7 +42,7 @@ func PrintVersion(version, commit string) { if err := PreRun(); err != nil { return } - about, err := FetchAbout(&client) + about, err := analytics.FetchAbout(&client) if err != nil { return } diff --git a/go.mod b/go.mod index d71b784..c622463 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,11 @@ require ( github.com/charmbracelet/bubbletea v0.26.6 github.com/charmbracelet/lipgloss v0.12.1 github.com/dustin/go-humanize v1.0.1 + github.com/google/uuid v1.6.0 golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 golang.org/x/term v0.21.0 google.golang.org/grpc v1.64.1 + gopkg.in/yaml.v2 v2.4.0 ) require ( diff --git a/go.sum b/go.sum index 6857252..e9b2573 100644 --- a/go.sum +++ b/go.sum @@ -120,7 +120,10 @@ google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index c7b9e49..8cbfd6d 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "pb/cmd" + "pb/pkg/analytics" "pb/pkg/config" "github.com/spf13/cobra" @@ -63,6 +64,9 @@ var profile = &cobra.Command{ Use: "profile", Short: "Manage different Parseable targets", Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", + PersistentPostRun: func(cmd *cobra.Command, args []string) { + go analytics.PostRunFunction(cmd, args) + }, } var user = &cobra.Command{ diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go new file mode 100644 index 0000000..7658c75 --- /dev/null +++ b/pkg/analytics/analytics.go @@ -0,0 +1,367 @@ +package analytics + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "time" + + "pb/pkg/config" + internalHTTP "pb/pkg/http" + + "github.com/google/uuid" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "gopkg.in/yaml.v2" +) + +type Event struct { + CLIVersion string `json:"cli_version"` + UUID string `json:"uuid"` + CommitHash string `json:"commit_hash"` + OSName string `json:"os_name"` + OSVersion string `json:"os_version"` + ReportCreatedAt string `json:"report_created_at"` + Command Command `json:"command"` + Profile config.Profile `json:"profile"` + Errors *string `json:"errors"` + ExecutionStatus string `json:"execution_status"` +} + +// About struct +type About struct { + Version string `json:"version"` + UIVersion string `json:"uiVersion"` + Commit string `json:"commit"` + DeploymentID string `json:"deploymentId"` + UpdateAvailable bool `json:"updateAvailable"` + LatestVersion string `json:"latestVersion"` + LLMActive bool `json:"llmActive"` + LLMProvider string `json:"llmProvider"` + OIDCActive bool `json:"oidcActive"` + License string `json:"license"` + Mode string `json:"mode"` + Staging string `json:"staging"` + HotTier string `json:"hotTier"` + GRPCPort int `json:"grpcPort"` + Store Store `json:"store"` + Analytics Analytics `json:"analytics"` + QueryEngine string `json:"queryEngine"` +} + +// Store struct +type Store struct { + Type string `json:"type"` + Path string `json:"path"` +} + +// Analytics struct +type Analytics struct { + ClarityTag string `json:"clarityTag"` +} + +type Command struct { + Name string `json:"name"` + Arguments []string `json:"arguments"` + Flags map[string]string `json:"flags"` +} + +// Config struct for parsing YAML +type Config struct { + UUID string `yaml:"uuid"` +} + +// checkAndCreateUUID checks for a UUID in the config file and creates it if absent. +// Signature is just for +func CheckAndCreateUUID(cmd *cobra.Command, args []string) error { + homeDir, err := os.UserHomeDir() + if err != nil { + fmt.Printf("could not find home directory: %v\n", err) + return err + } + + configPath := filepath.Join(homeDir, ".parseable", "config.yaml") + + // Check if config path exists + if _, err := os.Stat(configPath); os.IsNotExist(err) { + // Create the directory if needed + if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + fmt.Printf("could not create config directory: %v\n", err) + return err + } + } + + // Read the config file + var config Config + data, err := os.ReadFile(configPath) + if err == nil { + // If the file exists, unmarshal the content + if err := yaml.Unmarshal(data, &config); err != nil { + fmt.Printf("could not parse config file: %v\n", err) + return err + } + } + + // Check if UUID is missing + if config.UUID == "" { + config.UUID = uuid.New().String() // Generate a new UUID + newData, err := yaml.Marshal(&config) + if err != nil { + fmt.Printf("could not marshal config data: %v\n", err) + return err + } + + // Write updated config with UUID back to the file + if err := os.WriteFile(configPath, newData, 0644); err != nil { + fmt.Printf("could not write to config file: %v\n", err) + return err + } + fmt.Printf("Generated and saved new UUID: %s\n", config.UUID) + } + + return nil +} + +func PostRunFunction(cmd *cobra.Command, args []string) { + executionTime := cmd.Annotations["executionTime"] + commandError := cmd.Annotations["error"] + flags := make(map[string]string) + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + flags[flag.Name] = flag.Value.String() + }) + // Call SendEvent in PostRunE + err := SendEvent( + cmd.Name(), + args, + &commandError, // Pass the error here if there was one + executionTime, + flags, + ) + if err != nil { + fmt.Println("Error sending analytics event:", err) + } + +} + +// sendEvent is a placeholder function to simulate sending an event after command execution. +func SendEvent(commandName string, arguments []string, errors *string, executionStatus string, flags map[string]string) error { + + uuid, err := ReadUUID() + if err != nil { + return fmt.Errorf("could not load UUID: %v", err) + } + + profile, err := GetProfile() + if err != nil { + return fmt.Errorf("failed to get profile: %v", err) + } + + httpClient := internalHTTP.DefaultClient(&profile) + + about, err := FetchAbout(&httpClient) + if err != nil { + return fmt.Errorf("failed to get about metadata for profile: %v", err) + } + + // Create the Command struct + cmd := Command{ + Name: commandName, + Arguments: arguments, + Flags: flags, + } + + // Populate the Event struct with OS details and timestamp + event := Event{ + CLIVersion: about.Commit, + UUID: uuid, + CommitHash: about.Commit, + Profile: profile, + OSName: GetOSName(), + OSVersion: GetOSVersion(), + ReportCreatedAt: GetCurrentTimestamp(), + Command: cmd, + Errors: errors, + ExecutionStatus: executionStatus, + } + + // Marshal the event to JSON for sending + eventJSON, err := json.Marshal(event) + if err != nil { + return fmt.Errorf("failed to marshal event JSON: %v", err) + } + + // Simulate sending the event (print or make an HTTP request) + fmt.Println("Sending event:", string(eventJSON)) + + // err = sendHTTPRequest("POST", "https://example.com/events", eventJSON) + // if err != nil { + // return fmt.Errorf("failed to send event: %v", err) + // } + + return nil +} + +// GetOSName retrieves the OS name. +func GetOSName() string { + switch runtime.GOOS { + case "windows": + return "Windows" + case "darwin": + return "macOS" + case "linux": + return getLinuxDistro() + default: + return "Unknown" + } +} + +// GetOSVersion retrieves the OS version. +func GetOSVersion() string { + switch runtime.GOOS { + case "windows": + return getWindowsVersion() + case "darwin": + return getMacOSVersion() + case "linux": + return getLinuxVersion() + default: + return "Unknown" + } +} + +// GetCurrentTimestamp returns the current timestamp in ISO 8601 format. +func GetCurrentTimestamp() string { + return time.Now().Format(time.RFC3339) +} + +// GetFormattedTimestamp formats a given time.Time in ISO 8601 format. +func GetFormattedTimestamp(t time.Time) string { + return t.Format(time.RFC3339) +} + +// getLinuxDistro retrieves the Linux distribution name. +func getLinuxDistro() string { + data, err := os.ReadFile("/etc/os-release") + if err != nil { + return "Linux" + } + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "NAME=") { + return strings.Trim(line[5:], "\"") + } + } + return "Linux" +} + +// getLinuxVersion retrieves the Linux distribution version. +func getLinuxVersion() string { + data, err := os.ReadFile("/etc/os-release") + if err != nil { + return "Unknown" + } + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "VERSION_ID=") { + return strings.Trim(line[11:], "\"") + } + } + return "Unknown" +} + +// getMacOSVersion retrieves the macOS version. +func getMacOSVersion() string { + out, err := exec.Command("sw_vers", "-productVersion").Output() + if err != nil { + return "Unknown" + } + return strings.TrimSpace(string(out)) +} + +// getWindowsVersion retrieves the Windows version. +func getWindowsVersion() string { + out, err := exec.Command("cmd", "ver").Output() + if err != nil { + return "Unknown" + } + return strings.TrimSpace(string(out)) +} + +func ReadUUID() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("could not find home directory: %v", err) + } + + configPath := filepath.Join(homeDir, ".parseable", "config.yaml") + + // Check if config path exists + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return "", fmt.Errorf("config file does not exist, please run CheckAndCreateUUID first") + } + + // Read the config file + var config Config + data, err := os.ReadFile(configPath) + if err != nil { + return "", fmt.Errorf("could not read config file: %v", err) + } + + // Unmarshal the content to get the UUID + if err := yaml.Unmarshal(data, &config); err != nil { + return "", fmt.Errorf("could not parse config file: %v", err) + } + + if config.UUID == "" { + return "", fmt.Errorf("UUID is missing in config file") + } + + return config.UUID, nil +} + +func FetchAbout(client *internalHTTP.HTTPClient) (about About, err error) { + req, err := client.NewRequest("GET", "about", nil) + if err != nil { + return + } + + resp, err := client.Client.Do(req) + if err != nil { + return + } + + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + err = json.Unmarshal(bytes, &about) + } else { + body := string(bytes) + body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) + err = errors.New(body) + } + return +} + +func GetProfile() (config.Profile, error) { + conf, err := config.ReadConfigFromFile() + if os.IsNotExist(err) { + return config.Profile{}, errors.New("no config found to run this command. add a profile using pb profile command") + } else if err != nil { + return config.Profile{}, err + } + + if conf.Profiles == nil || conf.DefaultProfile == "" { + return config.Profile{}, errors.New("no profile is configured to run this command. please create one using profile command") + } + + return conf.Profiles[conf.DefaultProfile], nil + +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 434113d..62d0d95 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -17,6 +17,7 @@ package config import ( + "errors" "fmt" "net" "net/url" @@ -105,3 +106,19 @@ func ReadConfigFromFile() (config *Config, err error) { return config, nil } + +func GetProfile() (Profile, error) { + conf, err := ReadConfigFromFile() + if os.IsNotExist(err) { + return Profile{}, errors.New("no config found to run this command. add a profile using pb profile command") + } else if err != nil { + return Profile{}, err + } + + if conf.Profiles == nil || conf.DefaultProfile == "" { + return Profile{}, errors.New("no profile is configured to run this command. please create one using profile command") + } + + return conf.Profiles[conf.DefaultProfile], nil + +} diff --git a/pkg/http/http.go b/pkg/http/http.go new file mode 100644 index 0000000..340d1b1 --- /dev/null +++ b/pkg/http/http.go @@ -0,0 +1,54 @@ +// Copyright (c) 2024 Parseable, Inc +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "io" + "net/http" + "net/url" + "pb/pkg/config" + "time" +) + +type HTTPClient struct { + Client http.Client + Profile *config.Profile +} + +func DefaultClient(profile *config.Profile) HTTPClient { + return HTTPClient{ + Client: http.Client{ + Timeout: 60 * time.Second, + }, + Profile: profile, + } +} + +func (client *HTTPClient) baseAPIURL(path string) (x string) { + x, _ = url.JoinPath(client.Profile.URL, "api/v1/", path) + return +} + +func (client *HTTPClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) { + req, err = http.NewRequest(method, client.baseAPIURL(path), body) + if err != nil { + return + } + req.SetBasicAuth(client.Profile.Username, client.Profile.Password) + req.Header.Add("Content-Type", "application/json") + return +} From aff301ecf0ac8072cc5b6f7aadfda5cf28eaf5ca Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 8 Nov 2024 00:37:12 +0530 Subject: [PATCH 02/14] remove about, client from cmd --- cmd/about.go | 83 --------------------------------------------------- cmd/client.go | 52 -------------------------------- 2 files changed, 135 deletions(-) delete mode 100644 cmd/about.go delete mode 100644 cmd/client.go diff --git a/cmd/about.go b/cmd/about.go deleted file mode 100644 index 23ed022..0000000 --- a/cmd/about.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2024 Parseable, Inc -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package cmd - -// import ( -// "encoding/json" -// "errors" -// "fmt" -// "io" -// internalHTTP "pb/pkg/http" -// ) - -// // About struct -// type About struct { -// Version string `json:"version"` -// UIVersion string `json:"uiVersion"` -// Commit string `json:"commit"` -// DeploymentID string `json:"deploymentId"` -// UpdateAvailable bool `json:"updateAvailable"` -// LatestVersion string `json:"latestVersion"` -// LLMActive bool `json:"llmActive"` -// LLMProvider string `json:"llmProvider"` -// OIDCActive bool `json:"oidcActive"` -// License string `json:"license"` -// Mode string `json:"mode"` -// Staging string `json:"staging"` -// HotTier string `json:"hotTier"` -// GRPCPort int `json:"grpcPort"` -// Store Store `json:"store"` -// Analytics Analytics `json:"analytics"` -// QueryEngine string `json:"queryEngine"` -// } - -// // Store struct -// type Store struct { -// Type string `json:"type"` -// Path string `json:"path"` -// } - -// // Analytics struct -// type Analytics struct { -// ClarityTag string `json:"clarityTag"` -// } - -// func FetchAbout(client *internalHTTP.HTTPClient) (about About, err error) { -// req, err := client.NewRequest("GET", "about", nil) -// if err != nil { -// return -// } - -// resp, err := client.Client.Do(req) -// if err != nil { -// return -// } - -// bytes, err := io.ReadAll(resp.Body) -// if err != nil { -// return -// } -// defer resp.Body.Close() - -// if resp.StatusCode == 200 { -// err = json.Unmarshal(bytes, &about) -// } else { -// body := string(bytes) -// body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) -// err = errors.New(body) -// } -// return -// } diff --git a/cmd/client.go b/cmd/client.go deleted file mode 100644 index 98f98c1..0000000 --- a/cmd/client.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2024 Parseable, Inc -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -package cmd - -// import ( -// "io" -// "net/http" -// "net/url" -// "pb/pkg/config" -// "time" -// ) - -// type HTTPClient struct { -// client http.Client -// profile *config.Profile -// } - -// func DefaultClient() HTTPClient { -// return HTTPClient{ -// client: http.Client{ -// Timeout: 60 * time.Second, -// }, -// profile: &DefaultProfile, -// } -// } - -// func (client *HTTPClient) baseAPIURL(path string) (x string) { -// x, _ = url.JoinPath(client.profile.URL, "api/v1/", path) -// return -// } - -// func (client *HTTPClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) { -// req, err = http.NewRequest(method, client.baseAPIURL(path), body) -// if err != nil { -// return -// } -// req.SetBasicAuth(client.profile.Username, client.profile.Password) -// req.Header.Add("Content-Type", "application/json") -// return -// } From 71820795ba0e058dbd2313e4afa52b2f71108a6f Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 8 Nov 2024 01:38:45 +0530 Subject: [PATCH 03/14] cleanup --- cmd/profile.go | 67 +++++++++----------------------------- main.go | 2 +- pkg/analytics/analytics.go | 6 ++-- 3 files changed, 20 insertions(+), 55 deletions(-) diff --git a/cmd/profile.go b/cmd/profile.go index 0c7afb7..3953265 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -59,19 +59,13 @@ func (item *ProfileListItem) Render(highlight bool) string { // Add an output flag to specify the output format. var outputFormat string -// // Initialize flags -// func init() { -// AddProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") -// RemoveProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") -// DefaultProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") -// ListProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") - -// // Add PreRun and PostRun to commands -// for _, cmd := range []*cobra.Command{AddProfileCmd, RemoveProfileCmd, DefaultProfileCmd, ListProfileCmd} { -// cmd.PreRunE = analytics.CheckAndCreateUUID -// //cmd.PostRun = sendEvent -// } -// } +// Initialize flags +func init() { + AddProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") + RemoveProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") + DefaultProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") + ListProfileCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") +} func outputResult(v interface{}) error { if outputFormat == "json" { @@ -254,13 +248,14 @@ var ListProfileCmd = &cobra.Command{ startTime := time.Now() // Initialize a variable to capture errors - var commandError string + var commandError error // Read the configuration from file fileConfig, err := config.ReadConfigFromFile() if err != nil { - commandError = fmt.Sprintf("Error reading config: %s", err) - return nil // Proceed to PostRunE for event handling + commandError = fmt.Errorf("error reading config: %s", err) + cmd.Annotations["error"] = commandError.Error() // Store error in annotations + return commandError // Return the error so it's handled properly } if len(fileConfig.Profiles) != 0 { @@ -269,9 +264,11 @@ var ListProfileCmd = &cobra.Command{ if outputFormat == "json" { if err := outputResult(fileConfig.Profiles); err != nil { - commandError = fmt.Sprintf("Error outputting result: %s", err) + commandError = fmt.Errorf("error outputting result: %s", err) + cmd.Annotations["error"] = commandError.Error() // Store error in annotations + return commandError // Return the error } - return nil // Proceed to PostRunE for event handling + return nil // No error, exit normally } row := 0 @@ -283,43 +280,11 @@ var ListProfileCmd = &cobra.Command{ } // Store the execution duration as a field for PostRunE to access - cmd.Annotations = map[string]string{ - "executionTime": time.Since(startTime).String(), - } + cmd.Annotations["executionTime"] = time.Since(startTime).String() // If there were no errors, return nil - if commandError == "" { - return nil - } - - // If there's an error, set it in Annotations so PostRunE can access it - cmd.Annotations["error"] = commandError - return nil }, - - // // PostRunE function to send analytics event - // PostRunE: func(cmd *cobra.Command, args []string) error { - // executionTime := cmd.Annotations["executionTime"] - // commandError := cmd.Annotations["error"] - // flags := make(map[string]string) - // cmd.Flags().VisitAll(func(flag *pflag.Flag) { - // flags[flag.Name] = flag.Value.String() - // }) - // // Call SendEvent in PostRunE - // err := analytics.SendEvent( - // cmd.Name(), - // args, - // &commandError, // Pass the error here if there was one - // executionTime, - // flags, - // ) - // if err != nil { - // fmt.Println("Error sending analytics event:", err) - // } - - // return nil - // }, } func Max(a int, b int) int { diff --git a/main.go b/main.go index 8cbfd6d..4681fd8 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ var profile = &cobra.Command{ Short: "Manage different Parseable targets", Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", PersistentPostRun: func(cmd *cobra.Command, args []string) { - go analytics.PostRunFunction(cmd, args) + go analytics.PostRunAnalytics(cmd, args) }, } diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 7658c75..cfab50a 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -128,7 +128,7 @@ func CheckAndCreateUUID(cmd *cobra.Command, args []string) error { return nil } -func PostRunFunction(cmd *cobra.Command, args []string) { +func PostRunAnalytics(cmd *cobra.Command, args []string) { executionTime := cmd.Annotations["executionTime"] commandError := cmd.Annotations["error"] flags := make(map[string]string) @@ -136,7 +136,7 @@ func PostRunFunction(cmd *cobra.Command, args []string) { flags[flag.Name] = flag.Value.String() }) // Call SendEvent in PostRunE - err := SendEvent( + err := sendEvent( cmd.Name(), args, &commandError, // Pass the error here if there was one @@ -150,7 +150,7 @@ func PostRunFunction(cmd *cobra.Command, args []string) { } // sendEvent is a placeholder function to simulate sending an event after command execution. -func SendEvent(commandName string, arguments []string, errors *string, executionStatus string, flags map[string]string) error { +func sendEvent(commandName string, arguments []string, errors *string, executionStatus string, flags map[string]string) error { uuid, err := ReadUUID() if err != nil { From d635047a0a404af7e1e9a11840f2ef850885260f Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Fri, 8 Nov 2024 01:49:09 +0530 Subject: [PATCH 04/14] lint checks fixed --- cmd/profile.go | 4 ++-- pkg/analytics/analytics.go | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/profile.go b/cmd/profile.go index 3953265..6214518 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -243,7 +243,7 @@ var ListProfileCmd = &cobra.Command{ Use: "list profiles", Short: "List all added profiles", Example: " pb profile list", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { // Record the start time of the command execution startTime := time.Now() @@ -253,7 +253,7 @@ var ListProfileCmd = &cobra.Command{ // Read the configuration from file fileConfig, err := config.ReadConfigFromFile() if err != nil { - commandError = fmt.Errorf("error reading config: %s", err) + commandError = fmt.Errorf("rror reading config: %s", err) cmd.Annotations["error"] = commandError.Error() // Store error in annotations return commandError // Return the error so it's handled properly } diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index cfab50a..193f8fb 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -77,9 +77,8 @@ type Config struct { UUID string `yaml:"uuid"` } -// checkAndCreateUUID checks for a UUID in the config file and creates it if absent. -// Signature is just for -func CheckAndCreateUUID(cmd *cobra.Command, args []string) error { +// CheckAndCreateUUID checks for a UUID in the config file and creates it if absent. +func CheckAndCreateUUID(_ *cobra.Command, _ []string) error { homeDir, err := os.UserHomeDir() if err != nil { fmt.Printf("could not find home directory: %v\n", err) From 7a4220a9ee99e85a5909d2321fac7a0ccd859b2a Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sat, 9 Nov 2024 23:50:14 +0530 Subject: [PATCH 05/14] add events to user cmd --- cmd/profile.go | 185 +++++++++++++++++++------------------ cmd/user.go | 82 +++++++++++----- main.go | 5 +- pkg/analytics/analytics.go | 33 +++++-- pkg/config/config.go | 6 +- 5 files changed, 188 insertions(+), 123 deletions(-) diff --git a/cmd/profile.go b/cmd/profile.go index 6214518..2d038c3 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -20,15 +20,12 @@ import ( "errors" "fmt" "net/url" - "os" "pb/pkg/config" "pb/pkg/model/credential" "pb/pkg/model/defaultprofile" "time" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/muesli/termenv" "github.com/spf13/cobra" ) @@ -91,68 +88,67 @@ var AddProfileCmd = &cobra.Command{ } return cobra.MaximumNArgs(4)(cmd, args) }, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } + startTime := time.Now() + var commandError error + + // Parsing input and handling errors name := args[0] url, err := url.Parse(args[1]) if err != nil { - return err + commandError = fmt.Errorf("error parsing URL: %s", err) + cmd.Annotations["error"] = commandError.Error() + return commandError } - var username string - var password string - + var username, password string if len(args) < 4 { _m, err := tea.NewProgram(credential.New()).Run() if err != nil { - fmt.Printf("Alas, there's been an error: %v", err) - os.Exit(1) + commandError = fmt.Errorf("error reading credentials: %s", err) + cmd.Annotations["error"] = commandError.Error() + return commandError } m := _m.(credential.Model) - username, password = m.Values() } else { username = args[2] password = args[3] } - profile := config.Profile{ - URL: url.String(), - Username: username, - Password: password, - } - + profile := config.Profile{URL: url.String(), Username: username, Password: password} fileConfig, err := config.ReadConfigFromFile() if err != nil { - // create new file newConfig := config.Config{ - Profiles: map[string]config.Profile{ - name: profile, - }, + Profiles: map[string]config.Profile{name: profile}, DefaultProfile: name, } err = config.WriteConfigToFile(&newConfig) - return err - } - if fileConfig.Profiles == nil { - fileConfig.Profiles = make(map[string]config.Profile) - } - fileConfig.Profiles[name] = profile - if fileConfig.DefaultProfile == "" { - fileConfig.DefaultProfile = name + commandError = err + } else { + if fileConfig.Profiles == nil { + fileConfig.Profiles = make(map[string]config.Profile) + } + fileConfig.Profiles[name] = profile + if fileConfig.DefaultProfile == "" { + fileConfig.DefaultProfile = name + } + commandError = config.WriteConfigToFile(fileConfig) } - err = config.WriteConfigToFile(fileConfig) - if err != nil { - fmt.Printf("add profile %s failed\n, err: %v\n", StyleBold.Render(name), err) - return err + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if commandError != nil { + cmd.Annotations["error"] = commandError.Error() + return commandError } - fmt.Printf("Added profile %s\n", StyleBold.Render(name)) if outputFormat == "json" { return outputResult(profile) } fmt.Printf("Profile %s added successfully\n", name) - return nil }, } @@ -163,29 +159,43 @@ var RemoveProfileCmd = &cobra.Command{ Example: " pb profile remove local_parseable", Args: cobra.ExactArgs(1), Short: "Delete a profile", - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } + startTime := time.Now() + name := args[0] fileConfig, err := config.ReadConfigFromFile() if err != nil { - return nil + cmd.Annotations["error"] = fmt.Sprintf("error reading config: %s", err) + return err } _, exists := fileConfig.Profiles[name] - if exists { - delete(fileConfig.Profiles, name) - if len(fileConfig.Profiles) == 0 { - fileConfig.DefaultProfile = "" - } + if !exists { + msg := fmt.Sprintf("No profile found with the name: %s", name) + cmd.Annotations["error"] = msg + fmt.Println(msg) + return nil + } - config.WriteConfigToFile(fileConfig) - if outputFormat == "json" { - return outputResult(fmt.Sprintf("Deleted profile %s", name)) - } - fmt.Printf("Deleted profile %s\n", StyleBold.Render(name)) - } else { - fmt.Printf("No profile found with the name: %s", StyleBold.Render(name)) + delete(fileConfig.Profiles, name) + if len(fileConfig.Profiles) == 0 { + fileConfig.DefaultProfile = "" } + commandError := config.WriteConfigToFile(fileConfig) + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if commandError != nil { + cmd.Annotations["error"] = commandError.Error() + return commandError + } + + if outputFormat == "json" { + return outputResult(fmt.Sprintf("Deleted profile %s", name)) + } + fmt.Printf("Deleted profile %s\n", name) return nil }, } @@ -195,46 +205,54 @@ var DefaultProfileCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: "Set default profile to use with all commands", Example: " pb profile default local_parseable", - RunE: func(_ *cobra.Command, args []string) error { - var name string + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } + startTime := time.Now() fileConfig, err := config.ReadConfigFromFile() if err != nil { - return nil + cmd.Annotations["error"] = fmt.Sprintf("error reading config: %s", err) + return err } + var name string if len(args) > 0 { name = args[0] } else { model := defaultprofile.New(fileConfig.Profiles) _m, err := tea.NewProgram(model).Run() if err != nil { - fmt.Printf("Alas, there's been an error: %v", err) - os.Exit(1) + cmd.Annotations["error"] = fmt.Sprintf("error selecting default profile: %s", err) + return err } m := _m.(defaultprofile.Model) - termenv.DefaultOutput().ClearLines(lipgloss.Height(model.View()) - 1) - if m.Success { - name = m.Choice - } else { + if !m.Success { return nil } + name = m.Choice } _, exists := fileConfig.Profiles[name] - if exists { - fileConfig.DefaultProfile = name - } else { - name = lipgloss.NewStyle().Bold(true).Render(name) - err := fmt.Sprintf("profile %s does not exist", StyleBold.Render(name)) - return errors.New(err) + if !exists { + commandError := fmt.Sprintf("profile %s does not exist", name) + cmd.Annotations["error"] = commandError + return errors.New(commandError) + } + + fileConfig.DefaultProfile = name + commandError := config.WriteConfigToFile(fileConfig) + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if commandError != nil { + cmd.Annotations["error"] = commandError.Error() + return commandError } - config.WriteConfigToFile(fileConfig) if outputFormat == "json" { return outputResult(fmt.Sprintf("%s is now set as default profile", name)) } - fmt.Printf("%s is now set as default profile\n", StyleBold.Render(name)) + fmt.Printf("%s is now set as default profile\n", name) return nil }, } @@ -244,45 +262,32 @@ var ListProfileCmd = &cobra.Command{ Short: "List all added profiles", Example: " pb profile list", RunE: func(cmd *cobra.Command, _ []string) error { - // Record the start time of the command execution + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } startTime := time.Now() - // Initialize a variable to capture errors - var commandError error - - // Read the configuration from file fileConfig, err := config.ReadConfigFromFile() if err != nil { - commandError = fmt.Errorf("rror reading config: %s", err) - cmd.Annotations["error"] = commandError.Error() // Store error in annotations - return commandError // Return the error so it's handled properly - } - - if len(fileConfig.Profiles) != 0 { - println() + cmd.Annotations["error"] = fmt.Sprintf("error reading config: %s", err) + return err } if outputFormat == "json" { - if err := outputResult(fileConfig.Profiles); err != nil { - commandError = fmt.Errorf("error outputting result: %s", err) - cmd.Annotations["error"] = commandError.Error() // Store error in annotations - return commandError // Return the error + commandError := outputResult(fileConfig.Profiles) + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if commandError != nil { + cmd.Annotations["error"] = commandError.Error() + return commandError } - return nil // No error, exit normally + return nil } - row := 0 for key, value := range fileConfig.Profiles { item := ProfileListItem{key, value.URL, value.Username} fmt.Println(item.Render(fileConfig.DefaultProfile == key)) - row++ - fmt.Println() } - - // Store the execution duration as a field for PostRunE to access cmd.Annotations["executionTime"] = time.Since(startTime).String() - - // If there were no errors, return nil return nil }, } diff --git a/cmd/user.go b/cmd/user.go index 43fc374..1db904b 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -23,6 +23,7 @@ import ( internalHTTP "pb/pkg/http" "strings" "sync" + "time" "github.com/charmbracelet/lipgloss" "github.com/spf13/cobra" @@ -47,11 +48,18 @@ var addUser = &cobra.Command{ Short: "Add a new user", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) // Initialize Annotations map + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { + cmd.Annotations["error"] = err.Error() return err } @@ -59,6 +67,7 @@ var addUser = &cobra.Command{ return user.ID == name }) { fmt.Println("user already exists") + cmd.Annotations["error"] = "user already exists" return nil } @@ -69,6 +78,7 @@ var addUser = &cobra.Command{ // fetch the role names on the server var rolesOnServer []string if err := fetchRoles(&client, &rolesOnServer); err != nil { + cmd.Annotations["error"] = err.Error() return err } rolesOnServerArr := strings.Join(rolesOnServer, " ") @@ -77,7 +87,8 @@ var addUser = &cobra.Command{ for idx, role := range rolesToSetArr { rolesToSetArr[idx] = strings.TrimSpace(role) if !strings.Contains(rolesOnServerArr, rolesToSetArr[idx]) { - fmt.Printf("role %s doesn't exist, please create a role using `pb role add %s`\n", rolesToSetArr[idx], rolesToSetArr[idx]) + fmt.Printf("role %s doesn't exist, please create a role using pb role add %s\n", rolesToSetArr[idx], rolesToSetArr[idx]) + cmd.Annotations["error"] = fmt.Sprintf("role %s doesn't exist", rolesToSetArr[idx]) return nil } } @@ -87,16 +98,19 @@ var addUser = &cobra.Command{ putBody = bytes.NewBuffer([]byte(putBodyJSON)) req, err := client.NewRequest("POST", "user/"+name, putBody) if err != nil { + cmd.Annotations["error"] = err.Error() return err } resp, err := client.Client.Do(req) if err != nil { + cmd.Annotations["error"] = err.Error() return err } bytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["error"] = err.Error() return err } body := string(bytes) @@ -104,8 +118,10 @@ var addUser = &cobra.Command{ if resp.StatusCode == 200 { fmt.Printf("Added user: %s \nPassword is: %s\nRole(s) assigned: %s\n", name, body, rolesToSet) + cmd.Annotations["error"] = "none" } else { fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) + cmd.Annotations["error"] = fmt.Sprintf("request failed with status code %s", resp.Status) } return nil @@ -117,36 +133,41 @@ var AddUserCmd = func() *cobra.Command { return addUser }() +// Similar changes for RemoveUserCmd var RemoveUserCmd = &cobra.Command{ Use: "remove user-name", Aliases: []string{"rm"}, Example: " pb user remove bob", Short: "Delete a user", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "user/"+name, nil) if err != nil { + cmd.Annotations["error"] = err.Error() return err } resp, err := client.Client.Do(req) if err != nil { + cmd.Annotations["error"] = err.Error() return err } if resp.StatusCode == 200 { fmt.Printf("Removed user %s\n", StyleBold.Render(name)) + cmd.Annotations["error"] = "none" } else { - bytes, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - body := string(bytes) - defer resp.Body.Close() - - fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) + body, _ := io.ReadAll(resp.Body) + fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, string(body)) + cmd.Annotations["error"] = fmt.Sprintf("request failed with status code %s", resp.Status) } return nil @@ -163,12 +184,18 @@ var SetUserRoleCmd = &cobra.Command{ } return nil }, - RunE: func(_ *cobra.Command, args []string) error { - name := args[0] + RunE: func(cmd *cobra.Command, args []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { + cmd.Annotations["error"] = err.Error() return err } @@ -176,25 +203,24 @@ var SetUserRoleCmd = &cobra.Command{ return user.ID == name }) { fmt.Printf("user doesn't exist. Please create the user with `pb user add %s`\n", name) + cmd.Annotations["error"] = "user does not exist" return nil } - // fetch all the roles to be applied to this user rolesToSet := args[1] rolesToSetArr := strings.Split(rolesToSet, ",") - - // fetch the role names on the server var rolesOnServer []string if err := fetchRoles(&client, &rolesOnServer); err != nil { + cmd.Annotations["error"] = err.Error() return err } rolesOnServerArr := strings.Join(rolesOnServer, " ") - // validate if roles to be applied are actually present on the server for idx, role := range rolesToSetArr { rolesToSetArr[idx] = strings.TrimSpace(role) if !strings.Contains(rolesOnServerArr, rolesToSetArr[idx]) { fmt.Printf("role %s doesn't exist, please create a role using `pb role add %s`\n", rolesToSetArr[idx], rolesToSetArr[idx]) + cmd.Annotations["error"] = fmt.Sprintf("role %s doesn't exist", rolesToSetArr[idx]) return nil } } @@ -204,16 +230,19 @@ var SetUserRoleCmd = &cobra.Command{ putBody = bytes.NewBuffer([]byte(putBodyJSON)) req, err := client.NewRequest("PUT", "user/"+name+"/role", putBody) if err != nil { + cmd.Annotations["error"] = err.Error() return err } resp, err := client.Client.Do(req) if err != nil { + cmd.Annotations["error"] = err.Error() return err } bytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["error"] = err.Error() return err } body := string(bytes) @@ -221,8 +250,10 @@ var SetUserRoleCmd = &cobra.Command{ if resp.StatusCode == 200 { fmt.Printf("Added role(s) %s to user %s\n", rolesToSet, name) + cmd.Annotations["error"] = "none" } else { fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) + cmd.Annotations["error"] = fmt.Sprintf("request failed with status code %s", resp.Status) } return nil @@ -234,14 +265,21 @@ var ListUserCmd = &cobra.Command{ Short: "List all users", Example: " pb user list", RunE: func(cmd *cobra.Command, _ []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + client := internalHTTP.DefaultClient(&DefaultProfile) users, err := fetchUsers(&client) if err != nil { + cmd.Annotations["error"] = err.Error() return err } roleResponses := make([]struct { - data []string // Collects roles as strings for text output + data []string err error }, len(users)) @@ -255,7 +293,6 @@ var ListUserCmd = &cobra.Command{ var userRolesData UserRoleData userRolesData, out.err = fetchUserRoles(client, userID) if out.err == nil { - // Collect role names for this user for role := range userRolesData { out.data = append(out.data, role) } @@ -266,13 +303,12 @@ var ListUserCmd = &cobra.Command{ wsg.Wait() - // Get the output format, defaulting to empty (existing behavior) outputFormat, err := cmd.Flags().GetString("output") if err != nil { + cmd.Annotations["error"] = err.Error() return err } - // JSON output if specified if outputFormat == "json" { usersWithRoles := make([]map[string]interface{}, len(users)) for idx, user := range users { @@ -283,13 +319,14 @@ var ListUserCmd = &cobra.Command{ } jsonOutput, err := json.MarshalIndent(usersWithRoles, "", " ") if err != nil { + cmd.Annotations["error"] = err.Error() return fmt.Errorf("failed to marshal JSON output: %w", err) } fmt.Println(string(jsonOutput)) + cmd.Annotations["error"] = "none" return nil } - // Text output if specified if outputFormat == "text" { fmt.Println() for idx, user := range users { @@ -302,10 +339,10 @@ var ListUserCmd = &cobra.Command{ } } fmt.Println() + cmd.Annotations["error"] = "none" return nil } - // Default output (existing layout) fmt.Println() for idx, user := range users { roles := roleResponses[idx] @@ -321,6 +358,7 @@ var ListUserCmd = &cobra.Command{ } fmt.Println() + cmd.Annotations["error"] = "none" return nil }, } diff --git a/main.go b/main.go index 4681fd8..6dbd737 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ var profile = &cobra.Command{ Short: "Manage different Parseable targets", Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", PersistentPostRun: func(cmd *cobra.Command, args []string) { - go analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, args) }, } @@ -74,6 +74,9 @@ var user = &cobra.Command{ Short: "Manage users", Long: "\nuser command is used to manage users.", PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + analytics.PostRunAnalytics(cmd, args) + }, } var role = &cobra.Command{ diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 193f8fb..0cbebad 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -1,10 +1,12 @@ package analytics import ( + "bytes" "encoding/json" "errors" "fmt" "io" + "net/http" "os" "os/exec" "path/filepath" @@ -150,7 +152,6 @@ func PostRunAnalytics(cmd *cobra.Command, args []string) { // sendEvent is a placeholder function to simulate sending an event after command execution. func sendEvent(commandName string, arguments []string, errors *string, executionStatus string, flags map[string]string) error { - uuid, err := ReadUUID() if err != nil { return fmt.Errorf("could not load UUID: %v", err) @@ -189,20 +190,38 @@ func sendEvent(commandName string, arguments []string, errors *string, execution ExecutionStatus: executionStatus, } + event.Profile.Password = "" + // Marshal the event to JSON for sending eventJSON, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal event JSON: %v", err) } - // Simulate sending the event (print or make an HTTP request) - fmt.Println("Sending event:", string(eventJSON)) + // Define the target URL for the HTTP request + url := "https://ingestor.demo.parseable.com/api/v1/logstream/analytics-test" + + // Create the HTTP POST request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(eventJSON)) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + req.SetBasicAuth("admin", "admin") + // Execute the HTTP request + resp, err := httpClient.Client.Do(req) + if err != nil { + return fmt.Errorf("failed to send event: %v", err) + } + defer resp.Body.Close() - // err = sendHTTPRequest("POST", "https://example.com/events", eventJSON) - // if err != nil { - // return fmt.Errorf("failed to send event: %v", err) - // } + // Check for a non-2xx status code + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("received non-2xx response: %v", resp.Status) + } + fmt.Println("Event sent successfully:", string(eventJSON)) return nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index 62d0d95..c57a9cc 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -49,9 +49,9 @@ type Config struct { // Profile is the struct that holds the profile configuration type Profile struct { - URL string - Username string - Password string + URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password,omitempty"` } func (p *Profile) GrpcAddr(port string) string { From 4e857b889e93968ff6d57ea9cb5242ec6c33ea98 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 00:07:46 +0530 Subject: [PATCH 06/14] streams --- cmd/stream.go | 124 +++++++++++++++++++++++++++++--------------------- main.go | 3 ++ 2 files changed, 76 insertions(+), 51 deletions(-) diff --git a/cmd/stream.go b/cmd/stream.go index bde61e6..d5d981b 100644 --- a/cmd/stream.go +++ b/cmd/stream.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "io" - "net/http" internalHTTP "pb/pkg/http" "strconv" "strings" @@ -111,24 +110,39 @@ var AddStreamCmd = &cobra.Command{ Example: " pb stream add backend_logs", Short: "Create a new stream", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + // Capture start time + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("PUT", "logstream/"+name, nil) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } resp, err := client.Client.Do(req) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } + // Capture execution time + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if resp.StatusCode == 200 { fmt.Printf("Created stream %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } body := string(bytes) @@ -147,12 +161,21 @@ var StatStreamCmd = &cobra.Command{ Short: "Get statistics for a stream", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + // Capture start time + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) // Fetch stats data stats, err := fetchStats(&client, name) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } @@ -164,12 +187,16 @@ var StatStreamCmd = &cobra.Command{ // Fetch retention data retention, err := fetchRetention(&client, name) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } // Fetch alerts data alertsData, err := fetchAlerts(&client, name) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } @@ -190,6 +217,8 @@ var StatStreamCmd = &cobra.Command{ jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } fmt.Println(string(jsonData)) @@ -255,29 +284,43 @@ var RemoveStreamCmd = &cobra.Command{ Example: " pb stream remove backend_logs", Short: "Delete a stream", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + // Capture start time + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "logstream/"+name, nil) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } resp, err := client.Client.Do(req) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } + // Capture execution time + cmd.Annotations["executionTime"] = time.Since(startTime).String() + if resp.StatusCode == 200 { - fmt.Printf("Removed stream %s\n", StyleBold.Render(name)) + fmt.Printf("Successfully deleted stream %s\n", StyleBold.Render(name)) } else { bytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } body := string(bytes) defer resp.Body.Close() - fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) } @@ -285,74 +328,53 @@ var RemoveStreamCmd = &cobra.Command{ }, } +// ListStreamCmd is the list command for streams var ListStreamCmd = &cobra.Command{ Use: "list", - Short: "List all streams", Example: " pb stream list", - RunE: func(cmd *cobra.Command, _ []string) error { + Short: "List all streams", + RunE: func(cmd *cobra.Command, args []string) error { + // Capture start time + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("GET", "logstream", nil) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } resp, err := client.Client.Do(req) if err != nil { + // Capture error + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - // Read response body for error message + var streams []StreamListItem + if resp.StatusCode == 200 { bytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) return err } - body := string(bytes) - fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) - return nil - } - - var items []map[string]string - err = json.NewDecoder(resp.Body).Decode(&items) - if err != nil { - return err - } - - // Get output flag value - outputFormat, err := cmd.Flags().GetString("output") - if err != nil { - return err - } - - // Handle JSON output format - if outputFormat == "json" { - // Collect stream names for JSON output - streams := make([]string, len(items)) - for i, item := range items { - streams[i] = item["name"] - } - jsonOutput, err := json.MarshalIndent(streams, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal JSON output: %w", err) + if err := json.Unmarshal(bytes, &streams); err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error: %s", err.Error()) + return err } - fmt.Println(string(jsonOutput)) - return nil - } - // Default to text output - if len(items) == 0 { - fmt.Println("No streams found") - return nil + for _, stream := range streams { + fmt.Println(stream.Render()) + } + } else { + fmt.Printf("Failed to fetch streams. Status Code: %s\n", resp.Status) } - fmt.Println() - for _, item := range items { - streamItem := StreamListItem{Name: item["name"]} - fmt.Print("• ") - fmt.Println(streamItem.Render()) - } - fmt.Println() return nil }, } diff --git a/main.go b/main.go index 6dbd737..aed936e 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,9 @@ var stream = &cobra.Command{ Short: "Manage streams", Long: "\nstream command is used to manage streams.", PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + analytics.PostRunAnalytics(cmd, args) + }, } var query = &cobra.Command{ From 5149d83b4997fac389e2d124f6dc0187d7ba3785 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 06:17:39 +0530 Subject: [PATCH 07/14] add analytics for cmds --- cmd/profile.go | 1 + cmd/query.go | 59 ++++++++++---------- cmd/role.go | 108 +++++++++++++++++++------------------ cmd/stream.go | 2 +- cmd/version.go | 62 ++++++++++++++++++--- main.go | 10 ++++ pkg/analytics/analytics.go | 42 +++++++-------- 7 files changed, 177 insertions(+), 107 deletions(-) diff --git a/cmd/profile.go b/cmd/profile.go index 2d038c3..3efc30f 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -286,6 +286,7 @@ var ListProfileCmd = &cobra.Command{ for key, value := range fileConfig.Profiles { item := ProfileListItem{key, value.URL, value.Username} fmt.Println(item.Render(fileConfig.DefaultProfile == key)) + fmt.Println() // Add a blank line after each profile } cmd.Annotations["executionTime"] = time.Since(startTime).String() return nil diff --git a/cmd/query.go b/cmd/query.go index 1768f9a..9d3fd08 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -12,7 +12,6 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - package cmd import ( @@ -22,6 +21,7 @@ import ( "io" "os" "strings" + "time" // "pb/pkg/model" @@ -41,15 +41,7 @@ var ( endFlagShort = "t" defaultEnd = "now" - // // save filter flags - // saveQueryFlag = "save-as" - // saveQueryShort = "s" - // // save filter with time flags - // saveQueryTimeFlag = "with-time" - // saveQueryTimeShort = "w" outputFlag = "output" - // interactiveFlag = "interactive" - // interactiveFlagShort = "i" ) var query = &cobra.Command{ @@ -60,18 +52,26 @@ var query = &cobra.Command{ Args: cobra.MaximumNArgs(1), PreRunE: PreRunDefaultProfile, RunE: func(command *cobra.Command, args []string) error { - var query string + startTime := time.Now() + command.Annotations = map[string]string{ + "startTime": startTime.Format(time.RFC3339), + } + + defer func() { + duration := time.Since(startTime) + command.Annotations["executionTime"] = duration.String() + }() if len(args) == 0 || strings.TrimSpace(args[0]) == "" { - fmt.Println("please enter your query") + fmt.Println("Please enter your query") fmt.Printf("Example:\n pb query run \"select * from frontend\" --from=10m --to=now\n") return nil } - query = args[0] - + query := args[0] start, err := command.Flags().GetString(startFlag) if err != nil { + command.Annotations["error"] = err.Error() return err } if start == "" { @@ -80,60 +80,65 @@ var query = &cobra.Command{ end, err := command.Flags().GetString(endFlag) if err != nil { + command.Annotations["error"] = err.Error() return err } if end == "" { end = defaultEnd } - outputFormat, err := command.Flags().GetString(outputFlag) + outputFormat, err := command.Flags().GetString("output") if err != nil { - return err + command.Annotations["error"] = err.Error() + return fmt.Errorf("failed to get 'output' flag: %w", err) } client := internalHTTP.DefaultClient(&DefaultProfile) - return fetchData(&client, query, start, end, outputFormat) + err = fetchData(&client, query, start, end, outputFormat) + if err != nil { + command.Annotations["error"] = err.Error() + } + return err }, } -var QueryCmd = func() *cobra.Command { +func init() { query.Flags().StringP(startFlag, startFlagShort, defaultStart, "Start time for query.") query.Flags().StringP(endFlag, endFlagShort, defaultEnd, "End time for query.") - query.Flags().StringP(outputFlag, "o", "text", "Output format (text or json).") - return query -}() + query.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") +} + +var QueryCmd = query -func fetchData(client *internalHTTP.HTTPClient, query string, startTime, endTime, outputFormat string) (err error) { +func fetchData(client *internalHTTP.HTTPClient, query string, startTime, endTime, outputFormat string) error { queryTemplate := `{ "query": "%s", "startTime": "%s", "endTime": "%s" }` - finalQuery := fmt.Sprintf(queryTemplate, query, startTime, endTime) req, err := client.NewRequest("POST", "query", bytes.NewBuffer([]byte(finalQuery))) if err != nil { - return + return fmt.Errorf("failed to create new request: %w", err) } resp, err := client.Client.Do(req) if err != nil { - return + return fmt.Errorf("request execution failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) - return nil + return fmt.Errorf("non-200 status code received: %s", resp.Status) } if outputFormat == "json" { var jsonResponse []map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { - fmt.Println("Error decoding JSON response:", err) - return err + return fmt.Errorf("error decoding JSON response: %w", err) } encodedResponse, _ := json.MarshalIndent(jsonResponse, "", " ") fmt.Println(string(encodedResponse)) diff --git a/cmd/role.go b/cmd/role.go index 6396360..dbd1cbd 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -20,10 +20,10 @@ import ( "encoding/json" "fmt" "io" - "os" "pb/pkg/model/role" "strings" "sync" + "time" internalHTTP "pb/pkg/http" @@ -68,15 +68,22 @@ var AddRoleCmd = &cobra.Command{ Example: " pb role add ingestors", Short: "Add a new role", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] - // check if the role already exists var roles []string client := internalHTTP.DefaultClient(&DefaultProfile) if err := fetchRoles(&client, &roles); err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error fetching roles: %s", err.Error()) return err } + if strings.Contains(strings.Join(roles, " "), name) { fmt.Println("role already exists, please use a different name") return nil @@ -84,11 +91,11 @@ var AddRoleCmd = &cobra.Command{ _m, err := tea.NewProgram(role.New()).Run() if err != nil { - fmt.Printf("there's been an error: %v", err) - os.Exit(1) + cmd.Annotations["errors"] = fmt.Sprintf("Error initializing program: %s", err.Error()) + return err } - m := _m.(role.Model) + m := _m.(role.Model) privilege := m.Selection.Value() stream := m.Stream.Value() tag := m.Tag.Value() @@ -99,28 +106,13 @@ var AddRoleCmd = &cobra.Command{ } var putBody io.Reader - - // set role if privilege != "none" { - roleData := RoleData{ - Privilege: privilege, - } + roleData := RoleData{Privilege: privilege} switch privilege { - case "writer": - roleData.Resource = &RoleResource{ - Stream: stream, - } + case "writer", "ingestor": + roleData.Resource = &RoleResource{Stream: stream} case "reader": - roleData.Resource = &RoleResource{ - Stream: stream, - } - if tag != "" { - roleData.Resource.Tag = tag - } - case "ingestor": - roleData.Resource = &RoleResource{ - Stream: stream, - } + roleData.Resource = &RoleResource{Stream: stream, Tag: tag} } roleDataJSON, _ := json.Marshal([]RoleData{roleData}) putBody = bytes.NewBuffer(roleDataJSON) @@ -128,24 +120,28 @@ var AddRoleCmd = &cobra.Command{ req, err := client.NewRequest("PUT", "role/"+name, putBody) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error creating request: %s", err.Error()) return err } resp, err := client.Client.Do(req) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error performing request: %s", err.Error()) return err } + defer resp.Body.Close() - bytes, err := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error reading response: %s", err.Error()) return err } - body := string(bytes) - defer resp.Body.Close() + body := string(bodyBytes) if resp.StatusCode == 200 { fmt.Printf("Added role %s", name) } else { + cmd.Annotations["errors"] = fmt.Sprintf("Request failed - Status: %s, Response: %s", resp.Status, body) fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) } @@ -159,29 +155,38 @@ var RemoveRoleCmd = &cobra.Command{ Example: " pb role remove ingestor", Short: "Delete a role", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + name := args[0] client := internalHTTP.DefaultClient(&DefaultProfile) req, err := client.NewRequest("DELETE", "role/"+name, nil) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error creating delete request: %s", err.Error()) return err } resp, err := client.Client.Do(req) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error performing delete request: %s", err.Error()) return err } + defer resp.Body.Close() if resp.StatusCode == 200 { fmt.Printf("Removed role %s\n", StyleBold.Render(name)) } else { - bytes, err := io.ReadAll(resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error reading response: %s", err.Error()) return err } - body := string(bytes) - defer resp.Body.Close() - + body := string(bodyBytes) + cmd.Annotations["errors"] = fmt.Sprintf("Request failed - Status: %s, Response: %s", resp.Status, body) fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body) } @@ -194,16 +199,23 @@ var ListRoleCmd = &cobra.Command{ Short: "List all roles", Example: " pb role list", RunE: func(cmd *cobra.Command, _ []string) error { + startTime := time.Now() + cmd.Annotations = make(map[string]string) + defer func() { + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + var roles []string client := internalHTTP.DefaultClient(&DefaultProfile) err := fetchRoles(&client, &roles) if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error fetching roles: %s", err.Error()) return err } - // Get output flag value outputFormat, err := cmd.Flags().GetString("output") if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error retrieving output flag: %s", err.Error()) return err } @@ -212,39 +224,32 @@ var ListRoleCmd = &cobra.Command{ err error }, len(roles)) - wsg := sync.WaitGroup{} + var wg sync.WaitGroup for idx, role := range roles { - wsg.Add(1) - out := &roleResponses[idx] - role := role - client := &client - go func() { - out.data, out.err = fetchSpecificRole(client, role) - wsg.Done() - }() + wg.Add(1) + go func(idx int, role string) { + defer wg.Done() + roleResponses[idx].data, roleResponses[idx].err = fetchSpecificRole(&client, role) + }(idx, role) } + wg.Wait() - wsg.Wait() - - // Output in JSON format if requested if outputFormat == "json" { - // Collect the role data into a structured format allRoles := map[string][]RoleData{} for idx, roleName := range roles { if roleResponses[idx].err == nil { allRoles[roleName] = roleResponses[idx].data } } - // Marshal and print as JSON jsonOutput, err := json.MarshalIndent(allRoles, "", " ") if err != nil { + cmd.Annotations["errors"] = fmt.Sprintf("Error marshaling JSON output: %s", err.Error()) return fmt.Errorf("failed to marshal JSON output: %w", err) } fmt.Println(string(jsonOutput)) return nil } - // Default output in text format fmt.Println() for idx, roleName := range roles { fetchRes := roleResponses[idx] @@ -255,7 +260,8 @@ var ListRoleCmd = &cobra.Command{ fmt.Println(lipgloss.NewStyle().PaddingLeft(3).Render(role.Render())) } } else { - fmt.Println(fetchRes.err) + fmt.Printf("Error fetching role data for %s: %v\n", roleName, fetchRes.err) + cmd.Annotations["errors"] += fmt.Sprintf("Error fetching role data for %s: %v\n", roleName, fetchRes.err) } } @@ -326,5 +332,5 @@ func fetchSpecificRole(client *internalHTTP.HTTPClient, role string) (res []Role func init() { // Add the --output flag with default value "text" - ListRoleCmd.Flags().String("output", "text", "Output format: 'text' or 'json'") + ListRoleCmd.Flags().StringP("output", "o", "text", "Output format: 'text' or 'json'") } diff --git a/cmd/stream.go b/cmd/stream.go index d5d981b..070a66b 100644 --- a/cmd/stream.go +++ b/cmd/stream.go @@ -275,7 +275,7 @@ var StatStreamCmd = &cobra.Command{ } func init() { - StatStreamCmd.Flags().String("output", "text", "Output format: text or json") + StatStreamCmd.Flags().StringVarP(&outputFormat, "output", "o", "", "Output format (text|json)") } var RemoveStreamCmd = &cobra.Command{ diff --git a/cmd/version.go b/cmd/version.go index 0dd3580..a3d069a 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -16,9 +16,11 @@ package cmd import ( + "encoding/json" "fmt" "pb/pkg/analytics" internalHTTP "pb/pkg/http" + "time" "github.com/spf13/cobra" ) @@ -29,25 +31,71 @@ var VersionCmd = &cobra.Command{ Short: "Print version", Long: "Print version and commit information", Example: " pb version", + Run: func(cmd *cobra.Command, args []string) { + if cmd.Annotations == nil { + cmd.Annotations = make(map[string]string) + } + + startTime := time.Now() + defer func() { + // Capture the execution time in annotations + cmd.Annotations["executionTime"] = time.Since(startTime).String() + }() + + err := PrintVersion("1.0.0", "abc123") // Replace with actual version and commit values + if err != nil { + cmd.Annotations["error"] = err.Error() + } + }, +} + +func init() { + VersionCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)") } // PrintVersion prints version information -func PrintVersion(version, commit string) { +func PrintVersion(version, commit string) error { client := internalHTTP.DefaultClient(&DefaultProfile) - fmt.Printf("\n%s \n", StandardStyleAlt.Render("pb version")) - fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), version) - fmt.Printf("- %s %s\n\n", StandardStyleBold.Render("commit: "), commit) - + // Fetch server information if err := PreRun(); err != nil { - return + return fmt.Errorf("error in PreRun: %w", err) } + about, err := analytics.FetchAbout(&client) if err != nil { - return + return fmt.Errorf("error fetching server information: %w", err) + } + + // Output as JSON if specified + if outputFormat == "json" { + versionInfo := map[string]interface{}{ + "client": map[string]string{ + "version": version, + "commit": commit, + }, + "server": map[string]string{ + "url": DefaultProfile.URL, + "version": about.Version, + "commit": about.Commit, + }, + } + jsonData, err := json.MarshalIndent(versionInfo, "", " ") + if err != nil { + return fmt.Errorf("error generating JSON output: %w", err) + } + fmt.Println(string(jsonData)) + return nil } + // Default: Output as text + fmt.Printf("\n%s \n", StandardStyleAlt.Render("pb version")) + fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), version) + fmt.Printf("- %s %s\n\n", StandardStyleBold.Render("commit: "), commit) + fmt.Printf("%s %s \n", StandardStyleAlt.Render("Connected to"), StandardStyleBold.Render(DefaultProfile.URL)) fmt.Printf("- %s %s\n", StandardStyleBold.Render("version: "), about.Version) fmt.Printf("- %s %s\n\n", StandardStyleBold.Render("commit: "), about.Commit) + + return nil } diff --git a/main.go b/main.go index aed936e..8dd76f3 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,9 @@ var cli = &cobra.Command{ } return errors.New("no command or flag supplied") }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + analytics.PostRunAnalytics(cmd, args) + }, } var profile = &cobra.Command{ @@ -84,6 +87,9 @@ var role = &cobra.Command{ Short: "Manage roles", Long: "\nrole command is used to manage roles.", PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + analytics.PostRunAnalytics(cmd, args) + }, } var stream = &cobra.Command{ @@ -101,6 +107,9 @@ var query = &cobra.Command{ Short: "Run SQL query on a log stream", Long: "\nRun SQL query on a log stream. Default output format is json. Use -i flag to open interactive table view.", PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + analytics.PostRunAnalytics(cmd, args) + }, } func main() { @@ -139,6 +148,7 @@ func main() { cmd.VersionCmd.Run = func(_ *cobra.Command, _ []string) { cmd.PrintVersion(Version, Commit) } + cli.AddCommand(cmd.VersionCmd) // set as flag cli.Flags().BoolP(versionFlag, versionFlagShort, false, "Print version") diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 0cbebad..5211d8d 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -24,16 +24,16 @@ import ( ) type Event struct { - CLIVersion string `json:"cli_version"` - UUID string `json:"uuid"` - CommitHash string `json:"commit_hash"` - OSName string `json:"os_name"` - OSVersion string `json:"os_version"` - ReportCreatedAt string `json:"report_created_at"` - Command Command `json:"command"` - Profile config.Profile `json:"profile"` - Errors *string `json:"errors"` - ExecutionStatus string `json:"execution_status"` + CLIVersion string `json:"cli_version"` + UUID string `json:"uuid"` + CommitHash string `json:"commit_hash"` + OSName string `json:"os_name"` + OSVersion string `json:"os_version"` + ReportCreatedAt string `json:"report_created_at"` + Command Command `json:"command"` + Profile config.Profile `json:"profile"` + Errors *string `json:"errors"` + ExecutionTimestamp string `json:"execution_timestamp"` } // About struct @@ -151,7 +151,7 @@ func PostRunAnalytics(cmd *cobra.Command, args []string) { } // sendEvent is a placeholder function to simulate sending an event after command execution. -func sendEvent(commandName string, arguments []string, errors *string, executionStatus string, flags map[string]string) error { +func sendEvent(commandName string, arguments []string, errors *string, executionTimestamp string, flags map[string]string) error { uuid, err := ReadUUID() if err != nil { return fmt.Errorf("could not load UUID: %v", err) @@ -178,16 +178,16 @@ func sendEvent(commandName string, arguments []string, errors *string, execution // Populate the Event struct with OS details and timestamp event := Event{ - CLIVersion: about.Commit, - UUID: uuid, - CommitHash: about.Commit, - Profile: profile, - OSName: GetOSName(), - OSVersion: GetOSVersion(), - ReportCreatedAt: GetCurrentTimestamp(), - Command: cmd, - Errors: errors, - ExecutionStatus: executionStatus, + CLIVersion: about.Commit, + UUID: uuid, + CommitHash: about.Commit, + Profile: profile, + OSName: GetOSName(), + OSVersion: GetOSVersion(), + ReportCreatedAt: GetCurrentTimestamp(), + Command: cmd, + Errors: errors, + ExecutionTimestamp: executionTimestamp, } event.Profile.Password = "" From 2e2d91d444436a21f84afddea51bbdc81da60ae0 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 06:38:59 +0530 Subject: [PATCH 08/14] streamline pre runs --- cmd/query.go | 1 + cmd/stream.go | 2 +- cmd/user.go | 1 - cmd/version.go | 2 +- main.go | 39 ++++++++++++++++++++++++++++----------- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/cmd/query.go b/cmd/query.go index 9d3fd08..49afd93 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -12,6 +12,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . + package cmd import ( diff --git a/cmd/stream.go b/cmd/stream.go index 070a66b..7dcef13 100644 --- a/cmd/stream.go +++ b/cmd/stream.go @@ -333,7 +333,7 @@ var ListStreamCmd = &cobra.Command{ Use: "list", Example: " pb stream list", Short: "List all streams", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { // Capture start time startTime := time.Now() cmd.Annotations = make(map[string]string) diff --git a/cmd/user.go b/cmd/user.go index 1db904b..fc02679 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -133,7 +133,6 @@ var AddUserCmd = func() *cobra.Command { return addUser }() -// Similar changes for RemoveUserCmd var RemoveUserCmd = &cobra.Command{ Use: "remove user-name", Aliases: []string{"rm"}, diff --git a/cmd/version.go b/cmd/version.go index a3d069a..21cdeb5 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -31,7 +31,7 @@ var VersionCmd = &cobra.Command{ Short: "Print version", Long: "Print version and commit information", Example: " pb version", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, _ []string) { if cmd.Annotations == nil { cmd.Annotations = make(map[string]string) } diff --git a/main.go b/main.go index 8dd76f3..ce62d85 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "pb/cmd" + pb "pb/cmd" "pb/pkg/analytics" "pb/pkg/config" @@ -48,12 +49,13 @@ func defaultInitialProfile() config.Profile { // Root command var cli = &cobra.Command{ - Use: "pb", - Short: "\nParseable command line interface", - Long: "\npb is the command line interface for Parseable", + Use: "pb", + Short: "\nParseable command line interface", + Long: "\npb is the command line interface for Parseable", + PersistentPreRunE: analytics.CheckAndCreateUUID, RunE: func(command *cobra.Command, _ []string) error { if p, _ := command.Flags().GetBool(versionFlag); p { - cmd.PrintVersion(Version, Commit) + pb.PrintVersion(Version, Commit) return nil } return errors.New("no command or flag supplied") @@ -64,9 +66,10 @@ var cli = &cobra.Command{ } var profile = &cobra.Command{ - Use: "profile", - Short: "Manage different Parseable targets", - Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", + Use: "profile", + Short: "Manage different Parseable targets", + Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", + PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { analytics.PostRunAnalytics(cmd, args) }, @@ -76,7 +79,7 @@ var user = &cobra.Command{ Use: "user", Short: "Manage users", Long: "\nuser command is used to manage users.", - PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { analytics.PostRunAnalytics(cmd, args) }, @@ -86,7 +89,7 @@ var role = &cobra.Command{ Use: "role", Short: "Manage roles", Long: "\nrole command is used to manage roles.", - PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { analytics.PostRunAnalytics(cmd, args) }, @@ -96,7 +99,7 @@ var stream = &cobra.Command{ Use: "stream", Short: "Manage streams", Long: "\nstream command is used to manage streams.", - PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { analytics.PostRunAnalytics(cmd, args) }, @@ -106,7 +109,7 @@ var query = &cobra.Command{ Use: "query", Short: "Run SQL query on a log stream", Long: "\nRun SQL query on a log stream. Default output format is json. Use -i flag to open interactive table view.", - PersistentPreRunE: cmd.PreRunDefaultProfile, + PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { analytics.PostRunAnalytics(cmd, args) }, @@ -194,3 +197,17 @@ func main() { os.Exit(1) } } + +// Wrapper to combine existing pre-run logic and UUID check +func combinedPreRun(cmd *cobra.Command, args []string) error { + err := pb.PreRunDefaultProfile(cmd, args) + if err != nil { + return fmt.Errorf("error initialising default profile: %w", err) + } + + if err := analytics.CheckAndCreateUUID(cmd, args); err != nil { + return fmt.Errorf("error while creating UUID: %v", err) + } + + return nil +} From da867ac4643275bde339f3ee1bc52fb8df100f41 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 07:53:09 +0530 Subject: [PATCH 09/14] make veriferis, add wait sync groups --- go.mod | 6 +-- go.sum | 3 ++ main.go | 96 ++++++++++++++++++++++++-------------- pkg/analytics/analytics.go | 47 ++++++++++--------- 4 files changed, 94 insertions(+), 58 deletions(-) diff --git a/go.mod b/go.mod index c622463..8f39a8e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,8 @@ require ( github.com/charmbracelet/bubbletea v0.26.6 github.com/charmbracelet/lipgloss v0.12.1 github.com/dustin/go-humanize v1.0.1 - github.com/google/uuid v1.6.0 + github.com/oklog/ulid/v2 v2.1.0 + github.com/spf13/pflag v1.0.5 golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 golang.org/x/term v0.21.0 google.golang.org/grpc v1.64.1 @@ -27,7 +28,6 @@ require ( github.com/klauspost/compress v1.15.15 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/mod v0.18.0 // indirect @@ -41,7 +41,7 @@ require ( require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/evertras/bubble-table v0.15.2 - github.com/muesli/termenv v0.15.2 + github.com/muesli/termenv v0.15.2 // indirect github.com/pelletier/go-toml/v2 v2.0.9 github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect ) diff --git a/go.sum b/go.sum index e9b2573..ea491fe 100644 --- a/go.sum +++ b/go.sum @@ -61,6 +61,9 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= diff --git a/main.go b/main.go index ce62d85..ba1c1e3 100644 --- a/main.go +++ b/main.go @@ -20,14 +20,17 @@ import ( "errors" "fmt" "os" - "pb/cmd" + pb "pb/cmd" "pb/pkg/analytics" "pb/pkg/config" + "sync" "github.com/spf13/cobra" ) +var wg sync.WaitGroup + // populated at build time var ( Version string @@ -52,7 +55,7 @@ var cli = &cobra.Command{ Use: "pb", Short: "\nParseable command line interface", Long: "\npb is the command line interface for Parseable", - PersistentPreRunE: analytics.CheckAndCreateUUID, + PersistentPreRunE: analytics.CheckAndCreateULID, RunE: func(command *cobra.Command, _ []string) error { if p, _ := command.Flags().GetBool(versionFlag); p { pb.PrintVersion(Version, Commit) @@ -61,7 +64,11 @@ var cli = &cobra.Command{ return errors.New("no command or flag supplied") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } @@ -71,7 +78,11 @@ var profile = &cobra.Command{ Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } @@ -81,7 +92,11 @@ var user = &cobra.Command{ Long: "\nuser command is used to manage users.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } @@ -91,7 +106,11 @@ var role = &cobra.Command{ Long: "\nrole command is used to manage roles.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } @@ -101,7 +120,11 @@ var stream = &cobra.Command{ Long: "\nstream command is used to manage streams.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } @@ -111,48 +134,52 @@ var query = &cobra.Command{ Long: "\nRun SQL query on a log stream. Default output format is json. Use -i flag to open interactive table view.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { - analytics.PostRunAnalytics(cmd, args) + wg.Add(1) + go func() { + defer wg.Done() + analytics.PostRunAnalytics(cmd, args) + }() }, } func main() { - profile.AddCommand(cmd.AddProfileCmd) - profile.AddCommand(cmd.RemoveProfileCmd) - profile.AddCommand(cmd.ListProfileCmd) - profile.AddCommand(cmd.DefaultProfileCmd) + profile.AddCommand(pb.AddProfileCmd) + profile.AddCommand(pb.RemoveProfileCmd) + profile.AddCommand(pb.ListProfileCmd) + profile.AddCommand(pb.DefaultProfileCmd) - user.AddCommand(cmd.AddUserCmd) - user.AddCommand(cmd.RemoveUserCmd) - user.AddCommand(cmd.ListUserCmd) - user.AddCommand(cmd.SetUserRoleCmd) + user.AddCommand(pb.AddUserCmd) + user.AddCommand(pb.RemoveUserCmd) + user.AddCommand(pb.ListUserCmd) + user.AddCommand(pb.SetUserRoleCmd) - role.AddCommand(cmd.AddRoleCmd) - role.AddCommand(cmd.RemoveRoleCmd) - role.AddCommand(cmd.ListRoleCmd) + role.AddCommand(pb.AddRoleCmd) + role.AddCommand(pb.RemoveRoleCmd) + role.AddCommand(pb.ListRoleCmd) - stream.AddCommand(cmd.AddStreamCmd) - stream.AddCommand(cmd.RemoveStreamCmd) - stream.AddCommand(cmd.ListStreamCmd) - stream.AddCommand(cmd.StatStreamCmd) + stream.AddCommand(pb.AddStreamCmd) + stream.AddCommand(pb.RemoveStreamCmd) + stream.AddCommand(pb.ListStreamCmd) + stream.AddCommand(pb.StatStreamCmd) - query.AddCommand(cmd.QueryCmd) - query.AddCommand(cmd.SavedQueryList) + query.AddCommand(pb.QueryCmd) + query.AddCommand(pb.SavedQueryList) cli.AddCommand(profile) cli.AddCommand(query) cli.AddCommand(stream) cli.AddCommand(user) cli.AddCommand(role) - cli.AddCommand(cmd.TailCmd) + cli.AddCommand(pb.TailCmd) - cli.AddCommand(cmd.AutocompleteCmd) + cli.AddCommand(pb.AutocompleteCmd) // Set as command - cmd.VersionCmd.Run = func(_ *cobra.Command, _ []string) { - cmd.PrintVersion(Version, Commit) + pb.VersionCmd.Run = func(_ *cobra.Command, _ []string) { + pb.PrintVersion(Version, Commit) } - cli.AddCommand(cmd.VersionCmd) + cli.AddCommand(pb.VersionCmd) // set as flag cli.Flags().BoolP(versionFlag, versionFlagShort, false, "Print version") @@ -196,17 +223,18 @@ func main() { if err != nil { os.Exit(1) } + wg.Wait() } -// Wrapper to combine existing pre-run logic and UUID check +// Wrapper to combine existing pre-run logic and ULID check func combinedPreRun(cmd *cobra.Command, args []string) error { err := pb.PreRunDefaultProfile(cmd, args) if err != nil { - return fmt.Errorf("error initialising default profile: %w", err) + return fmt.Errorf("error initializing default profile: %w", err) } - if err := analytics.CheckAndCreateUUID(cmd, args); err != nil { - return fmt.Errorf("error while creating UUID: %v", err) + if err := analytics.CheckAndCreateULID(cmd, args); err != nil { + return fmt.Errorf("error while creating ulid: %v", err) } return nil diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 5211d8d..62f16f6 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math/rand" "net/http" "os" "os/exec" @@ -17,7 +18,7 @@ import ( "pb/pkg/config" internalHTTP "pb/pkg/http" - "github.com/google/uuid" + "github.com/oklog/ulid/v2" "github.com/spf13/cobra" "github.com/spf13/pflag" "gopkg.in/yaml.v2" @@ -25,7 +26,7 @@ import ( type Event struct { CLIVersion string `json:"cli_version"` - UUID string `json:"uuid"` + ULID string `json:"ulid"` CommitHash string `json:"commit_hash"` OSName string `json:"os_name"` OSVersion string `json:"os_version"` @@ -76,11 +77,11 @@ type Command struct { // Config struct for parsing YAML type Config struct { - UUID string `yaml:"uuid"` + ULID string `yaml:"ulid"` } -// CheckAndCreateUUID checks for a UUID in the config file and creates it if absent. -func CheckAndCreateUUID(_ *cobra.Command, _ []string) error { +// CheckAndCreateULID checks for a ULID in the config file and creates it if absent. +func CheckAndCreateULID(_ *cobra.Command, _ []string) error { homeDir, err := os.UserHomeDir() if err != nil { fmt.Printf("could not find home directory: %v\n", err) @@ -109,21 +110,25 @@ func CheckAndCreateUUID(_ *cobra.Command, _ []string) error { } } - // Check if UUID is missing - if config.UUID == "" { - config.UUID = uuid.New().String() // Generate a new UUID + // Check if ULID is missing + if config.ULID == "" { + // Generate a new ULID + entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0) + ulidInstance := ulid.MustNew(ulid.Timestamp(time.Now()), entropy) + config.ULID = ulidInstance.String() + newData, err := yaml.Marshal(&config) if err != nil { fmt.Printf("could not marshal config data: %v\n", err) return err } - // Write updated config with UUID back to the file + // Write updated config with ULID back to the file if err := os.WriteFile(configPath, newData, 0644); err != nil { fmt.Printf("could not write to config file: %v\n", err) return err } - fmt.Printf("Generated and saved new UUID: %s\n", config.UUID) + fmt.Printf("Generated and saved new ULID: %s\n", config.ULID) } return nil @@ -152,9 +157,9 @@ func PostRunAnalytics(cmd *cobra.Command, args []string) { // sendEvent is a placeholder function to simulate sending an event after command execution. func sendEvent(commandName string, arguments []string, errors *string, executionTimestamp string, flags map[string]string) error { - uuid, err := ReadUUID() + ulid, err := ReadUULD() if err != nil { - return fmt.Errorf("could not load UUID: %v", err) + return fmt.Errorf("could not load ULID: %v", err) } profile, err := GetProfile() @@ -179,7 +184,7 @@ func sendEvent(commandName string, arguments []string, errors *string, execution // Populate the Event struct with OS details and timestamp event := Event{ CLIVersion: about.Commit, - UUID: uuid, + ULID: ulid, CommitHash: about.Commit, Profile: profile, OSName: GetOSName(), @@ -199,7 +204,7 @@ func sendEvent(commandName string, arguments []string, errors *string, execution } // Define the target URL for the HTTP request - url := "https://ingestor.demo.parseable.com/api/v1/logstream/analytics-test" + url := "https://ingestor.demo.parseable.com/api/v1/logstream/analytics-test-new" // Create the HTTP POST request req, err := http.NewRequest("POST", url, bytes.NewBuffer(eventJSON)) @@ -221,7 +226,7 @@ func sendEvent(commandName string, arguments []string, errors *string, execution return fmt.Errorf("received non-2xx response: %v", resp.Status) } - fmt.Println("Event sent successfully:", string(eventJSON)) + //fmt.Println("Event sent successfully:", string(eventJSON)) return nil } @@ -309,7 +314,7 @@ func getWindowsVersion() string { return strings.TrimSpace(string(out)) } -func ReadUUID() (string, error) { +func ReadUULD() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("could not find home directory: %v", err) @@ -319,7 +324,7 @@ func ReadUUID() (string, error) { // Check if config path exists if _, err := os.Stat(configPath); os.IsNotExist(err) { - return "", fmt.Errorf("config file does not exist, please run CheckAndCreateUUID first") + return "", fmt.Errorf("config file does not exist, please run CheckAndCreateULID first") } // Read the config file @@ -329,16 +334,16 @@ func ReadUUID() (string, error) { return "", fmt.Errorf("could not read config file: %v", err) } - // Unmarshal the content to get the UUID + // Unmarshal the content to get the ULID if err := yaml.Unmarshal(data, &config); err != nil { return "", fmt.Errorf("could not parse config file: %v", err) } - if config.UUID == "" { - return "", fmt.Errorf("UUID is missing in config file") + if config.ULID == "" { + return "", fmt.Errorf("ULID is missing in config file") } - return config.UUID, nil + return config.ULID, nil } func FetchAbout(client *internalHTTP.HTTPClient) (about About, err error) { From ea97e091d85d679fdf5c7b5df02da8c5c5696199 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 07:55:55 +0530 Subject: [PATCH 10/14] disable analytics via env --- main.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/main.go b/main.go index ba1c1e3..18e3337 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,9 @@ var cli = &cobra.Command{ return errors.New("no command or flag supplied") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() @@ -78,6 +81,9 @@ var profile = &cobra.Command{ Long: "\nuse profile command to configure different Parseable instances. Each profile takes a URL and credentials.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() @@ -92,6 +98,9 @@ var user = &cobra.Command{ Long: "\nuser command is used to manage users.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() @@ -106,6 +115,9 @@ var role = &cobra.Command{ Long: "\nrole command is used to manage roles.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() @@ -120,6 +132,9 @@ var stream = &cobra.Command{ Long: "\nstream command is used to manage streams.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() @@ -134,6 +149,9 @@ var query = &cobra.Command{ Long: "\nRun SQL query on a log stream. Default output format is json. Use -i flag to open interactive table view.", PersistentPreRunE: combinedPreRun, PersistentPostRun: func(cmd *cobra.Command, args []string) { + if os.Getenv("PB_ANALYTICS") == "disable" { + return + } wg.Add(1) go func() { defer wg.Done() From 1a8b52306e34de0873663b0036d85dafee689a1b Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Sun, 10 Nov 2024 07:58:56 +0530 Subject: [PATCH 11/14] handle null in query list json --- cmd/queryList.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/queryList.go b/cmd/queryList.go index 576177c..a81b196 100644 --- a/cmd/queryList.go +++ b/cmd/queryList.go @@ -65,6 +65,10 @@ var SavedQueryList = &cobra.Command{ fmt.Println("Error converting saved queries to JSON:", err) return } + if string(jsonOutput) == "null" { + fmt.Println("[]") + return + } fmt.Println(string(jsonOutput)) } else { for _, query := range userSavedQueries { From 1eb2d4dfab827f07a54090249ed17714c1e266de Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Mon, 11 Nov 2024 15:56:12 +0530 Subject: [PATCH 12/14] tweaks to server url, stream name and args --- main.go | 12 ++++++------ pkg/analytics/analytics.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 18e3337..55985fe 100644 --- a/main.go +++ b/main.go @@ -70,7 +70,7 @@ var cli = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "cli", args) }() }, } @@ -87,7 +87,7 @@ var profile = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "profile", args) }() }, } @@ -104,7 +104,7 @@ var user = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "user", args) }() }, } @@ -121,7 +121,7 @@ var role = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "role", args) }() }, } @@ -138,7 +138,7 @@ var stream = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "stream", args) }() }, } @@ -155,7 +155,7 @@ var query = &cobra.Command{ wg.Add(1) go func() { defer wg.Done() - analytics.PostRunAnalytics(cmd, args) + analytics.PostRunAnalytics(cmd, "query", args) }() }, } diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 62f16f6..385c9e7 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -134,17 +134,18 @@ func CheckAndCreateULID(_ *cobra.Command, _ []string) error { return nil } -func PostRunAnalytics(cmd *cobra.Command, args []string) { +func PostRunAnalytics(cmd *cobra.Command, name string, args []string) { executionTime := cmd.Annotations["executionTime"] commandError := cmd.Annotations["error"] flags := make(map[string]string) cmd.Flags().VisitAll(func(flag *pflag.Flag) { flags[flag.Name] = flag.Value.String() }) + // Call SendEvent in PostRunE err := sendEvent( - cmd.Name(), - args, + name, + append(args, cmd.Name()), &commandError, // Pass the error here if there was one executionTime, flags, @@ -186,7 +187,6 @@ func sendEvent(commandName string, arguments []string, errors *string, execution CLIVersion: about.Commit, ULID: ulid, CommitHash: about.Commit, - Profile: profile, OSName: GetOSName(), OSVersion: GetOSVersion(), ReportCreatedAt: GetCurrentTimestamp(), @@ -204,7 +204,7 @@ func sendEvent(commandName string, arguments []string, errors *string, execution } // Define the target URL for the HTTP request - url := "https://ingestor.demo.parseable.com/api/v1/logstream/analytics-test-new" + url := "https://analytics.parseable.io:80" // Create the HTTP POST request req, err := http.NewRequest("POST", url, bytes.NewBuffer(eventJSON)) @@ -212,8 +212,8 @@ func sendEvent(commandName string, arguments []string, errors *string, execution return fmt.Errorf("failed to create HTTP request: %v", err) } req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-P-Stream", "pb-usage") - req.SetBasicAuth("admin", "admin") // Execute the HTTP request resp, err := httpClient.Client.Do(req) if err != nil { From 5f57e342fca96564f861842e2e0e378497492db5 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Mon, 11 Nov 2024 15:59:39 +0530 Subject: [PATCH 13/14] remove profile --- pkg/analytics/analytics.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 385c9e7..109075f 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -25,16 +25,15 @@ import ( ) type Event struct { - CLIVersion string `json:"cli_version"` - ULID string `json:"ulid"` - CommitHash string `json:"commit_hash"` - OSName string `json:"os_name"` - OSVersion string `json:"os_version"` - ReportCreatedAt string `json:"report_created_at"` - Command Command `json:"command"` - Profile config.Profile `json:"profile"` - Errors *string `json:"errors"` - ExecutionTimestamp string `json:"execution_timestamp"` + CLIVersion string `json:"cli_version"` + ULID string `json:"ulid"` + CommitHash string `json:"commit_hash"` + OSName string `json:"os_name"` + OSVersion string `json:"os_version"` + ReportCreatedAt string `json:"report_created_at"` + Command Command `json:"command"` + Errors *string `json:"errors"` + ExecutionTimestamp string `json:"execution_timestamp"` } // About struct @@ -195,8 +194,6 @@ func sendEvent(commandName string, arguments []string, errors *string, execution ExecutionTimestamp: executionTimestamp, } - event.Profile.Password = "" - // Marshal the event to JSON for sending eventJSON, err := json.Marshal(event) if err != nil { From e517b0aa49a0f3fb0518672584bd82b86c8b8ad8 Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Tue, 12 Nov 2024 01:40:37 +0530 Subject: [PATCH 14/14] update analytics to /pb --- pkg/analytics/analytics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/analytics/analytics.go b/pkg/analytics/analytics.go index 109075f..816a1b0 100644 --- a/pkg/analytics/analytics.go +++ b/pkg/analytics/analytics.go @@ -201,7 +201,7 @@ func sendEvent(commandName string, arguments []string, errors *string, execution } // Define the target URL for the HTTP request - url := "https://analytics.parseable.io:80" + url := "https://analytics.parseable.io:80/pb" // Create the HTTP POST request req, err := http.NewRequest("POST", url, bytes.NewBuffer(eventJSON))