Compare commits

..

27 Commits

Author SHA1 Message Date
github-actions[bot]
4c773175cd chore(main): release 0.1.8 (#192)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-03 10:55:55 +02:00
Thomas Schuetz
67753be6f3 chore: create linux packages (#201)
* chore: build deb, rpm, apk packages

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: IMAGE NAME

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-03 10:54:40 +02:00
HOLLEVILLE Matthis
075a940d2c feat: add password flag for backend authentication (#199)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-03 10:12:59 +02:00
renovate[bot]
f8291aab08 chore(deps): pin dependencies (#198)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-02 23:09:52 +02:00
Alex Jones
622bf5824b Merge pull request #197 from k8sgpt-ai/feat/unit-testing-example 2023-04-02 21:56:09 +01:00
AlexsJones
5f30a4ddf4 feat: test workflow
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:29:52 +01:00
AlexsJones
d5bc91fa8a test workflow
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:29:08 +01:00
AlexsJones
44cc8f7ad6 feat: service test
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:24:04 +01:00
AlexsJones
35b838bfaf feat: adding unit testing and example
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:17:05 +01:00
Alex Jones
b6436378e1 Merge pull request #196 from k8sgpt-ai/feat/analyzer-ifacing-example
feat: analyzer ifacing
2023-04-02 20:50:44 +01:00
Thomas Schuetz
508b5c37e1 Merge branch 'main' into feat/analyzer-ifacing-example 2023-04-02 21:46:39 +02:00
Alex Jones
124e46e4ed Merge pull request #195 from k8sgpt-ai/renovate/github.com-sashabaranov-go-openai-1.x 2023-04-02 20:41:39 +01:00
AlexsJones
426f562be8 feat: analyzer ifacing
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 20:32:44 +01:00
renovate[bot]
91fb06530a fix(deps): update module github.com/sashabaranov/go-openai to v1.5.8
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-02 16:38:47 +00:00
Alex Jones
bbbbd851df Merge pull request #194 from rakshitgondwal/dev 2023-04-02 17:14:14 +01:00
Rakshit Gondwal
dde4e833b0 feat: alias filter to filters
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-02 21:35:15 +05:30
Alex Jones
5986f4f840 Merge pull request #191 from k8sgpt-ai/feat/shields
feat: adding shields to readme
2023-04-02 14:35:24 +01:00
AlexsJones
213ecd8e83 feat: adding shields to readme
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 14:34:36 +01:00
Alex Jones
6ead5b356d Merge pull request #173 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.7
2023-04-02 14:26:40 +01:00
github-actions[bot]
c733292b92 chore(main): release 0.1.7 2023-04-02 13:14:01 +00:00
Alex Jones
3c95a5db82 Merge pull request #186 from yeahservice/feature/pdb-analyzer
feat: add pdb analyzer
2023-04-02 14:13:21 +01:00
Alex Jones
ea1ca44dba Merge branch 'main' into feature/pdb-analyzer 2023-04-02 14:11:46 +01:00
Alex Jones
33319b8ef5 Merge pull request #189 from yeahservice/fix/hpa-analyzer-parent-object
fix: hpaAnalyzer analysis result is using wrong parent
2023-04-02 14:05:49 +01:00
Dominik Augustin
1190fe60fd fix: hpaAnalyzer analysis result is using wrong parent
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 15:05:07 +02:00
Dominik Augustin
ceff0084df fix: spelling of PodDisruptionBudget
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:57:24 +02:00
Dominik Augustin
f6974d0758 docs: add pdbAnalyzer as optional analyzer
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:50:03 +02:00
Dominik Augustin
532a5ce033 feat: add pda analyzer
Adds a PodDisruptionBudget analyzer, that checks if PDBs have matching
pods with their defined selector.

Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:38:39 +02:00
31 changed files with 718 additions and 455 deletions

View File

@@ -36,7 +36,6 @@ jobs:
if: needs.release-please.outputs.releases_created == 'true'
permissions:
contents: write
needs:
- release-please
runs-on: ubuntu-latest
@@ -49,6 +48,8 @@ jobs:
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: '1.20'
- name: Download Syft
uses: anchore/sbom-action/download-syft@v0.13.4
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4
with:
@@ -70,6 +71,7 @@ jobs:
id-token: write
env:
IMAGE_TAG: ghcr.io/k8sgpt-ai/k8sgpt:${{ needs.release-please.outputs.tag_name }}
IMAGE_NAME: k8sgpt
steps:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
@@ -98,8 +100,8 @@ jobs:
${{ env.IMAGE_TAG }}
builder: ${{ steps.buildx.outputs.name }}
push: true
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
- name: Generate SBOM
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1

21
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Run tests
on: [push]
env:
GO_VERSION: "~1.20"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
- name: Set up Go
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Test
run: go test -v ./...

View File

@@ -16,6 +16,27 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}}
nfpms:
- file_name_template: '{{ .ProjectName }}_{{ .Arch }}'
homepage: https://k8sgpt.ai
description: >-
K8sGPT is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english. It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
license: "MIT"
formats:
- deb
- rpm
- apk
bindir: /usr/bin
section: utils
contents:
- src: ./LICENSE
dst: /usr/share/doc/nfpm/copyright
file_info:
mode: 0644
sboms:
- artifacts: archive
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.

View File

@@ -1 +1 @@
{".":"0.1.6"}
{".":"0.1.8"}

View File

@@ -1,5 +1,65 @@
# Changelog
## [0.1.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.7...v0.1.8) (2023-04-03)
### Features
* add password flag for backend authentication ([#199](https://github.com/k8sgpt-ai/k8sgpt/issues/199)) ([075a940](https://github.com/k8sgpt-ai/k8sgpt/commit/075a940d2c9bdd8aa9162940ed46abad47d46998))
* adding shields to readme ([213ecd8](https://github.com/k8sgpt-ai/k8sgpt/commit/213ecd8e83933fabaa5d3d674c67958599dd72ce))
* adding unit testing and example ([35b838b](https://github.com/k8sgpt-ai/k8sgpt/commit/35b838bfafa248dbf3932c7a3ee708b1a1539f18))
* alias filter to filters ([dde4e83](https://github.com/k8sgpt-ai/k8sgpt/commit/dde4e833b0e87553dea4e5c1e17a14e303956bc1))
* analyzer ifacing ([426f562](https://github.com/k8sgpt-ai/k8sgpt/commit/426f562be83ed0e708a07b9e1900ac06fa017c27))
* service test ([44cc8f7](https://github.com/k8sgpt-ai/k8sgpt/commit/44cc8f7ad68d152ec577e57cab7d8d9ab9613378))
* test workflow ([5f30a4d](https://github.com/k8sgpt-ai/k8sgpt/commit/5f30a4ddf44ebff949bb0573f261667539a2dcfb))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.8 ([91fb065](https://github.com/k8sgpt-ai/k8sgpt/commit/91fb06530a21259da6e72c28342e743d2b481294))
### Other
* create linux packages ([#201](https://github.com/k8sgpt-ai/k8sgpt/issues/201)) ([67753be](https://github.com/k8sgpt-ai/k8sgpt/commit/67753be6f317c462ebe1d9a316f2b0c9684ca4e5))
* **deps:** pin dependencies ([#198](https://github.com/k8sgpt-ai/k8sgpt/issues/198)) ([f8291aa](https://github.com/k8sgpt-ai/k8sgpt/commit/f8291aab085209f9fee13a6c92c96076163e2e90))
## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)
### Features
* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))
* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))
* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))
### Bug Fixes
* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))
* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))
* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))
### Refactoring
* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)
### Other
* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))
* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))
* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))
* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))
* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))
* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))
### Docs
* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))
## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)

