From 59ed27fe733647e677e8a6255ad7780d0db2a045 Mon Sep 17 00:00:00 2001 From: wsan3 Date: Tue, 5 Mar 2024 11:07:29 -0600 Subject: [PATCH 1/5] Add support for PR label event 1. Add new action to the Pull struct 2. Update constants to reflect new event for the integer mask 3. Update YAML package to add new label rule 4. Update ruleset matching logic --- constants/action.go | 3 +++ constants/allow_events.go | 2 +- library/actions/pull.go | 30 ++++++++++++++++++++++++++++++ library/events.go | 2 ++ pipeline/ruleset.go | 14 +++++++++++--- webhook.go | 1 + yaml/ruleset.go | 5 +++++ 7 files changed, 53 insertions(+), 4 deletions(-) diff --git a/constants/action.go b/constants/action.go index f1e18c6e..4a7f5210 100644 --- a/constants/action.go +++ b/constants/action.go @@ -22,6 +22,9 @@ const ( // ActionSynchronize defines the action for the synchronizing of pull requests. ActionSynchronize = "synchronize" + // ActionLabeled defines the action for the labelin of pull requests. + ActionLabeled = "labeled" + // ActionTransferred defines the action for transferring repository ownership. ActionTransferred = "transferred" diff --git a/constants/allow_events.go b/constants/allow_events.go index 4a163a73..34124f03 100644 --- a/constants/allow_events.go +++ b/constants/allow_events.go @@ -11,7 +11,7 @@ const ( AllowPullSync _ // AllowPullAssigned - Not Implemented _ // AllowPullMilestoned - Not Implemented - _ // AllowPullLabel - Not Implemented + AllowPullLabel _ // AllowPullLocked - Not Implemented _ // AllowPullReady - Not Implemented AllowPullReopen diff --git a/library/actions/pull.go b/library/actions/pull.go index 609c6248..1bcae12c 100644 --- a/library/actions/pull.go +++ b/library/actions/pull.go @@ -12,6 +12,7 @@ type Pull struct { Edited *bool `json:"edited"` Synchronize *bool `json:"synchronize"` Reopened *bool `json:"reopened"` + Labeled *bool `json:"labeled"` } // FromMask returns the Pull type resulting from the provided integer mask. @@ -20,6 +21,7 @@ func (a *Pull) FromMask(mask int64) *Pull { a.SetSynchronize(mask&constants.AllowPullSync > 0) a.SetEdited(mask&constants.AllowPullEdit > 0) a.SetReopened(mask&constants.AllowPullReopen > 0) + a.SetLabeled(mask&constants.AllowPullLabel > 0) return a } @@ -44,6 +46,10 @@ func (a *Pull) ToMask() int64 { mask = mask | constants.AllowPullReopen } + if a.GetLabeled() { + mask = mask | constants.AllowPullLabel + } + return mask } @@ -91,6 +97,17 @@ func (a *Pull) GetReopened() bool { return *a.Reopened } +// GetLabeled returns the Labeled field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetLabeled() bool { + // return zero value if Pull type or Reopened field is nil + if a == nil || a.Labeled == nil { + return false + } + + return *a.Labeled +} + // SetOpened sets the Pull Opened field. // // When the provided Pull type is nil, it @@ -142,3 +159,16 @@ func (a *Pull) SetReopened(v bool) { a.Reopened = &v } + +// GetLabeled sets the Pull Labeled field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetLabeled(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Labeled = &v +} diff --git a/library/events.go b/library/events.go index 88074d80..ab1c9544 100644 --- a/library/events.go +++ b/library/events.go @@ -58,6 +58,8 @@ func (e *Events) Allowed(event, action string) bool { allowed = e.GetPullRequest().GetEdited() case constants.EventPull + ":" + constants.ActionReopened: allowed = e.GetPullRequest().GetReopened() + case constants.EventPull + ":" + constants.ActionLabeled: + allowed = e.GetPullRequest().GetLabeled() case constants.EventTag: allowed = e.GetPush().GetTag() case constants.EventComment + ":" + constants.ActionCreated: diff --git a/pipeline/ruleset.go b/pipeline/ruleset.go index 04880d57..7241c367 100644 --- a/pipeline/ruleset.go +++ b/pipeline/ruleset.go @@ -38,6 +38,7 @@ type ( Tag Ruletype `json:"tag,omitempty" yaml:"tag,omitempty"` Target Ruletype `json:"target,omitempty" yaml:"target,omitempty"` Parallel bool `json:"-" yaml:"-"` + Label Ruletype `json:"label,omitempty" yaml:"label,omitempty"` } // Ruletype is the pipeline representation of an element @@ -58,6 +59,7 @@ type ( Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` Target string `json:"target,omitempty" yaml:"target,omitempty"` Parallel bool `json:"-" yaml:"-"` + Label string `json:"label,omitempty" yaml:"label,omitempty"` } ) @@ -111,7 +113,8 @@ func (r *Rules) Empty() bool { len(r.Repo) == 0 && len(r.Status) == 0 && len(r.Tag) == 0 && - len(r.Target) == 0 { + len(r.Target) == 0 && + len(r.Label) == 0 { return true } @@ -261,10 +264,15 @@ func matches(r *Rules, from *RuleData, matcher, path, logic string) (bool, error return false, err } + matchLabel, err := r.Label.Match(from.Label, matcher, logic) + if err != nil { + return false, err + } + switch logic { case constants.OperatorAnd: - return (matchBranch && matchComment && matchEvent && matchPath && matchRepo && matchTag && matchTarget && status), nil + return (matchBranch && matchComment && matchEvent && matchPath && matchRepo && matchTag && matchTarget && matchLabel && status), nil default: - return (matchBranch || matchComment || matchEvent || matchPath || matchRepo || matchTag || matchTarget || status), nil + return (matchBranch || matchComment || matchEvent || matchPath || matchRepo || matchTag || matchTarget || matchLabel || status), nil } } diff --git a/webhook.go b/webhook.go index 1941e38f..8b86c357 100644 --- a/webhook.go +++ b/webhook.go @@ -19,6 +19,7 @@ type PullRequest struct { Comment string Number int IsFromFork bool + Label string } // Webhook defines a struct that is used to return diff --git a/yaml/ruleset.go b/yaml/ruleset.go index 7b8fc615..20f6bce0 100644 --- a/yaml/ruleset.go +++ b/yaml/ruleset.go @@ -30,6 +30,7 @@ type ( Status []string `yaml:"status,omitempty,flow" json:"status,omitempty" jsonschema:"enum=[failure],enum=[success],description=Limits the execution of a step to matching build statuses.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` Tag []string `yaml:"tag,omitempty,flow" json:"tag,omitempty" jsonschema:"description=Limits the execution of a step to matching build tag references.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` Target []string `yaml:"target,omitempty,flow" json:"target,omitempty" jsonschema:"description=Limits the execution of a step to matching build deployment targets.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` + Label []string `yaml:"label,omitempty,flow" json:"label,omitempty" jsonschema:"description=Limits the execution of a step to matching build deployment targets.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` } ) @@ -84,6 +85,7 @@ func (r *Ruleset) UnmarshalYAML(unmarshal func(interface{}) error) error { advanced.If.Status = append(advanced.If.Status, simple.Status...) advanced.If.Tag = append(advanced.If.Tag, simple.Tag...) advanced.If.Target = append(advanced.If.Target, simple.Target...) + advanced.If.Label = append(advanced.If.Label, simple.Label...) // set ruleset `if` to advanced `if` rules r.If = advanced.If @@ -113,6 +115,7 @@ func (r *Rules) ToPipeline() *pipeline.Rules { Status: r.Status, Tag: r.Tag, Target: r.Target, + Label: r.Label, } } @@ -128,6 +131,7 @@ func (r *Rules) UnmarshalYAML(unmarshal func(interface{}) error) error { Status raw.StringSlice Tag raw.StringSlice Target raw.StringSlice + Label raw.StringSlice }) // attempt to unmarshal rules @@ -140,6 +144,7 @@ func (r *Rules) UnmarshalYAML(unmarshal func(interface{}) error) error { r.Status = rules.Status r.Tag = rules.Tag r.Target = rules.Target + r.Label = rules.Label // account for users who use non-scoped pull_request event events := []string{} From 361dac193064759fab9880c8ce96c641bdc9a40f Mon Sep 17 00:00:00 2001 From: wsan3 Date: Tue, 12 Mar 2024 12:14:09 -0500 Subject: [PATCH 2/5] Update test files related to PR event --- library/actions/pull_test.go | 1 + library/events.go | 4 ++++ library/events_test.go | 5 +++++ pipeline/ruleset.go | 4 ++-- pipeline/ruleset_test.go | 20 ++++++++++++++++---- yaml/ruleset_test.go | 4 ++++ 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/library/actions/pull_test.go b/library/actions/pull_test.go index 30b5ed8c..2819c9eb 100644 --- a/library/actions/pull_test.go +++ b/library/actions/pull_test.go @@ -123,6 +123,7 @@ func testPull() *Pull { pr.SetSynchronize(true) pr.SetEdited(false) pr.SetReopened(true) + pr.SetLabeled(false) return pr } diff --git a/library/events.go b/library/events.go index ab1c9544..94262c19 100644 --- a/library/events.go +++ b/library/events.go @@ -104,6 +104,10 @@ func (e *Events) List() []string { eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionReopened) } + if e.GetPullRequest().GetLabeled() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionLabeled) + } + if e.GetPush().GetTag() { eventSlice = append(eventSlice, constants.EventTag) } diff --git a/library/events_test.go b/library/events_test.go index a26cc4dc..fb6d81db 100644 --- a/library/events_test.go +++ b/library/events_test.go @@ -130,6 +130,7 @@ func TestLibrary_Events_List(t *testing.T) { wantTwo := []string{ "pull_request:edited", + "pull_request:labeled", "deployment", "comment:edited", "delete:tag", @@ -166,6 +167,7 @@ func TestLibrary_Events_NewEventsFromMask_ToDatabase(t *testing.T) { constants.AllowPushDeleteTag | constants.AllowPullEdit | constants.AllowCommentEdit | + constants.AllowPullLabel | constants.AllowDeployCreate, ) @@ -210,6 +212,7 @@ func TestLibrary_Events_Allowed(t *testing.T) { {event: "pull_request", action: "synchronize", want: true}, {event: "pull_request", action: "edited", want: false}, {event: "pull_request", action: "reopened", want: true}, + {event: "pull_request", action: "labeled", want: false}, {event: "deployment", want: false}, {event: "comment", action: "created", want: true}, {event: "comment", action: "edited", want: false}, @@ -249,6 +252,7 @@ func testEvents() (*Events, *Events) { Synchronize: &tBool, Edited: &fBool, Reopened: &tBool, + Labeled: &fBool, }, Deployment: &actions.Deploy{ Created: &fBool, @@ -274,6 +278,7 @@ func testEvents() (*Events, *Events) { Synchronize: &fBool, Edited: &tBool, Reopened: &fBool, + Labeled: &tBool, }, Deployment: &actions.Deploy{ Created: &tBool, diff --git a/pipeline/ruleset.go b/pipeline/ruleset.go index 7241c367..db958cc7 100644 --- a/pipeline/ruleset.go +++ b/pipeline/ruleset.go @@ -37,8 +37,8 @@ type ( Status Ruletype `json:"status,omitempty" yaml:"status,omitempty"` Tag Ruletype `json:"tag,omitempty" yaml:"tag,omitempty"` Target Ruletype `json:"target,omitempty" yaml:"target,omitempty"` - Parallel bool `json:"-" yaml:"-"` Label Ruletype `json:"label,omitempty" yaml:"label,omitempty"` + Parallel bool `json:"-" yaml:"-"` } // Ruletype is the pipeline representation of an element @@ -58,8 +58,8 @@ type ( Status string `json:"status,omitempty" yaml:"status,omitempty"` Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` Target string `json:"target,omitempty" yaml:"target,omitempty"` - Parallel bool `json:"-" yaml:"-"` Label string `json:"label,omitempty" yaml:"label,omitempty"` + Parallel bool `json:"-" yaml:"-"` } ) diff --git a/pipeline/ruleset_test.go b/pipeline/ruleset_test.go index a310ed71..d7e88b88 100644 --- a/pipeline/ruleset_test.go +++ b/pipeline/ruleset_test.go @@ -425,14 +425,20 @@ func TestPipeline_Rules_Match(t *testing.T) { }, { rules: &Rules{Event: []string{"push", "pull_request"}, Tag: []string{"release/*"}}, - data: &RuleData{Branch: "main", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "release/*", Target: ""}, + data: &RuleData{Branch: "main", Event: "tag", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, operator: "or", + want: false, + }, + { + rules: &Rules{Event: []string{"pull_request:labeled"}, Label: []string{"enhancement", "documentation"}}, + data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: "documentation"}, + operator: "and", want: true, }, { - rules: &Rules{Event: []string{"push", "pull_request"}, Tag: []string{"release/*"}}, - data: &RuleData{Branch: "main", Event: "tag", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, - operator: "or", + rules: &Rules{Event: []string{"pull_request:labeled"}, Label: []string{"enhancement", "documentation"}}, + data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: "support"}, + operator: "and", want: false, }, } @@ -572,6 +578,9 @@ func TestPipeline_Ruletype_MatchOr(t *testing.T) { // Target with filepath matcher {matcher: "filepath", rule: []string{"production"}, pattern: "production", want: true}, {matcher: "filepath", rule: []string{"stage"}, pattern: "production", want: false}, + // Label with filepath matcher + {matcher: "filepath", rule: []string{"enhancement", "documentation"}, pattern: "documentation", want: true}, + {matcher: "filepath", rule: []string{"enhancement", "documentation"}, pattern: "question", want: false}, // Empty with regexp matcher {matcher: "regexp", rule: []string{}, pattern: "main", want: false}, {matcher: "regexp", rule: []string{}, pattern: "push", want: false}, @@ -599,6 +608,9 @@ func TestPipeline_Ruletype_MatchOr(t *testing.T) { // Target with regexp matcher {matcher: "regexp", rule: []string{"production"}, pattern: "production", want: true}, {matcher: "regexp", rule: []string{"stage"}, pattern: "production", want: false}, + // Label with regexp matcher + {matcher: "regexp", rule: []string{"enhancement", "documentation"}, pattern: "documentation", want: true}, + {matcher: "regexp", rule: []string{"enhancement", "documentation"}, pattern: "question", want: false}, } // run test diff --git a/yaml/ruleset_test.go b/yaml/ruleset_test.go index 762bb904..06f3c676 100644 --- a/yaml/ruleset_test.go +++ b/yaml/ruleset_test.go @@ -28,6 +28,7 @@ func TestYaml_Ruleset_ToPipeline(t *testing.T) { Status: []string{"success"}, Tag: []string{"v0.1.0"}, Target: []string{"production"}, + Label: []string{"enhancement"}, }, Unless: Rules{ Branch: []string{"main"}, @@ -53,6 +54,7 @@ func TestYaml_Ruleset_ToPipeline(t *testing.T) { Status: []string{"success"}, Tag: []string{"v0.1.0"}, Target: []string{"production"}, + Label: []string{"enhancement"}, }, Unless: pipeline.Rules{ Branch: []string{"main"}, @@ -173,6 +175,7 @@ func TestYaml_Rules_ToPipeline(t *testing.T) { Status: []string{"success"}, Tag: []string{"v0.1.0"}, Target: []string{"production"}, + Label: []string{"enhancement"}, }, want: &pipeline.Rules{ Branch: []string{"main"}, @@ -183,6 +186,7 @@ func TestYaml_Rules_ToPipeline(t *testing.T) { Status: []string{"success"}, Tag: []string{"v0.1.0"}, Target: []string{"production"}, + Label: []string{"enhancement"}, }, }, } From 84ad8bbb8bdb6087e23020e3ea763be975bdecdd Mon Sep 17 00:00:00 2001 From: wsan3 Date: Tue, 19 Mar 2024 12:33:22 -0500 Subject: [PATCH 3/5] Add support for PR unlabeled event 1. Add new action to the Pull struct 2. Update constants to reflect new event for the integer mask 3. Update YAML package to add new unlabeled rule 4. Update tests --- constants/action.go | 5 ++++- constants/allow_events.go | 1 + library/actions/pull.go | 34 ++++++++++++++++++++++++++++++++-- library/actions/pull_test.go | 21 ++++++++++++++++++++- library/actions/push_test.go | 1 + library/events.go | 6 ++++++ library/events_test.go | 5 +++++ library/repo_test.go | 4 ++-- pipeline/ruleset_test.go | 6 ++++++ yaml/ruleset.go | 2 +- yaml/ruleset_test.go | 8 ++++---- 11 files changed, 82 insertions(+), 11 deletions(-) diff --git a/constants/action.go b/constants/action.go index 4a7f5210..2f37b804 100644 --- a/constants/action.go +++ b/constants/action.go @@ -22,9 +22,12 @@ const ( // ActionSynchronize defines the action for the synchronizing of pull requests. ActionSynchronize = "synchronize" - // ActionLabeled defines the action for the labelin of pull requests. + // ActionLabeled defines the action for the labeling of pull requests. ActionLabeled = "labeled" + // ActionUnlabeled defines the action for the unlabeling of pull requests. + ActionUnlabeled = "unlabeled" + // ActionTransferred defines the action for transferring repository ownership. ActionTransferred = "transferred" diff --git a/constants/allow_events.go b/constants/allow_events.go index 34124f03..5df53926 100644 --- a/constants/allow_events.go +++ b/constants/allow_events.go @@ -23,4 +23,5 @@ const ( AllowSchedule AllowPushDeleteBranch AllowPushDeleteTag + AllowPullUnlabel ) diff --git a/library/actions/pull.go b/library/actions/pull.go index 1bcae12c..8e0063b1 100644 --- a/library/actions/pull.go +++ b/library/actions/pull.go @@ -13,6 +13,7 @@ type Pull struct { Synchronize *bool `json:"synchronize"` Reopened *bool `json:"reopened"` Labeled *bool `json:"labeled"` + Unlabeled *bool `json:"unlabeled"` } // FromMask returns the Pull type resulting from the provided integer mask. @@ -22,6 +23,7 @@ func (a *Pull) FromMask(mask int64) *Pull { a.SetEdited(mask&constants.AllowPullEdit > 0) a.SetReopened(mask&constants.AllowPullReopen > 0) a.SetLabeled(mask&constants.AllowPullLabel > 0) + a.SetUnlabeled(mask&constants.AllowPullUnlabel > 0) return a } @@ -50,6 +52,10 @@ func (a *Pull) ToMask() int64 { mask = mask | constants.AllowPullLabel } + if a.GetUnlabeled() { + mask = mask | constants.AllowPullUnlabel + } + return mask } @@ -100,7 +106,7 @@ func (a *Pull) GetReopened() bool { // GetLabeled returns the Labeled field from the provided Pull. If the object is nil, // or the field within the object is nil, it returns the zero value instead. func (a *Pull) GetLabeled() bool { - // return zero value if Pull type or Reopened field is nil + // return zero value if Pull type or Labeled field is nil if a == nil || a.Labeled == nil { return false } @@ -108,6 +114,17 @@ func (a *Pull) GetLabeled() bool { return *a.Labeled } +// GetUnlabeled returns the Unlabeled field from the provided Pull. If the object is nil, +// or the field within the object is nil, it returns the zero value instead. +func (a *Pull) GetUnlabeled() bool { + // return zero value if Pull type or Unlabeled field is nil + if a == nil || a.Unlabeled == nil { + return false + } + + return *a.Unlabeled +} + // SetOpened sets the Pull Opened field. // // When the provided Pull type is nil, it @@ -160,7 +177,7 @@ func (a *Pull) SetReopened(v bool) { a.Reopened = &v } -// GetLabeled sets the Pull Labeled field. +// SetLabeled sets the Pull Labeled field. // // When the provided Pull type is nil, it // will set nothing and immediately return. @@ -172,3 +189,16 @@ func (a *Pull) SetLabeled(v bool) { a.Labeled = &v } + +// SetUnlabeled sets the Pull Unlabeled field. +// +// When the provided Pull type is nil, it +// will set nothing and immediately return. +func (a *Pull) SetUnlabeled(v bool) { + // return if Pull type is nil + if a == nil { + return + } + + a.Unlabeled = &v +} diff --git a/library/actions/pull_test.go b/library/actions/pull_test.go index 2819c9eb..f4c815b5 100644 --- a/library/actions/pull_test.go +++ b/library/actions/pull_test.go @@ -42,6 +42,14 @@ func TestLibrary_Pull_Getters(t *testing.T) { if test.actions.GetReopened() != test.want.GetReopened() { t.Errorf("GetReopened is %v, want %v", test.actions.GetReopened(), test.want.GetReopened()) } + + if test.actions.GetLabeled() != test.want.GetLabeled() { + t.Errorf("GetLabeled is %v, want %v", test.actions.GetLabeled(), test.want.GetLabeled()) + } + + if test.actions.GetUnlabeled() != test.want.GetUnlabeled() { + t.Errorf("GetUnlabeled is %v, want %v", test.actions.GetUnlabeled(), test.want.GetUnlabeled()) + } } } @@ -70,6 +78,8 @@ func TestLibrary_Pull_Setters(t *testing.T) { test.actions.SetSynchronize(test.want.GetSynchronize()) test.actions.SetEdited(test.want.GetEdited()) test.actions.SetReopened(test.want.GetReopened()) + test.actions.SetLabeled(test.want.GetLabeled()) + test.actions.SetUnlabeled(test.want.GetUnlabeled()) if test.actions.GetOpened() != test.want.GetOpened() { t.Errorf("SetOpened is %v, want %v", test.actions.GetOpened(), test.want.GetOpened()) @@ -86,6 +96,14 @@ func TestLibrary_Pull_Setters(t *testing.T) { if test.actions.GetReopened() != test.want.GetReopened() { t.Errorf("SetReopened is %v, want %v", test.actions.GetReopened(), test.want.GetReopened()) } + + if test.actions.GetLabeled() != test.want.GetLabeled() { + t.Errorf("SetLabeled is %v, want %v", test.actions.GetLabeled(), test.want.GetLabeled()) + } + + if test.actions.GetUnlabeled() != test.want.GetUnlabeled() { + t.Errorf("SetUnlabeled is %v, want %v", test.actions.GetUnlabeled(), test.want.GetUnlabeled()) + } } } @@ -107,7 +125,7 @@ func TestLibrary_Pull_ToMask(t *testing.T) { // setup types actions := testPull() - want := int64(constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen) + want := int64(constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen | constants.AllowPullUnlabel) // run test got := actions.ToMask() @@ -124,6 +142,7 @@ func testPull() *Pull { pr.SetEdited(false) pr.SetReopened(true) pr.SetLabeled(false) + pr.SetUnlabeled(true) return pr } diff --git a/library/actions/push_test.go b/library/actions/push_test.go index 330444ba..b0cfba47 100644 --- a/library/actions/push_test.go +++ b/library/actions/push_test.go @@ -128,6 +128,7 @@ func testMask() int64 { constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen | + constants.AllowPullUnlabel | constants.AllowDeployCreate | constants.AllowCommentCreate | constants.AllowSchedule, diff --git a/library/events.go b/library/events.go index 94262c19..372182b0 100644 --- a/library/events.go +++ b/library/events.go @@ -60,6 +60,8 @@ func (e *Events) Allowed(event, action string) bool { allowed = e.GetPullRequest().GetReopened() case constants.EventPull + ":" + constants.ActionLabeled: allowed = e.GetPullRequest().GetLabeled() + case constants.EventPull + ":" + constants.ActionUnlabeled: + allowed = e.GetPullRequest().GetUnlabeled() case constants.EventTag: allowed = e.GetPush().GetTag() case constants.EventComment + ":" + constants.ActionCreated: @@ -108,6 +110,10 @@ func (e *Events) List() []string { eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionLabeled) } + if e.GetPullRequest().GetUnlabeled() { + eventSlice = append(eventSlice, constants.EventPull+":"+constants.ActionUnlabeled) + } + if e.GetPush().GetTag() { eventSlice = append(eventSlice, constants.EventTag) } diff --git a/library/events_test.go b/library/events_test.go index fb6d81db..1f3bfbbe 100644 --- a/library/events_test.go +++ b/library/events_test.go @@ -122,6 +122,7 @@ func TestLibrary_Events_List(t *testing.T) { "pull_request:opened", "pull_request:synchronize", "pull_request:reopened", + "pull_request:unlabeled", "tag", "comment:created", "schedule", @@ -159,6 +160,7 @@ func TestLibrary_Events_NewEventsFromMask_ToDatabase(t *testing.T) { constants.AllowPullOpen | constants.AllowPullSync | constants.AllowPullReopen | + constants.AllowPullUnlabel | constants.AllowCommentCreate | constants.AllowSchedule, ) @@ -213,6 +215,7 @@ func TestLibrary_Events_Allowed(t *testing.T) { {event: "pull_request", action: "edited", want: false}, {event: "pull_request", action: "reopened", want: true}, {event: "pull_request", action: "labeled", want: false}, + {event: "pull_request", action: "unlabeled", want: true}, {event: "deployment", want: false}, {event: "comment", action: "created", want: true}, {event: "comment", action: "edited", want: false}, @@ -253,6 +256,7 @@ func testEvents() (*Events, *Events) { Edited: &fBool, Reopened: &tBool, Labeled: &fBool, + Unlabeled: &tBool, }, Deployment: &actions.Deploy{ Created: &fBool, @@ -279,6 +283,7 @@ func testEvents() (*Events, *Events) { Edited: &tBool, Reopened: &fBool, Labeled: &tBool, + Unlabeled: &fBool, }, Deployment: &actions.Deploy{ Created: &tBool, diff --git a/library/repo_test.go b/library/repo_test.go index 2f2684cd..3892b85c 100644 --- a/library/repo_test.go +++ b/library/repo_test.go @@ -20,7 +20,7 @@ func TestLibrary_Repo_Environment(t *testing.T) { "VELA_REPO_ALLOW_PULL": "false", "VELA_REPO_ALLOW_PUSH": "true", "VELA_REPO_ALLOW_TAG": "false", - "VELA_REPO_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,tag,comment:created,schedule,delete:branch", + "VELA_REPO_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", "VELA_REPO_BRANCH": "main", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "10", @@ -41,7 +41,7 @@ func TestLibrary_Repo_Environment(t *testing.T) { "REPOSITORY_ALLOW_PULL": "false", "REPOSITORY_ALLOW_PUSH": "true", "REPOSITORY_ALLOW_TAG": "false", - "REPOSITORY_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,tag,comment:created,schedule,delete:branch", + "REPOSITORY_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", "REPOSITORY_BRANCH": "main", "REPOSITORY_CLONE": "https://github.com/github/octocat.git", "REPOSITORY_FULL_NAME": "github/octocat", diff --git a/pipeline/ruleset_test.go b/pipeline/ruleset_test.go index d7e88b88..0b435a23 100644 --- a/pipeline/ruleset_test.go +++ b/pipeline/ruleset_test.go @@ -496,6 +496,9 @@ func TestPipeline_Ruletype_MatchAnd(t *testing.T) { // Target with filepath matcher {matcher: "filepath", rule: []string{"production"}, pattern: "production", want: true}, {matcher: "filepath", rule: []string{"stage"}, pattern: "production", want: false}, + // Label with filepath matcher + {matcher: "filepath", rule: []string{"enhancement", "documentation"}, pattern: "documentation", want: true}, + {matcher: "filepath", rule: []string{"enhancement", "documentation"}, pattern: "question", want: false}, // Empty with regex matcher {matcher: "regexp", rule: []string{}, pattern: "main", want: true}, {matcher: "regexp", rule: []string{}, pattern: "push", want: true}, @@ -531,6 +534,9 @@ func TestPipeline_Ruletype_MatchAnd(t *testing.T) { // Target with regex matcher {matcher: "regexp", rule: []string{"production"}, pattern: "production", want: true}, {matcher: "regexp", rule: []string{"stage"}, pattern: "production", want: false}, + // Label with regexp matcher + {matcher: "regexp", rule: []string{"enhancement", "documentation"}, pattern: "documentation", want: true}, + {matcher: "regexp", rule: []string{"enhancement", "documentation"}, pattern: "question", want: false}, } // run test diff --git a/yaml/ruleset.go b/yaml/ruleset.go index 20f6bce0..2fe23e16 100644 --- a/yaml/ruleset.go +++ b/yaml/ruleset.go @@ -30,7 +30,7 @@ type ( Status []string `yaml:"status,omitempty,flow" json:"status,omitempty" jsonschema:"enum=[failure],enum=[success],description=Limits the execution of a step to matching build statuses.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` Tag []string `yaml:"tag,omitempty,flow" json:"tag,omitempty" jsonschema:"description=Limits the execution of a step to matching build tag references.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` Target []string `yaml:"target,omitempty,flow" json:"target,omitempty" jsonschema:"description=Limits the execution of a step to matching build deployment targets.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` - Label []string `yaml:"label,omitempty,flow" json:"label,omitempty" jsonschema:"description=Limits the execution of a step to matching build deployment targets.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` + Label []string `yaml:"label,omitempty,flow" json:"label,omitempty" jsonschema:"description=Limits step execution to match on pull requests labels.\nReference: https://go-vela.github.io/docs/reference/yaml/steps/#the-ruleset-tag"` } ) diff --git a/yaml/ruleset_test.go b/yaml/ruleset_test.go index 06f3c676..559395ba 100644 --- a/yaml/ruleset_test.go +++ b/yaml/ruleset_test.go @@ -22,7 +22,7 @@ func TestYaml_Ruleset_ToPipeline(t *testing.T) { If: Rules{ Branch: []string{"main"}, Comment: []string{"test comment"}, - Event: []string{"push"}, + Event: []string{"push", "pull_request:labeled"}, Path: []string{"foo.txt"}, Repo: []string{"github/octocat"}, Status: []string{"success"}, @@ -48,7 +48,7 @@ func TestYaml_Ruleset_ToPipeline(t *testing.T) { If: pipeline.Rules{ Branch: []string{"main"}, Comment: []string{"test comment"}, - Event: []string{"push"}, + Event: []string{"push", "pull_request:labeled"}, Path: []string{"foo.txt"}, Repo: []string{"github/octocat"}, Status: []string{"success"}, @@ -169,7 +169,7 @@ func TestYaml_Rules_ToPipeline(t *testing.T) { rules: &Rules{ Branch: []string{"main"}, Comment: []string{"test comment"}, - Event: []string{"push"}, + Event: []string{"push", "pull_request:labeled"}, Path: []string{"foo.txt"}, Repo: []string{"github/octocat"}, Status: []string{"success"}, @@ -180,7 +180,7 @@ func TestYaml_Rules_ToPipeline(t *testing.T) { want: &pipeline.Rules{ Branch: []string{"main"}, Comment: []string{"test comment"}, - Event: []string{"push"}, + Event: []string{"push", "pull_request:labeled"}, Path: []string{"foo.txt"}, Repo: []string{"github/octocat"}, Status: []string{"success"}, From c11442a38f7de11b2a91c5135591a5c32f168b9d Mon Sep 17 00:00:00 2001 From: wsan3 Date: Mon, 1 Apr 2024 17:05:56 -0500 Subject: [PATCH 4/5] Extend label rule to include other PR events --- library/events.go | 4 + library/events_test.go | 8 +- library/repo_test.go | 4 +- pipeline/container_test.go | 25 +++++ pipeline/ruleset.go | 201 ++++++++++++++++++------------------- pipeline/ruleset_test.go | 72 +++++++++---- webhook.go | 2 +- 7 files changed, 186 insertions(+), 130 deletions(-) diff --git a/library/events.go b/library/events.go index e7384459..e93acb10 100644 --- a/library/events.go +++ b/library/events.go @@ -68,6 +68,10 @@ func NewEventsFromSlice(events []string) *Events { mask = mask | constants.AllowPullSync case constants.EventPull + ":" + constants.ActionReopened: mask = mask | constants.AllowPullReopen + case constants.EventPull + ":" + constants.ActionLabeled: + mask = mask | constants.AllowPullLabel + case constants.EventPull + ":" + constants.ActionUnlabeled: + mask = mask | constants.AllowPullUnlabel // deployment actions case constants.EventDeploy, constants.EventDeployAlternate, constants.EventDeploy + ":" + constants.ActionCreated: diff --git a/library/events_test.go b/library/events_test.go index aa93ab44..79f6ee16 100644 --- a/library/events_test.go +++ b/library/events_test.go @@ -213,12 +213,12 @@ func Test_NewEventsFromSlice(t *testing.T) { }{ { name: "action specific events to e1", - events: []string{"push:branch", "push:tag", "delete:branch", "pull_request:opened", "pull_request:synchronize", "pull_request:reopened", "comment:created", "schedule:run"}, + events: []string{"push:branch", "push:tag", "delete:branch", "pull_request:opened", "pull_request:synchronize", "pull_request:reopened", "comment:created", "schedule:run", "pull_request:unlabeled"}, want: e1, }, { name: "action specific events to e2", - events: []string{"delete:tag", "pull_request:edited", "deployment:created", "comment:edited"}, + events: []string{"delete:tag", "pull_request:edited", "deployment:created", "comment:edited", "pull_request:labeled"}, want: e2, }, { @@ -236,6 +236,8 @@ func Test_NewEventsFromSlice(t *testing.T) { Reopened: &tBool, Edited: &fBool, Synchronize: &tBool, + Labeled: &fBool, + Unlabeled: &fBool, }, Deployment: &actions.Deploy{ Created: &tBool, @@ -264,6 +266,8 @@ func Test_NewEventsFromSlice(t *testing.T) { Reopened: &tBool, Edited: &fBool, Synchronize: &tBool, + Labeled: &fBool, + Unlabeled: &fBool, }, Deployment: &actions.Deploy{ Created: &fBool, diff --git a/library/repo_test.go b/library/repo_test.go index de256a0e..f9eaba2b 100644 --- a/library/repo_test.go +++ b/library/repo_test.go @@ -15,7 +15,7 @@ func TestLibrary_Repo_Environment(t *testing.T) { // setup types want := map[string]string{ "VELA_REPO_ACTIVE": "true", - "VELA_REPO_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,tag,comment:created,schedule,delete:branch", + "VELA_REPO_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", "VELA_REPO_BRANCH": "main", "VELA_REPO_TOPICS": "cloud,security", "VELA_REPO_BUILD_LIMIT": "10", @@ -31,7 +31,7 @@ func TestLibrary_Repo_Environment(t *testing.T) { "VELA_REPO_PIPELINE_TYPE": "", "VELA_REPO_APPROVE_BUILD": "never", "REPOSITORY_ACTIVE": "true", - "REPOSITORY_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,tag,comment:created,schedule,delete:branch", + "REPOSITORY_ALLOW_EVENTS": "push,pull_request:opened,pull_request:synchronize,pull_request:reopened,pull_request:unlabeled,tag,comment:created,schedule,delete:branch", "REPOSITORY_BRANCH": "main", "REPOSITORY_CLONE": "https://github.com/github/octocat.git", "REPOSITORY_FULL_NAME": "github/octocat", diff --git a/pipeline/container_test.go b/pipeline/container_test.go index ba08cd2b..1e959cb5 100644 --- a/pipeline/container_test.go +++ b/pipeline/container_test.go @@ -202,6 +202,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -222,6 +223,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -241,6 +243,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -260,6 +263,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess, constants.StatusFailure}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -279,6 +283,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess, constants.StatusFailure}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -298,6 +303,7 @@ func TestPipeline_Container_Execute(t *testing.T) { If: Rules{ Status: []string{constants.StatusSuccess, constants.StatusFailure}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -318,6 +324,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Branch: []string{"main"}, Event: []string{constants.EventPush}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -338,6 +345,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Branch: []string{"main"}, Event: []string{constants.EventPush}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -359,6 +367,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Path: []string{"README.md"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -380,6 +389,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Path: []string{"README.md"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -401,6 +411,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Path: []string{"README.md"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -422,6 +433,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventComment}, Comment: []string{"run vela"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -443,6 +455,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventComment}, Comment: []string{"run vela"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -464,6 +477,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventComment}, Comment: []string{"run vela"}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -485,6 +499,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -506,6 +521,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -527,6 +543,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -570,6 +587,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventTag}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -592,6 +610,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventTag}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -614,6 +633,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventTag}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -634,6 +654,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Unless: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -653,6 +674,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Unless: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -672,6 +694,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Unless: Rules{ Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -693,6 +716,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ @@ -714,6 +738,7 @@ func TestPipeline_Container_Execute(t *testing.T) { Event: []string{constants.EventPush}, Status: []string{constants.StatusSuccess}, }, + Operator: "and", }, }, ruleData: &RuleData{ diff --git a/pipeline/ruleset.go b/pipeline/ruleset.go index db958cc7..4575edb2 100644 --- a/pipeline/ruleset.go +++ b/pipeline/ruleset.go @@ -58,7 +58,7 @@ type ( Status string `json:"status,omitempty" yaml:"status,omitempty"` Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` Target string `json:"target,omitempty" yaml:"target,omitempty"` - Label string `json:"label,omitempty" yaml:"label,omitempty"` + Label []string `json:"label,omitempty" yaml:"label,omitempty"` Parallel bool `json:"-" yaml:"-"` } ) @@ -122,157 +122,150 @@ func (r *Rules) Empty() bool { return false } -// Match returns true for the or operator when one of the +// Match returns true for the `or` operator when one of the // ruletypes from the rules match the provided ruledata. -// Match returns true for the and operator when all of the +// Match returns true for the `and` operator when all of the // ruletypes from the rules match the provided ruledata. For // both operators, when none of the ruletypes from the rules // match the provided ruledata, the function returns false. func (r *Rules) Match(from *RuleData, matcher, op string) (bool, error) { - // if the path ruletype is provided - if len(from.Path) > 0 { - // if the "or" operator is provided in the ruleset - if strings.EqualFold(op, constants.OperatorOr) { - // iterate through each path in the ruletype - for _, p := range from.Path { - matches, err := matches(r, from, matcher, p, constants.OperatorOr) - if err != nil { - return false, err - } - - // return true if any ruletype matches the ruledata - if matches { - return true, nil - } - } - - // return false if no match is found - return false, nil - } - - // iterate through each path in the ruletype - for _, p := range from.Path { - matches, err := matches(r, from, matcher, p, constants.OperatorAnd) - if err != nil { - return false, err - } - - // return true if any ruletype matches the ruledata - if matches { - return true, nil - } - } - - // return false if no match is found - return false, nil - } - - // if the "or" operator is provided in the ruleset - if strings.EqualFold(op, constants.OperatorOr) { - // return true if any ruletype matches the ruledata - return matches(r, from, matcher, "", constants.OperatorOr) - } - - return matches(r, from, matcher, "", constants.OperatorAnd) -} - -// Match returns true when the provided ruletype -// matches the provided ruledata. When the provided -// ruletype is empty, the function returns true for -// the `and` operator and false for the `or` operator. -func (r *Ruletype) Match(data, matcher, logic string) (bool, error) { - // return true for `and`, false for `or` if an empty ruletype is provided - if len(*r) == 0 { - return strings.EqualFold(logic, constants.OperatorAnd), nil - } - - // iterate through each pattern in the ruletype - for _, pattern := range *r { - // handle the pattern based off the matcher provided - switch matcher { - case constants.MatcherRegex, "regex": - regExpPattern, err := regexp.Compile(pattern) - if err != nil { - return false, fmt.Errorf("error in regex pattern %s: %w", pattern, err) - } - - // return true if the regexp pattern matches the ruledata - if regExpPattern.MatchString(data) { - return true, nil - } - case constants.MatcherFilepath: - fallthrough - default: - // return true if the pattern matches the ruledata - ok, _ := filepath.Match(pattern, data) - if ok { - return true, nil - } - } - } - - // return false if no match is found - return false, nil -} - -// matches is a helper function which leverages the Match method for all rules -// and returns `true` if the ruleset is indeed a match. -func matches(r *Rules, from *RuleData, matcher, path, logic string) (bool, error) { status := true var err error if len(from.Status) != 0 { - status, err = r.Status.Match(from.Status, matcher, logic) + status, err = r.Status.MatchSingle(from.Status, matcher, op) if err != nil { return false, err } } - matchBranch, err := r.Branch.Match(from.Branch, matcher, logic) + matchBranch, err := r.Branch.MatchSingle(from.Branch, matcher, op) if err != nil { return false, err } - matchComment, err := r.Comment.Match(from.Comment, matcher, logic) + matchComment, err := r.Comment.MatchSingle(from.Comment, matcher, op) if err != nil { return false, err } - matchEvent, err := r.Event.Match(from.Event, matcher, logic) + matchEvent, err := r.Event.MatchSingle(from.Event, matcher, op) if err != nil { return false, err } - matchPath, err := r.Path.Match(path, matcher, logic) + matchPath, err := r.Path.MatchMultiple(from.Path, matcher, op) if err != nil { return false, err } - matchRepo, err := r.Repo.Match(from.Repo, matcher, logic) + matchRepo, err := r.Repo.MatchSingle(from.Repo, matcher, op) if err != nil { return false, err } - matchTag, err := r.Tag.Match(from.Tag, matcher, logic) + matchTag, err := r.Tag.MatchSingle(from.Tag, matcher, op) if err != nil { return false, err } - matchTarget, err := r.Target.Match(from.Target, matcher, logic) + matchTarget, err := r.Target.MatchSingle(from.Target, matcher, op) if err != nil { return false, err } - matchLabel, err := r.Label.Match(from.Label, matcher, logic) + matchLabel, err := r.Label.MatchMultiple(from.Label, matcher, op) if err != nil { return false, err } - switch logic { - case constants.OperatorAnd: + switch op { + case constants.OperatorOr: + return (matchBranch || matchComment || matchEvent || matchPath || matchRepo || matchTag || matchTarget || matchLabel || status), nil + default: return (matchBranch && matchComment && matchEvent && matchPath && matchRepo && matchTag && matchTarget && matchLabel && status), nil + } +} + +// MatchSingle returns true when the provided ruletype +// matches the provided ruledata. When the provided +// ruletype is empty, the function returns true for +// the `and` operator and false for the `or` operator. +func (r *Ruletype) MatchSingle(data, matcher, logic string) (bool, error) { + // return true for `and`, false for `or` if an empty ruletype is provided + if len(*r) == 0 { + return strings.EqualFold(logic, constants.OperatorAnd), nil + } + + // iterate through each pattern in the ruletype + for _, pattern := range *r { + match, err := match(data, matcher, pattern) + if err != nil { + return false, err + } + if match { + return true, nil + } + } + + // return false if no match is found + return false, nil +} + +// MatchMultiple returns true when the provided ruletype +// matches the provided ruledata. When the provided +// ruletype is empty, the function returns true for +// the `and` operator and false for the `or` operator. +func (r *Ruletype) MatchMultiple(data []string, matcher, logic string) (bool, error) { + // return true for `and`, false for `or` if an empty ruletype is provided + if len(*r) == 0 { + return strings.EqualFold(logic, constants.OperatorAnd), nil + } + + // iterate through each pattern in the ruletype + for _, pattern := range *r { + for _, value := range data { + match, err := match(value, matcher, pattern) + if err != nil { + return false, err + } + if match { + return true, nil + } + } + } + + // return false if no match is found + return false, nil +} + +// match is a helper function that compares data against a pattern +// and returns true if the data matches the pattern, depending on +// matcher specified. +func match(data, matcher, pattern string) (bool, error) { + // handle the pattern based off the matcher provided + switch matcher { + case constants.MatcherRegex, "regex": + regExpPattern, err := regexp.Compile(pattern) + if err != nil { + return false, fmt.Errorf("error in regex pattern %s: %w", pattern, err) + } + + // return true if the regexp pattern matches the ruledata + if regExpPattern.MatchString(data) { + return true, nil + } + case constants.MatcherFilepath: + fallthrough default: - return (matchBranch || matchComment || matchEvent || matchPath || matchRepo || matchTag || matchTarget || matchLabel || status), nil + // return true if the pattern matches the ruledata + ok, _ := filepath.Match(pattern, data) + if ok { + return true, nil + } } + + // return false if no match is found + return false, nil } diff --git a/pipeline/ruleset_test.go b/pipeline/ruleset_test.go index 0b435a23..7ea87a31 100644 --- a/pipeline/ruleset_test.go +++ b/pipeline/ruleset_test.go @@ -20,62 +20,62 @@ func TestPipeline_Ruleset_Match(t *testing.T) { {ruleset: &Ruleset{}, data: &RuleData{Branch: "main"}, want: true}, // If with and operator { - ruleset: &Ruleset{If: Rules{Branch: []string{"main"}}}, + ruleset: &Ruleset{If: Rules{Branch: []string{"main"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, { - ruleset: &Ruleset{If: Rules{Branch: []string{"main"}}}, + ruleset: &Ruleset{If: Rules{Branch: []string{"main"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{If: Rules{Branch: []string{"main"}, Event: []string{"push"}}}, + ruleset: &Ruleset{If: Rules{Branch: []string{"main"}, Event: []string{"push"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, { - ruleset: &Ruleset{If: Rules{Branch: []string{"main"}, Event: []string{"push"}}}, + ruleset: &Ruleset{If: Rules{Branch: []string{"main"}, Event: []string{"push"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "pull_request", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{If: Rules{Path: []string{"foo.txt", "/foo/bar.txt"}}}, + ruleset: &Ruleset{If: Rules{Path: []string{"foo.txt", "/foo/bar.txt"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "pull_request", Path: []string{}, Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{If: Rules{Comment: []string{"rerun"}}}, + ruleset: &Ruleset{If: Rules{Comment: []string{"rerun"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, { - ruleset: &Ruleset{If: Rules{Comment: []string{"rerun"}}}, + ruleset: &Ruleset{If: Rules{Comment: []string{"rerun"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "ok to test", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{If: Rules{Event: []string{"deployment"}, Target: []string{"production"}}}, + ruleset: &Ruleset{If: Rules{Event: []string{"deployment"}, Target: []string{"production"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "", Event: "deployment", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: "production"}, want: true, }, { - ruleset: &Ruleset{If: Rules{Event: []string{"deployment"}, Target: []string{"production"}}}, + ruleset: &Ruleset{If: Rules{Event: []string{"deployment"}, Target: []string{"production"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "", Event: "deployment", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: "stage"}, want: false, }, { - ruleset: &Ruleset{If: Rules{Event: []string{"schedule"}, Target: []string{"weekly"}}}, + ruleset: &Ruleset{If: Rules{Event: []string{"schedule"}, Target: []string{"weekly"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "", Event: "schedule", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: "weekly"}, want: true, }, { - ruleset: &Ruleset{If: Rules{Event: []string{"schedule"}, Target: []string{"weekly"}}}, + ruleset: &Ruleset{If: Rules{Event: []string{"schedule"}, Target: []string{"weekly"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "", Event: "schedule", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: "nightly"}, want: false, }, { - ruleset: &Ruleset{If: Rules{Status: []string{"success", "failure"}}}, + ruleset: &Ruleset{If: Rules{Status: []string{"success", "failure"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "ok to test", Event: "push", Repo: "octocat/hello-world", Status: "failure", Tag: "refs/heads/main", Target: ""}, want: true, }, @@ -107,27 +107,27 @@ func TestPipeline_Ruleset_Match(t *testing.T) { }, // Unless with and operator { - ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}}}, + ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}}}, + ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}}, Operator: "and"}, data: &RuleData{Branch: "dev", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, { - ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}, Event: []string{"push"}}}, + ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}, Event: []string{"push"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "push", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: false, }, { - ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}, Event: []string{"push"}}}, + ruleset: &Ruleset{Unless: Rules{Branch: []string{"main"}, Event: []string{"push"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "pull_request", Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, { - ruleset: &Ruleset{Unless: Rules{Path: []string{"foo.txt", "/foo/bar.txt"}}}, + ruleset: &Ruleset{Unless: Rules{Path: []string{"foo.txt", "/foo/bar.txt"}}, Operator: "and"}, data: &RuleData{Branch: "main", Comment: "rerun", Event: "pull_request", Path: []string{}, Repo: "octocat/hello-world", Status: "pending", Tag: "refs/heads/main", Target: ""}, want: true, }, @@ -431,16 +431,46 @@ func TestPipeline_Rules_Match(t *testing.T) { }, { rules: &Rules{Event: []string{"pull_request:labeled"}, Label: []string{"enhancement", "documentation"}}, - data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: "documentation"}, + data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, operator: "and", want: true, }, { rules: &Rules{Event: []string{"pull_request:labeled"}, Label: []string{"enhancement", "documentation"}}, - data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: "support"}, + data: &RuleData{Branch: "main", Event: "pull_request:labeled", Repo: "octocat/hello-world", Status: "pending", Label: []string{"support"}}, operator: "and", want: false, }, + { + rules: &Rules{Event: []string{"pull_request:unlabeled"}, Label: []string{"enhancement", "documentation"}}, + data: &RuleData{Branch: "main", Event: "pull_request:unlabeled", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, + operator: "and", + want: true, + }, + { + rules: &Rules{Event: []string{"pull_request:unlabeled"}, Label: []string{"enhancement"}}, + data: &RuleData{Branch: "main", Event: "pull_request:unlabeled", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, + operator: "and", + want: false, + }, + { + rules: &Rules{Event: []string{"push"}, Label: []string{"enhancement", "documentation"}}, + data: &RuleData{Branch: "main", Event: "push", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, + operator: "and", + want: true, + }, + { + rules: &Rules{Event: []string{"push"}, Label: []string{"enhancement"}}, + data: &RuleData{Branch: "main", Event: "push", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, + operator: "and", + want: false, + }, + { + rules: &Rules{Event: []string{"push"}, Label: []string{"enhancement"}}, + data: &RuleData{Branch: "main", Event: "push", Repo: "octocat/hello-world", Status: "pending", Label: []string{"documentation"}}, + operator: "or", + want: true, + }, } // run test @@ -541,7 +571,7 @@ func TestPipeline_Ruletype_MatchAnd(t *testing.T) { // run test for _, test := range tests { - got, _ := test.rule.Match(test.pattern, test.matcher, constants.OperatorAnd) + got, _ := test.rule.MatchSingle(test.pattern, test.matcher, constants.OperatorAnd) if got != test.want { t.Errorf("MatchAnd for %s matcher is %v, want %v", test.matcher, got, test.want) @@ -621,7 +651,7 @@ func TestPipeline_Ruletype_MatchOr(t *testing.T) { // run test for _, test := range tests { - got, _ := test.rule.Match(test.pattern, test.matcher, constants.OperatorOr) + got, _ := test.rule.MatchSingle(test.pattern, test.matcher, constants.OperatorOr) if got != test.want { t.Errorf("MatchOr for %s matcher is %v, want %v", test.matcher, got, test.want) diff --git a/webhook.go b/webhook.go index 8b86c357..579e8230 100644 --- a/webhook.go +++ b/webhook.go @@ -19,7 +19,7 @@ type PullRequest struct { Comment string Number int IsFromFork bool - Label string + Labels []string } // Webhook defines a struct that is used to return From 7164ea7587f855d9c6e1d1052ae2d75ad1e8d424 Mon Sep 17 00:00:00 2001 From: wsan3 Date: Tue, 2 Apr 2024 09:43:16 -0500 Subject: [PATCH 5/5] Fix linter warning --- pipeline/ruleset.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pipeline/ruleset.go b/pipeline/ruleset.go index 4575edb2..a27c8ea6 100644 --- a/pipeline/ruleset.go +++ b/pipeline/ruleset.go @@ -204,6 +204,7 @@ func (r *Ruletype) MatchSingle(data, matcher, logic string) (bool, error) { if err != nil { return false, err } + if match { return true, nil } @@ -230,6 +231,7 @@ func (r *Ruletype) MatchMultiple(data []string, matcher, logic string) (bool, er if err != nil { return false, err } + if match { return true, nil }