...

Source file src/k8s.io/kubectl/pkg/cmd/config/navigation_step_parser.go

Documentation: k8s.io/kubectl/pkg/cmd/config

     1  /*
     2  Copyright 2014 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 config
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strings"
    23  
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    26  )
    27  
    28  type navigationSteps struct {
    29  	steps            []navigationStep
    30  	currentStepIndex int
    31  }
    32  
    33  type navigationStep struct {
    34  	stepValue string
    35  	stepType  reflect.Type
    36  }
    37  
    38  func newNavigationSteps(path string) (*navigationSteps, error) {
    39  	steps := []navigationStep{}
    40  	individualParts := strings.Split(path, ".")
    41  
    42  	currType := reflect.TypeOf(clientcmdapi.Config{})
    43  	currPartIndex := 0
    44  	for currPartIndex < len(individualParts) {
    45  		switch currType.Kind() {
    46  		case reflect.Map:
    47  			// if we're in a map, we need to locate a name.  That name may contain dots, so we need to know what tokens are legal for the map's value type
    48  			// for example, we could have a set request like: `set clusters.10.10.12.56.insecure-skip-tls-verify true`.  We enter this case with
    49  			// steps representing 10, 10, 12, 56, insecure-skip-tls-verify.  The name is "10.10.12.56", so we want to collect all those parts together and
    50  			// store them as a single step.  In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key
    51  			// This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags.  Those legal tags are used to
    52  			// walk the list of remaining parts until we find a match to a legal tag or the end of the string.  That name is used to burn all the used parts.
    53  			mapValueType := currType.Elem().Elem()
    54  			mapValueOptions, err := getPotentialTypeValues(mapValueType)
    55  			if err != nil {
    56  				return nil, err
    57  			}
    58  			nextPart := findNameStep(individualParts[currPartIndex:], sets.StringKeySet(mapValueOptions))
    59  
    60  			steps = append(steps, navigationStep{nextPart, mapValueType})
    61  			currPartIndex += len(strings.Split(nextPart, "."))
    62  			currType = mapValueType
    63  
    64  		case reflect.Struct:
    65  			nextPart := individualParts[currPartIndex]
    66  
    67  			options, err := getPotentialTypeValues(currType)
    68  			if err != nil {
    69  				return nil, err
    70  			}
    71  			fieldType, exists := options[nextPart]
    72  			if !exists {
    73  				return nil, fmt.Errorf("unable to parse %v after %v at %v", path, steps, currType)
    74  			}
    75  
    76  			steps = append(steps, navigationStep{nextPart, fieldType})
    77  			currPartIndex += len(strings.Split(nextPart, "."))
    78  			currType = fieldType
    79  		default:
    80  			return nil, fmt.Errorf("unable to parse one or more field values of %v", path)
    81  		}
    82  	}
    83  
    84  	return &navigationSteps{steps, 0}, nil
    85  }
    86  
    87  func (s *navigationSteps) pop() navigationStep {
    88  	if s.moreStepsRemaining() {
    89  		s.currentStepIndex++
    90  		return s.steps[s.currentStepIndex-1]
    91  	}
    92  	return navigationStep{}
    93  }
    94  
    95  func (s *navigationSteps) moreStepsRemaining() bool {
    96  	return len(s.steps) > s.currentStepIndex
    97  }
    98  
    99  // findNameStep takes the list of parts and a set of valid tags that can be used after the name.  It then walks the list of parts
   100  // until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts
   101  func findNameStep(parts []string, typeOptions sets.String) string {
   102  	if len(parts) == 0 {
   103  		return ""
   104  	}
   105  
   106  	numberOfPartsInStep := findKnownValue(parts[1:], typeOptions) + 1
   107  	// if we didn't find a known value, then the entire thing must be a name
   108  	if numberOfPartsInStep == 0 {
   109  		numberOfPartsInStep = len(parts)
   110  	}
   111  	nextParts := parts[0:numberOfPartsInStep]
   112  
   113  	return strings.Join(nextParts, ".")
   114  }
   115  
   116  // getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized.
   117  func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) {
   118  	if typeValue.Kind() == reflect.Pointer {
   119  		typeValue = typeValue.Elem()
   120  	}
   121  
   122  	if typeValue.Kind() != reflect.Struct {
   123  		return nil, fmt.Errorf("%v is not of type struct", typeValue)
   124  	}
   125  
   126  	ret := make(map[string]reflect.Type)
   127  
   128  	for fieldIndex := 0; fieldIndex < typeValue.NumField(); fieldIndex++ {
   129  		fieldType := typeValue.Field(fieldIndex)
   130  		yamlTag := fieldType.Tag.Get("json")
   131  		yamlTagName := strings.Split(yamlTag, ",")[0]
   132  
   133  		ret[yamlTagName] = fieldType.Type
   134  	}
   135  
   136  	return ret, nil
   137  }
   138  
   139  func findKnownValue(parts []string, valueOptions sets.String) int {
   140  	for i := range parts {
   141  		if valueOptions.Has(parts[i]) {
   142  			return i
   143  		}
   144  	}
   145  
   146  	return -1
   147  }
   148  

View as plain text