diff --git a/cmd/diagnose/diagnose.go b/cmd/diagnose/diagnose.go index 6366bfb..18de355 100644 --- a/cmd/diagnose/diagnose.go +++ b/cmd/diagnose/diagnose.go @@ -1,55 +1,22 @@ package diagnose import ( - "bytes" - "fmt" - "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/yaml" "github.com/knight42/kopilot/cmd" ) func New(commonOpts cmd.Options) *cobra.Command { + opts := Options{ + common: commonOpts, + } c := &cobra.Command{ Use: "diagnose TYPE NAME", Short: "Diagnose a resource", SilenceUsage: true, Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - ns, _, _ := commonOpts.ToRawKubeConfigLoader().Namespace() - obj, err := commonOpts.NewBuilder(). - NamespaceParam(ns). - DefaultNamespace(). - WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). - ResourceNames(args[0], args[1]). - Do(). - Object() - if err != nil { - return fmt.Errorf("get object: %w", err) - } - - pruneObject(obj) - - // TODO: include logs or events - data, err := yaml.Marshal(obj) - if err != nil { - return fmt.Errorf("marshal object: %w", err) - } - - var buf bytes.Buffer - _ = promptDiagnose.Execute(&buf, templateData{ - Data: string(data), - Lang: commonOpts.Lang, - }) - - // TODO: limit the length of the prompt? - return commonOpts.NewChatGPTClient(" Diagnosing..."). - CreateCompletion(cmd.Context(), buf.String(), cmd.OutOrStdout()) - }, + RunE: opts.Run, } - flags := c.Flags() - commonOpts.AddFlags(flags) + opts.AddFlags(c.Flags()) return c } diff --git a/cmd/diagnose/options.go b/cmd/diagnose/options.go new file mode 100644 index 0000000..b0e5932 --- /dev/null +++ b/cmd/diagnose/options.go @@ -0,0 +1,74 @@ +package diagnose + +import ( + "bytes" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/yaml" + + "github.com/knight42/kopilot/cmd" +) + +type Options struct { + common cmd.Options + + FullObject bool +} + +func (o *Options) AddFlags(flags *pflag.FlagSet) { + o.common.AddFlags(flags) + + flags.BoolVar(&o.FullObject, "full-object", false, "Include the full object in prompt. Note that the GPT 3.5 modal can only handle 4097 tokens, including the full object might exceed the limit.") +} + +func (o *Options) Run(cmd *cobra.Command, args []string) error { + ns, _, _ := o.common.ToRawKubeConfigLoader().Namespace() + obj, err := o.common.NewBuilder(). + NamespaceParam(ns). + DefaultNamespace(). + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + ResourceNames(args[0], args[1]). + Do(). + Object() + if err != nil { + return fmt.Errorf("get object: %w", err) + } + + data := o.marshalObject(obj) + var buf bytes.Buffer + _ = promptDiagnose.Execute(&buf, templateData{ + // TODO: include logs or events + Data: data, + Lang: o.common.Lang, + }) + + return o.common.NewChatGPTClient(" Diagnosing..."). + CreateCompletion(cmd.Context(), buf.String(), cmd.OutOrStdout()) +} + +func (o *Options) marshalObject(obj runtime.Object) string { + if a, err := meta.Accessor(obj); err == nil { + a.SetManagedFields(nil) + a.SetAnnotations(nil) + a.SetLabels(nil) + } + + switch actual := obj.(type) { + case *corev1.Pod: + if !o.FullObject { + // Usually we only need to know the pod's status. + actual.Spec = corev1.PodSpec{} + } + case *corev1.Node: + actual.Status.Images = nil + } + + data, _ := yaml.Marshal(obj) + return string(data) +} diff --git a/cmd/diagnose/prune.go b/cmd/diagnose/prune.go deleted file mode 100644 index ad29ce7..0000000 --- a/cmd/diagnose/prune.go +++ /dev/null @@ -1,24 +0,0 @@ -package diagnose - -import ( - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" -) - -func pruneObject(obj runtime.Object) { - if a, err := meta.Accessor(obj); err == nil { - a.SetManagedFields(nil) - a.SetAnnotations(nil) - a.SetLabels(nil) - } - - switch actual := obj.(type) { - case *corev1.Node: - pruneNode(actual) - } -} - -func pruneNode(obj *corev1.Node) { - obj.Status.Images = nil -}