-
Notifications
You must be signed in to change notification settings - Fork 248
feat: refactor filter #820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d843132
feat: refactor filter
timmilesdw 7aabe0b
feat: minor improvements
timmilesdw dad42a6
chore: fix lint issues
timmilesdw abfad15
fix: nil check
timmilesdw 0f311f9
fix: do not compile filter if absent
timmilesdw 97e333f
chore: small architecture refactor
timmilesdw 33798ad
chore: lint fix
timmilesdw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,158 @@ | ||
| package filter | ||
|
|
||
| type Filter interface { | ||
| ApplyFilter(filterStr string, data map[string]any) ([]byte, error) | ||
| FilterInfo() string | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "reflect" | ||
| "runtime" | ||
| "runtime/trace" | ||
|
|
||
| "github.com/itchyny/gojq" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
|
|
||
| kemtypes "github.com/flant/shell-operator/pkg/kube_events_manager/types" | ||
| utils_checksum "github.com/flant/shell-operator/pkg/utils/checksum" | ||
| ) | ||
|
|
||
| type FilterFn func(obj *unstructured.Unstructured) (result interface{}, err error) | ||
|
|
||
| type Expression struct { | ||
| *gojq.Code | ||
| Query string | ||
| } | ||
|
|
||
| // Run filters an object with a custom function or jq expression, calculates checksum | ||
| // over the result and returns ObjectAndFilterResult. | ||
| // | ||
| // Filter precedence (highest to lowest): | ||
| // 1. Custom filter function (filterFn) - if provided, takes precedence over jq expression | ||
| // 2. JQ expression (expression) - used when filterFn is nil but expression is provided | ||
| // 3. No filter - when both filterFn and expression are nil, uses raw object JSON | ||
| // | ||
| // The function calculates a checksum over the filtered result (or full object if no filter) | ||
| // and populates metadata including resource ID and jq filter query (if applicable). | ||
| func Run(expression *Expression, filterFn FilterFn, obj *unstructured.Unstructured) (*kemtypes.ObjectAndFilterResult, error) { | ||
| defer trace.StartRegion(context.Background(), "ApplyJqFilter").End() | ||
|
|
||
| // Initialize result with object and resource ID | ||
| result := &kemtypes.ObjectAndFilterResult{ | ||
| Object: obj, | ||
| } | ||
| result.Metadata.ResourceId = fmt.Sprintf("%s/%s/%s", obj.GetNamespace(), obj.GetKind(), obj.GetName()) | ||
|
|
||
| // Set JQ filter in metadata if expression is provided (even if custom filter takes precedence) | ||
| if expression != nil { | ||
| result.Metadata.JqFilter = expression.Query | ||
| } | ||
|
|
||
| // Apply filters based on precedence: custom filter > jq expression > no filter | ||
| switch { | ||
| case filterFn != nil: | ||
| // Custom filter function takes highest precedence | ||
| return applyCustomFilter(filterFn, result, obj) | ||
|
|
||
| case expression != nil: | ||
| // JQ expression filter when no custom filter is provided | ||
| return applyJQFilter(expression, result, obj) | ||
|
|
||
| default: | ||
| // No filter - use raw object JSON when both filterFn and expression are nil | ||
| return applyNoFilter(result, obj) | ||
| } | ||
| } | ||
|
|
||
| func applyCustomFilter(filterFn FilterFn, result *kemtypes.ObjectAndFilterResult, obj *unstructured.Unstructured) (*kemtypes.ObjectAndFilterResult, error) { | ||
| filteredObj, err := filterFn(obj) | ||
| if err != nil { | ||
| funcName := runtime.FuncForPC(reflect.ValueOf(filterFn).Pointer()).Name() | ||
| return nil, fmt.Errorf("filterFn (%s) contains an error: %v", funcName, err) | ||
| } | ||
|
|
||
| filteredBytes, err := json.Marshal(filteredObj) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| result.FilterResult = filteredObj | ||
| result.Metadata.Checksum = utils_checksum.CalculateChecksum(string(filteredBytes)) | ||
| return result, nil | ||
| } | ||
|
|
||
| func applyNoFilter(result *kemtypes.ObjectAndFilterResult, obj *unstructured.Unstructured) (*kemtypes.ObjectAndFilterResult, error) { | ||
| data, err := json.Marshal(obj) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| result.Metadata.Checksum = utils_checksum.CalculateChecksum(string(data)) | ||
| return result, nil | ||
| } | ||
|
|
||
| func applyJQFilter(expression *Expression, result *kemtypes.ObjectAndFilterResult, obj *unstructured.Unstructured) (*kemtypes.ObjectAndFilterResult, error) { | ||
| filtered, err := RunJQ(expression, obj.DeepCopy()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("jqFilter: %v", err) | ||
| } | ||
|
|
||
| filteredStr := string(filtered) | ||
| result.FilterResult = filteredStr | ||
| result.Metadata.Checksum = utils_checksum.CalculateChecksum(filteredStr) | ||
| return result, nil | ||
| } | ||
|
|
||
| func RunJQ(expression *Expression, obj *unstructured.Unstructured) ([]byte, error) { | ||
| // Execute jq expression and collect results | ||
| iter := expression.Run(obj.UnstructuredContent()) | ||
| var results []any | ||
|
|
||
| for { | ||
| v, ok := iter.Next() | ||
| if !ok { | ||
| break | ||
| } | ||
|
|
||
| // Handle errors from jq execution | ||
| if err, ok := v.(error); ok { | ||
| // HaltError with nil value means graceful termination | ||
| var haltErr *gojq.HaltError | ||
| if errors.As(err, &haltErr) && haltErr.Value() == nil { | ||
| break | ||
| } | ||
| return nil, err | ||
| } | ||
|
|
||
| results = append(results, v) | ||
| } | ||
|
|
||
| // Marshal results based on count | ||
| switch len(results) { | ||
| case 0: | ||
| return []byte("null"), nil | ||
| case 1: | ||
| return json.Marshal(results[0]) | ||
| default: | ||
| return json.Marshal(results) | ||
| } | ||
| } | ||
|
|
||
| func CompileExpression(expression string) (*Expression, error) { | ||
| parsedQuery, err := gojq.Parse(expression) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| compiledQuery, err := gojq.Compile(parsedQuery) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &Expression{ | ||
| Code: compiledQuery, | ||
| Query: expression, | ||
| }, nil | ||
| } | ||
|
|
||
| func Info() string { | ||
| return "Filter implementation: using itchyny/gojq" | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.