View File

@@ -2,6 +2,11 @@
<source media="(prefers-color-scheme: dark)" srcset="./images/banner-white.png" width="600px;">
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
</picture>
<br/>
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/k8sgpt-ai/k8sgpt)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/k8sgpt-ai/k8sgpt/release.yaml)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/k8sgpt-ai/k8sgpt)
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
@@ -46,7 +51,8 @@ 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.
* And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.
@@ -72,6 +78,7 @@ you will be able to write your own analyzers.
#### Optional
- [x] hpaAnalyzer
- [x] pdbAnalyzer
## Usage
@@ -177,4 +184,8 @@ the root cause of an issue.
Please read our [contributing guide](./CONTRIBUTING.md).
## Community
* Find us on [Slack](https://k8sgpt.slack.com/)
Find us on [Slack](https://k8sgpt.slack.com/)
<a href="https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt" />
</a>

View File

@@ -1,13 +1,19 @@
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/analysis"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/schollz/progressbar/v3"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var (
@@ -47,28 +53,91 @@ var AnalyzeCmd = &cobra.Command{
os.Exit(1)
}
// AnalysisResult configuration
aiClient, err := ai.NewAIClient("openai")
if err != nil {
color.Red("Error: %v", err)
var aiClient ai.IAI
switch backendType {
case "openai":
aiClient = &ai.OpenAIClient{}
if err := aiClient.Configure(token, language); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
default:
color.Red("Backend not supported")
os.Exit(1)
}
if err := aiClient.Configure(token, language); err != nil {
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
// Analysis configuration
config := &analyzer.AnalysisConfiguration{
Namespace: namespace,
NoCache: nocache,
Explain: explain,
}
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)
}
analysis := analysis.NewAnalysis(namespace, nocache, explain, filters, backend)
if len(*analysisResults) == 0 {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
}
var bar = progressbar.Default(int64(len(*analysisResults)))
if !explain {
bar.Clear()
}
var printOutput []analyzer.Analysis
// Run analysis
_ = analysis.RunAnalysis()
for _, analysis := range *analysisResults {
analysis.PrintJsonResult()
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 + "\n"))
}
}
},
}
func init() {
// namespace flag
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
// no cache flag

View File

@@ -13,7 +13,8 @@ import (
)
var (
backend string
backend string
password string
)
// authCmd represents the auth command
@@ -38,14 +39,16 @@ var AuthCmd = &cobra.Command{
color.Green("Using %s as backend AI provider", backendType)
}
fmt.Printf("Enter %s Key: ", backendType)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backendType,
err.Error())
os.Exit(1)
if password == "" {
fmt.Printf("Enter %s Key: ", backendType)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backendType,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}
password := strings.TrimSpace(string(bytePassword))
viper.Set(fmt.Sprintf("%s_key", backendType), password)
if err := viper.WriteConfig(); err != nil {
@@ -59,4 +62,6 @@ var AuthCmd = &cobra.Command{
func init() {
// add flag for backend
AuthCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
// add flag for password
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
}

View File

@@ -6,7 +6,7 @@ import (
var FiltersCmd = &cobra.Command{
Use: "filters",
Aliases: []string{"filters"},
Aliases: []string{"filter"},
Short: "Manage filters for analyzing Kubernetes resources",
Long: `The filters command allows you to manage filters that are used to analyze Kubernetes resources.
You can list available filters to analyze resources.`,

View File

@@ -7,7 +7,6 @@ import (
"github.com/spf13/viper"
"os/exec"
"runtime"
"time"
)
var (
@@ -31,10 +30,6 @@ var GenerateCmd = &cobra.Command{
backendType = backend
}
fmt.Println("")
color.Green("Opening: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
fmt.Println("")
time.Sleep(5 * time.Second)
openbrowser("https://beta.openai.com/account/api-keys")
},
}
@@ -46,9 +41,15 @@ func init() {
func openbrowser(url string) {
var err error
isGui := true
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
_, err = exec.LookPath("xdg-open")
if err != nil {
isGui = false
} else {
err = exec.Command("xdg-open", url).Start()
}
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
@@ -56,7 +57,21 @@ func openbrowser(url string) {
default:
err = fmt.Errorf("unsupported platform")
}
printInstructions(isGui, backend)
if err != nil {
fmt.Println(err)
}
}
func printInstructions(isGui bool, backendType string) {
fmt.Println("")
if isGui {
color.Green("Opening: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
fmt.Println("")
} else {
color.Green("Please open: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
fmt.Println("")
}
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
fmt.Println("")
}

8
go.mod
View File

@@ -4,7 +4,8 @@ go 1.20
require (
github.com/fatih/color v1.15.0
github.com/sashabaranov/go-openai v1.5.7
github.com/sashabaranov/go-openai v1.5.8
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
@@ -16,6 +17,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
@@ -36,11 +38,15 @@ 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/pkg/errors v0.9.1 // 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

18
go.sum
View File

@@ -66,6 +66,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
@@ -173,6 +175,7 @@ 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=
@@ -191,8 +194,13 @@ 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=
@@ -206,17 +214,23 @@ github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
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/sashabaranov/go-openai v1.5.8 h1:EfNEmc+Ue+CuRy7iSpNdxfHyiOv2vQsQ2Y0kZRA/z5w=
github.com/sashabaranov/go-openai v1.5.8/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=

View File

@@ -1,13 +1,9 @@
package openai
package ai
import (
"context"
"encoding/base64"
"errors"
"fmt"
"github.com/fatih/color"
"github.com/spf13/viper"
"strings"
"github.com/sashabaranov/go-openai"
)
@@ -20,10 +16,8 @@ const (
)
type OpenAIClient struct {
context context.Context
client *openai.Client
language string
nocache bool
}
func (c *OpenAIClient) Configure(token string, language string) error {
@@ -52,41 +46,3 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
}
return resp.Choices[0].Message.Content, nil
}
func (c OpenAIClient) Parse(text string, prompt []string, nocache bool) (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) && !c.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
}
response, err := c.GetCompletion(c.context, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}
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
}

View File

@@ -1,25 +1,8 @@
package ai
import (
"context"
"errors"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai/openai"
)
var AIProviderMap = map[string]IAI{
"openai": &openai.OpenAIClient{},
}
import "context"
type IAI interface {
Configure(token string, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
Parse(text string, prompt []string, nocache bool) (string, error)
}
func NewAIClient(provider string) (IAI, error) {
ai, ok := AIProviderMap[provider]
if !ok {
return nil, errors.New("AI provider not found")
}
return ai, nil
}

View File

@@ -1,98 +0,0 @@
package analysis
import (
"context"
"encoding/json"
"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 NewAnalysis(namespace string, noCache bool, explain bool, filters []string, aiProvider string) *Analysis {
var aiClient ai.IAI
var err error
ctx := context.Background()
client := viper.Get("kubernetesClient").(*kubernetes.Client)
if explain {
aiClient, err = ai.NewAIClient(aiProvider)
if err != nil {
fmt.Println("Error creating AI client: ", err)
}
}
return &Analysis{
Context: ctx,
Namespace: namespace,
NoCache: noCache,
Explain: explain,
Filters: filters,
Client: client,
AIClient: aiClient,
}
}
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, a.AIClient, a.Explain)
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, a.AIClient, a.Explain)
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)
}
}
func (a *Analysis) PrintJsonResult() {
output, err := json.MarshalIndent(a.analysisResults, "", " ")
if err != nil {
fmt.Println("Error marshalling json: ", err)
}
fmt.Println(string(output))
}

34
pkg/analyzer/analysis.go Normal file
View File

@@ -0,0 +1,34 @@
package analyzer
import (
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/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
HorizontalPodAutoscalers autoscalingv1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
}
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,78 +2,133 @@ package analyzer
import (
"context"
"encoding/base64"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"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"
)
type IAnalyzer interface {
Analyze() error
GetResult() []common.Result
var coreAnalyzerMap = map[string]IAnalyzer{
"Pod": PodAnalyzer{},
"ReplicaSet": ReplicaSetAnalyzer{},
"PersistentVolumeClaim": PvcAnalyzer{},
"Service": ServiceAnalyzer{},
"Ingress": IngressAnalyzer{},
}
var AnalyzerMap = map[string]IAnalyzer{
"Pod": &pod.PodAnalyzer{},
"ReplicaSet": &rs.ReplicaSetAnalyzer{},
"PersistentVolumeClaim": &pvc.PvcAnalyzer{},
"Service": &service.ServiceAnalyzer{},
"Ingress": &ingress.IngressAnalyzer{},
"HPA": &hpa.HPAAnalyzer{},
var additionalAnalyzerMap = map[string]IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
}
var coreAnalyzerList = []string{"Pod", "ReplicaSet", "PersistentVolumeClaim", "Service", "Ingress"}
var additionalAnalyzerList = []string{"HPA"}
func RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration,
client *kubernetes.Client,
aiClient ai.IAI, analysisResults *[]Analysis) error {
func NewAnalyzer(analyzer string, client *kubernetes.Client, context context.Context, namespace string, aiClient ai.IAI, explain bool) (IAnalyzer, error) {
analyzerConfig := common.Analyzer{
AIClient: aiClient,
Namespace: namespace,
Context: context,
Client: client,
Explain: explain,
activeFilters := viper.GetStringSlice("active_filters")
analyzerMap := getAnalyzerMap()
// 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.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
return nil
}
analyzerConfig.PreAnalysis = make(map[string]common.PreAnalysis)
return AnalyzerMap[analyzer], nil
// if the filters flag is specified
if len(filters) != 0 {
for _, filter := range filters {
if analyzer, ok := analyzerMap[filter]; ok {
if err := analyzer.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
}
return nil
}
// use active_filters
for _, filter := range activeFilters {
if analyzer, ok := analyzerMap[filter]; ok {
if err := analyzer.RunAnalysis(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
}
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
}
output, err := base64.StdEncoding.DecodeString(response)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
}
return string(output), nil
}
response, err := aiClient.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}
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)
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
}
additionalKeys := []string{}
for _, filter := range coreAnalyzerList {
coreKeys = append(additionalKeys, filter)
additionalKeys := make([]string, 0, len(additionalAnalyzerMap))
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
}
return coreKeys, additionalKeys
}
func GetAnalyzerList() []string {
list := []string{}
func getAnalyzerMap() map[string]IAnalyzer {
list = append(list, coreAnalyzerList...)
list = append(list, additionalAnalyzerList...)
mergedMap := make(map[string]IAnalyzer)
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)
}
// add core analyzer
for key, value := range coreAnalyzerMap {
mergedMap[key] = value
}
return list
// add additional analyzer
for key, value := range additionalAnalyzerMap {
mergedMap[key] = value
}
return mergedMap
}

