Files
argo-cd/util/argo/normalizers/diff_normalizer.go
Alexandre Gaudreault 88994ea5cd feat: add ignoreResourceUpdates to reduce controller CPU usage (#13534) (#13912)
* feat: ignore watched resource update

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add doc and CLI

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update doc index

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add command

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* codegen

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* revert formatting

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* do not skip on health change

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update doc

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update logging to use context

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* fix typos. local build broken...

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* change after review

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* manifestHash to string

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* more doc

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* example for argoproj Application

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add unit test for ignored logs

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* codegen

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update docs/operator-manual/reconcile.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* move hash and set log to debug

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update util/settings/settings.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update util/settings/settings.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* feature flag

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* fix

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* less aggressive managedFields ignore rule

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* Update docs/operator-manual/reconcile.md

Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* use local settings

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* latest settings

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* safety first

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* since it's behind a feature flag, go aggressive on overrides

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-25 01:32:20 +00:00

207 lines
5.4 KiB
Go

package normalizers
import (
"encoding/json"
"fmt"
"strings"
"github.com/argoproj/gitops-engine/pkg/diff"
jsonpatch "github.com/evanphx/json-patch"
"github.com/itchyny/gojq"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/glob"
)
type normalizerPatch interface {
GetGroupKind() schema.GroupKind
GetNamespace() string
GetName() string
// Apply(un *unstructured.Unstructured) (error)
Apply(data []byte) ([]byte, error)
}
type baseNormalizerPatch struct {
groupKind schema.GroupKind
namespace string
name string
}
func (np *baseNormalizerPatch) GetGroupKind() schema.GroupKind {
return np.groupKind
}
func (np *baseNormalizerPatch) GetNamespace() string {
return np.namespace
}
func (np *baseNormalizerPatch) GetName() string {
return np.name
}
type jsonPatchNormalizerPatch struct {
baseNormalizerPatch
patch *jsonpatch.Patch
}
func (np *jsonPatchNormalizerPatch) Apply(data []byte) ([]byte, error) {
patchedData, err := np.patch.Apply(data)
if err != nil {
return nil, err
}
return patchedData, nil
}
type jqNormalizerPatch struct {
baseNormalizerPatch
code *gojq.Code
}
func (np *jqNormalizerPatch) Apply(data []byte) ([]byte, error) {
dataJson := make(map[string]interface{})
err := json.Unmarshal(data, &dataJson)
if err != nil {
return nil, err
}
iter := np.code.Run(dataJson)
first, ok := iter.Next()
if !ok {
return nil, fmt.Errorf("JQ patch did not return any data")
}
_, ok = iter.Next()
if ok {
return nil, fmt.Errorf("JQ patch returned multiple objects")
}
patchedData, err := json.Marshal(first)
if err != nil {
return nil, err
}
return patchedData, err
}
type ignoreNormalizer struct {
patches []normalizerPatch
}
// NewIgnoreNormalizer creates diff normalizer which removes ignored fields according to given application spec and resource overrides
func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (diff.Normalizer, error) {
for key, override := range overrides {
group, kind, err := getGroupKindForOverrideKey(key)
if err != nil {
log.Warn(err)
}
if len(override.IgnoreDifferences.JSONPointers) > 0 || len(override.IgnoreDifferences.JQPathExpressions) > 0 {
resourceIgnoreDifference := v1alpha1.ResourceIgnoreDifferences{
Group: group,
Kind: kind,
}
if len(override.IgnoreDifferences.JSONPointers) > 0 {
resourceIgnoreDifference.JSONPointers = override.IgnoreDifferences.JSONPointers
}
if len(override.IgnoreDifferences.JQPathExpressions) > 0 {
resourceIgnoreDifference.JQPathExpressions = override.IgnoreDifferences.JQPathExpressions
}
ignore = append(ignore, resourceIgnoreDifference)
}
}
patches := make([]normalizerPatch, 0)
for i := range ignore {
for _, path := range ignore[i].JSONPointers {
patchData, err := json.Marshal([]map[string]string{{"op": "remove", "path": path}})
if err != nil {
return nil, err
}
patch, err := jsonpatch.DecodePatch(patchData)
if err != nil {
return nil, err
}
patches = append(patches, &jsonPatchNormalizerPatch{
baseNormalizerPatch: baseNormalizerPatch{
groupKind: schema.GroupKind{Group: ignore[i].Group, Kind: ignore[i].Kind},
name: ignore[i].Name,
namespace: ignore[i].Namespace,
},
patch: &patch,
})
}
for _, pathExpression := range ignore[i].JQPathExpressions {
jqDeletionQuery, err := gojq.Parse(fmt.Sprintf("del(%s)", pathExpression))
if err != nil {
return nil, err
}
jqDeletionCode, err := gojq.Compile(jqDeletionQuery)
if err != nil {
return nil, err
}
patches = append(patches, &jqNormalizerPatch{
baseNormalizerPatch: baseNormalizerPatch{
groupKind: schema.GroupKind{Group: ignore[i].Group, Kind: ignore[i].Kind},
name: ignore[i].Name,
namespace: ignore[i].Namespace,
},
code: jqDeletionCode,
})
}
}
return &ignoreNormalizer{patches: patches}, nil
}
// Normalize removes fields from supplied resource using json paths from matching items of specified resources ignored differences list
func (n *ignoreNormalizer) Normalize(un *unstructured.Unstructured) error {
if un == nil {
return fmt.Errorf("invalid argument: unstructured is nil")
}
matched := make([]normalizerPatch, 0)
for _, patch := range n.patches {
groupKind := un.GroupVersionKind().GroupKind()
if glob.Match(patch.GetGroupKind().Group, groupKind.Group) &&
glob.Match(patch.GetGroupKind().Kind, groupKind.Kind) &&
(patch.GetName() == "" || patch.GetName() == un.GetName()) &&
(patch.GetNamespace() == "" || patch.GetNamespace() == un.GetNamespace()) {
matched = append(matched, patch)
}
}
if len(matched) == 0 {
return nil
}
docData, err := json.Marshal(un)
if err != nil {
return err
}
for _, patch := range matched {
patchedDocData, err := patch.Apply(docData)
if err != nil {
if shouldLogError(err) {
log.Debugf("Failed to apply normalization: %v", err)
}
continue
}
docData = patchedDocData
}
err = json.Unmarshal(docData, un)
if err != nil {
return err
}
return nil
}
func shouldLogError(e error) bool {
if strings.Contains(e.Error(), "Unable to remove nonexistent key") {
return false
}
if strings.Contains(e.Error(), "remove operation does not apply: doc is missing path") {
return false
}
return true
}