Compare commits

...

19 Commits

Author SHA1 Message Date
Thomas Schuetz
d246fc1ec2 refactor: first try with more interfaces
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-02 13:55:06 +02:00
AlexsJones
071ee560f3 chore: merge branch 'chetanguptaa-some-fixes' 2023-04-01 17:23:10 +01:00
AlexsJones
e1d89920b0 chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:50 +01:00
AlexsJones
96d0d754ea chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:37 +01:00
chetan gupta
86e9564aaf Merge remote-tracking branch 'origin/some-fixes' into some-fixes 2023-04-01 21:30:04 +05:30
chetan gupta
9cef52fb8e Merge branch 'main' of https://github.com/k8sgpt-ai/k8sgpt into some-fixes
docs: updated README.md
2023-04-01 21:24:20 +05:30
chetan gupta
9915f9406b Merge branch 'main' into some-fixes 2023-04-01 21:15:26 +05:30
chetan gupta
a24a8da246 updated README.md
Signed-off-by: chetan gupta <chetangupta123raj@gmail.com>
2023-04-01 21:14:07 +05:30
Alex Jones
f337f6b447 Merge pull request #177 from k8sgpt-ai/renovate/anchore-sbom-action-0.x
chore(deps): update anchore/sbom-action action to v0.14.1
2023-04-01 16:33:54 +01:00
renovate[bot]
80f29dae4f chore(deps): update anchore/sbom-action action to v0.14.1
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-01 15:24:01 +00:00
Alex Jones
a76d60a652 Merge pull request #171 from matthisholleville/feature/hpa-analyzer
feat: add hpa analyzer and init additionalAnalyzers
2023-04-01 16:23:39 +01:00
Alex Jones
dea3ad9ebf Merge branch 'main' into feature/hpa-analyzer 2023-04-01 16:22:44 +01:00
Roberth Strand
207f26432c Merge pull request #175 from k8sgpt-ai/101-need-more-space-between-issues-with-explain
refactor: 101 need more space between issues with explain
2023-04-01 16:27:45 +02:00
Roberth Strand
3e836d81b7 refactor: merged main into branch
Merged origin/main into the branch to get the PR up to date and mergable.

Closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-04-01 11:36:15 +02:00
Matthis Holleville
006751567f Merge branch 'main' into feature/hpa-analyzer 2023-03-31 23:25:07 +02:00
Matthis Holleville
5dad75fbe9 feat: check if ScaleTargetRef is possible option
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:47:10 +02:00
HOLLEVILLE Matthis
4916fef9d6 fix: update client API call to use StatefulSet instead of Deployment
Co-authored-by: Dominik Augustin <yeahservice@users.noreply.github.com>
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:39:17 +02:00
Matthis Holleville
360387249f feat: add hpa analyzer and init additionalAnalyzers
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 18:22:01 +02:00
Roberth Strand
c0aebb21a0 refactor: added newline to explanation
The newline at the end of the explanation will make is so that the wall of text that you get with several issues get a bit of space. This does not effect the JSON output, only the regular output.

closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-03-31 11:23:35 +02:00
19 changed files with 434 additions and 277 deletions

View File

@@ -102,7 +102,7 @@ jobs:
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
- name: Generate SBOM
uses: anchore/sbom-action@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
with:
image: ${{ env.IMAGE_TAG }}
artifact-name: sbom-${{ env.IMAGE_NAME }}

View File

