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 }