Compare commits

...

24 Commits

Author SHA1 Message Date
Thomas Schuetz
b985697e82 feat: temporarily changed image
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:52:33 +02:00
Thomas Schuetz
cafd4fce17 feat: make secret optional
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:39:55 +02:00
Thomas Schuetz
ec9c7ba0c3 feat: make secret optional
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:31:01 +02:00
Thomas Schuetz
fa6f086331 feat: reduced resources for service
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-15 20:07:19 +02:00
Alex Jones
0f88edf4e3 Merge pull request #280 from k8sgpt-ai/feat/manifests 2023-04-15 18:19:12 +01:00
Alex Jones
e5a8c57877 Merge branch 'main' into feat/manifests 2023-04-15 18:06:12 +01:00
Alex Jones
842f08c655 feat: running in cluster
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
3988eb2fd0 feat: running in cluster
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
f0a0c9aebf chore: updated
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
ec2e7703c6 adding manifest example
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Matthis
a3becc9906 feat: add server metrics (#273)
* feat: expose metrics path & init analyzer errors metrics

This commit add metrics path & the analyzer error metrics in the codebase. The changes have been made across all analyzers and include the addition of a new metric with label values for the analyzer's name, analyzed object's name, and namespace. The metric's value is set to the length of the analyzer objects failures.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: add metric to cronjob & deployment & netpol

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: expose metric to NodeAnalyzer

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

---------

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-15 19:03:37 +02:00
Alex Jones
ffde363588 Merge branch 'main' into feat/manifests 2023-04-15 16:59:13 +01:00
Alex Jones
03a95e7b2a Merge pull request #282 from matthisholleville/fix/duplicated-vulnerabilityreport-filters
fix: resolve issue with duplicated integration filters.
2023-04-15 16:28:16 +01:00
Alex Jones
56a323c129 Merge branch 'main' into fix/duplicated-vulnerabilityreport-filters 2023-04-15 16:28:11 +01:00
Alex Jones
f20c139b1c Merge pull request #281 from k8sgpt-ai/feat/server-envs
feat: envs to initialise server
2023-04-15 16:27:33 +01:00
Matthis Holleville
960ba568d0 fix: resolve issue with duplicated integration filters.
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-15 15:49:39 +02:00
Alex Jones
0071e25992 feat: envs to initialise server
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 12:26:51 +01:00
Alex Jones
fe2c08cf72 feat: wip blocked until we have envs
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 12:08:43 +01:00
renovate[bot]
51b1b352ac fix(deps): update module github.com/sashabaranov/go-openai to v1.8.0 (#277)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-15 07:57:50 +02:00
Alex Jones
bbf159455a Merge pull request #274 from matthisholleville/fix/server-authentication
fix: use the aiProvider object when launching the server
2023-04-14 16:33:25 +01:00
Matthis Holleville
e7076ed609 fix: use the aiProvider object when launching the server instead of the deprecated configuration keys
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-14 17:23:12 +02:00
Dominik Augustin
6247a1c0f3 feat: add node analyzer (#272)
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-14 15:08:47 +02:00
Alex Jones
a8f8070e16 Merge pull request #270 from nunoadrego/fix/version
fix: add new line after version cmd output
2023-04-14 12:12:18 +01:00
Nuno Adrego
92e7b3d3fb fix: add new line after version cmd output
Signed-off-by: Nuno Adrego <55922671+nunoadrego@users.noreply.github.com>
2023-04-14 11:41:55 +01:00
35 changed files with 524 additions and 65 deletions

16
Makefile Normal file
View File

@@ -0,0 +1,16 @@
.PHONY: docker-build
IMG ?= ghcr.io/k8sgpt-ai/k8sgpt:latest
deploy:
ifndef SECRET
$(error SECRET environment variable is not set)
endif
kubectl create ns k8sgpt || true
kubectl create secret generic ai-backend-secret --from-literal=secret-key=$(SECRET) --namespace=k8sgpt || true
kubectl apply -f container/manifests
undeploy:
kubectl delete secret ai-backend-secret --namespace=k8sgpt
kubectl delete -f container/manifests
kubectl delete ns k8sgpt
docker-build:
docker buildx build --build-arg=VERSION="$$(git describe --tags --abbrev=0)" --build-arg=COMMIT="$$(git rev-parse --short HEAD)" --build-arg DATE="$$(date +%FT%TZ)" --platform="linux/amd64,linux/arm64" -t ${IMG} -f container/Dockerfile . --push

View File

@@ -143,6 +143,7 @@ you will be able to write your own analyzers.
- [x] statefulSetAnalyzer
- [x] deploymentAnalyzer
- [x] cronJobAnalyzer
- [x] nodeAnalyzer
#### Optional

View File

@@ -1,10 +1,11 @@
package cmd
import (
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"os"
"path/filepath"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
@@ -81,6 +82,7 @@ func initConfig() {
viper.Set("kubecontext", kubecontext)
viper.Set("kubeconfig", kubeconfig)
viper.SetEnvPrefix("K8SGPT")
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.

View File

@@ -1,12 +1,13 @@
package serve
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
k8sgptserver "github.com/k8sgpt-ai/k8sgpt/pkg/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var (
@@ -21,30 +22,60 @@ var ServeCmd = &cobra.Command{
Long: `Runs k8sgpt as a server to allow for easy integration with other applications.`,
Run: func(cmd *cobra.Command, args []string) {
backendType := viper.GetString("backend_type")
if backendType == "" {
color.Red("No backend set. Please run k8sgpt auth")
var configAI ai.AIConfiguration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
var aiProvider *ai.AIProvider
if len(configAI.Providers) == 0 {
// Check for env injection
backend = os.Getenv("K8SGPT_BACKEND")
password := os.Getenv("K8SGPT_PASSWORD")
model := os.Getenv("K8SGPT_MODEL")
// If the envs are set, alocate in place to the aiProvider
// else exit with error
if backend != "" || password != "" || model != "" {
aiProvider = &ai.AIProvider{
Name: backend,
Password: password,
Model: model,
}
if backend != "" {
backendType = backend
configAI.Providers = append(configAI.Providers, *aiProvider)
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
} else {
color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth")
os.Exit(1)
}
}
if aiProvider == nil {
for _, provider := range configAI.Providers {
if backend == provider.Name {
aiProvider = &provider
break
}
}
}
token := viper.GetString(fmt.Sprintf("%s_key", backendType))
// check if nil
if token == "" {
color.Red("No %s key set. Please run k8sgpt auth", backendType)
if aiProvider.Name == "" {
color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
os.Exit(1)
}
server := k8sgptserver.Config{
Backend: backend,
Backend: aiProvider.Name,
Port: port,
Token: token,
Token: aiProvider.Password,
}
err := server.Serve()
err = server.Serve()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)

View File

@@ -10,7 +10,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version number of k8sgpt",
Long: `All software has versions. This is k8sgpt's`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Printf("k8sgpt version %s", version)
cmd.Printf("k8sgpt version %s\n", version)
},
}

View File

@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8sgpt-deployment
namespace: k8sgpt
labels:
app: k8sgpt
spec:
replicas: 1
selector:
matchLabels:
app: k8sgpt
template:
metadata:
labels:
app: k8sgpt
spec:
serviceAccountName: k8sgpt
containers:
- name: k8sgpt-container
imagePullPolicy: Always
image: ghcr.io/k8sgpt-ai/k8sgpt:dev-202304151719 #x-release-please-version
ports:
- containerPort: 8080
args: ["serve"]
resources:
limits:
cpu: "0.5"
memory: "256Mi"
requests:
cpu: "0.125"
memory: "64Mi"
env:
- name: K8SGPT_MODEL
value: "gpt-3.5-turbo"
- name: K8SGPT_BACKEND
value: "openai"
- name: K8SGPT_PASSWORD
valueFrom:
secretKeyRef:
name: ai-backend-secret
key: secret-key

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8sgpt-cluster-role-all
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- get
- list
- watch

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k8sgpt-rolebinding
namespace: k8sgpt
subjects:
- kind: ServiceAccount
name: k8sgpt
namespace: k8sgpt
roleRef:
kind: ClusterRole
name: k8sgpt-cluster-role-all
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8sgpt
namespace: k8sgpt

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: k8sgpt-service
namespace: k8sgpt
spec:
selector:
app: k8sgpt
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP

11
demo Executable file
View File

@@ -0,0 +1,11 @@
. demo-magic.sh
clear
pe "k8sgpt filter list"
pe "k8sgpt analyze --filter=Pod --explain -o json | jq ."
pe "k8sgpt integration list"
pe "k8sgpt integration activate trivy"
pe "k8sgpt filter list"
pe "k8sgpt analyze --filter=VulnerabilityReport"
pe "./k8sgpt analyze --filter=Node --explain"

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
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/sashabaranov/go-openai v1.8.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0

4
go.sum
View File

@@ -623,8 +623,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sashabaranov/go-openai v1.7.0 h1:D1dBXoZhtf/aKNu6WFf0c7Ah2NM30PZ/3Mqly6cZ7fk=
github.com/sashabaranov/go-openai v1.7.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.8.0 h1:IZrNK/gGqxtp0j19F4NLGbmfoOkyDpM3oC9i/tv9bBM=
github.com/sashabaranov/go-openai v1.8.0/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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=

BIN
images/demo5.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
images/nodes.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -7,6 +7,15 @@ import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
AnalyzerErrorsMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "analyzer_errors",
Help: "Number of errors detected by analyzer",
}, []string{"analyzer_name", "object_name", "namespace"})
)
var coreAnalyzerMap = map[string]common.IAnalyzer{
@@ -18,6 +27,7 @@ var coreAnalyzerMap = map[string]common.IAnalyzer{
"Ingress": IngressAnalyzer{},
"StatefulSet": StatefulSetAnalyzer{},
"CronJob": CronJobAnalyzer{},
"Node": NodeAnalyzer{},
}
var additionalAnalyzerMap = map[string]common.IAnalyzer{

View File

@@ -13,6 +13,13 @@ import (
type CronJobAnalyzer struct{}
func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "CronJob"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
var results []common.Result
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs("").List(a.Context, v1.ListOptions{})
@@ -81,14 +88,16 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
}
if len(failures) > 0 {
preAnalysis[cronJob.Name] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", cronJob.Namespace, cronJob.Name)] = common.PreAnalysis{
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, cronJob.Name, cronJob.Namespace).Set(float64(len(failures)))
}
for key, value := range preAnalysis {
currentAnalysis := common.Result{
Kind: "CronJob",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -121,6 +121,6 @@ func TestCronJobBroken(t *testing.T) {
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Name, "example-cronjob")
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
assert.Equal(t, analysisResults[0].Kind, "CronJob")
}

View File

@@ -17,6 +17,12 @@ type DeploymentAnalyzer struct {
// Analyze scans all namespaces for Deployments with misconfigurations
func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Deployment"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
deployments, err := a.Client.GetClient().AppsV1().Deployments("").List(context.Background(), v1.ListOptions{})
if err != nil {
return nil, err
@@ -44,13 +50,14 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
FailureDetails: failures,
Deployment: deployment,
}
AnalyzerErrorsMetric.WithLabelValues(kind, deployment.Name, deployment.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "Deployment",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -12,6 +12,12 @@ type HpaAnalyzer struct{}
func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "HorizontalPodAutoscaler"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
@@ -71,13 +77,14 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, hpa.Name, hpa.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "HorizontalPodAutoscaler",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -12,6 +12,12 @@ type IngressAnalyzer struct{}
func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Ingress"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
@@ -107,13 +113,15 @@ func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
Ingress: ing,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, ing.Name, ing.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "Ingress",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -11,6 +11,13 @@ import (
type NetworkPolicyAnalyzer struct{}
func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "NetworkPolicy"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// get all network policies in the namespace
policies, err := a.Client.GetClient().NetworkingV1().
NetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -54,16 +61,18 @@ func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
}
if len(failures) > 0 {
preAnalysis[policy.Name] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", policy.Namespace, policy.Name)] = common.PreAnalysis{
FailureDetails: failures,
NetworkPolicy: policy,
}
AnalyzerErrorsMetric.WithLabelValues(kind, policy.Name, policy.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
currentAnalysis := common.Result{
Kind: "NetworkPolicy",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

83
pkg/analyzer/node.go Normal file
View File

@@ -0,0 +1,83 @@
package analyzer
import (
"fmt"
v1 "k8s.io/api/core/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type NodeAnalyzer struct{}
func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Node"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, node := range list.Items {
var failures []common.Failure
for _, nodeCondition := range node.Status.Conditions {
// https://kubernetes.io/docs/concepts/architecture/nodes/#condition
switch nodeCondition.Type {
case v1.NodeReady:
if nodeCondition.Status == v1.ConditionTrue {
break
}
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
default:
if nodeCondition.Status != v1.ConditionFalse {
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s", node.Name)] = common.PreAnalysis{
Node: node,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, node.Name, "").Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Node.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, err
}
func addNodeConditionFailure(failures []common.Failure, nodeName string, nodeCondition v1.NodeCondition) []common.Failure {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s has condition of type %s, reason %s: %s", nodeName, nodeCondition.Type, nodeCondition.Reason, nodeCondition.Message),
Sensitive: []common.Sensitive{
{
Unmasked: nodeName,
Masked: util.MaskString(nodeName),
},
},
})
return failures
}

111
pkg/analyzer/node_test.go Normal file
View File

@@ -0,0 +1,111 @@
package analyzer
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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestNodeAnalyzerNodeReady(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: v1.NodeReady,
Status: v1.ConditionTrue,
Reason: "KubeletReady",
Message: "kubelet is posting ready status",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 0)
}
func TestNodeAnalyzerNodeDiskPressure(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: v1.NodeDiskPressure,
Status: v1.ConditionTrue,
Reason: "KubeletHasDiskPressure",
Message: "kubelet has disk pressure",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
// A cloud provider may set their own condition and/or a new status might be introduced
// In such cases a failure is assumed and the code shouldn't break, although it might be a false positive
func TestNodeAnalyzerNodeUnknownType(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: "UnknownNodeConditionType",
Status: "CompletelyUnknown",
Reason: "KubeletHasTheUnknown",
Message: "kubelet has the unknown",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

View File

@@ -12,6 +12,12 @@ type PdbAnalyzer struct{}
func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "PodDisruptionBudget"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
@@ -63,12 +69,13 @@ func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pdb.Name, pdb.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "PodDisruptionBudget",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -12,6 +12,13 @@ type PodAnalyzer struct {
}
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Pod"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// 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 {
@@ -70,12 +77,13 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
Pod: pod,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "Pod",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -12,6 +12,12 @@ type PvcAnalyzer struct{}
func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "PersistentVolumeClaim"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
@@ -43,12 +49,13 @@ func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pvc.Name, pvc.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "PersistentVolumeClaim",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -12,6 +12,12 @@ type ReplicaSetAnalyzer struct{}
func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "ReplicaSet"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
@@ -42,12 +48,13 @@ func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
ReplicaSet: rs,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, rs.Name, rs.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "ReplicaSet",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -13,6 +13,12 @@ type ServiceAnalyzer struct{}
func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Service"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
@@ -71,12 +77,13 @@ func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
Endpoint: ep,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, ep.Name, ep.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "Service",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -11,6 +11,13 @@ import (
type StatefulSetAnalyzer struct{}
func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "StatefulSet"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
@@ -61,12 +68,13 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
StatefulSet: sts,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, sts.Name, sts.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "StatefulSet",
Kind: kind,
Name: key,
Error: value.FailureDetails,
}

View File

@@ -38,6 +38,7 @@ type PreAnalysis struct {
PodDisruptionBudget policyv1.PodDisruptionBudget
StatefulSet appsv1.StatefulSet
NetworkPolicy networkv1.NetworkPolicy
Node v1.Node
// Integrations
TrivyVulnerabilityReport trivy.VulnerabilityReport
}

View File

@@ -3,10 +3,12 @@ package integration
import (
"errors"
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/viper"
)
@@ -56,16 +58,24 @@ func (*Integration) Activate(name string, namespace string) error {
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())
mergedFilters := append(activeFilters, integrations[name].GetAnalyzerName())
viper.Set("active_filters", activeFilters)
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
// Verify dupplicate
if len(dupplicatedFilters) != 0 {
color.Red("Integration already activated : %s", strings.Join(dupplicatedFilters, ", "))
os.Exit(1)
}
viper.Set("active_filters", uniqueFilters)
if err := integrations[name].Deploy(namespace); err != nil {
return err
}
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
@@ -80,21 +90,27 @@ func (*Integration) Deactivate(name string, namespace string) error {
return errors.New("integration not found")
}
if err := integrations[name].UnDeploy(namespace); err != nil {
return err
}
activeFilters := viper.GetStringSlice("active_filters")
// Update filters
// This might be a bad idea, but we cannot reference analyzer here
activeFilters := viper.GetStringSlice("active_filters")
// Remove filter
foundFilter := false
for i, v := range activeFilters {
if v == integrations[name].GetAnalyzerName() {
foundFilter = true
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
break
}
}
if !foundFilter {
color.Red("Ingregation %s does not exist in configuration file. Please use k8sgpt integration add.", name)
os.Exit(1)
}
if err := integrations[name].UnDeploy(namespace); err != nil {
return err
}
viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil {

View File

@@ -28,26 +28,29 @@ func (c *Client) GetRestClient() rest.Interface {
}
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{
CurrentContext: kubecontext,
})
// create the clientset
c, err := config.ClientConfig()
var config *rest.Config
config, err := rest.InClusterConfig()
if err != nil {
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{
CurrentContext: kubecontext,
})
// create the clientset
config, err = clientConfig.ClientConfig()
if err != nil {
return nil, err
}
}
clientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
clientSet, err := kubernetes.NewForConfig(c)
if err != nil {
return nil, err
}
c.APIPath = "/api"
c.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
c.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
config.APIPath = "/api"
config.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
restClient, err := rest.RESTClientFor(c)
restClient, err := rest.RESTClientFor(config)
if err != nil {
return nil, err
}
@@ -55,6 +58,6 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
return &Client{
Client: clientSet,
RestClient: restClient,
Config: c,
Config: config,
}, nil
}

View File

@@ -3,11 +3,13 @@ package server
import (
json "encoding/json"
"fmt"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"net/http"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Config struct {
@@ -74,6 +76,7 @@ func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) {
}
func (s *Config) Serve() error {
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/analyze", s.analyzeHandler)
http.HandleFunc("/healthz", s.healthzHandler)
color.Green("Starting server on port %s", s.Port)

View File

@@ -11,7 +11,8 @@
"README.md",
"deploy/manifest.yaml",
"chart/Chart.yaml",
"chart/values.yaml"
"chart/values.yaml",
"container/manifests/deployment.yaml"
],
"changelog-sections": [
{