Compare commits

..

4 Commits

Author SHA1 Message Date
Thomas Schuetz
b985697e82 feat: temporarily changed image
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:52:33 +02:00
Thomas Schuetz
cafd4fce17 feat: make secret optional
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:39:55 +02:00
Thomas Schuetz
ec9c7ba0c3 feat: make secret optional
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:31:01 +02:00
Thomas Schuetz
fa6f086331 feat: reduced resources for service
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:07:19 +02:00
27 changed files with 114 additions and 1149 deletions

View File

@@ -1 +1 @@
{".":"0.2.4"}
{".":"0.2.2"}

View File

@@ -1,48 +1,5 @@
# Changelog
## [0.2.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.3...v0.2.4) (2023-04-18)
### Features
* improve HPA analyzer to check ScaleTargetRef resources ([#283](https://github.com/k8sgpt-ai/k8sgpt/issues/283)) ([7173203](https://github.com/k8sgpt-ai/k8sgpt/commit/71732037fa40071cef0c2bc143736019d75eac86))
* init logging middleware on server mode ([6742410](https://github.com/k8sgpt-ai/k8sgpt/commit/6742410025d5e99c60045bb314730799f0e1e5ce))
### Bug Fixes
* deployment/cronjob namespace filtering ([#290](https://github.com/k8sgpt-ai/k8sgpt/issues/290)) ([3d684a2](https://github.com/k8sgpt-ai/k8sgpt/commit/3d684a2af7a9e1821bdb8b1bd6e85867b800d3ee))
* ensure parent directories are created in EnsureDirExists function ([#293](https://github.com/k8sgpt-ai/k8sgpt/issues/293)) ([af8b350](https://github.com/k8sgpt-ai/k8sgpt/commit/af8b350520d1a187a199482dd338db0086118db8))
* resolve language toggle bug (issue [#294](https://github.com/k8sgpt-ai/k8sgpt/issues/294)) ([0313627](https://github.com/k8sgpt-ai/k8sgpt/commit/03136278486ba12e3352580b317b9e63fa3a80f0))
## [0.2.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.2...v0.2.3) (2023-04-16)
### Features
* add node analyzer ([#272](https://github.com/k8sgpt-ai/k8sgpt/issues/272)) ([6247a1c](https://github.com/k8sgpt-ai/k8sgpt/commit/6247a1c0f3c2ead6a59661afed06973c29e57eca))
* add output query param on serve mode & refactor output logic ([9642202](https://github.com/k8sgpt-ai/k8sgpt/commit/9642202ed1b09c06a687651b7818c2a4df8a0c06))
* add server metrics ([#273](https://github.com/k8sgpt-ai/k8sgpt/issues/273)) ([a3becc9](https://github.com/k8sgpt-ai/k8sgpt/commit/a3becc9906515d0567808fee9a4e322451d6dc3f))
* envs to initialise server ([0071e25](https://github.com/k8sgpt-ai/k8sgpt/commit/0071e25992fc86c3882c2066873a2b04b43fe476))
* rename server/main.go to server/server.go ([9121a98](https://github.com/k8sgpt-ai/k8sgpt/commit/9121a983e52fa15c07bcc3bb361df97b8085c24c))
* running in cluster ([842f08c](https://github.com/k8sgpt-ai/k8sgpt/commit/842f08c655fde66b6b628192490e50be2ac3dcef))
* running in cluster ([3988eb2](https://github.com/k8sgpt-ai/k8sgpt/commit/3988eb2fd0a7d29ffa7b7bbc59960ca91e50466e))
* switch config file to XDG conform location ([dee4355](https://github.com/k8sgpt-ai/k8sgpt/commit/dee435514d7f717e4eb63b15a9d9fdb0722330ac))
* wip blocked until we have envs ([fe2c08c](https://github.com/k8sgpt-ai/k8sgpt/commit/fe2c08cf72a6ca271d1b431be66653f1396f304d))
### Bug Fixes
* add new line after version cmd output ([92e7b3d](https://github.com/k8sgpt-ai/k8sgpt/commit/92e7b3d3fb00c33ac48230caac34f45729e2f6b2))
* **deps:** update module github.com/sashabaranov/go-openai to v1.8.0 ([#277](https://github.com/k8sgpt-ai/k8sgpt/issues/277)) ([51b1b35](https://github.com/k8sgpt-ai/k8sgpt/commit/51b1b352acd24ebdc4cf9d9121f25c90e8f76ba7))
* resolve issue with duplicated integration filters. ([960ba56](https://github.com/k8sgpt-ai/k8sgpt/commit/960ba568d0dcc2ace722dc5c9b7c846366a98070))
* use the aiProvider object when launching the server instead of the deprecated configuration keys ([e7076ed](https://github.com/k8sgpt-ai/k8sgpt/commit/e7076ed6093aa9609d8c884b7a03e295057aaa8e))
### Other
* updated ([f0a0c9a](https://github.com/k8sgpt-ai/k8sgpt/commit/f0a0c9aebf627d65b0192ba3d0786cefd81e1fef))
## [0.2.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.1...v0.2.2) (2023-04-14)

View File

@@ -12,8 +12,6 @@
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
<a href="https://www.producthunt.com/posts/k8sgpt?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-k8sgpt" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=389489&theme=light" alt="K8sGPT - K8sGPT&#0032;gives&#0032;Kubernetes&#0032;Superpowers&#0032;to&#0032;everyone | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
# Installation
@@ -30,16 +28,16 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_386.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_386.rpm
sudo rpm -ivh k8sgpt_386.rpm
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_amd64.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_amd64.rpm
sudo rpm -ivh -i k8sgpt_amd64.rpm
```
<!---x-release-please-end-->
@@ -51,15 +49,15 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_386.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_386.deb
sudo dpkg -i k8sgpt_386.deb
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_amd64.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_amd64.deb
sudo dpkg -i k8sgpt_amd64.deb
```
<!---x-release-please-end-->
@@ -72,14 +70,14 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_386.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_386.apk
apk add k8sgpt_386.apk
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.4/k8sgpt_amd64.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.2/k8sgpt_amd64.apk
apk add k8sgpt_amd64.apk
```
<!---x-release-please-end-->x
@@ -90,7 +88,7 @@ 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 a bottle and must be
==> 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.
```
@@ -104,7 +102,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
## Windows
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
tab based on your system architecture.
* Extract the downloaded package to your desired location. Configure the system *path* variable with the binary location
@@ -119,7 +117,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
* Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)
* You can do this by running `k8sgpt generate` to open a browser link to generate it
* Run `k8sgpt auth` to set it in k8sgpt.
* Run `k8sgpt auth` to set it in k8sgpt.
* You can provide the password directly using the `--password` flag.
* Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis.
* Run `k8sgpt analyze` to run a scan.
@@ -129,7 +127,7 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
## Analyzers
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
you will be able to write your own analyzers.
### Built in analyzers
@@ -277,25 +275,17 @@ The Kubernetes system is trying to scale a StatefulSet named fake-deployment usi
## What about kubectl-ai?
The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the
The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the
cluster. It is not what we are trying to do here, it is focusing on writing YAML manifests.
K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers
to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find
K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers
to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find
the root cause of an issue.
## Configuration
`k8sgpt` stores config data in the `$XDG_CONFIG_HOME/k8sgpt/k8sgpt.yaml` file. The data is stored in plain text, including your OpenAI key.
Config file locations:
| OS | Path |
|---------|--------------------------------------------------|
| MacOS | ~/Library/Application Support/k8sgpt/k8sgpt.yaml |
| Linux | ~/.config/k8sgpt/k8sgpt.yaml |
| Windows | %LOCALAPPDATA%/k8sgpt/k8sgpt.yaml |
`k8sgpt` stores config data in `~/.k8sgpt.yaml` the data is stored in plain text, including your OpenAI key.
## Contributing

View File

@@ -51,12 +51,17 @@ var AnalyzeCmd = &cobra.Command{
}
// print results
output, err := config.PrintOutput(output)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
switch output {
case "json":
output, err := config.JsonOutput()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(output))
default:
config.PrintOutput()
}
fmt.Println(string(output))
},
}

View File

@@ -1,13 +1,11 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/adrg/xdg"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
@@ -46,8 +44,6 @@ func Execute(v string) {
}
func init() {
performConfigMigrationIfNeeded()
cobra.OnInitialize(initConfig)
var kubeconfigPath string
@@ -71,12 +67,14 @@ func initConfig() {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// the config will belocated under `~/.config/k8sgpt/k8sgpt.yaml` on linux
configDir := filepath.Join(xdg.ConfigHome, "k8sgpt")
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(configDir)
// Search config in home directory with name ".k8sgpt.git" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName("k8sgpt")
viper.SetConfigName(".k8sgpt")
viper.SafeWriteConfig()
}
@@ -92,44 +90,3 @@ func initConfig() {
// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
func performConfigMigrationIfNeeded() {
oldConfig, err := getLegacyConfigFilePath()
cobra.CheckErr(err)
oldConfigExists, err := util.FileExists(oldConfig)
cobra.CheckErr(err)
newConfig := getConfigFilePath()
newConfigExists, err := util.FileExists(newConfig)
cobra.CheckErr(err)
configDir := filepath.Dir(newConfig)
err = util.EnsureDirExists(configDir)
cobra.CheckErr(err)
if oldConfigExists && newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Warning: Legacy config file at `%s` detected! This file will be ignored!", oldConfig))
return
}
if oldConfigExists && !newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Performing config file migration from `%s` to `%s`", oldConfig, newConfig))
err = os.Rename(oldConfig, newConfig)
cobra.CheckErr(err)
}
}
func getConfigFilePath() string {
return filepath.Join(xdg.ConfigHome, "k8sgpt", "k8sgpt.yaml")
}
func getLegacyConfigFilePath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".k8sgpt.yaml"), nil
}

View File

@@ -19,17 +19,17 @@ spec:
containers:
- name: k8sgpt-container
imagePullPolicy: Always
image: ghcr.io/k8sgpt-ai/k8sgpt:v0.2.4 #x-release-please-version
image: ghcr.io/k8sgpt-ai/k8sgpt:dev-202304151719 #x-release-please-version
ports:
- containerPort: 8080
args: ["serve"]
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.5"
memory: "256Mi"
requests:
cpu: "0.125"
memory: "64Mi"
env:
- name: K8SGPT_MODEL
value: "gpt-3.5-turbo"

2
go.mod
View File

@@ -21,8 +21,6 @@ require (
)
require github.com/adrg/xdg v0.4.0 // indirect
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect

3
go.sum
View File

@@ -69,8 +69,6 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0A
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -948,7 +946,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -4,11 +4,10 @@ import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/viper"
"strings"
)
type NoOpAIClient struct {
@@ -35,7 +34,7 @@ func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool)
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
cacheKey := util.GetCacheKey(a.GetName(), sEnc)
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {

View File

@@ -5,9 +5,8 @@ import (
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"strings"
"github.com/fatih/color"
"github.com/spf13/viper"
@@ -60,7 +59,7 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool)
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
cacheKey := util.GetCacheKey(a.GetName(), sEnc)
// find in viper cache
if viper.IsSet(cacheKey) && !nocache {
// retrieve data from cache

View File

@@ -2,6 +2,7 @@ package analysis
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
@@ -148,6 +149,45 @@ func (a *Analysis) RunAnalysis() error {
return nil
}
func (a *Analysis) JsonOutput() ([]byte, error) {
var problems int
var status AnalysisStatus
for _, result := range a.Results {
problems += len(result.Error)
}
if problems > 0 {
status = StateProblemDetected
} else {
status = StateOK
}
result := JsonOutput{
Problems: problems,
Results: a.Results,
Status: status,
}
output, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("error marshalling json: %v", err)
}
return output, nil
}
func (a *Analysis) PrintOutput() {
fmt.Println("")
if len(a.Results) == 0 {
fmt.Println(color.GreenString("No problems detected"))
}
for n, result := range a.Results {
fmt.Printf("%s %s(%s)\n", color.CyanString("%d", n),
color.YellowString(result.Name), color.CyanString(result.ParentObject))
for _, err := range result.Error {
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text))
}
fmt.Println(color.GreenString(result.Details + "\n"))
}
}
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
if len(a.Results) == 0 {
return nil

View File

@@ -22,7 +22,7 @@ func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
Results: []common.Result{},
}
gotJson, err := analysis.PrintOutput("json")
gotJson, err := analysis.JsonOutput()
if err != nil {
t.Error(err)
}
@@ -75,7 +75,7 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
},
}
gotJson, err := analysis.PrintOutput("json")
gotJson, err := analysis.JsonOutput()
if err != nil {
t.Error(err)
}
@@ -136,7 +136,7 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
},
}
gotJson, err := analysis.PrintOutput("json")
gotJson, err := analysis.JsonOutput()
if err != nil {
t.Error(err)
}

View File

@@ -1,72 +0,0 @@
package analysis
import (
"encoding/json"
"fmt"
"strings"
"github.com/fatih/color"
)
var outputFormats = map[string]func(*Analysis) ([]byte, error){
"json": (*Analysis).jsonOutput,
"text": (*Analysis).textOutput,
}
func getOutputFormats() []string {
formats := make([]string, 0, len(outputFormats))
for format := range outputFormats {
formats = append(formats, format)
}
return formats
}
func (a *Analysis) PrintOutput(format string) ([]byte, error) {
outputFunc, ok := outputFormats[format]
if !ok {
return nil, fmt.Errorf("unsupported output format: %s. Available format %s", format, strings.Join(getOutputFormats(), ","))
}
return outputFunc(a)
}
func (a *Analysis) jsonOutput() ([]byte, error) {
var problems int
var status AnalysisStatus
for _, result := range a.Results {
problems += len(result.Error)
}
if problems > 0 {
status = StateProblemDetected
} else {
status = StateOK
}
result := JsonOutput{
Problems: problems,
Results: a.Results,
Status: status,
}
output, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("error marshalling json: %v", err)
}
return output, nil
}
func (a *Analysis) textOutput() ([]byte, error) {
var output strings.Builder
output.WriteString("\n")
if len(a.Results) == 0 {
output.WriteString(color.GreenString("No problems detected\n"))
return []byte(output.String()), nil
}
for n, result := range a.Results {
output.WriteString(fmt.Sprintf("%s %s(%s)\n", color.CyanString("%d", n),
color.YellowString(result.Name), color.CyanString(result.ParentObject)))
for _, err := range result.Error {
output.WriteString(fmt.Sprintf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text)))
}
output.WriteString(color.GreenString(result.Details + "\n"))
}
return []byte(output.String()), nil
}

View File

@@ -22,7 +22,7 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
var results []common.Result
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{})
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs("").List(a.Context, v1.ListOptions{})
if err != nil {
return results, err
}

View File

@@ -124,97 +124,3 @@ func TestCronJobBroken(t *testing.T) {
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
assert.Equal(t, analysisResults[0].Kind, "CronJob")
}
func TestCronJobBrokenMultipleNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "default",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*** * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
},
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "other-namespace",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*** * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := CronJobAnalyzer{}
analysisResults, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
assert.Equal(t, analysisResults[0].Kind, "CronJob")
}

View File

@@ -23,7 +23,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
"analyzer_name": kind,
})
deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{})
deployments, err := a.Client.GetClient().AppsV1().Deployments("").List(context.Background(), v1.ListOptions{})
if err != nil {
return nil, err
}

View File

@@ -60,81 +60,3 @@ func TestDeploymentAnalyzer(t *testing.T) {
assert.Equal(t, analysisResults[0].Kind, "Deployment")
assert.Equal(t, analysisResults[0].Name, "default/example")
}
func TestDeploymentAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
Replicas: 2,
AvailableReplicas: 1,
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
Replicas: 2,
AvailableReplicas: 1,
},
},
)
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
deploymentAnalyzer := DeploymentAnalyzer{}
analysisResults, err := deploymentAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Kind, "Deployment")
assert.Equal(t, analysisResults[0].Name, "default/example")
}

View File

@@ -5,8 +5,6 @@ import (
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -32,28 +30,28 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
var podInfo PodInfo
scaleTargetRefNotFound := false
switch scaleTargetRef.Kind {
case "Deployment":
deployment, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = DeploymentInfo{deployment}
_, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicationController":
rc, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = ReplicationControllerInfo{rc}
_, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicaSet":
rs, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = ReplicaSetInfo{rs}
_, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "StatefulSet":
ss, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = StatefulSetInfo{ss}
_, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
default:
failures = append(failures, common.Failure{
@@ -62,7 +60,7 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
})
}
if podInfo == nil {
if scaleTargetRefNotFound {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
Sensitive: []common.Sensitive{
@@ -72,26 +70,6 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
},
},
})
} else {
containers := len(podInfo.GetPodSpec().Containers)
for _, container := range podInfo.GetPodSpec().Containers {
if container.Resources.Requests == nil || container.Resources.Limits == nil {
containers--
}
}
if containers <= 0 {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s %s/%s does not have resource configured.", scaleTargetRef.Kind, a.Namespace, scaleTargetRef.Name),
Sensitive: []common.Sensitive{
{
Unmasked: scaleTargetRef.Name,
Masked: util.MaskString(scaleTargetRef.Name),
},
},
})
}
}
if len(failures) > 0 {
@@ -118,43 +96,3 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
return a.Results, nil
}
type PodInfo interface {
GetPodSpec() corev1.PodSpec
}
type DeploymentInfo struct {
*appsv1.Deployment
}
func (d DeploymentInfo) GetPodSpec() corev1.PodSpec {
return d.Spec.Template.Spec
}
// define a structure for ReplicationController
type ReplicationControllerInfo struct {
*corev1.ReplicationController
}
func (rc ReplicationControllerInfo) GetPodSpec() corev1.PodSpec {
return rc.Spec.Template.Spec
}
// define a structure for ReplicaSet
type ReplicaSetInfo struct {
*appsv1.ReplicaSet
}
func (rs ReplicaSetInfo) GetPodSpec() corev1.PodSpec {
return rs.Spec.Template.Spec
}
// define a structure for StatefulSet
type StatefulSetInfo struct {
*appsv1.StatefulSet
}
// implement PodInfo for StatefulSetInfo
func (ss StatefulSetInfo) GetPodSpec() corev1.PodSpec {
return ss.Spec.Template.Spec
}

View File

@@ -10,8 +10,6 @@ import (
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
@@ -165,7 +163,7 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
func TestHPAAnalyzerWithExistingScaleTargetRef(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
@@ -187,28 +185,6 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
@@ -228,293 +204,3 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "ReplicationController",
Name: "example",
},
},
},
&corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: corev1.ReplicationControllerSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "ReplicaSet",
Name: "example",
},
},
},
&appsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "StatefulSet",
Name: "example",
},
},
},
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.StatefulSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "example",
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "does not have resource configured."){
errorFound = true
break
}
if errorFound {
break
}
}
if !errorFound {
t.Error("expected error 'does not have resource configured.' not found in analysis results")
}
}
}
func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
})
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -113,35 +113,3 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
t.Error("expected error 'does not specify an Ingress class' not found in analysis results")
}
}
func TestIngressAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
})
ingressAnalyzer := IngressAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -120,77 +120,3 @@ func TestNetpolWithPod(t *testing.T) {
assert.Equal(t, len(results), 0)
}
func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
},
&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := NetworkPolicyAnalyzer{}
results, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(results), 1)
assert.Equal(t, results[0].Kind, "NetworkPolicy")
}

