Improve comments in name transform code.

This commit is contained in:
Jeffrey Regan
2019-06-10 15:48:53 -07:00
parent e1e622d985
commit 9c36ac28fa
3 changed files with 83 additions and 49 deletions

View File

@@ -134,15 +134,13 @@ type ResMap interface {
// Clear removes all resources and Ids. // Clear removes all resources and Ids.
Clear() Clear()
// ResourcesThatCouldReference returns a new ResMap with // SubsetThatCouldBeReferencedBy returns a ResMap subset
// resources that _might_ reference the resource represented // of self with resources that could be referenced by the
// by the argument Id, excluding resources that should // resource represented by the argument Id.
// _never_ reference the Id. E.g., if the Id // This is a filter; it excludes things that cannot be
// refers to a ConfigMap, the returned set may include a // referenced by the Id's resource, e.g. objects in other
// Deployment from the same namespace and exclude Deployments // namespaces. Cluster wide objects are never excluded.
// from other namespaces. Cluster wide objects are SubsetThatCouldBeReferencedBy(resid.ResId) ResMap
// never excluded.
ResourcesThatCouldReference(resid.ResId) ResMap
// DeepCopy copies the ResMap and underlying resources. // DeepCopy copies the ResMap and underlying resources.
DeepCopy() ResMap DeepCopy() ResMap
@@ -456,8 +454,8 @@ func (m *resWrangler) makeCopy(copier resCopier) ResMap {
return result return result
} }
// ResourcesThatCouldReference implements ResMap. // SubsetThatCouldBeReferencedBy implements ResMap.
func (m *resWrangler) ResourcesThatCouldReference(inputId resid.ResId) ResMap { func (m *resWrangler) SubsetThatCouldBeReferencedBy(inputId resid.ResId) ResMap {
if inputId.Gvk().IsClusterKind() { if inputId.Gvk().IsClusterKind() {
return m return m
} }

View File

@@ -381,7 +381,7 @@ func TestFilterBy(t *testing.T) {
for name, test := range tests { for name, test := range tests {
test := test test := test
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
got := test.resMap.ResourcesThatCouldReference(test.filter) got := test.resMap.SubsetThatCouldBeReferencedBy(test.filter)
err := test.expected.ErrorIfNotEqualSets(got) err := test.expected.ErrorIfNotEqualSets(got)
if err != nil { if err != nil {
t.Fatalf("Expected %v but got back %v", test.expected, got) t.Fatalf("Expected %v but got back %v", test.expected, got)

View File

@@ -41,38 +41,68 @@ func NewNameReferenceTransformer(br []config.NameBackReferences) Transformer {
return &nameReferenceTransformer{backRefs: br} return &nameReferenceTransformer{backRefs: br}
} }
// Transform updates name references in resource A that refer to resource B, // Transform updates name references in resource A that
// given that B's name may have changed. // refer to resource B, given that B's name may have
// changed.
// //
// For example, a HorizontalPodAutoscaler (HPA) necessarily refers to a // For example, a HorizontalPodAutoscaler (HPA)
// Deployment (the thing that the HPA scales). The Deployment name might change // necessarily refers to a Deployment, the thing that
// (e.g. prefix added), and the reference in the HPA has to be fixed. // the HPA scales. The Deployment name might change
// (e.g. prefix added), and the reference in the HPA
// has to be fixed.
// //
// In the outer loop below, we encounter an HPA. In scanning backrefs, we // In the outer loop over the ResMap below, say we
// find that HPA refers to a Deployment. So we find all resources in the same // encounter a specific HPA. Then, in scanning backrefs,
// namespace as the HPA (and with the same prefix and suffix), and look through // we encounter an entry like
// them to find all the Deployments with a resId that has a Name matching the
// field in HPA. For each match, we overwrite the HPA name field with the value
// found in the Deployment's name field (the name in the raw object - the
// modified name - not the unmodified name in the resId).
// //
// This assumes that the name stored in a ResId (the ResMap key) isn't modified // - kind: Deployment
// by name transformers. Name transformers should only modify the name in the // fieldSpecs:
// - path: spec/scaleTargetRef/name
// kind: HorizontalPodAutoscaler
//
// saying that an HPA, via its 'spec/scaleTargetRef/name'
// field, may refer to a Deployment. This match to HPA
// means we may need to modify the value in its
// 'spec/scaleTargetRef/name' field, by searching for
// the thing it refers to, and getting its new name.
//
// As a filter, and search optimization, we compute a
// subset of all resources that the HPA could refer to,
// by excluding objects from other namespaces, and
// excluding objects that don't have the same prefix-
// suffix mods as the HPA.
//
// We look in this subset for all Deployment objects
// with a resId that has a Name matching the field value
// present in the HPA. If no match do nothing; if more
// than one match, it's an error.
//
// We overwrite the HPA name field with the value found
// in the Deployment's name field (the name in the raw
// object - the modified name - not the unmodified name
// in the Deployment's resId).
//
// This process assumes that the name stored in a ResId
// (the ResMap key) isn't modified by name transformers.
// Name transformers should only modify the name in the
// body of the resource object (the value in the ResMap). // body of the resource object (the value in the ResMap).
//
func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error { func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
// TODO: Too much looping. // TODO: Too much looping, here and in transitive calls.
// Even more hidden loops in ResourcesThatCouldReference, for referrer, res := range m.AsMap() {
// updateNameReference and FindByGVKN. var candidates resmap.ResMap
for id, r := range m.AsMap() { for _, target := range o.backRefs {
for _, backRef := range o.backRefs { for _, fSpec := range target.FieldSpecs {
for _, fSpec := range backRef.FieldSpecs { if referrer.Gvk().IsSelected(&fSpec.Gvk) {
if id.Gvk().IsSelected(&fSpec.Gvk) { if candidates == nil {
candidates = m.SubsetThatCouldBeReferencedBy(referrer)
}
err := mutateField( err := mutateField(
r.Map(), res.Map(),
fSpec.PathSlice(), fSpec.PathSlice(),
fSpec.CreateIfNotPresent, fSpec.CreateIfNotPresent,
o.updateNameReference( o.getNewName(
id, backRef.Gvk, m.ResourcesThatCouldReference(id))) referrer, target.Gvk, candidates))
if err != nil { if err != nil {
return err return err
} }
@@ -83,24 +113,28 @@ func (o *nameReferenceTransformer) Transform(m resmap.ResMap) error {
return nil return nil
} }
func (o *nameReferenceTransformer) updateNameReference( func (o *nameReferenceTransformer) getNewName(
rid resid.ResId, backRef gvk.Gvk, m resmap.ResMap) func(in interface{}) (interface{}, error) { referrer resid.ResId,
target gvk.Gvk,
referralCandidates resmap.ResMap) func(in interface{}) (interface{}, error) {
return func(in interface{}) (interface{}, error) { return func(in interface{}) (interface{}, error) {
switch in.(type) { switch in.(type) {
case string: case string:
s, _ := in.(string) oldName, _ := in.(string)
for id, res := range m.AsMap() { for id, res := range referralCandidates.AsMap() {
if id.Gvk().IsSelected(&backRef) && id.Name() == s { if id.Gvk().IsSelected(&target) && id.Name() == oldName {
matchedIds := m.GetMatchingIds(id.GvknEquals) matchedIds := referralCandidates.GetMatchingIds(id.GvknEquals)
// If there's more than one match, there's no way // If there's more than one match, there's no way
// to know which one to pick, so emit error. // to know which one to pick, so emit error.
if len(matchedIds) > 1 { if len(matchedIds) > 1 {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"Multiple matches for name %s:\n %v", id, matchedIds) "Multiple matches for name %s:\n %v", id, matchedIds)
} }
// In the resource, note that it is referenced
// by the referrer.
res.AppendRefBy(referrer)
// Return transformed name of the object, // Return transformed name of the object,
// complete with prefixes, hashes, etc. // complete with prefixes, hashes, etc.
res.AppendRefBy(rid)
return res.GetName(), nil return res.GetName(), nil
} }
} }
@@ -111,14 +145,15 @@ func (o *nameReferenceTransformer) updateNameReference(
for _, item := range l { for _, item := range l {
name, ok := item.(string) name, ok := item.(string)
if !ok { if !ok {
return nil, fmt.Errorf("%#v is expected to be %T", item, name) return nil, fmt.Errorf(
"%#v is expected to be %T", item, name)
} }
names = append(names, name) names = append(names, name)
} }
for id, res := range m.AsMap() { for id, res := range referralCandidates.AsMap() {
indexes := indexOf(id.Name(), names) indexes := indexOf(id.Name(), names)
if id.Gvk().IsSelected(&backRef) && len(indexes) > 0 { if id.Gvk().IsSelected(&target) && len(indexes) > 0 {
matchedIds := m.GetMatchingIds(id.GvknEquals) matchedIds := referralCandidates.GetMatchingIds(id.GvknEquals)
if len(matchedIds) > 1 { if len(matchedIds) > 1 {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"Multiple matches for name %s:\n %v", id, matchedIds) "Multiple matches for name %s:\n %v", id, matchedIds)
@@ -126,13 +161,14 @@ func (o *nameReferenceTransformer) updateNameReference(
for _, index := range indexes { for _, index := range indexes {
l[index] = res.GetName() l[index] = res.GetName()
} }
res.AppendRefBy(rid) res.AppendRefBy(referrer)
return l, nil return l, nil
} }
} }
return in, nil return in, nil
default: default:
return nil, fmt.Errorf("%#v is expected to be either a string or a []interface{}", in) return nil, fmt.Errorf(
"%#v is expected to be either a string or a []interface{}", in)
} }
} }
} }