From cf67dc2a16ad182bc2b112b1a4b71056d3079e67 Mon Sep 17 00:00:00 2001 From: David Sabatie Date: Tue, 23 May 2023 22:44:26 +0200 Subject: [PATCH] feat: get official field doc Signed-off-by: David Sabatie --- go.mod | 3 +- go.sum | 3 ++ pkg/analyzer/statefulset.go | 13 ++++++- pkg/kubernetes/kubernetes.go | 20 +++++++--- pkg/util/k8sapireference.go | 75 ++++++++++++++++++++++++++++++++++++ pkg/util/types.go | 9 +++++ 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 pkg/util/k8sapireference.go create mode 100644 pkg/util/types.go diff --git a/go.mod b/go.mod index 791d89e3b1..fbfc042bb2 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230515081240-6b5b845c638e.1 buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230514071713-3d78cb8bbc06.1 github.com/aws/aws-sdk-go v1.44.267 + github.com/go-resty/resty/v2 v2.7.0 ) require github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -152,7 +153,7 @@ require ( go.uber.org/zap v1.24.0 golang.org/x/crypto v0.7.0 // indirect golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 // indirect - golang.org/x/net v0.9.0 // indirect + golang.org/x/net v0.9.0 golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.8.0 // indirect diff --git a/go.sum b/go.sum index 53e9ea1a48..b0fce83e25 100644 --- a/go.sum +++ b/go.sum @@ -594,6 +594,8 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -1232,6 +1234,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= diff --git a/pkg/analyzer/statefulset.go b/pkg/analyzer/statefulset.go index 42e33cd9ee..63727fd9b4 100644 --- a/pkg/analyzer/statefulset.go +++ b/pkg/analyzer/statefulset.go @@ -26,6 +26,11 @@ type StatefulSetAnalyzer struct{} func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { kind := "StatefulSet" + apiDoc := util.K8sApiReference{ + Kind: kind, + ApiVersion: "apps/v1", + ServerVersion: a.Client.ServerVersion, + } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ "analyzer_name": kind, @@ -44,8 +49,14 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { serviceName := sts.Spec.ServiceName _, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{}) if err != nil { + doc, _ := apiDoc.GetApiDoc("serviceName") failures = append(failures, common.Failure{ - Text: fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName), + Text: fmt.Sprintf( + "StatefulSet uses the service %s/%s which does not exist.\n Official Doc: %s", + sts.Namespace, + serviceName, + doc, + ), Sensitive: []common.Sensitive{ { Unmasked: sts.Namespace, diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index 926fa8c98c..0cf87bd5f0 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -15,6 +15,7 @@ package kubernetes import ( "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" "k8s.io/client-go/rest" @@ -23,9 +24,10 @@ import ( ) type Client struct { - Client kubernetes.Interface - RestClient rest.Interface - Config *rest.Config + Client kubernetes.Interface + RestClient rest.Interface + Config *rest.Config + ServerVersion *version.Info } func (c *Client) GetConfig() *rest.Config { @@ -74,9 +76,15 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) { return nil, err } + serverVersion, err := clientSet.ServerVersion() + if err != nil { + return nil, err + } + return &Client{ - Client: clientSet, - RestClient: restClient, - Config: config, + Client: clientSet, + RestClient: restClient, + Config: config, + ServerVersion: serverVersion, }, nil } diff --git a/pkg/util/k8sapireference.go b/pkg/util/k8sapireference.go new file mode 100644 index 0000000000..e46e83afe0 --- /dev/null +++ b/pkg/util/k8sapireference.go @@ -0,0 +1,75 @@ +package util + +import ( + "fmt" + "strings" + + "github.com/go-resty/resty/v2" + "golang.org/x/net/html" +) + +func (k *K8sApiReference) GetApiDoc(field string) (string, error) { + client := resty.New() + splitApiVersion := strings.Split( + strings.ToLower(k.ApiVersion), + "/", + ) + + resp, err := client.R(). + EnableTrace(). + Get( + fmt.Sprintf( + "https://kubernetes.io/docs/reference/generated/kubernetes-api/v%s.%s/#%sspec-%s-%s", + k.ServerVersion.Major, + k.ServerVersion.Minor, + strings.ToLower(k.Kind), + splitApiVersion[1], + splitApiVersion[0], + ), + ) + if err != nil { + fmt.Printf("%w", err) + } + + fieldDoc := "" + isFound := false + for _, line := range strings.Split(strings.TrimRight(string(resp.Body()), "\n"), "\n") { + if strings.Contains(line, field) { + tkn := html.NewTokenizer(strings.NewReader(line)) + isTd := false + + for { + tt := tkn.Next() + + switch tt { + case html.ErrorToken: + break + + case html.StartTagToken: + t := tkn.Token() + isTd = t.Data == "td" + + case html.TextToken: + t := tkn.Token() + + if isTd && t.Data != field { + fieldDoc = t.Data + isFound = true + } + + isTd = false + } + + if isFound { + break + } + } + } + + if isFound { + break + } + } + + return fieldDoc, nil +} diff --git a/pkg/util/types.go b/pkg/util/types.go new file mode 100644 index 0000000000..24d6fa170a --- /dev/null +++ b/pkg/util/types.go @@ -0,0 +1,9 @@ +package util + +import "k8s.io/apimachinery/pkg/version" + +type K8sApiReference struct { + ApiVersion string + Kind string + ServerVersion *version.Info +}