github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/yamltags/tags.go (about)

     1  /*
     2  Copyright 2019 The Skaffold 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 yamltags
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  	"unicode"
    25  
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
    28  )
    29  
    30  type fieldSet map[string]struct{}
    31  
    32  // ValidateStruct validates and processes the provided pointer to a struct.
    33  func ValidateStruct(s interface{}) error {
    34  	parentStruct := reflect.Indirect(reflect.ValueOf(s))
    35  	t := parentStruct.Type()
    36  	log.Entry(context.TODO()).Tracef("validating yamltags of struct %s", t.Name())
    37  
    38  	// Loop through the fields on the struct, looking for tags.
    39  	for i := 0; i < t.NumField(); i++ {
    40  		f := t.Field(i)
    41  		val := parentStruct.Field(i)
    42  		field := parentStruct.Type().Field(i)
    43  		if tags, ok := f.Tag.Lookup("yamltags"); ok {
    44  			if err := processTags(tags, val, parentStruct, field); err != nil {
    45  				return err
    46  			}
    47  		}
    48  	}
    49  	return nil
    50  }
    51  
    52  // YamlName returns the YAML name of the given field
    53  func YamlName(field reflect.StructField) string {
    54  	if yamltags, ok := field.Tag.Lookup("yaml"); ok {
    55  		tags := strings.Split(yamltags, ",")
    56  		if len(tags) > 0 && tags[0] != "" {
    57  			return tags[0]
    58  		}
    59  	}
    60  	return field.Name
    61  }
    62  
    63  // GetYamlTag returns the first yaml tag used in the raw yaml text of the given struct
    64  func GetYamlTag(value interface{}) string {
    65  	buf, err := yaml.Marshal(value)
    66  	if err != nil {
    67  		log.Entry(context.TODO()).Warnf("error marshaling %-v", value)
    68  		return ""
    69  	}
    70  	rawStr := string(buf)
    71  	i := strings.Index(rawStr, ":")
    72  	if i == -1 {
    73  		return ""
    74  	}
    75  	return rawStr[:i]
    76  }
    77  
    78  // GetYamlKeys returns the yaml key for each non-nested field of the given non-nil config parameter
    79  // For example if config is `latest.DeployType{HelmDeploy: &HelmDeploy{...}, KustomizeDeploy: &KustomizeDeploy{...}}`
    80  // then it returns `["helm", "kustomize"]`
    81  func GetYamlKeys(config interface{}) []string {
    82  	var tags []string
    83  	if config == nil {
    84  		return tags
    85  	}
    86  	st := reflect.Indirect(reflect.ValueOf(config))
    87  	t := st.Type()
    88  	for i := 0; i < t.NumField(); i++ {
    89  		f := t.Field(i)
    90  		v := st.Field(i)
    91  		if v.Kind() == reflect.Ptr && v.IsNil() { // exclude ptr fields not explicitly defined in the configuration
    92  			continue
    93  		}
    94  		tag := getYamlKey(f)
    95  		if tag != "" {
    96  			tags = append(tags, tag)
    97  		}
    98  	}
    99  	return tags
   100  }
   101  
   102  func getYamlKey(f reflect.StructField) string {
   103  	if f.PkgPath != "" { // field is unexported
   104  		return ""
   105  	}
   106  	t, ok := f.Tag.Lookup("yaml")
   107  	if !ok {
   108  		return lowerCaseFirst(f.Name)
   109  	}
   110  	tags := strings.Split(t, ",")
   111  	for i := 1; i < len(tags); i++ { // return empty string if it contains yaml flag `inline`
   112  		if tags[i] == "inline" {
   113  			return ""
   114  		}
   115  	}
   116  	if len(tags) == 0 || tags[0] == "" {
   117  		return lowerCaseFirst(f.Name)
   118  	}
   119  	return tags[0]
   120  }
   121  
   122  func lowerCaseFirst(s string) string {
   123  	r := []rune(s)
   124  	r[0] = unicode.ToLower(r[0])
   125  	return string(r)
   126  }
   127  
   128  func processTags(yamltags string, val reflect.Value, parentStruct reflect.Value, field reflect.StructField) error {
   129  	tags := strings.Split(yamltags, ",")
   130  	for _, tag := range tags {
   131  		tagParts := strings.Split(tag, "=")
   132  		var yt yamlTag
   133  		switch tagParts[0] {
   134  		case "required":
   135  			yt = &requiredTag{
   136  				Field: field,
   137  			}
   138  		case "oneOf":
   139  			yt = &oneOfTag{
   140  				Field:  field,
   141  				Parent: parentStruct,
   142  			}
   143  		case "skipTrim":
   144  			yt = &skipTrimTag{
   145  				Field: field,
   146  			}
   147  		default:
   148  			log.Entry(context.TODO()).Panicf("unknown yaml tag in %s", yamltags)
   149  		}
   150  		if err := yt.Load(tagParts); err != nil {
   151  			return err
   152  		}
   153  		if err := yt.Process(val); err != nil {
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  type yamlTag interface {
   161  	Load([]string) error
   162  	Process(reflect.Value) error
   163  }
   164  
   165  type requiredTag struct {
   166  	Field reflect.StructField
   167  }
   168  
   169  func (rt *requiredTag) Load(s []string) error {
   170  	return nil
   171  }
   172  
   173  func (rt *requiredTag) Process(val reflect.Value) error {
   174  	if isZeroValue(val) {
   175  		if tags, ok := rt.Field.Tag.Lookup("yaml"); ok {
   176  			return fmt.Errorf("required value not set: %s", strings.Split(tags, ",")[0])
   177  		}
   178  		return fmt.Errorf("required value not set: %s", rt.Field.Name)
   179  	}
   180  	return nil
   181  }
   182  
   183  // A program can have many structs, that each have many oneOfSets.
   184  // Each oneOfSet is a map of a oneOf-set name to the set of fields that belong to that oneOf-set
   185  // Only one field in that set may have a non-zero value.
   186  
   187  var allOneOfs map[string]map[string]fieldSet
   188  
   189  func getOneOfSetsForStruct(structName string) map[string]fieldSet {
   190  	_, ok := allOneOfs[structName]
   191  	if !ok {
   192  		allOneOfs[structName] = map[string]fieldSet{}
   193  	}
   194  	return allOneOfs[structName]
   195  }
   196  
   197  type oneOfTag struct {
   198  	Field     reflect.StructField
   199  	Parent    reflect.Value
   200  	oneOfSets map[string]fieldSet
   201  	setName   string
   202  }
   203  
   204  func (oot *oneOfTag) Load(s []string) error {
   205  	if len(s) != 2 {
   206  		return fmt.Errorf("invalid default struct tag: %v, expected key=value", s)
   207  	}
   208  	oot.setName = s[1]
   209  
   210  	// Fetch the right oneOfSet for the struct.
   211  	structName := oot.Parent.Type().Name()
   212  	oot.oneOfSets = getOneOfSetsForStruct(structName)
   213  
   214  	// Add this field to the oneOfSet
   215  	if _, ok := oot.oneOfSets[oot.setName]; !ok {
   216  		oot.oneOfSets[oot.setName] = fieldSet{}
   217  	}
   218  	oot.oneOfSets[oot.setName][oot.Field.Name] = struct{}{}
   219  	return nil
   220  }
   221  
   222  func (oot *oneOfTag) Process(val reflect.Value) error {
   223  	if isZeroValue(val) {
   224  		return nil
   225  	}
   226  
   227  	// This must exist because process is always called after Load.
   228  	oneOfSet := oot.oneOfSets[oot.setName]
   229  	for otherField := range oneOfSet {
   230  		if otherField == oot.Field.Name {
   231  			continue
   232  		}
   233  		field := oot.Parent.FieldByName(otherField)
   234  		if !isZeroValue(field) {
   235  			return fmt.Errorf("only one element in set %s can be set. got %s and %s", oot.setName, otherField, oot.Field.Name)
   236  		}
   237  	}
   238  	return nil
   239  }
   240  
   241  type skipTrimTag struct {
   242  	Field reflect.StructField
   243  }
   244  
   245  func (tag *skipTrimTag) Load(s []string) error {
   246  	return nil
   247  }
   248  
   249  func (tag *skipTrimTag) Process(val reflect.Value) error {
   250  	if isZeroValue(val) {
   251  		if tags, ok := tag.Field.Tag.Lookup("yaml"); ok {
   252  			return fmt.Errorf("skipTrim value not set: %s", strings.Split(tags, ",")[0])
   253  		}
   254  		return fmt.Errorf("skipTrim value not set: %s", tag.Field.Name)
   255  	}
   256  	return nil
   257  }
   258  
   259  func isZeroValue(val reflect.Value) bool {
   260  	if val.Kind() == reflect.Invalid {
   261  		return true
   262  	}
   263  	zv := reflect.Zero(val.Type()).Interface()
   264  	return reflect.DeepEqual(zv, val.Interface())
   265  }
   266  
   267  func init() {
   268  	allOneOfs = make(map[string]map[string]fieldSet)
   269  }