-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Adding a Tide Configuration for checking only github required checks. #7457
Changes from all commits
c233bf0
82e3879
20b07e4
7b4ef04
7c6e5e4
cc09455
fea785b
1016f6d
0a105df
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 |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Tide Documentation | ||
|
||
Tide merges PR that match a given sets of criteria | ||
|
||
## Tide configuration | ||
|
||
Extend the primary prow [`config.yaml`] document to include a top-level | ||
`tide` key that looks like the following: | ||
|
||
```yaml | ||
|
||
tide: | ||
queries: | ||
... | ||
branchProtection: | ||
... | ||
merge_method: | ||
... | ||
|
||
|
||
presubmits: | ||
kubernetes/test-infra: | ||
- name: fancy-job-name | ||
context: fancy-job-name | ||
always_run: true | ||
spec: # podspec that runs job | ||
``` | ||
|
||
|
||
### Merging Options | ||
|
||
Tide supports all 3 github merging options: | ||
|
||
* squash | ||
* merge | ||
* rebase | ||
|
||
A merge method can be set for repo or per org. | ||
|
||
Example: | ||
|
||
```yaml | ||
tide: | ||
... | ||
merge_method: | ||
org1: squash | ||
org2/repo1: rebase | ||
org2/repo2: merge | ||
``` | ||
|
||
### Queries Configuration | ||
|
||
Queries are using github queries to find PRs to be merged. Multiple queries can be defined for a single repo. Queries support filtering per existing and missing labels. In order to filter PRs that have been approved, use the reviewApprovedRequired. | ||
|
||
```yaml | ||
tide: | ||
queries: | ||
... | ||
- repos: | ||
- org1/repo1 | ||
- org2/repo2 | ||
labels: | ||
- labelThatMustsExists | ||
- OtherLabelThatMustsExist | ||
missingLabels: | ||
- labelThatShouldNotBePresent | ||
# If you want github approval | ||
reviewApprovedRequired: true | ||
``` | ||
|
||
### Branch Protection Options | ||
Branch Protection options are use to enforce github branch protection. | ||
A PR will be merged when all checks are passing. If we want to skip unknown checks (ie checks that are not in Prow Config), we can set skip-unknown-contexts to true. This option can be set globally or per org, repo and branch. | ||
|
||
Example: | ||
|
||
```yaml | ||
branch-protection: | ||
# Tell Tide to merge when unspecified contexts are pending or failing | ||
skip-unknown-contexts: true | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
Copyright 2018 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package tide | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/shurcooL/githubql" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/test-infra/prow/config" | ||
) | ||
|
||
type contextChecker interface { | ||
// ignoreContext tells whether a context is optional. | ||
ignoreContext(context Context) bool | ||
// missingRequiredContexts tells if required contexts are missing from the list of contexts provided. | ||
missingRequiredContexts([]Context) []Context | ||
} | ||
|
||
// newExpectedContext creates a Context with Expected state. | ||
// This should not only be used when contexts are missing. | ||
func newExpectedContext(c string) Context { | ||
return Context{ | ||
Context: githubql.String(c), | ||
State: githubql.StatusStateExpected, | ||
Description: githubql.String(""), | ||
} | ||
} | ||
|
||
// contextRegister implements contextChecker and allow registering of required and optional contexts. | ||
type contextRegister struct { | ||
lock sync.RWMutex | ||
required, optional sets.String | ||
skipUnknownContexts bool | ||
} | ||
|
||
// newContextRegister instantiates a new contextRegister and register tide as optional by default | ||
// and uses Prow Config to find optional and required tests, as well as skipUnknownContexts. | ||
func newContextRegister(skipUnknownContexts bool) *contextRegister { | ||
r := &contextRegister{ | ||
required: sets.NewString(), | ||
optional: sets.NewString(), | ||
skipUnknownContexts: skipUnknownContexts, | ||
} | ||
r.registerOptionalContexts(statusContext) | ||
return r | ||
} | ||
|
||
// newContextRegisterFromPolicy instantiates a new contextRegister and register tide as optional by default | ||
// and uses Prow Config to find optional and required tests, as well as skipUnknownContexts. | ||
func newContextRegisterFromPolicy(policy *config.Policy) *contextRegister { | ||
r := newContextRegister(false) | ||
if policy != nil { | ||
if policy.SkipUnknownContexts != nil { | ||
r.skipUnknownContexts = *policy.SkipUnknownContexts | ||
} | ||
if policy.Protect != nil && *policy.Protect { | ||
r.registerRequiredContexts(policy.Contexts...) | ||
} else if r.skipUnknownContexts { | ||
r.registerRequiredContexts(policy.Contexts...) | ||
} | ||
|
||
} | ||
return r | ||
} | ||
|
||
// newContextRegisterFromPolicy instantiates a new contextRegister and register tide as optional by default | ||
// and uses Prow Config to find optional and required tests, as well as skipUnknownContexts. | ||
func newContextRegisterFromConfig(org, repo, branch string, c *config.Config) (*contextRegister, error) { | ||
policy, err := c.GetBranchProtection(org, repo, branch) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, optional := config.BranchRequirements(org, repo, branch, c.Presubmits) | ||
r := newContextRegisterFromPolicy(policy) | ||
r.registerOptionalContexts(optional...) | ||
return r, nil | ||
} | ||
|
||
// ignoreContext checks whether a context can be ignored. | ||
// Will return true if | ||
// - context is registered as optional | ||
// - required contexts are registered and the context provided is not required | ||
// Will return false otherwise. Every context is required. | ||
func (r *contextRegister) ignoreContext(c Context) bool { | ||
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. This function need to use the mutex as a reader. 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. done |
||
r.lock.RLock() | ||
defer r.lock.RUnlock() | ||
if r.optional.Has(string(c.Context)) { | ||
return true | ||
} | ||
if r.required.Has(string(c.Context)) { | ||
return false | ||
} | ||
if r.skipUnknownContexts { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// missingRequiredContexts discard the optional contexts and only look of extra required contexts that are not provided. | ||
func (r *contextRegister) missingRequiredContexts(contexts []Context) []Context { | ||
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. This function need to use the mutex as a reader. 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. done |
||
r.lock.RLock() | ||
defer r.lock.RUnlock() | ||
if r.required.Len() == 0 { | ||
return nil | ||
} | ||
existingContexts := sets.NewString() | ||
for _, c := range contexts { | ||
existingContexts.Insert(string(c.Context)) | ||
} | ||
var missingContexts []Context | ||
for c := range r.required.Difference(existingContexts) { | ||
missingContexts = append(missingContexts, newExpectedContext(c)) | ||
} | ||
return missingContexts | ||
} | ||
|
||
// registerOptionalContexts registers optional contexts | ||
func (r *contextRegister) registerOptionalContexts(c ...string) { | ||
r.lock.Lock() | ||
defer r.lock.Unlock() | ||
r.optional.Insert(c...) | ||
} | ||
|
||
// registerRequiredContexts register required contexts. | ||
// Once required contexts are registered other contexts will be considered optional. | ||
func (r *contextRegister) registerRequiredContexts(c ...string) { | ||
r.lock.Lock() | ||
defer r.lock.Unlock() | ||
r.required.Insert(c...) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great documentation! Consider putting the README in its own PR and just adding the skip optional tag in this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that will be your insentive to get this PR in.