// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package walk import ( "sort" "sigs.k8s.io/kustomize/kyaml/fieldmeta" "sigs.k8s.io/kustomize/kyaml/openapi" "sigs.k8s.io/kustomize/kyaml/sets" "sigs.k8s.io/kustomize/kyaml/yaml" ) // walkMap returns the value of VisitMap // // - call VisitMap // - set the return value on l.Dest // - walk each source field // - set each source field value on l.Dest func (l Walker) walkMap() (*yaml.RNode, error) { // get the new map value dest, err := l.Sources.setDestNode(l.VisitMap(l.Sources, l.Schema)) if dest == nil || err != nil { return nil, err } // recursively set the field values on the map for _, key := range l.fieldNames() { var res *yaml.RNode var keys []*yaml.RNode if l.VisitKeysAsScalars { // visit the map keys as if they were scalars, // this is necessary if doing things such as copying // comments for i := range l.Sources { // construct the sources from the keys if l.Sources[i] == nil { keys = append(keys, nil) continue } field := l.Sources[i].Field(key) if field == nil || yaml.IsMissingOrNull(field.Key) { keys = append(keys, nil) continue } keys = append(keys, field.Key) } // visit the sources as a scalar // keys don't have any schema --pass in nil res, err = l.Visitor.VisitScalar(keys, nil) if err != nil { return nil, err } } var s *openapi.ResourceSchema if l.Schema != nil { s = l.Schema.Field(key) } fv, commentSch := l.fieldValue(key) if commentSch != nil { s = commentSch } val, err := Walker{ VisitKeysAsScalars: l.VisitKeysAsScalars, InferAssociativeLists: l.InferAssociativeLists, Visitor: l, Schema: s, Sources: fv, MergeOptions: l.MergeOptions, Path: append(l.Path, key)}.Walk() if err != nil { return nil, err } // transfer the comments of res to dest node var comments yaml.Comments if !yaml.IsMissingOrNull(res) { comments = yaml.Comments{ LineComment: res.YNode().LineComment, HeadComment: res.YNode().HeadComment, FootComment: res.YNode().FootComment, } if len(keys) > 0 && !yaml.IsMissingOrNull(keys[DestIndex]) { keys[DestIndex].YNode().HeadComment = res.YNode().HeadComment keys[DestIndex].YNode().LineComment = res.YNode().LineComment keys[DestIndex].YNode().FootComment = res.YNode().FootComment } } // this handles empty and non-empty values _, err = dest.Pipe(yaml.FieldSetter{Name: key, Comments: comments, Value: val}) if err != nil { return nil, err } } return dest, nil } // valueIfPresent returns node.Value if node is non-nil, otherwise returns nil func (l Walker) valueIfPresent(node *yaml.MapNode) (*yaml.RNode, *openapi.ResourceSchema) { if node == nil { return nil, nil } // parse the schema for the field if present var s *openapi.ResourceSchema fm := fieldmeta.FieldMeta{} var err error // check the value for a schema if err = fm.Read(node.Value); err == nil { s = &openapi.ResourceSchema{Schema: &fm.Schema} if fm.Schema.Ref.String() != "" { r, err := openapi.Resolve(&fm.Schema.Ref, openapi.Schema()) if err == nil && r != nil { s.Schema = r } } } // check the key for a schema -- this will be used // when the value is a Sequence (comments are attached) // to the key if fm.IsEmpty() { if err = fm.Read(node.Key); err == nil { s = &openapi.ResourceSchema{Schema: &fm.Schema} } if fm.Schema.Ref.String() != "" { r, err := openapi.Resolve(&fm.Schema.Ref, openapi.Schema()) if err == nil && r != nil { s.Schema = r } } } return node.Value, s } // fieldNames returns a sorted slice containing the names of all fields that appear in any of // the sources func (l Walker) fieldNames() []string { fields := sets.String{} for _, s := range l.Sources { if s == nil { continue } // don't check error, we know this is a mapping node sFields, _ := s.Fields() fields.Insert(sFields...) } result := fields.List() sort.Strings(result) return result } // fieldValue returns a slice containing each source's value for fieldName func (l Walker) fieldValue(fieldName string) ([]*yaml.RNode, *openapi.ResourceSchema) { var fields []*yaml.RNode var sch *openapi.ResourceSchema for i := range l.Sources { if l.Sources[i] == nil { fields = append(fields, nil) continue } field := l.Sources[i].Field(fieldName) f, s := l.valueIfPresent(field) fields = append(fields, f) if sch == nil && !s.IsMissingOrNull() { sch = s } } return fields, sch }