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

     1  package diff_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
     9  
    10  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    11  	testutil "github.com/argoproj/argo-cd/v3/test"
    12  	argo "github.com/argoproj/argo-cd/v3/util/argo/diff"
    13  	"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
    14  	"github.com/argoproj/argo-cd/v3/util/argo/testdata"
    15  	appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
    16  )
    17  
    18  func TestStateDiff(t *testing.T) {
    19  	type diffConfigParams struct {
    20  		ignores        []v1alpha1.ResourceIgnoreDifferences
    21  		overrides      map[string]v1alpha1.ResourceOverride
    22  		label          string
    23  		trackingMethod string
    24  		ignoreRoles    bool
    25  	}
    26  	defaultDiffConfigParams := func() *diffConfigParams {
    27  		return &diffConfigParams{
    28  			ignores:        []v1alpha1.ResourceIgnoreDifferences{},
    29  			overrides:      map[string]v1alpha1.ResourceOverride{},
    30  			label:          "",
    31  			trackingMethod: "",
    32  			ignoreRoles:    true,
    33  		}
    34  	}
    35  	diffConfig := func(t *testing.T, params *diffConfigParams) argo.DiffConfig {
    36  		t.Helper()
    37  		diffConfig, err := argo.NewDiffConfigBuilder().
    38  			WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
    39  			WithTracking(params.label, params.trackingMethod).
    40  			WithNoCache().
    41  			Build()
    42  		require.NoError(t, err)
    43  		return diffConfig
    44  	}
    45  	type testcase struct {
    46  		name                       string
    47  		params                     func() *diffConfigParams
    48  		desiredState               *unstructured.Unstructured
    49  		liveState                  *unstructured.Unstructured
    50  		expectedNormalizedReplicas int
    51  		expectedPredictedReplicas  int
    52  	}
    53  	testcases := []*testcase{
    54  		{
    55  			name: "will normalize replica field if owned by trusted manager",
    56  			params: func() *diffConfigParams {
    57  				params := defaultDiffConfigParams()
    58  				params.ignores = []v1alpha1.ResourceIgnoreDifferences{
    59  					{
    60  						Group:                 "*",
    61  						Kind:                  "*",
    62  						ManagedFieldsManagers: []string{"kube-controller-manager"},
    63  					},
    64  				}
    65  				return params
    66  			},
    67  			desiredState:               testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
    68  			liveState:                  testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
    69  			expectedNormalizedReplicas: 1,
    70  			expectedPredictedReplicas:  1,
    71  		},
    72  		{
    73  			name: "will keep replica field not owned by trusted manager",
    74  			params: func() *diffConfigParams {
    75  				params := defaultDiffConfigParams()
    76  				params.ignores = []v1alpha1.ResourceIgnoreDifferences{
    77  					{
    78  						Group:                 "*",
    79  						Kind:                  "*",
    80  						ManagedFieldsManagers: []string{"some-other-manager"},
    81  					},
    82  				}
    83  				return params
    84  			},
    85  			desiredState:               testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
    86  			liveState:                  testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
    87  			expectedNormalizedReplicas: 2,
    88  			expectedPredictedReplicas:  3,
    89  		},
    90  		{
    91  			name: "will normalize replica field if configured with json pointers",
    92  			params: func() *diffConfigParams {
    93  				params := defaultDiffConfigParams()
    94  				params.ignores = []v1alpha1.ResourceIgnoreDifferences{
    95  					{
    96  						Group:        "*",
    97  						Kind:         "*",
    98  						JSONPointers: []string{"/spec/replicas"},
    99  					},
   100  				}
   101  				return params
   102  			},
   103  			desiredState:               testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
   104  			liveState:                  testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
   105  			expectedNormalizedReplicas: 1,
   106  			expectedPredictedReplicas:  1,
   107  		},
   108  		{
   109  			name: "will normalize replica field if configured with jq expression",
   110  			params: func() *diffConfigParams {
   111  				params := defaultDiffConfigParams()
   112  				params.ignores = []v1alpha1.ResourceIgnoreDifferences{
   113  					{
   114  						Group:             "*",
   115  						Kind:              "*",
   116  						JQPathExpressions: []string{".spec.replicas"},
   117  					},
   118  				}
   119  				return params
   120  			},
   121  			desiredState:               testutil.YamlToUnstructured(testdata.DesiredDeploymentYaml),
   122  			liveState:                  testutil.YamlToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml),
   123  			expectedNormalizedReplicas: 1,
   124  			expectedPredictedReplicas:  1,
   125  		},
   126  	}
   127  	for _, tc := range testcases {
   128  		tc := tc
   129  		t.Run(tc.name, func(t *testing.T) {
   130  			// given
   131  			dc := diffConfig(t, tc.params())
   132  
   133  			// when
   134  			result, err := argo.StateDiff(tc.liveState, tc.desiredState, dc)
   135  
   136  			// then
   137  			require.NoError(t, err)
   138  			assert.NotNil(t, result)
   139  			assert.True(t, result.Modified)
   140  			normalized := testutil.YamlToUnstructured(string(result.NormalizedLive))
   141  			replicas, found, err := unstructured.NestedFloat64(normalized.Object, "spec", "replicas")
   142  			require.NoError(t, err)
   143  			assert.True(t, found)
   144  			assert.InEpsilon(t, float64(tc.expectedNormalizedReplicas), replicas, 0.0001)
   145  			predicted := testutil.YamlToUnstructured(string(result.PredictedLive))
   146  			predictedReplicas, found, err := unstructured.NestedFloat64(predicted.Object, "spec", "replicas")
   147  			require.NoError(t, err)
   148  			assert.True(t, found)
   149  			assert.InEpsilon(t, float64(tc.expectedPredictedReplicas), predictedReplicas, 0.0001)
   150  		})
   151  	}
   152  }
   153  
   154  func TestDiffConfigBuilder(t *testing.T) {
   155  	type fixture struct {
   156  		ignores        []v1alpha1.ResourceIgnoreDifferences
   157  		overrides      map[string]v1alpha1.ResourceOverride
   158  		label          string
   159  		trackingMethod string
   160  		noCache        bool
   161  		ignoreRoles    bool
   162  		appName        string
   163  	}
   164  	setup := func() *fixture {
   165  		return &fixture{
   166  			ignores:        []v1alpha1.ResourceIgnoreDifferences{},
   167  			overrides:      make(map[string]v1alpha1.ResourceOverride),
   168  			label:          "some-label",
   169  			trackingMethod: "tracking-method",
   170  			noCache:        true,
   171  			ignoreRoles:    false,
   172  			appName:        "application-name",
   173  		}
   174  	}
   175  	t.Run("will build diff config successfully", func(t *testing.T) {
   176  		// given
   177  		f := setup()
   178  
   179  		// when
   180  		diffConfig, err := argo.NewDiffConfigBuilder().
   181  			WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
   182  			WithTracking(f.label, f.trackingMethod).
   183  			WithNoCache().
   184  			Build()
   185  
   186  		// then
   187  		require.NoError(t, err)
   188  		require.NotNil(t, diffConfig)
   189  		assert.Empty(t, diffConfig.Ignores())
   190  		assert.Empty(t, diffConfig.Overrides())
   191  		assert.Equal(t, f.label, diffConfig.AppLabelKey())
   192  		assert.Equal(t, f.overrides, diffConfig.Overrides())
   193  		assert.Equal(t, f.trackingMethod, diffConfig.TrackingMethod())
   194  		assert.Equal(t, f.noCache, diffConfig.NoCache())
   195  		assert.Equal(t, f.ignoreRoles, diffConfig.IgnoreAggregatedRoles())
   196  		assert.Empty(t, diffConfig.AppName())
   197  		assert.Nil(t, diffConfig.StateCache())
   198  	})
   199  	t.Run("will initialize ignore differences if nil is passed", func(t *testing.T) {
   200  		// given
   201  		f := setup()
   202  
   203  		// when
   204  		diffConfig, err := argo.NewDiffConfigBuilder().
   205  			WithDiffSettings(nil, nil, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
   206  			WithTracking(f.label, f.trackingMethod).
   207  			WithNoCache().
   208  			Build()
   209  
   210  		// then
   211  		require.NoError(t, err)
   212  		require.NotNil(t, diffConfig)
   213  		assert.Empty(t, diffConfig.Ignores())
   214  		assert.Empty(t, diffConfig.Overrides())
   215  		assert.Equal(t, f.label, diffConfig.AppLabelKey())
   216  		assert.Equal(t, f.overrides, diffConfig.Overrides())
   217  		assert.Equal(t, f.trackingMethod, diffConfig.TrackingMethod())
   218  		assert.Equal(t, f.noCache, diffConfig.NoCache())
   219  		assert.Equal(t, f.ignoreRoles, diffConfig.IgnoreAggregatedRoles())
   220  	})
   221  	t.Run("will return error if retrieving diff from cache an no appName configured", func(t *testing.T) {
   222  		// given
   223  		f := setup()
   224  
   225  		// when
   226  		diffConfig, err := argo.NewDiffConfigBuilder().
   227  			WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
   228  			WithTracking(f.label, f.trackingMethod).
   229  			WithCache(&appstatecache.Cache{}, "").
   230  			Build()
   231  
   232  		// then
   233  		require.Error(t, err)
   234  		require.Nil(t, diffConfig)
   235  	})
   236  	t.Run("will return error if retrieving diff from cache and no stateCache configured", func(t *testing.T) {
   237  		// given
   238  		f := setup()
   239  
   240  		// when
   241  		diffConfig, err := argo.NewDiffConfigBuilder().
   242  			WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
   243  			WithTracking(f.label, f.trackingMethod).
   244  			WithCache(nil, f.appName).
   245  			Build()
   246  
   247  		// then
   248  		require.Error(t, err)
   249  		require.Nil(t, diffConfig)
   250  	})
   251  }