continue azure devops support

This commit is contained in:
David McPike
2019-04-03 23:52:48 -05:00
parent 1946dc0477
commit 03bfefe9f4
16 changed files with 127 additions and 55 deletions

View File

@@ -98,7 +98,7 @@
[[constraint]]
branch = "master"
name = "github.com/benmatselby/go-azuredevops/azuredevops"
name = "github.com/benmatselby/go-azuredevops"
[[constraint]]
name = "github.com/go-test/deep"

View File

@@ -75,7 +75,7 @@ const (
TFETokenFlag = "tfe-token"
// Flag defaults.
DefaultADHostname = "linkconsulting.visualstudio.com"
DefaultADHostname = "visualstudio.com"
DefaultCheckoutStrategy = "branch"
DefaultBitbucketBaseURL = bitbucketcloud.BaseURL
DefaultDataDir = "~/.atlantis"

View File

@@ -380,8 +380,8 @@ func TestExecute_Defaults(t *testing.T) {
Equals(t, "bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "https://dev.azure.org", passedConfig.ADBaseURL)
Equals(t, "azuredevops-token", passedConfig.AzuredevopsToken)
Equals(t, "azuredevops-user", passedConfig.AzuredevopsUser)
Equals(t, "azuredevops-token", passedConfig.AzureDevopsToken)
Equals(t, "azuredevops-user", passedConfig.AzureDevopsUser)
Equals(t, "", passedConfig.BitbucketWebhookSecret)
Equals(t, "info", passedConfig.LogLevel)
Equals(t, 4141, passedConfig.Port)

View File

@@ -65,9 +65,10 @@ type CommentBuilder interface {
// CommentParser implements CommentParsing
type CommentParser struct {
GithubUser string
GitlabUser string
BitbucketUser string
GithubUser string
GitlabUser string
BitbucketUser string
AzureDevopsUser string
}
// CommentParseResult describes the result of parsing a comment as a command.
@@ -125,6 +126,8 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
vcsUser = e.GitlabUser
case models.BitbucketCloud, models.BitbucketServer:
vcsUser = e.BitbucketUser
case models.AzureDevops:
vcsUser = e.AzureDevopsUser
}
executableNames := []string{"run", atlantisExecutable, "@" + vcsUser}
if !e.stringInSlice(args[0], executableNames) {

View File

@@ -644,9 +644,10 @@ func TestBuildPlanApplyComment(t *testing.T) {
func TestParse_VCSUsername(t *testing.T) {
cp := events.CommentParser{
GithubUser: "gh",
GitlabUser: "gl",
BitbucketUser: "bb",
GithubUser: "gh",
GitlabUser: "gl",
BitbucketUser: "bb",
AzureDevopsUser: "ad",
}
cases := []struct {
vcs models.VCSHostType
@@ -668,6 +669,10 @@ func TestParse_VCSUsername(t *testing.T) {
vcs: models.BitbucketCloud,
user: "bb",
},
{
vcs: models.AzureDevops,
user: "ad",
},
}
for _, c := range cases {

View File

@@ -245,6 +245,8 @@ type EventParser struct {
BitbucketUser string
BitbucketToken string
BitbucketServerURL string
AzureDevopsUser string
AzureDevopsToken string
}
// GetBitbucketCloudPullEventType returns the type of the pull request

View File

@@ -38,6 +38,8 @@ var parser = events.EventParser{
BitbucketUser: "bitbucket-user",
BitbucketToken: "bitbucket-token",
BitbucketServerURL: "http://mycorp.com:7490",
AzureDevopsUser: "azuredevops-user",
AzureDevopsToken: "azuredevops-token",
}
func TestParseGithubRepo(t *testing.T) {

View File

@@ -282,7 +282,6 @@ func (h VCSHostType) String() string {
return "BitbucketCloud"
case BitbucketServer:
return "BitbucketServer"
}
case AzureDevops:
return "AzureDevops"
}

View File

@@ -3,4 +3,4 @@
// APIs.
package azuredevops
const BaseURL = "https://dev.azure.com"
const BaseURL = "https://visualstudio.com"

View File

@@ -197,7 +197,7 @@ func (b *Client) prepRequest(method string, path string, body io.Reader) (*http.
}
// Add this header to disable CSRF checks.
// See https://confluence.atlassian.com/cloudkb/xsrf-check-failed-when-calling-cloud-apis-826874382.html
req.Header.Add("X-Atlassian-Token", "no-check")
//req.Header.Add("X-Atlassian-Token", "no-check")
return req, nil
}

View File

@@ -9,6 +9,7 @@ import (
"testing"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs/azuredevops"
. "github.com/runatlantis/atlantis/testing"
)
@@ -28,7 +29,7 @@ func TestClient_GetModifiedFilesPagination(t *testing.T) {
"type": "commit_file",
"links": {
"self": {
"href": "https://dev.azure.com/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py"
"href": "https://visualstudio.com/org/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py"
}
}
},
@@ -37,7 +38,7 @@ func TestClient_GetModifiedFilesPagination(t *testing.T) {
"type": "commit_file",
"links": {
"self": {
"href": "https://dev.azure.com/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py"
"href": "https://visualstudio.com/org/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py"
}
}
}
@@ -69,7 +70,7 @@ func TestClient_GetModifiedFilesPagination(t *testing.T) {
defer testServer.Close()
serverURL = testServer.URL
client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client := azuredevops.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client.BaseURL = testServer.URL
files, err := client.GetModifiedFiles(models.Repo{
@@ -79,8 +80,8 @@ func TestClient_GetModifiedFilesPagination(t *testing.T) {
CloneURL: "",
SanitizedCloneURL: "",
VCSHost: models.VCSHost{
Type: models.BitbucketCloud,
Hostname: "dev.azure.com",
Type: models.AzureDevops,
Hostname: "visualstudio.com",
},
}, models.PullRequest{
Num: 1,
@@ -105,7 +106,7 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) {
"type": "commit_file",
"links": {
"self": {
"href": "https://dev.azure.com/2.0/repositories/lkysow/atlantis-example/src/1ed8205eec00dab4f1c0a8c486a4492c98c51f8e/main.tf"
"href": "https://visualstudio.com/2.0/repositories/lkysow/atlantis-example/src/1ed8205eec00dab4f1c0a8c486a4492c98c51f8e/main.tf"
}
}
},
@@ -130,7 +131,7 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) {
}))
defer testServer.Close()
client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client := azuredevops.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client.BaseURL = testServer.URL
files, err := client.GetModifiedFiles(models.Repo{
@@ -140,8 +141,8 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) {
CloneURL: "",
SanitizedCloneURL: "",
VCSHost: models.VCSHost{
Type: models.BitbucketCloud,
Hostname: "dev.azure.com",
Type: models.AzureDevops,
Hostname: "visualstudio.com",
},
}, models.PullRequest{
Num: 1,
@@ -196,10 +197,10 @@ func TestClient_PullIsApproved(t *testing.T) {
}))
defer testServer.Close()
client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client := azuredevops.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io")
client.BaseURL = testServer.URL
repo, err := models.NewRepo(models.BitbucketServer, "owner/repo", "https://dev.azure.com/owner/repo.git", "user", "token")
repo, err := models.NewRepo(models.AzureDevops, "owner/repo", "https://visualstudio.com/owner/repo.git", "user", "token")
Ok(t, err)
approved, err := client.PullIsApproved(repo, models.PullRequest{
Num: 1,

View File

@@ -1,11 +1,11 @@
package azuredevops
const (
PullCreatedHeader = "pullrequest:created"
PullUpdatedHeader = "pullrequest:updated"
PullFulfilledHeader = "pullrequest:fulfilled"
PullCreatedHeader = "git.pullrequest.created"
PullUpdatedHeader = "git.pullrequest.updated"
PullFulfilledHeader = "git.pullrequest.merged"
PullRejectedHeader = "pullrequest:rejected"
PullCommentCreatedHeader = "pullrequest:comment_created"
PullCommentCreatedHeader = "workitem.commented"
)
type CommentEvent struct {

View File

@@ -47,7 +47,7 @@ func NewClientProxy(githubClient Client, gitlabClient Client, bitbucketCloudClie
models.Gitlab: gitlabClient,
models.BitbucketCloud: bitbucketCloudClient,
models.BitbucketServer: bitbucketServerClient,
models.AzureDevops: azuredevopsClient
models.AzureDevops: azuredevopsClient,
},
}
}

View File

@@ -31,6 +31,7 @@ import (
const githubHeader = "X-Github-Event"
const gitlabHeader = "X-Gitlab-Event"
const azuredevopsHeader = "X-AzureDevops-Event"
// bitbucketEventTypeHeader is the same in both cloud and server.
const bitbucketEventTypeHeader = "X-Event-Key"
@@ -69,6 +70,10 @@ type EventsController struct {
// UI that identifies this call as coming from Bitbucket. If empty, no
// request validation is done.
BitbucketWebhookSecret []byte
// AzureDevopsWebhookSecret is the secret added to this webhook via the Azure
// Devops UI that identifies this call as coming from Bitbucket. If empty, no
// request validation is done.
AzureDevopsWebhookSecret []byte
}
// Post handles POST webhook requests.
@@ -108,6 +113,14 @@ func (e *EventsController) Post(w http.ResponseWriter, r *http.Request) {
e.Logger.Debug("handling Bitbucket Server post")
e.handleBitbucketServerPost(w, r)
return
} else if r.Header.Get(azuredevopsHeader) != "" {
if !e.supportsHost(models.AzureDevops) {
e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request since not configured to support AzureDevops")
return
}
e.Logger.Debug("handling AzureDevops post")
e.handleAzureDevopsPost(w, r)
return
}
}
e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request")
@@ -195,6 +208,44 @@ func (e *EventsController) handleBitbucketServerPost(w http.ResponseWriter, r *h
}
}
func (e *EventsController) handleAzureDevopsPost(w http.ResponseWriter, r *http.Request) {}
/*func (e *EventsController) handleAzureDevopsPost(w http.ResponseWriter, r *http.Request) {
eventType := r.Header.Get(azuredevopsEventTypeHeader)
reqID := r.Header.Get(azuredevopsRequestIDHeader)
sig := r.Header.Get(azuredevopsSignatureHeader)
defer r.Body.Close() // nolint: errcheck
body, err := ioutil.ReadAll(r.Body)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Unable to read body: %s %s=%s", err, azuredevopsRequestIDHeader, reqID)
return
}
if eventType == azuredevops.DiagnosticsPingHeader {
// Specially handle the diagnostics:ping event because Bitbucket Server
// doesn't send the signature with this event for some reason.
e.respond(w, logging.Info, http.StatusOK, "Successfully received %s event %s=%s", eventType, azuredevopsRequestIDHeader, reqID)
return
}
if len(e.AzureDevopsWebhookSecret) > 0 {
if err := azuredevops.ValidateSignature(body, sig, e.AzureDevopsWebhookSecret); err != nil {
e.respond(w, logging.Warn, http.StatusBadRequest, errors.Wrap(err, "request did not pass validation").Error())
return
}
}
switch eventType {
case azuredevops.PullCreatedHeader, azuredevops.PullMergedHeader, azuredevops.PullDeclinedHeader, azuredevops.PullDeletedHeader:
e.Logger.Debug("handling as pull request state changed event")
e.handleBitbucketServerPullRequestEvent(w, eventType, body, reqID)
return
case azuredevops.PullCommentCreatedHeader:
e.Logger.Debug("handling as comment created event")
e.HandleBitbucketServerCommentEvent(w, body, reqID)
return
default:
e.respond(w, logging.Debug, http.StatusOK, "Ignoring unsupported event type %s %s=%s", eventType, bitbucketServerRequestIDHeader, reqID)
}
}*/
// HandleGithubCommentEvent handles comment events from GitHub where Atlantis
// commands can come from. It's exported to make testing easier.
func (e *EventsController) HandleGithubCommentEvent(w http.ResponseWriter, event *github.IssueCommentEvent, githubReqID string) {

View File

@@ -235,11 +235,14 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
BitbucketUser: userConfig.BitbucketUser,
BitbucketToken: userConfig.BitbucketToken,
BitbucketServerURL: userConfig.BitbucketBaseURL,
AzureDevopsUser: userConfig.AzureDevopsUser,
AzureDevopsToken: userConfig.AzureDevopsToken,
}
commentParser := &events.CommentParser{
GithubUser: userConfig.GithubUser,
GitlabUser: userConfig.GitlabUser,
BitbucketUser: userConfig.BitbucketUser,
GithubUser: userConfig.GithubUser,
GitlabUser: userConfig.GitlabUser,
BitbucketUser: userConfig.BitbucketUser,
AzureDevopsUser: userConfig.AzureDevopsUser,
}
defaultTfVersion := terraformClient.DefaultVersion()
pendingPlanFinder := &events.DefaultPendingPlanFinder{}
@@ -324,6 +327,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
SupportedVCSHosts: supportedVCSHosts,
VCSClient: vcsClient,
BitbucketWebhookSecret: []byte(userConfig.BitbucketWebhookSecret),
AzureDevopsWebhookSecret: []byte(userConfig.AzureDevopsWebhookSecret),
}
return &Server{
AtlantisVersion: config.AtlantisVersion,

View File

@@ -6,29 +6,34 @@ import "github.com/runatlantis/atlantis/server/logging"
// The mapstructure tags correspond to flags in cmd/server.go and are used when
// the config is parsed from a YAML file.
type UserConfig struct {
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
AtlantisURL string `mapstructure:"atlantis-url"`
Automerge bool `mapstructure:"automerge"`
BitbucketBaseURL string `mapstructure:"bitbucket-base-url"`
BitbucketToken string `mapstructure:"bitbucket-token"`
BitbucketUser string `mapstructure:"bitbucket-user"`
BitbucketWebhookSecret string `mapstructure:"bitbucket-webhook-secret"`
CheckoutStrategy string `mapstructure:"checkout-strategy"`
DataDir string `mapstructure:"data-dir"`
GithubHostname string `mapstructure:"gh-hostname"`
GithubToken string `mapstructure:"gh-token"`
GithubUser string `mapstructure:"gh-user"`
GithubWebhookSecret string `mapstructure:"gh-webhook-secret"`
GitlabHostname string `mapstructure:"gitlab-hostname"`
GitlabToken string `mapstructure:"gitlab-token"`
GitlabUser string `mapstructure:"gitlab-user"`
GitlabWebhookSecret string `mapstructure:"gitlab-webhook-secret"`
LogLevel string `mapstructure:"log-level"`
Port int `mapstructure:"port"`
RepoConfig string `mapstructure:"repo-config"`
RepoConfigJSON string `mapstructure:"repo-config-json"`
RepoWhitelist string `mapstructure:"repo-whitelist"`
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
AtlantisURL string `mapstructure:"atlantis-url"`
Automerge bool `mapstructure:"automerge"`
AzureDevopsBaseURL string `mapstructure:"azuredevops-base-url"`
AzureDevopsHostname string `mapstructure:"azuredevops-hostname"`
AzureDevopsToken string `mapstructure:"azuredevops-token"`
AzureDevopsUser string `mapstructure:"azuredevops-user"`
AzureDevopsWebhookSecret string `mapstructure:"azuredevops-webhook-secret"`
BitbucketBaseURL string `mapstructure:"bitbucket-base-url"`
BitbucketToken string `mapstructure:"bitbucket-token"`
BitbucketUser string `mapstructure:"bitbucket-user"`
BitbucketWebhookSecret string `mapstructure:"bitbucket-webhook-secret"`
CheckoutStrategy string `mapstructure:"checkout-strategy"`
DataDir string `mapstructure:"data-dir"`
GithubHostname string `mapstructure:"gh-hostname"`
GithubToken string `mapstructure:"gh-token"`
GithubUser string `mapstructure:"gh-user"`
GithubWebhookSecret string `mapstructure:"gh-webhook-secret"`
GitlabHostname string `mapstructure:"gitlab-hostname"`
GitlabToken string `mapstructure:"gitlab-token"`
GitlabUser string `mapstructure:"gitlab-user"`
GitlabWebhookSecret string `mapstructure:"gitlab-webhook-secret"`
LogLevel string `mapstructure:"log-level"`
Port int `mapstructure:"port"`
RepoConfig string `mapstructure:"repo-config"`
RepoConfigJSON string `mapstructure:"repo-config-json"`
RepoWhitelist string `mapstructure:"repo-whitelist"`
// RequireApproval is whether to require pull request approval before
// allowing terraform apply's to be run.
RequireApproval bool `mapstructure:"require-approval"`