feat: refactor integration to use Failure object

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
This commit is contained in:
Matthis Holleville
2023-04-12 11:40:51 +02:00
35 changed files with 1394 additions and 160 deletions

View File

@@ -25,7 +25,7 @@ jobs:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
- uses: google-github-actions/release-please-action@f7edb9e61420a022c0f1123edaf342d7e127df92 # v3
- uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3
id: release
with:
command: manifest

View File

@@ -67,7 +67,15 @@ var AnalyzeCmd = &cobra.Command{
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
kubecontext := viper.GetString("kubecontext")
kubeconfig := viper.GetString("kubeconfig")
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
os.Exit(1)
}
// AnalysisResult configuration
config := &analysis.Analysis{
Namespace: namespace,

View File

@@ -18,10 +18,9 @@ var addCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
// Verify filter exist
invalidFilters := []string{}
for _, f := range inputFilters {

View File

@@ -16,23 +16,35 @@ var listCmd = &cobra.Command{
Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
Run: func(cmd *cobra.Command, args []string) {
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, additionalFilters := analyzer.ListFilters()
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
availableFilters := append(coreFilters, additionalFilters...)
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
fmt.Printf(color.YellowString("Active: \n"))
for _, filter := range activeFilters {
fmt.Printf("> %s\n", color.GreenString(filter))
// if the filter is an integration, mark this differently
if util.SliceContainsString(integrationFilters, filter) {
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
} else {
fmt.Printf("> %s\n", color.GreenString(filter))
}
}
// display inactive filters
if len(inactiveFilters) != 0 {
fmt.Printf(color.YellowString("Unused: \n"))
for _, filter := range inactiveFilters {
fmt.Printf("> %s\n", color.RedString(filter))
// if the filter is an integration, mark this differently
if util.SliceContainsString(integrationFilters, filter) {
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
} else {
fmt.Printf("> %s\n", color.RedString(filter))
}
}
}

View File

@@ -21,7 +21,7 @@ var removeCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, _ := analyzer.ListFilters()
coreFilters, _, _ := analyzer.ListFilters()
if len(activeFilters) == 0 {
activeFilters = coreFilters

View File

@@ -0,0 +1,33 @@
package integration
import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// activateCmd represents the activate command
var activateCmd = &cobra.Command{
Use: "activate [integration]",
Short: "Activate an integration",
Long: ``,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
integrationName := args[0]
integration := integration.NewIntegration()
// Check if the integation exists
err := integration.Activate(integrationName, namespace)
if err != nil {
color.Red("Error: %v", err)
return
}
color.Green("Activated integration %s", integrationName)
},
}
func init() {
IntegrationCmd.AddCommand(activateCmd)
}

View File

@@ -0,0 +1,35 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package integration
import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// deactivateCmd represents the deactivate command
var deactivateCmd = &cobra.Command{
Use: "deactivate [integration]",
Short: "Deactivate an integration",
Args: cobra.ExactArgs(1),
Long: `For example e.g. k8sgpt integration deactivate trivy`,
Run: func(cmd *cobra.Command, args []string) {
integrationName := args[0]
integration := integration.NewIntegration()
if err := integration.Deactivate(integrationName, namespace); err != nil {
color.Red("Error: %v", err)
return
}
color.Green("Deactivated integration %s", integrationName)
},
}
func init() {
IntegrationCmd.AddCommand(deactivateCmd)
}

View File

@@ -0,0 +1,28 @@
package integration
import (
"github.com/spf13/cobra"
)
var (
namespace string
)
// IntegrationCmd represents the integrate command
var IntegrationCmd = &cobra.Command{
Use: "integration",
Aliases: []string{"integrations"},
Short: "Intergrate another tool into K8sGPT",
Long: `Intergrate another tool into K8sGPT. For example:
k8sgpt integration activate trivy
This would allow you to deploy trivy into your cluster and use a K8sGPT analyzer to parse trivy results.`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func init() {
IntegrationCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to use for the integration")
}

50
cmd/integration/list.go Normal file
View File

@@ -0,0 +1,50 @@
package integration
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Short: "Lists built-in integrations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
integrationProvider := integration.NewIntegration()
integrations := integrationProvider.List()
fmt.Println(color.YellowString("Active:"))
for _, i := range integrations {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if b {
fmt.Printf("> %s\n", color.GreenString(i))
}
}
fmt.Println(color.YellowString("Unused: "))
for _, i := range integrations {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !b {
fmt.Printf("> %s\n", color.GreenString(i))
}
}
},
}
func init() {
IntegrationCmd.AddCommand(listCmd)
}

View File

@@ -4,16 +4,14 @@ import (
"os"
"path/filepath"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"k8s.io/client-go/util/homedir"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/integration"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/client-go/util/homedir"
)
var (
@@ -54,13 +52,10 @@ func init() {
rootCmd.AddCommand(analyze.AnalyzeCmd)
rootCmd.AddCommand(filters.FiltersCmd)
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.AddCommand(integration.IntegrationCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)")
rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.")
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// initConfig reads in config file and ENV variables if set.
@@ -81,14 +76,8 @@ func initConfig() {
viper.SafeWriteConfig()
}
//Initialise the kubeconfig
kubernetesClient, err := kubernetes.NewClient(kubecontext, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
os.Exit(1)
}
viper.Set("kubernetesClient", kubernetesClient)
viper.Set("kubecontext", kubecontext)
viper.Set("kubeconfig", kubeconfig)
viper.AutomaticEnv() // read in environment variables that match

102
go.mod
View File

@@ -3,72 +3,172 @@ module github.com/k8sgpt-ai/k8sgpt
go 1.20
require (
github.com/aquasecurity/trivy-operator v0.13.0
github.com/fatih/color v1.15.0
github.com/magiconair/properties v1.8.7
github.com/mittwald/go-helm-client v0.12.1
github.com/sashabaranov/go-openai v1.7.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
golang.org/x/term v0.7.0
helm.sh/helm/v3 v3.11.2
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/kubectl v0.26.3
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/aquasecurity/defsec v0.85.0 // indirect
github.com/aquasecurity/go-dep-parser v0.0.0-20230324043952-2172dc218241 // indirect
github.com/aquasecurity/table v1.8.0 // indirect
github.com/aquasecurity/tml v0.6.1 // indirect
github.com/aquasecurity/trivy v0.39.0 // indirect
github.com/aquasecurity/trivy-db v0.0.0-20230116084806-4bcdf1c414d0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/containerd v1.6.19 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v23.0.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-containerregistry v0.14.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/masahiro331/go-xfs-filesystem v0.0.0-20221225060805-c02764233454 // 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/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rubenv/sql-migrate v1.3.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spdx/tools-golang v0.5.0 // 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
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/exp v0.0.0-20221109205753-fc8884afc316 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2 // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.26.3 // indirect
k8s.io/apiserver v0.26.3 // indirect
k8s.io/cli-runtime v0.26.3 // indirect
k8s.io/component-base v0.26.3 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c // indirect
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
oras.land/oras-go v1.2.2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
replace oras.land/oras-go => oras.land/oras-go v1.1.1
// v0.3.1-0.20230104082527-d6f58551be3f is taken from github.com/moby/buildkit v0.11.0
// spdx logic write on v0.3.0 and incompatible with v0.3.1-0.20230104082527-d6f58551be3f
replace github.com/spdx/tools-golang => github.com/spdx/tools-golang v0.3.0

609
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/schollz/progressbar/v3"
@@ -21,7 +22,7 @@ type Analysis struct {
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []analyzer.Result
Results []common.Result
Namespace string
NoCache bool
Explain bool
@@ -35,9 +36,9 @@ const (
)
type JsonOutput struct {
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []analyzer.Result `json:"results"`
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []common.Result `json:"results"`
}
func (a *Analysis) RunAnalysis() error {
@@ -46,7 +47,7 @@ func (a *Analysis) RunAnalysis() error {
analyzerMap := analyzer.GetAnalyzerMap()
analyzerConfig := analyzer.Analyzer{
analyzerConfig := common.Analyzer{
Client: a.Client,
Context: a.Context,
Namespace: a.Namespace,

View File

@@ -5,21 +5,21 @@ import (
"fmt"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/stretchr/testify/require"
)
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{},
Results: []common.Result{},
Namespace: "default",
}
expected := JsonOutput{
Status: StateOK,
Problems: 0,
Results: []analyzer.Result{},
Results: []common.Result{},
}
gotJson, err := analysis.JsonOutput()
@@ -41,14 +41,14 @@ func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
func TestAnalysis_ProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []analyzer.Failure{
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
@@ -60,14 +60,14 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 1,
Results: []analyzer.Result{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []analyzer.Failure{
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
@@ -94,18 +94,18 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []analyzer.Failure{
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
{
Text: "another-test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
@@ -117,18 +117,18 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 2,
Results: []analyzer.Result{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []analyzer.Failure{
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
{
Text: "another-test-problem",
Sensitive: []analyzer.Sensitive{},
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",

View File

@@ -1,10 +1,15 @@
package analyzer
type IAnalyzer interface {
Analyze(analysis Analyzer) ([]Result, error)
}
import (
"fmt"
"os"
var coreAnalyzerMap = map[string]IAnalyzer{
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
)
var coreAnalyzerMap = map[string]common.IAnalyzer{
"Pod": PodAnalyzer{},
"ReplicaSet": ReplicaSetAnalyzer{},
"PersistentVolumeClaim": PvcAnalyzer{},
@@ -13,12 +18,12 @@ var coreAnalyzerMap = map[string]IAnalyzer{
"StatefulSet": StatefulSetAnalyzer{},
}
var additionalAnalyzerMap = map[string]IAnalyzer{
var additionalAnalyzerMap = map[string]common.IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
}
func ListFilters() ([]string, []string) {
func ListFilters() ([]string, []string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
@@ -28,12 +33,28 @@ func ListFilters() ([]string, []string) {
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
}
return coreKeys, additionalKeys
integrationProvider := integration.NewIntegration()
var integrationAnalyzers []string
for _, i := range integrationProvider.List() {
b, _ := integrationProvider.IsActivate(i)
if b {
in, err := integrationProvider.Get(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
integrationAnalyzers = append(integrationAnalyzers, in.GetAnalyzerName())
}
}
return coreKeys, additionalKeys, integrationAnalyzers
}
func GetAnalyzerMap() map[string]IAnalyzer {
func GetAnalyzerMap() map[string]common.IAnalyzer {
mergedMap := make(map[string]IAnalyzer)
mergedMap := make(map[string]common.IAnalyzer)
// add core analyzer
for key, value := range coreAnalyzerMap {
@@ -45,5 +66,23 @@ func GetAnalyzerMap() map[string]IAnalyzer {
mergedMap[key] = value
}
integrationProvider := integration.NewIntegration()
for _, i := range integrationProvider.List() {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
if b {
in, err := integrationProvider.Get(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
in.AddAnalyzer(&mergedMap)
}
}
return mergedMap
}

View File

@@ -2,6 +2,7 @@ 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"

View File

@@ -3,23 +3,24 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type HpaAnalyzer struct{}
func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, hpa := range list.Items {
var failures []Failure
var failures []common.Failure
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
@@ -47,16 +48,16 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
scaleTargetRefNotFound = true
}
default:
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which is not an option.", scaleTargetRef.Kind),
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
if scaleTargetRefNotFound {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: scaleTargetRef.Name,
Masked: util.MaskString(scaleTargetRef.Name),
@@ -66,7 +67,7 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
@@ -75,7 +76,7 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "HorizontalPodAutoscaler",
Name: key,
Error: value.FailureDetails,

View File

@@ -5,6 +5,7 @@ import (
"strings"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
@@ -23,7 +24,7 @@ func TestHPAAnalyzer(t *testing.T) {
},
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -55,7 +56,7 @@ func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
},
)
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -86,7 +87,7 @@ func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -133,7 +134,7 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -188,7 +189,7 @@ func TestHPAAnalyzerWithExistingScaleTargetRef(t *testing.T) {
)
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -3,32 +3,33 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type IngressAnalyzer struct{}
func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ing := range list.Items {
var failures []Failure
var failures []common.Failure
// get ingressClassName
ingressClassName := ing.Spec.IngressClassName
if ingressClassName == nil {
ingClassValue := ing.Annotations["kubernetes.io/ingress.class"]
if ingClassValue == "" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
@@ -48,9 +49,9 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if ingressClassName != nil {
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: *ingressClassName,
Masked: util.MaskString(*ingressClassName),
@@ -66,9 +67,9 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
for _, path := range rule.HTTP.Paths {
_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})
if err != nil {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
@@ -86,9 +87,9 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
for _, tls := range ing.Spec.TLS {
_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})
if err != nil {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
@@ -102,7 +103,7 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
@@ -111,7 +112,7 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Ingress",
Name: key,
Error: value.FailureDetails,

View File

@@ -5,6 +5,7 @@ import (
"strings"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
networkingv1 "k8s.io/api/networking/v1"
@@ -23,7 +24,7 @@ func TestIngressAnalyzer(t *testing.T) {
})
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -56,7 +57,7 @@ func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
)
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -83,7 +84,7 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
})
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -3,23 +3,24 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PdbAnalyzer struct{}
func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pdb := range list.Items {
var failures []Failure
var failures []common.Failure
evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name)
if err != nil || evt == nil {
@@ -29,9 +30,9 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if evt.Reason == "NoPods" && evt.Message != "" {
if pdb.Spec.Selector != nil {
for k, v := range pdb.Spec.Selector.MatchLabels {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: k,
Masked: util.MaskString(k),
@@ -44,21 +45,21 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
})
}
for _, v := range pdb.Spec.Selector.MatchExpressions {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, expected expression %s", evt.Message, v),
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
} else {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, selector is nil", evt.Message),
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = common.PreAnalysis{
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
@@ -66,7 +67,7 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "PodDisruptionBudget",
Name: key,
Error: value.FailureDetails,

View File

@@ -3,6 +3,7 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -10,16 +11,16 @@ import (
type PodAnalyzer struct {
}
func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pod := range list.Items {
var failures []Failure
var failures []common.Failure
// Check for pending pods
if pod.Status.Phase == "Pending" {
@@ -27,9 +28,9 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
for _, containerStatus := range pod.Status.Conditions {
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
if containerStatus.Message != "" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: containerStatus.Message,
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
@@ -41,9 +42,9 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if containerStatus.State.Waiting != nil {
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
if containerStatus.State.Waiting.Message != "" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: containerStatus.State.Waiting.Message,
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
@@ -56,16 +57,16 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
continue
}
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
Pod: pod,
FailureDetails: failures,
}
@@ -73,7 +74,7 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Pod",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
@@ -31,7 +32,7 @@ func TestPodAnalyzer(t *testing.T) {
},
})
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -39,7 +40,7 @@ func TestPodAnalyzer(t *testing.T) {
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []Result
var analysisResults []common.Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil {
t.Error(err)

View File

@@ -3,13 +3,14 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PvcAnalyzer struct{}
func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -17,10 +18,10 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pvc := range list.Items {
var failures []Failure
var failures []common.Failure
// Check for empty rs
if pvc.Status.Phase == "Pending" {
@@ -31,14 +32,14 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
continue
}
if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
@@ -46,7 +47,7 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "PersistentVolumeClaim",
Name: key,
Error: value.FailureDetails,

View File

@@ -3,13 +3,14 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ReplicaSetAnalyzer struct{}
func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -17,10 +18,10 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, rs := range list.Items {
var failures []Failure
var failures []common.Failure
// Check for empty rs
if rs.Status.Replicas == 0 {
@@ -28,16 +29,16 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
// Check through container status to check for crashes
for _, rsStatus := range rs.Status.Conditions {
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: rsStatus.Message,
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
ReplicaSet: rs,
FailureDetails: failures,
}
@@ -45,7 +46,7 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "ReplicaSet",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,13 +4,14 @@ import (
"fmt"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ServiceAnalyzer struct{}
func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -18,10 +19,10 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ep := range list.Items {
var failures []Failure
var failures []common.Failure
// Check for empty service
if len(ep.Subsets) == 0 {
@@ -32,9 +33,9 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for k, v := range svc.Spec.Selector {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: k,
Masked: util.MaskString(k),
@@ -57,16 +58,16 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
count++
pods = append(pods, addresses.TargetRef.Kind+"/"+addresses.TargetRef.Name)
}
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count),
Sensitive: []Sensitive{},
Sensitive: []common.Sensitive{},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = common.PreAnalysis{
Endpoint: ep,
FailureDetails: failures,
}
@@ -74,7 +75,7 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Service",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
@@ -32,7 +33,7 @@ func TestServiceAnalyzer(t *testing.T) {
},
}})
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -3,29 +3,30 @@ package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type StatefulSetAnalyzer struct{}
func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, sts := range list.Items {
var failures []Failure
var failures []common.Failure
// get serviceName
serviceName := sts.Spec.ServiceName
_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})
if err != nil {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: sts.Namespace,
Masked: util.MaskString(sts.Namespace),
@@ -42,9 +43,9 @@ func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if volumeClaimTemplate.Spec.StorageClassName != nil {
_, err := a.Client.GetClient().StorageV1().StorageClasses().Get(a.Context, *volumeClaimTemplate.Spec.StorageClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, Failure{
failures = append(failures, common.Failure{
Text: fmt.Sprintf("StatefulSet uses the storage class %s which does not exist.", *volumeClaimTemplate.Spec.StorageClassName),
Sensitive: []Sensitive{
Sensitive: []common.Sensitive{
{
Unmasked: *volumeClaimTemplate.Spec.StorageClassName,
Masked: util.MaskString(*volumeClaimTemplate.Spec.StorageClassName),
@@ -56,7 +57,7 @@ func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = common.PreAnalysis{
StatefulSet: sts,
FailureDetails: failures,
}
@@ -64,7 +65,7 @@ func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "StatefulSet",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
@@ -23,7 +24,7 @@ func TestStatefulSetAnalyzer(t *testing.T) {
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -50,7 +51,7 @@ func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -115,7 +116,7 @@ func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -1,8 +1,9 @@
package analyzer
package common
import (
"context"
trivy "github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
appsv1 "k8s.io/api/apps/v1"
@@ -12,6 +13,10 @@ import (
policyv1 "k8s.io/api/policy/v1"
)
type IAnalyzer interface {
Analyze(analysis Analyzer) ([]Result, error)
}
type Analyzer struct {
Client *kubernetes.Client
Context context.Context
@@ -31,6 +36,8 @@ type PreAnalysis struct {
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
StatefulSet appsv1.StatefulSet
// Integrations
TrivyVulnerabilityReport trivy.VulnerabilityReport
}
type Result struct {

View File

@@ -0,0 +1,113 @@
package integration
import (
"errors"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
"github.com/spf13/viper"
)
type IIntegration interface {
// Add adds an integration to the cluster
Deploy(namespace string) error
// Remove removes an integration from the cluster
UnDeploy(namespace string) error
//
AddAnalyzer(*map[string]common.IAnalyzer)
// RemoveAnalyzer removes an analyzer from the cluster
RemoveAnalyzer() error
GetAnalyzerName() string
IsActivate() bool
}
type Integration struct {
}
var integrations = map[string]IIntegration{
"trivy": trivy.NewTrivy(),
}
func NewIntegration() *Integration {
return &Integration{}
}
func (*Integration) List() []string {
keys := make([]string, 0, len(integrations))
for k := range integrations {
keys = append(keys, k)
}
return keys
}
func (*Integration) Get(name string) (IIntegration, error) {
if _, ok := integrations[name]; !ok {
return nil, errors.New("integration not found")
}
return integrations[name], nil
}
func (*Integration) Activate(name string, namespace string) error {
if _, ok := integrations[name]; !ok {
return errors.New("integration not found")
}
if err := integrations[name].Deploy(namespace); err != nil {
return err
}
// Update filters
activeFilters := viper.GetStringSlice("active_filters")
activeFilters = append(activeFilters, integrations[name].GetAnalyzerName())
viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
return nil
}
func (*Integration) Deactivate(name string, namespace string) error {
if _, ok := integrations[name]; !ok {
return errors.New("integration not found")
}
if err := integrations[name].UnDeploy(namespace); err != nil {
return err
}
// Update filters
// This might be a bad idea, but we cannot reference analyzer here
activeFilters := viper.GetStringSlice("active_filters")
// Remove filter
for i, v := range activeFilters {
if v == integrations[name].GetAnalyzerName() {
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
break
}
}
viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
return nil
}
func (*Integration) IsActivate(name string) (bool, error) {
if _, ok := integrations[name]; !ok {
return false, errors.New("integration not found")
}
return integrations[name].IsActivate(), nil
}

View File

@@ -0,0 +1,74 @@
package trivy
import (
"fmt"
"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"k8s.io/client-go/rest"
)
type TrivyAnalyzer struct {
}
func (TrivyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// Get all trivy VulnerabilityReports
result := &v1alpha1.VulnerabilityReportList{}
config := a.Client.GetConfig()
// Add group version to sceheme
config.ContentConfig.GroupVersion = &v1alpha1.SchemeGroupVersion
config.UserAgent = rest.DefaultKubernetesUserAgent()
config.APIPath = "/apis"
restClient, err := rest.UnversionedRESTClientFor(config)
if err != nil {
return nil, err
}
err = restClient.Get().Resource("vulnerabilityreports").Do(a.Context).Into(result)
if err != nil {
return nil, err
}
// Find criticals and get CVE
var preAnalysis = map[string]common.PreAnalysis{}
for _, report := range result.Items {
// For each pod there may be multiple vulnerabilities
var failures []common.Failure
for _, vuln := range report.Report.Vulnerabilities {
if vuln.Severity == "CRITICAL" {
// get the vulnerability ID
// get the vulnerability description
failures = append(failures, common.Failure{
Text: fmt.Sprintf("critical Vulnerability found ID: %s (learn more at: %s)", vuln.VulnerabilityID, vuln.PrimaryLink),
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", report.Labels["trivy-operator.resource.namespace"],
report.Labels["trivy-operator.resource.name"])] = common.PreAnalysis{
TrivyVulnerabilityReport: report,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "VulnerabilityReport",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.TrivyVulnerabilityReport.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,103 @@
package trivy
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
helmclient "github.com/mittwald/go-helm-client"
"helm.sh/helm/v3/pkg/repo"
)
const (
Repo = "https://aquasecurity.github.io/helm-charts/"
Version = "0.13.0"
ChartName = "trivy-operator"
RepoShortName = "aqua"
ReleaseName = "trivy-operator-k8sgpt"
)
type Trivy struct {
helm helmclient.Client
}
func NewTrivy() *Trivy {
helmClient, err := helmclient.New(&helmclient.Options{})
if err != nil {
panic(err)
}
return &Trivy{
helm: helmClient,
}
}
func (t *Trivy) GetAnalyzerName() string {
return "VulnerabilityReport"
}
func (t *Trivy) Deploy(namespace string) error {
// Add the repository
chartRepo := repo.Entry{
Name: RepoShortName,
URL: Repo,
}
// Add a chart-repository to the client.
if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil {
panic(err)
}
chartSpec := helmclient.ChartSpec{
ReleaseName: ReleaseName,
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
Namespace: namespace,
UpgradeCRDs: true,
Wait: false,
Timeout: 300,
}
// Install a chart release.
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
if _, err := t.helm.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {
return err
}
return nil
}
func (t *Trivy) UnDeploy(namespace string) error {
chartSpec := helmclient.ChartSpec{
ReleaseName: ReleaseName,
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
Namespace: namespace,
UpgradeCRDs: true,
Wait: false,
Timeout: 300,
}
// Uninstall the chart release.
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
if err := t.helm.UninstallRelease(&chartSpec); err != nil {
return err
}
return nil
}
func (t *Trivy) IsActivate() bool {
if _, err := t.helm.GetRelease(ReleaseName); err != nil {
return false
}
return true
}
func (t *Trivy) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
(*mergedMap)["VulnerabilityReport"] = &TrivyAnalyzer{}
}
func (t *Trivy) RemoveAnalyzer() error {
return nil
}

View File

@@ -1,18 +1,31 @@
package kubernetes
import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/scheme"
)
type Client struct {
Client kubernetes.Interface
Client kubernetes.Interface
RestClient rest.Interface
Config *rest.Config
}
func (c *Client) GetConfig() *rest.Config {
return c.Config
}
func (c *Client) GetClient() kubernetes.Interface {
return c.Client
}
func (c *Client) GetRestClient() rest.Interface {
return c.RestClient
}
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
@@ -29,8 +42,18 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
if err != nil {
return nil, err
}
c.APIPath = "/api"
c.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
c.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
restClient, err := rest.RESTClientFor(c)
if err != nil {
return nil, err
}
return &Client{
Client: clientSet,
Client: clientSet,
RestClient: restClient,
Config: c,
}, nil
}

View File

@@ -13,6 +13,15 @@ import (
var anonymizePattern = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;':\",./<>?")
func SliceContainsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) {
if meta.OwnerReferences != nil {
for _, owner := range meta.OwnerReferences {