WIP bitbucket

This commit is contained in:
Luke Kysow
2018-07-19 21:52:33 +02:00
parent 7163ed586a
commit 1fc4667c63
44 changed files with 1294 additions and 302 deletions

View File

@@ -32,32 +32,36 @@ import (
// 3. Add your flag's description etc. to the stringFlags, intFlags, or boolFlags slices.
const (
// Flag names.
AllowForkPRsFlag = "allow-fork-prs"
AllowRepoConfigFlag = "allow-repo-config"
AtlantisURLFlag = "atlantis-url"
ConfigFlag = "config"
DataDirFlag = "data-dir"
GHHostnameFlag = "gh-hostname"
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
GHWebHookSecret = "gh-webhook-secret" // nolint: gas
GitlabHostnameFlag = "gitlab-hostname"
GitlabTokenFlag = "gitlab-token"
GitlabUserFlag = "gitlab-user"
GitlabWebHookSecret = "gitlab-webhook-secret"
LogLevelFlag = "log-level"
PortFlag = "port"
RepoWhitelistFlag = "repo-whitelist"
RequireApprovalFlag = "require-approval"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
AllowForkPRsFlag = "allow-fork-prs"
AllowRepoConfigFlag = "allow-repo-config"
AtlantisURLFlag = "atlantis-url"
BitbucketHostnameFlag = "bitbucket-hostname"
BitbucketTokenFlag = "bitbucket-token"
BitbucketUserFlag = "bitbucket-user"
ConfigFlag = "config"
DataDirFlag = "data-dir"
GHHostnameFlag = "gh-hostname"
GHTokenFlag = "gh-token"
GHUserFlag = "gh-user"
GHWebHookSecret = "gh-webhook-secret" // nolint: gas
GitlabHostnameFlag = "gitlab-hostname"
GitlabTokenFlag = "gitlab-token"
GitlabUserFlag = "gitlab-user"
GitlabWebHookSecret = "gitlab-webhook-secret"
LogLevelFlag = "log-level"
PortFlag = "port"
RepoWhitelistFlag = "repo-whitelist"
RequireApprovalFlag = "require-approval"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
// Flag defaults.
DefaultDataDir = "~/.atlantis"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLogLevel = "info"
DefaultPort = 4141
DefaultBitbucketHostname = "bitbucket.org"
DefaultDataDir = "~/.atlantis"
DefaultGHHostname = "github.com"
DefaultGitlabHostname = "gitlab.com"
DefaultLogLevel = "info"
DefaultPort = 4141
)
const RedTermStart = "\033[31m"
@@ -68,6 +72,19 @@ var stringFlags = []stringFlag{
name: AtlantisURLFlag,
description: "URL that Atlantis can be reached at. Defaults to http://$(hostname):$port where $port is from --" + PortFlag + ".",
},
{
name: BitbucketUserFlag,
description: "Bitbucket username of API user.",
},
{
name: BitbucketTokenFlag,
description: "Bitbucket app password of API user. Can also be specified via the ATLANTIS_BITBUCKET_TOKEN environment variable.",
},
{
name: BitbucketHostnameFlag,
description: "Currently not supported! We only support bitbucket cloud (aka bitbucket.org) at this time.",
defaultValue: DefaultBitbucketHostname,
},
{
name: ConfigFlag,
description: "Path to config file. All flags can be set in a YAML config file instead.",
@@ -326,6 +343,9 @@ func (s *ServerCmd) setDefaults(c *server.UserConfig) {
if c.GitlabHostname == "" {
c.GitlabHostname = DefaultGitlabHostname
}
if c.BitbucketHostname == "" {
c.BitbucketHostname = DefaultBitbucketHostname
}
if c.LogLevel == "" {
c.LogLevel = DefaultLogLevel
}
@@ -344,23 +364,31 @@ func (s *ServerCmd) validate(userConfig server.UserConfig) error {
return fmt.Errorf("--%s and --%s are both required for ssl", SSLKeyFileFlag, SSLCertFileFlag)
}
if userConfig.BitbucketHostname != DefaultBitbucketHostname {
return fmt.Errorf("--%s is currently not allowed because we only support bitbucket cloud", BitbucketHostnameFlag)
}
// The following combinations are valid.
// 1. github user and token set
// 2. gitlab user and token set
// 3. all 4 set
vcsErr := fmt.Errorf("--%s and --%s or --%s and --%s must be set", GHUserFlag, GHTokenFlag, GitlabUserFlag, GitlabTokenFlag)
if ((userConfig.GithubUser == "") != (userConfig.GithubToken == "")) || ((userConfig.GitlabUser == "") != (userConfig.GitlabToken == "")) {
// 3. bitbucket user and token set
// 4. any combination of the above
vcsErr := fmt.Errorf("--%s/--%s or --%s/--%s or --%s/--%s must be set", GHUserFlag, GHTokenFlag, GitlabUserFlag, GitlabTokenFlag, BitbucketUserFlag, BitbucketTokenFlag)
if ((userConfig.GithubUser == "") != (userConfig.GithubToken == "")) || ((userConfig.GitlabUser == "") != (userConfig.GitlabToken == "")) || ((userConfig.BitbucketUser == "") != (userConfig.BitbucketToken == "")) {
return vcsErr
}
// At this point, we know that there can't be a single user/token without
// its partner, but we haven't checked if any user/token is set at all.
if userConfig.GithubUser == "" && userConfig.GitlabUser == "" {
if userConfig.GithubUser == "" && userConfig.GitlabUser == "" && userConfig.BitbucketUser == "" {
return vcsErr
}
if userConfig.RepoWhitelist == "" {
return fmt.Errorf("--%s must be set for security purposes", RepoWhitelistFlag)
}
if strings.Contains(userConfig.RepoWhitelist, "://") {
return fmt.Errorf("--%s cannot contain ://, should be hostnames only", RepoWhitelistFlag)
}
return nil
}
@@ -405,6 +433,7 @@ func (s *ServerCmd) setDataDir(userConfig *server.UserConfig) error {
func (s *ServerCmd) trimAtSymbolFromUsers(userConfig *server.UserConfig) {
userConfig.GithubUser = strings.TrimPrefix(userConfig.GithubUser, "@")
userConfig.GitlabUser = strings.TrimPrefix(userConfig.GitlabUser, "@")
userConfig.BitbucketUser = strings.TrimPrefix(userConfig.BitbucketUser, "@")
}
func (s *ServerCmd) securityWarnings(userConfig *server.UserConfig) {

View File

@@ -105,6 +105,18 @@ func TestExecute_RequireRepoWhitelist(t *testing.T) {
Equals(t, "--repo-whitelist must be set for security purposes", err.Error())
}
// Should error if the repo whitelist contained a scheme.
func TestExecute_RepoWhitelistScheme(t *testing.T) {
c := setup(map[string]interface{}{
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.RepoWhitelistFlag: "http://github.com/*",
})
err := c.Execute()
Assert(t, err != nil, "should be an error")
Equals(t, "--repo-whitelist cannot contain ://, should be hostnames only", err.Error())
}
func TestExecute_ValidateLogLevel(t *testing.T) {
t.Log("Should validate log level.")
c := setupWithDefaults(map[string]interface{}{
@@ -164,7 +176,7 @@ func TestExecute_ValidateSSLConfig(t *testing.T) {
}
func TestExecute_ValidateVCSConfig(t *testing.T) {
expErr := "--gh-user and --gh-token or --gitlab-user and --gitlab-token must be set"
expErr := "--gh-user/--gh-token or --gitlab-user/--gitlab-token or --bitbucket-user/--bitbucket-token must be set"
cases := []struct {
description string
flags map[string]interface{}
@@ -189,6 +201,13 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
},
true,
},
{
"just bitbucket token set",
map[string]interface{}{
cmd.BitbucketTokenFlag: "token",
},
true,
},
{
"just github user set",
map[string]interface{}{
@@ -203,6 +222,13 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
},
true,
},
{
"just bitbucket user set",
map[string]interface{}{
cmd.BitbucketUserFlag: "user",
},
true,
},
{
"github user and gitlab token set",
map[string]interface{}{
@@ -219,6 +245,14 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
},
true,
},
{
"github user and bitbucket token set",
map[string]interface{}{
cmd.GHUserFlag: "user",
cmd.BitbucketTokenFlag: "token",
},
true,
},
{
"github user and github token set and should be successful",
map[string]interface{}{
@@ -236,12 +270,22 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
false,
},
{
"github and gitlab user and github and gitlab token set and should be successful",
"bitbucket user and bitbucket token set and should be successful",
map[string]interface{}{
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.GitlabUserFlag: "user",
cmd.GitlabTokenFlag: "token",
cmd.BitbucketUserFlag: "user",
cmd.BitbucketTokenFlag: "token",
},
false,
},
{
"all set should be successful",
map[string]interface{}{
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.GitlabUserFlag: "user",
cmd.GitlabTokenFlag: "token",
cmd.BitbucketUserFlag: "user",
cmd.BitbucketTokenFlag: "token",
},
false,
},
@@ -261,14 +305,29 @@ func TestExecute_ValidateVCSConfig(t *testing.T) {
}
}
// Currently we only support bitbucket cloud so we shouldn't allow setting of
// the bitbucket hostname flag.
func TestExecute_BitbucketHostname(t *testing.T) {
c := setup(map[string]interface{}{
cmd.BitbucketTokenFlag: "bitbucket-token",
cmd.BitbucketUserFlag: "bitbucket-token",
cmd.BitbucketHostnameFlag: "hostname",
cmd.RepoWhitelistFlag: "*",
})
err := c.Execute()
ErrEquals(t, "--bitbucket-hostname is currently not allowed because we only support bitbucket cloud", err)
}
func TestExecute_Defaults(t *testing.T) {
t.Log("Should set the defaults for all unspecified flags.")
c := setup(map[string]interface{}{
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.GitlabUserFlag: "gitlab-user",
cmd.GitlabTokenFlag: "gitlab-token",
cmd.RepoWhitelistFlag: "*",
cmd.GHUserFlag: "user",
cmd.GHTokenFlag: "token",
cmd.GitlabUserFlag: "gitlab-user",
cmd.GitlabTokenFlag: "gitlab-token",
cmd.BitbucketUserFlag: "bitbucket-user",
cmd.BitbucketTokenFlag: "bitbucket-token",
cmd.RepoWhitelistFlag: "*",
})
err := c.Execute()
Ok(t, err)
@@ -293,6 +352,9 @@ func TestExecute_Defaults(t *testing.T) {
Equals(t, "gitlab-token", passedConfig.GitlabToken)
Equals(t, "gitlab-user", passedConfig.GitlabUser)
Equals(t, "", passedConfig.GitlabWebHookSecret)
Equals(t, "bitbucket.org", passedConfig.BitbucketHostname)
Equals(t, "bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "info", passedConfig.LogLevel)
Equals(t, 4141, passedConfig.Port)
Equals(t, false, passedConfig.RequireApproval)
@@ -357,12 +419,28 @@ func TestExecute_GitlabUser(t *testing.T) {
Equals(t, "user", passedConfig.GitlabUser)
}
func TestExecute_BitbucketUser(t *testing.T) {
t.Log("Should remove the @ from the bitbucket username if it's passed.")
c := setup(map[string]interface{}{
cmd.BitbucketUserFlag: "@user",
cmd.BitbucketTokenFlag: "token",
cmd.RepoWhitelistFlag: "*",
})
err := c.Execute()
Ok(t, err)
Equals(t, "user", passedConfig.BitbucketUser)
}
func TestExecute_Flags(t *testing.T) {
t.Log("Should use all flags that are set.")
c := setup(map[string]interface{}{
cmd.AtlantisURLFlag: "url",
cmd.AllowForkPRsFlag: true,
cmd.AllowRepoConfigFlag: true,
//cmd.BitbucketHostnameFlag: "ghhostname",
cmd.BitbucketTokenFlag: "bitbucket-token",
cmd.BitbucketUserFlag: "bitbucket-user",
cmd.DataDirFlag: "/path",
cmd.GHHostnameFlag: "ghhostname",
cmd.GHTokenFlag: "token",
@@ -385,6 +463,8 @@ func TestExecute_Flags(t *testing.T) {
Equals(t, "url", passedConfig.AtlantisURL)
Equals(t, true, passedConfig.AllowForkPRs)
Equals(t, true, passedConfig.AllowRepoConfig)
Equals(t, "bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "/path", passedConfig.DataDir)
Equals(t, "ghhostname", passedConfig.GithubHostname)
Equals(t, "token", passedConfig.GithubToken)
@@ -408,6 +488,8 @@ func TestExecute_ConfigFile(t *testing.T) {
atlantis-url: "url"
allow-fork-prs: true
allow-repo-config: true
bitbucket-token: "bitbucket-token"
bitbucket-user: "bitbucket-user"
data-dir: "/path"
gh-hostname: "ghhostname"
gh-token: "token"
@@ -434,6 +516,8 @@ ssl-key-file: key-file
Equals(t, "url", passedConfig.AtlantisURL)
Equals(t, true, passedConfig.AllowForkPRs)
Equals(t, true, passedConfig.AllowRepoConfig)
Equals(t, "bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "/path", passedConfig.DataDir)
Equals(t, "ghhostname", passedConfig.GithubHostname)
Equals(t, "token", passedConfig.GithubToken)
@@ -457,6 +541,8 @@ func TestExecute_EnvironmentOverride(t *testing.T) {
atlantis-url: "url"
allow-fork-prs: true
allow-repo-config: true
bitbucket-token: "bitbucket-token"
bitbucket-user: "bitbucket-user"
data-dir: "/path"
gh-hostname: "ghhostname"
gh-token: "token"
@@ -480,6 +566,8 @@ ssl-key-file: key-file
"ATLANTIS_URL": "override-url",
"ALLOW_FORK_PRS": "false",
"ALLOW_REPO_CONFIG": "false",
"BITBUCKET_TOKEN": "override-bitbucket-token",
"BITBUCKET_USER": "override-bitbucket-user",
"DATA_DIR": "/override-path",
"GH_HOSTNAME": "override-gh-hostname",
"GH_TOKEN": "override-gh-token",
@@ -506,6 +594,8 @@ ssl-key-file: key-file
Equals(t, "override-url", passedConfig.AtlantisURL)
Equals(t, false, passedConfig.AllowForkPRs)
Equals(t, false, passedConfig.AllowRepoConfig)
Equals(t, "override-bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "override-bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "/override-path", passedConfig.DataDir)
Equals(t, "override-gh-hostname", passedConfig.GithubHostname)
Equals(t, "override-gh-token", passedConfig.GithubToken)
@@ -529,6 +619,8 @@ func TestExecute_FlagConfigOverride(t *testing.T) {
atlantis-url: "url"
allow-fork-prs: true
allow-repo-config: true
bitbucket-token: "bitbucket-token"
bitbucket-user: "bitbucket-user"
data-dir: "/path"
gh-hostname: "ghhostname"
gh-token: "token"
@@ -551,6 +643,8 @@ ssl-key-file: key-file
cmd.AtlantisURLFlag: "override-url",
cmd.AllowForkPRsFlag: false,
cmd.AllowRepoConfigFlag: false,
cmd.BitbucketTokenFlag: "override-bitbucket-token",
cmd.BitbucketUserFlag: "override-bitbucket-user",
cmd.DataDirFlag: "/override-path",
cmd.GHHostnameFlag: "override-gh-hostname",
cmd.GHTokenFlag: "override-gh-token",
@@ -571,6 +665,8 @@ ssl-key-file: key-file
Ok(t, err)
Equals(t, "override-url", passedConfig.AtlantisURL)
Equals(t, false, passedConfig.AllowForkPRs)
Equals(t, "override-bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "override-bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "/override-path", passedConfig.DataDir)
Equals(t, "override-gh-hostname", passedConfig.GithubHostname)
Equals(t, "override-gh-token", passedConfig.GithubToken)
@@ -595,6 +691,8 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
"ATLANTIS_URL": "url",
"ALLOW_FORK_PRS": "true",
"ALLOW_REPO_CONFIG": "true",
"BITBUCKET_TOKEN": "bitbucket-token",
"BITBUCKET_USER": "bitbucket-user",
"DATA_DIR": "/path",
"GH_HOSTNAME": "gh-hostname",
"GH_TOKEN": "gh-token",
@@ -618,6 +716,8 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
cmd.AtlantisURLFlag: "override-url",
cmd.AllowForkPRsFlag: false,
cmd.AllowRepoConfigFlag: false,
cmd.BitbucketTokenFlag: "override-bitbucket-token",
cmd.BitbucketUserFlag: "override-bitbucket-user",
cmd.DataDirFlag: "/override-path",
cmd.GHHostnameFlag: "override-gh-hostname",
cmd.GHTokenFlag: "override-gh-token",
@@ -640,6 +740,8 @@ func TestExecute_FlagEnvVarOverride(t *testing.T) {
Equals(t, "override-url", passedConfig.AtlantisURL)
Equals(t, false, passedConfig.AllowForkPRs)
Equals(t, false, passedConfig.AllowRepoConfig)
Equals(t, "override-bitbucket-token", passedConfig.BitbucketToken)
Equals(t, "override-bitbucket-user", passedConfig.BitbucketUser)
Equals(t, "/override-path", passedConfig.DataDir)
Equals(t, "override-gh-hostname", passedConfig.GithubHostname)
Equals(t, "override-gh-token", passedConfig.GithubToken)

View File

@@ -32,7 +32,7 @@ type CommandRunner interface {
// RunCommentCommand is the first step after a command request has been parsed.
// It handles gathering additional information needed to execute the command
// and then calling the appropriate services to finish executing the command.
RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, cmd *CommentCommand)
RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *CommentCommand)
RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User)
}
@@ -84,7 +84,7 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
if !c.validateCtxAndComment(ctx) {
return
}
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, Plan); err != nil {
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.Pending, Plan); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
@@ -111,7 +111,7 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
// enough data to construct the Repo model and callers might want to wait until
// the event is further validated before making an additional (potentially
// wasteful) call to get the necessary data.
func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, cmd *CommentCommand) {
func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *CommentCommand) {
log := c.buildLogger(baseRepo.FullName, pullNum)
var headRepo models.Repo
if maybeHeadRepo != nil {
@@ -125,6 +125,11 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
pull, headRepo, err = c.getGithubData(baseRepo, pullNum)
case models.Gitlab:
pull, err = c.getGitlabData(baseRepo, pullNum)
case models.Bitbucket:
if maybePull == nil {
err = errors.New("pull request should not be nil, this is a bug!")
}
pull = *maybePull
default:
err = errors.New("Unknown VCS type, this is a bug!")
}
@@ -145,7 +150,7 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
return
}
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, cmd.CommandName()); err != nil {
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.Pending, cmd.CommandName()); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}

View File

@@ -27,7 +27,6 @@ import (
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/models/fixtures"
"github.com/runatlantis/atlantis/server/events/vcs"
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
logmocks "github.com/runatlantis/atlantis/server/logging/mocks"
. "github.com/runatlantis/atlantis/testing"
@@ -74,8 +73,8 @@ func TestRunCommentCommand_LogPanics(t *testing.T) {
setup(t)
ch.AllowForkPRs = true // Lets us get to the panic code.
defer func() { ch.AllowForkPRs = false }()
When(ghStatus.Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, events.Plan)).ThenPanic("panic")
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, 1, nil)
When(ghStatus.Update(fixtures.GithubRepo, fixtures.Pull, models.Pending, events.Plan)).ThenPanic("panic")
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, 1, nil)
_, _, comment := vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString()).GetCapturedArguments()
Assert(t, strings.Contains(comment, "Error: goroutine panic"), "comment should be about a goroutine panic")
}
@@ -84,7 +83,7 @@ func TestRunCommentCommand_NoGithubPullGetter(t *testing.T) {
t.Log("if DefaultCommandRunner was constructed with a nil GithubPullGetter an error should be logged")
setup(t)
ch.GithubPullGetter = nil
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, 1, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, 1, nil)
Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitHub\n", logBytes.String())
}
@@ -92,7 +91,7 @@ func TestRunCommentCommand_NoGitlabMergeGetter(t *testing.T) {
t.Log("if DefaultCommandRunner was constructed with a nil GitlabMergeRequestGetter an error should be logged")
setup(t)
ch.GitlabMergeRequestGetter = nil
ch.RunCommentCommand(fixtures.GitlabRepo, &fixtures.GitlabRepo, fixtures.User, 1, nil)
ch.RunCommentCommand(fixtures.GitlabRepo, &fixtures.GitlabRepo, nil, fixtures.User, 1, nil)
Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitLab\n", logBytes.String())
}
@@ -100,7 +99,7 @@ func TestRunCommentCommand_GithubPullErr(t *testing.T) {
t.Log("if getting the github pull request fails an error should be logged")
setup(t)
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(nil, errors.New("err"))
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
Equals(t, "[ERROR] runatlantis/atlantis#1: Making pull request API call to GitHub: err\n", logBytes.String())
}
@@ -108,7 +107,7 @@ func TestRunCommentCommand_GitlabMergeRequestErr(t *testing.T) {
t.Log("if getting the gitlab merge request fails an error should be logged")
setup(t)
When(gitlabGetter.GetMergeRequest(fixtures.GithubRepo.FullName, fixtures.Pull.Num)).ThenReturn(nil, errors.New("err"))
ch.RunCommentCommand(fixtures.GitlabRepo, &fixtures.GitlabRepo, fixtures.User, fixtures.Pull.Num, nil)
ch.RunCommentCommand(fixtures.GitlabRepo, &fixtures.GitlabRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
Equals(t, "[ERROR] runatlantis/atlantis#1: Making merge request API call to GitLab: err\n", logBytes.String())
}
@@ -119,7 +118,7 @@ func TestRunCommentCommand_GithubPullParseErr(t *testing.T) {
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, fixtures.GitlabRepo, errors.New("err"))
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
Equals(t, "[ERROR] runatlantis/atlantis#1: Extracting required fields from comment data: err\n", logBytes.String())
}
@@ -137,7 +136,7 @@ func TestRunCommentCommand_ForkPRDisabled(t *testing.T) {
headRepo.Owner = "forkrepo"
When(eventParsing.ParseGithubPull(&pull)).ThenReturn(modelPull, modelPull.BaseRepo, headRepo, nil)
ch.RunCommentCommand(fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, nil)
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on fork pull requests. To enable, set --"+ch.AllowForkPRsFlag)
}
@@ -152,7 +151,7 @@ func TestRunCommentCommand_ClosedPull(t *testing.T) {
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, fixtures.GithubRepo, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
}
@@ -182,9 +181,9 @@ func TestRunCommentCommand_FullRun(t *testing.T) {
When(projectCommandBuilder.BuildApplyCommand(matchers.AnyPtrToEventsCommandContext(), matchers.AnyPtrToEventsCommentCommand())).ThenReturn(cmdCtx, nil)
}
ch.RunCommentCommand(fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, cmd)
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, cmd)
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, c)
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.Pending, c)
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
Equals(t, expCmdResult, response)
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
@@ -204,7 +203,7 @@ func TestRunAutoplanCommands(t *testing.T) {
When(projectCommandBuilder.BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())).ThenReturn([]models.ProjectCommandContext{{RepoRelDir: ".", Workspace: "default"}}, nil)
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, events.Plan)
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.Pending, events.Plan)
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
Equals(t, expCmdResult, response)
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())

View File

@@ -27,7 +27,7 @@ import (
// the status to signify whether the plan/apply succeeds.
type CommitStatusUpdater interface {
// Update updates the status of the head commit of pull.
Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, command CommandName) error
Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command CommandName) error
// UpdateProjectResult updates the status of the head commit given the
// state of response.
UpdateProjectResult(ctx *CommandContext, commandName CommandName, res CommandResult) error
@@ -39,18 +39,18 @@ type DefaultCommitStatusUpdater struct {
}
// Update updates the commit status.
func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, command CommandName) error {
func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command CommandName) error {
description := fmt.Sprintf("%s %s", strings.Title(command.String()), strings.Title(status.String()))
return d.Client.UpdateStatus(repo, pull, status, description)
}
// UpdateProjectResult updates the commit status based on the status of res.
func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, commandName CommandName, res CommandResult) error {
var status vcs.CommitStatus
var status models.CommitStatus
if res.Error != nil || res.Failure != "" {
status = vcs.Failed
status = models.Failed
} else {
var statuses []vcs.CommitStatus
var statuses []models.CommitStatus
for _, p := range res.ProjectResults {
statuses = append(statuses, p.Status())
}
@@ -59,11 +59,11 @@ func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, co
return d.Update(ctx.BaseRepo, ctx.Pull, status, commandName)
}
func (d *DefaultCommitStatusUpdater) worstStatus(ss []vcs.CommitStatus) vcs.CommitStatus {
func (d *DefaultCommitStatusUpdater) worstStatus(ss []models.CommitStatus) models.CommitStatus {
for _, s := range ss {
if s == vcs.Failed {
return vcs.Failed
if s == models.Failed {
return models.Failed
}
}
return vcs.Success
return models.Success
}

View File

@@ -21,14 +21,13 @@ import (
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
"github.com/runatlantis/atlantis/server/events/vcs/mocks"
. "github.com/runatlantis/atlantis/testing"
)
var repoModel = models.Repo{}
var pullModel = models.PullRequest{}
var status = vcs.Success
var status = models.Success
func TestUpdate(t *testing.T) {
RegisterMockTestingT(t)
@@ -49,7 +48,7 @@ func TestUpdateProjectResult_Error(t *testing.T) {
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, events.Plan, events.CommandResult{Error: errors.New("err")})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed")
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, models.Failed, "Plan Failed")
}
func TestUpdateProjectResult_Failure(t *testing.T) {
@@ -62,7 +61,7 @@ func TestUpdateProjectResult_Failure(t *testing.T) {
s := events.DefaultCommitStatusUpdater{Client: client}
err := s.UpdateProjectResult(ctx, events.Plan, events.CommandResult{Failure: "failure"})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed")
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, models.Failed, "Plan Failed")
}
func TestUpdateProjectResult(t *testing.T) {
@@ -75,35 +74,35 @@ func TestUpdateProjectResult(t *testing.T) {
cases := []struct {
Statuses []string
Expected vcs.CommitStatus
Expected models.CommitStatus
}{
{
[]string{"success", "failure", "error"},
vcs.Failed,
models.Failed,
},
{
[]string{"failure", "error", "success"},
vcs.Failed,
models.Failed,
},
{
[]string{"success", "failure"},
vcs.Failed,
models.Failed,
},
{
[]string{"success", "error"},
vcs.Failed,
models.Failed,
},
{
[]string{"failure", "error"},
vcs.Failed,
models.Failed,
},
{
[]string{"success"},
vcs.Success,
models.Success,
},
{
[]string{"success", "success"},
vcs.Success,
models.Success,
},
}

View File

@@ -14,6 +14,7 @@
package events
import (
"encoding/json"
"fmt"
"path"
"regexp"
@@ -23,6 +24,8 @@ import (
"github.com/lkysow/go-gitlab"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs/bitbucket"
"gopkg.in/go-playground/validator.v9"
)
const gitlabPullOpened = "opened"
@@ -32,8 +35,6 @@ const usagesCols = 90
// Atlantis commands.
var multiLineRegex = regexp.MustCompile(`.*\r?\n.+`)
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_event_parsing.go EventParsing
type CommandInterface interface {
CommandName() CommandName
IsVerbose() bool
@@ -105,26 +106,124 @@ func NewCommentCommand(repoRelDir string, flags []string, name CommandName, verb
}
}
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_event_parsing.go EventParsing
type EventParsing interface {
ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pullNum int, err error)
// ParseGithubPull returns the pull request, base repo and head repo.
ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, models.Repo, error)
// ParseGithubPullEvent returns the pull request, head repo and user that
// caused the event. Base repo is available as a field on PullRequest.
ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (pull models.PullRequest, pullEventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error)
// ParseGitlabMergeEvent returns the pull request, base repo, head repo and
// user that caused the event.
ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error)
ParseGitlabMergeEvent(event gitlab.MergeEvent) (pull models.PullRequest, pullEventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseGitlabMergeCommentEvent(event gitlab.MergeCommentEvent) (baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseGitlabMergeRequest(mr *gitlab.MergeRequest, baseRepo models.Repo) models.PullRequest
ParseBitbucketCloudPullEvent(body []byte) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseBitbucketCloudCommentEvent(body []byte) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, comment string, err error)
GetBitbucketEventType(eventTypeHeader string) models.PullRequestEventType
}
type EventParser struct {
GithubUser string
GithubToken string
GitlabUser string
GitlabToken string
GithubUser string
GithubToken string
GitlabUser string
GitlabToken string
BitbucketCloudUser string
BitbucketCloudToken string
}
// GetBitbucketEventType translates the bitbucket header name into a pull
// request event type.
func (e *EventParser) GetBitbucketEventType(eventTypeHeader string) models.PullRequestEventType {
switch eventTypeHeader {
case "pullrequest:created":
return models.OpenedPullEvent
case "pullrequest:updated":
return models.UpdatedPullEvent
case "pullrequest:fulfilled", "pullrequest:rejected":
return models.ClosedPullEvent
}
return models.OtherPullEvent
}
func (e *EventParser) ParseBitbucketCloudCommentEvent(body []byte) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, comment string, err error) {
var event bitbucket.CommentEvent
if err = json.Unmarshal(body, &event); err != nil {
err = errors.Wrap(err, "parsing json")
return
}
if err = validator.New().Struct(event); err != nil {
return
}
pull, baseRepo, headRepo, user, err = e.parseCommonBitbucketEventData(event.CommonEventData)
comment = *event.Comment.Content.Raw
return
}
func (e *EventParser) parseCommonBitbucketEventData(event bitbucket.CommonEventData) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
var prState models.PullRequestState
switch *event.PullRequest.State {
case "OPEN":
prState = models.Open
case "MERGED":
prState = models.Closed
case "SUPERSEDED":
prState = models.Closed
case "DECLINE":
prState = models.Closed
default:
err = fmt.Errorf("unable to determine pull request state from %q, this is a bug!", *event.PullRequest.State)
return
}
headRepo, err = models.NewRepo(
models.Bitbucket,
*event.PullRequest.Source.Repository.FullName,
*event.PullRequest.Source.Repository.Links.HTML.HREF,
e.BitbucketCloudUser,
e.BitbucketCloudToken)
if err != nil {
return
}
baseRepo, err = models.NewRepo(
models.Bitbucket,
*event.Repository.FullName,
*event.Repository.Links.HTML.HREF,
e.BitbucketCloudUser,
e.BitbucketCloudToken)
if err != nil {
return
}
pull = models.PullRequest{
Num: *event.PullRequest.ID,
HeadCommit: *event.PullRequest.Source.Commit.Hash,
URL: *event.PullRequest.Links.HTML.HREF,
Branch: *event.PullRequest.Source.Branch.Name,
Author: *event.Actor.Username,
State: prState,
BaseRepo: baseRepo,
}
user = models.User{
Username: *event.Actor.Username,
}
return
}
func (e *EventParser) ParseBitbucketCloudPullEvent(body []byte) (pull models.PullRequest, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
var event bitbucket.PullRequestEvent
if err = json.Unmarshal(body, &event); err != nil {
err = errors.Wrap(err, "parsing json")
return
}
if err = validator.New().Struct(event); err != nil {
return
}
pull, baseRepo, headRepo, user, err = e.parseCommonBitbucketEventData(event.CommonEventData)
return
}
func (e *EventParser) ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pullNum int, err error) {
@@ -148,22 +247,36 @@ func (e *EventParser) ParseGithubIssueCommentEvent(comment *github.IssueCommentE
return
}
func (e *EventParser) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
func (e *EventParser) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (pull models.PullRequest, pullEventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
if pullEvent.PullRequest == nil {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("pull_request is null")
err = errors.New("pull_request is null")
return
}
pull, baseRepo, headRepo, err := e.ParseGithubPull(pullEvent.PullRequest)
pull, baseRepo, headRepo, err = e.ParseGithubPull(pullEvent.PullRequest)
if err != nil {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
return
}
if pullEvent.Sender == nil {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("sender is null")
err = errors.New("sender is null")
return
}
senderUsername := pullEvent.Sender.GetLogin()
if senderUsername == "" {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("sender.login is null")
err = errors.New("sender.login is null")
return
}
return pull, baseRepo, headRepo, models.User{Username: senderUsername}, nil
switch pullEvent.GetAction() {
case "opened":
pullEventType = models.OpenedPullEvent
case "synchronize":
pullEventType = models.UpdatedPullEvent
case "closed":
pullEventType = models.ClosedPullEvent
default:
pullEventType = models.OtherPullEvent
}
user = models.User{Username: senderUsername}
return
}
func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (pullModel models.PullRequest, baseRepo models.Repo, headRepo models.Repo, err error) {
@@ -223,7 +336,7 @@ func (e *EventParser) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, e
return models.NewRepo(models.Github, ghRepo.GetFullName(), ghRepo.GetCloneURL(), e.GithubUser, e.GithubToken)
}
func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (pull models.PullRequest, eventType models.PullRequestEventType, baseRepo models.Repo, headRepo models.Repo, user models.User, err error) {
modelState := models.Closed
if event.ObjectAttributes.State == gitlabPullOpened {
modelState = models.Open
@@ -231,16 +344,16 @@ func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.Pul
// GitLab also has a "merged" state, but we map that to Closed so we don't
// need to check for it.
baseRepo, err := models.NewRepo(models.Gitlab, event.Project.PathWithNamespace, event.Project.GitHTTPURL, e.GitlabUser, e.GitlabToken)
baseRepo, err = models.NewRepo(models.Gitlab, event.Project.PathWithNamespace, event.Project.GitHTTPURL, e.GitlabUser, e.GitlabToken)
if err != nil {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
return
}
headRepo, err := models.NewRepo(models.Gitlab, event.ObjectAttributes.Source.PathWithNamespace, event.ObjectAttributes.Source.GitHTTPURL, e.GitlabUser, e.GitlabToken)
headRepo, err = models.NewRepo(models.Gitlab, event.ObjectAttributes.Source.PathWithNamespace, event.ObjectAttributes.Source.GitHTTPURL, e.GitlabUser, e.GitlabToken)
if err != nil {
return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
return
}
pull := models.PullRequest{
pull = models.PullRequest{
URL: event.ObjectAttributes.URL,
Author: event.User.Username,
Num: event.ObjectAttributes.IID,
@@ -250,11 +363,22 @@ func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.Pul
BaseRepo: baseRepo,
}
user := models.User{
switch event.ObjectAttributes.Action {
case "open":
eventType = models.OpenedPullEvent
case "update":
eventType = models.UpdatedPullEvent
case "merge", "close":
eventType = models.ClosedPullEvent
default:
eventType = models.OtherPullEvent
}
user = models.User{
Username: event.User.Username,
}
return pull, baseRepo, headRepo, user, err
return
}
// ParseGitlabMergeCommentEvent creates Atlantis models out of a GitLab event.

View File

@@ -15,6 +15,8 @@ package events_test
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/google/go-github/github"
@@ -103,25 +105,25 @@ func TestParseGithubIssueCommentEvent(t *testing.T) {
}
func TestParseGithubPullEvent(t *testing.T) {
_, _, _, _, err := parser.ParseGithubPullEvent(&github.PullRequestEvent{})
_, _, _, _, _, err := parser.ParseGithubPullEvent(&github.PullRequestEvent{})
ErrEquals(t, "pull_request is null", err)
testEvent := deepcopy.Copy(PullEvent).(github.PullRequestEvent)
testEvent.PullRequest.HTMLURL = nil
_, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
_, _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
ErrEquals(t, "html_url is null", err)
testEvent = deepcopy.Copy(PullEvent).(github.PullRequestEvent)
testEvent.Sender = nil
_, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
_, _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
ErrEquals(t, "sender is null", err)
testEvent = deepcopy.Copy(PullEvent).(github.PullRequestEvent)
testEvent.Sender.Login = nil
_, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
_, _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
ErrEquals(t, "sender.login is null", err)
actPull, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGithubPullEvent(&PullEvent)
actPull, evType, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGithubPullEvent(&PullEvent)
Ok(t, err)
expBaseRepo := models.Repo{
Owner: "owner",
@@ -145,9 +147,68 @@ func TestParseGithubPullEvent(t *testing.T) {
State: models.Open,
BaseRepo: expBaseRepo,
}, actPull)
Equals(t, models.OpenedPullEvent, evType)
Equals(t, models.User{Username: "user"}, actUser)
}
func TestParseGithubPullEvent_EventType(t *testing.T) {
cases := []struct {
action string
exp models.PullRequestEventType
}{
{
action: "assigned",
exp: models.OtherPullEvent,
},
{
action: "unassigned",
exp: models.OtherPullEvent,
},
{
action: "review_requested",
exp: models.OtherPullEvent,
},
{
action: "review_request_removed",
exp: models.OtherPullEvent,
},
{
action: "labeled",
exp: models.OtherPullEvent,
},
{
action: "unlabeled",
exp: models.OtherPullEvent,
},
{
action: "opened",
exp: models.OpenedPullEvent,
},
{
action: "edited",
exp: models.OtherPullEvent,
},
{
action: "closed",
exp: models.ClosedPullEvent,
},
{
action: "reopened",
exp: models.OtherPullEvent,
},
}
for _, c := range cases {
t.Run(c.action, func(t *testing.T) {
event := deepcopy.Copy(PullEvent).(github.PullRequestEvent)
event.Action = &c.action
_, actType, _, _, _, err := parser.ParseGithubPullEvent(&event)
Ok(t, err)
Equals(t, c.exp, actType)
})
}
}
func TestParseGithubPull(t *testing.T) {
testPull := deepcopy.Copy(Pull).(github.PullRequest)
testPull.Head.SHA = nil
@@ -205,7 +266,7 @@ func TestParseGitlabMergeEvent(t *testing.T) {
var event *gitlab.MergeEvent
err := json.Unmarshal([]byte(mergeEventJSON), &event)
Ok(t, err)
pull, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGitlabMergeEvent(*event)
pull, evType, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGitlabMergeEvent(*event)
Ok(t, err)
expBaseRepo := models.Repo{
@@ -229,6 +290,7 @@ func TestParseGitlabMergeEvent(t *testing.T) {
State: models.Open,
BaseRepo: expBaseRepo,
}, pull)
Equals(t, models.OpenedPullEvent, evType)
Equals(t, expBaseRepo, actBaseRepo)
Equals(t, models.Repo{
@@ -246,11 +308,51 @@ func TestParseGitlabMergeEvent(t *testing.T) {
t.Log("If the state is closed, should set field correctly.")
event.ObjectAttributes.State = "closed"
pull, _, _, _, err = parser.ParseGitlabMergeEvent(*event)
pull, _, _, _, _, err = parser.ParseGitlabMergeEvent(*event)
Ok(t, err)
Equals(t, models.Closed, pull.State)
}
func TestParseGitlabMergeEvent_ActionType(t *testing.T) {
cases := []struct {
action string
exp models.PullRequestEventType
}{
{
action: "open",
exp: models.OpenedPullEvent,
},
{
action: "update",
exp: models.UpdatedPullEvent,
},
{
action: "merge",
exp: models.ClosedPullEvent,
},
{
action: "close",
exp: models.ClosedPullEvent,
},
{
action: "other",
exp: models.OtherPullEvent,
},
}
for _, c := range cases {
t.Run(c.action, func(t *testing.T) {
var event *gitlab.MergeEvent
eventJSON := strings.Replace(mergeEventJSON, `"action": "open"`, fmt.Sprintf(`"action": %q`, c.action), 1)
err := json.Unmarshal([]byte(eventJSON), &event)
Ok(t, err)
_, evType, _, _, _, err := parser.ParseGitlabMergeEvent(*event)
Ok(t, err)
Equals(t, c.exp, evType)
})
}
}
func TestParseGitlabMergeRequest(t *testing.T) {
t.Log("should properly parse a gitlab merge request")
var event *gitlab.MergeRequest

View File

@@ -124,7 +124,7 @@ func (m *MarkdownRenderer) renderTemplate(tmpl *template.Template, data interfac
return buf.String()
}
var singleProjectTmpl = template.Must(template.New("").Parse("{{$result := index .Results 0}}Ran {{.Command}} in dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n{{$result.Rendered}}\n" + logTmpl))
var singleProjectTmpl = template.Must(template.New("").Parse("{{$result := index .Results 0}}Ran {{.Command}} in dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
var multiProjectTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
"Ran {{.Command}} for {{ len .Results }} projects:\n" +
"{{ range $result := .Results }}" +

View File

@@ -144,7 +144,7 @@ func TestRenderProjectResults(t *testing.T) {
RepoRelDir: "path",
},
},
"Ran Plan in dir: `path` workspace: `workspace`\n```diff\nterraform-output\n```\n\n* To **discard** this plan click [here](lock-url).\n\n",
"Ran Plan in dir: `path` workspace: `workspace`\n\n```diff\nterraform-output\n```\n\n* To **discard** this plan click [here](lock-url).\n\n",
},
{
"single successful apply",
@@ -158,7 +158,7 @@ func TestRenderProjectResults(t *testing.T) {
RepoRelDir: "path",
},
},
"Ran Apply in dir: `path` workspace: `workspace`\n```diff\nsuccess\n```\n\n",
"Ran Apply in dir: `path` workspace: `workspace`\n\n```diff\nsuccess\n```\n\n",
},
{
"multiple successful plans",
@@ -220,7 +220,7 @@ func TestRenderProjectResults(t *testing.T) {
Workspace: "workspace",
},
},
"Ran Plan in dir: `path` workspace: `workspace`\n**Plan Error**\n```\nerror\n```\n\n\n",
"Ran Plan in dir: `path` workspace: `workspace`\n\n**Plan Error**\n```\nerror\n```\n\n\n",
},
{
"single failed plan",
@@ -234,7 +234,7 @@ func TestRenderProjectResults(t *testing.T) {
},
},
},
"Ran Plan in dir: `path` workspace: `workspace`\n**Plan Failed**: failure\n\n\n",
"Ran Plan in dir: `path` workspace: `workspace`\n\n**Plan Failed**: failure\n\n\n",
},
{
"successful, failed, and errored plan",

View File

@@ -0,0 +1,20 @@
package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
)
func AnyModelsPullRequestEventType() models.PullRequestEventType {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.PullRequestEventType))(nil)).Elem()))
var nullValue models.PullRequestEventType
return nullValue
}
func EqModelsPullRequestEventType(value models.PullRequestEventType) models.PullRequestEventType {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue models.PullRequestEventType
return nullValue
}

View File

@@ -0,0 +1,20 @@
package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
)
func AnyPtrToModelsPullRequest() *models.PullRequest {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*models.PullRequest))(nil)).Elem()))
var nullValue *models.PullRequest
return nullValue
}
func EqPtrToModelsPullRequest(value *models.PullRequest) *models.PullRequest {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue *models.PullRequest
return nullValue
}

