-
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
Conversation
prow/github/client.go
Outdated
@@ -930,6 +930,19 @@ func (c *Client) GetCombinedStatus(org, repo, ref string) (*CombinedStatus, erro | |||
return &combinedStatus, err | |||
} | |||
|
|||
// GetRequiredChecks returns the latest statuses for a given ref. | |||
func (c *Client) GetRequiredChecks(org, repo, ref string) ([]string, error) { | |||
c.log("GetCombinedStatus", org, repo, ref) |
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.
GetRequiredChecks
prow/config/tide.go
Outdated
@@ -78,6 +83,61 @@ func (t *Tide) MergeMethod(org, repo string) github.PullRequestMergeType { | |||
return v | |||
} | |||
|
|||
// TideBranchProtection is initially used to know whether to use the github status or only check for required checks. | |||
// This can extended in the future to support |
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.
unfinished sentence
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.
How do I configure the required checks? If this is something that is done on Github alongside configuring branch protection, please add pointers to documentation on how to do it.
prow/config/tide.go
Outdated
return true | ||
} | ||
for _, b := range o.Branches { | ||
if strings.Compare(b, branch) == 0 { |
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.
You can just use ==
to check for string equality in golang.
prow/config/tide_test.go
Outdated
expected: TideBranchProtection{UseRequiredCheckOnly: true}, | ||
}, | ||
{ | ||
name: "org, repo and branch listed", |
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.
All the tests have the same name.
prow/github/client.go
Outdated
@@ -930,6 +930,19 @@ func (c *Client) GetCombinedStatus(org, repo, ref string) (*CombinedStatus, erro | |||
return &combinedStatus, err | |||
} | |||
|
|||
// GetRequiredChecks returns the latest statuses for a given ref. |
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.
This needs to be updated.
prow/tide/tide.go
Outdated
|
||
requiredChecksSet := sets.NewString() | ||
if bp.UseRequiredCheckOnly { | ||
requiredChecks, err := ghc.GetRequiredChecks( |
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.
This could use too many API tokens. We need to cache this info, but I'm not sure what a sane eviction policy would be. Maybe just use a 1 hour TTL?
A proxy cache layer would handle this transparently. @stevekuznetsov @BenTheElder
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.
I don't really like the idea that we'll pull this from GitHub and lag behind the current state or that we'll instead eat API tokens for this. We could accomplish the same thing by instead requiring you to set the list of required statuses in Prow when you set them on GitHub.
It's more effort and but it's also much less complex and less magical. This worked fine for the submit-queue...
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.
With the branch protector we're also currently deriving these from Prow config for Kubernetes repos anyhow, that would just leave non-Prow statuses, which generally seem to be made required without automation anyhow.
prow/tide/tide.go
Outdated
} | ||
requiredChecksSet.Insert(requiredChecks...) | ||
} | ||
combinedSet := sets.NewString() |
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.
I think this variable name is a misnomer. It is the set of contexts seen on the PR, nothing is combined?
prow/tide/tide.go
Outdated
return false | ||
} | ||
} | ||
diff := requiredChecksSet.Difference(combinedSet) | ||
if diff.Len() != 0 { | ||
log.Info("the following checks are missing %v", strings.Join(diff.List(), ". ")) |
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.
s/". "/", "/
s/Info/Warnf/
prow/tide/tide.go
Outdated
@@ -678,7 +701,8 @@ func (c *Controller) pickBatch(sp subpool) ([]PullRequest, error) { | |||
if i == 5 { | |||
break | |||
} | |||
if !isPassingTests(sp.log, c.ghc, pr) { | |||
bp := c.ca.Config().Tide.BranchProtection.GetBranchProtection(sp.org, sp.repo, sp.branch) | |||
if !isPassingTests(sp.log, c.ghc, pr, bp) { |
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.
pickBatch
should accept bp
as a parameter from its caller instead of looking it up from the config.
This should be paired with a change to the PR dashboard as well |
prow/config/tide.go
Outdated
@@ -78,6 +83,61 @@ func (t *Tide) MergeMethod(org, repo string) github.PullRequestMergeType { | |||
return v | |||
} | |||
|
|||
// TideBranchProtection is initially used to know whether to use the github status or only check for required checks. | |||
// This can extended in the future to support all the branch protection options. |
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.
Needs a link to github docs for setting up the required checks.
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.
Do you have an example ?
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.
I added a README.md file in cmd/tide
@stevekuznetsov I don't understand what you mean. Can you elaborate ? |
@stevekuznetsov please take an extra look since this PR conflicted with #7454 |
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.
I'll create a deck
PR to change the required branches and link you there to show an example of what I mean.
prow/tide/tide.go
Outdated
@@ -157,6 +163,24 @@ func NewController(ghcSync, ghcStatus *github.Client, kc *kube.Client, ca *confi | |||
} | |||
} | |||
|
|||
func (b *branchProtection) skipContext(c string) bool { |
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.
Maybe contextRequired
as a name?
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.
done
prow/tide/tide.go
Outdated
} | ||
var ctxs []Context | ||
for _, c := range b.requiredChecks.Difference(c).List() { | ||
ctxs = append(ctxs, Context{Context: githubql.String(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.
Shouldn't construct semi-initialized Context
object here, someone's going to nil pointer downstream of this
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.
done
prow/tide/tide.go
Outdated
for _, ctx := range contexts { | ||
combinedSet.Insert(string(ctx.Context)) |
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.
Let's break this out -- the doc on this method reads "unsuccessfulContexts determines which contexts from the list are failed"
-- contexts that are not in the list should not be returned? Maybe a method missingContexts
-- but unsure of the usefulness of this actually as once you mark a context required, GitHub automatically adds it to every commit, so we're coding against GitHub acting up?
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.
Or actually on second thought, you are changing the semantics of the check entirely -- rename this method to reflect the fact that instead of identifying failed contexts from an owning set, you are checking for successful contexts from a required set.
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.
I don't think we are changing the semantic. Here, we still skip the test we don't care about, ie tests that are not required, and we add required tests that are missing. (In theory this should not happen, as github will add them as pending by default, but it is a safe guard). I updated the documentation.
prow/tide/tide.go
Outdated
config: c.ca.Config().Tide.BranchProtection.GetBranchProtection(sp.org, sp.repo, sp.branch), | ||
} | ||
if bp.config.UseRequiredChecksOnly { | ||
requiredChecks, err := c.ghc.GetRequiredChecks(sp.org, sp.repo, sp.branch) |
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.
Why should we fetch this twice (in takeAction
and here) for the same controller?
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.
As stated above, we shouldn't be fetching this from Github at all. Rather, we should maintain a list of contexts that can be ignored in the Prow config.
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.
Yes, right now AFAIK all users are either:
- manually setting required jobs, in which case it shouldn't be a big deal to add them to a prow config entry
- autogenerating the required jobs from prow config with the branch protector, in which case we should reuse that.
I think this will have much less surprising behavior than attempting to sync with github behind a cache. Without some form of caching this will probably waste too many API calls.
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.
we should not :)
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.
Why can't a required context be inferred by the following combination in the config:
- job exists (thus can be controlled)
- job.always_run: true || job.run_if_changed != ""
- job.skip_report: false
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.
@Kargakis this only works for Prow jobs, what about other CI ?
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.
I tought of getting the required contexts from the PR using githubql as this would not take any additional api call, but then I saw you were already making an API call for combined status, so the same argument holds.
No? Getting the required contexts does cost more.
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.
it will be out of sync, because that s problem that we have right now with mungegithub. We have multiple admins, and people doing changes out of our control. And you can tell people that they need to update it, but you cannot force them to.
Not if you write it in a config in the first place, which also creates a public record and controls it via PR instead of admins randomly changing merge requirements silently. Nothing is out of sync if we push out the requirements to GitHub on config load instead of trying to keep up with GitHub.
And you can tell people that they need to update it, but you cannot force them to.
Well I mean, you can't force them to use tide or the queue either, they can click merge manually even when it is deployed, but we don't optimize for this sort of broken usage either...
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.
headContexts
should not be used as an example, it is a hacky workaround for getting the status contexts for the most recent commit even though Github returns the commits in a non-useful ordering. I just had a discussion with @BenTheElder about more scalable alternatives to this.
Furthermore, headContexts
uses far fewer tokens than fetching the required contexts like you are doing would. headContexts
doesn't usually have to actually make an API call whereas GetRequiredChecks
always makes an API call.
Using GetRequiredChecks
the way it is used now is prohibitively expensive. Tide is designed to operate on multiple thousands of open PRs. If we listed the required contexts for each of these PRs when setting statuses, Tide's status sync loop could use up all of the bots hourly API tokens in a matter of minutes all on its own. This is problematic because the tokens are shared by Tide's main sync loop and all of the other Prow components.
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's fair, but it disabled by default in your use case. So can we still get this in until we have a better approach ?
prow/tide/tide.go
Outdated
@@ -987,6 +1054,7 @@ type PullRequest struct { | |||
} | |||
HeadRefOID githubql.String `graphql:"headRefOid"` | |||
Mergeable githubql.MergeableState | |||
|
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.
nit: You've got a couple of these newline diffs, maybe remove?
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.
done
prow/tide/tide_test.go
Outdated
@@ -1233,7 +1248,7 @@ func TestHeadContexts(t *testing.T) { | |||
|
|||
for _, tc := range testCases { | |||
t.Logf("Running test case %q", tc.name) | |||
fgc := &fgc{} | |||
fgc := &fgc{combinedStatus: map[string]string{win: "SUCCESS"}} |
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.
Do we want to use the GithubQL types instead of raw string literals for the context status?
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 would be changing the signature of the interface method.
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.
but I used the githubql const.
prow/tide/tide_test.go
Outdated
|
||
func TestIsPassing(t *testing.T) { | ||
headSHA := "head" | ||
success := "SUCCESS" |
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.
githubql.StatusStateSuccess
?
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.
Done
prow/tide/tide_test.go
Outdated
func TestIsPassing(t *testing.T) { | ||
headSHA := "head" | ||
success := "SUCCESS" | ||
failure := "FAILED" |
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.
githubql.StatusStateFailure
?
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.
Done
prow/tide/tide_test.go
Outdated
requiredContexts []string | ||
}{ | ||
{ | ||
name: "required checks but not used - passing", |
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.
"required checks but not used"
what does this mean? Maybe "no required checks configured"
?
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.
Wait, why is this a valid case? Shouldn't we never query GitHub for the list of required checks if the useRequiredChecksOnly
field is not true
?
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.
Right this is what the test is checking, I renamed it. PTAL
prow/tide/tide_test.go
Outdated
skipContext: true, | ||
}, | ||
{ | ||
name: "config no required checks", |
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.
This seems like something we should validate at config load time -- if you require checks but configured none, that's a user error in config
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.
the required checks are coming from the github api, so validating the config would require an api call for every branch, plus checking it at config parsing does not actually guarantees that it'll still be valid a couple of minute after.
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.
I am doing validation now since I am gertting the required checks from the branch protection config.
Thanks for the review @stevekuznetsov PTAL |
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.
I think using a whitelist/blacklist approach is the right way to go in order to save API tokens and keep config in one place instead of spread across both Prow and Github.
I'm fine with this merging as a temporary solution if this is needed urgently as long as the intent is to change it soon. What do others think?
prow/tide/tide.go
Outdated
return nil | ||
} | ||
var ctxs []Context | ||
for _, c := range b.requiredChecks.Difference(c).List() { |
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.
Please avoid variable shadowing (c
).
@fejta @cjwagner @BenTheElder @stevekuznetsov could we continue the review ? I'll resolved the conflicts once we have an agreement. |
Whitelist/blacklist seems like a good approach but what were the original design intents behind |
/assign fejta |
prow/cmd/branchprotector/protect.go
Outdated
@@ -39,11 +38,11 @@ type options struct { | |||
|
|||
func (o *options) Validate() error { | |||
if o.config == "" { | |||
return errors.New("empty --config") | |||
return fmt.Errorf("empty --config") |
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.
Unnecessary change? It doesn't format anything.
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.
it is just that it was inconsistent all over the code. Some use fmt.Errorf some use errors.New. Since errors was conflicting with errors renamed from Errors I used fmt.Errorf
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.
will revert to ease review
prow/cmd/branchprotector/protect.go
Outdated
if !after && !j.AlwaysRun && j.RunIfChanged == "" { | ||
continue // No | ||
} | ||
if !j.SkipReport { // This job needs a context |
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.
One thing we need is to change this to if !j.SkipReport && !j.Optional
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.
what is optional ?
prow/cmd/branchprotector/protect.go
Outdated
return required | ||
} | ||
|
||
func repoRequirements(org, repo string, cfg config.Config) []string { |
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.
A second thing would be to write a allBranchRequirements(org, repo, branch string, cfg config.Confg)
that combines repoRequirements()
with the union of all the contexts at branch, repo, org, overall levels...
For this PR can we focus on making these minimal set of changes and save refactoring the whole thing for later?
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 s done here https://sourcegraph.com/github.com/sebastienvas/k8s-test-infra@34a25d3febb106ca88b5ba7c8560f73cf791609f/-/blob/prow/config/branch_protection.go#L121
I moved those to the branch_protection config as this is config parsing
prow/config/jobs.go
Outdated
@@ -101,6 +101,8 @@ type Presubmit struct { | |||
Spec *v1.PodSpec `json:"spec,omitempty"` | |||
// Run these jobs after successfully running this one. | |||
RunAfterSuccess []Presubmit `json:"run_after_success"` | |||
// Defines if a test is a required check as defined in the Github Branch protection. | |||
NonRequiredCheck bool `json:"non_required_check,omitempty"` |
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.
Please avoid double negatives.
if optional
is easier on the brain than if !NonRequiredCheck
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.
well I have to make nonrequired default so I don't have much choice.
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.
Choose a better name than non-required, look up antonyms of required if you don't like my naming suggestion (optional).
optional = false
is easier to understand than nonrequired = false
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.
you are right ! Yes optional is better, sorry somehow I understood that optional was reverse of non required. I ll change it.
prow/config/tide.go
Outdated
// BranchProtection specifies branch protection options. | ||
// Initially it is used for allowing to merge a PR when required checks only are passing, | ||
// but it can be extended with more of the github features. | ||
BranchProtection TideProtection `json:"branchProtection,omitempty"` |
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.
I don't understand why this is necessary. Tide can just read the branch-protection options?
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.
don't you think one would still want to prevent merging when one of the non required checks is failing ?If not then, you are right.
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.
@BenTheElder @cjwagner what do you think ? I am fine either way.
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.
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.
I was gonna do that first, but then since you can't really query it I decided to use a separate option, but I really like your idea.
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.
actually looking at the implementation, you can define multiple queries per PR, and therefore you could define a query with ignoreOptionalContexts enabled and another disabled which could be a nice option, however when we go in the subpool we loose the query information. We could hack the code to make this happen, but that's ugly. The more I think about it, the more I think we should have an option in the branch protection config. What do you think ?
prow/tide/tide.go
Outdated
@@ -200,7 +200,7 @@ func byRepoAndNumber(prs []PullRequest) map[string]PullRequest { | |||
// Note: an empty diff can be returned if the reason that the PR does not match | |||
// the TideQuery is unknown. This can happen happen if this function's logic | |||
// does not match GitHub's and does not indicate that the PR matches the query. | |||
func requirementDiff(pr *PullRequest, q *config.TideQuery) (string, int) { | |||
func requirementDiff(pr *PullRequest, q *config.TideQuery, bp config.TideBranchProtection) (string, int) { |
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.
I think it would be useful to better separate out the concerns here, so send optionalContexts sets.String
or else an interface like:
type foo interface {
requiredSubset([]string) []string
}
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.
I ll wait for you to answer to "don't you think one would still want to prevent merging when one of the non required checks is failing ?If not then, you are right." before fixing this one
prow/cmd/branchprotector/protect.go
Outdated
// Unopinionated org, just set explicitly defined repos | ||
for r := range org.Repos { | ||
repos = append(repos, r) | ||
// Do not automatically protect tested repositories |
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.
Please do not change default behavior in this PR
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.
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.
Note that yours tests were pretty good, so it was easy to make sure that my change kept the funcionality
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.
Given that I'm essentially in the middle of refactoring this whole file to support the rest of the github options, a secondary goal is to make it as easy as possible for me to understand what parts you need me to pull in
prow/cmd/branchprotector/protect.go
Outdated
Protect bool | ||
Contexts []string | ||
Pushers []string | ||
type requirements struct { |
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.
I agree with this sort of thing generally (I had done something similar in my PR), but given that there is already a lot going on in this PR please keep as many lines the same as possible (I removed those changes from my PR to help simplify review).
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.
ok will fix
This PR is too large to review and needs to be broken down into smaller self-contained PRs. Before that happens I would suggest writting up a proposal in a google doc where we can bikeshed the design of this and discuss alternatives. |
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: sebastienvas Assign the PR to them by writing The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
2264e4b
to
71da3d1
Compare
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.
Can we squash/rebase away some of these commits?
prow/tide/contextregister.go
Outdated
} | ||
|
||
func (ic tideIC) ignoreContext(c string) bool { | ||
if c == statusContext { |
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.
nit: return c == statusContext
prow/tide/contextregister.go
Outdated
r.ics = append(r.ics, i) | ||
} | ||
|
||
func (ic tideIC) ignoreContext(c string) bool { |
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.
Add a comment that this is used for ignoring the tide status context.
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.
I actually changed the implementation to be a lot simpler.
prow/cmd/tide/README.md
Outdated
|
||
### Branch Protection Options | ||
Branch Protection options are use to enforce github branch protection. | ||
A PR will be merged when all required checks are passing, meaning we will skip optional contexts. |
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.
So if I understand this correctly, branch protection configuration stored in github specifies required contexts. Also optional
in the presubmits specifies required contexts from the prow side. Is tide going to merge based on the union of these contexts?
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.
So instead of using the data stored in github (querying github for required check on every PR consumes too many token), we use the branch protection data stored in the config. The branch protection parse the config + the prow jobs settings and all that information goes into the contextRegister
63d1e7c
to
c755b01
Compare
c755b01
to
c233bf0
Compare
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.
@cjwagner @Kargakis @stevekuznetsov can one of you take the lead for this PR?
This feels more like your part of the codebase, want to make sure it is written according to your preferred style &c
prow/cmd/tide/README.md
Outdated
```yaml | ||
tide: | ||
... | ||
skip_optional_contexts: true |
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.
This now lives in the TideQuery struct above, right?
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.
No. I tried this option, but it does not feel natural (you can't really query it) and is a bit hacky I feel. There could be multiple tide queries per PR, subpools regroups PR per (org, repo, branch), not per Tide query, so that means that we need to attach the query or the information to the PullRequest struct since querying happens way before we check for required checks. Right now I simplified this PR by creating a global tide option, but I think the right place to put this would be in the branch protection configuration. I can do that in another PR.
prow/tide/contextregister.go
Outdated
"k8s.io/apimachinery/pkg/util/sets" | ||
) | ||
|
||
type ContextChecker interface { |
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.
what other packages need a ContextChecker
?
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.
just tide.
prow/tide/contextregister.go
Outdated
|
||
// ContextRegister implements ContextChecker and allow registering of required and optional contexts. | ||
type ContextRegister struct { | ||
lock sync.RWMutex |
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.
I don't understand the need for thread safety... I don't see where we concurrently register required contexts
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.
I don't see why it would hurt anything. Tide runs multiple things in goroutine.
prow/tide/contextregister.go
Outdated
} | ||
|
||
// NewContextRegister instantiates a new ContextRegister and register the optional contexts provided. | ||
func NewContextRegister(optional ...string) *ContextRegister { |
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.
I don't see other packages using this method. Can we keep things private until they are needed?
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.
Everthing should be private I guess.
@stevekuznetsov @Kargakis @cjwagner could you please take a final look. Based on all the feedback I reduced the change dramatically to ease code review. |
// - 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
|
||
// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
done
prow/tide/tide.go
Outdated
log.WithError(err).Error("Getting branch protection") | ||
return | ||
} | ||
cr.registerRequiredContexts(findRequiredContexts(bp)...) |
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.
It doesn't seem right that all of this is conditional on sc.ca.Config().Tide.SkipOptionalContexts
.
If SkipOptionalContexts
is false we should still require the required contexts.
Additionally, it doesn't look like we are ever actually adding the optional contexts with registerOptionalContexts
.
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.
Should we get ride of the SkipOptionalContexts all together ?
The way it is implemented now, is that once required checks are registered all other checks are optional, so you don't need to register the optional ones.
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.
It doesn't provide all of the behavior cases that we need to support. I think what we really want is a SkipUnknownContexts
instead.
As this PR is currently, if we require that all statuses are green to merge (including ephemeral ones that might be on a PR) by setting SkipOptionalContexts=false
we wouldn't be able to require/expect specific statuses. This is problematic because this is the default value for a Tide config and will likely be the common case for smaller deployments since requiring all statuses to be green to merge is simple and intuitive.
We should change the contextRegister to have a set of required contexts, a set of known optional contexts (tide
and jobs marked optional) and a bool indicating if unknown statuses should be required (SkipUnknownContexts
).
When we are checking if a context can be ignored we just check that it is either
- in the optional set
or - it is not in the required set and
SkipUnknownContexts
is true.
When we are checking for missing required contexts we just ensure that all the contexts in the required set are present.
WDYT?
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.
I think there is a misunderstanding, if SkipOptionalContexts=false it is exactly as it is today. the only optional check will be tide and all other will be considered required.
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.
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.
The problem is that if SkipOptionalContexts=false
we don't want to just require that all statuses we see are green. We also want to ensure that all the required statuses actually appear and are not missing.
In other words the ignoreContext
function will work properly when SkipOptionalContexts=false
, but the missingRequiredContexts
function will not because no required contexts will have been registered.
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.
which is not different than today
prow/tide/tide.go
Outdated
@@ -972,11 +993,18 @@ func (c *Controller) presubmitsByPull(sp subpool) (map[int]sets.String, error) { | |||
} | |||
|
|||
func (c *Controller) syncSubpool(sp subpool) (Pool, error) { | |||
sp.log.Infof("Syncing subpool: %d PRs, %d PJs.", len(sp.prs), len(sp.pjs)) |
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.
Please leave this log message.
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.
done
prow/tide/tide.go
Outdated
if err != nil { | ||
return Pool{}, fmt.Errorf("error parsing branch protection: %v", err) | ||
} | ||
cr.registerRequiredContexts(findRequiredContexts(bp)...) |
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.
Same comment as above in setStatuses
.
prow/tide/tide_test.go
Outdated
config *config.Branch | ||
combinedContexts map[string]string | ||
}{ | ||
{ |
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.
Please add some test cases for optional contexts.
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.
done
prow/tide/contextregister.go
Outdated
|
||
// contextRegister implements contextChecker and allow registering of required and optional contexts. | ||
type contextRegister struct { | ||
lock sync.RWMutex |
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.
Is this lock actually necessary? It doesn't look like the contextRegister
is ever used concurrently.
If it is necessary, please either switch it to a normal mutex or use the RLock
and RUnlock
methods where appropriate.
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.
I don't understand this comment ? Why having a lock would hurt ? if there no concurrency it does not change perf and if there is it does the right thing. What is the technical concern that I am missing ?
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.
Updated to read lock.
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.
I was just wondering why it was added since this doesn't seem like something that would require locking given that we need to register all the contexts before it can be used and I would expect that to always be done from one thread.
It doesn't hurt though and is definitely fine to leave as is! We may encounter a concurrent use case later.
prow/tide/tide.go
Outdated
log.WithError(err).Error("Getting branch protection") | ||
return | ||
} | ||
cr.registerRequiredContexts(findRequiredContexts(bp)...) |
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.
It doesn't provide all of the behavior cases that we need to support. I think what we really want is a SkipUnknownContexts
instead.
As this PR is currently, if we require that all statuses are green to merge (including ephemeral ones that might be on a PR) by setting SkipOptionalContexts=false
we wouldn't be able to require/expect specific statuses. This is problematic because this is the default value for a Tide config and will likely be the common case for smaller deployments since requiring all statuses to be green to merge is simple and intuitive.
We should change the contextRegister to have a set of required contexts, a set of known optional contexts (tide
and jobs marked optional) and a bool indicating if unknown statuses should be required (SkipUnknownContexts
).
When we are checking if a context can be ignored we just check that it is either
- in the optional set
or - it is not in the required set and
SkipUnknownContexts
is true.
When we are checking for missing required contexts we just ensure that all the contexts in the required set are present.
WDYT?
PTAL |
…o required-tests
@cjwagner Please take a look! |
closed in favor of #7983 |
Ref: https://goo.gl/cd9MEu which was discussed with @fejta @cjwagner @BenTheElder
Instead of getting required checks from the github api, it will use the branch protection configuration and infere the required context from it. We are also adding a optional options to Prow Presubmits.
Config would look like this:
#7140