169 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2017 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package explain
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/api/meta"
 | |
| 	"k8s.io/apimachinery/pkg/runtime/schema"
 | |
| 	"k8s.io/client-go/util/jsonpath"
 | |
| 	"k8s.io/kube-openapi/pkg/util/proto"
 | |
| )
 | |
| 
 | |
| type fieldsPrinter interface {
 | |
| 	PrintFields(proto.Schema) error
 | |
| }
 | |
| 
 | |
| // jsonPathParse gets back the inner list of nodes we want to work with
 | |
| func jsonPathParse(in string) ([]jsonpath.Node, error) {
 | |
| 	// Remove trailing period just in case
 | |
| 	in = strings.TrimSuffix(in, ".")
 | |
| 
 | |
| 	// Define initial jsonpath Parser
 | |
| 	jpp, err := jsonpath.Parse("user", "{."+in+"}")
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Because of the way the jsonpath library works, the schema of the parser is [][]NodeList
 | |
| 	// meaning we need to get the outer node list, make sure it's only length 1, then get the inner node
 | |
| 	// list, and only then can we look at the individual nodes themselves.
 | |
| 	outerNodeList := jpp.Root.Nodes
 | |
| 	if len(outerNodeList) != 1 {
 | |
| 		return nil, fmt.Errorf("must pass in 1 jsonpath string")
 | |
| 	}
 | |
| 
 | |
| 	// The root node is always a list node so this type assertion is safe
 | |
| 	return outerNodeList[0].(*jsonpath.ListNode).Nodes, nil
 | |
| }
 | |
| 
 | |
| // SplitAndParseResourceRequest separates the users input into a model and fields
 | |
| func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (schema.GroupVersionResource, []string, error) {
 | |
| 	inResourceNodeList, err := jsonPathParse(inResource)
 | |
| 	if err != nil {
 | |
| 		return schema.GroupVersionResource{}, nil, err
 | |
| 	}
 | |
| 
 | |
| 	if inResourceNodeList[0].Type() != jsonpath.NodeField {
 | |
| 		return schema.GroupVersionResource{}, nil, fmt.Errorf("invalid jsonpath syntax, first node must be field node")
 | |
| 	}
 | |
| 	resource := inResourceNodeList[0].(*jsonpath.FieldNode).Value
 | |
| 	gvr, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: resource})
 | |
| 	if err != nil {
 | |
| 		return schema.GroupVersionResource{}, nil, err
 | |
| 	}
 | |
| 
 | |
| 	var fieldsPath []string
 | |
| 	for _, node := range inResourceNodeList[1:] {
 | |
| 		if node.Type() != jsonpath.NodeField {
 | |
| 			return schema.GroupVersionResource{}, nil, fmt.Errorf("invalid jsonpath syntax, all nodes must be field nodes")
 | |
| 		}
 | |
| 		fieldsPath = append(fieldsPath, node.(*jsonpath.FieldNode).Value)
 | |
| 	}
 | |
| 
 | |
| 	return gvr, fieldsPath, nil
 | |
| }
 | |
| 
 | |
| // SplitAndParseResourceRequestWithMatchingPrefix separates the users input into a model and fields
 | |
| // while selecting gvr whose (resource, group) prefix matches the resource
 | |
| func SplitAndParseResourceRequestWithMatchingPrefix(inResource string, mapper meta.RESTMapper) (gvr schema.GroupVersionResource, fieldsPath []string, err error) {
 | |
| 	inResourceNodeList, err := jsonPathParse(inResource)
 | |
| 	if err != nil {
 | |
| 		return schema.GroupVersionResource{}, nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Get resource from first node of jsonpath
 | |
| 	if inResourceNodeList[0].Type() != jsonpath.NodeField {
 | |
| 		return schema.GroupVersionResource{}, nil, fmt.Errorf("invalid jsonpath syntax, first node must be field node")
 | |
| 	}
 | |
| 	resource := inResourceNodeList[0].(*jsonpath.FieldNode).Value
 | |
| 
 | |
| 	gvrs, err := mapper.ResourcesFor(schema.GroupVersionResource{Resource: resource})
 | |
| 	if err != nil {
 | |
| 		return schema.GroupVersionResource{}, nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, gvrItem := range gvrs {
 | |
| 		// Find first gvr whose gr prefixes requested resource
 | |
| 		groupResource := gvrItem.GroupResource().String()
 | |
| 		if strings.HasPrefix(inResource, groupResource) {
 | |
| 			resourceSuffix := inResource[len(groupResource):]
 | |
| 			var fieldsPath []string
 | |
| 			if len(resourceSuffix) > 0 {
 | |
| 				// Define another jsonpath Parser for the resource suffix
 | |
| 				resourceSuffixNodeList, err := jsonPathParse(resourceSuffix)
 | |
| 				if err != nil {
 | |
| 					return schema.GroupVersionResource{}, nil, err
 | |
| 				}
 | |
| 
 | |
| 				if len(resourceSuffixNodeList) > 0 {
 | |
| 					nodeList := resourceSuffixNodeList[1:]
 | |
| 					for _, node := range nodeList {
 | |
| 						if node.Type() != jsonpath.NodeField {
 | |
| 							return schema.GroupVersionResource{}, nil, fmt.Errorf("invalid jsonpath syntax, first node must be field node")
 | |
| 						}
 | |
| 						fieldsPath = append(fieldsPath, node.(*jsonpath.FieldNode).Value)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			return gvrItem, fieldsPath, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If no match, take the first (the highest priority) gvr
 | |
| 	fieldsPath = []string{}
 | |
| 	if len(gvrs) > 0 {
 | |
| 		gvr = gvrs[0]
 | |
| 
 | |
| 		fieldsPathNodeList, err := jsonPathParse(inResource)
 | |
| 		if err != nil {
 | |
| 			return schema.GroupVersionResource{}, nil, err
 | |
| 		}
 | |
| 
 | |
| 		for _, node := range fieldsPathNodeList[1:] {
 | |
| 			if node.Type() != jsonpath.NodeField {
 | |
| 				return schema.GroupVersionResource{}, nil, fmt.Errorf("invalid jsonpath syntax, first node must be field node")
 | |
| 			}
 | |
| 			fieldsPath = append(fieldsPath, node.(*jsonpath.FieldNode).Value)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return gvr, fieldsPath, nil
 | |
| }
 | |
| 
 | |
| // PrintModelDescription prints the description of a specific model or dot path.
 | |
| // If recursive, all components nested within the fields of the schema will be
 | |
| // printed.
 | |
| func PrintModelDescription(fieldsPath []string, w io.Writer, schema proto.Schema, gvk schema.GroupVersionKind, recursive bool) error {
 | |
| 	fieldName := ""
 | |
| 	if len(fieldsPath) != 0 {
 | |
| 		fieldName = fieldsPath[len(fieldsPath)-1]
 | |
| 	}
 | |
| 
 | |
| 	// Go down the fieldsPath to find what we're trying to explain
 | |
| 	schema, err := LookupSchemaForField(schema, fieldsPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	b := fieldsPrinterBuilder{Recursive: recursive}
 | |
| 	f := &Formatter{Writer: w, Wrap: 80}
 | |
| 	return PrintModel(fieldName, f, b, schema, gvk)
 | |
| }
 |