@@ -904,6 +904,11 @@ func TestServerSideDiff(t *testing.T) {
904
904
return opts
905
905
}
906
906
907
+ buildOptsWithNormalizer := func (predictedLive string , normalizer Normalizer ) []Option {
908
+ opts := buildOpts (predictedLive )
909
+ return append (opts , WithNormalizer (normalizer ))
910
+ }
911
+
907
912
t .Run ("will ignore modifications done by mutation webhook by default" , func (t * testing.T ) {
908
913
// given
909
914
t .Parallel ()
@@ -1061,6 +1066,61 @@ func TestServerSideDiff(t *testing.T) {
1061
1066
assert .False (t , predictedLabelExists )
1062
1067
assert .True (t , liveLabelExists )
1063
1068
})
1069
+
1070
+ t .Run ("will respect ignoreDifferences when full normalization is not skipped" , func (t * testing.T ) {
1071
+ // given
1072
+ t .Parallel ()
1073
+ liveState := StrToUnstructured (testdata .ServiceLiveYAMLSSD )
1074
+ desiredState := StrToUnstructured (testdata .ServiceConfigYAMLSSD )
1075
+
1076
+ // Normalizer that ignores sessionAffinity (auto-assigned field that's commonly ignored)
1077
+ normalizer := & testIgnoreDifferencesNormalizer {
1078
+ fieldsToRemove : [][]string {
1079
+ {"spec" , "sessionAffinity" },
1080
+ },
1081
+ }
1082
+
1083
+ opts := buildOptsWithNormalizer (testdata .ServicePredictedLiveJSONSSD , normalizer )
1084
+
1085
+ // when
1086
+ result , err := serverSideDiff (desiredState , liveState , opts ... )
1087
+
1088
+ // then
1089
+ require .NoError (t , err )
1090
+ assert .NotNil (t , result )
1091
+
1092
+ // Should show diff for other fields but not the ignored sessionAffinity
1093
+ assert .True (t , result .Modified , "Should show diff for non-ignored fields" )
1094
+
1095
+ // Convert results to strings for verification
1096
+ predictedLiveStr := string (result .PredictedLive )
1097
+ normalizedLiveStr := string (result .NormalizedLive )
1098
+
1099
+ // Ports should appear in diff (not ignored)
1100
+ assert .Contains (t , predictedLiveStr , "port" , "Port differences should be visible" )
1101
+
1102
+ // The ignored sessionAffinity should NOT appear in final result
1103
+ assert .NotContains (t , predictedLiveStr , "sessionAffinity" , "sessionAffinity should be removed by normalization" )
1104
+ assert .NotContains (t , normalizedLiveStr , "sessionAffinity" , "sessionAffinity should be removed by normalization" )
1105
+
1106
+ // Other fields should still be visible (not ignored)
1107
+ assert .Contains (t , predictedLiveStr , "selector" , "Other fields should remain visible" )
1108
+ })
1109
+ }
1110
+
1111
+ // testIgnoreDifferencesNormalizer implements a simple normalizer that removes specified fields
1112
+ type testIgnoreDifferencesNormalizer struct {
1113
+ fieldsToRemove [][]string
1114
+ }
1115
+
1116
+ func (n * testIgnoreDifferencesNormalizer ) Normalize (un * unstructured.Unstructured ) error {
1117
+ if un == nil {
1118
+ return nil
1119
+ }
1120
+ for _ , fieldPath := range n .fieldsToRemove {
1121
+ unstructured .RemoveNestedField (un .Object , fieldPath ... )
1122
+ }
1123
+ return nil
1064
1124
}
1065
1125
1066
1126
func createSecret (data map [string ]string ) * unstructured.Unstructured {
@@ -1613,3 +1673,138 @@ func StrToUnstructured(yamlStr string) *unstructured.Unstructured {
1613
1673
}
1614
1674
return & unstructured.Unstructured {Object : obj }
1615
1675
}
1676
+
1677
+ func TestDiffWithIgnoreDifferences (t * testing.T ) {
1678
+ t .Run ("TwoWayDiff will respect ignoreDifferences for comparison but not output normalization" , func (t * testing.T ) {
1679
+ // given
1680
+ t .Parallel ()
1681
+
1682
+ // Create a simple service with sessionAffinity that should be ignored
1683
+ liveService := StrToUnstructured (`
1684
+ apiVersion: v1
1685
+ kind: Service
1686
+ metadata:
1687
+ name: my-service
1688
+ spec:
1689
+ selector:
1690
+ app: my-app
1691
+ ports:
1692
+ - port: 80
1693
+ sessionAffinity: None
1694
+ type: ClusterIP
1695
+ ` )
1696
+
1697
+ desiredService := StrToUnstructured (`
1698
+ apiVersion: v1
1699
+ kind: Service
1700
+ metadata:
1701
+ name: my-service
1702
+ spec:
1703
+ selector:
1704
+ app: my-app
1705
+ ports:
1706
+ - port: 80
1707
+ sessionAffinity: ClientIP
1708
+ type: ClusterIP
1709
+ ` )
1710
+
1711
+ // Normalizer that ignores sessionAffinity
1712
+ normalizer := & testIgnoreDifferencesNormalizer {
1713
+ fieldsToRemove : [][]string {
1714
+ {"spec" , "sessionAffinity" },
1715
+ },
1716
+ }
1717
+
1718
+ opts := []Option {
1719
+ WithNormalizer (normalizer ),
1720
+ WithLogr (textlogger .NewLogger (textlogger .NewConfig ())),
1721
+ }
1722
+
1723
+ // when
1724
+ result , err := Diff (desiredService , liveService , opts ... )
1725
+ require .NoError (t , err )
1726
+
1727
+ // then
1728
+ assert .NotNil (t , result )
1729
+
1730
+ // Since sessionAffinity is ignored in input normalization, there should be no modification
1731
+ assert .False (t , result .Modified , "Should not show diff for ignored fields" )
1732
+
1733
+ predictedLiveStr := string (result .PredictedLive )
1734
+ normalizedLiveStr := string (result .NormalizedLive )
1735
+
1736
+ // NOTE: Unlike server-side diff, TwoWayDiff/ThreeWayDiff don't normalize outputs
1737
+ // So sessionAffinity WILL still appear in the output bytes, but Modified should be false
1738
+ // because input normalization removed the differences during comparison
1739
+ assert .Contains (t , predictedLiveStr , "sessionAffinity" , "sessionAffinity should still appear in output (no output normalization)" )
1740
+ assert .Contains (t , normalizedLiveStr , "sessionAffinity" , "sessionAffinity should still appear in output (no output normalization)" )
1741
+ })
1742
+
1743
+ t .Run ("ThreeWayDiff will respect ignoreDifferences for comparison but not output normalization" , func (t * testing.T ) {
1744
+ // given
1745
+ t .Parallel ()
1746
+
1747
+ // Create config and live with sessionAffinity differences that should be ignored
1748
+ configService := StrToUnstructured (`
1749
+ apiVersion: v1
1750
+ kind: Service
1751
+ metadata:
1752
+ name: my-service
1753
+ spec:
1754
+ selector:
1755
+ app: my-app
1756
+ ports:
1757
+ - port: 80
1758
+ sessionAffinity: ClientIP
1759
+ type: ClusterIP
1760
+ ` )
1761
+
1762
+ liveService := StrToUnstructured (`
1763
+ apiVersion: v1
1764
+ kind: Service
1765
+ metadata:
1766
+ name: my-service
1767
+ annotations:
1768
+ kubectl.kubernetes.io/last-applied-configuration: |
1769
+ {"apiVersion":"v1","kind":"Service","metadata":{"name":"my-service"},"spec":{"selector":{"app":"my-app"},"ports":[{"port":80}],"sessionAffinity":"None","type":"ClusterIP"}}
1770
+ spec:
1771
+ selector:
1772
+ app: my-app
1773
+ ports:
1774
+ - port: 80
1775
+ sessionAffinity: None
1776
+ type: ClusterIP
1777
+ ` )
1778
+
1779
+ // Normalizer that ignores sessionAffinity
1780
+ normalizer := & testIgnoreDifferencesNormalizer {
1781
+ fieldsToRemove : [][]string {
1782
+ {"spec" , "sessionAffinity" },
1783
+ },
1784
+ }
1785
+
1786
+ opts := []Option {
1787
+ WithNormalizer (normalizer ),
1788
+ WithLogr (textlogger .NewLogger (textlogger .NewConfig ())),
1789
+ }
1790
+
1791
+ // when
1792
+ result , err := Diff (configService , liveService , opts ... )
1793
+ require .NoError (t , err )
1794
+
1795
+ // then
1796
+ assert .NotNil (t , result )
1797
+
1798
+ // Since sessionAffinity is ignored in input normalization, there should be no modification
1799
+ assert .False (t , result .Modified , "Should not show diff for ignored fields" )
1800
+
1801
+ predictedLiveStr := string (result .PredictedLive )
1802
+ normalizedLiveStr := string (result .NormalizedLive )
1803
+
1804
+ // NOTE: Unlike server-side diff, TwoWayDiff/ThreeWayDiff don't normalize outputs
1805
+ // So sessionAffinity WILL still appear in the output bytes, but Modified should be false
1806
+ // because input normalization removed the differences during comparison
1807
+ assert .Contains (t , predictedLiveStr , "sessionAffinity" , "sessionAffinity should still appear in output (no output normalization)" )
1808
+ assert .Contains (t , normalizedLiveStr , "sessionAffinity" , "sessionAffinity should still appear in output (no output normalization)" )
1809
+ })
1810
+ }
0 commit comments