View File

@@ -47,57 +47,3 @@ func TestPodAnalyzer(t *testing.T) {
}
assert.Equal(t, len(analysisResults), 1)
}
func TestPodAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
},
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []common.Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -48,62 +48,3 @@ func TestServiceAnalyzer(t *testing.T) {
}
assert.Equal(t, len(analysisResults), 1)
}
func TestServiceAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": "example",
},
},
},
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": "example",
},
},
},
)
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
serviceAnalyzer := ServiceAnalyzer{}
analysisResults, err := serviceAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -145,33 +145,3 @@ func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
}
}
func TestStatefulSetAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
},
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -1,77 +0,0 @@
package server
import (
"bytes"
"net/http"
"time"
"go.uber.org/zap"
)
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
buf *bytes.Buffer
}
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
return &loggingResponseWriter{
w,
http.StatusOK,
&bytes.Buffer{},
}
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
return lrw.buf.Write(b)
}
func (lrw *loggingResponseWriter) Flush() {
if f, ok := lrw.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
lrw.ResponseWriter.Write(lrw.buf.Bytes())
}
func logRequest(logger *zap.Logger, fields []zap.Field, statusCode int, message string) {
if statusCode >= 400 {
logger.Error(message, fields...)
} else {
logger.Info("request completed", fields...)
}
}
func loggingMiddleware(next http.Handler) http.Handler {
config := zap.NewProductionConfig()
config.DisableCaller = true
logger, err := config.Build()
if err != nil {
panic(err)
}
defer logger.Sync()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
lrw := NewLoggingResponseWriter(w)
start := time.Now()
defer func() {
duration := time.Since(start).Milliseconds()
fields := []zap.Field{
zap.Int64("duration_ms", duration),
zap.String("method", r.Method),
zap.String("remote_addr", r.RemoteAddr),
zap.Int("status_code", lrw.statusCode),
zap.String("url", r.URL.Path),
}
logRequest(logger, fields, lrw.statusCode, lrw.buf.String())
}()
next.ServeHTTP(lrw, r)
lrw.Flush()
})
}

