mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-06 22:58:58 +00:00
Azure Devops support
- Add bluemonday to strip HTML tags from comments - Rename all WorkItemEvent refs to WorkItemCommentedEvent - Rename pullEvent to event to match types - Minor comment clean-ups - Support multiple pull request links in a work item
This commit is contained in:
@@ -100,6 +100,10 @@
|
||||
branch = "master"
|
||||
name = "github.com/mcdafydd/go-azuredevops"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/microcosm-cc/bluemonday"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-test/deep"
|
||||
version = "1.0.1"
|
||||
|
||||
@@ -238,7 +238,7 @@ type EventParsing interface {
|
||||
// event given the Bitbucket Server header.
|
||||
GetBitbucketServerPullEventType(eventTypeHeader string) models.PullRequestEventType
|
||||
|
||||
// ParseAzureDevopsWorkItemEvent parses Azure Devops work item comment events.
|
||||
// ParseAzureDevopsWorkItemCommentedEvent parses Azure Devops work item comment events.
|
||||
// There is no service hook event for comments made directly on a pull request, only
|
||||
// for comments made on a work item. In order to see the content of a comment in
|
||||
// the webhook payload, the comment must be on a work item. Since a work item
|
||||
@@ -251,8 +251,8 @@ type EventParsing interface {
|
||||
// baseRepo is the repo that the pull request will be merged into.
|
||||
// user is the pull request author.
|
||||
// pullNum is the number of the pull request that triggered the webhook.
|
||||
ParseAzureDevopsWorkItemEvent(comment *azuredevops.WorkItem) (
|
||||
baseRepo models.Repo, user models.User, pullNum int, err error)
|
||||
// *** Add tests and handle linking multiple pull requests to a work item.
|
||||
ParseAzureDevopsWorkItemCommentedEvent(comment *azuredevops.WorkItem) (pullRefs []PullRef, err error)
|
||||
|
||||
// ParseAzureDevopsPull parses the response from the Azure Devops API endpoint (not
|
||||
// from a webhook) that returns a pull request.
|
||||
@@ -723,12 +723,11 @@ func (e *EventParser) ParseBitbucketServerPullEvent(body []byte) (pull models.Pu
|
||||
|
||||
// ParseAzureDevopsPullEvent parses Azure Devops pull request events.
|
||||
// See EventParsing for return value docs.
|
||||
func (e *EventParser) ParseAzureDevopsPullEvent(pullEvent azuredevops.Event) (pull models.PullRequest, pullEventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
|
||||
/*if pullEvent == nil {
|
||||
err = errors.New("pullEvent is null")
|
||||
func (e *EventParser) ParseAzureDevopsPullEvent(event azuredevops.Event) (pull models.PullRequest, pullEventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
|
||||
pullResource, ok := event.Resource.(*azuredevops.GitPullRequest)
|
||||
if !ok {
|
||||
return
|
||||
}*/
|
||||
pullResource := pullEvent.Resource.(*azuredevops.GitPullRequest)
|
||||
}
|
||||
pull, baseRepo, headRepo, err = e.ParseAzureDevopsPull(pullResource)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -743,7 +742,7 @@ func (e *EventParser) ParseAzureDevopsPullEvent(pullEvent azuredevops.Event) (pu
|
||||
err = errors.New("CreatedBy.UniqueName is null")
|
||||
return
|
||||
}
|
||||
switch pullEvent.EventType {
|
||||
switch event.EventType {
|
||||
case "git.pullrequest.created":
|
||||
pullEventType = models.OpenedPullEvent
|
||||
case "git.pullrequest.updated":
|
||||
@@ -815,22 +814,19 @@ func (e *EventParser) ParseAzureDevopsPull(pull *azuredevops.GitPullRequest) (pu
|
||||
return
|
||||
}
|
||||
|
||||
// ParseAzureDevopsWorkItemEvent parses Azure Devops work item comment events.
|
||||
// Azure Devops does not have a webhook that fires upon commenting on a pull request,
|
||||
// only for commenting on a work item.
|
||||
// It is possible to get a workitem.commented event without a reference linking it
|
||||
// to a repository if the user hasn't linked it to a pull request (relations list field
|
||||
// in the JSON payload). *** Test outcome of linking multiple pull requests to a work item.
|
||||
// Example comment in JSON payload:
|
||||
// "System.History": "<div>third comment discussion - see where this shows up in the
|
||||
// webhook. the webhook should also indicate two existing links to this work item.
|
||||
// one branch and one active pull request!</div>"
|
||||
// PullRef Data necessary to process a single pull request.
|
||||
type PullRef struct {
|
||||
BaseRepo models.Repo
|
||||
User models.User
|
||||
PullNum int
|
||||
}
|
||||
|
||||
// ParseAzureDevopsWorkItemCommentedEvent parses Azure Devops work item comment events.
|
||||
// Multiple pull requests can be linked to a single work item.
|
||||
// See EventParsing for return value docs.
|
||||
func (e *EventParser) ParseAzureDevopsWorkItemEvent(comment *azuredevops.WorkItem) (baseRepo models.Repo, user models.User, pullNum int, err error) {
|
||||
// Check for pull request links to the work item that generated the comment
|
||||
// service event. If a pull request link exists, extract its number
|
||||
// Example link URI: vstfs:///Git/PullRequestId/d03edfe3-755b-4e8d-8f88-5aa5b51aba61%2f5d4fa372-78d4-4d85-aff6-7dcbc45d44d2%2f7
|
||||
func (e *EventParser) ParseAzureDevopsWorkItemCommentedEvent(comment *azuredevops.WorkItem) (pullRefs []PullRef, err error) {
|
||||
for _, relation := range comment.Relations {
|
||||
ref := &PullRef{}
|
||||
if uri := relation.GetURL(); strings.Contains(uri, "vstfs:///Git/PullRequestId/") {
|
||||
var parsed *url.URL
|
||||
parsed, err = url.Parse(uri)
|
||||
@@ -838,28 +834,34 @@ func (e *EventParser) ParseAzureDevopsWorkItemEvent(comment *azuredevops.WorkIte
|
||||
return
|
||||
}
|
||||
pullNumStr := strings.Split(parsed.Path, "/")[5]
|
||||
pullNum, err = strconv.Atoi(pullNumStr)
|
||||
ref.PullNum, err = strconv.Atoi(pullNumStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve the linked pull request to get baseRepo and user
|
||||
client := new(azuredevops.Client)
|
||||
client, err = azuredevops.NewClient(e.AzureDevopsOrg, e.AzureDevopsProject, e.AzureDevopsToken, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pr := new(azuredevops.GitPullRequest)
|
||||
opts := azuredevops.PullRequestListOptions{}
|
||||
pr, _, err = client.PullRequests.Get(ref.PullNum, &opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
createdBy := pr.GetCreatedBy()
|
||||
ref.User = models.User{Username: createdBy.GetUniqueName()}
|
||||
ref.BaseRepo, err = e.ParseAzureDevopsRepo(pr.GetRepository())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pullRefs = append(pullRefs, *ref)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the linked pull request to get baseRepo and user
|
||||
client, err := azuredevops.NewClient(e.AzureDevopsOrg, e.AzureDevopsProject, e.AzureDevopsToken, nil)
|
||||
opts := azuredevops.PullRequestListOptions{}
|
||||
pr, _, err := client.PullRequests.Get(pullNum, &opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
createdBy := pr.GetCreatedBy()
|
||||
user = models.User{Username: createdBy.GetUniqueName()}
|
||||
baseRepo, err = e.ParseAzureDevopsRepo(pr.GetRepository())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return baseRepo, user, pullNum, err
|
||||
return pullRefs, err
|
||||
}
|
||||
|
||||
// ParseAzureDevopsRepo parses the response from the Azure Devops API endpoint that
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/google/go-github/github"
|
||||
gitlab "github.com/lkysow/go-gitlab"
|
||||
"github.com/mcdafydd/go-azuredevops/azuredevops"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/runatlantis/atlantis/server/events"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
@@ -231,7 +232,7 @@ func (e *EventsController) handleAzureDevopsPost(w http.ResponseWriter, r *http.
|
||||
e.respond(w, logging.Debug, http.StatusBadRequest, "Error unmarshaling webhook payload %s", azuredevopsReqID)
|
||||
}*/
|
||||
switch event.PayloadType {
|
||||
case azuredevops.WorkItemEvent:
|
||||
case azuredevops.WorkItemCommentedEvent:
|
||||
e.Logger.Debug("handling as comment event")
|
||||
e.HandleAzureDevopsCommentEvent(w, event, azuredevopsReqID)
|
||||
case azuredevops.PullRequestEvent:
|
||||
@@ -459,32 +460,42 @@ func (e *EventsController) HandleGitlabMergeRequestEvent(w http.ResponseWriter,
|
||||
|
||||
// HandleAzureDevopsCommentEvent handles comment events from Azure Devops where Atlantis
|
||||
// commands can come from. It's exported to make testing easier.
|
||||
// Sometimes we may want data from the parent azuredevops.Event struct, so we handle type checking here.
|
||||
func (e *EventsController) HandleAzureDevopsCommentEvent(w http.ResponseWriter, event *azuredevops.Event, azuredevopsReqID string) {
|
||||
workItem := event.Resource.(*azuredevops.WorkItem)
|
||||
fields := *workItem.Fields
|
||||
if fields["System.State"] != "New" {
|
||||
e.respond(w, logging.Debug, http.StatusOK, "Ignoring comment event since action was not created %s", azuredevopsReqID)
|
||||
comment := new(string)
|
||||
workItem, ok := event.Resource.(*azuredevops.WorkItem)
|
||||
if !ok || event.PayloadType != azuredevops.WorkItemCommentedEvent {
|
||||
e.respond(w, logging.Debug, http.StatusBadRequest, "Event.Resource is nil or received bad event type %v; Request-Id = %s", event.Resource, azuredevopsReqID)
|
||||
return
|
||||
}
|
||||
*comment, ok = (*workItem.Fields)["System.History"].(string)
|
||||
if !ok {
|
||||
e.respond(w, logging.Debug, http.StatusOK, "Ignoring comment event since comment is not a string; Request-Id = %s", azuredevopsReqID)
|
||||
return
|
||||
}
|
||||
strippedComment := bluemonday.StrictPolicy().SanitizeBytes([]byte(*comment))
|
||||
|
||||
if len(workItem.Relations) == 0 {
|
||||
e.respond(w, logging.Debug, http.StatusOK, "Ignoring comment event since no pull request is linked to work item; Request-Id = %s", azuredevopsReqID)
|
||||
return
|
||||
}
|
||||
pullRefs, err := e.Parser.ParseAzureDevopsWorkItemCommentedEvent(workItem)
|
||||
|
||||
baseRepo, user, pullNum, err := e.Parser.ParseAzureDevopsWorkItemEvent(workItem)
|
||||
if err != nil {
|
||||
e.respond(w, logging.Error, http.StatusBadRequest, "Failed parsing event: %v %s", err, azuredevopsReqID)
|
||||
return
|
||||
}
|
||||
|
||||
// *** Verify this for Azure Devops - comes from Github behavior ***
|
||||
// We pass in nil for maybeHeadRepo because the head repo data isn't
|
||||
// available in the AzureDevopsWorkItem event.
|
||||
comment := fields["System.History"]
|
||||
e.handleCommentEvent(w, baseRepo, nil, nil, user, pullNum, comment, models.AzureDevops)
|
||||
for _, ref := range pullRefs {
|
||||
e.handleCommentEvent(w, ref.BaseRepo, nil, nil, ref.User, ref.PullNum, string(strippedComment), models.AzureDevops)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAzureDevopsPullRequestEvent will delete any locks associated with the pull
|
||||
// request if the event is a pull request closed event. It's exported to make
|
||||
// testing easier.
|
||||
func (e *EventsController) HandleAzureDevopsPullRequestEvent(w http.ResponseWriter, pullEvent *azuredevops.Event, azuredevopsReqID string) {
|
||||
pull, pullEventType, baseRepo, headRepo, user, err := e.Parser.ParseAzureDevopsPullEvent(*pullEvent)
|
||||
func (e *EventsController) HandleAzureDevopsPullRequestEvent(w http.ResponseWriter, event *azuredevops.Event, azuredevopsReqID string) {
|
||||
pull, pullEventType, baseRepo, headRepo, user, err := e.Parser.ParseAzureDevopsPullEvent(*event)
|
||||
if err != nil {
|
||||
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing pull data: %s %s", err, azuredevopsReqID)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user