Skip to content

Commit 877f705

Browse files
committed
ssa: add DriftIgnoreRules to ApplyOptions for field-level ignore on apply
Signed-off-by: Dipti Pai <diptipai89@outlook.com>
1 parent 90cfd68 commit 877f705

2 files changed

Lines changed: 1313 additions & 1 deletion

File tree

ssa/manager_apply.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"sigs.k8s.io/controller-runtime/pkg/client"
3535

3636
ssaerrors "github.com/fluxcd/pkg/ssa/errors"
37+
"github.com/fluxcd/pkg/ssa/jsondiff"
3738
"github.com/fluxcd/pkg/ssa/utils"
3839
)
3940

@@ -68,6 +69,12 @@ type ApplyOptions struct {
6869
// CustomStageKinds defines a set of Kubernetes resource types that should be applied
6970
// in a separate stage after CRDs and before namespaced objects.
7071
CustomStageKinds map[schema.GroupKind]struct{} `json:"customStageKinds,omitempty"`
72+
73+
// DriftIgnoreRules defines a list of JSON pointer ignore rules that are used to
74+
// remove specific fields from objects before applying them.
75+
// This is useful for ignoring fields that are managed by other controllers
76+
// (e.g. VPA, HPA) and would otherwise cause drift.
77+
DriftIgnoreRules []jsondiff.IgnoreRule `json:"driftIgnoreRules,omitempty"`
7178
}
7279

7380
// ApplyCleanupOptions defines which metadata entries are to be removed before applying objects.
@@ -133,6 +140,13 @@ func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstru
133140
}
134141

135142
appliedObject := object.DeepCopy()
143+
144+
if existingObject.GetResourceVersion() != "" && len(opts.DriftIgnoreRules) > 0 {
145+
if err := removeIgnoredFields(appliedObject, opts.DriftIgnoreRules); err != nil {
146+
return nil, err
147+
}
148+
}
149+
136150
if err := m.apply(ctx, appliedObject); err != nil {
137151
return nil, fmt.Errorf("%s apply failed: %w", utils.FmtUnstructured(appliedObject), err)
138152
}
@@ -232,9 +246,14 @@ func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.
232246
}
233247
}
234248

235-
for _, object := range toApply {
249+
for i, object := range toApply {
236250
if object != nil {
237251
appliedObject := object.DeepCopy()
252+
if changes[i].Action != CreatedAction && len(opts.DriftIgnoreRules) > 0 {
253+
if err := removeIgnoredFields(appliedObject, opts.DriftIgnoreRules); err != nil {
254+
return nil, err
255+
}
256+
}
238257
if err := m.apply(ctx, appliedObject); err != nil {
239258
return nil, fmt.Errorf("%s apply failed: %w", utils.FmtUnstructured(appliedObject), err)
240259
}
@@ -424,3 +443,33 @@ func (m *ResourceManager) shouldSkipApply(desiredObject *unstructured.Unstructur
424443

425444
return false
426445
}
446+
447+
// removeIgnoredFields removes the fields matched by the given ignore rules from the object.
448+
// For each rule, a SelectorRegex is compiled from the rule's Selector. If the selector matches
449+
// the object, the rule's Paths are collected and a JSON remove patch is applied to the object.
450+
func removeIgnoredFields(obj *unstructured.Unstructured, rules []jsondiff.IgnoreRule) error {
451+
sm := make(map[*jsondiff.SelectorRegex][]string, len(rules))
452+
for _, rule := range rules {
453+
sr, err := jsondiff.NewSelectorRegex(rule.Selector)
454+
if err != nil {
455+
return fmt.Errorf("failed to create ignore rule selector: %w", err)
456+
}
457+
sm[sr] = rule.Paths
458+
}
459+
460+
var ignorePaths jsondiff.IgnorePaths
461+
for sr, paths := range sm {
462+
if sr.MatchUnstructured(obj) {
463+
ignorePaths = append(ignorePaths, paths...)
464+
}
465+
}
466+
467+
if len(ignorePaths) > 0 {
468+
patch := jsondiff.GenerateRemovePatch(ignorePaths...)
469+
if err := jsondiff.ApplyPatchToUnstructured(obj, patch); err != nil {
470+
return err
471+
}
472+
}
473+
474+
return nil
475+
}

0 commit comments

Comments
 (0)