github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/managedfields/managed_fields.go (about)

     1  package managedfields
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    10  	"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
    11  	"sigs.k8s.io/structured-merge-diff/v6/typed"
    12  )
    13  
    14  // Normalize will compare the live and config states. If config mutates
    15  // a field that belongs to one of the trustedManagers it will remove
    16  // that field from both live and config objects and return the normalized
    17  // objects in this order. This function won't modify the live and config
    18  // parameters. If pt is nil, the normalization will use a deduced parseable
    19  // type which means that lists and maps are manipulated atomically.
    20  // It is a no-op if no trustedManagers is provided. It is also a no-op if
    21  // live or config are nil.
    22  func Normalize(live, config *unstructured.Unstructured, trustedManagers []string, pt *typed.ParseableType) (*unstructured.Unstructured, *unstructured.Unstructured, error) {
    23  	if len(trustedManagers) == 0 {
    24  		return nil, nil, nil
    25  	}
    26  	if live == nil || config == nil {
    27  		return nil, nil, nil
    28  	}
    29  
    30  	liveCopy := live.DeepCopy()
    31  	configCopy := config.DeepCopy()
    32  	normalized := false
    33  
    34  	results, err := newTypedResults(liveCopy, configCopy, pt)
    35  	// error might happen if the resources are not parsable and so cannot be normalized
    36  	if err != nil {
    37  		log.Debugf("error building typed results: %v", err)
    38  		return liveCopy, configCopy, nil
    39  	}
    40  
    41  	for _, mf := range live.GetManagedFields() {
    42  		if trustedManager(mf.Manager, trustedManagers) {
    43  			err := normalize(mf, results)
    44  			if err != nil {
    45  				return nil, nil, fmt.Errorf("error normalizing manager %s: %w", mf.Manager, err)
    46  			}
    47  			normalized = true
    48  		}
    49  	}
    50  
    51  	if !normalized {
    52  		return liveCopy, configCopy, nil
    53  	}
    54  	lvu := results.live.AsValue().Unstructured()
    55  	l, ok := lvu.(map[string]any)
    56  	if !ok {
    57  		return nil, nil, fmt.Errorf("error converting live typedValue: expected map got %T", lvu)
    58  	}
    59  	normLive := &unstructured.Unstructured{Object: l}
    60  
    61  	cvu := results.config.AsValue().Unstructured()
    62  	c, ok := cvu.(map[string]any)
    63  	if !ok {
    64  		return nil, nil, fmt.Errorf("error converting config typedValue: expected map got %T", cvu)
    65  	}
    66  	normConfig := &unstructured.Unstructured{Object: c}
    67  	return normLive, normConfig, nil
    68  }
    69  
    70  // normalize will check if the modified set has fields that are present
    71  // in the managed fields entry. If so, it will remove the fields from
    72  // the live and config objects so it is ignored in diffs.
    73  func normalize(mf metav1.ManagedFieldsEntry, tr *typedResults) error {
    74  	mfs := &fieldpath.Set{}
    75  	err := mfs.FromJSON(bytes.NewReader(mf.FieldsV1.Raw))
    76  	if err != nil {
    77  		return err
    78  	}
    79  	intersect := mfs.Intersection(tr.comparison.Modified)
    80  	if intersect.Empty() {
    81  		return nil
    82  	}
    83  	tr.live = tr.live.RemoveItems(intersect)
    84  	tr.config = tr.config.RemoveItems(intersect)
    85  	return nil
    86  }
    87  
    88  type typedResults struct {
    89  	live       *typed.TypedValue
    90  	config     *typed.TypedValue
    91  	comparison *typed.Comparison
    92  }
    93  
    94  // newTypedResults will convert live and config into a TypedValue using the given pt
    95  // and compare them. Returns a typedResults with the converted types and the comparison.
    96  // If pt is nil, will use the DeducedParseableType.
    97  func newTypedResults(live, config *unstructured.Unstructured, pt *typed.ParseableType) (*typedResults, error) {
    98  	typedLive, err := pt.FromUnstructured(live.Object)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("error creating typedLive: %w", err)
   101  	}
   102  
   103  	typedConfig, err := pt.FromUnstructured(config.Object)
   104  	if err != nil {
   105  		return nil, fmt.Errorf("error creating typedConfig: %w", err)
   106  	}
   107  	comparison, err := typedLive.Compare(typedConfig)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("error comparing typed resources: %w", err)
   110  	}
   111  	return &typedResults{
   112  		live:       typedLive,
   113  		config:     typedConfig,
   114  		comparison: comparison,
   115  	}, nil
   116  }
   117  
   118  // trustedManager will return true if trustedManagers contains curManager.
   119  // Returns false otherwise.
   120  func trustedManager(curManager string, trustedManagers []string) bool {
   121  	for _, m := range trustedManagers {
   122  		if m == curManager {
   123  			return true
   124  		}
   125  	}
   126  	return false
   127  }