@@ -3,9 +3,9 @@
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
</picture>
`k8sgpt` is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english.
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
It has SRE experience codified into it's analyzers and helps to pull out the most relevent information to enrich it with AI.
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
## Installation
@@ -21,8 +21,8 @@ brew install k8sgpt
When installing Homebrew on WSL or Linux, you may encounter the following error:
```
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from bottle and must be
built from source. k8sgpt Install Clang or run brew install gcc.
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be
built from the source. k8sgpt Install Clang or run brew install gcc.
```
If you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.
@@ -60,6 +60,8 @@ you will be able to write your own analyzers.
### Built in analyzers
#### Enabled by default
- [x] podAnalyzer
- [x] pvcAnalyzer
- [x] rsAnalyzer
@@ -67,6 +69,10 @@ you will be able to write your own analyzers.
- [x] eventAnalyzer
- [x] ingressAnalyzer
#### Optional
- [x] hpaAnalyzer
## Usage
```

View File

@@ -2,18 +2,14 @@ package analyze
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/schollz/progressbar/v3"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var (
@@ -69,77 +65,26 @@ var AnalyzeCmd = &cobra.Command{
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
// Analysis configuration
config := &analyzer.AnalysisConfiguration{
// AnalysisResult configuration
analysis := &analysis.Analysis{
Context: ctx,
Namespace: namespace,
NoCache: nocache,
Explain: explain,
AIClient: aiClient,
Filters: filters,
Client: client,
}
var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{}
if err := analyzer.RunAnalysis(ctx, filters, config, client,
aiClient, analysisResults); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Run analysis
_ = analysis.RunAnalysis()
var bar *progressbar.ProgressBar
if len(*analysisResults) > 0 {
bar = progressbar.Default(int64(len(*analysisResults)))
} else {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
}
// This variable is used to store the results that will be printed
// It's necessary because the heap memory is lost when the function returns
var printOutput []analyzer.Analysis
for _, analysis := range *analysisResults {
if explain {
parsedText, err := analyzer.ParseViaAI(ctx, config, aiClient, analysis.Error)
if err != nil {
// Check for exhaustion
if strings.Contains(err.Error(), "status code: 429") {
color.Red("Exhausted API quota. Please try again later")
os.Exit(1)
}
color.Red("Error: %v", err)
continue
}
analysis.Details = parsedText
bar.Add(1)
}
printOutput = append(printOutput, analysis)
}
// print results
for n, analysis := range printOutput {
switch output {
case "json":
analysis.Error = analysis.Error[0:]
j, err := json.Marshal(analysis)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(j))
default:
fmt.Printf("%s %s(%s)\n", color.CyanString("%d", n),
color.YellowString(analysis.Name), color.CyanString(analysis.ParentObject))
for _, err := range analysis.Error {
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err))
}
fmt.Println(color.GreenString(analysis.Details))
}
}
analysis.PrintAnalysisResult()
},
}
func init() {
// namespace flag
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
// no cache flag

View File

@@ -18,7 +18,9 @@ var addCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
availableFilters := analyzer.ListFilters()
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
// Verify filter exist
invalidFilters := []string{}
@@ -47,7 +49,7 @@ var addCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
if len(activeFilters) == 0 {
activeFilters = availableFilters
activeFilters = coreFilters
}
mergedFilters := append(activeFilters, inputFilters...)

View File

@@ -16,10 +16,11 @@ var listCmd = &cobra.Command{
Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
Run: func(cmd *cobra.Command, args []string) {
activeFilters := viper.GetStringSlice("active_filters")
availableFilters := analyzer.ListFilters()
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
if len(activeFilters) == 0 {
activeFilters = availableFilters
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)

View File

@@ -21,8 +21,10 @@ var removeCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, _ := analyzer.ListFilters()
if len(activeFilters) == 0 {
activeFilters = analyzer.ListFilters()
activeFilters = coreFilters
}
// Check if input input filters is not empty

4
go.mod
View File

@@ -5,7 +5,6 @@ go 1.20
require (
github.com/fatih/color v1.15.0
github.com/sashabaranov/go-openai v1.5.7
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
golang.org/x/term v0.6.0
@@ -37,14 +36,11 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // 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/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect

11
go.sum
View File

@@ -173,7 +173,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -192,13 +191,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
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.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
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=
@@ -217,17 +211,12 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sashabaranov/go-openai v1.5.7 h1:8DGgRG+P7yWixte5j720y6yiXgY3Hlgcd0gcpHdltfo=
github.com/sashabaranov/go-openai v1.5.7/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=

65
pkg/analysis/analysis.go Normal file
View File

@@ -0,0 +1,65 @@
package analysis
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper"
)
type Analysis struct {
Context context.Context
Namespace string
NoCache bool
Explain bool
AIClient ai.IAI
Filters []string
Client *kubernetes.Client
analysisResults []common.Result
}
func (a *Analysis) RunAnalysis() error {
activeFilters := viper.GetStringSlice("active_filters")
analyzerList := analyzer.GetAnalyzerList()
// if there are no filters selected and no active_filters then run all of them
if len(a.Filters) == 0 && len(activeFilters) == 0 {
for _, al := range analyzerList {
thisanalysis, _ := analyzer.NewAnalyzer(al, a.Client, a.Context, a.Namespace)
err := thisanalysis.Analyze()
if err != nil {
fmt.Println("Error running analysis: ", err)
}
a.analysisResults = append(a.analysisResults, thisanalysis.GetResult()...)
}
return nil
}
// if the filters flag is specified
if len(a.Filters) != 0 {
for _, filter := range a.Filters {
for _, ali := range analyzerList {
if filter == ali {
thisanalysis, _ := analyzer.NewAnalyzer(ali, a.Client, a.Context, a.Namespace)
err := thisanalysis.Analyze()
if err != nil {
fmt.Println("Error running analysis: ", err)
}
a.analysisResults = append(a.analysisResults, thisanalysis.GetResult()...)
}
}
}
return nil
}
return nil
}
func (a *Analysis) PrintAnalysisResult() {
for _, result := range a.analysisResults {
fmt.Println(result)
}
}

View File

@@ -1,30 +0,0 @@
package analyzer
import (
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
)
type AnalysisConfiguration struct {
Namespace string
NoCache bool
Explain bool
}
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkingv1.Ingress
}
type Analysis struct {
Kind string `json:"kind"`
Name string `json:"name"`
Error []string `json:"error"`
Details string `json:"details"`
ParentObject string `json:"parentObject"`
}

View File

@@ -2,105 +2,157 @@ package analyzer
import (
"context"
"encoding/base64"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/hpa"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/ingress"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/pod"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/pvc"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/rs"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/service"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper"
)
var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{
"Pod": AnalyzePod,
"ReplicaSet": AnalyzeReplicaSet,
"PersistentVolumeClaim": AnalyzePersistentVolumeClaim,
"Service": AnalyzeEndpoints,
"Ingress": AnalyzeIngress,
type IAnalyzer interface {
Analyze() error
GetResult() []common.Result
}
func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration,
client *kubernetes.Client,
aiClient ai.IAI, analysisResults *[]Analysis) error {
const (
PodAnalyzerName = "Pod"
ReplicaSetAnalyzerName = "ReplicaSet"
PersistentVolumeClaimAnalyzerName = "PersistentVolumeClaim"
ServiceAnalyzerName = "Service"
IngressAnalyzerName = "Ingress"
HPAAnalyzerName = "HorizontalPodAutoScaler"
)
activeFilters := viper.GetStringSlice("active_filters")
// if there are no filters selected and no active_filters then run all of them
if len(filters) == 0 && len(activeFilters) == 0 {
for _, analyzer := range analyzerMap {
if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
return nil
var (
coreAnalyzerList = []string{
PodAnalyzerName,
ReplicaSetAnalyzerName,
PersistentVolumeClaimAnalyzerName,
ServiceAnalyzerName,
IngressAnalyzerName,
HPAAnalyzerName,
}
// if the filters flag is specified
if len(filters) != 0 {
for _, filter := range filters {
if analyzer, ok := analyzerMap[filter]; ok {
if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
}
return nil
additionalAnalyzers = []string{
HPAAnalyzerName,
}
)
func NewAnalyzer(analyzer string, client *kubernetes.Client, context context.Context, namespace string) (IAnalyzer, error) {
analyzerConfig := common.Analyzer{
Namespace: namespace,
Context: context,
Client: client,
}
// use active_filters
for _, filter := range activeFilters {
if analyzer, ok := analyzerMap[filter]; ok {
if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
analyzerConfig.PreAnalysis = make(map[string]common.PreAnalysis)
switch analyzer {
case PodAnalyzerName:
return &pod.PodAnalyzer{
Analyzer: analyzerConfig,
}, nil
case ReplicaSetAnalyzerName:
return &rs.ReplicaSetAnalyzer{
Analyzer: analyzerConfig,
}, nil
case IngressAnalyzerName:
return &ingress.IngressAnalyzer{
Analyzer: analyzerConfig,
}, nil
case HPAAnalyzerName:
return &hpa.HPAAnalyzer{
Analyzer: analyzerConfig,
}, nil
case PersistentVolumeClaimAnalyzerName:
return &pvc.PvcAnalyzer{
Analyzer: analyzerConfig,
}, nil
case ServiceAnalyzerName:
return &service.ServiceAnalyzer{
Analyzer: analyzerConfig,
}, nil
default:
return nil, fmt.Errorf("Analyzer %s not supported", analyzer)
}
return nil
}
func ParseViaAI(ctx context.Context, config *AnalysisConfiguration,
aiClient ai.IAI, prompt []string) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
// find in viper cache
if viper.IsSet(sEnc) && !config.NoCache {
// retrieve data from cache
response := viper.GetString(sEnc)
if response == "" {
color.Red("error retrieving cached data")
return "", nil
/*
func ParseViaAI(ctx context.Context, config *analysis.Analysis,
aiClient ai.IAI, prompt []string) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
// find in viper cache
if viper.IsSet(sEnc) && !config.NoCache {
// retrieve data from cache
response := viper.GetString(sEnc)
if response == "" {
color.Red("error retrieving cached data")
return "", nil
}
output, err := base64.StdEncoding.DecodeString(response)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
}
return string(output), nil
}
output, err := base64.StdEncoding.DecodeString(response)
response, err := aiClient.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
color.Red("error getting completion: %v", err)
return "", err
}
return string(output), nil
if !viper.IsSet(sEnc) {
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
}
*/
func ListFilters() ([]string, []string) {
coreKeys := []string{}
for _, filter := range coreAnalyzerList {
coreKeys = append(coreKeys, filter)
}
response, err := aiClient.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
additionalKeys := []string{}
for _, filter := range additionalAnalyzers {
coreKeys = append(additionalKeys, filter)
}
if !viper.IsSet(sEnc) {
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
return coreKeys, additionalKeys
}
func ListFilters() []string {
keys := make([]string, 0, len(analyzerMap))
for k := range analyzerMap {
keys = append(keys, k)
}
return keys
func GetAnalyzerList() []string {
list := []string{}
list = append(list, coreAnalyzerList...)
list = append(list, additionalAnalyzers...)
list = removeDuplicateStr(list)
return list
}
func removeDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}

View File

@@ -1,4 +1,4 @@
package analyzer
package common
import (
"context"

View File

@@ -0,0 +1,36 @@
package common
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
appsv1 "k8s.io/api/apps/v1"
autov1 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
networkv1 "k8s.io/api/networking/v1"
)
type Analyzer struct {
Client *kubernetes.Client
Context context.Context
Namespace string
PreAnalysis map[string]PreAnalysis
Result []Result
}
type Result struct {
Kind string `json:"kind"`
Name string `json:"name"`
Error []string `json:"error"`
Details string `json:"details"`
ParentObject string `json:"parentObject"`
}
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkv1.Ingress
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
}

View File

@@ -0,0 +1,81 @@
package hpa
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type HPAAnalyzer struct {
common.Analyzer
}
func (a *HPAAnalyzer) Analyze() error {
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
for _, hpa := range list.Items {
var failures []string
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
scaleTargetRefNotFound := false
switch scaleTargetRef.Kind {
case "Deployment":
_, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicationController":
_, err := a.Client.GetClient().CoreV1().ReplicationControllers(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicaSet":
_, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "StatefulSet":
_, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
default:
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which does not possible option.", scaleTargetRef.Kind))
}
if scaleTargetRefNotFound {
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name))
}
if len(failures) > 0 {
a.PreAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
}
}
for key, value := range a.PreAnalysis {
var currentAnalysis = common.Result{
Kind: "HorizontalPodAutoscaler",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *HPAAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -1,25 +1,23 @@
package analyzer
package ingress
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
type IngressAnalyzer struct {
common.Analyzer
}
list, err := client.GetClient().NetworkingV1().Ingresses(config.Namespace).List(ctx, metav1.ListOptions{})
func (a *IngressAnalyzer) Analyze() error {
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, ing := range list.Items {
var failures []string
@@ -36,7 +34,7 @@ func AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *
// check if ingressclass exist
if ingressClassName != nil {
_, err := client.GetClient().NetworkingV1().IngressClasses().Get(ctx, *ingressClassName, metav1.GetOptions{})
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName))
}
@@ -46,7 +44,7 @@ func AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *
for _, rule := range ing.Spec.Rules {
// loop over paths
for _, path := range rule.HTTP.Paths {
_, err := client.GetClient().CoreV1().Services(ing.Namespace).Get(ctx, path.Backend.Service.Name, metav1.GetOptions{})
_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name))
}
@@ -54,13 +52,13 @@ func AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *
}
for _, tls := range ing.Spec.TLS {
_, err := client.GetClient().CoreV1().Secrets(ing.Namespace).Get(ctx, tls.SecretName, metav1.GetOptions{})
_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName))
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = PreAnalysis{
a.PreAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
@@ -68,17 +66,20 @@ func AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
for key, value := range a.PreAnalysis {
var currentAnalysis = common.Result{
Kind: "Ingress",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Ingress.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *IngressAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -1,25 +1,22 @@
package analyzer
package pod
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
type PodAnalyzer struct {
common.Analyzer ", inline"
}
func (a *PodAnalyzer) Analyze() error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Pods(config.Namespace).List(ctx, metav1.ListOptions{})
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, pod := range list.Items {
var failures []string
// Check for pending pods
@@ -47,7 +44,7 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPodEvent(ctx, client, &pod)
evt, err := common.FetchLatestPodEvent(a.Context, a.Client, &pod)
if err != nil || evt == nil {
continue
}
@@ -58,24 +55,27 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = PreAnalysis{
a.PreAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
Pod: pod,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
for key, value := range a.PreAnalysis {
var currentAnalysis = common.Result{
Kind: "Pod",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Pod.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *PodAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -1,24 +1,25 @@
package analyzer
package pvc
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
type PvcAnalyzer struct {
common.Analyzer
}
func (a *PvcAnalyzer) Analyze() error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().PersistentVolumeClaims(config.Namespace).List(ctx, metav1.ListOptions{})
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pvc := range list.Items {
var failures []string
@@ -27,7 +28,7 @@ func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfigura
if pvc.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPvcEvent(ctx, client, &pvc)
evt, err := common.FetchLatestPvcEvent(a.Context, a.Client, &pvc)
if err != nil || evt == nil {
continue
}
@@ -36,7 +37,7 @@ func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfigura
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
@@ -44,16 +45,19 @@ func AnalyzePersistentVolumeClaim(ctx context.Context, config *AnalysisConfigura
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
var currentAnalysis = common.Result{
Kind: "PersistentVolumeClaim",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.PersistentVolumeClaim.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *PvcAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -1,25 +1,25 @@
package analyzer
package rs
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeReplicaSet(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
type ReplicaSetAnalyzer struct {
common.Analyzer
}
func (a *ReplicaSetAnalyzer) Analyze() error {
// search all namespaces for pods that are not running
list, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).List(ctx, metav1.ListOptions{})
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, rs := range list.Items {
var failures []string
@@ -35,7 +35,7 @@ func AnalyzeReplicaSet(ctx context.Context, config *AnalysisConfiguration,
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
ReplicaSet: rs,
FailureDetails: failures,
}
@@ -43,16 +43,19 @@ func AnalyzeReplicaSet(ctx context.Context, config *AnalysisConfiguration,
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
var currentAnalysis = common.Result{
Kind: "ReplicaSet",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.ReplicaSet.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *ReplicaSetAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -1,33 +1,33 @@
package analyzer
package service
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer/common"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeEndpoints(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
type ServiceAnalyzer struct {
common.Analyzer
}
func (a *ServiceAnalyzer) Analyze() error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Endpoints(config.Namespace).List(ctx, metav1.ListOptions{})
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ep := range list.Items {
var failures []string
// Check for empty service
if len(ep.Subsets) == 0 {
svc, err := client.GetClient().CoreV1().Services(ep.Namespace).Get(ctx, ep.Name, metav1.GetOptions{})
svc, err := a.Client.GetClient().CoreV1().Services(ep.Namespace).Get(a.Context, ep.Name, metav1.GetOptions{})
if err != nil {
color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name)
continue
@@ -53,7 +53,7 @@ func AnalyzeEndpoints(ctx context.Context, config *AnalysisConfiguration, client
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = common.PreAnalysis{
Endpoint: ep,
FailureDetails: failures,
}
@@ -61,15 +61,19 @@ func AnalyzeEndpoints(ctx context.Context, config *AnalysisConfiguration, client
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
var currentAnalysis = common.Result{
Kind: "Service",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Endpoint.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Result = append(a.Result, currentAnalysis)
}
return nil
}
func (a *ServiceAnalyzer) GetResult() []common.Result {
return a.Result
}