...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go

Documentation: k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tableconvertor
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"reflect"
    26  
    27  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    28  	"k8s.io/apimachinery/pkg/api/meta"
    29  	metatable "k8s.io/apimachinery/pkg/api/meta/table"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apiserver/pkg/registry/rest"
    34  	"k8s.io/client-go/util/jsonpath"
    35  )
    36  
    37  var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
    38  
    39  // New creates a new table convertor for the provided CRD column definition. If the printer definition cannot be parsed,
    40  // error will be returned along with a default table convertor.
    41  func New(crdColumns []apiextensionsv1.CustomResourceColumnDefinition) (rest.TableConvertor, error) {
    42  	headers := []metav1.TableColumnDefinition{
    43  		{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
    44  	}
    45  	c := &convertor{
    46  		headers: headers,
    47  	}
    48  
    49  	for _, col := range crdColumns {
    50  		path := jsonpath.New(col.Name)
    51  		if err := path.Parse(fmt.Sprintf("{%s}", col.JSONPath)); err != nil {
    52  			return c, fmt.Errorf("unrecognized column definition %q", col.JSONPath)
    53  		}
    54  		path.AllowMissingKeys(true)
    55  
    56  		desc := fmt.Sprintf("Custom resource definition column (in JSONPath format): %s", col.JSONPath)
    57  		if len(col.Description) > 0 {
    58  			desc = col.Description
    59  		}
    60  
    61  		c.additionalColumns = append(c.additionalColumns, path)
    62  		c.headers = append(c.headers, metav1.TableColumnDefinition{
    63  			Name:        col.Name,
    64  			Type:        col.Type,
    65  			Format:      col.Format,
    66  			Description: desc,
    67  			Priority:    col.Priority,
    68  		})
    69  	}
    70  
    71  	return c, nil
    72  }
    73  
    74  type columnPrinter interface {
    75  	FindResults(data interface{}) ([][]reflect.Value, error)
    76  	PrintResults(w io.Writer, results []reflect.Value) error
    77  }
    78  
    79  type convertor struct {
    80  	headers           []metav1.TableColumnDefinition
    81  	additionalColumns []columnPrinter
    82  }
    83  
    84  func (c *convertor) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
    85  	table := &metav1.Table{}
    86  	opt, ok := tableOptions.(*metav1.TableOptions)
    87  	noHeaders := ok && opt != nil && opt.NoHeaders
    88  	if !noHeaders {
    89  		table.ColumnDefinitions = c.headers
    90  	}
    91  
    92  	if m, err := meta.ListAccessor(obj); err == nil {
    93  		table.ResourceVersion = m.GetResourceVersion()
    94  		table.Continue = m.GetContinue()
    95  		table.RemainingItemCount = m.GetRemainingItemCount()
    96  	} else {
    97  		if m, err := meta.CommonAccessor(obj); err == nil {
    98  			table.ResourceVersion = m.GetResourceVersion()
    99  		}
   100  	}
   101  
   102  	var err error
   103  	buf := &bytes.Buffer{}
   104  	table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
   105  		cells := make([]interface{}, 1, 1+len(c.additionalColumns))
   106  		cells[0] = name
   107  		customHeaders := c.headers[1:]
   108  		us, ok := obj.(runtime.Unstructured)
   109  		if !ok {
   110  			m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			us = &unstructured.Unstructured{Object: m}
   115  		}
   116  		for i, column := range c.additionalColumns {
   117  			results, err := column.FindResults(us.UnstructuredContent())
   118  			if err != nil || len(results) == 0 || len(results[0]) == 0 {
   119  				cells = append(cells, nil)
   120  				continue
   121  			}
   122  
   123  			// as we only support simple JSON path, we can assume to have only one result (or none, filtered out above)
   124  			value := results[0][0].Interface()
   125  			if customHeaders[i].Type == "string" {
   126  				if err := column.PrintResults(buf, []reflect.Value{reflect.ValueOf(value)}); err == nil {
   127  					cells = append(cells, buf.String())
   128  					buf.Reset()
   129  				} else {
   130  					cells = append(cells, nil)
   131  				}
   132  			} else {
   133  				cells = append(cells, cellForJSONValue(customHeaders[i].Type, value))
   134  			}
   135  		}
   136  		return cells, nil
   137  	})
   138  	return table, err
   139  }
   140  
   141  func cellForJSONValue(headerType string, value interface{}) interface{} {
   142  	if value == nil {
   143  		return nil
   144  	}
   145  
   146  	switch headerType {
   147  	case "integer":
   148  		switch typed := value.(type) {
   149  		case int64:
   150  			return typed
   151  		case float64:
   152  			return int64(typed)
   153  		case json.Number:
   154  			if i64, err := typed.Int64(); err == nil {
   155  				return i64
   156  			}
   157  		}
   158  	case "number":
   159  		switch typed := value.(type) {
   160  		case int64:
   161  			return float64(typed)
   162  		case float64:
   163  			return typed
   164  		case json.Number:
   165  			if f, err := typed.Float64(); err == nil {
   166  				return f
   167  			}
   168  		}
   169  	case "boolean":
   170  		if b, ok := value.(bool); ok {
   171  			return b
   172  		}
   173  	case "string":
   174  		if s, ok := value.(string); ok {
   175  			return s
   176  		}
   177  	case "date":
   178  		if typed, ok := value.(string); ok {
   179  			var timestamp metav1.Time
   180  			err := timestamp.UnmarshalQueryParameter(typed)
   181  			if err != nil {
   182  				return "<invalid>"
   183  			}
   184  			return metatable.ConvertToHumanReadableDateType(timestamp)
   185  		}
   186  	}
   187  
   188  	return nil
   189  }
   190  

View as plain text