mirror of https://github.com/kubernetes/kops.git
				
				
				
			
		
			
				
	
	
		
			217 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2019 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 flagbuilder
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/api/resource"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/klog/v2"
 | |
| 	"k8s.io/kops/util/pkg/reflectutils"
 | |
| )
 | |
| 
 | |
| // BuildFlags returns a space separated list arguments
 | |
| // @deprecated: please use BuildFlagsList
 | |
| func BuildFlags(options interface{}) (string, error) {
 | |
| 	flags, err := BuildFlagsList(options)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	return strings.Join(flags, " "), nil
 | |
| }
 | |
| 
 | |
| // BuildFlagsList reflects the options interface and extracts the flags from struct tags
 | |
| func BuildFlagsList(options interface{}) ([]string, error) {
 | |
| 	var flags []string
 | |
| 
 | |
| 	walker := func(path *reflectutils.FieldPath, field *reflect.StructField, val reflect.Value) error {
 | |
| 		if field == nil {
 | |
| 			klog.V(10).Infof("ignoring non-field: %s", path)
 | |
| 			return nil
 | |
| 		}
 | |
| 		tag := field.Tag.Get("flag")
 | |
| 		if tag == "" {
 | |
| 			klog.V(4).Infof("not writing field with no flag tag: %s", path)
 | |
| 			// We want to descend - it could be a structure containing flags
 | |
| 			return nil
 | |
| 		}
 | |
| 		if tag == "-" {
 | |
| 			klog.V(4).Infof("skipping field with %q flag tag: %s", tag, path)
 | |
| 			return reflectutils.SkipReflection
 | |
| 		}
 | |
| 
 | |
| 		// If we specify the repeat option, we will repeat the flag rather than joining it with commas
 | |
| 		repeatFlag := false
 | |
| 
 | |
| 		tokens := strings.Split(tag, ",")
 | |
| 		if len(tokens) > 1 {
 | |
| 			for i, t := range tokens {
 | |
| 				if i == 0 {
 | |
| 					continue
 | |
| 				}
 | |
| 				if t == "repeat" {
 | |
| 					repeatFlag = true
 | |
| 				} else {
 | |
| 					return fmt.Errorf("cannot parse flag spec: %q", tag)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		flagName := tokens[0]
 | |
| 
 | |
| 		// If the "unset" value is not empty string, by setting this tag we avoid passing spurious flag values
 | |
| 		flagEmpty := field.Tag.Get("flag-empty")
 | |
| 
 | |
| 		flagIncludeEmpty, _ := strconv.ParseBool(field.Tag.Get("flag-include-empty"))
 | |
| 
 | |
| 		// We do have to do this, even though the recursive walk will do it for us
 | |
| 		// because when we descend we won't have `field` set
 | |
| 		if val.Kind() == reflect.Ptr && reflect.TypeOf(val.Interface()).String() != "*string" {
 | |
| 			if val.IsNil() {
 | |
| 				return nil
 | |
| 			}
 | |
| 			val = val.Elem()
 | |
| 		}
 | |
| 
 | |
| 		if val.Kind() == reflect.Map {
 | |
| 			if val.IsNil() {
 | |
| 				return nil
 | |
| 			}
 | |
| 			// We handle a map[string]string like --node-labels=k1=v1,k2=v2 etc
 | |
| 			// As we need more formats we can add additional spec to the flags tag
 | |
| 			if stringStringMap, ok := val.Interface().(map[string]string); ok {
 | |
| 				var args []string
 | |
| 				for k, v := range stringStringMap {
 | |
| 					arg := fmt.Sprintf("%s=%s", k, v)
 | |
| 					args = append(args, arg)
 | |
| 				}
 | |
| 				sort.Strings(args)
 | |
| 				if len(args) != 0 {
 | |
| 					flag := fmt.Sprintf("--%s=%s", flagName, strings.Join(args, ","))
 | |
| 					flags = append(flags, flag)
 | |
| 				}
 | |
| 				return reflectutils.SkipReflection
 | |
| 			}
 | |
| 
 | |
| 			return fmt.Errorf("BuildFlags of value type not handled: %T %s=%v", val.Interface(), path, val.Interface())
 | |
| 		}
 | |
| 
 | |
| 		if val.Kind() == reflect.Slice {
 | |
| 			if val.IsNil() {
 | |
| 				return nil
 | |
| 			}
 | |
| 			// We handle a []string like --admission-control=v1,v2 etc
 | |
| 			if stringSlice, ok := val.Interface().([]string); ok {
 | |
| 				if len(stringSlice) != 0 {
 | |
| 					if repeatFlag {
 | |
| 						for _, v := range stringSlice {
 | |
| 							flag := fmt.Sprintf("--%s=%s", flagName, v)
 | |
| 							flags = append(flags, flag)
 | |
| 						}
 | |
| 					} else {
 | |
| 						flag := fmt.Sprintf("--%s=%s", flagName, strings.Join(stringSlice, ","))
 | |
| 						flags = append(flags, flag)
 | |
| 					}
 | |
| 				}
 | |
| 				return reflectutils.SkipReflection
 | |
| 			}
 | |
| 
 | |
| 			return fmt.Errorf("BuildFlags of value type not handled: %T %s=%v", val.Interface(), path, val.Interface())
 | |
| 		}
 | |
| 
 | |
| 		var flag string
 | |
| 		switch v := val.Interface().(type) {
 | |
| 		case string:
 | |
| 			vString := fmt.Sprintf("%v", v)
 | |
| 			if vString != "" && vString != flagEmpty {
 | |
| 				flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 			}
 | |
| 
 | |
| 		case *string:
 | |
| 			if v != nil {
 | |
| 				// If flagIncludeEmpty is specified, include anything, including empty strings. Otherwise, behave
 | |
| 				// just like the string case above.
 | |
| 				if flagIncludeEmpty {
 | |
| 					vString := fmt.Sprintf("%v", *v)
 | |
| 					flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 				} else {
 | |
| 					vString := fmt.Sprintf("%v", *v)
 | |
| 					if vString != "" && vString != flagEmpty {
 | |
| 						flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		case bool, int, int32, int64:
 | |
| 			vString := fmt.Sprintf("%v", v)
 | |
| 			if vString != flagEmpty {
 | |
| 				flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 			}
 | |
| 
 | |
| 		case float32, float64:
 | |
| 			// Because these types don't round-trip, we should use resource.Quantity instead
 | |
| 			klog.Warningf("use of unsafe float type for flag %q; use resource.Quantity instead", flagName)
 | |
| 			vString := fmt.Sprintf("%v", v)
 | |
| 			if vString != flagEmpty {
 | |
| 				flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 			}
 | |
| 
 | |
| 		case metav1.Duration:
 | |
| 			vString := v.Duration.String()
 | |
| 
 | |
| 			// See https://github.com/kubernetes/kubernetes/issues/40783
 | |
| 			// Go renders a time.Duration to `0` in <= 1.6, and `0s` in >= 1.7
 | |
| 			// We force it to be `0s`, regardless of value
 | |
| 			if vString == "0" {
 | |
| 				vString = "0s"
 | |
| 			}
 | |
| 
 | |
| 			if vString != flagEmpty {
 | |
| 				flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 			}
 | |
| 
 | |
| 		case resource.Quantity:
 | |
| 			// Format as a floating point value (i.e. 3.14, not 3140m)
 | |
| 			vString := v.AsDec().String()
 | |
| 			if vString != flagEmpty {
 | |
| 				flag = fmt.Sprintf("--%s=%s", flagName, vString)
 | |
| 			}
 | |
| 
 | |
| 		default:
 | |
| 			return fmt.Errorf("BuildFlagsList of value type not handled: %T %s=%v", v, path, v)
 | |
| 		}
 | |
| 		if flag != "" {
 | |
| 			flags = append(flags, flag)
 | |
| 		}
 | |
| 
 | |
| 		return reflectutils.SkipReflection
 | |
| 	}
 | |
| 	err := reflectutils.ReflectRecursive(reflect.ValueOf(options), walker, &reflectutils.ReflectOptions{DeprecatedDoubleVisit: true})
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("BuildFlagsList to reflect value: %s", err)
 | |
| 	}
 | |
| 	// Sort so that the order is stable across runs
 | |
| 	sort.Strings(flags)
 | |
| 
 | |
| 	return flags, nil
 | |
| }
 |