-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Regex filters for P2P preheat (phase 3) #754
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
base: feat/regex-filters-phase2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import ( | |
|
|
||
| "github.com/goharbor/harbor/src/common/utils" | ||
| "github.com/goharbor/harbor/src/lib/errors" | ||
| "github.com/goharbor/harbor/src/lib/pattern" | ||
| "github.com/goharbor/harbor/src/lib/q" | ||
| ) | ||
|
|
||
|
|
@@ -48,6 +49,12 @@ const ( | |
| // FilterTypeLabel represents the label filter type | ||
| FilterTypeLabel FilterType = "label" | ||
|
|
||
| // FilterKindDoublestar interprets the filter value as a doublestar pattern, the default | ||
| FilterKindDoublestar = pattern.KindDoublestar | ||
| // FilterKindRegex interprets the filter value as a regular expression matching the whole | ||
| // value, the same engine the retention, immutability and replication filters use | ||
| FilterKindRegex = pattern.KindRegex | ||
|
|
||
| // TriggerTypeManual represents the manual trigger type | ||
| TriggerTypeManual TriggerType = "manual" | ||
| // TriggerTypeScheduled represents the scheduled trigger type | ||
|
|
@@ -103,6 +110,50 @@ type FilterType = string | |
| type Filter struct { | ||
| Type FilterType `json:"type"` | ||
| Value any `json:"value"` | ||
| // Kind selects the pattern engine used for Value, empty means FilterKindDoublestar. | ||
| // Only the repository and tag filters carry a pattern, the others are exact or numeric. | ||
| Kind string `json:"kind,omitempty"` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A policy stored with kind=regex carries a regex value inside the opaque FiltersStr, but any consumer that does not know the Prompt for AI agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Known and deliberate, no change. The downgrade behavior is documented in the compatibility section of the proposal this stack implements (goharbor/community#298), which covers every filter that gained a kind, not just preheat. Repeating it as a per-PR release-note line would duplicate that and would say it in the one place a downgrading operator is least likely to read. Enforcing it at the boundary is not possible either: the filters travel as an opaque JSON string end to end, so an older Harbor decodes them with a struct that has no kind field, and nothing this version writes can make that decode fail. |
||
| } | ||
|
|
||
| // supportsKind reports whether the filter type evaluates its value as a pattern | ||
| func (f *Filter) supportsKind() bool { | ||
| return f.Type == FilterTypeRepository || f.Type == FilterTypeTag | ||
| } | ||
|
|
||
| // Validate checks the pattern engine selection of the filter. The value type checks stay | ||
| // in the filter builder, which is also reached by the policies stored before this existed. | ||
| func (f *Filter) Validate() error { | ||
| if f.Kind == "" { | ||
| return nil | ||
| } | ||
|
|
||
| if f.Kind != FilterKindDoublestar && f.Kind != FilterKindRegex { | ||
| return errors.New(nil).WithCode(errors.BadRequestCode). | ||
| WithMessagef("invalid filter kind: %s", f.Kind) | ||
| } | ||
|
|
||
| if !f.supportsKind() { | ||
| return errors.New(nil).WithCode(errors.BadRequestCode). | ||
| WithMessagef("only the %s and %s filters support kind, got: %s", | ||
| FilterTypeRepository, FilterTypeTag, f.Type) | ||
| } | ||
|
|
||
| if f.Kind != FilterKindRegex { | ||
| return nil | ||
| } | ||
|
|
||
| value, ok := f.Value.(string) | ||
| if !ok { | ||
| return errors.New(nil).WithCode(errors.BadRequestCode). | ||
| WithMessagef("the value of the %s filter isn't a string", f.Type) | ||
| } | ||
|
|
||
| if err := pattern.ValidateRegex(value); err != nil { | ||
| return errors.New(nil).WithCode(errors.BadRequestCode). | ||
| WithMessagef("invalid regex filter value %q: %v", value, err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // TriggerType represents the type of trigger. | ||
|
|
@@ -120,6 +171,15 @@ type Trigger struct { | |
|
|
||
| // ValidatePreheatPolicy validate preheat policy | ||
| func (s *Schema) ValidatePreheatPolicy() error { | ||
| for _, filter := range s.Filters { | ||
| if filter == nil { | ||
| continue | ||
| } | ||
| if err := filter.Validate(); err != nil { | ||
| return err | ||
| } | ||
| } | ||
|
|
||
| // currently only validate cron string of preheat policy | ||
| if s.Trigger != nil && s.Trigger.Type == TriggerTypeScheduled && len(s.Trigger.Settings.Cron) > 0 { | ||
| if err := utils.ValidateCronString(s.Trigger.Settings.Cron); err != nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import ( | |
| "github.com/goharbor/harbor/src/lib/selector" | ||
| "github.com/goharbor/harbor/src/lib/selector/selectors/doublestar" | ||
| "github.com/goharbor/harbor/src/lib/selector/selectors/label" | ||
| regexpselector "github.com/goharbor/harbor/src/lib/selector/selectors/regexp" | ||
| "github.com/goharbor/harbor/src/lib/selector/selectors/severity" | ||
| "github.com/goharbor/harbor/src/lib/selector/selectors/signature" | ||
| "github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy" | ||
|
|
@@ -152,6 +153,12 @@ func buildFilter(f *policy.Filter) (selector.Selector, error) { | |
| return nil, errors.Errorf("pattern value is missing for filter: %s", f.Type) | ||
| } | ||
|
|
||
| // Backstop for the write time validation: a policy stored by an older version, or | ||
| // written past the API, still has to fail here instead of building a broken selector. | ||
| if err := f.Validate(); err != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Prompt for AI agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deliberate, no change. The Validate call in buildFilter is a backstop for the two paths that do not go through write-time validation: a policy stored before this version existed, and anything written past the API. Runtime keeping its own check is the point of it. The cost is not on the hot path either. buildFilter runs once per filter per enforcer run, not per candidate — the compiled expression then lives in the selector's Matcher and is reused across every artifact the run evaluates. So this is one extra regexp.Compile of a pattern capped at 512 characters per policy execution. |
||
| return nil, err | ||
| } | ||
|
|
||
| // Current value type | ||
| cvt := reflect.TypeOf(f.Value).Name() | ||
|
|
||
|
|
@@ -176,8 +183,14 @@ func buildFilter(f *policy.Filter) (selector.Selector, error) { | |
| // Build selectors | ||
| switch f.Type { | ||
| case policy.FilterTypeRepository: | ||
| if f.Kind == policy.FilterKindRegex { | ||
| return regexpselector.New(regexpselector.RepoMatches, f.Value, ""), nil | ||
| } | ||
| return doublestar.New(doublestar.RepoMatches, f.Value, ""), nil | ||
| case policy.FilterTypeTag: | ||
| if f.Kind == policy.FilterKindRegex { | ||
| return regexpselector.New(regexpselector.Matches, f.Value, ""), nil | ||
| } | ||
| return doublestar.New(doublestar.Matches, f.Value, ""), nil | ||
| case policy.FilterTypeLabel: | ||
| return label.New(label.With, f.Value, ""), nil | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.