diff --git a/cmd/analyze/analyze.go b/cmd/analyze/analyze.go index 535c0d08f3..9f202d2e1a 100644 --- a/cmd/analyze/analyze.go +++ b/cmd/analyze/analyze.go @@ -32,6 +32,7 @@ var ( namespace string anonymize bool maxConcurrency int + withDoc bool ) // AnalyzeCmd represents the problems command @@ -45,7 +46,7 @@ var AnalyzeCmd = &cobra.Command{ // AnalysisResult configuration config, err := analysis.NewAnalysis(backend, - language, filters, namespace, nocache, explain, maxConcurrency) + language, filters, namespace, nocache, explain, maxConcurrency, withDoc) if err != nil { color.Red("Error: %v", err) os.Exit(1) @@ -91,4 +92,6 @@ func init() { AnalyzeCmd.Flags().StringVarP(&language, "language", "l", "english", "Languages to use for AI (e.g. 'English', 'Spanish', 'French', 'German', 'Italian', 'Portuguese', 'Dutch', 'Russian', 'Chinese', 'Japanese', 'Korean')") // add max concurrency AnalyzeCmd.Flags().IntVarP(&maxConcurrency, "max-concurrency", "m", 10, "Maximum number of concurrent requests to the Kubernetes API server") + // kubernetes doc flag + AnalyzeCmd.Flags().BoolVarP(&withDoc, "with-doc", "d", false, "Give me the official documentation of the involved field") } diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go index 8f7ce408bf..d70cf3f4ca 100644 --- a/pkg/analysis/analysis.go +++ b/pkg/analysis/analysis.go @@ -23,6 +23,7 @@ import ( "sync" "github.com/fatih/color" + openapi_v2 "github.com/google/gnostic/openapiv2" "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/analyzer" "github.com/k8sgpt-ai/k8sgpt/pkg/cache" @@ -45,6 +46,7 @@ type Analysis struct { Explain bool MaxConcurrency int AnalysisAIProvider string // The name of the AI Provider used for this analysis + WithDoc bool } type AnalysisStatus string @@ -63,7 +65,7 @@ type JsonOutput struct { Results []common.Result `json:"results"` } -func NewAnalysis(backend string, language string, filters []string, namespace string, noCache bool, explain bool, maxConcurrency int) (*Analysis, error) { +func NewAnalysis(backend string, language string, filters []string, namespace string, noCache bool, explain bool, maxConcurrency int, withDoc bool) (*Analysis, error) { var configAI ai.AIConfiguration err := viper.UnmarshalKey("ai", &configAI) if err != nil { @@ -128,6 +130,7 @@ func NewAnalysis(backend string, language string, filters []string, namespace st Explain: explain, MaxConcurrency: maxConcurrency, AnalysisAIProvider: backend, + WithDoc: withDoc, }, nil } @@ -136,11 +139,23 @@ func (a *Analysis) RunAnalysis() { coreAnalyzerMap, analyzerMap := analyzer.GetAnalyzerMap() + // we get the openapi schema from the server only if required by the flag "with-doc" + openapiSchema := &openapi_v2.Document{} + if a.WithDoc { + var openApiErr error + + openapiSchema, openApiErr = a.Client.Client.Discovery().OpenAPISchema() + if openApiErr != nil { + a.Errors = append(a.Errors, fmt.Sprintf("[KubernetesDoc] %s", openApiErr)) + } + } + analyzerConfig := common.Analyzer{ - Client: a.Client, - Context: a.Context, - Namespace: a.Namespace, - AIClient: a.AIClient, + Client: a.Client, + Context: a.Context, + Namespace: a.Namespace, + AIClient: a.AIClient, + OpenapiSchema: openapiSchema, } semaphore := make(chan struct{}, a.MaxConcurrency) diff --git a/pkg/analyzer/cronjob.go b/pkg/analyzer/cronjob.go index 741e29fefa..e2b4310680 100644 --- a/pkg/analyzer/cronjob.go +++ b/pkg/analyzer/cronjob.go @@ -36,7 +36,7 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err Group: "batch", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/deployment.go b/pkg/analyzer/deployment.go index 45ca5c0b91..d2d7b19ef1 100644 --- a/pkg/analyzer/deployment.go +++ b/pkg/analyzer/deployment.go @@ -39,7 +39,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) Group: "apps", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/hpa.go b/pkg/analyzer/hpa.go index f1be0f92b1..76e4a5ac00 100644 --- a/pkg/analyzer/hpa.go +++ b/pkg/analyzer/hpa.go @@ -36,7 +36,7 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { Group: "autoscaling", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/ingress.go b/pkg/analyzer/ingress.go index df13b4a2a9..bc4ba084fb 100644 --- a/pkg/analyzer/ingress.go +++ b/pkg/analyzer/ingress.go @@ -34,7 +34,7 @@ func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { Group: "networking", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/netpol.go b/pkg/analyzer/netpol.go index 2494e1c0f2..aeb302dcd8 100644 --- a/pkg/analyzer/netpol.go +++ b/pkg/analyzer/netpol.go @@ -34,7 +34,7 @@ func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) Group: "networking", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/pdb.go b/pkg/analyzer/pdb.go index 8f0a0da880..2bc0efee7e 100644 --- a/pkg/analyzer/pdb.go +++ b/pkg/analyzer/pdb.go @@ -34,7 +34,7 @@ func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { Group: "policy", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/service.go b/pkg/analyzer/service.go index 04fefe083e..9293a8d9de 100644 --- a/pkg/analyzer/service.go +++ b/pkg/analyzer/service.go @@ -35,7 +35,7 @@ func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { Group: "", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/analyzer/statefulset.go b/pkg/analyzer/statefulset.go index c9bcca7886..106c487c3d 100644 --- a/pkg/analyzer/statefulset.go +++ b/pkg/analyzer/statefulset.go @@ -34,7 +34,7 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { Group: "apps", Version: "v1", }, - Discovery: a.Client.Client.Discovery(), + OpenapiSchema: a.OpenapiSchema, } AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{ diff --git a/pkg/common/types.go b/pkg/common/types.go index 2e4d01ae15..35bd539442 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -17,6 +17,7 @@ import ( "context" trivy "github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1" + openapi_v2 "github.com/google/gnostic/openapiv2" "github.com/k8sgpt-ai/k8sgpt/pkg/ai" "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" appsv1 "k8s.io/api/apps/v1" @@ -31,12 +32,13 @@ type IAnalyzer interface { } type Analyzer struct { - Client *kubernetes.Client - Context context.Context - Namespace string - AIClient ai.IAI - PreAnalysis map[string]PreAnalysis - Results []Result + Client *kubernetes.Client + Context context.Context + Namespace string + AIClient ai.IAI + PreAnalysis map[string]PreAnalysis + Results []Result + OpenapiSchema *openapi_v2.Document } type PreAnalysis struct { diff --git a/pkg/kubernetes/apireference.go b/pkg/kubernetes/apireference.go index a7ef9edee2..40ae53d802 100644 --- a/pkg/kubernetes/apireference.go +++ b/pkg/kubernetes/apireference.go @@ -12,12 +12,14 @@ func (k *K8sApiReference) GetApiDocV2(field string) string { paths := strings.Split(field, ".") group := strings.Split(k.ApiVersion.Group, ".") - openapiSchema, err := k.Discovery.OpenAPISchema() - if err != nil { - return "" - } + // openapiSchema := openapi_v2.Document{} + + // openapiSchema, err := k.Discovery.OpenAPISchema() + // if err != nil { + // return "" + // } - definitions := openapiSchema.GetDefinitions().GetAdditionalProperties() + definitions := k.OpenapiSchema.GetDefinitions().GetAdditionalProperties() // extract the startpoint by searching the highest leaf corresponding to the requested group qnd kind for _, prop := range definitions { diff --git a/pkg/kubernetes/types.go b/pkg/kubernetes/types.go index 55633f5559..b97745a5b7 100644 --- a/pkg/kubernetes/types.go +++ b/pkg/kubernetes/types.go @@ -1,9 +1,9 @@ package kubernetes import ( + openapi_v2 "github.com/google/gnostic/openapiv2" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" - "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) @@ -16,8 +16,7 @@ type Client struct { } type K8sApiReference struct { - ApiVersion schema.GroupVersion - Kind string - // Property string - Discovery discovery.DiscoveryInterface + ApiVersion schema.GroupVersion + Kind string + OpenapiSchema *openapi_v2.Document } diff --git a/pkg/server/analyze.go b/pkg/server/analyze.go index ca5190c4d4..204f6ba803 100644 --- a/pkg/server/analyze.go +++ b/pkg/server/analyze.go @@ -32,6 +32,7 @@ func (h *handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) ( i.Nocache, i.Explain, int(i.MaxConcurrency), + false, // Kubernetes Doc disabled in server mode ) if err != nil { return &schemav1.AnalyzeResponse{}, err