View File

@@ -1,57 +0,0 @@
package common
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pod.Name,
})
if err != nil {
return nil, err
}
// find most recent event
var latestEvent *v1.Event
for _, event := range events.Items {
if latestEvent == nil {
latestEvent = &event
}
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
latestEvent = &event
}
}
return latestEvent, nil
}
func FetchLatestPvcEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pvc *v1.PersistentVolumeClaim) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pvc.Namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pvc.Name,
})
if err != nil {
return nil, err
}
// find most recent event
var latestEvent *v1.Event
for _, event := range events.Items {
if latestEvent == nil {
latestEvent = &event
}
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
latestEvent = &event
}
}
return latestEvent, nil
}

View File

@@ -1,40 +0,0 @@
package common
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"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
AIClient ai.IAI
Context context.Context
Namespace string
PreAnalysis map[string]PreAnalysis
Explain bool
NoCache bool
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
}

33
pkg/analyzer/events.go Normal file
View File

@@ -0,0 +1,33 @@
package analyzer
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + name,
})
if err != nil {
return nil, err
}
// find most recent event
var latestEvent *v1.Event
for _, event := range events.Items {
if latestEvent == nil {
latestEvent = &event
}
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
latestEvent = &event
}
}
return latestEvent, nil
}

View File

@@ -1,22 +1,27 @@
package hpa
package analyzer
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"
)
type HPAAnalyzer struct {
common.Analyzer
}
type HpaAnalyzer struct{}
func (a *HPAAnalyzer) Analyze() error {
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
func (HpaAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, hpa := range list.Items {
var failures []string
@@ -26,22 +31,22 @@ func (a *HPAAnalyzer) Analyze() error {
switch scaleTargetRef.Kind {
case "Deployment":
_, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
_, err := client.GetClient().AppsV1().Deployments(config.Namespace).Get(ctx, 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{})
_, err := client.GetClient().CoreV1().ReplicationControllers(config.Namespace).Get(ctx, 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{})
_, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).Get(ctx, 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{})
_, err := client.GetClient().AppsV1().StatefulSets(config.Namespace).Get(ctx, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
@@ -54,26 +59,25 @@ func (a *HPAAnalyzer) Analyze() error {
}
if len(failures) > 0 {
a.PreAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
}
}
for key, value := range a.PreAnalysis {
var currentAnalysis = common.Result{
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "HorizontalPodAutoscaler",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
parent, _ := util.GetParent(client, value.HorizontalPodAutoscalers.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Result = append(a.Result, currentAnalysis)
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}
func (a *HPAAnalyzer) GetResult() []common.Result {
return a.Result
}

13
pkg/analyzer/ianalyzer.go Normal file
View File

@@ -0,0 +1,13 @@
package analyzer
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
)
type IAnalyzer interface {
RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error
}

View File

@@ -1,23 +1,27 @@
package ingress
package analyzer
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"
)
type IngressAnalyzer struct {
common.Analyzer
}
type IngressAnalyzer struct{}
func (a *IngressAnalyzer) Analyze() error {
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
func (IngressAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().NetworkingV1().Ingresses(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, ing := range list.Items {
var failures []string
@@ -34,7 +38,7 @@ func (a *IngressAnalyzer) Analyze() error {
// check if ingressclass exist
if ingressClassName != nil {
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
_, err := client.GetClient().NetworkingV1().IngressClasses().Get(ctx, *ingressClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName))
}
@@ -44,7 +48,7 @@ func (a *IngressAnalyzer) Analyze() error {
for _, rule := range ing.Spec.Rules {
// loop over paths
for _, path := range rule.HTTP.Paths {
_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})
_, err := client.GetClient().CoreV1().Services(ing.Namespace).Get(ctx, 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))
}
@@ -52,13 +56,13 @@ func (a *IngressAnalyzer) Analyze() error {
}
for _, tls := range ing.Spec.TLS {
_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})
_, err := client.GetClient().CoreV1().Secrets(ing.Namespace).Get(ctx, 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 {
a.PreAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
@@ -66,20 +70,17 @@ func (a *IngressAnalyzer) Analyze() error {
}
for key, value := range a.PreAnalysis {
var currentAnalysis = common.Result{
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "Ingress",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
parent, _ := util.GetParent(client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Result = append(a.Result, currentAnalysis)
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}
func (a *IngressAnalyzer) GetResult() []common.Result {
return a.Result
}

View File

@@ -0,0 +1,67 @@
package analyzer
import (
"context"
"fmt"
"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"
)
type PdbAnalyzer struct{}
func (PdbAnalyzer) RunAnalysis(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().PolicyV1().PodDisruptionBudgets(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, pdb := range list.Items {
var failures []string
evt, err := FetchLatestEvent(ctx, client, pdb.Namespace, pdb.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "NoPods" && evt.Message != "" {
if pdb.Spec.Selector != nil {
for k, v := range pdb.Spec.Selector.MatchLabels {
failures = append(failures, fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v))
}
for _, v := range pdb.Spec.Selector.MatchExpressions {
failures = append(failures, fmt.Sprintf("%s, expected expression %s", evt.Message, v))
}
} else {
failures = append(failures, fmt.Sprintf("%s, selector is nil", evt.Message))
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = PreAnalysis{
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "PodDisruptionBudget",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.PodDisruptionBudget.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

View File

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

View File

@@ -0,0 +1,45 @@
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestPodAnalzyer(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.",
},
},
},
})
podAnalyzer := PodAnalyzer{}
var analysisResults []Analysis
podAnalyzer.RunAnalysis(context.Background(),
&AnalysisConfiguration{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset,
}, nil, &analysisResults)
assert.Equal(t, len(analysisResults), 1)
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestServiceAnalzyer(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",
},
}})
serviceAnalyzer := ServiceAnalyzer{}
var analysisResults []Analysis
serviceAnalyzer.RunAnalysis(context.Background(),
&AnalysisConfiguration{
Namespace: "default",
},
&kubernetes.Client{
Client: clientset,
}, nil, &analysisResults)
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -6,11 +6,11 @@ import (
)
type Client struct {
client *kubernetes.Clientset
Client kubernetes.Interface
}
func (c *Client) GetClient() *kubernetes.Clientset {
return c.client
func (c *Client) GetClient() kubernetes.Interface {
return c.Client
}
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
@@ -31,6 +31,6 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
}
return &Client{
client: clientSet,
Client: clientSet,
}, nil
}