...

Source file src/sigs.k8s.io/kustomize/api/internal/accumulator/namereferencetransformer.go

Documentation: sigs.k8s.io/kustomize/api/internal/accumulator

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package accumulator
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  
    10  	"sigs.k8s.io/kustomize/api/filters/nameref"
    11  	"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
    12  	"sigs.k8s.io/kustomize/api/resmap"
    13  	"sigs.k8s.io/kustomize/api/resource"
    14  	"sigs.k8s.io/kustomize/kyaml/resid"
    15  )
    16  
    17  type nameReferenceTransformer struct {
    18  	backRefs []builtinconfig.NameBackReferences
    19  }
    20  
    21  const doDebug = false
    22  
    23  var _ resmap.Transformer = &nameReferenceTransformer{}
    24  
    25  type filterMap map[*resource.Resource][]nameref.Filter
    26  
    27  // newNameReferenceTransformer constructs a nameReferenceTransformer
    28  // with a given slice of NameBackReferences.
    29  func newNameReferenceTransformer(
    30  	br []builtinconfig.NameBackReferences) resmap.Transformer {
    31  	if br == nil {
    32  		log.Fatal("backrefs not expected to be nil")
    33  	}
    34  	return &nameReferenceTransformer{backRefs: br}
    35  }
    36  
    37  // Transform updates name references in resource A that
    38  // refer to resource B, given that B's name may have
    39  // changed.
    40  //
    41  // For example, a HorizontalPodAutoscaler (HPA)
    42  // necessarily refers to a Deployment, the thing that
    43  // an HPA scales. In this case:
    44  //
    45  //   - the HPA instance is the Referrer,
    46  //   - the Deployment instance is the ReferralTarget.
    47  //
    48  // If the Deployment's name changes, e.g. a prefix is added,
    49  // then the HPA's reference to the Deployment must be fixed.
    50  //
    51  func (t *nameReferenceTransformer) Transform(m resmap.ResMap) error {
    52  	fMap := t.determineFilters(m.Resources())
    53  	debug(fMap)
    54  	for r, fList := range fMap {
    55  		c, err := m.SubsetThatCouldBeReferencedByResource(r)
    56  		if err != nil {
    57  			return err
    58  		}
    59  		for _, f := range fList {
    60  			f.Referrer = r
    61  			f.ReferralCandidates = c
    62  			if err := f.Referrer.ApplyFilter(f); err != nil {
    63  				return err
    64  			}
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  func debug(fMap filterMap) {
    71  	if !doDebug {
    72  		return
    73  	}
    74  	fmt.Printf("filterMap has %d entries:\n", len(fMap))
    75  	rCount := 0
    76  	for r, fList := range fMap {
    77  		yml, _ := r.AsYAML()
    78  		rCount++
    79  		fmt.Printf(`
    80  ---- %3d. possible referrer -------------
    81  %s
    82  ---------`, rCount, string(yml),
    83  		)
    84  		for i, f := range fList {
    85  			fmt.Printf(`
    86  %3d/%3d update: %s
    87            from: %s
    88  `, rCount, i+1, f.NameFieldToUpdate.Path, f.ReferralTarget,
    89  			)
    90  		}
    91  	}
    92  }
    93  
    94  // Produce a map from referrer resources that might need to be fixed
    95  // to filters that might fix them.  The keys to this map are potential
    96  // referrers, so won't include resources like ConfigMap or Secret.
    97  //
    98  // In the inner loop over the resources below, say we
    99  // encounter an HPA instance. Then, in scanning the set
   100  // of all known backrefs, we encounter an entry like
   101  //
   102  //   - kind: Deployment
   103  //     fieldSpecs:
   104  //     - kind: HorizontalPodAutoscaler
   105  //       path: spec/scaleTargetRef/name
   106  //
   107  // This entry says that an HPA, via its
   108  // 'spec/scaleTargetRef/name' field, may refer to a
   109  // Deployment.
   110  //
   111  // This means that a filter will need to hunt for the right Deployment,
   112  // obtain it's new name, and write that name into the HPA's
   113  // 'spec/scaleTargetRef/name' field. Return a filter that can do that.
   114  func (t *nameReferenceTransformer) determineFilters(
   115  	resources []*resource.Resource) (fMap filterMap) {
   116  	// We cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
   117  	resourceOrgIds := make([]resid.ResId, len(resources))
   118  	for i, resource := range resources {
   119  		resourceOrgIds[i] = resource.OrgId()
   120  	}
   121  
   122  	fMap = make(filterMap)
   123  	for _, backReference := range t.backRefs {
   124  		for _, referrerSpec := range backReference.Referrers {
   125  			for i, res := range resources {
   126  				if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
   127  					// If this is true, the res might be a referrer, and if
   128  					// so, the name reference it holds might need an update.
   129  					if resHasField(res, referrerSpec.Path) {
   130  						// Optimization - the referrer has the field
   131  						// that might need updating.
   132  						fMap[res] = append(fMap[res], nameref.Filter{
   133  							// Name field to write in the Referrer.
   134  							// If the path specified here isn't found in
   135  							// the Referrer, nothing happens (no error,
   136  							// no field creation).
   137  							NameFieldToUpdate: referrerSpec,
   138  							// Specification of object class to read from.
   139  							// Always read from metadata/name field.
   140  							ReferralTarget: backReference.Gvk,
   141  						})
   142  					}
   143  				}
   144  			}
   145  		}
   146  	}
   147  	return fMap
   148  }
   149  
   150  // TODO: check res for field existence here to avoid extra work.
   151  // res.GetFieldValue, which uses yaml.Lookup under the hood, doesn't know
   152  // how to parse fieldspec-style paths that make no distinction
   153  // between maps and sequences.  This means it cannot lookup commonly
   154  // used "indeterminate" paths like
   155  //    spec/containers/env/valueFrom/configMapKeyRef/name
   156  // ('containers' is a list, not a map).
   157  // However, the fieldspec filter does know how to handle this;
   158  // extract that code and call it here?
   159  func resHasField(res *resource.Resource, path string) bool {
   160  	return true
   161  	// fld := strings.Join(utils.PathSplitter(path), ".")
   162  	// _, e := res.GetFieldValue(fld)
   163  	// return e == nil
   164  }
   165  

View as plain text