diff --git a/go.mod b/go.mod index bd1ec73168..5ec09bb672 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/imdario/mergo v0.3.5 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.7.3 // indirect - github.com/mcdafydd/go-azuredevops v0.10.2 + github.com/mcdafydd/go-azuredevops v0.11.1 github.com/microcosm-cc/bluemonday v1.0.1 github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286 github.com/mitchellh/go-homedir v1.0.0 @@ -53,7 +53,7 @@ require ( github.com/urfave/cli v1.20.0 github.com/urfave/negroni v0.2.0 github.com/xanzy/go-gitlab v0.22.2-0.20191127083556-16a492660b8c - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 + golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c // indirect google.golang.org/appengine v1.6.5 // indirect diff --git a/go.sum b/go.sum index 9b57293c65..dfcc04650e 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,10 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mcdafydd/go-azuredevops v0.10.2 h1:cVAxfGqSUK7i4ZRc7s+EpeWSOrDgkBM4SzTRI/IUfoE= github.com/mcdafydd/go-azuredevops v0.10.2/go.mod h1:/NYbgJ/1+9+SmG5CjETCoWm+FlLNcRwdiw1/AGW9zm0= +github.com/mcdafydd/go-azuredevops v0.11.0 h1:DHUctw4lNpPfExpwAxDsdat+n4IitH3vYLAxSGs9y0E= +github.com/mcdafydd/go-azuredevops v0.11.0/go.mod h1:B4UDyn7WEj1/97f45j3VnzEfkWKe05+/dCcAPdOET4A= +github.com/mcdafydd/go-azuredevops v0.11.1 h1:NO4wlkyFpdxqZZzNzn5m3fJc4box0jnkC8LBhAaPXeA= +github.com/mcdafydd/go-azuredevops v0.11.1/go.mod h1:B4UDyn7WEj1/97f45j3VnzEfkWKe05+/dCcAPdOET4A= github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286 h1:KHyL+3mQOF9sPfs26lsefckcFNDcIZtiACQiECzIUkw= @@ -225,6 +229,8 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/Le golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 h1:fpnn/HnJONpIu6hkXi1u/7rR0NzilgWr4T0JmWkEitk= +golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= diff --git a/server/events/vcs/azuredevops_client.go b/server/events/vcs/azuredevops_client.go index b3eee8d9eb..21a18f3154 100644 --- a/server/events/vcs/azuredevops_client.go +++ b/server/events/vcs/azuredevops_client.go @@ -124,43 +124,82 @@ func (g *AzureDevopsClient) CreateComment(repo models.Repo, pullNum int, comment return nil } -// PullIsApproved returns true if the merge request was approved. -// https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops#require-a-minimum-number-of-reviewers +// PullIsApproved returns true if the merge request was approved by another reviewer. func (g *AzureDevopsClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) { + owner, project, repoName := SplitAzureDevopsRepoFullName(repo.FullName) + opts := azuredevops.PullRequestGetOptions{ IncludeWorkItemRefs: true, } - owner, project, repoName := SplitAzureDevopsRepoFullName(repo.FullName) adPull, _, err := g.Client.PullRequests.GetWithRepo(g.ctx, owner, project, repoName, pull.Num, &opts) if err != nil { return false, errors.Wrap(err, "getting pull request") } + for _, review := range adPull.Reviewers { if review == nil { continue } - if review.GetVote() == azuredevops.VoteApproved { + + if review.IdentityRef.GetUniqueName() == adPull.GetCreatedBy().GetUniqueName() { + continue + } + + if review.GetVote() == azuredevops.VoteApproved || review.GetVote() == azuredevops.VoteApprovedWithSuggestions { return true, nil } } + return false, nil } // PullIsMergeable returns true if the merge request can be merged. func (g *AzureDevopsClient) PullIsMergeable(repo models.Repo, pull models.PullRequest) (bool, error) { - opts := azuredevops.PullRequestGetOptions{ - IncludeWorkItemRefs: true, - } owner, project, repoName := SplitAzureDevopsRepoFullName(repo.FullName) + + opts := azuredevops.PullRequestGetOptions{IncludeWorkItemRefs: true} adPull, _, err := g.Client.PullRequests.GetWithRepo(g.ctx, owner, project, repoName, pull.Num, &opts) if err != nil { return false, errors.Wrap(err, "getting pull request") } - if *adPull.MergeStatus != azuredevops.MergeConflicts.String() && - *adPull.MergeStatus != azuredevops.MergeRejectedByPolicy.String() { - return true, nil + + if *adPull.MergeStatus != azuredevops.MergeSucceeded.String() { + return false, nil } - return false, nil + + if *adPull.IsDraft { + return false, nil + } + + if *adPull.Status != azuredevops.PullActive.String() { + return false, nil + } + + projectID := *adPull.Repository.Project.ID + artifactID := g.Client.PolicyEvaluations.GetPullRequestArtifactID(projectID, pull.Num) + policyEvaluations, _, err := g.Client.PolicyEvaluations.List(g.ctx, owner, project, artifactID, &azuredevops.PolicyEvaluationsListOptions{}) + if err != nil { + return false, errors.Wrap(err, "getting policy evaluations") + } + + for _, policyEvaluation := range policyEvaluations { + if !*policyEvaluation.Configuration.IsEnabled || *policyEvaluation.Configuration.IsDeleted { + continue + } + + // Ignore the Atlantis status, even if its set as a blocker. + // This status should not be considered when evaluating if the pull request can be applied. + settings := (policyEvaluation.Configuration.Settings).(map[string]interface{}) + if status, ok := settings["statusName"]; ok && status == "atlantis/apply" { + continue + } + + if *policyEvaluation.Configuration.IsBlocking && *policyEvaluation.Status != azuredevops.PolicyEvaluationApproved { + return false, nil + } + } + + return true, nil } // GetPullRequest returns the pull request. diff --git a/server/events/vcs/azuredevops_client_test.go b/server/events/vcs/azuredevops_client_test.go index b8f6eaea1b..89cfb15cf0 100644 --- a/server/events/vcs/azuredevops_client_test.go +++ b/server/events/vcs/azuredevops_client_test.go @@ -278,53 +278,53 @@ func TestAzureDevopsClient_GetModifiedFiles(t *testing.T) { func TestAzureDevopsClient_PullIsMergeable(t *testing.T) { cases := []struct { - state string + testName string + mergeStatus string + policyStatus string expMergeable bool }{ { + "merge conflicts", azuredevops.MergeConflicts.String(), + "approved", false, }, { - azuredevops.MergeRejectedByPolicy.String(), + "rejected policy status", + azuredevops.MergeSucceeded.String(), + "rejected", false, }, { - azuredevops.MergeFailure.String(), - true, - }, - { - azuredevops.MergeNotSet.String(), - true, - }, - { - azuredevops.MergeQueued.String(), - true, - }, - { + "merge succeeded", azuredevops.MergeSucceeded.String(), + "approved", true, }, } - // Use a real Azure DevOps json response and edit the mergeable_state field. - jsBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") + jsonPullRequestBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") Ok(t, err) - json := string(jsBytes) + + jsonPolicyEvaluationBytes, err := ioutil.ReadFile("fixtures/azuredevops-policyevaluations.json") + Ok(t, err) + + pullRequestBody := string(jsonPullRequestBytes) + policyEvaluationsBody := string(jsonPolicyEvaluationBytes) for _, c := range cases { - t.Run(c.state, func(t *testing.T) { - response := strings.Replace(json, - `"mergeStatus": "NotSet"`, - fmt.Sprintf(`"mergeStatus": "%s"`, c.state), - 1, - ) + t.Run(c.testName, func(t *testing.T) { + pullRequestResponse := strings.Replace(pullRequestBody, `"mergeStatus": "notSet"`, fmt.Sprintf(`"mergeStatus": "%s"`, c.mergeStatus), 1) + policyEvaluationsResponse := strings.Replace(policyEvaluationsBody, `"status": "approved"`, fmt.Sprintf(`"status": "%s"`, c.policyStatus), 1) testServer := httptest.NewTLSServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { case "/owner/project/_apis/git/repositories/repo/pullrequests/1?api-version=5.1-preview.1&includeWorkItemRefs=true": - w.Write([]byte(response)) // nolint: errcheck + w.Write([]byte(pullRequestResponse)) // nolint: errcheck + return + case "/owner/project/_apis/policy/evaluations?api-version=5.1-preview&artifactId=vstfs%3A%2F%2F%2FCodeReview%2FCodeReviewId%2F33333333-3333-3333-333333333333%2F1": + w.Write([]byte(policyEvaluationsResponse)) // nolint: errcheck return default: t.Errorf("got unexpected request at %q", r.RequestURI) @@ -332,10 +332,13 @@ func TestAzureDevopsClient_PullIsMergeable(t *testing.T) { return } })) + testServerURL, err := url.Parse(testServer.URL) Ok(t, err) + client, err := vcs.NewAzureDevopsClient(testServerURL.Host, "token") Ok(t, err) + defer disableSSLVerification()() actMergeable, err := client.PullIsMergeable(models.Repo{ @@ -359,49 +362,57 @@ func TestAzureDevopsClient_PullIsMergeable(t *testing.T) { func TestAzureDevopsClient_PullIsApproved(t *testing.T) { cases := []struct { - testName string - vote int - expApproved bool + testName string + reviewerUniqueName string + reviewerVote int + expApproved bool }{ { "approved", + "atlantis.reviewer@example.com", azuredevops.VoteApproved, true, }, { "approved with suggestions", + "atlantis.reviewer@example.com", azuredevops.VoteApprovedWithSuggestions, - false, + true, }, { "no vote", + "atlantis.reviewer@example.com", azuredevops.VoteNone, false, }, { "vote waiting for author", + "atlantis.reviewer@example.com", azuredevops.VoteWaitingForAuthor, false, }, { "vote rejected", + "atlantis.reviewer@example.com", azuredevops.VoteRejected, false, }, + { + "approved only by author", + "atlantis.author@example.com", + azuredevops.VoteApproved, + false, + }, } - // Use a real Azure DevOps json response and edit the mergeable_state field. jsBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") Ok(t, err) - json := string(jsBytes) + json := string(jsBytes) for _, c := range cases { t.Run(c.testName, func(t *testing.T) { - response := strings.Replace(json, - `"vote": 0,`, - fmt.Sprintf(`"vote": %d,`, c.vote), - 1, - ) + response := strings.Replace(json, `"vote": 0,`, fmt.Sprintf(`"vote": %d,`, c.reviewerVote), 1) + response = strings.Replace(response, "atlantis.reviewer@example.com", c.reviewerUniqueName, 1) testServer := httptest.NewTLSServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -415,10 +426,13 @@ func TestAzureDevopsClient_PullIsApproved(t *testing.T) { return } })) + testServerURL, err := url.Parse(testServer.URL) Ok(t, err) + client, err := vcs.NewAzureDevopsClient(testServerURL.Host, "token") Ok(t, err) + defer disableSSLVerification()() actApproved, err := client.PullIsApproved(models.Repo{ diff --git a/server/events/vcs/fixtures/azuredevops-policyevaluations.json b/server/events/vcs/fixtures/azuredevops-policyevaluations.json new file mode 100644 index 0000000000..c44821ff72 --- /dev/null +++ b/server/events/vcs/fixtures/azuredevops-policyevaluations.json @@ -0,0 +1,16 @@ +{ + "value": [ + { + "configuration": { + "isDeleted": false, + "isEnabled": true, + "isBlocking": true, + "settings": { + "statusName": "pending" + } + }, + "status": "approved" + } + ], + "count": 1 +} \ No newline at end of file diff --git a/server/events/vcs/fixtures/azuredevops-pr.json b/server/events/vcs/fixtures/azuredevops-pr.json index 8e5cc5c9ca..ca5f44f441 100644 --- a/server/events/vcs/fixtures/azuredevops-pr.json +++ b/server/events/vcs/fixtures/azuredevops-pr.json @@ -1,33 +1,46 @@ { - "status": "completed", - "mergeStatus": "NotSet", - "autoCompleteSetBy": { - "id": "02228777-0784-420f-bdaa-8bc4830a14a6", - "displayName": "Atlantis User", - "uniqueName": "atlantis.user@example.com", - "url": "https://vssps.dev.azure.com/owner/_apis/Identities/02228777-0784-420f-bdaa-8bc4830a14a6", - "imageUrl": "https://dev.azure.com/owner/DefaultCollection/_api/_common/identityImage?id=02228777-0784-420f-bdaa-8bc4830a14a6" - }, - "pullRequestId": 22, - "completionOptions": { - "bypassPolicy":false, - "bypassReason":"", - "deleteSourceBranch":false, - "mergeCommitMessage":"TEST MERGE COMMIT MESSAGE", - "mergeStrategy":"noFastForward", - "squashMerge":false, - "transitionWorkItems":true, - "triggeredByAutoComplete":false - }, - "reviewers": [ - { - "reviewerUrl": "https://example:8080/tfs/_apis/git/repositories/8010495e-1002-438d-acbf-aaf245dac7c2/pullRequests/22/reviewers/8010495e-1002-438d-acbf-aaf245dac7c2", - "vote": 0, - "id": "8010495e-1002-438d-acbf-aaf245dac7c2", - "displayName": "Atlantis User", - "uniqueName": "atlantis.user@example.com", - "url": "https://owner:8080/tfs/_apis/Identities/8010495e-1002-438d-acbf-aaf245dac7c2", - "imageUrl": "https://owner:8080/tfs/_api/_common/identityImage?id=8010495e-1002-438d-acbf-aaf245dac7c2" - } - ] -} + "repository": { + "id": "22222222-2222-2222-222222222222", + "name": "MyRepository", + "project": { + "id": "33333333-3333-3333-333333333333", + "name": "MyProject", + "description": "The place for MyProject" + } + }, + "status": "active", + "createdBy": { + "displayName": "Atlantis Author", + "id": "11111111-1111-1111-111111111111", + "uniqueName": "atlantis.author@example.com" + }, + "mergeStatus": "notSet", + "isDraft": false, + "autoCompleteSetBy": { + "id": "11111111-1111-1111-111111111111", + "displayName": "Atlantis Author", + "uniqueName": "atlantis.author@example.com" + }, + "pullRequestId": 22, + "completionOptions": { + "bypassPolicy": false, + "bypassReason": "", + "deleteSourceBranch": false, + "mergeCommitMessage": "TEST MERGE COMMIT MESSAGE", + "mergeStrategy": "noFastForward", + "squashMerge": false, + "transitionWorkItems": true, + "triggeredByAutoComplete": false + }, + "reviewers": [ + { + "reviewerUrl": "https://example:8080/tfs/_apis/git/repositories/8010495e-1002-438d-acbf-aaf245dac7c2/pullRequests/22/reviewers/8010495e-1002-438d-acbf-aaf245dac7c2", + "vote": 0, + "id": "8010495e-1002-438d-acbf-aaf245dac7c2", + "displayName": "Atlantis Reviewer", + "uniqueName": "atlantis.reviewer@example.com", + "url": "https://owner:8080/tfs/_apis/Identities/8010495e-1002-438d-acbf-aaf245dac7c2", + "imageUrl": "https://owner:8080/tfs/_api/_common/identityImage?id=8010495e-1002-438d-acbf-aaf245dac7c2" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/azuredevops.go b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/azuredevops.go index e765658e30..fcc0a2da91 100644 --- a/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/azuredevops.go +++ b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/azuredevops.go @@ -40,18 +40,19 @@ type Client struct { Account string // Services used to proxy to other API endpoints - Boards *BoardsService - BuildDefinitions *BuildDefinitionsService - Builds *BuildsService - DeliveryPlans *DeliveryPlansService - Favourites *FavouritesService - Git *GitService - Iterations *IterationsService - PullRequests *PullRequestsService - Teams *TeamsService - Tests *TestsService - Users *UsersService - WorkItems *WorkItemsService + Boards *BoardsService + BuildDefinitions *BuildDefinitionsService + Builds *BuildsService + DeliveryPlans *DeliveryPlansService + Favourites *FavouritesService + Git *GitService + Iterations *IterationsService + PolicyEvaluations *PolicyEvaluationsService + PullRequests *PullRequestsService + Teams *TeamsService + Tests *TestsService + Users *UsersService + WorkItems *WorkItemsService } // NewClient returns a new Azure DevOps API client. If a nil httpClient is @@ -79,6 +80,7 @@ func NewClient(httpClient *http.Client) (*Client, error) { c.Favourites = &FavouritesService{client: c} c.Git = &GitService{client: c} c.Iterations = &IterationsService{client: c} + c.PolicyEvaluations = &PolicyEvaluationsService{client: c} c.PullRequests = &PullRequestsService{client: c} c.Teams = &TeamsService{client: c} c.Tests = &TestsService{client: c} diff --git a/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/policy_evaluations.go b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/policy_evaluations.go new file mode 100644 index 0000000000..34f12cc5ed --- /dev/null +++ b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/policy_evaluations.go @@ -0,0 +1,106 @@ +package azuredevops + +import ( + "context" + "fmt" + "net/http" +) + +// PolicyEvaluationsService handles communication with the evaluations methods on the API +// utilising https://docs.microsoft.com/en-us/rest/api/azure/devops/policy/evaluations +type PolicyEvaluationsService struct { + client *Client +} + +// PolicyEvaluationsListOptions describes what the request to the API should look like +type PolicyEvaluationsListOptions struct{} + +// PolicyEvaluationsListResponse describes a pull requests list response +type PolicyEvaluationsListResponse struct { + Count int `json:"count"` + PolicyEvaluations []*PolicyEvaluationRecord `json:"value"` +} + +// PolicyEvaluationRecord encapsulates the current state of a policy as it applies to one specific pull request. +type PolicyEvaluationRecord struct { + Links *map[string]Link `json:"_links,omitempty"` + ArtifactID *string `json:"artifactId,omitempty"` + CompletedDate *string `json:"completedDate,omitempty"` + Configuration *PolicyConfiguration `json:"configuration,omitempty"` + Context interface{} `json:"context,omitempty"` + EvaluationID *string `json:"evaluationId,omitempty"` + StartedDate *string `json:"startedDate,omitempty"` + Status *string `json:"status,omitempty"` +} + +// PolicyConfiguration is the full policy configuration with settings. +type PolicyConfiguration struct { + Links interface{} `json:"_links,omitempty"` + CreatedBy *IdentityRef `json:"createdBy,omitempty"` + CreatedDate *string `json:"createdDate,omitempty"` + ID *int `json:"id,omitempty"` + IsBlocking *bool `json:"isBlocking,omitempty"` + IsDeleted *bool `json:"isDeleted,omitempty"` + IsEnabled *bool `json:"isEnabled,omitempty"` + Revision *int `json:"revision,omitempty"` + Settings interface{} `json:"settings,omitempty"` + Type *PolicyTypeRef `json:"type,omitempty"` + Url *string `json:"url,omitempty"` +} + +// PolicyTypeRef is the policy type reference. +type PolicyTypeRef struct { + DisplayName *string `json:"displayName,omitempty"` + ID *string `json:"id,omitempty"` + Url *string `json:"url,omitempty"` +} + +const ( + // PolicyEvaluationApproved represents that the policy has been fulfilled for this pull request. + PolicyEvaluationApproved = "approved" + + // PolicyEvaluationBroken represents that the policy encountered an unexpected error. + PolicyEvaluationBroken = "broken" + + // PolicyEvaluationNotApplicable represents that the policy does not apply to this pull request. + PolicyEvaluationNotApplicable = "notApplicable" + + // PolicyEvaluationQueued represents that the policy is either queued to run, or is waiting for some event before progressing. + PolicyEvaluationQueued = "queued" + + // PolicyEvaluationRejected represents that the policy has rejected this pull request. + PolicyEvaluationRejected = "rejected" + + // PolicyEvaluationRunning represents that the policy is currently running. + PolicyEvaluationRunning = "running" +) + +// GetPullRequestArtifactID gets the Artifact ID of a pull request. +// ex: vstfs:///CodeReview/CodeReviewId/{projectId}/{pullRequestId} +func (s *PolicyEvaluationsService) GetPullRequestArtifactID(projectID string, pullRequestID int) string { + return fmt.Sprintf("vstfs:///CodeReview/CodeReviewId/%s/%d", projectID, pullRequestID) +} + +// List retrieves a list of all the policy evaluation statuses for a specific pull request. +// https://docs.microsoft.com/en-us/rest/api/azure/devops/policy/evaluations/list?view=azure-devops-rest-5.1 +func (s *PolicyEvaluationsService) List(ctx context.Context, owner, project, artifactID string, opts *PolicyEvaluationsListOptions) ([]*PolicyEvaluationRecord, *http.Response, error) { + URL := fmt.Sprintf("%s/%s/_apis/policy/evaluations?artifactId=%s&api-version=5.1-preview", + owner, + project, + artifactID, + ) + URL, err := addOptions(URL, opts) + + req, err := s.client.NewRequest("GET", URL, nil) + if err != nil { + return nil, nil, err + } + + r := new(PolicyEvaluationsListResponse) + resp, err := s.client.Execute(ctx, req, r) + if err != nil { + return nil, nil, err + } + + return r.PolicyEvaluations, resp, err +} diff --git a/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/pull_requests.go b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/pull_requests.go index 9197bcb615..ff1f84a490 100644 --- a/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/pull_requests.go +++ b/vendor/github.com/mcdafydd/go-azuredevops/azuredevops/pull_requests.go @@ -58,16 +58,16 @@ type PullRequestAsyncStatus int // PullRequestAsyncStatus enum values const ( - MergeConflicts PullRequestAsyncStatus = iota - MergeFailure - MergeNotSet + MergeNotSet PullRequestAsyncStatus = iota MergeQueued - MergeRejectedByPolicy + MergeConflicts MergeSucceeded + MergeRejectedByPolicy + MergeFailure ) func (d PullRequestAsyncStatus) String() string { - return [...]string{"conflicts", "failure", "notSet", "queued", "rejectedByPolicy", "succeeded"}[d] + return [...]string{"notSet", "queued", "conflicts", "succeeded", "rejectedByPolicy", "failure"}[d] } // PullRequestMergeFailureType The specific type of merge request failure diff --git a/vendor/golang.org/x/crypto/openpgp/armor/armor.go b/vendor/golang.org/x/crypto/openpgp/armor/armor.go index 592d186436..36a6804364 100644 --- a/vendor/golang.org/x/crypto/openpgp/armor/armor.go +++ b/vendor/golang.org/x/crypto/openpgp/armor/armor.go @@ -62,10 +62,11 @@ var armorEndOfLine = []byte("-----") // lineReader wraps a line based reader. It watches for the end of an armor // block and records the expected CRC value. type lineReader struct { - in *bufio.Reader - buf []byte - eof bool - crc uint32 + in *bufio.Reader + buf []byte + eof bool + crc uint32 + crcSet bool } func (l *lineReader) Read(p []byte) (n int, err error) { @@ -87,6 +88,11 @@ func (l *lineReader) Read(p []byte) (n int, err error) { return 0, ArmorCorrupt } + if bytes.HasPrefix(line, armorEnd) { + l.eof = true + return 0, io.EOF + } + if len(line) == 5 && line[0] == '=' { // This is the checksum line var expectedBytes [3]byte @@ -108,6 +114,7 @@ func (l *lineReader) Read(p []byte) (n int, err error) { } l.eof = true + l.crcSet = true return 0, io.EOF } @@ -141,10 +148,8 @@ func (r *openpgpReader) Read(p []byte) (n int, err error) { n, err = r.b64Reader.Read(p) r.currentCRC = crc24(r.currentCRC, p[:n]) - if err == io.EOF { - if r.lReader.crc != uint32(r.currentCRC&crc24Mask) { - return 0, ArmorCorrupt - } + if err == io.EOF && r.lReader.crcSet && r.lReader.crc != uint32(r.currentCRC&crc24Mask) { + return 0, ArmorCorrupt } return diff --git a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go index 73f4fe3785..72a6a73947 100644 --- a/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go +++ b/vendor/golang.org/x/crypto/openpgp/elgamal/elgamal.go @@ -76,7 +76,9 @@ func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err // Bleichenbacher, Advances in Cryptology (Crypto '98), func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { s := new(big.Int).Exp(c1, priv.X, priv.P) - s.ModInverse(s, priv.P) + if s.ModInverse(s, priv.P) == nil { + return nil, errors.New("elgamal: invalid private key") + } s.Mul(s, c2) s.Mod(s, priv.P) em := s.Bytes() diff --git a/vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go b/vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go index 02b372cf37..6d7639722c 100644 --- a/vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go +++ b/vendor/golang.org/x/crypto/openpgp/packet/encrypted_key.go @@ -5,6 +5,7 @@ package packet import ( + "crypto" "crypto/rsa" "encoding/binary" "io" @@ -78,8 +79,9 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { // padding oracle attacks. switch priv.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - k := priv.PrivateKey.(*rsa.PrivateKey) - b, err = rsa.DecryptPKCS1v15(config.Random(), k, padToKeySize(&k.PublicKey, e.encryptedMPI1.bytes)) + // Supports both *rsa.PrivateKey and crypto.Decrypter + k := priv.PrivateKey.(crypto.Decrypter) + b, err = k.Decrypt(config.Random(), padToKeySize(k.Public().(*rsa.PublicKey), e.encryptedMPI1.bytes), nil) case PubKeyAlgoElGamal: c1 := new(big.Int).SetBytes(e.encryptedMPI1.bytes) c2 := new(big.Int).SetBytes(e.encryptedMPI2.bytes) diff --git a/vendor/golang.org/x/crypto/openpgp/packet/packet.go b/vendor/golang.org/x/crypto/openpgp/packet/packet.go index 5af64c5421..9728d61d7a 100644 --- a/vendor/golang.org/x/crypto/openpgp/packet/packet.go +++ b/vendor/golang.org/x/crypto/openpgp/packet/packet.go @@ -14,6 +14,7 @@ import ( "crypto/rsa" "io" "math/big" + "math/bits" "golang.org/x/crypto/cast5" "golang.org/x/crypto/openpgp/errors" @@ -100,33 +101,65 @@ func (r *partialLengthReader) Read(p []byte) (n int, err error) { type partialLengthWriter struct { w io.WriteCloser lengthByte [1]byte + sentFirst bool + buf []byte } +// RFC 4880 4.2.2.4: the first partial length MUST be at least 512 octets long. +const minFirstPartialWrite = 512 + func (w *partialLengthWriter) Write(p []byte) (n int, err error) { + off := 0 + if !w.sentFirst { + if len(w.buf) > 0 || len(p) < minFirstPartialWrite { + off = len(w.buf) + w.buf = append(w.buf, p...) + if len(w.buf) < minFirstPartialWrite { + return len(p), nil + } + p = w.buf + w.buf = nil + } + w.sentFirst = true + } + + power := uint8(30) for len(p) > 0 { - for power := uint(14); power < 32; power-- { - l := 1 << power - if len(p) >= l { - w.lengthByte[0] = 224 + uint8(power) - _, err = w.w.Write(w.lengthByte[:]) - if err != nil { - return - } - var m int - m, err = w.w.Write(p[:l]) - n += m - if err != nil { - return - } - p = p[l:] - break + l := 1 << power + if len(p) < l { + power = uint8(bits.Len32(uint32(len(p)))) - 1 + l = 1 << power + } + w.lengthByte[0] = 224 + power + _, err = w.w.Write(w.lengthByte[:]) + if err == nil { + var m int + m, err = w.w.Write(p[:l]) + n += m + } + if err != nil { + if n < off { + return 0, err } + return n - off, err } + p = p[l:] } - return + return n - off, nil } func (w *partialLengthWriter) Close() error { + if len(w.buf) > 0 { + // In this case we can't send a 512 byte packet. + // Just send what we have. + p := w.buf + w.sentFirst = true + w.buf = nil + if _, err := w.Write(p); err != nil { + return err + } + } + w.lengthByte[0] = 0 _, err := w.w.Write(w.lengthByte[:]) if err != nil { diff --git a/vendor/golang.org/x/crypto/openpgp/packet/private_key.go b/vendor/golang.org/x/crypto/openpgp/packet/private_key.go index 6f8ec09384..81abb7cef9 100644 --- a/vendor/golang.org/x/crypto/openpgp/packet/private_key.go +++ b/vendor/golang.org/x/crypto/openpgp/packet/private_key.go @@ -31,7 +31,7 @@ type PrivateKey struct { encryptedData []byte cipher CipherFunction s2k func(out, in []byte) - PrivateKey interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or a crypto.Signer. + PrivateKey interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or crypto.Signer/crypto.Decrypter (Decryptor RSA only). sha1Checksum bool iv []byte } diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go index 2f04ee5b5c..d1b4fca3a9 100644 --- a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go @@ -7,6 +7,7 @@ package terminal import ( "bytes" "io" + "runtime" "strconv" "sync" "unicode/utf8" @@ -939,6 +940,8 @@ func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { // readPasswordLine reads from reader until it finds \n or io.EOF. // The slice returned does not include the \n. // readPasswordLine also ignores any \r it finds. +// Windows uses \r as end of line. So, on Windows, readPasswordLine +// reads until it finds \r and ignores any \n it finds during processing. func readPasswordLine(reader io.Reader) ([]byte, error) { var buf [1]byte var ret []byte @@ -947,10 +950,20 @@ func readPasswordLine(reader io.Reader) ([]byte, error) { n, err := reader.Read(buf[:]) if n > 0 { switch buf[0] { + case '\b': + if len(ret) > 0 { + ret = ret[:len(ret)-1] + } case '\n': - return ret, nil + if runtime.GOOS != "windows" { + return ret, nil + } + // otherwise ignore \n case '\r': - // remove \r from passwords on Windows + if runtime.GOOS == "windows" { + return ret, nil + } + // otherwise ignore \r default: ret = append(ret, buf[0]) } diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go index 5cfdf8f3f0..f614e9cb60 100644 --- a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -85,8 +85,8 @@ func ReadPassword(fd int) ([]byte, error) { } old := st - st &^= (windows.ENABLE_ECHO_INPUT) - st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) + st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT) if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { return nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index bd4dbe7dec..7587ddea7f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -177,7 +177,7 @@ github.com/magiconair/properties github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.4 github.com/mattn/go-isatty -# github.com/mcdafydd/go-azuredevops v0.10.2 +# github.com/mcdafydd/go-azuredevops v0.11.1 ## explicit github.com/mcdafydd/go-azuredevops/azuredevops # github.com/microcosm-cc/bluemonday v1.0.1 @@ -281,7 +281,7 @@ go.opencensus.io/trace go.opencensus.io/trace/internal go.opencensus.io/trace/propagation go.opencensus.io/trace/tracestate -# golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 +# golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 ## explicit golang.org/x/crypto/cast5 golang.org/x/crypto/openpgp