From 60305db4f984e3475235119e41510140b6cd06fa Mon Sep 17 00:00:00 2001 From: AdheipSingh Date: Tue, 26 Nov 2024 11:23:51 +0530 Subject: [PATCH] analyze with llm --- cmd/analyze.go | 375 +++++++++++------------------------ go.mod | 40 +++- go.sum | 144 +++++++++++++- pkg/analyze/duckdb/duckdb.go | 198 ++++++++++++++++++ pkg/analyze/k8s/prompts.go | 150 ++++++++++++++ pkg/analyze/openai/openai.go | 96 +++++++++ 6 files changed, 739 insertions(+), 264 deletions(-) create mode 100644 pkg/analyze/duckdb/duckdb.go create mode 100644 pkg/analyze/k8s/prompts.go create mode 100644 pkg/analyze/openai/openai.go diff --git a/cmd/analyze.go b/cmd/analyze.go index 111e478..0f27969 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -1,19 +1,46 @@ package cmd import ( - "bytes" - "database/sql" - "encoding/json" "fmt" - "io" - "os" // import os for Stdout + "log" + "os" + "time" + internalHTTP "pb/pkg/http" + "github.com/briandowns/spinner" + + "pb/pkg/analyze/duckdb" + "pb/pkg/analyze/k8s" + "pb/pkg/analyze/openai" + _ "github.com/marcboeker/go-duckdb" - "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" ) +// ANSI escape codes for colors +const ( + yellow = "\033[33m" + green = "\033[32m" + red = "\033[31m" + reset = "\033[0m" +) + +// Check if the required environment variable is set +func checkEnvVar(key string) bool { + _, exists := os.LookupEnv(key) + return exists +} + +// Prompt the user to set the environment variable +func promptForEnvVar(key string) { + var value string + fmt.Printf(yellow+"Environment variable %s is not set. Please enter its value: "+reset, key) + fmt.Scanln(&value) + os.Setenv(key, value) + fmt.Println(green + "Environment variable set successfully." + reset) +} + var AnalyzeCmd = &cobra.Command{ Use: "stream", // Subcommand for "analyze" Short: "Analyze streams in the Parseable server", @@ -21,278 +48,118 @@ var AnalyzeCmd = &cobra.Command{ Args: cobra.ExactArgs(1), // Ensure exactly one argument is passed RunE: func(cmd *cobra.Command, args []string) error { name := args[0] - fmt.Printf("Analyzing stream: %s\n", name) + fmt.Printf(yellow+"Analyzing stream: %s\n"+reset, name) + + detectSchema(name) + // Prompt the user to select LLM + fmt.Println(yellow + "Select LLM for analysis (1: GPT, 2: Claude): " + reset) + var choice int + fmt.Scanln(&choice) + + var llmType string + switch choice { + case 1: + llmType = "GPT" + if !checkEnvVar("OPENAI_API_KEY") { + promptForEnvVar("OPENAI_API_KEY") + } + case 2: + llmType = "Claude" + if !checkEnvVar("CLAUDE_API_KEY") { + promptForEnvVar("CLAUDE_API_KEY") + } + default: + fmt.Println(red + "Invalid choice. Exiting..." + reset) + return fmt.Errorf("invalid LLM selection") + } + fmt.Printf(green+"Using %s for analysis.\n"+reset, llmType) + // Initialize spinner + s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) + + // Step 1: Query Data + s.Suffix = " Querying data from Parseable server..." + s.Start() client := internalHTTP.DefaultClient(&DefaultProfile) - query := `with distinct_name as (select distinct(\"involvedObject_name\") as name from \"k8s-events\" where reason ilike '%kill%' or reason ilike '%fail%' or reason ilike '%back%') select reason, message, \"involvedObject_name\", \"involvedObject_namespace\", \"reportingComponent\", p_timestamp from \"k8s-events\" as t1 join distinct_name t2 on t1.\"involvedObject_name\" = t2.name order by p_timestamp` + query := `with distinct_name as (select distinct(\"involvedObject_name\") as name from \"k8s-events\" where reason ilike '%kill%' or reason ilike '%fail%' or reason ilike '%back%') select reason, message, \"involvedObject_name\", \"involvedObject_namespace\", \"reportingComponent\", timestamp from \"k8s-events\" as t1 join distinct_name t2 on t1.\"involvedObject_name\" = t2.name order by timestamp` - allData, err := queryPb(&client, query, "2024-11-11T00:00:00+00:00", "2024-11-21T00:00:00+00:00") + allData, err := duckdb.QueryPb(&client, query, "2024-11-11T00:00:00+00:00", "2024-11-21T00:00:00+00:00") + s.Stop() if err != nil { - return err + fmt.Printf(red+"Error querying data in Parseable: %v\n"+reset, err) + return fmt.Errorf("error querying data in Parseable, err [%w]", err) } - - // Insert the response into DuckDB - if err := storeInDuckDB(allData); err != nil { - return err + fmt.Println(green + "Data successfully queried from Parseable." + reset) + + // Step 2: Store Data in DuckDB + s.Suffix = " Storing data in DuckDB..." + s.Start() + if err := duckdb.StoreInDuckDB(allData); err != nil { + s.Stop() + fmt.Printf(red+"Error storing data in DuckDB: %v\n"+reset, err) + return fmt.Errorf("error storing data in DuckDB, err [%w]", err) } + s.Stop() + fmt.Println(green + "Data successfully stored in DuckDB." + reset) - // Fetch and display the reason summary statistics - stats, err := fetchSummaryStats() + // Step 3: Prompt Kubernetes Context + _, err = k8s.PromptK8sContext() if err != nil { + fmt.Printf(red+"Error prompting Kubernetes context: %v\n"+reset, err) return err } - // Display the statistics in a table - displaySummaryTable(stats) - - // Prompt user for namespace and involved object - var selectedNamespace, selectedObject string - fmt.Print("\nEnter the namespace you're interested in: ") - fmt.Scan(&selectedNamespace) - - // Prompt the user to select an involved object - fmt.Print("\nEnter the involved object you're interested in: ") - fmt.Scan(&selectedObject) - - // Display the selected data - displaySelectedData(selectedNamespace, selectedObject) - - return nil - }, -} - -func queryPb(client *internalHTTP.HTTPClient, query, startTime, endTime string) (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 "", fmt.Errorf("failed to create new request: %w", err) - } - - resp, err := client.Client.Do(req) - if err != nil { - 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 "", fmt.Errorf("non-200 status code received: %s", resp.Status) - } - - var jsonResponse []map[string]interface{} - if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { - return "", fmt.Errorf("error decoding JSON response: %w", err) - } - encodedResponse, _ := json.MarshalIndent(jsonResponse, "", " ") - - return string(encodedResponse), nil -} - -func storeInDuckDB(data string) error { - // Parse the JSON response - var jsonResponse []map[string]interface{} - if err := json.Unmarshal([]byte(data), &jsonResponse); err != nil { - return fmt.Errorf("error decoding JSON response: %w", err) - } - - // Open a connection to DuckDB - db, err := sql.Open("duckdb", "mydatabasenew.duckdb") - if err != nil { - return fmt.Errorf("error connecting to DuckDB: %w", err) - } - defer db.Close() - - // Create a table if it doesn't exist - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS k8s_events ( - reason VARCHAR, - message VARCHAR, - involvedObject_name VARCHAR, - involvedObject_namespace VARCHAR, - reportingComponent VARCHAR, - p_timestamp TIMESTAMP - )`) - if err != nil { - return fmt.Errorf("error creating table: %w", err) - } - - // Insert data into the table - for _, record := range jsonResponse { - _, err := db.Exec( - `INSERT INTO k8s_events (reason, message, involvedObject_name, involvedObject_namespace, reportingComponent, p_timestamp) VALUES (?, ?, ?, ?, ?, ?)`, - record["reason"], - record["message"], - record["involvedObject_name"], - record["involvedObject_namespace"], - record["reportingComponent"], - record["p_timestamp"], - ) + // Step 4: Select Namespace + namespace, err := k8s.PromptNamespace(k8s.GetKubeClient()) if err != nil { - return fmt.Errorf("error inserting record into DuckDB: %w", err) + log.Fatalf(red+"Error selecting namespace: %v\n"+reset, err) } - } + fmt.Printf(yellow+"Selected Namespace: %s\n"+reset, namespace) - // Run the reason_counts query to get summary statistics - summaryQuery := ` - WITH reason_counts AS ( - SELECT - involvedObject_name, - involvedObject_namespace, - reason, - COUNT(*) AS reason_count - FROM - k8s_events - GROUP BY - involvedObject_name, - involvedObject_namespace, - reason - ) - SELECT - involvedObject_namespace, - involvedObject_name, - STRING_AGG(CONCAT(reason, ' ', reason_count, ' times'), ', ' ORDER BY reason) AS reason_summary - FROM - reason_counts - GROUP BY - involvedObject_namespace, - involvedObject_name - ORDER BY - involvedObject_namespace, involvedObject_name; - ` - - // Execute the summary query - rows, err := db.Query(summaryQuery) - if err != nil { - return fmt.Errorf("error executing summary query: %w", err) - } - defer rows.Close() - - // Create a summary table if it doesn't exist - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS reason_summary_stats ( - involvedObject_namespace VARCHAR, - involvedObject_name VARCHAR, - reason_summary VARCHAR - )`) - if err != nil { - return fmt.Errorf("error creating summary table: %w", err) - } - - // Insert the summary stats into the new table - for rows.Next() { - var namespace, objectName, reasonSummary string - if err := rows.Scan(&namespace, &objectName, &reasonSummary); err != nil { - return fmt.Errorf("error scanning summary row: %w", err) - } - _, err := db.Exec( - `INSERT INTO reason_summary_stats (involvedObject_namespace, involvedObject_name, reason_summary) VALUES (?, ?, ?)`, - namespace, objectName, reasonSummary, - ) + // Step 5: Select Pod + pod, err := k8s.PromptPod(k8s.GetKubeClient(), namespace) if err != nil { - return fmt.Errorf("error inserting summary record into DuckDB: %w", err) + log.Fatalf(red+"Error selecting pod: %v\n"+reset, err) } - } - - return nil -} - -func fetchSummaryStats() ([]SummaryStat, error) { - // Open a connection to DuckDB - db, err := sql.Open("duckdb", "mydatabasenew.duckdb") - if err != nil { - return nil, fmt.Errorf("error connecting to DuckDB: %w", err) - } - defer db.Close() + fmt.Printf(yellow+"Selected Pod: %s\n"+reset, pod) - // Query to fetch summary statistics - rows, err := db.Query(` - SELECT involvedObject_namespace, involvedObject_name, reason_summary - FROM reason_summary_stats - ORDER BY involvedObject_namespace, involvedObject_name - `) - if err != nil { - return nil, fmt.Errorf("error executing summary query: %w", err) - } - defer rows.Close() - - var stats []SummaryStat - for rows.Next() { - var namespace, objectName, reasonSummary string - if err := rows.Scan(&namespace, &objectName, &reasonSummary); err != nil { - return nil, fmt.Errorf("error scanning summary row: %w", err) + // Step 6: Fetch Events from DuckDB + s.Suffix = " Fetching pod events from DuckDB..." + s.Start() + result, err := duckdb.FetchPodEventsfromDb(pod) + s.Stop() + if err != nil { + fmt.Printf(red+"Error fetching pod events from DuckDB: %v\n"+reset, err) + return err } - stats = append(stats, SummaryStat{ - Namespace: namespace, - ObjectName: objectName, - ReasonSummary: reasonSummary, - }) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("error reading rows: %w", err) - } - - return stats, nil -} -func displaySummaryTable(stats []SummaryStat) { - table := tablewriter.NewWriter(os.Stdout) // Use os.Stdout for io.Writer - table.SetHeader([]string{"Namespace", "Involved Object", "Reason Summary"}) + // Step 7: Analyze with GPT + s.Suffix = " Analyzing events with GPT..." + s.Start() + gptResponse, err := openai.AnalyzeEventsWithGPT(pod, namespace, result) + s.Stop() + if err != nil { + fmt.Printf(red+"Failed to analyze events with GPT: %v\n"+reset, err) + return fmt.Errorf("failed to analyze events with GPT: %w", err) + } - for _, stat := range stats { - table.Append([]string{stat.Namespace, stat.ObjectName, stat.ReasonSummary}) - } + // Display GPT Analysis Result + fmt.Println(green + "\nGPT Analysis:\n" + reset + gptResponse) - table.Render() + return nil + }, } -func displaySelectedData(namespace, objectName string) { - // Open a connection to DuckDB - db, err := sql.Open("duckdb", "mydatabasenew.duckdb") - if err != nil { - fmt.Printf("Error connecting to DuckDB: %v\n", err) - return - } - defer db.Close() +// Dummy function to simulate schema detection +func detectSchema(streamName string) { + // Simulate schema detection + fmt.Printf(yellow+"Starting schema detection for stream: %s\n"+reset, streamName) - // Query to fetch selected data based on namespace and object name - query := ` - SELECT reason, message, reportingComponent, p_timestamp - FROM k8s_events - WHERE involvedObject_namespace = ? AND involvedObject_name = ? - ` - rows, err := db.Query(query, namespace, objectName) - if err != nil { - fmt.Printf("Error executing query: %v\n", err) - return - } - defer rows.Close() - - // Display the result - fmt.Printf("\nSelected Data for Namespace: %s, Object: %s\n", namespace, objectName) - for rows.Next() { - var reason, message, component, timestamp string - if err := rows.Scan(&reason, &message, &component, ×tamp); err != nil { - fmt.Printf("Error scanning row: %v\n", err) - return - } - fmt.Printf("Reason: %s\nMessage: %s\nComponent: %s\nTimestamp: %s\n\n", reason, message, component, timestamp) + // Dummy condition to check if the schema is known + if streamName == "k8s-events" { + fmt.Println(green + "Kubernetes events schema found. Schema is known to the tool.\n" + reset) + } else { + fmt.Println(red + "Schema not recognized. Please ensure it's defined in the tool.\n" + reset) } - - if err := rows.Err(); err != nil { - fmt.Printf("Error reading rows: %v\n", err) - } -} - -// Struct to hold summary statistics -type SummaryStat struct { - Namespace string - ObjectName string - ReasonSummary string } diff --git a/go.mod b/go.mod index eeda7d6..16b9765 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,21 @@ toolchain go1.23.3 require ( github.com/apache/arrow/go/v13 v13.0.0 + github.com/briandowns/spinner v1.23.1 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.26.6 github.com/charmbracelet/lipgloss v0.12.1 github.com/dustin/go-humanize v1.0.1 + github.com/manifoldco/promptui v0.9.0 github.com/marcboeker/go-duckdb v1.8.3 github.com/oklog/ulid/v2 v2.1.0 - github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/pflag v1.0.5 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 golang.org/x/term v0.25.0 google.golang.org/grpc v1.67.1 gopkg.in/yaml.v2 v2.4.0 + k8s.io/apimachinery v0.31.3 + k8s.io/client-go v0.31.3 ) require ( @@ -26,22 +29,57 @@ require ( github.com/charmbracelet/x/input v0.1.0 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect github.com/charmbracelet/x/windows v0.1.0 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 713e0c8..f1dee50 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650= +github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s= @@ -24,49 +26,103 @@ github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXD github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/evertras/bubble-table v0.15.2 h1:hVj27V9tk5TD5p6mVv0RK/KJu2sHq0U+mBMux/HptkU= github.com/evertras/bubble-table v0.15.2/go.mod h1:SPOZKbIpyYWPHBNki3fyNpiPBQkvkULAtOT7NTD5fKY= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -76,6 +132,11 @@ github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8D github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -84,21 +145,28 @@ 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 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.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= @@ -109,36 +177,73 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= @@ -150,10 +255,31 @@ google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFN google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/analyze/duckdb/duckdb.go b/pkg/analyze/duckdb/duckdb.go new file mode 100644 index 0000000..eb4fccf --- /dev/null +++ b/pkg/analyze/duckdb/duckdb.go @@ -0,0 +1,198 @@ +package duckdb + +import ( + "bytes" + "database/sql" + "encoding/json" + "fmt" + "io" + "pb/pkg/analyze/openai" + internalHTTP "pb/pkg/http" +) + +func QueryPb(client *internalHTTP.HTTPClient, query, startTime, endTime string) (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 "", fmt.Errorf("failed to create new request: %w", err) + } + + resp, err := client.Client.Do(req) + if err != nil { + 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 "", fmt.Errorf("non-200 status code received: %s", resp.Status) + } + + var jsonResponse []map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&jsonResponse); err != nil { + return "", fmt.Errorf("error decoding JSON response: %w", err) + } + encodedResponse, _ := json.MarshalIndent(jsonResponse, "", " ") + + return string(encodedResponse), nil +} + +func StoreInDuckDB(data string) error { + // Parse the JSON response + var jsonResponse []map[string]interface{} + if err := json.Unmarshal([]byte(data), &jsonResponse); err != nil { + return fmt.Errorf("error decoding JSON response: %w", err) + } + + // Open a connection to DuckDB + db, err := sql.Open("duckdb", "k8s_events.duckdb") + if err != nil { + return fmt.Errorf("error connecting to DuckDB: %w", err) + } + defer db.Close() + + // Create a table if it doesn't exist + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS k8s_events ( + reason VARCHAR, + message VARCHAR, + involvedObject_name VARCHAR, + involvedObject_namespace VARCHAR, + reportingComponent VARCHAR, + timestamp TIMESTAMP + )`) + if err != nil { + return fmt.Errorf("error creating table: %w", err) + } + + // Insert data into the table + for _, record := range jsonResponse { + _, err := db.Exec( + `INSERT INTO k8s_events (reason, message, involvedObject_name, involvedObject_namespace, reportingComponent, timestamp) VALUES (?, ?, ?, ?, ?, ?)`, + record["reason"], + record["message"], + record["involvedObject_name"], + record["involvedObject_namespace"], + record["reportingComponent"], + record["timestamp"], + ) + if err != nil { + return fmt.Errorf("error inserting record into DuckDB: %w", err) + } + } + + // Run the reason_counts query to get summary statistics + summaryQuery := ` + WITH reason_counts AS ( + SELECT + involvedObject_name, + involvedObject_namespace, + reason, + COUNT(*) AS reason_count + FROM + k8s_events + GROUP BY + involvedObject_name, + involvedObject_namespace, + reason + ) + SELECT + involvedObject_namespace, + involvedObject_name, + STRING_AGG(CONCAT(reason, ' ', reason_count, ' times'), ', ' ORDER BY reason) AS reason_summary + FROM + reason_counts + GROUP BY + involvedObject_namespace, + involvedObject_name + ORDER BY + involvedObject_namespace, involvedObject_name; + ` + + // Execute the summary query + rows, err := db.Query(summaryQuery) + if err != nil { + return fmt.Errorf("error executing summary query: %w", err) + } + defer rows.Close() + + // Create a summary table if it doesn't exist + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS reason_summary_stats ( + involvedObject_namespace VARCHAR, + involvedObject_name VARCHAR, + reason_summary VARCHAR + )`) + if err != nil { + return fmt.Errorf("error creating summary table: %w", err) + } + + // Insert the summary stats into the new table + for rows.Next() { + var namespace, objectName, reasonSummary string + if err := rows.Scan(&namespace, &objectName, &reasonSummary); err != nil { + return fmt.Errorf("error scanning summary row: %w", err) + } + _, err := db.Exec( + `INSERT INTO reason_summary_stats (involvedObject_namespace, involvedObject_name, reason_summary) VALUES (?, ?, ?)`, + namespace, objectName, reasonSummary, + ) + if err != nil { + return fmt.Errorf("error inserting summary record into DuckDB: %w", err) + } + } + + return nil +} + +// fetchPodEventsfromDb fetches summary statistics for a given pod from DuckDB. +func FetchPodEventsfromDb(podName string) ([]openai.SummaryStat, error) { + // Open a connection to DuckDB + db, err := sql.Open("duckdb", "k8s_events.duckdb") + if err != nil { + return nil, fmt.Errorf("error connecting to DuckDB: %w", err) + } + defer db.Close() + + // Prepare the query with a placeholder for podName + query := ` + SELECT DISTINCT ON (message) * + FROM k8s_events + WHERE involvedObject_name = ? + ORDER BY "timestamp"; + ` + + // Execute the query with podName as a parameter + rows, err := db.Query(query, podName) + if err != nil { + return nil, fmt.Errorf("error executing summary query: %w", err) + } + defer rows.Close() + + var stats []openai.SummaryStat + for rows.Next() { + var reason, message, objectName, objectNamespace, reportingComponent, timestamp string + if err := rows.Scan(&reason, &message, &objectName, &objectNamespace, &reportingComponent, ×tamp); err != nil { + return nil, fmt.Errorf("error scanning summary row: %w", err) + } + stats = append(stats, openai.SummaryStat{ + Reason: reason, + Message: message, + ObjectName: objectName, + ObjectNamespace: objectNamespace, + ReportingComponent: reportingComponent, + Timestamp: timestamp, + }) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error reading rows: %w", err) + } + + return stats, nil +} diff --git a/pkg/analyze/k8s/prompts.go b/pkg/analyze/k8s/prompts.go new file mode 100644 index 0000000..87ad2cd --- /dev/null +++ b/pkg/analyze/k8s/prompts.go @@ -0,0 +1,150 @@ +package k8s + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/manifoldco/promptui" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +// promptPod prompts the user to select a pod from the chosen namespace +func PromptPod(clientset *kubernetes.Clientset, namespace string) (string, error) { + // Retrieve the list of pods in the specified namespace + pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return "", fmt.Errorf("failed to retrieve pods in namespace %s: %v", namespace, err) + } + + // Extract pod names + var podNames []string + for _, pod := range pods.Items { + podNames = append(podNames, pod.Name) + } + + if len(podNames) == 0 { + return "", fmt.Errorf("no pods found in namespace %s", namespace) + } + + // Prompt user to select a pod + prompt := promptui.Select{ + Label: fmt.Sprintf("\033[32mSelect a Pod in Namespace '%s':\033[0m", namespace), + Items: podNames, + Templates: &promptui.SelectTemplates{ + Active: "\033[33m▸ {{ . }}\033[0m", + Inactive: "{{ . }}", + Selected: "\033[32mPod '{{ . }}' selected successfully.\033[0m", + }, + } + + _, selectedPod, err := prompt.Run() + if err != nil { + return "", fmt.Errorf("pod selection cancelled: %v", err) + } + + return selectedPod, nil +} + +// promptK8sContext retrieves Kubernetes contexts from kubeconfig. +func PromptK8sContext() (clusterName string, err error) { + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath == "" { + kubeconfigPath = os.Getenv("HOME") + "/.kube/config" + } + + // Load kubeconfig file + config, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + fmt.Printf("\033[31mError loading kubeconfig: %v\033[0m\n", err) + os.Exit(1) + } + + // Get current contexts + currentContext := config.Contexts + var contexts []string + for i := range currentContext { + contexts = append(contexts, i) + } + + // Prompt user to select Kubernetes context + promptK8s := promptui.Select{ + Label: "\033[32mSelect your Kubernetes context:\033[0m", + Items: contexts, + Templates: &promptui.SelectTemplates{ + Active: "\033[33m▸ {{ . }}\033[0m", // Yellow arrow and context name for active selection + Inactive: "{{ . }}", // Default color for inactive items + Selected: "\033[32mKubernetes context '{{ . }}' selected successfully.\033[0m", + }, + } + + _, clusterName, err = promptK8s.Run() + if err != nil { + return "", err + } + + // Set current context as selected + config.CurrentContext = clusterName + err = clientcmd.WriteToFile(*config, kubeconfigPath) + if err != nil { + return "", err + } + + return clusterName, nil +} + +func GetKubeClient() *kubernetes.Clientset { + var kubeconfig string + path, ok := os.LookupEnv("KUBECONFIG") + if ok { + kubeconfig = path + } else { + kubeconfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") + } + + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + panic(err.Error()) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + return clientset +} + +// promptNamespace prompts the user to select a namespace from available namespaces +func PromptNamespace(clientset *kubernetes.Clientset) (string, error) { + // Retrieve the list of namespaces + namespaces, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return "", fmt.Errorf("failed to retrieve namespaces: %v", err) + } + + // Extract namespace names + var namespaceNames []string + for _, ns := range namespaces.Items { + namespaceNames = append(namespaceNames, ns.Name) + } + + // Prompt user to select a namespace + prompt := promptui.Select{ + Label: "\033[32mSelect your Namespace:\033[0m", + Items: namespaceNames, + Templates: &promptui.SelectTemplates{ + Active: "\033[33m▸ {{ . }}\033[0m", // Yellow arrow and context name for active selection + Inactive: "{{ . }}", // Default color for inactive items + Selected: "\033[32mNamespace '{{ . }}' selected successfully.\033[0m", + }, + } + + _, selectedNamespace, err := prompt.Run() + if err != nil { + return "", fmt.Errorf("namespace selection cancelled: %v", err) + } + + return selectedNamespace, nil +} diff --git a/pkg/analyze/openai/openai.go b/pkg/analyze/openai/openai.go new file mode 100644 index 0000000..0e1188b --- /dev/null +++ b/pkg/analyze/openai/openai.go @@ -0,0 +1,96 @@ +package openai + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" +) + +// Struct to hold summary statistics +type SummaryStat struct { + Reason string + Message string + ObjectName string + ObjectNamespace string + ReportingComponent string + Timestamp string +} + +// Define the structure for the OpenAI request +type OpenAIRequest struct { + Model string `json:"model"` + Messages []Message `json:"messages"` +} + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +// Define the structure for the response from OpenAI +type OpenAIResponse struct { + Choices []struct { + Message Message `json:"message"` + } `json:"choices"` +} + +// Function to send events to OpenAI GPT +func AnalyzeEventsWithGPT(podName, namespace string, data []SummaryStat) (string, error) { + // Format the data into a readable string + var formattedData string + for _, stat := range data { + formattedData += fmt.Sprintf("Reason: %s, Message: %s, Timestamp: %s\n", stat.Reason, stat.Message, stat.Timestamp) + } + + // Create the prompt with the data + prompt := fmt.Sprintf(`You are an expert at debugging Kubernetes Events. I have a table containing those events and want to debug what is happening in this pod (%s) / namespace (%s). + Give me a detailed explanation and overview of what happened by looking at these events. Provide a root cause analysis and suggest steps to mitigate the error if present. + In case you are unable to figure out what happened, just say "I'm unable to figure out what is happening here. Please give a table format which can be easily displayed on cli". + %s`, podName, namespace, formattedData) + + // Build the OpenAI request payload + openAIRequest := OpenAIRequest{ + Model: "gpt-4o", + Messages: []Message{ + {Role: "user", Content: prompt}, + }, + } + + // Marshal the request to JSON + payload, err := json.Marshal(openAIRequest) + if err != nil { + return "", fmt.Errorf("failed to marshal OpenAI request: %w", err) + } + + // Send the request to the OpenAI API + apiKey := os.Getenv("OPENAI_API_KEY") + req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(payload)) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("failed to send request to OpenAI: %w", err) + } + defer resp.Body.Close() + + // Parse the OpenAI response + var openAIResponse OpenAIResponse + + if err := json.NewDecoder(resp.Body).Decode(&openAIResponse); err != nil { + return "", fmt.Errorf("failed to decode OpenAI response: %w", err) + } + + // Return the GPT response + if len(openAIResponse.Choices) > 0 { + return openAIResponse.Choices[0].Message.Content, nil + } + + return "No response from OpenAI.", nil +}