View File

@@ -42,54 +42,45 @@ func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) {
anonymize := getBoolParam(r.URL.Query().Get("anonymize"))
nocache := getBoolParam(r.URL.Query().Get("nocache"))
language := r.URL.Query().Get("language")
s.Output = r.URL.Query().Get("output")
if s.Output == "" {
s.Output = "json"
}
config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
fmt.Fprintf(w, err.Error())
}
err = config.RunAnalysis()
if err != nil {
color.Red("Error: %v", err)
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
fmt.Fprintf(w, err.Error())
}
if explain {
err := config.GetAIResults(s.Output, anonymize)
if err != nil {
color.Red("Error: %v", err)
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
fmt.Fprintf(w, err.Error())
}
}
out, err := config.PrintOutput(s.Output)
output, err := config.JsonOutput()
if err != nil {
color.Red("Error: %v", err)
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
fmt.Fprintf(w, err.Error())
}
health.Success++
fmt.Fprintf(w, string(out))
fmt.Fprintf(w, string(output))
}
func (s *Config) Serve() error {
handler := loggingMiddleware(http.DefaultServeMux)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/analyze", s.analyzeHandler)
http.HandleFunc("/healthz", s.healthzHandler)
color.Green("Starting server on port %s", s.Port)
err := http.ListenAndServe(":"+s.Port, handler)
err := http.ListenAndServe(":"+s.Port, nil)
if err != nil {
fmt.Printf("error starting server: %s\n", err)
return err

View File

@@ -3,10 +3,8 @@ package util
import (
"context"
"encoding/base64"
"errors"
"fmt"
"math/rand"
"os"
"regexp"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
@@ -134,8 +132,8 @@ func ReplaceIfMatch(text string, pattern string, replacement string) string {
return text
}
func GetCacheKey(provider string, language string, sEnc string) string {
return fmt.Sprintf("%s-%s-%s", provider, language, sEnc)
func GetCacheKey(provider string, sEnc string) string {
return fmt.Sprintf("%s-%s", provider, sEnc)
}
func GetPodListByLabels(client k.Interface,
@@ -152,23 +150,3 @@ func GetPodListByLabels(client k.Interface,
return pods, nil
}
func FileExists(path string) (bool, error) {
if _, err := os.Stat(path); err == nil {
return true, nil
} else if errors.Is(err, os.ErrNotExist) {
return false, nil
} else {
return false, err
}
}
func EnsureDirExists(dir string) error {
err := os.MkdirAll(dir, 0755)
if errors.Is(err, os.ErrExist) {
return nil
}
return err
}