@@ -17,6 +17,8 @@ limitations under the License.
1717package ssa
1818
1919import (
20+ "reflect"
21+
2022 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2123
2224 "sigs.k8s.io/cluster-api/internal/contract"
@@ -32,26 +34,43 @@ type FilterObjectInput struct {
3234 // spec.ControlPlaneEndpoint.
3335 // NOTE: ignore paths which point to an array are not supported by the current implementation.
3436 IgnorePaths []contract.Path
37+
38+ // DropEmptyStruct instruct the Helper to drop all fields with values equals to empty struct.
39+ // NOTE: This is required when using typed objects, because the DefaultUnstructuredConverter does
40+ // not handle omitzero (yet).
41+ DropEmptyStruct bool
3542}
3643
3744// FilterObject filter out changes not relevant for the controller.
3845func FilterObject (obj * unstructured.Unstructured , input * FilterObjectInput ) {
3946 // filter out changes not in the allowed paths (fields to not consider, e.g. status);
47+ // also drop empty struct if required.
4048 if len (input .AllowedPaths ) > 0 {
4149 FilterIntent (& FilterIntentInput {
42- Path : contract.Path {},
43- Value : obj .Object ,
44- ShouldFilter : IsPathNotAllowed (input .AllowedPaths ),
50+ Path : contract.Path {},
51+ Value : obj .Object ,
52+ ShouldFilter : IsPathNotAllowed (input .AllowedPaths ),
53+ DropEmptyStruct : input .DropEmptyStruct ,
4554 })
4655 }
4756
4857 // filter out changes for ignore paths (well known fields owned by other controllers, e.g.
49- // spec.controlPlaneEndpoint in the InfrastructureCluster object);
58+ // spec.controlPlaneEndpoint in the InfrastructureCluster object); also drop empty struct if required.
5059 if len (input .IgnorePaths ) > 0 {
5160 FilterIntent (& FilterIntentInput {
52- Path : contract.Path {},
53- Value : obj .Object ,
54- ShouldFilter : IsPathIgnored (input .IgnorePaths ),
61+ Path : contract.Path {},
62+ Value : obj .Object ,
63+ ShouldFilter : IsPathIgnored (input .IgnorePaths ),
64+ DropEmptyStruct : input .DropEmptyStruct ,
65+ })
66+ }
67+
68+ // DropEmptyStruct if not already done above.
69+ if input .DropEmptyStruct && len (input .AllowedPaths ) == 0 && len (input .IgnorePaths ) == 0 {
70+ FilterIntent (& FilterIntentInput {
71+ Path : contract.Path {},
72+ Value : obj .Object ,
73+ DropEmptyStruct : input .DropEmptyStruct ,
5574 })
5675 }
5776}
@@ -76,11 +95,19 @@ func FilterIntent(ctx *FilterIntentInput) bool {
7695 // Gets the original and the modified Value for the field.
7796 Value : value [field ],
7897 // Carry over global values from the context.
79- ShouldFilter : ctx .ShouldFilter ,
98+ ShouldFilter : ctx .ShouldFilter ,
99+ DropEmptyStruct : ctx .DropEmptyStruct ,
80100 }
81101
82102 // If the field should be filtered out, delete it from the modified object.
83- if fieldCtx .ShouldFilter (fieldCtx .Path ) {
103+ if fieldCtx .ShouldFilter != nil && fieldCtx .ShouldFilter (fieldCtx .Path ) {
104+ delete (value , field )
105+ gotDeletions = true
106+ continue
107+ }
108+
109+ // If empty struct should be dropped and the value is a empty struct, delete it from the modified object.
110+ if fieldCtx .DropEmptyStruct && reflect .DeepEqual (fieldCtx .Value , map [string ]interface {}{}) {
84111 delete (value , field )
85112 gotDeletions = true
86113 continue
@@ -109,6 +136,8 @@ type FilterIntentInput struct {
109136
110137 // ShouldFilter handle the func that determine if the current Path should be dropped or not.
111138 ShouldFilter func (path contract.Path ) bool
139+
140+ DropEmptyStruct bool
112141}
113142
114143// IsPathAllowed returns true when the Path is one of the AllowedPaths.
0 commit comments