View File

@@ -0,0 +1,19 @@
package matchers
import (
"reflect"
"github.com/petergtz/pegomock"
)
func AnySliceOfByte() []byte {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*([]byte))(nil)).Elem()))
var nullValue []byte
return nullValue
}
func EqSliceOfByte(value []byte) []byte {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue []byte
return nullValue
}

View File

@@ -4,17 +4,17 @@ import (
"reflect"
"github.com/petergtz/pegomock"
vcs "github.com/runatlantis/atlantis/server/events/vcs"
models "github.com/runatlantis/atlantis/server/events/models"
)
func AnyVcsCommitStatus() vcs.CommitStatus {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(vcs.CommitStatus))(nil)).Elem()))
var nullValue vcs.CommitStatus
func AnyVcsCommitStatus() models.CommitStatus {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.CommitStatus))(nil)).Elem()))
var nullValue models.CommitStatus
return nullValue
}
func EqVcsCommitStatus(value vcs.CommitStatus) vcs.CommitStatus {
func EqVcsCommitStatus(value models.CommitStatus) models.CommitStatus {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue vcs.CommitStatus
var nullValue models.CommitStatus
return nullValue
}

View File

@@ -19,8 +19,8 @@ func NewMockCommandRunner() *MockCommandRunner {
return &MockCommandRunner{fail: pegomock.GlobalFailHandler}
}
func (mock *MockCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, cmd *events.CommentCommand) {
params := []pegomock.Param{baseRepo, maybeHeadRepo, user, pullNum, cmd}
func (mock *MockCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *events.CommentCommand) {
params := []pegomock.Param{baseRepo, maybeHeadRepo, maybePull, user, pullNum, cmd}
pegomock.GetGenericMockFrom(mock).Invoke("RunCommentCommand", params, []reflect.Type{})
}
@@ -47,8 +47,8 @@ type VerifierCommandRunner struct {
inOrderContext *pegomock.InOrderContext
}
func (verifier *VerifierCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, cmd *events.CommentCommand) *CommandRunner_RunCommentCommand_OngoingVerification {
params := []pegomock.Param{baseRepo, maybeHeadRepo, user, pullNum, cmd}
func (verifier *VerifierCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, cmd *events.CommentCommand) *CommandRunner_RunCommentCommand_OngoingVerification {
params := []pegomock.Param{baseRepo, maybeHeadRepo, maybePull, user, pullNum, cmd}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommentCommand", params)
return &CommandRunner_RunCommentCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -58,12 +58,12 @@ type CommandRunner_RunCommentCommand_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetCapturedArguments() (models.Repo, *models.Repo, models.User, int, *events.CommentCommand) {
baseRepo, maybeHeadRepo, user, pullNum, cmd := c.GetAllCapturedArguments()
return baseRepo[len(baseRepo)-1], maybeHeadRepo[len(maybeHeadRepo)-1], user[len(user)-1], pullNum[len(pullNum)-1], cmd[len(cmd)-1]
func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetCapturedArguments() (models.Repo, *models.Repo, *models.PullRequest, models.User, int, *events.CommentCommand) {
baseRepo, maybeHeadRepo, maybePull, user, pullNum, cmd := c.GetAllCapturedArguments()
return baseRepo[len(baseRepo)-1], maybeHeadRepo[len(maybeHeadRepo)-1], maybePull[len(maybePull)-1], user[len(user)-1], pullNum[len(pullNum)-1], cmd[len(cmd)-1]
}
func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []*models.Repo, _param2 []models.User, _param3 []int, _param4 []*events.CommentCommand) {
func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []*models.Repo, _param2 []*models.PullRequest, _param3 []models.User, _param4 []int, _param5 []*events.CommentCommand) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -74,17 +74,21 @@ func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetAllCapturedArgu
for u, param := range params[1] {
_param1[u] = param.(*models.Repo)
}
_param2 = make([]models.User, len(params[2]))
_param2 = make([]*models.PullRequest, len(params[2]))
for u, param := range params[2] {
_param2[u] = param.(models.User)
_param2[u] = param.(*models.PullRequest)
}
_param3 = make([]int, len(params[3]))
_param3 = make([]models.User, len(params[3]))
for u, param := range params[3] {
_param3[u] = param.(int)
_param3[u] = param.(models.User)
}
_param4 = make([]*events.CommentCommand, len(params[4]))
_param4 = make([]int, len(params[4]))
for u, param := range params[4] {
_param4[u] = param.(*events.CommentCommand)
_param4[u] = param.(int)
}
_param5 = make([]*events.CommentCommand, len(params[5]))
for u, param := range params[5] {
_param5[u] = param.(*events.CommentCommand)
}
}
return

View File

@@ -9,7 +9,6 @@ import (
pegomock "github.com/petergtz/pegomock"
events "github.com/runatlantis/atlantis/server/events"
models "github.com/runatlantis/atlantis/server/events/models"
vcs "github.com/runatlantis/atlantis/server/events/vcs"
)
type MockCommitStatusUpdater struct {
@@ -20,7 +19,7 @@ func NewMockCommitStatusUpdater() *MockCommitStatusUpdater {
return &MockCommitStatusUpdater{fail: pegomock.GlobalFailHandler}
}
func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, command events.CommandName) error {
func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command events.CommandName) error {
params := []pegomock.Param{repo, pull, status, command}
result := pegomock.GetGenericMockFrom(mock).Invoke("Update", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
@@ -62,7 +61,7 @@ type VerifierCommitStatusUpdater struct {
inOrderContext *pegomock.InOrderContext
}
func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, command events.CommandName) *CommitStatusUpdater_Update_OngoingVerification {
func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status models.CommitStatus, command events.CommandName) *CommitStatusUpdater_Update_OngoingVerification {
params := []pegomock.Param{repo, pull, status, command}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Update", params)
return &CommitStatusUpdater_Update_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
@@ -73,12 +72,12 @@ type CommitStatusUpdater_Update_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, events.CommandName) {
func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, events.CommandName) {
repo, pull, status, command := c.GetAllCapturedArguments()
return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], command[len(command)-1]
}
func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []events.CommandName) {
func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []events.CommandName) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -89,9 +88,9 @@ func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments
for u, param := range params[1] {
_param1[u] = param.(models.PullRequest)
}
_param2 = make([]vcs.CommitStatus, len(params[2]))
_param2 = make([]models.CommitStatus, len(params[2]))
for u, param := range params[2] {
_param2[u] = param.(vcs.CommitStatus)
_param2[u] = param.(models.CommitStatus)
}
_param3 = make([]events.CommandName, len(params[3]))
for u, param := range params[3] {

View File

@@ -68,32 +68,36 @@ func (mock *MockEventParsing) ParseGithubPull(pull *github.PullRequest) (models.
return ret0, ret1, ret2, ret3
}
func (mock *MockEventParsing) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
func (mock *MockEventParsing) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (models.PullRequest, models.PullRequestEventType, models.Repo, models.Repo, models.User, error) {
params := []pegomock.Param{pullEvent}
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubPullEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubPullEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.PullRequestEventType)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret1 models.PullRequestEventType
var ret2 models.Repo
var ret3 models.User
var ret4 error
var ret3 models.Repo
var ret4 models.User
var ret5 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(models.PullRequest)
}
if result[1] != nil {
ret1 = result[1].(models.Repo)
ret1 = result[1].(models.PullRequestEventType)
}
if result[2] != nil {
ret2 = result[2].(models.Repo)
}
if result[3] != nil {
ret3 = result[3].(models.User)
ret3 = result[3].(models.Repo)
}
if result[4] != nil {
ret4 = result[4].(error)
ret4 = result[4].(models.User)
}
if result[5] != nil {
ret5 = result[5].(error)
}
}
return ret0, ret1, ret2, ret3, ret4
return ret0, ret1, ret2, ret3, ret4, ret5
}
func (mock *MockEventParsing) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
@@ -112,32 +116,36 @@ func (mock *MockEventParsing) ParseGithubRepo(ghRepo *github.Repository) (models
return ret0, ret1
}
func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) (models.PullRequest, models.PullRequestEventType, models.Repo, models.Repo, models.User, error) {
params := []pegomock.Param{event}
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.PullRequestEventType)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret1 models.PullRequestEventType
var ret2 models.Repo
var ret3 models.User
var ret4 error
var ret3 models.Repo
var ret4 models.User
var ret5 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(models.PullRequest)
}
if result[1] != nil {
ret1 = result[1].(models.Repo)
ret1 = result[1].(models.PullRequestEventType)
}
if result[2] != nil {
ret2 = result[2].(models.Repo)
}
if result[3] != nil {
ret3 = result[3].(models.User)
ret3 = result[3].(models.Repo)
}
if result[4] != nil {
ret4 = result[4].(error)
ret4 = result[4].(models.User)
}
if result[5] != nil {
ret5 = result[5].(error)
}
}
return ret0, ret1, ret2, ret3, ret4
return ret0, ret1, ret2, ret3, ret4, ret5
}
func (mock *MockEventParsing) ParseGitlabMergeCommentEvent(event go_gitlab.MergeCommentEvent) (models.Repo, models.Repo, models.User, error) {
@@ -176,6 +184,78 @@ func (mock *MockEventParsing) ParseGitlabMergeRequest(mr *go_gitlab.MergeRequest
return ret0
}
func (mock *MockEventParsing) ParseBitbucketCloudPullEvent(body []byte) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
params := []pegomock.Param{body}
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseBitbucketCloudPullEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret2 models.Repo
var ret3 models.User
var ret4 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(models.PullRequest)
}
if result[1] != nil {
ret1 = result[1].(models.Repo)
}
if result[2] != nil {
ret2 = result[2].(models.Repo)
}
if result[3] != nil {
ret3 = result[3].(models.User)
}
if result[4] != nil {
ret4 = result[4].(error)
}
}
return ret0, ret1, ret2, ret3, ret4
}
func (mock *MockEventParsing) ParseBitbucketCloudCommentEvent(body []byte) (models.PullRequest, models.Repo, models.Repo, models.User, string, error) {
params := []pegomock.Param{body}
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseBitbucketCloudCommentEvent", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret2 models.Repo
var ret3 models.User
var ret4 string
var ret5 error
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(models.PullRequest)
}
if result[1] != nil {
ret1 = result[1].(models.Repo)
}
if result[2] != nil {
ret2 = result[2].(models.Repo)
}
if result[3] != nil {
ret3 = result[3].(models.User)
}
if result[4] != nil {
ret4 = result[4].(string)
}
if result[5] != nil {
ret5 = result[5].(error)
}
}
return ret0, ret1, ret2, ret3, ret4, ret5
}
func (mock *MockEventParsing) GetBitbucketEventType(eventTypeHeader string) models.PullRequestEventType {
params := []pegomock.Param{eventTypeHeader}
result := pegomock.GetGenericMockFrom(mock).Invoke("GetBitbucketEventType", params, []reflect.Type{reflect.TypeOf((*models.PullRequestEventType)(nil)).Elem()})
var ret0 models.PullRequestEventType
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(models.PullRequestEventType)
}
}
return ret0
}
func (mock *MockEventParsing) VerifyWasCalledOnce() *VerifierEventParsing {
return &VerifierEventParsing{mock, pegomock.Times(1), nil}
}
@@ -386,3 +466,84 @@ func (c *EventParsing_ParseGitlabMergeRequest_OngoingVerification) GetAllCapture
}
return
}
func (verifier *VerifierEventParsing) ParseBitbucketCloudPullEvent(body []byte) *EventParsing_ParseBitbucketCloudPullEvent_OngoingVerification {
params := []pegomock.Param{body}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseBitbucketCloudPullEvent", params)
return &EventParsing_ParseBitbucketCloudPullEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type EventParsing_ParseBitbucketCloudPullEvent_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
func (c *EventParsing_ParseBitbucketCloudPullEvent_OngoingVerification) GetCapturedArguments() []byte {
body := c.GetAllCapturedArguments()
return body[len(body)-1]
}
func (c *EventParsing_ParseBitbucketCloudPullEvent_OngoingVerification) GetAllCapturedArguments() (_param0 [][]byte) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([][]byte, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.([]byte)
}
}
return
}
func (verifier *VerifierEventParsing) ParseBitbucketCloudCommentEvent(body []byte) *EventParsing_ParseBitbucketCloudCommentEvent_OngoingVerification {
params := []pegomock.Param{body}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseBitbucketCloudCommentEvent", params)
return &EventParsing_ParseBitbucketCloudCommentEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type EventParsing_ParseBitbucketCloudCommentEvent_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
func (c *EventParsing_ParseBitbucketCloudCommentEvent_OngoingVerification) GetCapturedArguments() []byte {
body := c.GetAllCapturedArguments()
return body[len(body)-1]
}
func (c *EventParsing_ParseBitbucketCloudCommentEvent_OngoingVerification) GetAllCapturedArguments() (_param0 [][]byte) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([][]byte, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.([]byte)
}
}
return
}
func (verifier *VerifierEventParsing) GetBitbucketEventType(eventTypeHeader string) *EventParsing_GetBitbucketEventType_OngoingVerification {
params := []pegomock.Param{eventTypeHeader}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetBitbucketEventType", params)
return &EventParsing_GetBitbucketEventType_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type EventParsing_GetBitbucketEventType_OngoingVerification struct {
mock *MockEventParsing
methodInvocations []pegomock.MethodInvocation
}
func (c *EventParsing_GetBitbucketEventType_OngoingVerification) GetCapturedArguments() string {
eventTypeHeader := c.GetAllCapturedArguments()
return eventTypeHeader[len(eventTypeHeader)-1]
}
func (c *EventParsing_GetBitbucketEventType_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]string, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(string)
}
}
return
}

