// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package dao import ( "context" "fmt" "strings" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" cmdutil "k8s.io/kubectl/pkg/cmd/util" ) type Dynamic struct { Generic } // Get returns a given resource as a table object. func (d *Dynamic) Get(ctx context.Context, path string) (runtime.Object, error) { oo, err := d.toTable(ctx, path) if err != nil || len(oo) == 0 { return nil, err } return oo[0], nil } // List returns a collection of resources as one or more table objects. func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error) { return d.toTable(ctx, ns+"/") } func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) { strLabel, _ := ctx.Value(internal.KeyLabels).(string) opts := []string{d.gvr.AsResourceName()} ns, n := client.Namespaced(fqn) if n != "" { opts = append(opts, n) } allNS := client.IsAllNamespaces(ns) flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags()) f := cmdutil.NewFactory(flags) b := f.NewBuilder(). Unstructured(). NamespaceParam(ns).DefaultNamespace().AllNamespaces(allNS). LabelSelectorParam(strLabel). FieldSelectorParam(""). RequestChunksOf(0). ResourceTypeOrNameArgs(true, opts...). ContinueOnError(). Latest(). Flatten(). TransformRequests(d.transformRequests). Do() if err := b.Err(); err != nil { return nil, err } infos, err := b.Infos() if err != nil { return nil, err } oo := make([]runtime.Object, 0, len(infos)) for _, info := range infos { o, err := decodeIntoTable(info.Object, allNS) if err != nil { return nil, err } oo = append(oo, o.(*metav1.Table)) } return oo, nil } var recognizedTableVersions = map[schema.GroupVersionKind]bool{ metav1beta1.SchemeGroupVersion.WithKind("Table"): true, metav1.SchemeGroupVersion.WithKind("Table"): true, } func decodeIntoTable(obj runtime.Object, allNs bool) (runtime.Object, error) { event, isEvent := obj.(*metav1.WatchEvent) if isEvent { obj = event.Object.Object } if !recognizedTableVersions[obj.GetObjectKind().GroupVersionKind()] { return nil, fmt.Errorf("attempt to decode non-Table object: %v", obj.GetObjectKind().GroupVersionKind()) } u, ok := obj.(*unstructured.Unstructured) if !ok { return nil, fmt.Errorf("attempt to decode non-Unstructured object") } var table metav1.Table if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &table); err != nil { return nil, err } if allNs { defs := make([]metav1.TableColumnDefinition, 0, len(table.ColumnDefinitions)+1) defs = append(defs, metav1.TableColumnDefinition{Name: "Namespace", Type: "string"}) defs = append(defs, table.ColumnDefinitions...) table.ColumnDefinitions = defs } for i := range table.Rows { row := &table.Rows[i] if row.Object.Raw == nil || row.Object.Object != nil { continue } converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw) if err != nil { return nil, err } row.Object.Object = converted var m metav1.Object if obj := row.Object.Object; obj != nil { m, _ = meta.Accessor(obj) } var ns string if m != nil { ns = m.GetNamespace() } if allNs { cells := make([]interface{}, 0, len(row.Cells)+1) cells = append(cells, ns) cells = append(cells, row.Cells...) row.Cells = cells } } if isEvent { event.Object.Object = &table return event, nil } return &table, nil } func (d *Dynamic) transformRequests(req *rest.Request) { req.SetHeader("Accept", strings.Join([]string{ fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName), fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName), "application/json", }, ",")) if d.includeObj { req.Param("includeObject", "Object") } }