Skip to content

Commit e62204a

Browse files
Improve topology diff
1 parent 346399e commit e62204a

File tree

5 files changed

+189
-61
lines changed

5 files changed

+189
-61
lines changed

internal/controllers/topology/cluster/reconcile_state.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,12 @@ func (r *Reconciler) reconcileCluster(ctx context.Context, s *scope.Scope) error
524524
return nil
525525
}
526526

527-
changes := patchHelper.Changes()
528-
if len(changes) == 0 {
527+
diff := patchHelper.Diff()
528+
patchData := patchHelper.PatchData()
529+
if diff == "" && patchData == "" {
529530
log.Info("Patching Cluster")
530531
} else {
531-
log.Info("Patching Cluster", "diff", string(changes))
532+
log.Info("Patching Cluster", "diff", diff, "patch", patchData)
532533
}
533534
modifiedResourceVersion, err := patchHelper.Patch(ctx)
534535
if err != nil {
@@ -808,11 +809,12 @@ func (r *Reconciler) updateMachineDeployment(ctx context.Context, s *scope.Scope
808809
return nil
809810
}
810811

811-
changes := patchHelper.Changes()
812-
if len(changes) == 0 {
812+
diff := patchHelper.Diff()
813+
patchData := patchHelper.PatchData()
814+
if diff == "" && patchData == "" {
813815
log.Info("Patching MachineDeployment")
814816
} else {
815-
log.Info("Patching MachineDeployment", "diff", string(changes))
817+
log.Info("Patching MachineDeployment", "diff", diff, "patch", patchData)
816818
}
817819
modifiedResourceVersion, err := patchHelper.Patch(ctx)
818820
if err != nil {
@@ -1072,11 +1074,12 @@ func (r *Reconciler) updateMachinePool(ctx context.Context, s *scope.Scope, mpTo
10721074
return nil
10731075
}
10741076

1075-
changes := patchHelper.Changes()
1076-
if len(changes) == 0 {
1077+
diff := patchHelper.Diff()
1078+
patchData := patchHelper.PatchData()
1079+
if diff == "" && patchData == "" {
10771080
log.Info("Patching MachinePool")
10781081
} else {
1079-
log.Info("Patching MachinePool", "diff", string(changes))
1082+
log.Info("Patching MachinePool", "diff", diff, "patch", patchData)
10801083
}
10811084
modifiedResourceVersion, err := patchHelper.Patch(ctx)
10821085
if err != nil {
@@ -1222,11 +1225,12 @@ func (r *Reconciler) reconcileReferencedObject(ctx context.Context, in reconcile
12221225
return false, nil
12231226
}
12241227

1225-
changes := patchHelper.Changes()
1226-
if len(changes) == 0 {
1228+
diff := patchHelper.Diff()
1229+
patchData := patchHelper.PatchData()
1230+
if diff == "" && patchData == "" {
12271231
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()))
12281232
} else {
1229-
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()), "diff", string(changes))
1233+
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()), "diff", diff, "patch", patchData)
12301234
}
12311235
if _, err := patchHelper.Patch(ctx); err != nil {
12321236
return false, errors.Wrapf(err, "failed to patch %s %s", in.current.GetKind(), klog.KObj(in.current))
@@ -1317,11 +1321,12 @@ func (r *Reconciler) reconcileReferencedTemplate(ctx context.Context, in reconci
13171321
// If there are no changes in the spec, and thus only changes in metadata, instead of doing a full template
13181322
// rotation we patch the object in place. This avoids recreating machines.
13191323
if !patchHelper.HasSpecChanges() {
1320-
changes := patchHelper.Changes()
1321-
if len(changes) == 0 {
1324+
diff := patchHelper.Diff()
1325+
patchData := patchHelper.PatchData()
1326+
if diff == "" && patchData == "" {
13221327
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()))
13231328
} else {
1324-
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()), "diff", string(changes))
1329+
log.Info(fmt.Sprintf("Patching %s", in.desired.GetKind()), "diff", diff, "patch", patchData)
13251330
}
13261331
if _, err := patchHelper.Patch(ctx); err != nil {
13271332
return false, errors.Wrapf(err, "failed to patch %s %s", in.desired.GetKind(), klog.KObj(in.desired))
@@ -1337,11 +1342,12 @@ func (r *Reconciler) reconcileReferencedTemplate(ctx context.Context, in reconci
13371342
newName := names.SimpleNameGenerator.GenerateName(in.templateNamePrefix)
13381343
in.desired.SetName(newName)
13391344

1340-
changes := patchHelper.Changes()
1341-
if len(changes) == 0 {
1345+
diff := patchHelper.Diff()
1346+
patchData := patchHelper.PatchData()
1347+
if diff == "" && patchData == "" {
13421348
log.Info(fmt.Sprintf("Rotating %s, new name %s", in.current.GetKind(), newName))
13431349
} else {
1344-
log.Info(fmt.Sprintf("Rotating %s, new name %s", in.current.GetKind(), newName), "diff", string(changes))
1350+
log.Info(fmt.Sprintf("Rotating %s, new name %s", in.current.GetKind(), newName), "diff", diff, "patch", patchData)
13451351
}
13461352
log.Info(fmt.Sprintf("Creating %s", in.current.GetKind()))
13471353
helper, err := structuredmerge.NewServerSidePatchHelper(ctx, nil, in.desired, r.Client, r.ssaCache)

internal/controllers/topology/cluster/structuredmerge/dryrun.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ package structuredmerge
1919
import (
2020
"context"
2121
"encoding/json"
22+
"strings"
2223

2324
jsonpatch "github.com/evanphx/json-patch/v5"
25+
"github.com/google/go-cmp/cmp"
2426
"github.com/pkg/errors"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2729
"sigs.k8s.io/controller-runtime/pkg/client"
30+
"sigs.k8s.io/yaml"
2831

2932
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
3033
"sigs.k8s.io/cluster-api/internal/contract"
@@ -47,7 +50,7 @@ type dryRunSSAPatchInput struct {
4750
}
4851

4952
// dryRunSSAPatch uses server side apply dry run to determine if the operation is going to change the actual object.
50-
func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool, bool, []byte, error) {
53+
func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool, bool, string, string, error) {
5154
// Compute a request identifier.
5255
// The identifier is unique for a specific request to ensure we don't have to re-run the request
5356
// once we found out that it would not produce a diff.
@@ -56,13 +59,13 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
5659
// This ensures that we re-run the request as soon as either original or modified changes.
5760
requestIdentifier, err := ssa.ComputeRequestIdentifier(dryRunCtx.client.Scheme(), dryRunCtx.originalUnstructured.GetResourceVersion(), dryRunCtx.modifiedUnstructured)
5861
if err != nil {
59-
return false, false, nil, err
62+
return false, false, "", "", err
6063
}
6164

6265
// Check if we already ran this request before by checking if the cache already contains this identifier.
6366
// Note: We only add an identifier to the cache if the result of the dry run was no diff.
6467
if exists := dryRunCtx.ssaCache.Has(requestIdentifier, dryRunCtx.originalUnstructured.GetKind()); exists {
65-
return false, false, nil, nil
68+
return false, false, "", "", nil
6669
}
6770

6871
// For dry run we use the same options as for the intent but with adding metadata.managedFields
@@ -74,17 +77,17 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
7477

7578
// Add TopologyDryRunAnnotation to notify validation webhooks to skip immutability checks.
7679
if err := unstructured.SetNestedField(dryRunCtx.originalUnstructured.Object, "", "metadata", "annotations", clusterv1.TopologyDryRunAnnotation); err != nil {
77-
return false, false, nil, errors.Wrap(err, "failed to add topology dry-run annotation to original object")
80+
return false, false, "", "", errors.Wrap(err, "failed to add topology dry-run annotation to original object")
7881
}
7982
if err := unstructured.SetNestedField(dryRunCtx.modifiedUnstructured.Object, "", "metadata", "annotations", clusterv1.TopologyDryRunAnnotation); err != nil {
80-
return false, false, nil, errors.Wrap(err, "failed to add topology dry-run annotation to modified object")
83+
return false, false, "", "", errors.Wrap(err, "failed to add topology dry-run annotation to modified object")
8184
}
8285

8386
// Do a server-side apply dry-run with modifiedUnstructured to get the updated object.
8487
err = dryRunCtx.client.Apply(ctx, client.ApplyConfigurationFromUnstructured(dryRunCtx.modifiedUnstructured), client.DryRunAll, client.FieldOwner(TopologyManagerName), client.ForceOwnership)
8588
if err != nil {
8689
// This catches errors like metadata.uid changes.
87-
return false, false, nil, errors.Wrap(err, "server side apply dry-run failed for modified object")
90+
return false, false, "", "", errors.Wrap(err, "server side apply dry-run failed for modified object")
8891
}
8992

9093
// Do a server-side apply dry-run with originalUnstructured to ensure the latest defaulting is applied.
@@ -109,7 +112,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
109112
dryRunCtx.originalUnstructured.SetManagedFields(nil)
110113
err = dryRunCtx.client.Apply(ctx, client.ApplyConfigurationFromUnstructured(dryRunCtx.originalUnstructured), client.DryRunAll, client.FieldOwner(TopologyManagerName), client.ForceOwnership)
111114
if err != nil {
112-
return false, false, nil, errors.Wrap(err, "server side apply dry-run failed for original object")
115+
return false, false, "", "", errors.Wrap(err, "server side apply dry-run failed for original object")
113116
}
114117
// Restore managed fields.
115118
dryRunCtx.originalUnstructured.SetManagedFields(originalUnstructuredManagedFieldsBeforeSSA)
@@ -124,7 +127,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
124127
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
125128
// this would affect our managed fields as well and we would still detect it by diffing our managed fields.
126129
if err := cleanupManagedFieldsAndAnnotation(dryRunCtx.modifiedUnstructured); err != nil {
127-
return false, false, nil, errors.Wrap(err, "failed to filter topology dry-run annotation on modified object")
130+
return false, false, "", "", errors.Wrap(err, "failed to filter topology dry-run annotation on modified object")
128131
}
129132

130133
// Also run the function for the originalUnstructured to remove the managedField
@@ -135,7 +138,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
135138
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
136139
// this would affect our managed fields as well and we would still detect it by diffing our managed fields.
137140
if err := cleanupManagedFieldsAndAnnotation(dryRunCtx.originalUnstructured); err != nil {
138-
return false, false, nil, errors.Wrap(err, "failed to filter topology dry-run annotation on original object")
141+
return false, false, "", "", errors.Wrap(err, "failed to filter topology dry-run annotation on original object")
139142
}
140143

141144
// Drop the other fields which are not part of our intent.
@@ -145,28 +148,28 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
145148
// Compare the output of dry run to the original object.
146149
originalJSON, err := json.Marshal(dryRunCtx.originalUnstructured)
147150
if err != nil {
148-
return false, false, nil, err
151+
return false, false, "", "", err
149152
}
150153
modifiedJSON, err := json.Marshal(dryRunCtx.modifiedUnstructured)
151154
if err != nil {
152-
return false, false, nil, err
155+
return false, false, "", "", err
153156
}
154157

155158
rawDiff, err := jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
156159
if err != nil {
157-
return false, false, nil, err
160+
return false, false, "", "", err
158161
}
159162

160163
// Determine if there are changes to the spec and object.
161164
diff := &unstructured.Unstructured{}
162165
if err := json.Unmarshal(rawDiff, &diff.Object); err != nil {
163-
return false, false, nil, err
166+
return false, false, "", "", err
164167
}
165168

166169
hasChanges := len(diff.Object) > 0
167170
_, hasSpecChanges := diff.Object["spec"]
168171

169-
var changes []byte
172+
var patchString, diffString string
170173
if hasChanges {
171174
// Cleanup diff by dropping .metadata.managedFields.
172175
ssa.FilterIntent(&ssa.FilterIntentInput{
@@ -177,10 +180,30 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
177180

178181
// changes should be empty (not "{}") if diff.Object is empty
179182
if len(diff.Object) != 0 {
180-
changes, err = json.Marshal(diff.Object)
183+
patchBytes, err := json.Marshal(diff.Object)
181184
if err != nil {
182-
return false, false, nil, errors.Wrapf(err, "failed to marshal diff")
185+
return false, false, "", "", errors.Wrapf(err, "failed to marshal diff")
183186
}
187+
patchString = string(patchBytes)
188+
189+
originalJSONWithChanges, err := jsonpatch.MergePatch(originalJSON, patchBytes)
190+
if err != nil {
191+
return false, false, "", "", errors.Wrapf(err, "failed to apply diff to original object")
192+
}
193+
194+
originalYAML, err := yaml.JSONToYAML(originalJSON)
195+
if err != nil {
196+
return false, false, "", "", errors.Wrapf(err, "failed to apply diff to original object")
197+
}
198+
199+
originalYAMLWithChanges, err := yaml.JSONToYAML(originalJSONWithChanges)
200+
if err != nil {
201+
return false, false, "", "", errors.Wrapf(err, "failed to apply diff to original object")
202+
}
203+
204+
diffString = cmp.Diff(string(originalYAML), string(originalYAMLWithChanges))
205+
diffString = strings.ReplaceAll(diffString, "\u00A0", " ")
206+
diffString = strings.ReplaceAll(diffString, "\t", " ")
184207
}
185208
}
186209

@@ -189,7 +212,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
189212
dryRunCtx.ssaCache.Add(requestIdentifier)
190213
}
191214

192-
return hasChanges, hasSpecChanges, changes, nil
215+
return hasChanges, hasSpecChanges, patchString, diffString, nil
193216
}
194217

195218
// cleanupManagedFieldsAndAnnotation adjusts the obj to remove the topology.cluster.x-k8s.io/dry-run

internal/controllers/topology/cluster/structuredmerge/interfaces.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ type PatchHelper interface {
3737
// HasSpecChanges return true if the modified object is generating spec changes vs the original object.
3838
HasSpecChanges() bool
3939

40-
// Changes returns the changes vs the original object.
41-
Changes() []byte
40+
// PatchData return the patch that will be applied.
41+
PatchData() string
42+
43+
// Diff return the diff between original and modified.
44+
Diff() string
4245

4346
// Patch patches the given obj in the Kubernetes cluster.
4447
Patch(ctx context.Context) (modifiedResourceVersion string, err error)

internal/controllers/topology/cluster/structuredmerge/serversidepathhelper.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ type serverSidePatchHelper struct {
3636
modified *unstructured.Unstructured
3737
hasChanges bool
3838
hasSpecChanges bool
39-
changes []byte
39+
patch string
40+
diff string
4041
}
4142

4243
// NewServerSidePatchHelper returns a new PatchHelper using server side apply.
@@ -90,13 +91,13 @@ func NewServerSidePatchHelper(ctx context.Context, original, modified client.Obj
9091
// Determine if the intent defined in the modified object is going to trigger
9192
// an actual change when running server side apply, and if this change might impact the object spec or not.
9293
var hasChanges, hasSpecChanges bool
93-
var changes []byte
94+
var patch, diff string
9495
switch {
9596
case util.IsNil(original):
9697
hasChanges, hasSpecChanges = true, true
9798
default:
9899
var err error
99-
hasChanges, hasSpecChanges, changes, err = dryRunSSAPatch(ctx, &dryRunSSAPatchInput{
100+
hasChanges, hasSpecChanges, patch, diff, err = dryRunSSAPatch(ctx, &dryRunSSAPatchInput{
100101
client: c,
101102
ssaCache: ssaCache,
102103
originalUnstructured: originalUnstructured,
@@ -113,7 +114,8 @@ func NewServerSidePatchHelper(ctx context.Context, original, modified client.Obj
113114
modified: modifiedUnstructured,
114115
hasChanges: hasChanges,
115116
hasSpecChanges: hasSpecChanges,
116-
changes: changes,
117+
patch: patch,
118+
diff: diff,
117119
}, nil
118120
}
119121

@@ -122,9 +124,14 @@ func (h *serverSidePatchHelper) HasSpecChanges() bool {
122124
return h.hasSpecChanges
123125
}
124126

125-
// Changes return the changes.
126-
func (h *serverSidePatchHelper) Changes() []byte {
127-
return h.changes
127+
// PatchData return the patch that will be applied.
128+
func (h *serverSidePatchHelper) PatchData() string {
129+
return h.patch
130+
}
131+
132+
// Diff return the diff between original and modified.
133+
func (h *serverSidePatchHelper) Diff() string {
134+
return h.diff
128135
}
129136

130137
// HasChanges return true if the patch has changes.

0 commit comments

Comments
 (0)