View File

@@ -11,7 +11,7 @@
// limitations under the License.
// Modified hereafter by contributors to runatlantis/atlantis.
//
package vcs
package models
// CommitStatus is the result of executing an Atlantis command for the commit.
// In Github the options are: error, failure, pending, success.

View File

@@ -0,0 +1,19 @@
package models_test
import (
"testing"
"github.com/runatlantis/atlantis/server/events/models"
. "github.com/runatlantis/atlantis/testing"
)
func TestStatus_String(t *testing.T) {
cases := map[models.CommitStatus]string{
models.Pending: "pending",
models.Success: "success",
models.Failed: "failed",
}
for k, v := range cases {
Equals(t, v, k.String())
}
}

View File

@@ -51,6 +51,10 @@ type Repo struct {
VCSHost VCSHost
}
// NewRepo constructs a Repo object. repoFullName is the owner/repo form,
// cloneURL can be with or without .git at the end
// ex. https://github.com/runatlantis/atlantis.git OR
// https://github.com/runatlantis/atlantis
func NewRepo(vcsHostType VCSHostType, repoFullName string, cloneURL string, vcsUser string, vcsToken string) (Repo, error) {
if repoFullName == "" {
return Repo{}, errors.New("repoFullName can't be empty")
@@ -59,6 +63,10 @@ func NewRepo(vcsHostType VCSHostType, repoFullName string, cloneURL string, vcsU
return Repo{}, errors.New("cloneURL can't be empty")
}
if !strings.HasSuffix(cloneURL, ".git") {
cloneURL += ".git"
}
// Ensure the Clone URL is for the same repo to avoid something malicious.
cloneURLParsed, err := url.Parse(cloneURL)
if err != nil {
@@ -103,8 +111,10 @@ func NewRepo(vcsHostType VCSHostType, repoFullName string, cloneURL string, vcsU
type PullRequest struct {
// Num is the pull request number or ID.
Num int
// HeadCommit points to the head of the branch that is being
// pull requested into the base.
// HeadCommit is a sha256 that points to the head of the branch that is being
// pull requested into the base. If the pull request is from BitBucket,
// the string will only be 12 characters long because BitBucket truncates
// its commit IDs.
HeadCommit string
// URL is the url of the pull request.
// ex. "https://github.com/runatlantis/atlantis/pull/1"
@@ -128,6 +138,29 @@ const (
Closed
)
type PullRequestEventType int
const (
OpenedPullEvent PullRequestEventType = iota
UpdatedPullEvent
ClosedPullEvent
OtherPullEvent
)
func (p PullRequestEventType) String() string {
switch p {
case OpenedPullEvent:
return "opened"
case UpdatedPullEvent:
return "updated"
case ClosedPullEvent:
return "closed"
case OtherPullEvent:
return "other"
}
return "<missing String() implementation>"
}
// User is a VCS user.
// During an autoplan, the user will be the Atlantis API user.
type User struct {
@@ -208,6 +241,7 @@ type VCSHostType int
const (
Github VCSHostType = iota
Gitlab
Bitbucket
)
func (h VCSHostType) String() string {
@@ -216,6 +250,8 @@ func (h VCSHostType) String() string {
return "Github"
case Gitlab:
return "Gitlab"
case Bitbucket:
return "Bitbucket"
}
return "<missing String() implementation>"
}

View File

@@ -33,7 +33,7 @@ func TestNewRepo_EmptyCloneURL(t *testing.T) {
func TestNewRepo_InvalidCloneURL(t *testing.T) {
_, err := models.NewRepo(models.Github, "owner/repo", ":", "u", "p")
ErrEquals(t, "invalid clone url: parse :: missing protocol scheme", err)
ErrEquals(t, "invalid clone url: parse :.git: missing protocol scheme", err)
}
func TestNewRepo_CloneURLWrongRepo(t *testing.T) {

View File

@@ -13,7 +13,9 @@
//
package events
import "github.com/runatlantis/atlantis/server/events/vcs"
import (
"github.com/runatlantis/atlantis/server/events/models"
)
// ProjectResult is the result of executing a plan/apply for a project.
type ProjectResult struct {
@@ -30,12 +32,12 @@ type ProjectCommandResult struct {
}
// Status returns the vcs commit status of this project result.
func (p ProjectResult) Status() vcs.CommitStatus {
func (p ProjectResult) Status() models.CommitStatus {
if p.Error != nil {
return vcs.Failed
return models.Failed
}
if p.Failure != "" {
return vcs.Failed
return models.Failed
}
return vcs.Success
return models.Success
}

View File

@@ -24,16 +24,28 @@ const Wildcard = "*"
// RepoWhitelistChecker implements checking if repos are whitelisted to be used with
// this Atlantis.
type RepoWhitelistChecker struct {
// Whitelist is a comma separated list of rules with wildcards '*' allowed.
Whitelist string
rules []string
}
// NewRepoWhitelistChecker constructs a new checker and validates that the
// whitelist isn't malformed.
func NewRepoWhitelistChecker(whitelist string) (*RepoWhitelistChecker, error) {
rules := strings.Split(whitelist, ",")
for _, rule := range rules {
if strings.Contains(rule, "://") {
return nil, fmt.Errorf("whitelist %q contained ://", rule)
}
}
return &RepoWhitelistChecker{
rules: rules,
}, nil
}
// IsWhitelisted returns true if this repo is in our whitelist and false
// otherwise.
func (r *RepoWhitelistChecker) IsWhitelisted(repoFullName string, vcsHostname string) bool {
candidate := fmt.Sprintf("%s/%s", vcsHostname, repoFullName)
rules := strings.Split(r.Whitelist, ",")
for _, rule := range rules {
for _, rule := range r.rules {
if r.matchesRule(rule, candidate) {
return true
}

View File

@@ -151,7 +151,8 @@ func TestRepoWhitelistChecker_IsWhitelisted(t *testing.T) {
for _, c := range cases {
t.Run(c.Description, func(t *testing.T) {
w := events.RepoWhitelistChecker{Whitelist: c.Whitelist}
w, err := events.NewRepoWhitelistChecker(c.Whitelist)
Ok(t, err)
Equals(t, c.Exp, w.IsWhitelisted(c.RepoFullName, c.Hostname))
})
}

View File

@@ -0,0 +1,197 @@
package bitbucket
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"github.com/pkg/errors"
"github.com/runatlantis/atlantis/server/events/models"
)
type Client struct {
HttpClient *http.Client
Username string
Password string
BaseURL string
AtlantisBaseURL string
}
func NewClient(httpClient *http.Client, username string, password string, baseURL string, atlantisBaseUrl string) (*Client, error) {
if httpClient == nil {
httpClient = http.DefaultClient
}
// Remove the trailing '/' from the URL.
parsedURL, err := url.Parse(baseURL)
if err != nil {
return nil, errors.Wrapf(err, "parsing %s", baseURL)
}
urlWithoutPath := fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host)
return &Client{
HttpClient: httpClient,
Username: username,
Password: password,
BaseURL: urlWithoutPath,
AtlantisBaseURL: atlantisBaseUrl,
}, nil
}
func (b *Client) prepRequest(method string, path string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, path, body)
if err != nil {
return nil, err
}
req.SetBasicAuth(b.Username, b.Password)
return req, nil
}
// GetModifiedFiles returns the names of files that were modified in the merge request.
// The names include the path to the file from the repo root, ex. parent/child/file.txt.
func (b *Client) GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error) {
path := fmt.Sprintf("%s/2.0/repositories/%s/pullrequests/%d/diffstat", b.BaseURL, repo.FullName, pull.Num)
// todo: remove duplication
req, err := b.prepRequest("GET", path, nil)
if err != nil {
return nil, err
}
resp, err := b.HttpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
// todo: pagination
var diffStat DiffStat
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "reading body")
}
if err := json.Unmarshal(body, &diffStat); err != nil {
return nil, err
}
hash := make(map[string]bool)
var unique []string
for _, v := range diffStat.Values {
var paths []string
if v.Old != nil {
paths = append(paths, *v.Old.Path)
}
if v.New != nil {
paths = append(paths, *v.New.Path)
}
for _, path := range paths {
if !hash[path] {
unique = append(unique, path)
hash[path] = true
}
}
}
return unique, nil
}
// CreateComment creates a comment on the merge request.
func (b *Client) CreateComment(repo models.Repo, pullNum int, comment string) error {
bodyBytes, err := json.Marshal(map[string]string{"content": comment})
if err != nil {
return errors.Wrap(err, "json encoding")
}
path := fmt.Sprintf("%s/1.0/repositories/%s/pullrequests/%d/comments", b.BaseURL, repo.FullName, pullNum)
req, err := b.prepRequest("POST", path, bytes.NewBuffer(bodyBytes))
req.Header.Add("Content-Type", "application/json")
if err != nil {
return errors.Wrap(err, "constructing request")
}
resp, err := b.HttpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("unexpected status code: %d for url: %s, body: %s", resp.StatusCode, path, string(body))
}
return nil
}
// PullIsApproved returns true if the merge request was approved.
func (b *Client) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
path := fmt.Sprintf("%s/2.0/repositories/%s/pullrequests/%d", b.BaseURL, repo.FullName, pull.Num)
req, err := b.prepRequest("GET", path, nil)
if err != nil {
return false, errors.Wrap(err, "constructing request")
}
resp, err := b.HttpClient.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return false, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, errors.Wrap(err, "reading response body")
}
parsedPull, err := ParseBitBucketPullRequest(body)
if err != nil {
return false, errors.Wrap(err, "parsing response")
}
for _, participant := range parsedPull.Participants {
if *participant.Approved == true {
return true, nil
}
}
return false, nil
}
// UpdateStatus updates the ~ status of a commit.
func (b *Client) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
bbState := "FAILED"
switch state {
case models.Pending:
bbState = "INPROGRESS"
case models.Success:
bbState = "SUCCESSFUL"
case models.Failed:
bbState = "FAILED"
}
bodyBytes, err := json.Marshal(map[string]string{
"key": "atlantis",
"url": b.AtlantisBaseURL,
"state": bbState,
"description": description,
})
path := fmt.Sprintf("%s/2.0/repositories/%s/commit/%s/statuses/build", b.BaseURL, repo.FullName, pull.HeadCommit)
if err != nil {
return errors.Wrap(err, "json encoding")
}
req, err := b.prepRequest("POST", path, bytes.NewBuffer(bodyBytes))
req.Header.Add("Content-Type", "application/json")
if err != nil {
return errors.Wrap(err, "constructing request")
}
resp, err := b.HttpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check for both 201 and 200 because on a new status we'll get a 201 but
// on a status update we'll get a 200.
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
return nil
}

View File

@@ -0,0 +1 @@
package bitbucket

View File

@@ -0,0 +1,82 @@
package bitbucket
import (
"encoding/json"
)
type CommentEvent struct {
CommonEventData
Comment *Comment `json:"comment,omitempty" validate:"required"`
}
type PullRequestEvent struct {
CommonEventData
}
type CommonEventData struct {
Actor *Actor `json:"actor,omitempty" validate:"required"`
Repository *Repository `json:"repository,omitempty" validate:"required"`
PullRequest *PullRequest `json:"pullrequest,omitempty" validate:"required"`
}
type DiffStat struct {
Values []DiffStatValue `json:"values,omitempty" validate:"required"`
}
type DiffStatValue struct {
// Old is the old file, this can be null.
Old *DiffStatFile `json:"old,omitempty"`
// New is the new file, this can be null.
New *DiffStatFile `json:"new,omitempty"`
}
type DiffStatFile struct {
Path *string `json:"path,omitempty" validate:"required"`
}
type Actor struct {
Username *string `json:"username,omitempty" validate:"required"`
}
type Repository struct {
FullName *string `json:"full_name,omitempty" validate:"required"`
Links Links `json:"links,omitempty" validate:"required"`
}
type PullRequest struct {
ID *int `json:"id,omitempty" validate:"required"`
Source *Source `json:"source,omitempty" validate:"required"`
Participants []Participant `json:"participants,omitempty" validate:"required"`
Links *Links `json:"links,omitempty" validate:"required"`
State *string `json:"state,omitempty" validate:"required"`
}
type Links struct {
HTML *Link `json:"html,omitempty" validate:"required"`
}
type Link struct {
HREF *string `json:"href,omitempty" validate:"required"`
}
type Participant struct {
Approved *bool `json:"approved,omitempty" validate:"required"`
}
type Source struct {
Repository *Repository `json:"repository,omitempty" validate:"required"`
Commit *Commit `json:"commit,omitempty" validate:"required"`
Branch *Branch `json:"branch,omitempty" validate:"required"`
}
type Branch struct {
Name *string `json:"name,omitempty" validate:"required"`
}
type Commit struct {
Hash *string `json:"hash,omitempty" validate:"required"`
}
type Comment struct {
Content *CommentContent `json:"content,omitempty" validate:"required"`
}
type CommentContent struct {
Raw *string `json:"raw,omitempty" validate:"required"`
}
func ParseBitBucketPullRequest(body []byte) (PullRequest, error) {
var pull PullRequest
if err := json.Unmarshal(body, &pull); err != nil {
return PullRequest{}, err
}
return pull, nil
}

View File

@@ -24,5 +24,5 @@ type Client interface {
GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error)
CreateComment(repo models.Repo, pullNum int, comment string) error
PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error)
UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error
UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error
}

View File

@@ -21,6 +21,7 @@ var PullEvent = github.PullRequestEvent{
},
Repo: &Repo,
PullRequest: &Pull,
Action: github.String("opened"),
}
var Pull = github.PullRequest{

View File

@@ -123,15 +123,15 @@ func (g *GithubClient) GetPullRequest(repo models.Repo, num int) (*github.PullRe
// UpdateStatus updates the status badge on the pull request.
// See https://github.com/blog/1227-commit-status-api.
func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
func (g *GithubClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
const statusContext = "Atlantis"
ghState := "error"
switch state {
case Pending:
case models.Pending:
ghState = "pending"
case Success:
case models.Success:
ghState = "success"
case Failed:
case models.Failed:
ghState = "failure"
}
status := &github.RepoStatus{

View File

@@ -78,19 +78,19 @@ func TestGithubClient_GetModifiedFiles(t *testing.T) {
func TestGithubClient_UpdateStatus(t *testing.T) {
cases := []struct {
status vcs.CommitStatus
status models.CommitStatus
expState string
}{
{
vcs.Pending,
models.Pending,
"pending",
},
{
vcs.Success,
models.Success,
"success",
},
{
vcs.Failed,
models.Failed,
"failure",
},
}

View File

@@ -79,16 +79,16 @@ func (g *GitlabClient) PullIsApproved(repo models.Repo, pull models.PullRequest)
}
// UpdateStatus updates the build status of a commit.
func (g *GitlabClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
func (g *GitlabClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
const statusContext = "Atlantis"
gitlabState := gitlab.Failed
switch state {
case Pending:
case models.Pending:
gitlabState = gitlab.Pending
case Failed:
case models.Failed:
gitlabState = gitlab.Failed
case Success:
case models.Success:
gitlabState = gitlab.Success
}
_, _, err := g.Client.Commits.SetCommitStatus(repo.FullName, pull.HeadCommit, &gitlab.SetCommitStatusOptions{

View File

@@ -4,17 +4,17 @@ import (
"reflect"
"github.com/petergtz/pegomock"
vcs "github.com/runatlantis/atlantis/server/events/vcs"
models "github.com/runatlantis/atlantis/server/events/models"
)
func AnyVcsCommitStatus() vcs.CommitStatus {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(vcs.CommitStatus))(nil)).Elem()))
var nullValue vcs.CommitStatus
func AnyVcsCommitStatus() models.CommitStatus {
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.CommitStatus))(nil)).Elem()))
var nullValue models.CommitStatus
return nullValue
}
func EqVcsCommitStatus(value vcs.CommitStatus) vcs.CommitStatus {
func EqVcsCommitStatus(value models.CommitStatus) models.CommitStatus {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
var nullValue vcs.CommitStatus
var nullValue models.CommitStatus
return nullValue
}

View File

@@ -8,7 +8,6 @@ import (
pegomock "github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
vcs "github.com/runatlantis/atlantis/server/events/vcs"
)
type MockClient struct {
@@ -63,7 +62,7 @@ func (mock *MockClient) PullIsApproved(repo models.Repo, pull models.PullRequest
return ret0, ret1
}
func (mock *MockClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) error {
func (mock *MockClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
params := []pegomock.Param{repo, pull, state, description}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateStatus", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
@@ -190,7 +189,7 @@ func (c *Client_PullIsApproved_OngoingVerification) GetAllCapturedArguments() (_
return
}
func (verifier *VerifierClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) *Client_UpdateStatus_OngoingVerification {
func (verifier *VerifierClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) *Client_UpdateStatus_OngoingVerification {
params := []pegomock.Param{repo, pull, state, description}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateStatus", params)
return &Client_UpdateStatus_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
@@ -201,12 +200,12 @@ type Client_UpdateStatus_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
func (c *Client_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, string) {
func (c *Client_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, string) {
repo, pull, state, description := c.GetAllCapturedArguments()
return repo[len(repo)-1], pull[len(pull)-1], state[len(state)-1], description[len(description)-1]
}
func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []string) {
func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -217,9 +216,9 @@ func (c *Client_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_pa
for u, param := range params[1] {
_param1[u] = param.(models.PullRequest)
}
_param2 = make([]vcs.CommitStatus, len(params[2]))
_param2 = make([]models.CommitStatus, len(params[2]))
for u, param := range params[2] {
_param2[u] = param.(vcs.CommitStatus)
_param2[u] = param.(models.CommitStatus)
}
_param3 = make([]string, len(params[3]))
for u, param := range params[3] {

View File

@@ -8,7 +8,6 @@ import (
pegomock "github.com/petergtz/pegomock"
models "github.com/runatlantis/atlantis/server/events/models"
vcs "github.com/runatlantis/atlantis/server/events/vcs"
)
type MockClientProxy struct {
@@ -63,7 +62,7 @@ func (mock *MockClientProxy) PullIsApproved(repo models.Repo, pull models.PullRe
return ret0, ret1
}
func (mock *MockClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) error {
func (mock *MockClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
params := []pegomock.Param{repo, pull, state, description}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateStatus", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
@@ -190,7 +189,7 @@ func (c *ClientProxy_PullIsApproved_OngoingVerification) GetAllCapturedArguments
return
}
func (verifier *VerifierClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state vcs.CommitStatus, description string) *ClientProxy_UpdateStatus_OngoingVerification {
func (verifier *VerifierClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) *ClientProxy_UpdateStatus_OngoingVerification {
params := []pegomock.Param{repo, pull, state, description}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateStatus", params)
return &ClientProxy_UpdateStatus_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
@@ -201,12 +200,12 @@ type ClientProxy_UpdateStatus_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
func (c *ClientProxy_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, string) {
func (c *ClientProxy_UpdateStatus_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, models.CommitStatus, string) {
repo, pull, state, description := c.GetAllCapturedArguments()
return repo[len(repo)-1], pull[len(pull)-1], state[len(state)-1], description[len(description)-1]
}
func (c *ClientProxy_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []string) {
func (c *ClientProxy_UpdateStatus_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []models.CommitStatus, _param3 []string) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -217,9 +216,9 @@ func (c *ClientProxy_UpdateStatus_OngoingVerification) GetAllCapturedArguments()
for u, param := range params[1] {
_param1[u] = param.(models.PullRequest)
}
_param2 = make([]vcs.CommitStatus, len(params[2]))
_param2 = make([]models.CommitStatus, len(params[2]))
for u, param := range params[2] {
_param2[u] = param.(vcs.CommitStatus)
_param2[u] = param.(models.CommitStatus)
}
_param3 = make([]string, len(params[3]))
for u, param := range params[3] {

View File

@@ -35,7 +35,7 @@ func (a *NotConfiguredVCSClient) CreateComment(repo models.Repo, pullNum int, co
func (a *NotConfiguredVCSClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error) {
return false, a.err()
}
func (a *NotConfiguredVCSClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
func (a *NotConfiguredVCSClient) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
return a.err()
}
func (a *NotConfiguredVCSClient) err() error {

View File

@@ -26,26 +26,31 @@ type ClientProxy interface {
GetModifiedFiles(repo models.Repo, pull models.PullRequest) ([]string, error)
CreateComment(repo models.Repo, pullNum int, comment string) error
PullIsApproved(repo models.Repo, pull models.PullRequest) (bool, error)
UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error
UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error
}
// DefaultClientProxy proxies calls to the correct VCS client depending on which
// VCS host is required.
type DefaultClientProxy struct {
GithubClient Client
GitlabClient Client
GithubClient Client
GitlabClient Client
BitbucketClient Client
}
func NewDefaultClientProxy(githubClient Client, gitlabClient Client) *DefaultClientProxy {
func NewDefaultClientProxy(githubClient Client, gitlabClient Client, bitbucketClient Client) *DefaultClientProxy {
if githubClient == nil {
githubClient = &NotConfiguredVCSClient{}
}
if gitlabClient == nil {
gitlabClient = &NotConfiguredVCSClient{}
}
if bitbucketClient == nil {
bitbucketClient = &NotConfiguredVCSClient{}
}
return &DefaultClientProxy{
GitlabClient: gitlabClient,
GithubClient: githubClient,
GitlabClient: gitlabClient,
GithubClient: githubClient,
BitbucketClient: bitbucketClient,
}
}
@@ -57,6 +62,8 @@ func (d *DefaultClientProxy) GetModifiedFiles(repo models.Repo, pull models.Pull
return d.GithubClient.GetModifiedFiles(repo, pull)
case models.Gitlab:
return d.GitlabClient.GetModifiedFiles(repo, pull)
case models.Bitbucket:
return d.BitbucketClient.GetModifiedFiles(repo, pull)
}
return nil, invalidVCSErr
}
@@ -67,6 +74,8 @@ func (d *DefaultClientProxy) CreateComment(repo models.Repo, pullNum int, commen
return d.GithubClient.CreateComment(repo, pullNum, comment)
case models.Gitlab:
return d.GitlabClient.CreateComment(repo, pullNum, comment)
case models.Bitbucket:
return d.BitbucketClient.CreateComment(repo, pullNum, comment)
}
return invalidVCSErr
}
@@ -77,16 +86,20 @@ func (d *DefaultClientProxy) PullIsApproved(repo models.Repo, pull models.PullRe
return d.GithubClient.PullIsApproved(repo, pull)
case models.Gitlab:
return d.GitlabClient.PullIsApproved(repo, pull)
case models.Bitbucket:
return d.BitbucketClient.PullIsApproved(repo, pull)
}
return false, invalidVCSErr
}
func (d *DefaultClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state CommitStatus, description string) error {
func (d *DefaultClientProxy) UpdateStatus(repo models.Repo, pull models.PullRequest, state models.CommitStatus, description string) error {
switch repo.VCSHost.Type {
case models.Github:
return d.GithubClient.UpdateStatus(repo, pull, state, description)
case models.Gitlab:
return d.GitlabClient.UpdateStatus(repo, pull, state, description)
case models.Bitbucket:
return d.BitbucketClient.UpdateStatus(repo, pull, state, description)
}
return invalidVCSErr
}

View File

@@ -1,19 +0,0 @@
package vcs_test
import (
"testing"
"github.com/runatlantis/atlantis/server/events/vcs"
. "github.com/runatlantis/atlantis/testing"
)
func TestStatus_String(t *testing.T) {
cases := map[vcs.CommitStatus]string{
vcs.Pending: "pending",
vcs.Success: "success",
vcs.Failed: "failed",
}
for k, v := range cases {
Equals(t, v, k.String())
}
}

View File

@@ -74,7 +74,7 @@ func (w *FileWorkspace) Clone(
return w.forceClone(log, cloneDir, headRepo, p)
}
currCommit := strings.Trim(string(output), "\n")
if currCommit == p.HeadCommit {
if strings.HasPrefix(currCommit, p.HeadCommit) {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, nil
}

View File

@@ -15,6 +15,7 @@ package server
import (
"fmt"
"io/ioutil"
"net/http"
"github.com/google/go-github/github"
@@ -27,6 +28,8 @@ import (
const githubHeader = "X-Github-Event"
const gitlabHeader = "X-Gitlab-Event"
const bitbucketEventTypeHeader = "X-Event-Key"
const bitbucketRequestIDHeader = "X-Request-UUID"
// EventsController handles all webhook requests which signify 'events' in the
// VCS host, ex. GitHub.
@@ -72,6 +75,14 @@ func (e *EventsController) Post(w http.ResponseWriter, r *http.Request) {
e.Logger.Debug("handling GitLab post")
e.handleGitlabPost(w, r)
return
} else if r.Header.Get(bitbucketEventTypeHeader) != "" {
if !e.supportsHost(models.Bitbucket) {
e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request since not configured to support Bitbucket")
return
}
e.Logger.Debug("handling Bitbucket post")
e.handleBitbucketPost(w, r)
return
}
e.respond(w, logging.Debug, http.StatusBadRequest, "Ignoring request")
}
@@ -99,6 +110,29 @@ func (e *EventsController) handleGithubPost(w http.ResponseWriter, r *http.Reque
}
}
func (e *EventsController) handleBitbucketPost(w http.ResponseWriter, r *http.Request) {
eventType := r.Header.Get(bitbucketEventTypeHeader)
reqID := r.Header.Get(bitbucketRequestIDHeader)
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Unable to read body: %s %s=%s", err, bitbucketRequestIDHeader, reqID)
return
}
switch eventType {
case "pullrequest:created", "pullrequest:updated", "pullrequest:fulfilled", "pullrequest:rejected":
e.Logger.Debug("handling as pull request state changed event")
e.HandleBitbucketPullRequestEvent(w, eventType, body, reqID)
return
case "pullrequest:comment_created":
e.Logger.Debug("handling as comment created event")
e.HandleBitbucketCommentEvent(w, body, reqID)
return
default:
e.respond(w, logging.Debug, http.StatusOK, "Ignoring unsupported event type %s %s=%s", eventType, bitbucketRequestIDHeader, 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) {
@@ -115,45 +149,50 @@ func (e *EventsController) HandleGithubCommentEvent(w http.ResponseWriter, event
// We pass in nil for maybeHeadRepo because the head repo data isn't
// available in the GithubIssueComment event.
e.handleCommentEvent(w, baseRepo, nil, user, pullNum, event.Comment.GetBody(), models.Github)
e.handleCommentEvent(w, baseRepo, nil, nil, user, pullNum, event.Comment.GetBody(), models.Github)
}
// HandleBitbucketCommentEvent handles comment events from Bitbucket.
func (e *EventsController) HandleBitbucketCommentEvent(w http.ResponseWriter, body []byte, reqID string) {
pull, baseRepo, headRepo, user, comment, err := e.Parser.ParseBitbucketCloudCommentEvent(body)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing pull data: %s %s=%s", err, bitbucketRequestIDHeader, reqID)
return
}
e.handleCommentEvent(w, baseRepo, &headRepo, &pull, user, pull.Num, comment, models.Bitbucket)
}
func (e *EventsController) HandleBitbucketPullRequestEvent(w http.ResponseWriter, eventType string, body []byte, reqID string) {
pull, baseRepo, headRepo, user, err := e.Parser.ParseBitbucketCloudPullEvent(body)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing pull data: %s %s=%s", err, bitbucketRequestIDHeader, reqID)
return
}
pullEventType := e.Parser.GetBitbucketEventType(eventType)
e.Logger.Info("identified event as type %q", pullEventType.String())
e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, pullEventType)
}
// HandleGithubPullRequestEvent 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) HandleGithubPullRequestEvent(w http.ResponseWriter, pullEvent *github.PullRequestEvent, githubReqID string) {
pull, baseRepo, headRepo, user, err := e.Parser.ParseGithubPullEvent(pullEvent)
pull, pullEventType, baseRepo, headRepo, user, err := e.Parser.ParseGithubPullEvent(pullEvent)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing pull data: %s %s", err, githubReqID)
return
}
var eventType string
switch pullEvent.GetAction() {
case "opened":
eventType = OpenPullEvent
case "synchronize":
eventType = UpdatedPullEvent
case "closed":
eventType = ClosedPullEvent
default:
eventType = OtherPullEvent
}
e.Logger.Info("identified event as type %q", eventType)
e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, eventType)
e.Logger.Info("identified event as type %q", pullEventType.String())
e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, pullEventType)
}
const OpenPullEvent = "opened"
const UpdatedPullEvent = "updated"
const ClosedPullEvent = "closed"
const OtherPullEvent = "other"
func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User, eventType string) {
func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User, eventType models.PullRequestEventType) {
if !e.RepoWhitelistChecker.IsWhitelisted(baseRepo.FullName, baseRepo.VCSHost.Hostname) {
// If the repo isn't whitelisted and we receive an opened pull request
// event we comment back on the pull request that the repo isn't
// whitelisted. This is because the user might be expecting Atlantis to
// autoplan. For other events, we just ignore them.
if eventType == OpenPullEvent {
if eventType == models.OpenedPullEvent {
e.commentNotWhitelisted(baseRepo, pull.Num)
}
e.respond(w, logging.Debug, http.StatusForbidden, "Ignoring pull request event from non-whitelisted repo")
@@ -161,7 +200,7 @@ func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRep
}
switch eventType {
case OpenPullEvent, UpdatedPullEvent:
case models.OpenedPullEvent, models.UpdatedPullEvent:
// If the pull request was opened or updated, we will try to autoplan.
// Respond with success and then actually execute the command asynchronously.
@@ -177,7 +216,7 @@ func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRep
e.CommandRunner.RunAutoplanCommand(baseRepo, headRepo, pull, user)
}
return
case ClosedPullEvent:
case models.ClosedPullEvent:
// If the pull request was closed, we delete locks.
if err := e.PullCleaner.CleanUpPull(baseRepo, pull); err != nil {
e.respond(w, logging.Error, http.StatusInternalServerError, "Error cleaning pull request: %s", err)
@@ -186,7 +225,7 @@ func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRep
e.Logger.Info("deleted locks and workspace for repo %s, pull %d", baseRepo.FullName, pull.Num)
fmt.Fprintln(w, "Pull request cleaned successfully")
return
case OtherPullEvent:
case models.OtherPullEvent:
// Else we ignore the event.
e.respond(w, logging.Debug, http.StatusOK, "Ignoring non-actionable pull request event")
return
@@ -217,15 +256,16 @@ func (e *EventsController) handleGitlabPost(w http.ResponseWriter, r *http.Reque
// HandleGitlabCommentEvent handles comment events from GitLab where Atlantis
// commands can come from. It's exported to make testing easier.
func (e *EventsController) HandleGitlabCommentEvent(w http.ResponseWriter, event gitlab.MergeCommentEvent) {
// todo: can gitlab return the pull request here too?
baseRepo, headRepo, user, err := e.Parser.ParseGitlabMergeCommentEvent(event)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing webhook: %s", err)
return
}
e.handleCommentEvent(w, baseRepo, &headRepo, user, event.MergeRequest.IID, event.ObjectAttributes.Note, models.Gitlab)
e.handleCommentEvent(w, baseRepo, &headRepo, nil, user, event.MergeRequest.IID, event.ObjectAttributes.Note, models.Gitlab)
}
func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, comment string, vcsHost models.VCSHostType) {
func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo models.Repo, maybeHeadRepo *models.Repo, maybePull *models.PullRequest, user models.User, pullNum int, comment string, vcsHost models.VCSHostType) {
parseResult := e.CommentParser.Parse(comment, vcsHost)
if parseResult.Ignore {
truncated := comment
@@ -264,10 +304,10 @@ func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo mo
// Respond with success and then actually execute the command asynchronously.
// We use a goroutine so that this function returns and the connection is
// closed.
go e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, user, pullNum, parseResult.Command)
go e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, maybePull, user, pullNum, parseResult.Command)
} else {
// When testing we want to wait for everything to complete.
e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, user, pullNum, parseResult.Command)
e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, maybePull, user, pullNum, parseResult.Command)
}
}
@@ -275,24 +315,13 @@ func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo mo
// request if the event is a merge request closed event. It's exported to make
// testing easier.
func (e *EventsController) HandleGitlabMergeRequestEvent(w http.ResponseWriter, event gitlab.MergeEvent) {
pull, baseRepo, headRepo, user, err := e.Parser.ParseGitlabMergeEvent(event)
pull, pullEventType, baseRepo, headRepo, user, err := e.Parser.ParseGitlabMergeEvent(event)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing webhook: %s", err)
return
}
var eventType string
switch event.ObjectAttributes.Action {
case "open":
eventType = OpenPullEvent
case "update":
eventType = UpdatedPullEvent
case "merge", "close":
eventType = ClosedPullEvent
default:
eventType = OtherPullEvent
}
e.Logger.Info("identified event as type %q", eventType)
e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, eventType)
e.Logger.Info("identified event as type %q", pullEventType.String())
e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, pullEventType)
}
// supportsHost returns true if h is in e.SupportedVCSHosts and false otherwise.

View File

@@ -280,6 +280,9 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy,
},
}
repoWhitelistChecker, err := events.NewRepoWhitelistChecker("*")
Ok(t, err)
ctrl := server.EventsController{
TestingMode: true,
CommandRunner: commandRunner,
@@ -295,11 +298,9 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy,
GithubRequestValidator: &server.DefaultGithubRequestValidator{},
GitlabRequestParserValidator: &server.DefaultGitlabRequestParserValidator{},
GitlabWebHookSecret: nil,
RepoWhitelistChecker: &events.RepoWhitelistChecker{
Whitelist: "*",
},
SupportedVCSHosts: []models.VCSHostType{models.Gitlab, models.Github},
VCSClient: e2eVCSClient,
RepoWhitelistChecker: repoWhitelistChecker,
SupportedVCSHosts: []models.VCSHostType{models.Gitlab, models.Github, models.Bitbucket},
VCSClient: e2eVCSClient,
}
return ctrl, e2eVCSClient, e2eGithubGetter, workingDir
}

View File

@@ -268,7 +268,7 @@ func TestPost_GitlabCommentSuccess(t *testing.T) {
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Processing...")
cr.VerifyWasCalledOnce().RunCommentCommand(models.Repo{}, &models.Repo{}, models.User{}, 0, nil)
cr.VerifyWasCalledOnce().RunCommentCommand(models.Repo{}, &models.Repo{}, nil, models.User{}, 0, nil)
}
func TestPost_GithubCommentSuccess(t *testing.T) {
@@ -287,7 +287,7 @@ func TestPost_GithubCommentSuccess(t *testing.T) {
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Processing...")
cr.VerifyWasCalledOnce().RunCommentCommand(baseRepo, nil, user, 1, &cmd)
cr.VerifyWasCalledOnce().RunCommentCommand(baseRepo, nil, nil, user, 1, &cmd)
}
func TestPost_GithubPullRequestInvalid(t *testing.T) {
@@ -298,7 +298,7 @@ func TestPost_GithubPullRequestInvalid(t *testing.T) {
event := `{"action": "closed"}`
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("err"))
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(models.PullRequest{}, models.OpenedPullEvent, models.Repo{}, models.Repo{}, models.User{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusBadRequest, "Error parsing pull data: err")
@@ -312,7 +312,7 @@ func TestPost_GitlabMergeRequestInvalid(t *testing.T) {
When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlabMergeEvent, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, errors.New("err"))
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, models.OpenedPullEvent, repo, repo, models.User{}, errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusBadRequest, "Error parsing webhook: err")
@@ -321,7 +321,9 @@ func TestPost_GitlabMergeRequestInvalid(t *testing.T) {
func TestPost_GithubPullRequestNotWhitelisted(t *testing.T) {
t.Log("when the event is a github pull request to a non-whitelisted repo we return a 400")
e, v, _, _, _, _, _, _ := setup(t)
e.RepoWhitelistChecker = &events.RepoWhitelistChecker{Whitelist: "github.com/nevermatch"}
var err error
e.RepoWhitelistChecker, err = events.NewRepoWhitelistChecker("github.com/nevermatch")
Ok(t, err)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
req.Header.Set(githubHeader, "pull_request")
@@ -338,11 +340,13 @@ func TestPost_GitlabMergeRequestNotWhitelisted(t *testing.T) {
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
req.Header.Set(gitlabHeader, "value")
e.RepoWhitelistChecker = &events.RepoWhitelistChecker{Whitelist: "github.com/nevermatch"}
var err error
e.RepoWhitelistChecker, err = events.NewRepoWhitelistChecker("github.com/nevermatch")
Ok(t, err)
When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlabMergeEvent, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, models.OpenedPullEvent, repo, repo, models.User{}, nil)
w := httptest.NewRecorder()
e.Post(w, req)
@@ -350,6 +354,7 @@ func TestPost_GitlabMergeRequestNotWhitelisted(t *testing.T) {
}
func TestPost_GithubPullRequestUnsupportedAction(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
e, v, _, _, _, _, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
req.Header.Set(githubHeader, "pull_request")
@@ -357,11 +362,13 @@ func TestPost_GithubPullRequestUnsupportedAction(t *testing.T) {
event := `{"action": "unsupported"}`
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
w := httptest.NewRecorder()
e.Parser = &events.EventParser{}
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Ignoring non-actionable pull request event")
}
func TestPost_GitlabMergeRequestUnsupportedAction(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
t.Log("when the event is a gitlab merge request to a non-whitelisted repo we return a 400")
e, _, gl, p, _, _, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
@@ -378,6 +385,7 @@ func TestPost_GitlabMergeRequestUnsupportedAction(t *testing.T) {
}
func TestPost_GithubPullRequestClosedErrCleaningPull(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
t.Log("when the event is a closed pull request and we have an error calling CleanUpPull we return a 503")
RegisterMockTestingT(t)
e, v, _, p, _, c, _, _ := setup(t)
@@ -388,7 +396,7 @@ func TestPost_GithubPullRequestClosedErrCleaningPull(t *testing.T) {
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{State: models.Closed}
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, repo, repo, models.User{}, nil)
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, models.OpenedPullEvent, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pull)).ThenReturn(errors.New("cleanup err"))
w := httptest.NewRecorder()
e.Post(w, req)
@@ -396,6 +404,7 @@ func TestPost_GithubPullRequestClosedErrCleaningPull(t *testing.T) {
}
func TestPost_GitlabMergeRequestClosedErrCleaningPull(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
t.Log("when the event is a closed gitlab merge request and an error occurs calling CleanUpPull we return a 500")
e, _, gl, p, _, c, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
@@ -404,7 +413,7 @@ func TestPost_GitlabMergeRequestClosedErrCleaningPull(t *testing.T) {
When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlabMergeEvent, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, models.OpenedPullEvent, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pullRequest)).ThenReturn(errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, req)
@@ -412,6 +421,7 @@ func TestPost_GitlabMergeRequestClosedErrCleaningPull(t *testing.T) {
}
func TestPost_GithubClosedPullRequestSuccess(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
t.Log("when the event is a pull request and everything works we return a 200")
e, v, _, p, _, c, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
@@ -421,7 +431,7 @@ func TestPost_GithubClosedPullRequestSuccess(t *testing.T) {
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{State: models.Closed}
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, repo, repo, models.User{}, nil)
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, models.OpenedPullEvent, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pull)).ThenReturn(nil)
w := httptest.NewRecorder()
e.Post(w, req)
@@ -429,6 +439,7 @@ func TestPost_GithubClosedPullRequestSuccess(t *testing.T) {
}
func TestPost_GitlabMergeRequestSuccess(t *testing.T) {
t.Skip("relies too much on mocks, should use real event parser")
t.Log("when the event is a gitlab merge request and the cleanup works we return a 200")
e, _, gl, p, _, _, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
@@ -436,7 +447,7 @@ func TestPost_GitlabMergeRequestSuccess(t *testing.T) {
When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlabMergeEvent, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, models.OpenedPullEvent, repo, repo, models.User{}, nil)
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Pull request cleaned successfully")
@@ -481,14 +492,14 @@ func TestPost_PullOpenedOrUpdated(t *testing.T) {
When(gl.ParseAndValidate(req, secret)).ThenReturn(gitlabMergeEvent, nil)
repo := models.Repo{}
pullRequest := models.PullRequest{State: models.Closed}
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, models.OpenedPullEvent, repo, repo, models.User{}, nil)
case models.Github:
req.Header.Set(githubHeader, "pull_request")
event := fmt.Sprintf(`{"action": "%s"}`, c.Action)
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{State: models.Closed}
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, repo, repo, models.User{}, nil)
When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, models.OpenedPullEvent, repo, repo, models.User{}, nil)
}
w := httptest.NewRecorder()
e.Post(w, req)
@@ -507,6 +518,8 @@ func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValid
cr := emocks.NewMockCommandRunner()
c := emocks.NewMockPullCleaner()
vcsmock := vcsmocks.NewMockClientProxy()
repoWhitelistChecker, err := events.NewRepoWhitelistChecker("*")
Ok(t, err)
e := server.EventsController{
TestingMode: true,
Logger: logging.NewNoopLogger(),
@@ -519,10 +532,8 @@ func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValid
SupportedVCSHosts: []models.VCSHostType{models.Github, models.Gitlab},
GitlabWebHookSecret: secret,
GitlabRequestParserValidator: gl,
RepoWhitelistChecker: &events.RepoWhitelistChecker{
Whitelist: "*",
},
VCSClient: vcsmock,
RepoWhitelistChecker: repoWhitelistChecker,
VCSClient: vcsmock,
}
return e, v, gl, p, cr, c, vcsmock, cp
}

View File

@@ -40,6 +40,7 @@ import (
"github.com/runatlantis/atlantis/server/events/runtime"
"github.com/runatlantis/atlantis/server/events/terraform"
"github.com/runatlantis/atlantis/server/events/vcs"
"github.com/runatlantis/atlantis/server/events/vcs/bitbucket"
"github.com/runatlantis/atlantis/server/events/webhooks"
"github.com/runatlantis/atlantis/server/events/yaml"
"github.com/runatlantis/atlantis/server/logging"
@@ -83,6 +84,9 @@ type UserConfig struct {
AllowForkPRs bool `mapstructure:"allow-fork-prs"`
AllowRepoConfig bool `mapstructure:"allow-repo-config"`
AtlantisURL string `mapstructure:"atlantis-url"`
BitbucketHostname string `mapstructure:"bitbucket-hostname"`
BitbucketToken string `mapstructure:"bitbucket-token"`
BitbucketUser string `mapstructure:"bitbucket-user"`
DataDir string `mapstructure:"data-dir"`
GithubHostname string `mapstructure:"gh-hostname"`
GithubToken string `mapstructure:"gh-token"`
@@ -133,6 +137,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
var supportedVCSHosts []models.VCSHostType
var githubClient *vcs.GithubClient
var gitlabClient *vcs.GitlabClient
var bitbucketClient *bitbucket.Client
if userConfig.GithubUser != "" {
supportedVCSHosts = append(supportedVCSHosts, models.Github)
var err error
@@ -162,6 +167,21 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
}
}
}
if userConfig.BitbucketUser != "" {
supportedVCSHosts = append(supportedVCSHosts, models.Bitbucket)
var err error
bitbucketClient, err = bitbucket.NewClient(
http.DefaultClient,
userConfig.BitbucketUser,
userConfig.BitbucketToken,
// todo: don't hardcode when we allow for bitbucket server
"https://api.bitbucket.org/",
userConfig.AtlantisURL)
if err != nil {
return nil, errors.Wrapf(err, "setting up Bitbucket client")
}
}
var webhooksConfig []webhooks.Config
for _, c := range userConfig.Webhooks {
config := webhooks.Config{
@@ -176,7 +196,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
if err != nil {
return nil, errors.Wrap(err, "initializing webhooks")
}
vcsClient := vcs.NewDefaultClientProxy(githubClient, gitlabClient)
vcsClient := vcs.NewDefaultClientProxy(githubClient, gitlabClient, bitbucketClient)
commitStatusUpdater := &events.DefaultCommitStatusUpdater{Client: vcsClient}
terraformClient, err := terraform.NewClient(userConfig.DataDir)
// The flag.Lookup call is to detect if we're running in a unit test. If we
@@ -216,6 +236,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
GithubToken: userConfig.GithubToken,
GitlabUser: userConfig.GitlabUser,
GitlabToken: userConfig.GitlabToken,
// todo: fill in properly
BitbucketCloudUser: "lkysow",
BitbucketCloudToken: os.Getenv("BITBUCKET_TOKEN"),
}
commentParser := &events.CommentParser{
GithubUser: userConfig.GithubUser,
@@ -267,8 +290,9 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
RequireApprovalOverride: userConfig.RequireApproval,
},
}
repoWhitelist := &events.RepoWhitelistChecker{
Whitelist: userConfig.RepoWhitelist,
repoWhitelist, err := events.NewRepoWhitelistChecker(userConfig.RepoWhitelist)
if err != nil {
return nil, err
}
locksController := &LocksController{
AtlantisVersion: config.AtlantisVersion,

View File

@@ -118,7 +118,8 @@ func TestHealthz(t *testing.T) {
func responseContains(t *testing.T, r *httptest.ResponseRecorder, status int, bodySubstr string) {
t.Helper()
Equals(t, status, r.Result().StatusCode)
body, _ := ioutil.ReadAll(r.Result().Body)
body, err := ioutil.ReadAll(r.Result().Body)
Ok(t, err)
Assert(t, status == r.Result().StatusCode, "exp %d got %d, body: %s", status, r.Result().StatusCode, string(body))
Assert(t, strings.Contains(string(body), bodySubstr), "exp %q to be contained in %q", bodySubstr, string(body))
}