diff --git a/cli/cmd/generate_assertions.go b/cli/cmd/generate_assertions.go new file mode 100644 index 00000000..064c642f --- /dev/null +++ b/cli/cmd/generate_assertions.go @@ -0,0 +1,148 @@ +package cmd + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" + + clickhouse "github.com/ClickHouse/clickhouse-go/v2" + "github.com/spf13/cobra" +) + +var generateAssertionCmd = &cobra.Command{ + Use: "assertion", + Short: "generates assertion", + Long: "generates assertions based on traces generated on previous run", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + log.Println("ASSERTION GENERATION STARTED...") + var result []Repo + reponame := args[0] + if Config.ClickHouseUrl == "" { + log.Println(`Please set the Clickhouse url using this command: + qt config --set-clickhouse + OR + create $HOME/config/config.yaml and provide the details + for example: + ch_conn: http://localhost:9000?username=admin&password=admin + qt_conn: http://localhost:8080 `) + return + } + // parsing the clickhouse url + dsnURL, err := url.Parse(Config.ClickHouseUrl) + if err != nil { + logger.Println(err) + } + + options := &clickhouse.Options{ + Addr: []string{dsnURL.Host}, + } + if dsnURL.Query().Get("username") == "" || dsnURL.Query().Get("password") == "" { + logger.Println("url query has credentials missing") + } + if dsnURL.Query().Get("username") != "" { + auth := clickhouse.Auth{ + Database: "signoz_traces", + Username: dsnURL.Query().Get("username"), + Password: dsnURL.Query().Get("password"), + } + options.Auth = auth + } + + // creating a clickhouse connection + db, err := clickhouse.Open(options) + if err != nil { + logger.Println(err) + return + } + // closing the clickhouse connection at the end + defer db.Close() + // checking the clickhouse connection with a ping + err = db.Ping(context.Background()) + if err != nil { + logger.Println(err) + } + // we query the database and print the details + query := "SELECT * FROM signoz_traces.repo WHERE name = ?" + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err = db.Select(ctx, &result, query, reponame) + if err != nil { + logger.Println("Unable to query the database", err) + } + + if Config.QualityTraceUrl == "" { + log.Println(`Please set the Quality Trace url using this command: + qt config --set-server + OR + create $HOME/config/config.yaml and provide the details + for example: + CH_CONN: http://localhost:9000?username=admin&password=admin + QT_CONN: http://localhost:8080 `) + return + } + + jsonData, err := json.Marshal(result[0]) + if err != nil { + logger.Println("unable to marshal the data:", err) + } + + path := fmt.Sprintf("%s/dryRun/", Config.QualityTraceUrl) + resp, err := http.Post(path, "application/json", + bytes.NewBuffer(jsonData)) + + if err != nil { + logger.Println(err) + return + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.Println("unable to read the response body: ", err) + return + } + strBody := string(body) + statusStr := "Fetching Data..." + lastIdx := strings.LastIndex(strBody, statusStr) + jsonBody := strBody[lastIdx+len(statusStr)+2 : len(strBody)-2] + data := AssertionData{} + json.Unmarshal([]byte(jsonBody), &data) + generateAssertions(data) + }, +} + +type AssertionData struct { + Columns []string `json:"columns"` + Events [][]interface{} `json:"events"` +} + +func init() { + repoCmd.AddCommand(generateAssertionCmd) +} + +func generateAssertions(data AssertionData) { + assertionFieldValueMap := map[string]string{} + for _, event := range data.Events { + assertionFields := map[int]string{} + // assertion fields + for idx, v := range event[7].([]interface{}) { + assertionFields[idx] = v.(string) + } + // assertion values + for idx, v := range event[8].([]interface{}) { + if field, ok := assertionFields[idx]; ok { + assertionFieldValueMap[field] = v.(string) + } + } + } + fmt.Printf("\n assertionFieldValueMap %v", assertionFieldValueMap) + GenerateYaml(assertionFieldValueMap) +} diff --git a/cli/cmd/repo_dryrun.go b/cli/cmd/repo_dryrun.go index 1c84aaf6..21bdf547 100644 --- a/cli/cmd/repo_dryrun.go +++ b/cli/cmd/repo_dryrun.go @@ -9,6 +9,7 @@ import ( "log" "net/http" "net/url" + "strings" "time" clickhouse "github.com/ClickHouse/clickhouse-go/v2" @@ -101,7 +102,14 @@ var repoDryRunCmd = &cobra.Command{ if err != nil { logger.Println("unable to read the response body: ", err) } - fmt.Println(string(body)) + strBody := string(body) + statusStr := "Fetching Data..." + lastIdx := strings.LastIndex(strBody, statusStr) + jsonBody := strBody[lastIdx+len(statusStr)+2 : len(strBody)-2] + data := AssertionData{} + json.Unmarshal([]byte(jsonBody), &data) + generateAssertions(data) + fmt.Println(strBody) }, } diff --git a/cli/cmd/util.go b/cli/cmd/util.go new file mode 100644 index 00000000..c65a5b64 --- /dev/null +++ b/cli/cmd/util.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "fmt" + "gopkg.in/yaml.v3" + "io/ioutil" + "os" + "path" + "path/filepath" + "time" + + log "github.com/sirupsen/logrus" +) + +func GenerateYaml(values map[string]string) { + fmt.Println("\n Generating assertions yaml file") + wd, err := os.Getwd() + if err != nil { + log.Fatalf("error occured while getting working dir %v", err) + } + parentDir := filepath.Dir(wd) + assertionsDir := path.Join(parentDir, "generatedAssertionFiles") + os.Mkdir(assertionsDir, 0777) + fileName := fmt.Sprintf("assertions-%v.yaml", time.Now().Format(time.RFC822)) + filePath := path.Join(assertionsDir, fileName) + var assertions []string + for key, value := range values { + kv := fmt.Sprintf("%s = %s", key, value) + assertions = append(assertions, kv) + } + assertVals := map[string][]string{"spec": assertions} + + data, err := yaml.Marshal(&assertVals) + + if err != nil { + log.Fatal(err) + } + err = ioutil.WriteFile(filePath, data, 0666) + + if err != nil { + log.Fatal(err) + } + + fmt.Printf("\n assertions yaml file created succesfully at path: %v", filePath) +} diff --git a/generatedAssertionFiles/assertions-07 May 23 11:21 IST.yaml b/generatedAssertionFiles/assertions-07 May 23 11:21 IST.yaml new file mode 100644 index 00000000..6aeabf51 --- /dev/null +++ b/generatedAssertionFiles/assertions-07 May 23 11:21 IST.yaml @@ -0,0 +1,23 @@ +spec: + - db.statement = INSERT INTO `books` (`title`,`author`) VALUES ("foo","bar") + - http.route = /books + - http.status_code = 200 + - http.user_agent = Go-http-client/1.1 + - net.host.name = localhost + - http.request_content_length = 34 + - http.method = POST + - db.rows_affected = 1 + - net.peer.ip = 127.0.0.1 + - service.name = goapp + - signoz.collector.id = 2ea75c7d-640a-4e91-b7a1-cbe464e02cac + - db.sql.table = books + - http.server_name = goapp + - library.language = go + - http.host = localhost:8090 + - http.target = /books + - net.host.port = 8090 + - net.peer.port = 40148 + - http.scheme = http + - db.system = sqlite + - net.transport = ip_tcp + - http.flavor = 1.1