diff --git a/server/events/atlantis_workspace.go b/server/events/atlantis_workspace.go
index 06944bdae..251adb7e3 100644
--- a/server/events/atlantis_workspace.go
+++ b/server/events/atlantis_workspace.go
@@ -35,6 +35,7 @@ type AtlantisWorkspace interface {
// absolute path to the root of the cloned repo.
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error)
// GetWorkspace returns the path to the workspace for this repo and pull.
+ // If workspace does not exist on disk, error will be of type os.IsNotExist.
GetWorkspace(r models.Repo, p models.PullRequest, workspace string) (string, error)
// Delete deletes the workspace for this repo and pull.
Delete(r models.Repo, p models.PullRequest) error
diff --git a/server/events/atlantis_workspace_locker.go b/server/events/atlantis_workspace_locker.go
index 594ea08ab..cc86db42c 100644
--- a/server/events/atlantis_workspace_locker.go
+++ b/server/events/atlantis_workspace_locker.go
@@ -30,6 +30,8 @@ import (
type AtlantisWorkspaceLocker interface {
// TryLock tries to acquire a lock for this repo, workspace and pull.
TryLock(repoFullName string, workspace string, pullNum int) bool
+ // TryLock2 tries to acquire a lock for this repo, workspace and pull.
+ TryLock2(repoFullName string, workspace string, pullNum int) (func(), error)
// Unlock deletes the lock for this repo, workspace and pull. If there was no
// lock it will do nothing.
Unlock(repoFullName, workspace string, pullNum int)
@@ -48,6 +50,17 @@ func NewDefaultAtlantisWorkspaceLocker() *DefaultAtlantisWorkspaceLocker {
}
}
+func (d *DefaultAtlantisWorkspaceLocker) TryLock2(repoFullName string, workspace string, pullNum int) (func(), error) {
+ if !d.TryLock(repoFullName, workspace, pullNum) {
+ return func() {}, fmt.Errorf("the %s workspace is currently locked by another"+
+ " command that is running for this pull request–"+
+ "wait until the previous command is complete and try again", workspace)
+ }
+ return func() {
+ d.Unlock(repoFullName, workspace, pullNum)
+ }, nil
+}
+
// TryLock returns true if a lock is acquired for this repo, pull and workspace and
// false otherwise.
func (d *DefaultAtlantisWorkspaceLocker) TryLock(repoFullName string, workspace string, pullNum int) bool {
diff --git a/server/events/command_context.go b/server/events/command_context.go
index f986e43cc..49b0f0726 100644
--- a/server/events/command_context.go
+++ b/server/events/command_context.go
@@ -30,7 +30,6 @@ type CommandContext struct {
HeadRepo models.Repo
Pull models.PullRequest
// User is the user that triggered this command.
- User models.User
- Command *Command
- Log *logging.SimpleLogger
+ User models.User
+ Log *logging.SimpleLogger
}
diff --git a/server/events/command_handler_test.go b/server/events/command_handler_test.go
deleted file mode 100644
index 65bcf3c6f..000000000
--- a/server/events/command_handler_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright 2017 HootSuite Media Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the License);
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an AS IS BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// Modified hereafter by contributors to runatlantis/atlantis.
-//
-package events_test
-
-import (
- "bytes"
- "errors"
- "log"
- "strings"
- "testing"
-
- "github.com/google/go-github/github"
- . "github.com/petergtz/pegomock"
- "github.com/runatlantis/atlantis/server/events"
- "github.com/runatlantis/atlantis/server/events/mocks"
- "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"
-)
-
-var operator *mocks.MockPullRequestOperator
-var eventParsing *mocks.MockEventParsing
-var vcsClient *vcsmocks.MockClientProxy
-var ghStatus *mocks.MockCommitStatusUpdater
-var githubGetter *mocks.MockGithubPullGetter
-var gitlabGetter *mocks.MockGitlabMergeRequestGetter
-var workspaceLocker *mocks.MockAtlantisWorkspaceLocker
-var ch events.CommandHandler
-var logBytes *bytes.Buffer
-
-func setup(t *testing.T) {
- RegisterMockTestingT(t)
- operator = mocks.NewMockPullRequestOperator()
- eventParsing = mocks.NewMockEventParsing()
- ghStatus = mocks.NewMockCommitStatusUpdater()
- workspaceLocker = mocks.NewMockAtlantisWorkspaceLocker()
- vcsClient = vcsmocks.NewMockClientProxy()
- githubGetter = mocks.NewMockGithubPullGetter()
- gitlabGetter = mocks.NewMockGitlabMergeRequestGetter()
- logger := logmocks.NewMockSimpleLogging()
- logBytes = new(bytes.Buffer)
- When(logger.Underlying()).ThenReturn(log.New(logBytes, "", 0))
- ch = events.CommandHandler{
- VCSClient: vcsClient,
- CommitStatusUpdater: ghStatus,
- EventParser: eventParsing,
- AtlantisWorkspaceLocker: workspaceLocker,
- MarkdownRenderer: &events.MarkdownRenderer{},
- GithubPullGetter: githubGetter,
- GitlabMergeRequestGetter: gitlabGetter,
- Logger: logger,
- AllowForkPRs: false,
- AllowForkPRsFlag: "allow-fork-prs-flag",
- PullRequestOperator: operator,
- }
-}
-
-func TestExecuteCommand_LogPanics(t *testing.T) {
- t.Log("if there is a panic it is commented back on the pull request")
- 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, nil)).ThenPanic("panic")
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, 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")
-}
-
-func TestExecuteCommand_NoGithubPullGetter(t *testing.T) {
- t.Log("if CommandHandler was constructed with a nil GithubPullGetter an error should be logged")
- setup(t)
- ch.GithubPullGetter = nil
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, 1, nil)
- Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitHub\n", logBytes.String())
-}
-
-func TestExecuteCommand_NoGitlabMergeGetter(t *testing.T) {
- t.Log("if CommandHandler was constructed with a nil GitlabMergeRequestGetter an error should be logged")
- setup(t)
- ch.GitlabMergeRequestGetter = nil
- ch.ExecuteCommand(fixtures.GitlabRepo, fixtures.GitlabRepo, fixtures.User, 1, nil)
- Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitLab\n", logBytes.String())
-}
-
-func TestExecuteCommand_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.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
- Equals(t, "[ERROR] runatlantis/atlantis#1: Making pull request API call to GitHub: err\n", logBytes.String())
-}
-
-func TestExecuteCommand_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.ExecuteCommand(fixtures.GitlabRepo, fixtures.GitlabRepo, fixtures.User, fixtures.Pull.Num, nil)
- Equals(t, "[ERROR] runatlantis/atlantis#1: Making merge request API call to GitLab: err\n", logBytes.String())
-}
-
-func TestExecuteCommand_GithubPullParseErr(t *testing.T) {
- t.Log("if parsing the returned github pull request fails an error should be logged")
- setup(t)
- var pull github.PullRequest
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
- When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, errors.New("err"))
-
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
- Equals(t, "[ERROR] runatlantis/atlantis#1: Extracting required fields from comment data: err\n", logBytes.String())
-}
-
-func TestExecuteCommand_ForkPRDisabled(t *testing.T) {
- t.Log("if a command is run on a forked pull request and this is disabled atlantis should" +
- " comment saying that this is not allowed")
- setup(t)
- ch.AllowForkPRs = false // by default it's false so don't need to reset
- var pull github.PullRequest
- modelPull := models.PullRequest{State: models.Open}
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
-
- headRepo := fixtures.GithubRepo
- headRepo.FullName = "forkrepo/atlantis"
- headRepo.Owner = "forkrepo"
- When(eventParsing.ParseGithubPull(&pull)).ThenReturn(modelPull, headRepo, nil)
-
- ch.ExecuteCommand(fixtures.GithubRepo, models.Repo{} /* this isn't used */, 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)
-}
-
-func TestExecuteCommand_ClosedPull(t *testing.T) {
- t.Log("if a command is run on a closed pull request atlantis should" +
- " comment saying that this is not allowed")
- setup(t)
- pull := &github.PullRequest{
- State: github.String("closed"),
- }
- modelPull := models.PullRequest{State: models.Closed}
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
- When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, fixtures.GithubRepo, nil)
-
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
- vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
-}
-
-func TestExecuteCommand_WorkspaceLocked(t *testing.T) {
- t.Log("if the workspace is locked, should comment back on the pull")
- setup(t)
- pull := &github.PullRequest{
- State: github.String("closed"),
- }
- cmd := events.Command{
- Name: events.Plan,
- Workspace: "workspace",
- }
-
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
- When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, nil)
- When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(false)
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, &cmd)
-
- msg := "The workspace workspace is currently locked by another" +
- " command that is running for this pull request." +
- " Wait until the previous command is complete and try again."
- ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, &cmd)
- _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResponse()).GetCapturedArguments()
- Equals(t, msg, response.Failure)
- vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, fixtures.Pull.Num,
- "**Plan Failed**: "+msg+"\n\n")
-}
-
-func TestExecuteCommand_FullRun(t *testing.T) {
- t.Log("when running a plan, apply should comment")
- pull := &github.PullRequest{
- State: github.String("closed"),
- }
- cmdResponse := events.CommandResponse{}
- for _, c := range []events.CommandName{events.Plan, events.Apply} {
- setup(t)
- cmd := events.Command{
- Name: c,
- Workspace: "workspace",
- }
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
- When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, nil)
- When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(true)
- switch c {
- case events.Plan:
- When(operator.PlanViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResponse)
- case events.Apply:
- When(operator.ApplyViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResponse)
- }
-
- ch.ExecuteCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, &cmd)
-
- ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, &cmd)
- _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResponse()).GetCapturedArguments()
- Equals(t, cmdResponse, response)
- vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
- workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)
- }
-}
-
-func TestExecuteCommand_ForkPREnabled(t *testing.T) {
- t.Log("when running a plan on a fork PR, it should succeed")
- setup(t)
-
- // Enable forked PRs.
- ch.AllowForkPRs = true
- defer func() { ch.AllowForkPRs = false }() // Reset after test.
-
- var pull github.PullRequest
- cmdResponse := events.CommandResponse{}
- cmd := events.Command{
- Name: events.Plan,
- Workspace: "workspace",
- }
- When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
- headRepo := fixtures.GithubRepo
- headRepo.FullName = "forkrepo/atlantis"
- headRepo.Owner = "forkrepo"
- When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, headRepo, nil)
- When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(true)
- When(operator.PlanViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResponse)
-
- ch.ExecuteCommand(fixtures.GithubRepo, models.Repo{} /* this isn't used */, fixtures.User, fixtures.Pull.Num, &cmd)
-
- ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, &cmd)
- _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResponse()).GetCapturedArguments()
- Equals(t, cmdResponse, response)
- vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
- workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)
-}
diff --git a/server/events/command_response.go b/server/events/command_result.go
similarity index 89%
rename from server/events/command_response.go
rename to server/events/command_result.go
index abba3897b..ff767ddb5 100644
--- a/server/events/command_response.go
+++ b/server/events/command_result.go
@@ -13,8 +13,8 @@
//
package events
-// CommandResponse is the result of running a Command.
-type CommandResponse struct {
+// CommandResult is the result of running a Command.
+type CommandResult struct {
Error error
Failure string
ProjectResults []ProjectResult
diff --git a/server/events/command_handler.go b/server/events/command_runner.go
similarity index 58%
rename from server/events/command_handler.go
rename to server/events/command_runner.go
index bfd25dd93..c54891e87 100644
--- a/server/events/command_handler.go
+++ b/server/events/command_runner.go
@@ -29,10 +29,11 @@ import (
// CommandRunner is the first step after a command request has been parsed.
type CommandRunner interface {
- // ExecuteCommand is the first step after a command request has been parsed.
+ // 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.
- ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *Command)
+ RunCommentCommand(baseRepo models.Repo, maybeHeadRepo *models.Repo, user models.User, pullNum int, cmd *CommentCommand)
+ RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User)
}
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_github_pull_getter.go GithubPullGetter
@@ -51,14 +52,13 @@ type GitlabMergeRequestGetter interface {
GetMergeRequest(repoFullName string, pullNum int) (*gitlab.MergeRequest, error)
}
-// CommandHandler is the first step when processing a comment command.
-type CommandHandler struct {
+// DefaultCommandRunner is the first step when processing a comment command.
+type DefaultCommandRunner struct {
VCSClient vcs.ClientProxy
GithubPullGetter GithubPullGetter
GitlabMergeRequestGetter GitlabMergeRequestGetter
CommitStatusUpdater CommitStatusUpdater
EventParser EventParsing
- AtlantisWorkspaceLocker AtlantisWorkspaceLocker
MarkdownRenderer *MarkdownRenderer
Logger logging.SimpleLogging
// AllowForkPRs controls whether we operate on pull requests from forks.
@@ -66,17 +66,50 @@ type CommandHandler struct {
// AllowForkPRsFlag is the name of the flag that controls fork PR's. We use
// this in our error message back to the user on a forked PR so they know
// how to enable this functionality.
- AllowForkPRsFlag string
- PullRequestOperator PullRequestOperator
+ AllowForkPRsFlag string
+ ProjectCommandBuilder ProjectCommandBuilder
+ ProjectCommandRunner *ProjectCommandRunner
}
-// ExecuteCommand executes the command.
-// If the repo is from GitHub, we don't use headRepo and instead make an API call
-// to get the headRepo. This is because the caller is unable to pass in a
-// headRepo since there's not enough data available on the initial webhook
-// payload.
-func (c *CommandHandler) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *Command) {
+func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User) {
+ log := c.buildLogger(baseRepo.FullName, pull.Num)
+ ctx := &CommandContext{
+ User: user,
+ Log: log,
+ Pull: pull,
+ HeadRepo: headRepo,
+ BaseRepo: baseRepo,
+ }
+ runFn := func() ([]ProjectResult, error) {
+ projectCmds, err := c.ProjectCommandBuilder.BuildAutoplanCommands(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var results []ProjectResult
+ for _, cmd := range projectCmds {
+ res := c.ProjectCommandRunner.Plan(cmd)
+ results = append(results, ProjectResult{
+ ProjectCommandResult: res,
+ Path: cmd.RepoRelPath,
+ Workspace: cmd.Workspace,
+ })
+ }
+ return results, nil
+ }
+ c.run(ctx, AutoplanCommand{}, runFn)
+}
+
+// RunCommentCommand executes the command.
+// We take in a pointer for maybeHeadRepo because for some events there isn't
+// 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) {
log := c.buildLogger(baseRepo.FullName, pullNum)
+ var headRepo models.Repo
+ if maybeHeadRepo != nil {
+ headRepo = *maybeHeadRepo
+ }
var err error
var pull models.PullRequest
@@ -97,13 +130,38 @@ func (c *CommandHandler) ExecuteCommand(baseRepo models.Repo, headRepo models.Re
Log: log,
Pull: pull,
HeadRepo: headRepo,
- Command: cmd,
BaseRepo: baseRepo,
}
- c.run(ctx)
+
+ runFn := func() ([]ProjectResult, error) {
+ var result ProjectCommandResult
+ switch cmd.Name {
+ case Plan:
+ projectCmd, err := c.ProjectCommandBuilder.BuildPlanCommand(ctx, cmd)
+ if err != nil {
+ return nil, err
+ }
+ result = c.ProjectCommandRunner.Plan(projectCmd)
+ case Apply:
+ projectCmd, err := c.ProjectCommandBuilder.BuildApplyCommand(ctx, cmd)
+ if err != nil {
+ return nil, err
+ }
+ result = c.ProjectCommandRunner.Apply(projectCmd)
+ default:
+ ctx.Log.Err("failed to determine desired command, neither plan nor apply")
+ }
+ return []ProjectResult{{
+ Path: cmd.Dir,
+ Workspace: cmd.Workspace,
+ ProjectCommandResult: result,
+ }}, nil
+ }
+
+ c.run(ctx, cmd, runFn)
}
-func (c *CommandHandler) getGithubData(baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
+func (c *DefaultCommandRunner) getGithubData(baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
if c.GithubPullGetter == nil {
return models.PullRequest{}, models.Repo{}, errors.New("Atlantis not configured to support GitHub")
}
@@ -111,14 +169,14 @@ func (c *CommandHandler) getGithubData(baseRepo models.Repo, pullNum int) (model
if err != nil {
return models.PullRequest{}, models.Repo{}, errors.Wrap(err, "making pull request API call to GitHub")
}
- pull, repo, err := c.EventParser.ParseGithubPull(ghPull)
+ pull, _, headRepo, err := c.EventParser.ParseGithubPull(ghPull)
if err != nil {
- return pull, repo, errors.Wrap(err, "extracting required fields from comment data")
+ return pull, headRepo, errors.Wrap(err, "extracting required fields from comment data")
}
- return pull, repo, nil
+ return pull, headRepo, nil
}
-func (c *CommandHandler) getGitlabData(baseRepo models.Repo, pullNum int) (models.PullRequest, error) {
+func (c *DefaultCommandRunner) getGitlabData(baseRepo models.Repo, pullNum int) (models.PullRequest, error) {
if c.GitlabMergeRequestGetter == nil {
return models.PullRequest{}, errors.New("Atlantis not configured to support GitLab")
}
@@ -130,60 +188,47 @@ func (c *CommandHandler) getGitlabData(baseRepo models.Repo, pullNum int) (model
return pull, nil
}
-func (c *CommandHandler) buildLogger(repoFullName string, pullNum int) *logging.SimpleLogger {
+func (c *DefaultCommandRunner) buildLogger(repoFullName string, pullNum int) *logging.SimpleLogger {
src := fmt.Sprintf("%s#%d", repoFullName, pullNum)
return logging.NewSimpleLogger(src, c.Logger.Underlying(), true, c.Logger.GetLevel())
}
-func (c *CommandHandler) run(ctx *CommandContext) {
- defer c.logPanics(ctx)
-
+func (c *DefaultCommandRunner) validateCtxAndComment(ctx *CommandContext) bool {
if !c.AllowForkPRs && ctx.HeadRepo.Owner != ctx.BaseRepo.Owner {
ctx.Log.Info("command was run on a fork pull request which is disallowed")
c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, fmt.Sprintf("Atlantis commands can't be run on fork pull requests. To enable, set --%s", c.AllowForkPRsFlag)) // nolint: errcheck
- return
+ return false
}
if ctx.Pull.State != models.Open {
ctx.Log.Info("command was run on closed pull request")
c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, "Atlantis commands can't be run on closed pull requests") // nolint: errcheck
+ return false
+ }
+ return true
+}
+
+func (c *DefaultCommandRunner) run(ctx *CommandContext, command CommandInterface, commandRunner func() ([]ProjectResult, error)) {
+ defer c.logPanics(ctx)
+
+ if !c.validateCtxAndComment(ctx) {
return
}
ctx.Log.Debug("updating commit status to pending")
- if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, ctx.Command); err != nil {
+ if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, vcs.Pending, command.CommandName()); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
- if !c.AtlantisWorkspaceLocker.TryLock(ctx.BaseRepo.FullName, ctx.Command.Workspace, ctx.Pull.Num) {
- errMsg := fmt.Sprintf(
- "The %s workspace is currently locked by another"+
- " command that is running for this pull request."+
- " Wait until the previous command is complete and try again.",
- ctx.Command.Workspace)
- ctx.Log.Warn(errMsg)
- c.updatePull(ctx, CommandResponse{Failure: errMsg})
+
+ results, err := commandRunner()
+ if err != nil {
+ c.updatePull(ctx, command, CommandResult{Error: err})
return
}
- ctx.Log.Debug("successfully acquired workspace lock")
- defer c.AtlantisWorkspaceLocker.Unlock(ctx.BaseRepo.FullName, ctx.Command.Workspace, ctx.Pull.Num)
-
- var cr CommandResponse
- switch ctx.Command.Name {
- case Plan:
- if ctx.Command.Autoplan {
- cr = c.PullRequestOperator.Autoplan(ctx)
- } else {
- cr = c.PullRequestOperator.PlanViaComment(ctx)
- }
- case Apply:
- cr = c.PullRequestOperator.ApplyViaComment(ctx)
- default:
- ctx.Log.Err("failed to determine desired command, neither plan nor apply")
- }
- c.updatePull(ctx, cr)
+ c.updatePull(ctx, command, CommandResult{ProjectResults: results})
}
-func (c *CommandHandler) updatePull(ctx *CommandContext, res CommandResponse) {
+func (c *DefaultCommandRunner) updatePull(ctx *CommandContext, command CommandInterface, res CommandResult) {
// Log if we got any errors or failures.
if res.Error != nil {
ctx.Log.Err(res.Error.Error())
@@ -192,15 +237,15 @@ func (c *CommandHandler) updatePull(ctx *CommandContext, res CommandResponse) {
}
// Update the pull request's status icon and comment back.
- if err := c.CommitStatusUpdater.UpdateProjectResult(ctx, res); err != nil {
+ if err := c.CommitStatusUpdater.UpdateProjectResult(ctx, command.CommandName(), res); err != nil {
ctx.Log.Warn("unable to update commit status: %s", err)
}
- comment := c.MarkdownRenderer.Render(res, ctx.Command.Name, ctx.Log.History.String(), ctx.Command.Verbose, ctx.Command.Autoplan)
+ comment := c.MarkdownRenderer.Render(res, command.CommandName(), ctx.Log.History.String(), command.IsVerbose(), command.IsAutoplan())
c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, comment) // nolint: errcheck
}
// logPanics logs and creates a comment on the pull request for panics.
-func (c *CommandHandler) logPanics(ctx *CommandContext) {
+func (c *DefaultCommandRunner) logPanics(ctx *CommandContext) {
if err := recover(); err != nil {
stack := recovery.Stack(3)
c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, // nolint: errcheck
diff --git a/server/events/command_runner_test.go b/server/events/command_runner_test.go
new file mode 100644
index 000000000..a0d636c9a
--- /dev/null
+++ b/server/events/command_runner_test.go
@@ -0,0 +1,239 @@
+// Copyright 2017 HootSuite Media Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the License);
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an AS IS BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// Modified hereafter by contributors to runatlantis/atlantis.
+//
+package events_test
+
+import (
+ "bytes"
+ "log"
+ "testing"
+
+ . "github.com/petergtz/pegomock"
+ "github.com/runatlantis/atlantis/server/events"
+ "github.com/runatlantis/atlantis/server/events/mocks"
+ vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
+ logmocks "github.com/runatlantis/atlantis/server/logging/mocks"
+ //. "github.com/runatlantis/atlantis/testing"
+)
+
+var projectCommandBuilder *mocks.MockProjectCommandBuilder
+var eventParsing *mocks.MockEventParsing
+var vcsClient *vcsmocks.MockClientProxy
+var ghStatus *mocks.MockCommitStatusUpdater
+var githubGetter *mocks.MockGithubPullGetter
+var gitlabGetter *mocks.MockGitlabMergeRequestGetter
+var workspaceLocker *mocks.MockAtlantisWorkspaceLocker
+var ch events.DefaultCommandRunner
+var logBytes *bytes.Buffer
+
+func setup(t *testing.T) {
+ RegisterMockTestingT(t)
+ projectCommandBuilder = mocks.NewMockProjectCommandBuilder()
+ eventParsing = mocks.NewMockEventParsing()
+ ghStatus = mocks.NewMockCommitStatusUpdater()
+ workspaceLocker = mocks.NewMockAtlantisWorkspaceLocker()
+ vcsClient = vcsmocks.NewMockClientProxy()
+ githubGetter = mocks.NewMockGithubPullGetter()
+ gitlabGetter = mocks.NewMockGitlabMergeRequestGetter()
+ logger := logmocks.NewMockSimpleLogging()
+ logBytes = new(bytes.Buffer)
+ When(logger.Underlying()).ThenReturn(log.New(logBytes, "", 0))
+ ch = events.DefaultCommandRunner{
+ VCSClient: vcsClient,
+ CommitStatusUpdater: ghStatus,
+ EventParser: eventParsing,
+ MarkdownRenderer: &events.MarkdownRenderer{},
+ GithubPullGetter: githubGetter,
+ GitlabMergeRequestGetter: gitlabGetter,
+ Logger: logger,
+ AllowForkPRs: false,
+ AllowForkPRsFlag: "allow-fork-prs-flag",
+ ProjectCommandBuilder: projectCommandBuilder,
+ }
+}
+
+//func TestExecuteCommand_LogPanics(t *testing.T) {
+// t.Log("if there is a panic it is commented back on the pull request")
+// 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)
+// _, _, comment := vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString()).GetCapturedArguments()
+// Assert(t, strings.Contains(comment, "Error: goroutine panic"), "comment should be about a goroutine panic")
+//}
+//
+//func TestExecuteCommand_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)
+// Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitHub\n", logBytes.String())
+//}
+//
+//func TestExecuteCommand_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)
+// Equals(t, "[ERROR] runatlantis/atlantis#1: Atlantis not configured to support GitLab\n", logBytes.String())
+//}
+//
+//func TestExecuteCommand_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)
+// Equals(t, "[ERROR] runatlantis/atlantis#1: Making pull request API call to GitHub: err\n", logBytes.String())
+//}
+//
+//func TestExecuteCommand_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)
+// Equals(t, "[ERROR] runatlantis/atlantis#1: Making merge request API call to GitLab: err\n", logBytes.String())
+//}
+//
+//func TestExecuteCommand_GithubPullParseErr(t *testing.T) {
+// t.Log("if parsing the returned github pull request fails an error should be logged")
+// setup(t)
+// var pull github.PullRequest
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
+// When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, errors.New("err"))
+//
+// ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
+// Equals(t, "[ERROR] runatlantis/atlantis#1: Extracting required fields from comment data: err\n", logBytes.String())
+//}
+//
+//func TestExecuteCommand_ForkPRDisabled(t *testing.T) {
+// t.Log("if a command is run on a forked pull request and this is disabled atlantis should" +
+// " comment saying that this is not allowed")
+// setup(t)
+// ch.AllowForkPRs = false // by default it's false so don't need to reset
+// var pull github.PullRequest
+// modelPull := models.PullRequest{State: models.Open}
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
+//
+// headRepo := fixtures.GithubRepo
+// headRepo.FullName = "forkrepo/atlantis"
+// headRepo.Owner = "forkrepo"
+// When(eventParsing.ParseGithubPull(&pull)).ThenReturn(modelPull, headRepo, nil)
+//
+// ch.RunCommentCommand(fixtures.GithubRepo, 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)
+//}
+//
+//func TestExecuteCommand_ClosedPull(t *testing.T) {
+// t.Log("if a command is run on a closed pull request atlantis should" +
+// " comment saying that this is not allowed")
+// setup(t)
+// pull := &github.PullRequest{
+// State: github.String("closed"),
+// }
+// modelPull := models.PullRequest{State: models.Closed}
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+// When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, fixtures.GithubRepo, nil)
+//
+// ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, nil)
+// vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
+//}
+//
+//func TestExecuteCommand_WorkspaceLocked(t *testing.T) {
+// t.Log("if the workspace is locked, should comment back on the pull")
+// setup(t)
+// pull := &github.PullRequest{
+// State: github.String("closed"),
+// }
+// cmd := events.CommentCommand{
+// Name: events.Plan,
+// Workspace: "workspace",
+// }
+//
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+// When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, nil)
+// When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(false)
+// ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, &cmd)
+//
+// msg := "The workspace workspace is currently locked by another" +
+// " command that is running for this pull request." +
+// " Wait until the previous command is complete and try again."
+// ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, cmd.CommandName())
+// _, _, result := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
+// Equals(t, msg, result.Failure)
+// vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, fixtures.Pull.Num,
+// "**Plan Failed**: "+msg+"\n\n")
+//}
+//
+//func TestExecuteCommand_FullRun(t *testing.T) {
+// t.Log("when running a plan, apply should comment")
+// pull := &github.PullRequest{
+// State: github.String("closed"),
+// }
+// cmdResult := events.CommandResult{}
+// for _, c := range []events.CommandName{events.Plan, events.Apply} {
+// setup(t)
+// cmd := events.CommentCommand{
+// Name: c,
+// Workspace: "workspace",
+// }
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
+// When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, nil)
+// When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(true)
+// switch c {
+// case events.Plan:
+// When(projectCommandBuilder.PlanViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResult)
+// case events.Apply:
+// When(projectCommandBuilder.ApplyViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResult)
+// }
+//
+// ch.RunCommentCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.User, fixtures.Pull.Num, &cmd)
+//
+// ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, &cmd)
+// _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
+// Equals(t, cmdResult, response)
+// vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
+// workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)
+// }
+//}
+//
+//func TestExecuteCommand_ForkPREnabled(t *testing.T) {
+// t.Log("when running a plan on a fork PR, it should succeed")
+// setup(t)
+//
+// // Enable forked PRs.
+// ch.AllowForkPRs = true
+// defer func() { ch.AllowForkPRs = false }() // Reset after test.
+//
+// var pull github.PullRequest
+// cmdResponse := events.CommandResult{}
+// cmd := events.CommentCommand{
+// Name: events.Plan,
+// Workspace: "workspace",
+// }
+// When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(&pull, nil)
+// headRepo := fixtures.GithubRepo
+// headRepo.FullName = "forkrepo/atlantis"
+// headRepo.Owner = "forkrepo"
+// When(eventParsing.ParseGithubPull(&pull)).ThenReturn(fixtures.Pull, headRepo, nil)
+// When(workspaceLocker.TryLock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)).ThenReturn(true)
+// When(projectCommandBuilder.PlanViaComment(matchers.AnyPtrToEventsCommandContext())).ThenReturn(cmdResponse)
+//
+// ch.RunCommentCommand(fixtures.GithubRepo, models.Repo{} /* this isn't used */, fixtures.User, fixtures.Pull.Num, &cmd)
+//
+// ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, vcs.Pending, &cmd)
+// _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
+// Equals(t, cmdResponse, response)
+// vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
+// workspaceLocker.VerifyWasCalledOnce().Unlock(fixtures.GithubRepo.FullName, cmd.Workspace, fixtures.Pull.Num)
+//}
diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go
index 808d4493a..c6f9f2439 100644
--- a/server/events/comment_parser.go
+++ b/server/events/comment_parser.go
@@ -59,7 +59,7 @@ type CommentParser struct {
type CommentParseResult struct {
// Command is the successfully parsed command. Will be nil if
// CommentResponse or Ignore is set.
- Command *Command
+ Command *CommentCommand
// CommentResponse is set when we should respond immediately to the command
// for example for atlantis help.
CommentResponse string
@@ -215,7 +215,7 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
}
return CommentParseResult{
- Command: NewCommand(dir, extraArgs, name, verbose, workspace, project, false),
+ Command: NewCommand(dir, extraArgs, name, verbose, workspace, project),
}
}
diff --git a/server/events/commit_status_updater.go b/server/events/commit_status_updater.go
index 3debcd43f..ba5a23503 100644
--- a/server/events/commit_status_updater.go
+++ b/server/events/commit_status_updater.go
@@ -27,10 +27,10 @@ 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, cmd *Command) error
+ Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, command CommandName) error
// UpdateProjectResult updates the status of the head commit given the
// state of response.
- UpdateProjectResult(ctx *CommandContext, res CommandResponse) error
+ UpdateProjectResult(ctx *CommandContext, commandName CommandName, res CommandResult) error
}
// DefaultCommitStatusUpdater implements CommitStatusUpdater.
@@ -39,13 +39,13 @@ type DefaultCommitStatusUpdater struct {
}
// Update updates the commit status.
-func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *Command) error {
- description := fmt.Sprintf("%s %s", strings.Title(cmd.Name.String()), strings.Title(status.String()))
+func (d *DefaultCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.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, res CommandResponse) error {
+func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, commandName CommandName, res CommandResult) error {
var status vcs.CommitStatus
if res.Error != nil || res.Failure != "" {
status = vcs.Failed
@@ -56,7 +56,7 @@ func (d *DefaultCommitStatusUpdater) UpdateProjectResult(ctx *CommandContext, re
}
status = d.worstStatus(statuses)
}
- return d.Update(ctx.BaseRepo, ctx.Pull, status, ctx.Command)
+ return d.Update(ctx.BaseRepo, ctx.Pull, status, commandName)
}
func (d *DefaultCommitStatusUpdater) worstStatus(ss []vcs.CommitStatus) vcs.CommitStatus {
diff --git a/server/events/commit_status_updater_test.go b/server/events/commit_status_updater_test.go
index 7d05264b0..155822dc1 100644
--- a/server/events/commit_status_updater_test.go
+++ b/server/events/commit_status_updater_test.go
@@ -29,26 +29,12 @@ import (
var repoModel = models.Repo{}
var pullModel = models.PullRequest{}
var status = vcs.Success
-var cmd = events.Command{
- Name: events.Plan,
-}
-
-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())
- }
-}
func TestUpdate(t *testing.T) {
RegisterMockTestingT(t)
client := mocks.NewMockClientProxy()
s := events.DefaultCommitStatusUpdater{Client: client}
- err := s.Update(repoModel, pullModel, status, &cmd)
+ err := s.Update(repoModel, pullModel, status, events.Plan)
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, status, "Plan Success")
}
@@ -58,11 +44,10 @@ func TestUpdateProjectResult_Error(t *testing.T) {
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
- Command: &events.Command{Name: events.Plan},
}
client := mocks.NewMockClientProxy()
s := events.DefaultCommitStatusUpdater{Client: client}
- err := s.UpdateProjectResult(ctx, events.CommandResponse{Error: errors.New("err")})
+ err := s.UpdateProjectResult(ctx, events.Plan, events.CommandResult{Error: errors.New("err")})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed")
}
@@ -72,23 +57,20 @@ func TestUpdateProjectResult_Failure(t *testing.T) {
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
- Command: &events.Command{Name: events.Plan},
}
client := mocks.NewMockClientProxy()
s := events.DefaultCommitStatusUpdater{Client: client}
- err := s.UpdateProjectResult(ctx, events.CommandResponse{Failure: "failure"})
+ err := s.UpdateProjectResult(ctx, events.Plan, events.CommandResult{Failure: "failure"})
Ok(t, err)
client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, vcs.Failed, "Plan Failed")
}
func TestUpdateProjectResult(t *testing.T) {
- t.Log("should use worst status")
RegisterMockTestingT(t)
ctx := &events.CommandContext{
BaseRepo: repoModel,
Pull: pullModel,
- Command: &events.Command{Name: events.Plan},
}
cases := []struct {
@@ -126,25 +108,31 @@ func TestUpdateProjectResult(t *testing.T) {
}
for _, c := range cases {
- var results []events.ProjectResult
- for _, statusStr := range c.Statuses {
- var result events.ProjectResult
- switch statusStr {
- case "failure":
- result = events.ProjectResult{Failure: "failure"}
- case "error":
- result = events.ProjectResult{Error: errors.New("err")}
- default:
- result = events.ProjectResult{}
+ t.Run(strings.Join(c.Statuses, "-"), func(t *testing.T) {
+ var results []events.ProjectResult
+ for _, statusStr := range c.Statuses {
+ var result events.ProjectResult
+ switch statusStr {
+ case "failure":
+ result = events.ProjectResult{
+ ProjectCommandResult: events.ProjectCommandResult{Failure: "failure"},
+ }
+ case "error":
+ result = events.ProjectResult{
+ ProjectCommandResult: events.ProjectCommandResult{Error: errors.New("err")},
+ }
+ default:
+ result = events.ProjectResult{}
+ }
+ results = append(results, result)
}
- results = append(results, result)
- }
- resp := events.CommandResponse{ProjectResults: results}
+ resp := events.CommandResult{ProjectResults: results}
- client := mocks.NewMockClientProxy()
- s := events.DefaultCommitStatusUpdater{Client: client}
- err := s.UpdateProjectResult(ctx, resp)
- Ok(t, err)
- client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.Expected, "Plan "+strings.Title(c.Expected.String()))
+ client := mocks.NewMockClientProxy()
+ s := events.DefaultCommitStatusUpdater{Client: client}
+ err := s.UpdateProjectResult(ctx, events.Plan, resp)
+ Ok(t, err)
+ client.VerifyWasCalledOnce().UpdateStatus(repoModel, pullModel, c.Expected, "Plan "+strings.Title(c.Expected.String()))
+ })
}
}
diff --git a/server/events/event_parser.go b/server/events/event_parser.go
index dbdccc8e5..3bf4e7f15 100644
--- a/server/events/event_parser.go
+++ b/server/events/event_parser.go
@@ -34,7 +34,27 @@ 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 Command struct {
+type CommandInterface interface {
+ CommandName() CommandName
+ IsVerbose() bool
+ IsAutoplan() bool
+}
+
+type AutoplanCommand struct{}
+
+func (c AutoplanCommand) CommandName() CommandName {
+ return Plan
+}
+
+func (c AutoplanCommand) IsVerbose() bool {
+ return false
+}
+
+func (c AutoplanCommand) IsAutoplan() bool {
+ return true
+}
+
+type CommentCommand struct {
// Dir is the path relative to the repo root to run the command in.
// Will never be an empty string and will never end in "/".
Dir string
@@ -44,20 +64,29 @@ type Command struct {
Name CommandName
Verbose bool
Workspace string
- // Autoplan is true if the command is a plan command being executed in an
- // attempt to automatically run plan.
- Autoplan bool
// ProjectName is the name of a project to run the command on. It refers to a
// project specified in an atlantis.yaml file.
ProjectName string
}
-func (c Command) String() string {
- return fmt.Sprintf("command=%q verbose=%t dir=%q workspace=%q project=%q autoplan=%t flags=%q", c.Name.String(), c.Verbose, c.Dir, c.Workspace, c.ProjectName, c.Autoplan, strings.Join(c.Flags, ","))
+func (c CommentCommand) CommandName() CommandName {
+ return c.Name
+}
+
+func (c CommentCommand) IsVerbose() bool {
+ return c.Verbose
+}
+
+func (c CommentCommand) IsAutoplan() bool {
+ return false
+}
+
+func (c CommentCommand) String() string {
+ return fmt.Sprintf("command=%q verbose=%t dir=%q workspace=%q project=%q flags=%q", c.Name.String(), c.Verbose, c.Dir, c.Workspace, c.ProjectName, strings.Join(c.Flags, ","))
}
// NewCommand constructs a Command, setting all missing fields to defaults.
-func NewCommand(dir string, flags []string, name CommandName, verbose bool, workspace string, project string, autoplan bool) *Command {
+func NewCommand(dir string, flags []string, name CommandName, verbose bool, workspace string, project string) *CommentCommand {
// If dir was an empty string, this will return '.'.
validDir := path.Clean(dir)
if validDir == "/" {
@@ -66,24 +95,27 @@ func NewCommand(dir string, flags []string, name CommandName, verbose bool, work
if workspace == "" {
workspace = DefaultWorkspace
}
- return &Command{
+ return &CommentCommand{
Dir: validDir,
Flags: flags,
Name: name,
Verbose: verbose,
Workspace: workspace,
- Autoplan: autoplan,
ProjectName: project,
}
}
type EventParsing interface {
ParseGithubIssueCommentEvent(comment *github.IssueCommentEvent) (baseRepo models.Repo, user models.User, pullNum int, err error)
- // ParseGithubPull returns the pull request and head repo.
- ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, 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)
ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error)
- // ParseGitlabMergeEvent returns the pull request, base repo and head repo.
- ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo, 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)
ParseGitlabMergeCommentEvent(event gitlab.MergeCommentEvent) (baseRepo models.Repo, headRepo models.Repo, user models.User, err error)
ParseGitlabMergeRequest(mr *gitlab.MergeRequest, baseRepo models.Repo) models.PullRequest
}
@@ -116,38 +148,58 @@ func (e *EventParser) ParseGithubIssueCommentEvent(comment *github.IssueCommentE
return
}
-func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
- var pullModel models.PullRequest
- var headRepoModel models.Repo
+func (e *EventParser) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
+ if pullEvent.PullRequest == nil {
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("pull_request is null")
+ }
+ pull, baseRepo, headRepo, err := e.ParseGithubPull(pullEvent.PullRequest)
+ if err != nil {
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
+ }
+ if pullEvent.Sender == nil {
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("sender is null")
+ }
+ senderUsername := pullEvent.Sender.GetLogin()
+ if senderUsername == "" {
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, errors.New("sender.login is null")
+ }
+ return pull, baseRepo, headRepo, models.User{Username: senderUsername}, nil
+}
+func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (pullModel models.PullRequest, baseRepo models.Repo, headRepo models.Repo, err error) {
commit := pull.Head.GetSHA()
if commit == "" {
- return pullModel, headRepoModel, errors.New("head.sha is null")
+ err = errors.New("head.sha is null")
+ return
}
url := pull.GetHTMLURL()
if url == "" {
- return pullModel, headRepoModel, errors.New("html_url is null")
+ err = errors.New("html_url is null")
+ return
}
branch := pull.Head.GetRef()
if branch == "" {
- return pullModel, headRepoModel, errors.New("head.ref is null")
+ err = errors.New("head.ref is null")
+ return
}
authorUsername := pull.User.GetLogin()
if authorUsername == "" {
- return pullModel, headRepoModel, errors.New("user.login is null")
+ err = errors.New("user.login is null")
+ return
}
num := pull.GetNumber()
if num == 0 {
- return pullModel, headRepoModel, errors.New("number is null")
+ err = errors.New("number is null")
+ return
}
- baseRepoModel, err := e.ParseGithubRepo(pull.Base.Repo)
+ baseRepo, err = e.ParseGithubRepo(pull.Base.Repo)
if err != nil {
- return pullModel, headRepoModel, err
+ return
}
- headRepoModel, err = e.ParseGithubRepo(pull.Head.Repo)
+ headRepo, err = e.ParseGithubRepo(pull.Head.Repo)
if err != nil {
- return pullModel, headRepoModel, err
+ return
}
pullState := models.Closed
@@ -155,22 +207,23 @@ func (e *EventParser) ParseGithubPull(pull *github.PullRequest) (models.PullRequ
pullState = models.Open
}
- return models.PullRequest{
+ pullModel = models.PullRequest{
Author: authorUsername,
Branch: branch,
HeadCommit: commit,
URL: url,
Num: num,
State: pullState,
- BaseRepo: baseRepoModel,
- }, headRepoModel, nil
+ BaseRepo: baseRepo,
+ }
+ return
}
func (e *EventParser) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
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, error) {
+func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.PullRequest, models.Repo, models.Repo, models.User, error) {
modelState := models.Closed
if event.ObjectAttributes.State == gitlabPullOpened {
modelState = models.Open
@@ -180,11 +233,11 @@ func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.Pul
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{}, err
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
}
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{}, err
+ return models.PullRequest{}, models.Repo{}, models.Repo{}, models.User{}, err
}
pull := models.PullRequest{
@@ -197,7 +250,11 @@ func (e *EventParser) ParseGitlabMergeEvent(event gitlab.MergeEvent) (models.Pul
BaseRepo: baseRepo,
}
- return pull, baseRepo, headRepo, err
+ user := models.User{
+ Username: event.User.Username,
+ }
+
+ return pull, baseRepo, headRepo, user, err
}
// ParseGitlabMergeCommentEvent creates Atlantis models out of a GitLab event.
diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go
index 1415ffba3..d7ef7fce7 100644
--- a/server/events/event_parser_test.go
+++ b/server/events/event_parser_test.go
@@ -102,34 +102,40 @@ func TestParseGithubIssueCommentEvent(t *testing.T) {
Equals(t, *comment.Issue.Number, pullNum)
}
-func TestParseGithubPull(t *testing.T) {
- testPull := deepcopy.Copy(Pull).(github.PullRequest)
- testPull.Head.SHA = nil
- _, _, err := parser.ParseGithubPull(&testPull)
- ErrEquals(t, "head.sha is null", err)
+func TestParseGithubPullEvent(t *testing.T) {
+ _, _, _, _, err := parser.ParseGithubPullEvent(&github.PullRequestEvent{})
+ ErrEquals(t, "pull_request is null", err)
- testPull = deepcopy.Copy(Pull).(github.PullRequest)
- testPull.HTMLURL = nil
- _, _, err = parser.ParseGithubPull(&testPull)
+ testEvent := deepcopy.Copy(PullEvent).(github.PullRequestEvent)
+ testEvent.PullRequest.HTMLURL = nil
+ _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
ErrEquals(t, "html_url is null", err)
- testPull = deepcopy.Copy(Pull).(github.PullRequest)
- testPull.Head.Ref = nil
- _, _, err = parser.ParseGithubPull(&testPull)
- ErrEquals(t, "head.ref is null", err)
+ testEvent = deepcopy.Copy(PullEvent).(github.PullRequestEvent)
+ testEvent.Sender = nil
+ _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
+ ErrEquals(t, "sender is null", err)
- testPull = deepcopy.Copy(Pull).(github.PullRequest)
- testPull.User.Login = nil
- _, _, err = parser.ParseGithubPull(&testPull)
- ErrEquals(t, "user.login is null", err)
+ testEvent = deepcopy.Copy(PullEvent).(github.PullRequestEvent)
+ testEvent.Sender.Login = nil
+ _, _, _, _, err = parser.ParseGithubPullEvent(&testEvent)
+ ErrEquals(t, "sender.login is null", err)
- testPull = deepcopy.Copy(Pull).(github.PullRequest)
- testPull.Number = nil
- _, _, err = parser.ParseGithubPull(&testPull)
- ErrEquals(t, "number is null", err)
-
- pullRes, _, err := parser.ParseGithubPull(&Pull)
+ actPull, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGithubPullEvent(&PullEvent)
Ok(t, err)
+ expBaseRepo := models.Repo{
+ Owner: "owner",
+ FullName: "owner/repo",
+ CloneURL: "https://github-user:github-token@github.com/owner/repo.git",
+ SanitizedCloneURL: Repo.GetCloneURL(),
+ Name: "repo",
+ VCSHost: models.VCSHost{
+ Hostname: "github.com",
+ Type: models.Github,
+ },
+ }
+ Equals(t, expBaseRepo, actBaseRepo)
+ Equals(t, expBaseRepo, actHeadRepo)
Equals(t, models.PullRequest{
URL: Pull.GetHTMLURL(),
Author: Pull.User.GetLogin(),
@@ -137,18 +143,61 @@ func TestParseGithubPull(t *testing.T) {
HeadCommit: Pull.Head.GetSHA(),
Num: Pull.GetNumber(),
State: models.Open,
- BaseRepo: models.Repo{
- Owner: "owner",
- FullName: "owner/repo",
- CloneURL: "https://github-user:github-token@github.com/owner/repo.git",
- SanitizedCloneURL: Repo.GetCloneURL(),
- Name: "repo",
- VCSHost: models.VCSHost{
- Hostname: "github.com",
- Type: models.Github,
- },
+ BaseRepo: expBaseRepo,
+ }, actPull)
+ Equals(t, models.User{Username: "user"}, actUser)
+}
+
+func TestParseGithubPull(t *testing.T) {
+ testPull := deepcopy.Copy(Pull).(github.PullRequest)
+ testPull.Head.SHA = nil
+ _, _, _, err := parser.ParseGithubPull(&testPull)
+ ErrEquals(t, "head.sha is null", err)
+
+ testPull = deepcopy.Copy(Pull).(github.PullRequest)
+ testPull.HTMLURL = nil
+ _, _, _, err = parser.ParseGithubPull(&testPull)
+ ErrEquals(t, "html_url is null", err)
+
+ testPull = deepcopy.Copy(Pull).(github.PullRequest)
+ testPull.Head.Ref = nil
+ _, _, _, err = parser.ParseGithubPull(&testPull)
+ ErrEquals(t, "head.ref is null", err)
+
+ testPull = deepcopy.Copy(Pull).(github.PullRequest)
+ testPull.User.Login = nil
+ _, _, _, err = parser.ParseGithubPull(&testPull)
+ ErrEquals(t, "user.login is null", err)
+
+ testPull = deepcopy.Copy(Pull).(github.PullRequest)
+ testPull.Number = nil
+ _, _, _, err = parser.ParseGithubPull(&testPull)
+ ErrEquals(t, "number is null", err)
+
+ pullRes, actBaseRepo, actHeadRepo, err := parser.ParseGithubPull(&Pull)
+ Ok(t, err)
+ expBaseRepo := models.Repo{
+ Owner: "owner",
+ FullName: "owner/repo",
+ CloneURL: "https://github-user:github-token@github.com/owner/repo.git",
+ SanitizedCloneURL: Repo.GetCloneURL(),
+ Name: "repo",
+ VCSHost: models.VCSHost{
+ Hostname: "github.com",
+ Type: models.Github,
},
+ }
+ Equals(t, models.PullRequest{
+ URL: Pull.GetHTMLURL(),
+ Author: Pull.User.GetLogin(),
+ Branch: Pull.Head.GetRef(),
+ HeadCommit: Pull.Head.GetSHA(),
+ Num: Pull.GetNumber(),
+ State: models.Open,
+ BaseRepo: expBaseRepo,
}, pullRes)
+ Equals(t, expBaseRepo, actBaseRepo)
+ Equals(t, expBaseRepo, actHeadRepo)
}
func TestParseGitlabMergeEvent(t *testing.T) {
@@ -156,10 +205,10 @@ func TestParseGitlabMergeEvent(t *testing.T) {
var event *gitlab.MergeEvent
err := json.Unmarshal([]byte(mergeEventJSON), &event)
Ok(t, err)
- pull, repo, _, err := parser.ParseGitlabMergeEvent(*event)
+ pull, actBaseRepo, actHeadRepo, actUser, err := parser.ParseGitlabMergeEvent(*event)
Ok(t, err)
- expRepo := models.Repo{
+ expBaseRepo := models.Repo{
FullName: "gitlabhq/gitlab-test",
Name: "gitlab-test",
SanitizedCloneURL: "https://example.com/gitlabhq/gitlab-test.git",
@@ -178,14 +227,26 @@ func TestParseGitlabMergeEvent(t *testing.T) {
HeadCommit: "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
Branch: "ms-viewport",
State: models.Open,
- BaseRepo: expRepo,
+ BaseRepo: expBaseRepo,
}, pull)
- Equals(t, expRepo, repo)
+ Equals(t, expBaseRepo, actBaseRepo)
+ Equals(t, models.Repo{
+ FullName: "awesome_space/awesome_project",
+ Name: "awesome_project",
+ SanitizedCloneURL: "http://example.com/awesome_space/awesome_project.git",
+ Owner: "awesome_space",
+ CloneURL: "http://gitlab-user:gitlab-token@example.com/awesome_space/awesome_project.git",
+ VCSHost: models.VCSHost{
+ Hostname: "example.com",
+ Type: models.Gitlab,
+ },
+ }, actHeadRepo)
+ Equals(t, models.User{Username: "root"}, actUser)
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)
}
@@ -283,20 +344,20 @@ func TestNewCommand_CleansDir(t *testing.T) {
for _, c := range cases {
t.Run(c.Dir, func(t *testing.T) {
- cmd := events.NewCommand(c.Dir, nil, events.Plan, false, "workspace", "", false)
+ cmd := events.NewCommand(c.Dir, nil, events.Plan, false, "workspace", "")
Equals(t, c.ExpDir, cmd.Dir)
})
}
}
func TestNewCommand_EmptyWorkspace(t *testing.T) {
- cmd := events.NewCommand("dir", nil, events.Plan, false, "", "", false)
+ cmd := events.NewCommand("dir", nil, events.Plan, false, "", "")
Equals(t, "default", cmd.Workspace)
}
func TestNewCommand_AllFieldsSet(t *testing.T) {
- cmd := events.NewCommand("dir", []string{"a", "b"}, events.Plan, true, "workspace", "project", false)
- Equals(t, events.Command{
+ cmd := events.NewCommand("dir", []string{"a", "b"}, events.Plan, true, "workspace", "project")
+ Equals(t, events.CommentCommand{
Workspace: "workspace",
Dir: "dir",
Verbose: true,
@@ -306,6 +367,52 @@ func TestNewCommand_AllFieldsSet(t *testing.T) {
}, *cmd)
}
+func TestAutoplanCommand_CommandName(t *testing.T) {
+ Equals(t, events.Plan, (events.AutoplanCommand{}).CommandName())
+}
+
+func TestAutoplanCommand_IsVerbose(t *testing.T) {
+ Equals(t, false, (events.AutoplanCommand{}).IsVerbose())
+}
+
+func TestAutoplanCommand_IsAutoplan(t *testing.T) {
+ Equals(t, true, (events.AutoplanCommand{}).IsAutoplan())
+}
+
+func TestCommentCommand_CommandName(t *testing.T) {
+ Equals(t, events.Plan, (events.CommentCommand{
+ Name: events.Plan,
+ }).CommandName())
+ Equals(t, events.Apply, (events.CommentCommand{
+ Name: events.Apply,
+ }).CommandName())
+}
+
+func TestCommentCommand_IsVerbose(t *testing.T) {
+ Equals(t, false, (events.CommentCommand{
+ Verbose: false,
+ }).IsVerbose())
+ Equals(t, true, (events.CommentCommand{
+ Verbose: true,
+ }).IsVerbose())
+}
+
+func TestCommentCommand_IsAutoplan(t *testing.T) {
+ Equals(t, false, (events.CommentCommand{}).IsAutoplan())
+}
+
+func TestCommentCommand_String(t *testing.T) {
+ exp := `command="plan" verbose=true dir="mydir" workspace="myworkspace" project="myproject" flags="flag1,flag2"`
+ Equals(t, exp, (events.CommentCommand{
+ Dir: "mydir",
+ Flags: []string{"flag1", "flag2"},
+ Name: events.Plan,
+ Verbose: true,
+ Workspace: "myworkspace",
+ ProjectName: "myproject",
+ }).String())
+}
+
var mergeEventJSON = `{
"object_kind": "merge_request",
"user": {
diff --git a/server/events/executor.go b/server/events/executor.go
index 10df87a76..b308e9d02 100644
--- a/server/events/executor.go
+++ b/server/events/executor.go
@@ -18,5 +18,5 @@ package events
// Executor is the generic interface implemented by each command type:
// help, plan, and apply.
type Executor interface {
- Execute(ctx *CommandContext) CommandResponse
+ Execute(ctx *CommandContext) CommandResult
}
diff --git a/server/events/markdown_renderer.go b/server/events/markdown_renderer.go
index acb0d8679..eb06c4ead 100644
--- a/server/events/markdown_renderer.go
+++ b/server/events/markdown_renderer.go
@@ -58,7 +58,7 @@ type ProjectResultTmplData struct {
// Render formats the data into a markdown string.
// nolint: interfacer
-func (m *MarkdownRenderer) Render(res CommandResponse, cmdName CommandName, log string, verbose bool, autoplan bool) string {
+func (m *MarkdownRenderer) Render(res CommandResult, cmdName CommandName, log string, verbose bool, autoplan bool) string {
commandStr := strings.Title(cmdName.String())
common := CommonData{commandStr, verbose, log}
if res.Error != nil {
@@ -73,10 +73,10 @@ func (m *MarkdownRenderer) Render(res CommandResponse, cmdName CommandName, log
return m.renderProjectResults(res.ProjectResults, common)
}
-func (m *MarkdownRenderer) renderProjectResults(pathResults []ProjectResult, common CommonData) string {
+func (m *MarkdownRenderer) renderProjectResults(results []ProjectResult, common CommonData) string {
var resultsTmplData []ProjectResultTmplData
- for _, result := range pathResults {
+ for _, result := range results {
resultData := ProjectResultTmplData{
Workspace: result.Workspace,
Dir: result.Path,
diff --git a/server/events/markdown_renderer_test.go b/server/events/markdown_renderer_test.go
index 202097e9b..040c47731 100644
--- a/server/events/markdown_renderer_test.go
+++ b/server/events/markdown_renderer_test.go
@@ -45,18 +45,20 @@ func TestRenderErr(t *testing.T) {
r := events.MarkdownRenderer{}
for _, c := range cases {
- res := events.CommandResponse{
- Error: c.Error,
- }
- for _, verbose := range []bool{true, false} {
- t.Log("testing " + c.Description)
- s := r.Render(res, c.Command, "log", verbose, false)
- if !verbose {
- Equals(t, c.Expected, s)
- } else {
- Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
+ t.Run(c.Description, func(t *testing.T) {
+ res := events.CommandResult{
+ Error: c.Error,
}
- }
+ for _, verbose := range []bool{true, false} {
+ t.Log("testing " + c.Description)
+ s := r.Render(res, c.Command, "log", verbose, false)
+ if !verbose {
+ Equals(t, c.Expected, s)
+ } else {
+ Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
+ }
+ }
+ })
}
}
@@ -83,25 +85,27 @@ func TestRenderFailure(t *testing.T) {
r := events.MarkdownRenderer{}
for _, c := range cases {
- res := events.CommandResponse{
- Failure: c.Failure,
- }
- for _, verbose := range []bool{true, false} {
- t.Log("testing " + c.Description)
- s := r.Render(res, c.Command, "log", verbose, false)
- if !verbose {
- Equals(t, c.Expected, s)
- } else {
- Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
+ t.Run(c.Description, func(t *testing.T) {
+ res := events.CommandResult{
+ Failure: c.Failure,
}
- }
+ for _, verbose := range []bool{true, false} {
+ t.Log("testing " + c.Description)
+ s := r.Render(res, c.Command, "log", verbose, false)
+ if !verbose {
+ Equals(t, c.Expected, s)
+ } else {
+ Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
+ }
+ }
+ })
}
}
func TestRenderErrAndFailure(t *testing.T) {
t.Log("if there is an error and a failure, the error should be printed")
r := events.MarkdownRenderer{}
- res := events.CommandResponse{
+ res := events.CommandResult{
Error: errors.New("error"),
Failure: "failure",
}
@@ -109,6 +113,15 @@ func TestRenderErrAndFailure(t *testing.T) {
Equals(t, "**Plan Error**\n```\nerror\n```\n\n", s)
}
+func TestRenderAutoplanNoResults(t *testing.T) {
+ // If there are no project results during an autoplan we should still comment
+ // back because the user might expect some output.
+ r := events.MarkdownRenderer{}
+ res := events.CommandResult{}
+ s := r.Render(res, events.Plan, "", false, true)
+ Equals(t, "Ran `plan` in 0 projects because Atlantis detected no Terraform changes or could not determine where to run `plan`.\n\n", s)
+}
+
func TestRenderProjectResults(t *testing.T) {
cases := []struct {
Description string
@@ -121,9 +134,11 @@ func TestRenderProjectResults(t *testing.T) {
events.Plan,
[]events.ProjectResult{
{
- PlanSuccess: &events.PlanSuccess{
- TerraformOutput: "terraform-output",
- LockURL: "lock-url",
+ ProjectCommandResult: events.ProjectCommandResult{
+ PlanSuccess: &events.PlanSuccess{
+ TerraformOutput: "terraform-output",
+ LockURL: "lock-url",
+ },
},
Workspace: "workspace",
Path: "path",
@@ -136,9 +151,11 @@ func TestRenderProjectResults(t *testing.T) {
events.Apply,
[]events.ProjectResult{
{
- ApplySuccess: "success",
- Workspace: "workspace",
- Path: "path",
+ ProjectCommandResult: events.ProjectCommandResult{
+ ApplySuccess: "success",
+ },
+ Workspace: "workspace",
+ Path: "path",
},
},
"Ran Apply in dir: `path` workspace: `workspace`\n```diff\nsuccess\n```\n\n",
@@ -150,17 +167,21 @@ func TestRenderProjectResults(t *testing.T) {
{
Workspace: "workspace",
Path: "path",
- PlanSuccess: &events.PlanSuccess{
- TerraformOutput: "terraform-output",
- LockURL: "lock-url",
+ ProjectCommandResult: events.ProjectCommandResult{
+ PlanSuccess: &events.PlanSuccess{
+ TerraformOutput: "terraform-output",
+ LockURL: "lock-url",
+ },
},
},
{
Workspace: "workspace",
Path: "path2",
- PlanSuccess: &events.PlanSuccess{
- TerraformOutput: "terraform-output2",
- LockURL: "lock-url2",
+ ProjectCommandResult: events.ProjectCommandResult{
+ PlanSuccess: &events.PlanSuccess{
+ TerraformOutput: "terraform-output2",
+ LockURL: "lock-url2",
+ },
},
},
},
@@ -171,14 +192,18 @@ func TestRenderProjectResults(t *testing.T) {
events.Apply,
[]events.ProjectResult{
{
- Path: "path",
- Workspace: "workspace",
- ApplySuccess: "success",
+ Path: "path",
+ Workspace: "workspace",
+ ProjectCommandResult: events.ProjectCommandResult{
+ ApplySuccess: "success",
+ },
},
{
- Path: "path2",
- Workspace: "workspace",
- ApplySuccess: "success2",
+ Path: "path2",
+ Workspace: "workspace",
+ ProjectCommandResult: events.ProjectCommandResult{
+ ApplySuccess: "success2",
+ },
},
},
"Ran Apply for 2 projects:\n1. workspace: `workspace` path: `path`\n1. workspace: `workspace` path: `path2`\n\n### 1. workspace: `workspace` path: `path`\n```diff\nsuccess\n```\n---\n### 2. workspace: `workspace` path: `path2`\n```diff\nsuccess2\n```\n---\n\n",
@@ -188,7 +213,9 @@ func TestRenderProjectResults(t *testing.T) {
events.Plan,
[]events.ProjectResult{
{
- Error: errors.New("error"),
+ ProjectCommandResult: events.ProjectCommandResult{
+ Error: errors.New("error"),
+ },
Path: "path",
Workspace: "workspace",
},
@@ -202,7 +229,9 @@ func TestRenderProjectResults(t *testing.T) {
{
Path: "path",
Workspace: "workspace",
- Failure: "failure",
+ ProjectCommandResult: events.ProjectCommandResult{
+ Failure: "failure",
+ },
},
},
"Ran Plan in dir: `path` workspace: `workspace`\n**Plan Failed**: failure\n\n\n",
@@ -214,20 +243,26 @@ func TestRenderProjectResults(t *testing.T) {
{
Workspace: "workspace",
Path: "path",
- PlanSuccess: &events.PlanSuccess{
- TerraformOutput: "terraform-output",
- LockURL: "lock-url",
+ ProjectCommandResult: events.ProjectCommandResult{
+ PlanSuccess: &events.PlanSuccess{
+ TerraformOutput: "terraform-output",
+ LockURL: "lock-url",
+ },
},
},
{
Workspace: "workspace",
Path: "path2",
- Failure: "failure",
+ ProjectCommandResult: events.ProjectCommandResult{
+ Failure: "failure",
+ },
},
{
Workspace: "workspace",
Path: "path3",
- Error: errors.New("error"),
+ ProjectCommandResult: events.ProjectCommandResult{
+ Error: errors.New("error"),
+ },
},
},
"Ran Plan for 3 projects:\n1. workspace: `workspace` path: `path`\n1. workspace: `workspace` path: `path2`\n1. workspace: `workspace` path: `path3`\n\n### 1. workspace: `workspace` path: `path`\n```diff\nterraform-output\n```\n\n* To **discard** this plan click [here](lock-url).\n---\n### 2. workspace: `workspace` path: `path2`\n**Plan Failed**: failure\n\n---\n### 3. workspace: `workspace` path: `path3`\n**Plan Error**\n```\nerror\n```\n\n---\n\n",
@@ -237,19 +272,25 @@ func TestRenderProjectResults(t *testing.T) {
events.Apply,
[]events.ProjectResult{
{
- Workspace: "workspace",
- Path: "path",
- ApplySuccess: "success",
+ Workspace: "workspace",
+ Path: "path",
+ ProjectCommandResult: events.ProjectCommandResult{
+ ApplySuccess: "success",
+ },
},
{
Workspace: "workspace",
Path: "path2",
- Failure: "failure",
+ ProjectCommandResult: events.ProjectCommandResult{
+ Failure: "failure",
+ },
},
{
Workspace: "workspace",
Path: "path3",
- Error: errors.New("error"),
+ ProjectCommandResult: events.ProjectCommandResult{
+ Error: errors.New("error"),
+ },
},
},
"Ran Apply for 3 projects:\n1. workspace: `workspace` path: `path`\n1. workspace: `workspace` path: `path2`\n1. workspace: `workspace` path: `path3`\n\n### 1. workspace: `workspace` path: `path`\n```diff\nsuccess\n```\n---\n### 2. workspace: `workspace` path: `path2`\n**Apply Failed**: failure\n\n---\n### 3. workspace: `workspace` path: `path3`\n**Apply Error**\n```\nerror\n```\n\n---\n\n",
@@ -258,18 +299,20 @@ func TestRenderProjectResults(t *testing.T) {
r := events.MarkdownRenderer{}
for _, c := range cases {
- res := events.CommandResponse{
- ProjectResults: c.ProjectResults,
- }
- for _, verbose := range []bool{true, false} {
- t.Run(c.Description, func(t *testing.T) {
- s := r.Render(res, c.Command, "log", verbose, false)
- if !verbose {
- Equals(t, c.Expected, s)
- } else {
- Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
- }
- })
- }
+ t.Run(c.Description, func(t *testing.T) {
+ res := events.CommandResult{
+ ProjectResults: c.ProjectResults,
+ }
+ for _, verbose := range []bool{true, false} {
+ t.Run(c.Description, func(t *testing.T) {
+ s := r.Render(res, c.Command, "log", verbose, false)
+ if !verbose {
+ Equals(t, c.Expected, s)
+ } else {
+ Equals(t, c.Expected+"Log
\n \n\n```\nlog```\n
\n", s)
+ }
+ })
+ }
+ })
}
}
diff --git a/server/events/mocks/matchers/events_commandresponse.go b/server/events/mocks/matchers/events_commandname.go
similarity index 53%
rename from server/events/mocks/matchers/events_commandresponse.go
rename to server/events/mocks/matchers/events_commandname.go
index f596b2c4d..448c937ab 100644
--- a/server/events/mocks/matchers/events_commandresponse.go
+++ b/server/events/mocks/matchers/events_commandname.go
@@ -7,14 +7,14 @@ import (
events "github.com/runatlantis/atlantis/server/events"
)
-func AnyEventsCommandResponse() events.CommandResponse {
- pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(events.CommandResponse))(nil)).Elem()))
- var nullValue events.CommandResponse
+func AnyEventsCommandName() events.CommandName {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(events.CommandName))(nil)).Elem()))
+ var nullValue events.CommandName
return nullValue
}
-func EqEventsCommandResponse(value events.CommandResponse) events.CommandResponse {
+func EqEventsCommandName(value events.CommandName) events.CommandName {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
- var nullValue events.CommandResponse
+ var nullValue events.CommandName
return nullValue
}
diff --git a/server/events/mocks/matchers/events_commandresult.go b/server/events/mocks/matchers/events_commandresult.go
new file mode 100644
index 000000000..54269ef12
--- /dev/null
+++ b/server/events/mocks/matchers/events_commandresult.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ events "github.com/runatlantis/atlantis/server/events"
+)
+
+func AnyEventsCommandResult() events.CommandResult {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(events.CommandResult))(nil)).Elem()))
+ var nullValue events.CommandResult
+ return nullValue
+}
+
+func EqEventsCommandResult(value events.CommandResult) events.CommandResult {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue events.CommandResult
+ return nullValue
+}
diff --git a/server/events/mocks/matchers/models_projectcommandcontext.go b/server/events/mocks/matchers/models_projectcommandcontext.go
new file mode 100644
index 000000000..3f76b9a22
--- /dev/null
+++ b/server/events/mocks/matchers/models_projectcommandcontext.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ models "github.com/runatlantis/atlantis/server/events/models"
+)
+
+func AnyModelsProjectCommandContext() models.ProjectCommandContext {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.ProjectCommandContext))(nil)).Elem()))
+ var nullValue models.ProjectCommandContext
+ return nullValue
+}
+
+func EqModelsProjectCommandContext(value models.ProjectCommandContext) models.ProjectCommandContext {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue models.ProjectCommandContext
+ return nullValue
+}
diff --git a/server/events/mocks/matchers/ptr_to_events_command.go b/server/events/mocks/matchers/ptr_to_events_command.go
index 91edd3b66..771aacf30 100644
--- a/server/events/mocks/matchers/ptr_to_events_command.go
+++ b/server/events/mocks/matchers/ptr_to_events_command.go
@@ -7,14 +7,14 @@ import (
events "github.com/runatlantis/atlantis/server/events"
)
-func AnyPtrToEventsCommand() *events.Command {
- pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*events.Command))(nil)).Elem()))
- var nullValue *events.Command
+func AnyPtrToEventsCommand() *events.CommentCommand {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*events.CommentCommand))(nil)).Elem()))
+ var nullValue *events.CommentCommand
return nullValue
}
-func EqPtrToEventsCommand(value *events.Command) *events.Command {
+func EqPtrToEventsCommand(value *events.CommentCommand) *events.CommentCommand {
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
- var nullValue *events.Command
+ var nullValue *events.CommentCommand
return nullValue
}
diff --git a/server/events/mocks/matchers/ptr_to_events_commentcommand.go b/server/events/mocks/matchers/ptr_to_events_commentcommand.go
new file mode 100644
index 000000000..fbbbfcc15
--- /dev/null
+++ b/server/events/mocks/matchers/ptr_to_events_commentcommand.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ events "github.com/runatlantis/atlantis/server/events"
+)
+
+func AnyPtrToEventsCommentCommand() *events.CommentCommand {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*events.CommentCommand))(nil)).Elem()))
+ var nullValue *events.CommentCommand
+ return nullValue
+}
+
+func EqPtrToEventsCommentCommand(value *events.CommentCommand) *events.CommentCommand {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue *events.CommentCommand
+ return nullValue
+}
diff --git a/server/events/mocks/matchers/ptr_to_github_pullrequestevent.go b/server/events/mocks/matchers/ptr_to_github_pullrequestevent.go
new file mode 100644
index 000000000..1952cf1f7
--- /dev/null
+++ b/server/events/mocks/matchers/ptr_to_github_pullrequestevent.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ github "github.com/google/go-github/github"
+ "github.com/petergtz/pegomock"
+)
+
+func AnyPtrToGithubPullRequestEvent() *github.PullRequestEvent {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*github.PullRequestEvent))(nil)).Elem()))
+ var nullValue *github.PullRequestEvent
+ return nullValue
+}
+
+func EqPtrToGithubPullRequestEvent(value *github.PullRequestEvent) *github.PullRequestEvent {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue *github.PullRequestEvent
+ return nullValue
+}
diff --git a/server/events/mocks/matchers/ptr_to_models_repo.go b/server/events/mocks/matchers/ptr_to_models_repo.go
new file mode 100644
index 000000000..05ba1aef3
--- /dev/null
+++ b/server/events/mocks/matchers/ptr_to_models_repo.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ models "github.com/runatlantis/atlantis/server/events/models"
+)
+
+func AnyPtrToModelsRepo() *models.Repo {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*models.Repo))(nil)).Elem()))
+ var nullValue *models.Repo
+ return nullValue
+}
+
+func EqPtrToModelsRepo(value *models.Repo) *models.Repo {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue *models.Repo
+ return nullValue
+}
diff --git a/server/events/mocks/matchers/slice_of_models_projectcommandcontext.go b/server/events/mocks/matchers/slice_of_models_projectcommandcontext.go
new file mode 100644
index 000000000..08974c59c
--- /dev/null
+++ b/server/events/mocks/matchers/slice_of_models_projectcommandcontext.go
@@ -0,0 +1,20 @@
+package matchers
+
+import (
+ "reflect"
+
+ "github.com/petergtz/pegomock"
+ models "github.com/runatlantis/atlantis/server/events/models"
+)
+
+func AnySliceOfModelsProjectCommandContext() []models.ProjectCommandContext {
+ pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*([]models.ProjectCommandContext))(nil)).Elem()))
+ var nullValue []models.ProjectCommandContext
+ return nullValue
+}
+
+func EqSliceOfModelsProjectCommandContext(value []models.ProjectCommandContext) []models.ProjectCommandContext {
+ pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
+ var nullValue []models.ProjectCommandContext
+ return nullValue
+}
diff --git a/server/events/mocks/mock_command_runner.go b/server/events/mocks/mock_command_runner.go
index 481fcf72f..8ef3387c0 100644
--- a/server/events/mocks/mock_command_runner.go
+++ b/server/events/mocks/mock_command_runner.go
@@ -19,9 +19,14 @@ func NewMockCommandRunner() *MockCommandRunner {
return &MockCommandRunner{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockCommandRunner) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *events.Command) {
- params := []pegomock.Param{baseRepo, headRepo, user, pullNum, cmd}
- pegomock.GetGenericMockFrom(mock).Invoke("ExecuteCommand", params, []reflect.Type{})
+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}
+ pegomock.GetGenericMockFrom(mock).Invoke("RunCommentCommand", params, []reflect.Type{})
+}
+
+func (mock *MockCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User) {
+ params := []pegomock.Param{baseRepo, headRepo, pull, user}
+ pegomock.GetGenericMockFrom(mock).Invoke("RunAutoplanCommand", params, []reflect.Type{})
}
func (mock *MockCommandRunner) VerifyWasCalledOnce() *VerifierCommandRunner {
@@ -42,23 +47,66 @@ type VerifierCommandRunner struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierCommandRunner) ExecuteCommand(baseRepo models.Repo, headRepo models.Repo, user models.User, pullNum int, cmd *events.Command) *CommandRunner_ExecuteCommand_OngoingVerification {
- params := []pegomock.Param{baseRepo, headRepo, user, pullNum, cmd}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ExecuteCommand", params)
- return &CommandRunner_ExecuteCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+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}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunCommentCommand", params)
+ return &CommandRunner_RunCommentCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
-type CommandRunner_ExecuteCommand_OngoingVerification struct {
+type CommandRunner_RunCommentCommand_OngoingVerification struct {
mock *MockCommandRunner
methodInvocations []pegomock.MethodInvocation
}
-func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetCapturedArguments() (models.Repo, models.Repo, models.User, int, *events.Command) {
- baseRepo, headRepo, user, pullNum, cmd := c.GetAllCapturedArguments()
- return baseRepo[len(baseRepo)-1], headRepo[len(headRepo)-1], user[len(user)-1], pullNum[len(pullNum)-1], cmd[len(cmd)-1]
+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_ExecuteCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.Repo, _param2 []models.User, _param3 []int, _param4 []*events.Command) {
+func (c *CommandRunner_RunCommentCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []*models.Repo, _param2 []models.User, _param3 []int, _param4 []*events.CommentCommand) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]models.Repo, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(models.Repo)
+ }
+ _param1 = make([]*models.Repo, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(*models.Repo)
+ }
+ _param2 = make([]models.User, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(models.User)
+ }
+ _param3 = make([]int, len(params[3]))
+ for u, param := range params[3] {
+ _param3[u] = param.(int)
+ }
+ _param4 = make([]*events.CommentCommand, len(params[4]))
+ for u, param := range params[4] {
+ _param4[u] = param.(*events.CommentCommand)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User) *CommandRunner_RunAutoplanCommand_OngoingVerification {
+ params := []pegomock.Param{baseRepo, headRepo, pull, user}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RunAutoplanCommand", params)
+ return &CommandRunner_RunAutoplanCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type CommandRunner_RunAutoplanCommand_OngoingVerification struct {
+ mock *MockCommandRunner
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *CommandRunner_RunAutoplanCommand_OngoingVerification) GetCapturedArguments() (models.Repo, models.Repo, models.PullRequest, models.User) {
+ baseRepo, headRepo, pull, user := c.GetAllCapturedArguments()
+ return baseRepo[len(baseRepo)-1], headRepo[len(headRepo)-1], pull[len(pull)-1], user[len(user)-1]
+}
+
+func (c *CommandRunner_RunAutoplanCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.Repo, _param2 []models.PullRequest, _param3 []models.User) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -69,17 +117,13 @@ func (c *CommandRunner_ExecuteCommand_OngoingVerification) GetAllCapturedArgumen
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)
- }
- _param4 = make([]*events.Command, len(params[4]))
- for u, param := range params[4] {
- _param4[u] = param.(*events.Command)
+ _param3[u] = param.(models.User)
}
}
return
diff --git a/server/events/mocks/mock_commit_status_updater.go b/server/events/mocks/mock_commit_status_updater.go
index 3ed9e933b..e5815d414 100644
--- a/server/events/mocks/mock_commit_status_updater.go
+++ b/server/events/mocks/mock_commit_status_updater.go
@@ -20,8 +20,8 @@ func NewMockCommitStatusUpdater() *MockCommitStatusUpdater {
return &MockCommitStatusUpdater{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *events.Command) error {
- params := []pegomock.Param{repo, pull, status, cmd}
+func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.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
if len(result) != 0 {
@@ -32,8 +32,8 @@ func (mock *MockCommitStatusUpdater) Update(repo models.Repo, pull models.PullRe
return ret0
}
-func (mock *MockCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) error {
- params := []pegomock.Param{ctx, res}
+func (mock *MockCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, commandName events.CommandName, res events.CommandResult) error {
+ params := []pegomock.Param{ctx, commandName, res}
result := pegomock.GetGenericMockFrom(mock).Invoke("UpdateProjectResult", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
var ret0 error
if len(result) != 0 {
@@ -62,8 +62,8 @@ type VerifierCommitStatusUpdater struct {
inOrderContext *pegomock.InOrderContext
}
-func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.CommitStatus, cmd *events.Command) *CommitStatusUpdater_Update_OngoingVerification {
- params := []pegomock.Param{repo, pull, status, cmd}
+func (verifier *VerifierCommitStatusUpdater) Update(repo models.Repo, pull models.PullRequest, status vcs.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 +73,12 @@ type CommitStatusUpdater_Update_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.CommitStatus, *events.Command) {
- repo, pull, status, cmd := c.GetAllCapturedArguments()
- return repo[len(repo)-1], pull[len(pull)-1], status[len(status)-1], cmd[len(cmd)-1]
+func (c *CommitStatusUpdater_Update_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest, vcs.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.Command) {
+func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest, _param2 []vcs.CommitStatus, _param3 []events.CommandName) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]models.Repo, len(params[0]))
@@ -93,16 +93,16 @@ func (c *CommitStatusUpdater_Update_OngoingVerification) GetAllCapturedArguments
for u, param := range params[2] {
_param2[u] = param.(vcs.CommitStatus)
}
- _param3 = make([]*events.Command, len(params[3]))
+ _param3 = make([]events.CommandName, len(params[3]))
for u, param := range params[3] {
- _param3[u] = param.(*events.Command)
+ _param3[u] = param.(events.CommandName)
}
}
return
}
-func (verifier *VerifierCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, res events.CommandResponse) *CommitStatusUpdater_UpdateProjectResult_OngoingVerification {
- params := []pegomock.Param{ctx, res}
+func (verifier *VerifierCommitStatusUpdater) UpdateProjectResult(ctx *events.CommandContext, commandName events.CommandName, res events.CommandResult) *CommitStatusUpdater_UpdateProjectResult_OngoingVerification {
+ params := []pegomock.Param{ctx, commandName, res}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UpdateProjectResult", params)
return &CommitStatusUpdater_UpdateProjectResult_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
@@ -112,21 +112,25 @@ type CommitStatusUpdater_UpdateProjectResult_OngoingVerification struct {
methodInvocations []pegomock.MethodInvocation
}
-func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetCapturedArguments() (*events.CommandContext, events.CommandResponse) {
- ctx, res := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1], res[len(res)-1]
+func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetCapturedArguments() (*events.CommandContext, events.CommandName, events.CommandResult) {
+ ctx, commandName, res := c.GetAllCapturedArguments()
+ return ctx[len(ctx)-1], commandName[len(commandName)-1], res[len(res)-1]
}
-func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []events.CommandResponse) {
+func (c *CommitStatusUpdater_UpdateProjectResult_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []events.CommandName, _param2 []events.CommandResult) {
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
if len(params) > 0 {
_param0 = make([]*events.CommandContext, len(params[0]))
for u, param := range params[0] {
_param0[u] = param.(*events.CommandContext)
}
- _param1 = make([]events.CommandResponse, len(params[1]))
+ _param1 = make([]events.CommandName, len(params[1]))
for u, param := range params[1] {
- _param1[u] = param.(events.CommandResponse)
+ _param1[u] = param.(events.CommandName)
+ }
+ _param2 = make([]events.CommandResult, len(params[2]))
+ for u, param := range params[2] {
+ _param2[u] = param.(events.CommandResult)
}
}
return
diff --git a/server/events/mocks/mock_event_parsing.go b/server/events/mocks/mock_event_parsing.go
index 007f823a4..1c1feaf31 100644
--- a/server/events/mocks/mock_event_parsing.go
+++ b/server/events/mocks/mock_event_parsing.go
@@ -44,45 +44,9 @@ func (mock *MockEventParsing) ParseGithubIssueCommentEvent(comment *github.Issue
return ret0, ret1, ret2, ret3
}
-func (mock *MockEventParsing) ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, error) {
+func (mock *MockEventParsing) ParseGithubPull(pull *github.PullRequest) (models.PullRequest, models.Repo, models.Repo, error) {
params := []pegomock.Param{pull}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubPull", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
- var ret0 models.PullRequest
- var ret1 models.Repo
- var ret2 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].(error)
- }
- }
- return ret0, ret1, ret2
-}
-
-func (mock *MockEventParsing) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
- params := []pegomock.Param{ghRepo}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubRepo", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
- var ret0 models.Repo
- var ret1 error
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(models.Repo)
- }
- if result[1] != nil {
- ret1 = result[1].(error)
- }
- }
- return ret0, ret1
-}
-
-func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) (models.PullRequest, models.Repo, models.Repo, 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((*error)(nil)).Elem()})
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubPull", params, []reflect.Type{reflect.TypeOf((*models.PullRequest)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 models.PullRequest
var ret1 models.Repo
var ret2 models.Repo
@@ -104,6 +68,78 @@ func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent)
return ret0, ret1, ret2, ret3
}
+func (mock *MockEventParsing) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) (models.PullRequest, 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()})
+ 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) ParseGithubRepo(ghRepo *github.Repository) (models.Repo, error) {
+ params := []pegomock.Param{ghRepo}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGithubRepo", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 models.Repo
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.Repo)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockEventParsing) ParseGitlabMergeEvent(event go_gitlab.MergeEvent) (models.PullRequest, 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()})
+ 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) ParseGitlabMergeCommentEvent(event go_gitlab.MergeCommentEvent) (models.Repo, models.Repo, models.User, error) {
params := []pegomock.Param{event}
result := pegomock.GetGenericMockFrom(mock).Invoke("ParseGitlabMergeCommentEvent", params, []reflect.Type{reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.Repo)(nil)).Elem(), reflect.TypeOf((*models.User)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
@@ -212,6 +248,33 @@ func (c *EventParsing_ParseGithubPull_OngoingVerification) GetAllCapturedArgumen
return
}
+func (verifier *VerifierEventParsing) ParseGithubPullEvent(pullEvent *github.PullRequestEvent) *EventParsing_ParseGithubPullEvent_OngoingVerification {
+ params := []pegomock.Param{pullEvent}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGithubPullEvent", params)
+ return &EventParsing_ParseGithubPullEvent_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type EventParsing_ParseGithubPullEvent_OngoingVerification struct {
+ mock *MockEventParsing
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *EventParsing_ParseGithubPullEvent_OngoingVerification) GetCapturedArguments() *github.PullRequestEvent {
+ pullEvent := c.GetAllCapturedArguments()
+ return pullEvent[len(pullEvent)-1]
+}
+
+func (c *EventParsing_ParseGithubPullEvent_OngoingVerification) GetAllCapturedArguments() (_param0 []*github.PullRequestEvent) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*github.PullRequestEvent, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*github.PullRequestEvent)
+ }
+ }
+ return
+}
+
func (verifier *VerifierEventParsing) ParseGithubRepo(ghRepo *github.Repository) *EventParsing_ParseGithubRepo_OngoingVerification {
params := []pegomock.Param{ghRepo}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ParseGithubRepo", params)
diff --git a/server/events/mocks/mock_executor.go b/server/events/mocks/mock_executor.go
index f3c9ce749..208f43050 100644
--- a/server/events/mocks/mock_executor.go
+++ b/server/events/mocks/mock_executor.go
@@ -18,13 +18,13 @@ func NewMockExecutor() *MockExecutor {
return &MockExecutor{fail: pegomock.GlobalFailHandler}
}
-func (mock *MockExecutor) Execute(ctx *events.CommandContext) events.CommandResponse {
+func (mock *MockExecutor) Execute(ctx *events.CommandContext) events.CommandResult {
params := []pegomock.Param{ctx}
- result := pegomock.GetGenericMockFrom(mock).Invoke("Execute", params, []reflect.Type{reflect.TypeOf((*events.CommandResponse)(nil)).Elem()})
- var ret0 events.CommandResponse
+ result := pegomock.GetGenericMockFrom(mock).Invoke("Execute", params, []reflect.Type{reflect.TypeOf((*events.CommandResult)(nil)).Elem()})
+ var ret0 events.CommandResult
if len(result) != 0 {
if result[0] != nil {
- ret0 = result[0].(events.CommandResponse)
+ ret0 = result[0].(events.CommandResult)
}
}
return ret0
diff --git a/server/events/mocks/mock_project_command_builder.go b/server/events/mocks/mock_project_command_builder.go
new file mode 100644
index 000000000..f779acab3
--- /dev/null
+++ b/server/events/mocks/mock_project_command_builder.go
@@ -0,0 +1,175 @@
+// Automatically generated by pegomock. DO NOT EDIT!
+// Source: github.com/runatlantis/atlantis/server/events (interfaces: ProjectCommandBuilder)
+
+package mocks
+
+import (
+ "reflect"
+
+ pegomock "github.com/petergtz/pegomock"
+ events "github.com/runatlantis/atlantis/server/events"
+ models "github.com/runatlantis/atlantis/server/events/models"
+)
+
+type MockProjectCommandBuilder struct {
+ fail func(message string, callerSkip ...int)
+}
+
+func NewMockProjectCommandBuilder() *MockProjectCommandBuilder {
+ return &MockProjectCommandBuilder{fail: pegomock.GlobalFailHandler}
+}
+
+func (mock *MockProjectCommandBuilder) BuildAutoplanCommands(ctx *events.CommandContext) ([]models.ProjectCommandContext, error) {
+ params := []pegomock.Param{ctx}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("BuildAutoplanCommands", params, []reflect.Type{reflect.TypeOf((*[]models.ProjectCommandContext)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 []models.ProjectCommandContext
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].([]models.ProjectCommandContext)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockProjectCommandBuilder) BuildPlanCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) (models.ProjectCommandContext, error) {
+ params := []pegomock.Param{ctx, commentCommand}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("BuildPlanCommand", params, []reflect.Type{reflect.TypeOf((*models.ProjectCommandContext)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 models.ProjectCommandContext
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.ProjectCommandContext)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockProjectCommandBuilder) BuildApplyCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) (models.ProjectCommandContext, error) {
+ params := []pegomock.Param{ctx, commentCommand}
+ result := pegomock.GetGenericMockFrom(mock).Invoke("BuildApplyCommand", params, []reflect.Type{reflect.TypeOf((*models.ProjectCommandContext)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
+ var ret0 models.ProjectCommandContext
+ var ret1 error
+ if len(result) != 0 {
+ if result[0] != nil {
+ ret0 = result[0].(models.ProjectCommandContext)
+ }
+ if result[1] != nil {
+ ret1 = result[1].(error)
+ }
+ }
+ return ret0, ret1
+}
+
+func (mock *MockProjectCommandBuilder) VerifyWasCalledOnce() *VerifierProjectCommandBuilder {
+ return &VerifierProjectCommandBuilder{mock, pegomock.Times(1), nil}
+}
+
+func (mock *MockProjectCommandBuilder) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierProjectCommandBuilder {
+ return &VerifierProjectCommandBuilder{mock, invocationCountMatcher, nil}
+}
+
+func (mock *MockProjectCommandBuilder) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierProjectCommandBuilder {
+ return &VerifierProjectCommandBuilder{mock, invocationCountMatcher, inOrderContext}
+}
+
+type VerifierProjectCommandBuilder struct {
+ mock *MockProjectCommandBuilder
+ invocationCountMatcher pegomock.Matcher
+ inOrderContext *pegomock.InOrderContext
+}
+
+func (verifier *VerifierProjectCommandBuilder) BuildAutoplanCommands(ctx *events.CommandContext) *ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification {
+ params := []pegomock.Param{ctx}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildAutoplanCommands", params)
+ return &ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification struct {
+ mock *MockProjectCommandBuilder
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification) GetCapturedArguments() *events.CommandContext {
+ ctx := c.GetAllCapturedArguments()
+ return ctx[len(ctx)-1]
+}
+
+func (c *ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*events.CommandContext, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*events.CommandContext)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierProjectCommandBuilder) BuildPlanCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification {
+ params := []pegomock.Param{ctx, commentCommand}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildPlanCommand", params)
+ return &ProjectCommandBuilder_BuildPlanCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ProjectCommandBuilder_BuildPlanCommand_OngoingVerification struct {
+ mock *MockProjectCommandBuilder
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification) GetCapturedArguments() (*events.CommandContext, *events.CommentCommand) {
+ ctx, commentCommand := c.GetAllCapturedArguments()
+ return ctx[len(ctx)-1], commentCommand[len(commentCommand)-1]
+}
+
+func (c *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []*events.CommentCommand) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*events.CommandContext, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*events.CommandContext)
+ }
+ _param1 = make([]*events.CommentCommand, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(*events.CommentCommand)
+ }
+ }
+ return
+}
+
+func (verifier *VerifierProjectCommandBuilder) BuildApplyCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildApplyCommand_OngoingVerification {
+ params := []pegomock.Param{ctx, commentCommand}
+ methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildApplyCommand", params)
+ return &ProjectCommandBuilder_BuildApplyCommand_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
+}
+
+type ProjectCommandBuilder_BuildApplyCommand_OngoingVerification struct {
+ mock *MockProjectCommandBuilder
+ methodInvocations []pegomock.MethodInvocation
+}
+
+func (c *ProjectCommandBuilder_BuildApplyCommand_OngoingVerification) GetCapturedArguments() (*events.CommandContext, *events.CommentCommand) {
+ ctx, commentCommand := c.GetAllCapturedArguments()
+ return ctx[len(ctx)-1], commentCommand[len(commentCommand)-1]
+}
+
+func (c *ProjectCommandBuilder_BuildApplyCommand_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext, _param1 []*events.CommentCommand) {
+ params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
+ if len(params) > 0 {
+ _param0 = make([]*events.CommandContext, len(params[0]))
+ for u, param := range params[0] {
+ _param0[u] = param.(*events.CommandContext)
+ }
+ _param1 = make([]*events.CommentCommand, len(params[1]))
+ for u, param := range params[1] {
+ _param1[u] = param.(*events.CommentCommand)
+ }
+ }
+ return
+}
diff --git a/server/events/mocks/mock_pull_request_operator.go b/server/events/mocks/mock_pull_request_operator.go
deleted file mode 100644
index 443636863..000000000
--- a/server/events/mocks/mock_pull_request_operator.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Automatically generated by pegomock. DO NOT EDIT!
-// Source: github.com/runatlantis/atlantis/server/events (interfaces: PullRequestOperator)
-
-package mocks
-
-import (
- "reflect"
-
- pegomock "github.com/petergtz/pegomock"
- events "github.com/runatlantis/atlantis/server/events"
-)
-
-type MockPullRequestOperator struct {
- fail func(message string, callerSkip ...int)
-}
-
-func NewMockPullRequestOperator() *MockPullRequestOperator {
- return &MockPullRequestOperator{fail: pegomock.GlobalFailHandler}
-}
-
-func (mock *MockPullRequestOperator) Autoplan(ctx *events.CommandContext) events.CommandResponse {
- params := []pegomock.Param{ctx}
- result := pegomock.GetGenericMockFrom(mock).Invoke("Autoplan", params, []reflect.Type{reflect.TypeOf((*events.CommandResponse)(nil)).Elem()})
- var ret0 events.CommandResponse
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(events.CommandResponse)
- }
- }
- return ret0
-}
-
-func (mock *MockPullRequestOperator) PlanViaComment(ctx *events.CommandContext) events.CommandResponse {
- params := []pegomock.Param{ctx}
- result := pegomock.GetGenericMockFrom(mock).Invoke("PlanViaComment", params, []reflect.Type{reflect.TypeOf((*events.CommandResponse)(nil)).Elem()})
- var ret0 events.CommandResponse
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(events.CommandResponse)
- }
- }
- return ret0
-}
-
-func (mock *MockPullRequestOperator) ApplyViaComment(ctx *events.CommandContext) events.CommandResponse {
- params := []pegomock.Param{ctx}
- result := pegomock.GetGenericMockFrom(mock).Invoke("ApplyViaComment", params, []reflect.Type{reflect.TypeOf((*events.CommandResponse)(nil)).Elem()})
- var ret0 events.CommandResponse
- if len(result) != 0 {
- if result[0] != nil {
- ret0 = result[0].(events.CommandResponse)
- }
- }
- return ret0
-}
-
-func (mock *MockPullRequestOperator) VerifyWasCalledOnce() *VerifierPullRequestOperator {
- return &VerifierPullRequestOperator{mock, pegomock.Times(1), nil}
-}
-
-func (mock *MockPullRequestOperator) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierPullRequestOperator {
- return &VerifierPullRequestOperator{mock, invocationCountMatcher, nil}
-}
-
-func (mock *MockPullRequestOperator) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierPullRequestOperator {
- return &VerifierPullRequestOperator{mock, invocationCountMatcher, inOrderContext}
-}
-
-type VerifierPullRequestOperator struct {
- mock *MockPullRequestOperator
- invocationCountMatcher pegomock.Matcher
- inOrderContext *pegomock.InOrderContext
-}
-
-func (verifier *VerifierPullRequestOperator) Autoplan(ctx *events.CommandContext) *PullRequestOperator_Autoplan_OngoingVerification {
- params := []pegomock.Param{ctx}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Autoplan", params)
- return &PullRequestOperator_Autoplan_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type PullRequestOperator_Autoplan_OngoingVerification struct {
- mock *MockPullRequestOperator
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *PullRequestOperator_Autoplan_OngoingVerification) GetCapturedArguments() *events.CommandContext {
- ctx := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1]
-}
-
-func (c *PullRequestOperator_Autoplan_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
- }
- }
- return
-}
-
-func (verifier *VerifierPullRequestOperator) PlanViaComment(ctx *events.CommandContext) *PullRequestOperator_PlanViaComment_OngoingVerification {
- params := []pegomock.Param{ctx}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PlanViaComment", params)
- return &PullRequestOperator_PlanViaComment_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type PullRequestOperator_PlanViaComment_OngoingVerification struct {
- mock *MockPullRequestOperator
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *PullRequestOperator_PlanViaComment_OngoingVerification) GetCapturedArguments() *events.CommandContext {
- ctx := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1]
-}
-
-func (c *PullRequestOperator_PlanViaComment_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
- }
- }
- return
-}
-
-func (verifier *VerifierPullRequestOperator) ApplyViaComment(ctx *events.CommandContext) *PullRequestOperator_ApplyViaComment_OngoingVerification {
- params := []pegomock.Param{ctx}
- methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ApplyViaComment", params)
- return &PullRequestOperator_ApplyViaComment_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
-}
-
-type PullRequestOperator_ApplyViaComment_OngoingVerification struct {
- mock *MockPullRequestOperator
- methodInvocations []pegomock.MethodInvocation
-}
-
-func (c *PullRequestOperator_ApplyViaComment_OngoingVerification) GetCapturedArguments() *events.CommandContext {
- ctx := c.GetAllCapturedArguments()
- return ctx[len(ctx)-1]
-}
-
-func (c *PullRequestOperator_ApplyViaComment_OngoingVerification) GetAllCapturedArguments() (_param0 []*events.CommandContext) {
- params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
- if len(params) > 0 {
- _param0 = make([]*events.CommandContext, len(params[0]))
- for u, param := range params[0] {
- _param0[u] = param.(*events.CommandContext)
- }
- }
- return
-}
diff --git a/server/events/models/models_test.go b/server/events/models/models_test.go
index 0dbb4f49f..5883f2193 100644
--- a/server/events/models/models_test.go
+++ b/server/events/models/models_test.go
@@ -92,3 +92,10 @@ func TestNewRepo_HTTPSAuth(t *testing.T) {
Name: "repo",
}, repo)
}
+
+func TestProject_String(t *testing.T) {
+ Equals(t, "repofullname=owner/repo path=my/path", (models.Project{
+ RepoFullName: "owner/repo",
+ Path: "my/path",
+ }).String())
+}
diff --git a/server/events/pull_request_operator.go b/server/events/project_command_builder.go
similarity index 62%
rename from server/events/pull_request_operator.go
rename to server/events/project_command_builder.go
index fabad33d0..90f2324dd 100644
--- a/server/events/pull_request_operator.go
+++ b/server/events/project_command_builder.go
@@ -15,41 +15,45 @@ import (
"github.com/runatlantis/atlantis/server/logging"
)
-//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_pull_request_operator.go PullRequestOperator
+//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder
-type PullRequestOperator interface {
- Autoplan(ctx *CommandContext) CommandResponse
- PlanViaComment(ctx *CommandContext) CommandResponse
- ApplyViaComment(ctx *CommandContext) CommandResponse
+type ProjectCommandBuilder interface {
+ BuildAutoplanCommands(ctx *CommandContext) ([]models.ProjectCommandContext, error)
+ BuildPlanCommand(ctx *CommandContext, commentCommand *CommentCommand) (models.ProjectCommandContext, error)
+ BuildApplyCommand(ctx *CommandContext, commentCommand *CommentCommand) (models.ProjectCommandContext, error)
}
-type DefaultPullRequestOperator struct {
- TerraformExecutor TerraformExec
- DefaultTFVersion *version.Version
- ParserValidator *yaml.ParserValidator
- ProjectFinder ProjectFinder
- VCSClient vcs.ClientProxy
- Workspace AtlantisWorkspace
- ProjectOperator ProjectOperator
+type DefaultProjectCommandBuilder struct {
+ ParserValidator *yaml.ParserValidator
+ ProjectFinder ProjectFinder
+ VCSClient vcs.ClientProxy
+ Workspace AtlantisWorkspace
+ AtlantisWorkspaceLocker AtlantisWorkspaceLocker
}
type TerraformExec interface {
RunCommandWithVersion(log *logging.SimpleLogger, path string, args []string, v *version.Version, workspace string) (string, error)
}
-func (p *DefaultPullRequestOperator) Autoplan(ctx *CommandContext) CommandResponse {
- // check out repo to parse atlantis.yaml
- // this will check out the repo to a * dir
- repoDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Command.Workspace)
+func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext) ([]models.ProjectCommandContext, error) {
+ // Need to lock the workspace we're about to clone to.
+ workspace := DefaultWorkspace
+ unlockFn, err := p.AtlantisWorkspaceLocker.TryLock2(ctx.BaseRepo.FullName, workspace, ctx.Pull.Num)
if err != nil {
- return CommandResponse{Error: err}
+ return nil, err
+ }
+ defer unlockFn()
+
+ repoDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
+ if err != nil {
+ return nil, err
}
// Parse config file if it exists.
ctx.Log.Debug("parsing config file")
config, err := p.ParserValidator.ReadConfig(repoDir)
if err != nil && !os.IsNotExist(err) {
- return CommandResponse{Error: err}
+ return nil, err
}
noAtlantisYAML := os.IsNotExist(err)
if noAtlantisYAML {
@@ -61,11 +65,11 @@ func (p *DefaultPullRequestOperator) Autoplan(ctx *CommandContext) CommandRespon
// We'll need the list of modified files.
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
- return CommandResponse{Error: err}
+ return nil, err
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))
- // Prepare the project contexts so the ProjectOperator can execute.
+ // Prepare the project contexts so the ProjectCommandRunner can execute.
var projCtxs []models.ProjectCommandContext
// If there is no config file, then we try to plan for each project that
@@ -93,7 +97,7 @@ func (p *DefaultPullRequestOperator) Autoplan(ctx *CommandContext) CommandRespon
// in the config file.
matchingProjects, err := p.matchingProjects(ctx.Log, modifiedFiles, config)
if err != nil {
- return CommandResponse{Error: err}
+ return nil, err
}
ctx.Log.Info("%d projects are to be autoplanned based on their when_modified config", len(matchingProjects))
@@ -120,22 +124,21 @@ func (p *DefaultPullRequestOperator) Autoplan(ctx *CommandContext) CommandRespon
})
}
}
-
- // Execute the operations.
- var results []ProjectResult
- for _, pCtx := range projCtxs {
- res := p.ProjectOperator.Plan(pCtx, nil)
- res.Path = pCtx.RepoRelPath
- res.Workspace = pCtx.Workspace
- results = append(results, res)
- }
- return CommandResponse{ProjectResults: results}
+ return projCtxs, nil
}
-func (p *DefaultPullRequestOperator) PlanViaComment(ctx *CommandContext) CommandResponse {
- repoDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Command.Workspace)
+func (p *DefaultProjectCommandBuilder) BuildPlanCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
+ var projCtx models.ProjectCommandContext
+
+ unlockFn, err := p.AtlantisWorkspaceLocker.TryLock2(ctx.BaseRepo.FullName, cmd.Workspace, ctx.Pull.Num)
if err != nil {
- return CommandResponse{Error: err}
+ return projCtx, err
+ }
+ defer unlockFn()
+
+ repoDir, err := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, cmd.Workspace)
+ if err != nil {
+ return projCtx, err
}
var projCfg *valid.Project
@@ -144,55 +147,49 @@ func (p *DefaultPullRequestOperator) PlanViaComment(ctx *CommandContext) Command
// Parse config file if it exists.
config, err := p.ParserValidator.ReadConfig(repoDir)
if err != nil && !os.IsNotExist(err) {
- return CommandResponse{Error: err}
+ return projCtx, err
}
hasAtlantisYAML := !os.IsNotExist(err)
if hasAtlantisYAML {
// If they've specified a project by name we look it up. Otherwise we
// use the dir and workspace.
- if ctx.Command.ProjectName != "" {
- projCfg = config.FindProjectByName(ctx.Command.ProjectName)
+ if cmd.ProjectName != "" {
+ projCfg = config.FindProjectByName(cmd.ProjectName)
if projCfg == nil {
- return CommandResponse{Error: fmt.Errorf("no project with name %q configured", ctx.Command.ProjectName)}
+ return projCtx, fmt.Errorf("no project with name %q configured", cmd.ProjectName)
}
} else {
- projCfg = config.FindProject(ctx.Command.Dir, ctx.Command.Workspace)
+ projCfg = config.FindProject(cmd.Dir, cmd.Workspace)
}
globalCfg = &config
}
- if ctx.Command.ProjectName != "" && !hasAtlantisYAML {
- return CommandResponse{Error: fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename)}
+ if cmd.ProjectName != "" && !hasAtlantisYAML {
+ return projCtx, fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename)
}
- projCtx := models.ProjectCommandContext{
+ projCtx = models.ProjectCommandContext{
BaseRepo: ctx.BaseRepo,
HeadRepo: ctx.HeadRepo,
Pull: ctx.Pull,
User: ctx.User,
Log: ctx.Log,
- CommentArgs: ctx.Command.Flags,
- Workspace: ctx.Command.Workspace,
- RepoRelPath: ctx.Command.Dir,
- ProjectName: ctx.Command.ProjectName,
+ CommentArgs: cmd.Flags,
+ Workspace: cmd.Workspace,
+ RepoRelPath: cmd.Dir,
+ ProjectName: cmd.ProjectName,
ProjectConfig: projCfg,
GlobalConfig: globalCfg,
}
- projAbsPath := filepath.Join(repoDir, ctx.Command.Dir)
- res := p.ProjectOperator.Plan(projCtx, &projAbsPath)
- res.Workspace = projCtx.Workspace
- res.Path = projCtx.RepoRelPath
- return CommandResponse{
- ProjectResults: []ProjectResult{
- res,
- },
- }
+ return projCtx, nil
}
-func (p *DefaultPullRequestOperator) ApplyViaComment(ctx *CommandContext) CommandResponse {
- repoDir, err := p.Workspace.GetWorkspace(ctx.BaseRepo, ctx.Pull, ctx.Command.Workspace)
+func (p *DefaultProjectCommandBuilder) BuildApplyCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
+ var projCtx models.ProjectCommandContext
+
+ repoDir, err := p.Workspace.GetWorkspace(ctx.BaseRepo, ctx.Pull, cmd.Workspace)
if err != nil {
- return CommandResponse{Failure: "No workspace found. Did you run plan?"}
+ return projCtx, err
}
// todo: can deduplicate this between PlanViaComment
@@ -202,53 +199,46 @@ func (p *DefaultPullRequestOperator) ApplyViaComment(ctx *CommandContext) Comman
// Parse config file if it exists.
config, err := p.ParserValidator.ReadConfig(repoDir)
if err != nil && !os.IsNotExist(err) {
- return CommandResponse{Error: err}
+ return projCtx, err
}
hasAtlantisYAML := !os.IsNotExist(err)
if hasAtlantisYAML {
// If they've specified a project by name we look it up. Otherwise we
// use the dir and workspace.
- if ctx.Command.ProjectName != "" {
- projCfg = config.FindProjectByName(ctx.Command.ProjectName)
+ if cmd.ProjectName != "" {
+ projCfg = config.FindProjectByName(cmd.ProjectName)
if projCfg == nil {
- return CommandResponse{Error: fmt.Errorf("no project with name %q configured", ctx.Command.ProjectName)}
+ return projCtx, fmt.Errorf("no project with name %q configured", cmd.ProjectName)
}
} else {
- projCfg = config.FindProject(ctx.Command.Dir, ctx.Command.Workspace)
+ projCfg = config.FindProject(cmd.Dir, cmd.Workspace)
}
globalCfg = &config
}
- if ctx.Command.ProjectName != "" && !hasAtlantisYAML {
- return CommandResponse{Error: fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename)}
+ if cmd.ProjectName != "" && !hasAtlantisYAML {
+ return projCtx, fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename)
}
- projCtx := models.ProjectCommandContext{
+ projCtx = models.ProjectCommandContext{
BaseRepo: ctx.BaseRepo,
HeadRepo: ctx.HeadRepo,
Pull: ctx.Pull,
User: ctx.User,
Log: ctx.Log,
- CommentArgs: ctx.Command.Flags,
- Workspace: ctx.Command.Workspace,
- RepoRelPath: ctx.Command.Dir,
- ProjectName: ctx.Command.ProjectName,
+ CommentArgs: cmd.Flags,
+ Workspace: cmd.Workspace,
+ RepoRelPath: cmd.Dir,
+ ProjectName: cmd.ProjectName,
ProjectConfig: projCfg,
GlobalConfig: globalCfg,
}
- res := p.ProjectOperator.Apply(projCtx, filepath.Join(repoDir, ctx.Command.Dir))
- res.Workspace = projCtx.Workspace
- res.Path = projCtx.RepoRelPath
- return CommandResponse{
- ProjectResults: []ProjectResult{
- res,
- },
- }
+ return projCtx, nil
}
// matchingProjects returns the list of projects whose WhenModified fields match
// any of the modifiedFiles.
-func (p *DefaultPullRequestOperator) matchingProjects(log *logging.SimpleLogger, modifiedFiles []string, config valid.Spec) ([]valid.Project, error) {
+func (p *DefaultProjectCommandBuilder) matchingProjects(log *logging.SimpleLogger, modifiedFiles []string, config valid.Spec) ([]valid.Project, error) {
var projects []valid.Project
for _, project := range config.Projects {
log.Debug("checking if project at dir %q workspace %q was modified", project.Dir, project.Workspace)
diff --git a/server/events/pull_request_operator_test.go b/server/events/project_command_builder_test.go
similarity index 100%
rename from server/events/pull_request_operator_test.go
rename to server/events/project_command_builder_test.go
diff --git a/server/events/project_operator.go b/server/events/project_command_runner.go
similarity index 57%
rename from server/events/project_operator.go
rename to server/events/project_command_runner.go
index b5e8184ec..0db221def 100644
--- a/server/events/project_operator.go
+++ b/server/events/project_command_runner.go
@@ -14,6 +14,7 @@
package events
import (
+ "os"
"path/filepath"
"strings"
@@ -41,46 +42,48 @@ type PlanSuccess struct {
LockURL string
}
-type ProjectOperator struct {
- Locker ProjectLocker
- LockURLGenerator LockURLGenerator
- InitStepOperator runtime.InitStepOperator
- PlanStepOperator runtime.PlanStepOperator
- ApplyStepOperator runtime.ApplyStepOperator
- RunStepOperator runtime.RunStepOperator
- ApprovalOperator runtime.ApprovalOperator
- Workspace AtlantisWorkspace
- Webhooks WebhooksSender
+type ProjectCommandRunner struct {
+ Locker ProjectLocker
+ LockURLGenerator LockURLGenerator
+ InitStepRunner runtime.InitStepRunner
+ PlanStepRunner runtime.PlanStepRunner
+ ApplyStepRunner runtime.ApplyStepRunner
+ RunStepRunner runtime.RunStepRunner
+ PullApprovedChecker runtime.PullApprovedChecker
+ Workspace AtlantisWorkspace
+ Webhooks WebhooksSender
+ AtlantisWorkspaceLocker AtlantisWorkspaceLocker
}
-func (p *ProjectOperator) Plan(ctx models.ProjectCommandContext, projAbsPathPtr *string) ProjectResult {
+func (p *ProjectCommandRunner) Plan(ctx models.ProjectCommandContext) ProjectCommandResult {
// Acquire Atlantis lock for this repo/dir/workspace.
lockAttempt, err := p.Locker.TryLock(ctx.Log, ctx.Pull, ctx.User, ctx.Workspace, models.NewProject(ctx.BaseRepo.FullName, ctx.RepoRelPath))
if err != nil {
- return ProjectResult{Error: errors.Wrap(err, "acquiring lock")}
+ return ProjectCommandResult{
+ Error: errors.Wrap(err, "acquiring lock"),
+ }
}
if !lockAttempt.LockAcquired {
- return ProjectResult{Failure: lockAttempt.LockFailureReason}
+ return ProjectCommandResult{Failure: lockAttempt.LockFailureReason}
}
ctx.Log.Debug("acquired lock for project")
- // Ensure project has been cloned.
- var projAbsPath string
- if projAbsPathPtr == nil {
- ctx.Log.Debug("project has not yet been cloned")
- repoDir, cloneErr := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
- if cloneErr != nil {
- if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil {
- ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
- }
- return ProjectResult{Error: cloneErr}
- }
- projAbsPath = filepath.Join(repoDir, ctx.RepoRelPath)
- ctx.Log.Debug("project successfully cloned to %q", projAbsPath)
- } else {
- projAbsPath = *projAbsPathPtr
- ctx.Log.Debug("project was already cloned to %q", projAbsPath)
+ // Acquire internal lock for the directory we're going to operate in.
+ unlockFn, err := p.AtlantisWorkspaceLocker.TryLock2(ctx.BaseRepo.FullName, ctx.Workspace, ctx.Pull.Num)
+ if err != nil {
+ return ProjectCommandResult{Error: err}
}
+ defer unlockFn()
+
+ // Clone is idempotent so okay to run even if the repo was already cloned.
+ repoDir, cloneErr := p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
+ if cloneErr != nil {
+ if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil {
+ ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
+ }
+ return ProjectCommandResult{Error: cloneErr}
+ }
+ projAbsPath := filepath.Join(repoDir, ctx.RepoRelPath)
// Use default stage unless another workflow is defined in config
stage := p.defaultPlanStage()
@@ -98,10 +101,10 @@ func (p *ProjectOperator) Plan(ctx models.ProjectCommandContext, projAbsPathPtr
ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
}
// todo: include output from other steps.
- return ProjectResult{Error: err}
+ return ProjectCommandResult{Error: err}
}
- return ProjectResult{
+ return ProjectCommandResult{
PlanSuccess: &PlanSuccess{
LockURL: p.LockURLGenerator.GenerateLockURL(lockAttempt.LockKey),
TerraformOutput: strings.Join(outputs, "\n"),
@@ -109,20 +112,20 @@ func (p *ProjectOperator) Plan(ctx models.ProjectCommandContext, projAbsPathPtr
}
}
-func (p *ProjectOperator) runSteps(steps []valid.Step, ctx models.ProjectCommandContext, absPath string) ([]string, error) {
+func (p *ProjectCommandRunner) runSteps(steps []valid.Step, ctx models.ProjectCommandContext, absPath string) ([]string, error) {
var outputs []string
for _, step := range steps {
var out string
var err error
switch step.StepName {
case "init":
- out, err = p.InitStepOperator.Run(ctx, step.ExtraArgs, absPath)
+ out, err = p.InitStepRunner.Run(ctx, step.ExtraArgs, absPath)
case "plan":
- out, err = p.PlanStepOperator.Run(ctx, step.ExtraArgs, absPath)
+ out, err = p.PlanStepRunner.Run(ctx, step.ExtraArgs, absPath)
case "apply":
- out, err = p.ApplyStepOperator.Run(ctx, step.ExtraArgs, absPath)
+ out, err = p.ApplyStepRunner.Run(ctx, step.ExtraArgs, absPath)
case "run":
- out, err = p.RunStepOperator.Run(ctx, step.RunCommand, absPath)
+ out, err = p.RunStepRunner.Run(ctx, step.RunCommand, absPath)
}
if err != nil {
@@ -136,21 +139,36 @@ func (p *ProjectOperator) runSteps(steps []valid.Step, ctx models.ProjectCommand
return outputs, nil
}
-func (p *ProjectOperator) Apply(ctx models.ProjectCommandContext, absPath string) ProjectResult {
+func (p *ProjectCommandRunner) Apply(ctx models.ProjectCommandContext) ProjectCommandResult {
+ repoDir, err := p.Workspace.GetWorkspace(ctx.BaseRepo, ctx.Pull, ctx.Workspace)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return ProjectCommandResult{Error: errors.New("project has not been cloned–did you run plan?")}
+ }
+ return ProjectCommandResult{Error: err}
+ }
+ absPath := filepath.Join(repoDir, ctx.RepoRelPath)
+
if ctx.ProjectConfig != nil {
for _, req := range ctx.ProjectConfig.ApplyRequirements {
switch req {
case "approved":
- approved, err := p.ApprovalOperator.IsApproved(ctx.BaseRepo, ctx.Pull)
+ approved, err := p.PullApprovedChecker.IsApproved(ctx.BaseRepo, ctx.Pull)
if err != nil {
- return ProjectResult{Error: errors.Wrap(err, "checking if pull request was approved")}
+ return ProjectCommandResult{Error: errors.Wrap(err, "checking if pull request was approved")}
}
if !approved {
- return ProjectResult{Failure: "Pull request must be approved before running apply."}
+ return ProjectCommandResult{Failure: "Pull request must be approved before running apply."}
}
}
}
}
+ // Acquire internal lock for the directory we're going to operate in.
+ unlockFn, err := p.AtlantisWorkspaceLocker.TryLock2(ctx.BaseRepo.FullName, ctx.Workspace, ctx.Pull.Num)
+ if err != nil {
+ return ProjectCommandResult{Error: err}
+ }
+ defer unlockFn()
// Use default stage unless another workflow is defined in config
stage := p.defaultApplyStage()
@@ -170,14 +188,14 @@ func (p *ProjectOperator) Apply(ctx models.ProjectCommandContext, absPath string
})
if err != nil {
// todo: include output from other steps.
- return ProjectResult{Error: err}
+ return ProjectCommandResult{Error: err}
}
- return ProjectResult{
+ return ProjectCommandResult{
ApplySuccess: strings.Join(outputs, "\n"),
}
}
-func (p ProjectOperator) defaultPlanStage() valid.Stage {
+func (p ProjectCommandRunner) defaultPlanStage() valid.Stage {
return valid.Stage{
Steps: []valid.Step{
{
@@ -190,7 +208,7 @@ func (p ProjectOperator) defaultPlanStage() valid.Stage {
}
}
-func (p ProjectOperator) defaultApplyStage() valid.Stage {
+func (p ProjectCommandRunner) defaultApplyStage() valid.Stage {
return valid.Stage{
Steps: []valid.Step{
{
diff --git a/server/events/project_operator_test.go b/server/events/project_command_runner_test.go
similarity index 100%
rename from server/events/project_operator_test.go
rename to server/events/project_command_runner_test.go
diff --git a/server/events/project_result.go b/server/events/project_result.go
index ca5a1ecee..f01ccd936 100644
--- a/server/events/project_result.go
+++ b/server/events/project_result.go
@@ -17,8 +17,12 @@ import "github.com/runatlantis/atlantis/server/events/vcs"
// ProjectResult is the result of executing a plan/apply for a project.
type ProjectResult struct {
- Path string
- Workspace string
+ ProjectCommandResult
+ Path string
+ Workspace string
+}
+
+type ProjectCommandResult struct {
Error error
Failure string
PlanSuccess *PlanSuccess
diff --git a/server/events/runtime/apply_step_operator.go b/server/events/runtime/apply_step_runner.go
similarity index 83%
rename from server/events/runtime/apply_step_operator.go
rename to server/events/runtime/apply_step_runner.go
index 7b4a58c80..2cb3a50c1 100644
--- a/server/events/runtime/apply_step_operator.go
+++ b/server/events/runtime/apply_step_runner.go
@@ -9,12 +9,12 @@ import (
"github.com/runatlantis/atlantis/server/events/models"
)
-// ApplyStepOperator runs `terraform apply`.
-type ApplyStepOperator struct {
+// ApplyStepRunner runs `terraform apply`.
+type ApplyStepRunner struct {
TerraformExecutor TerraformExec
}
-func (a *ApplyStepOperator) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
+func (a *ApplyStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
// todo: move this to a common library
planFileName := fmt.Sprintf("%s.tfplan", ctx.Workspace)
if ctx.ProjectName != "" {
diff --git a/server/events/runtime/apply_step_operator_test.go b/server/events/runtime/apply_step_runner_test.go
similarity index 95%
rename from server/events/runtime/apply_step_operator_test.go
rename to server/events/runtime/apply_step_runner_test.go
index 1aad3c9e2..afc5c99e3 100644
--- a/server/events/runtime/apply_step_operator_test.go
+++ b/server/events/runtime/apply_step_runner_test.go
@@ -17,7 +17,7 @@ import (
)
func TestRun_NoDir(t *testing.T) {
- o := runtime.ApplyStepOperator{
+ o := runtime.ApplyStepRunner{
TerraformExecutor: nil,
}
_, err := o.Run(models.ProjectCommandContext{
@@ -30,7 +30,7 @@ func TestRun_NoDir(t *testing.T) {
func TestRun_NoPlanFile(t *testing.T) {
tmpDir, cleanup := TempDir(t)
defer cleanup()
- o := runtime.ApplyStepOperator{
+ o := runtime.ApplyStepRunner{
TerraformExecutor: nil,
}
_, err := o.Run(models.ProjectCommandContext{
@@ -49,7 +49,7 @@ func TestRun_Success(t *testing.T) {
RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
- o := runtime.ApplyStepOperator{
+ o := runtime.ApplyStepRunner{
TerraformExecutor: terraform,
}
@@ -74,7 +74,7 @@ func TestRun_UsesConfiguredTFVersion(t *testing.T) {
RegisterMockTestingT(t)
terraform := mocks.NewMockClient()
- o := runtime.ApplyStepOperator{
+ o := runtime.ApplyStepRunner{
TerraformExecutor: terraform,
}
tfVersion, _ := version.NewVersion("0.11.0")
diff --git a/server/events/runtime/init_step_operator.go b/server/events/runtime/init_step_runner.go
similarity index 87%
rename from server/events/runtime/init_step_operator.go
rename to server/events/runtime/init_step_runner.go
index f483ecbbd..da8ae585f 100644
--- a/server/events/runtime/init_step_operator.go
+++ b/server/events/runtime/init_step_runner.go
@@ -6,13 +6,13 @@ import (
)
// InitStep runs `terraform init`.
-type InitStepOperator struct {
+type InitStepRunner struct {
TerraformExecutor TerraformExec
DefaultTFVersion *version.Version
}
// nolint: unparam
-func (i *InitStepOperator) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
+func (i *InitStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
tfVersion := i.DefaultTFVersion
if ctx.ProjectConfig != nil && ctx.ProjectConfig.TerraformVersion != nil {
tfVersion = ctx.ProjectConfig.TerraformVersion
diff --git a/server/events/runtime/init_step_operator_test.go b/server/events/runtime/init_step_runner_test.go
similarity index 97%
rename from server/events/runtime/init_step_operator_test.go
rename to server/events/runtime/init_step_runner_test.go
index efbf4ca28..4a1416fba 100644
--- a/server/events/runtime/init_step_operator_test.go
+++ b/server/events/runtime/init_step_runner_test.go
@@ -44,7 +44,7 @@ func TestRun_UsesGetOrInitForRightVersion(t *testing.T) {
tfVersion, _ := version.NewVersion(c.version)
logger := logging.NewNoopLogger()
- iso := runtime.InitStepOperator{
+ iso := runtime.InitStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
diff --git a/server/events/runtime/plan_step_operater.go b/server/events/runtime/plan_step_runner.go
similarity index 93%
rename from server/events/runtime/plan_step_operater.go
rename to server/events/runtime/plan_step_runner.go
index 30028dd48..f0e49b6ed 100644
--- a/server/events/runtime/plan_step_operater.go
+++ b/server/events/runtime/plan_step_runner.go
@@ -15,12 +15,12 @@ import (
const atlantisUserTFVar = "atlantis_user"
const defaultWorkspace = "default"
-type PlanStepOperator struct {
+type PlanStepRunner struct {
TerraformExecutor TerraformExec
DefaultTFVersion *version.Version
}
-func (p *PlanStepOperator) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
+func (p *PlanStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) {
tfVersion := p.DefaultTFVersion
if ctx.ProjectConfig != nil && ctx.ProjectConfig.TerraformVersion != nil {
tfVersion = ctx.ProjectConfig.TerraformVersion
@@ -55,7 +55,7 @@ func (p *PlanStepOperator) Run(ctx models.ProjectCommandContext, extraArgs []str
// switchWorkspace changes the terraform workspace if necessary and will create
// it if it doesn't exist. It handles differences between versions.
-func (p *PlanStepOperator) switchWorkspace(ctx models.ProjectCommandContext, path string, tfVersion *version.Version) error {
+func (p *PlanStepRunner) switchWorkspace(ctx models.ProjectCommandContext, path string, tfVersion *version.Version) error {
// In versions less than 0.9 there is no support for workspaces.
noWorkspaceSupport := MustConstraint("<0.9").Check(tfVersion)
// If the user tried to set a specific workspace in the comment but their
diff --git a/server/events/runtime/plan_step_operater_test.go b/server/events/runtime/plan_step_runner_test.go
similarity index 87%
rename from server/events/runtime/plan_step_operater_test.go
rename to server/events/runtime/plan_step_runner_test.go
index bf80927b6..01dce930b 100644
--- a/server/events/runtime/plan_step_operater_test.go
+++ b/server/events/runtime/plan_step_runner_test.go
@@ -26,7 +26,7 @@ func TestRun_NoWorkspaceIn08(t *testing.T) {
tfVersion, _ := version.NewVersion("0.8")
logger := logging.NewNoopLogger()
workspace := "default"
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
DefaultTFVersion: tfVersion,
TerraformExecutor: terraform,
}
@@ -59,7 +59,7 @@ func TestRun_ErrWorkspaceIn08(t *testing.T) {
tfVersion, _ := version.NewVersion("0.8")
logger := logging.NewNoopLogger()
workspace := "notdefault"
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
@@ -107,7 +107,7 @@ func TestRun_SwitchesWorkspace(t *testing.T) {
tfVersion, _ := version.NewVersion(c.tfVersion)
logger := logging.NewNoopLogger()
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
@@ -162,7 +162,7 @@ func TestRun_CreatesWorkspace(t *testing.T) {
terraform := mocks.NewMockClient()
tfVersion, _ := version.NewVersion(c.tfVersion)
logger := logging.NewNoopLogger()
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
@@ -201,7 +201,7 @@ func TestRun_NoWorkspaceSwitchIfNotNecessary(t *testing.T) {
terraform := mocks.NewMockClient()
tfVersion, _ := version.NewVersion("0.10.0")
logger := logging.NewNoopLogger()
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
@@ -243,7 +243,7 @@ func TestRun_AddsEnvVarFile(t *testing.T) {
// Using version >= 0.10 here so we don't expect any env commands.
tfVersion, _ := version.NewVersion("0.10.0")
logger := logging.NewNoopLogger()
- s := runtime.PlanStepOperator{
+ s := runtime.PlanStepRunner{
TerraformExecutor: terraform,
DefaultTFVersion: tfVersion,
}
@@ -265,3 +265,31 @@ func TestRun_AddsEnvVarFile(t *testing.T) {
terraform.VerifyWasCalledOnce().RunCommandWithVersion(logger, tmpDir, expPlanArgs, tfVersion, "workspace")
Equals(t, "output", output)
}
+
+func TestRun_UsesDiffPathForProject(t *testing.T) {
+ // Test that if running for a project, uses a different path for the plan
+ // file.
+ RegisterMockTestingT(t)
+ terraform := mocks.NewMockClient()
+ tfVersion, _ := version.NewVersion("0.10.0")
+ logger := logging.NewNoopLogger()
+ s := runtime.PlanStepRunner{
+ TerraformExecutor: terraform,
+ DefaultTFVersion: tfVersion,
+ }
+ When(terraform.RunCommandWithVersion(logger, "/path", []string{"workspace", "show"}, tfVersion, "workspace")).ThenReturn("workspace\n", nil)
+
+ expPlanArgs := []string{"plan", "-refresh", "-no-color", "-out", "/path/projectname-default.tfplan", "-var", "atlantis_user=username", "extra", "args", "comment", "args"}
+ When(terraform.RunCommandWithVersion(logger, "/path", expPlanArgs, tfVersion, "default")).ThenReturn("output", nil)
+
+ output, err := s.Run(models.ProjectCommandContext{
+ Log: logger,
+ Workspace: "default",
+ RepoRelPath: ".",
+ ProjectName: "projectname",
+ User: models.User{Username: "username"},
+ CommentArgs: []string{"comment", "args"},
+ }, []string{"extra", "args"}, "/path")
+ Ok(t, err)
+ Equals(t, "output", output)
+}
diff --git a/server/events/runtime/approval_operator.go b/server/events/runtime/pull_approved_checker.go
similarity index 67%
rename from server/events/runtime/approval_operator.go
rename to server/events/runtime/pull_approved_checker.go
index 63731f342..049f43496 100644
--- a/server/events/runtime/approval_operator.go
+++ b/server/events/runtime/pull_approved_checker.go
@@ -5,11 +5,11 @@ import (
"github.com/runatlantis/atlantis/server/events/vcs"
)
-type ApprovalOperator struct {
+type PullApprovedChecker struct {
VCSClient vcs.ClientProxy
}
-func (a *ApprovalOperator) IsApproved(baseRepo models.Repo, pull models.PullRequest) (bool, error) {
+func (a *PullApprovedChecker) IsApproved(baseRepo models.Repo, pull models.PullRequest) (bool, error) {
approved, err := a.VCSClient.PullIsApproved(baseRepo, pull)
if err != nil {
return false, err
diff --git a/server/events/runtime/run_step_operator_test.go b/server/events/runtime/run_step_operator_test.go
deleted file mode 100644
index 899327a69..000000000
--- a/server/events/runtime/run_step_operator_test.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package runtime_test
-
-// todo
diff --git a/server/events/runtime/run_step_operator.go b/server/events/runtime/run_step_runner.go
similarity index 77%
rename from server/events/runtime/run_step_operator.go
rename to server/events/runtime/run_step_runner.go
index 6554527be..1b406737a 100644
--- a/server/events/runtime/run_step_operator.go
+++ b/server/events/runtime/run_step_runner.go
@@ -9,11 +9,11 @@ import (
"github.com/runatlantis/atlantis/server/events/models"
)
-// RunStepOperator runs custom commands.
-type RunStepOperator struct {
+// RunStepRunner runs custom commands.
+type RunStepRunner struct {
}
-func (r *RunStepOperator) Run(ctx models.ProjectCommandContext, command []string, path string) (string, error) {
+func (r *RunStepRunner) Run(ctx models.ProjectCommandContext, command []string, path string) (string, error) {
if len(command) < 1 {
return "", errors.New("no commands for run step")
}
diff --git a/server/events/runtime/run_step_runner_test.go b/server/events/runtime/run_step_runner_test.go
new file mode 100644
index 000000000..36262b235
--- /dev/null
+++ b/server/events/runtime/run_step_runner_test.go
@@ -0,0 +1,50 @@
+package runtime_test
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/runatlantis/atlantis/server/events/models"
+ "github.com/runatlantis/atlantis/server/events/runtime"
+ "github.com/runatlantis/atlantis/server/logging"
+ . "github.com/runatlantis/atlantis/testing"
+)
+
+func TestRunStepRunner_Run(t *testing.T) {
+ cases := []struct {
+ Command string
+ ExpOut string
+ ExpErr string
+ }{
+ {
+ Command: "echo hi",
+ ExpOut: "hi\n",
+ },
+ {
+ Command: "echo hi >> file && cat file",
+ ExpOut: "hi\n",
+ },
+ {
+ Command: "lkjlkj",
+ ExpErr: "exit status 127: running \"lkjlkj\" in",
+ },
+ }
+
+ r := runtime.RunStepRunner{}
+ ctx := models.ProjectCommandContext{
+ Log: logging.NewNoopLogger(),
+ }
+ for _, c := range cases {
+ t.Run(c.Command, func(t *testing.T) {
+ tmpDir, cleanup := TempDir(t)
+ defer cleanup()
+ out, err := r.Run(ctx, strings.Split(c.Command, " "), tmpDir)
+ if c.ExpErr != "" {
+ ErrContains(t, c.ExpErr, err)
+ return
+ }
+ Ok(t, err)
+ Equals(t, c.ExpOut, out)
+ })
+ }
+}
diff --git a/server/events/vcs/fixtures/fixtures.go b/server/events/vcs/fixtures/fixtures.go
index e3155f199..693916203 100644
--- a/server/events/vcs/fixtures/fixtures.go
+++ b/server/events/vcs/fixtures/fixtures.go
@@ -15,6 +15,14 @@ package fixtures
import "github.com/google/go-github/github"
+var PullEvent = github.PullRequestEvent{
+ Sender: &github.User{
+ Login: github.String("user"),
+ },
+ Repo: &Repo,
+ PullRequest: &Pull,
+}
+
var Pull = github.PullRequest{
Head: &github.PullRequestBranch{
SHA: github.String("sha256"),
diff --git a/server/events/vcs/vcs_test.go b/server/events/vcs/vcs_test.go
new file mode 100644
index 000000000..5333ff26f
--- /dev/null
+++ b/server/events/vcs/vcs_test.go
@@ -0,0 +1,19 @@
+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())
+ }
+}
diff --git a/server/events_controller.go b/server/events_controller.go
index 3db62bd96..f666e1e32 100644
--- a/server/events_controller.go
+++ b/server/events_controller.go
@@ -29,7 +29,7 @@ const githubHeader = "X-Github-Event"
const gitlabHeader = "X-Gitlab-Event"
// EventsController handles all webhook requests which signify 'events' in the
-// VCS host, ex. GitHub. It's split out from Server to make testing easier.
+// VCS host, ex. GitHub.
type EventsController struct {
CommandRunner events.CommandRunner
PullCleaner events.PullCleaner
@@ -51,11 +51,7 @@ type EventsController struct {
// startup to support.
SupportedVCSHosts []models.VCSHostType
VCSClient vcs.ClientProxy
- // AtlantisGithubUser is the user that atlantis is running as for Github.
- AtlantisGithubUser models.User
- // AtlantisGitlabUser is the user that atlantis is running as for Gitlab.
- AtlantisGitlabUser models.User
- TestingMode bool
+ TestingMode bool
}
// Post handles POST webhook requests.
@@ -117,27 +113,20 @@ func (e *EventsController) HandleGithubCommentEvent(w http.ResponseWriter, event
return
}
- // We pass in an empty models.Repo for headRepo because we need to do additional
- // calls to get that information but we need this code path to be generic.
- // Later on in CommandHandler we detect that this is a GitHub event and
- // make the necessary calls to get the headRepo.
- e.handleCommentEvent(w, baseRepo, models.Repo{}, user, pullNum, event.Comment.GetBody(), models.Github)
+ // 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)
}
// 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, headRepo, err := e.Parser.ParseGithubPull(pullEvent.PullRequest)
+ pull, 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
}
- baseRepo, err := e.Parser.ParseGithubRepo(pullEvent.Repo)
- if err != nil {
- e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing repo data: %s %s", err, githubReqID)
- return
- }
var eventType string
switch pullEvent.GetAction() {
case "opened":
@@ -150,7 +139,7 @@ func (e *EventsController) HandleGithubPullRequestEvent(w http.ResponseWriter, p
eventType = OtherPullEvent
}
e.Logger.Info("identified event as type %q", eventType)
- e.handlePullRequestEvent(w, baseRepo, headRepo, pull, e.AtlantisGithubUser, eventType)
+ e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, eventType)
}
const OpenPullEvent = "opened"
@@ -179,16 +168,13 @@ func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRep
// We use a goroutine so that this function returns and the connection is
// closed.
fmt.Fprintln(w, "Processing...")
- // We use a Command to represent autoplanning but we set dir and
- // workspace to '*' to indicate that all applicable dirs and workspaces
- // should be planned.
- autoplanCmd := events.NewCommand("*", nil, events.Plan, false, "*", "", true)
- e.Logger.Info("executing command %s", autoplanCmd)
+
+ e.Logger.Info("executing autoplan")
if !e.TestingMode {
- go e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pull.Num, autoplanCmd)
+ go e.CommandRunner.RunAutoplanCommand(baseRepo, headRepo, pull, user)
} else {
// When testing we want to wait for everything to complete.
- e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pull.Num, autoplanCmd)
+ e.CommandRunner.RunAutoplanCommand(baseRepo, headRepo, pull, user)
}
return
case ClosedPullEvent:
@@ -202,7 +188,7 @@ func (e *EventsController) handlePullRequestEvent(w http.ResponseWriter, baseRep
return
case OtherPullEvent:
// Else we ignore the event.
- e.respond(w, logging.Debug, http.StatusOK, "Ignoring opened pull request event")
+ e.respond(w, logging.Debug, http.StatusOK, "Ignoring non-actionable pull request event")
return
}
}
@@ -236,10 +222,10 @@ func (e *EventsController) HandleGitlabCommentEvent(w http.ResponseWriter, event
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, user, event.MergeRequest.IID, event.ObjectAttributes.Note, models.Gitlab)
}
-func (e *EventsController) handleCommentEvent(w http.ResponseWriter, baseRepo models.Repo, headRepo 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, user models.User, pullNum int, comment string, vcsHost models.VCSHostType) {
parseResult := e.CommentParser.Parse(comment, vcsHost)
if parseResult.Ignore {
truncated := comment
@@ -278,10 +264,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.ExecuteCommand(baseRepo, headRepo, user, pullNum, parseResult.Command)
+ go e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, user, pullNum, parseResult.Command)
} else {
// When testing we want to wait for everything to complete.
- e.CommandRunner.ExecuteCommand(baseRepo, headRepo, user, pullNum, parseResult.Command)
+ e.CommandRunner.RunCommentCommand(baseRepo, maybeHeadRepo, user, pullNum, parseResult.Command)
}
}
@@ -289,7 +275,7 @@ 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, err := e.Parser.ParseGitlabMergeEvent(event)
+ pull, baseRepo, headRepo, user, err := e.Parser.ParseGitlabMergeEvent(event)
if err != nil {
e.respond(w, logging.Error, http.StatusBadRequest, "Error parsing webhook: %s", err)
return
@@ -306,7 +292,7 @@ func (e *EventsController) HandleGitlabMergeRequestEvent(w http.ResponseWriter,
eventType = OtherPullEvent
}
e.Logger.Info("identified event as type %q", eventType)
- e.handlePullRequestEvent(w, baseRepo, headRepo, pull, e.AtlantisGitlabUser, eventType)
+ e.handlePullRequestEvent(w, baseRepo, headRepo, pull, user, eventType)
}
// supportsHost returns true if h is in e.SupportedVCSHosts and false otherwise.
diff --git a/server/events_controller_e2e_test.go b/server/events_controller_e2e_test.go
index 7d9f300a7..5abb66ad3 100644
--- a/server/events_controller_e2e_test.go
+++ b/server/events_controller_e2e_test.go
@@ -164,7 +164,7 @@ func TestGitHubWorkflow(t *testing.T) {
When(vcsClient.GetModifiedFiles(AnyRepo(), matchers.AnyModelsPullRequest())).ThenReturn(c.ModifiedFiles, nil)
// First, send the open pull request event and trigger an autoplan.
- pullOpenedReq := GitHubPullRequestOpenedEvent(t)
+ pullOpenedReq := GitHubPullRequestOpenedEvent(t, headSHA)
ctrl.Post(w, pullOpenedReq)
responseContains(t, w, 200, "Processing...")
if c.ExpAutoplanCommentFile != "" {
@@ -236,51 +236,51 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy,
}
defaultTFVersion := terraformClient.Version()
- commandHandler := &events.CommandHandler{
+ locker := events.NewDefaultAtlantisWorkspaceLocker()
+ commandRunner := &events.DefaultCommandRunner{
+ ProjectCommandRunner: &events.ProjectCommandRunner{
+ Locker: projectLocker,
+ LockURLGenerator: &mockLockURLGenerator{},
+ InitStepRunner: runtime.InitStepRunner{
+ TerraformExecutor: terraformClient,
+ DefaultTFVersion: defaultTFVersion,
+ },
+ PlanStepRunner: runtime.PlanStepRunner{
+ TerraformExecutor: terraformClient,
+ DefaultTFVersion: defaultTFVersion,
+ },
+ ApplyStepRunner: runtime.ApplyStepRunner{
+ TerraformExecutor: terraformClient,
+ },
+ RunStepRunner: runtime.RunStepRunner{},
+ PullApprovedChecker: runtime.PullApprovedChecker{
+ VCSClient: e2eVCSClient,
+ },
+ Workspace: atlantisWorkspace,
+ Webhooks: &mockWebhookSender{},
+ AtlantisWorkspaceLocker: locker,
+ },
EventParser: eventParser,
VCSClient: e2eVCSClient,
GithubPullGetter: e2eGithubGetter,
GitlabMergeRequestGetter: e2eGitlabGetter,
CommitStatusUpdater: e2eStatusUpdater,
- AtlantisWorkspaceLocker: events.NewDefaultAtlantisWorkspaceLocker(),
MarkdownRenderer: &events.MarkdownRenderer{},
Logger: logger,
AllowForkPRs: allowForkPRs,
AllowForkPRsFlag: "allow-fork-prs",
- PullRequestOperator: &events.DefaultPullRequestOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTFVersion,
- ParserValidator: &yaml.ParserValidator{},
- ProjectFinder: &events.DefaultProjectFinder{},
- VCSClient: e2eVCSClient,
- Workspace: atlantisWorkspace,
- ProjectOperator: events.ProjectOperator{
- Locker: projectLocker,
- LockURLGenerator: &mockLockURLGenerator{},
- InitStepOperator: runtime.InitStepOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTFVersion,
- },
- PlanStepOperator: runtime.PlanStepOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTFVersion,
- },
- ApplyStepOperator: runtime.ApplyStepOperator{
- TerraformExecutor: terraformClient,
- },
- RunStepOperator: runtime.RunStepOperator{},
- ApprovalOperator: runtime.ApprovalOperator{
- VCSClient: e2eVCSClient,
- },
- Workspace: atlantisWorkspace,
- Webhooks: &mockWebhookSender{},
- },
+ ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
+ ParserValidator: &yaml.ParserValidator{},
+ ProjectFinder: &events.DefaultProjectFinder{},
+ VCSClient: e2eVCSClient,
+ Workspace: atlantisWorkspace,
+ AtlantisWorkspaceLocker: locker,
},
}
ctrl := server.EventsController{
TestingMode: true,
- CommandRunner: commandHandler,
+ CommandRunner: commandRunner,
PullCleaner: &events.PullClosedExecutor{
Locker: lockingClient,
VCSClient: e2eVCSClient,
@@ -298,12 +298,6 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy,
},
SupportedVCSHosts: []models.VCSHostType{models.Gitlab, models.Github},
VCSClient: e2eVCSClient,
- AtlantisGithubUser: models.User{
- Username: "atlantisbot",
- },
- AtlantisGitlabUser: models.User{
- Username: "atlantisbot",
- },
}
return ctrl, e2eVCSClient, e2eGithubGetter, atlantisWorkspace
}
@@ -331,10 +325,12 @@ func GitHubCommentEvent(t *testing.T, comment string) *http.Request {
return req
}
-func GitHubPullRequestOpenedEvent(t *testing.T) *http.Request {
+func GitHubPullRequestOpenedEvent(t *testing.T, headSHA string) *http.Request {
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubPullRequestOpenedEvent.json"))
Ok(t, err)
- req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
+ // Replace sha with expected sha.
+ requestJSONStr := strings.Replace(string(requestJSON), "c31fd9ea6f557ad2ea659944c3844a059b83bc5d", headSHA, -1)
+ req, err := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(requestJSONStr)))
Ok(t, err)
req.Header.Set("Content-Type", "application/json")
req.Header.Set(githubHeader, "pull_request")
@@ -357,7 +353,7 @@ func GitHubPullRequestParsed(headSHA string) *github.PullRequest {
headSHA = "13940d121be73f656e2132c6d7b4c8e87878ac8d"
}
return &github.PullRequest{
- Number: github.Int(1),
+ Number: github.Int(2),
State: github.String("open"),
HTMLURL: github.String("htmlurl"),
Head: &github.PullRequestBranch{
diff --git a/server/events_controller_test.go b/server/events_controller_test.go
index a7f5c81a6..225021086 100644
--- a/server/events_controller_test.go
+++ b/server/events_controller_test.go
@@ -16,6 +16,7 @@ package server_test
import (
"bytes"
"errors"
+ "fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@@ -267,9 +268,7 @@ func TestPost_GitlabCommentSuccess(t *testing.T) {
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Processing...")
- // wait for 200ms so goroutine is called
- time.Sleep(200 * time.Millisecond)
- cr.VerifyWasCalledOnce().ExecuteCommand(models.Repo{}, models.Repo{}, models.User{}, 0, nil)
+ cr.VerifyWasCalledOnce().RunCommentCommand(models.Repo{}, &models.Repo{}, models.User{}, 0, nil)
}
func TestPost_GithubCommentSuccess(t *testing.T) {
@@ -281,16 +280,14 @@ func TestPost_GithubCommentSuccess(t *testing.T) {
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
baseRepo := models.Repo{}
user := models.User{}
- cmd := events.Command{}
+ cmd := events.CommentCommand{}
When(p.ParseGithubIssueCommentEvent(matchers.AnyPtrToGithubIssueCommentEvent())).ThenReturn(baseRepo, user, 1, nil)
When(cp.Parse("", models.Github)).ThenReturn(events.CommentParseResult{Command: &cmd})
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Processing...")
- // wait for 200ms so goroutine is called
- time.Sleep(200 * time.Millisecond)
- cr.VerifyWasCalledOnce().ExecuteCommand(baseRepo, baseRepo, user, 1, &cmd)
+ cr.VerifyWasCalledOnce().RunCommentCommand(baseRepo, nil, user, 1, &cmd)
}
func TestPost_GithubPullRequestInvalid(t *testing.T) {
@@ -301,45 +298,87 @@ func TestPost_GithubPullRequestInvalid(t *testing.T) {
event := `{"action": "closed"}`
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
- When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(models.PullRequest{}, models.Repo{}, errors.New("err"))
+ When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(models.PullRequest{}, 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")
}
-func TestPost_GithubPullRequestInvalidRepo(t *testing.T) {
- t.Log("when the event is a github pull request with invalid repo data we return a 400")
- e, v, _, p, _, _, _, _ := setup(t)
+func TestPost_GitlabMergeRequestInvalid(t *testing.T) {
+ t.Log("when the event is a gitlab merge request with invalid data we return a 400")
+ e, _, gl, p, _, _, _, _ := setup(t)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
- req.Header.Set(githubHeader, "pull_request")
-
- event := `{"action": "closed"}`
- When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
- When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(models.PullRequest{}, models.Repo{}, nil)
- When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(models.Repo{}, errors.New("err"))
+ req.Header.Set(gitlabHeader, "value")
+ 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"))
w := httptest.NewRecorder()
e.Post(w, req)
- responseContains(t, w, http.StatusBadRequest, "Error parsing repo data: err")
+ responseContains(t, w, http.StatusBadRequest, "Error parsing webhook: err")
}
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, _, p, _, _, _, _ := setup(t)
+ e, v, _, _, _, _, _, _ := setup(t)
e.RepoWhitelistChecker = &events.RepoWhitelistChecker{Whitelist: "github.com/nevermatch"}
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
req.Header.Set(githubHeader, "pull_request")
event := `{"action": "closed"}`
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
- When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(models.PullRequest{}, models.Repo{}, nil)
- When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(models.Repo{}, nil)
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusForbidden, "Ignoring pull request event from non-whitelisted repo")
}
-func TestPost_GithubPullRequestErrCleaningPull(t *testing.T) {
- t.Log("when the event is a pull request and we have an error calling CleanUpPull we return a 503")
+func TestPost_GitlabMergeRequestNotWhitelisted(t *testing.T) {
+ 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))
+ req.Header.Set(gitlabHeader, "value")
+
+ e.RepoWhitelistChecker = &events.RepoWhitelistChecker{Whitelist: "github.com/nevermatch"}
+ 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)
+
+ w := httptest.NewRecorder()
+ e.Post(w, req)
+ responseContains(t, w, http.StatusForbidden, "Ignoring pull request event from non-whitelisted repo")
+}
+
+func TestPost_GithubPullRequestUnsupportedAction(t *testing.T) {
+ e, v, _, _, _, _, _, _ := setup(t)
+ req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
+ req.Header.Set(githubHeader, "pull_request")
+
+ event := `{"action": "unsupported"}`
+ When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
+ w := httptest.NewRecorder()
+ e.Post(w, req)
+ responseContains(t, w, http.StatusOK, "Ignoring non-actionable pull request event")
+}
+
+func TestPost_GitlabMergeRequestUnsupportedAction(t *testing.T) {
+ 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))
+ req.Header.Set(gitlabHeader, "value")
+ gitlabMergeEvent.ObjectAttributes.Action = "unsupported"
+ 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)
+
+ w := httptest.NewRecorder()
+ e.Post(w, req)
+ responseContains(t, w, http.StatusOK, "Ignoring non-actionable pull request event")
+}
+
+func TestPost_GithubPullRequestClosedErrCleaningPull(t *testing.T) {
+ 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)
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
@@ -349,30 +388,30 @@ func TestPost_GithubPullRequestErrCleaningPull(t *testing.T) {
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{State: models.Closed}
- When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(pull, repo, nil)
- When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(repo, nil)
+ When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pull)).ThenReturn(errors.New("cleanup err"))
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: cleanup err")
}
-func TestPost_GitlabMergeRequestErrCleaningPull(t *testing.T) {
- t.Log("when the event is a gitlab merge request and an error occurs calling CleanUpPull we return a 500")
+func TestPost_GitlabMergeRequestClosedErrCleaningPull(t *testing.T) {
+ 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))
req.Header.Set(gitlabHeader, "value")
+ gitlabMergeEvent.ObjectAttributes.Action = "close"
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, nil)
+ When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pullRequest)).ThenReturn(errors.New("err"))
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusInternalServerError, "Error cleaning pull request: err")
}
-func TestPost_GithubPullRequestSuccess(t *testing.T) {
+func TestPost_GithubClosedPullRequestSuccess(t *testing.T) {
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))
@@ -382,8 +421,7 @@ func TestPost_GithubPullRequestSuccess(t *testing.T) {
When(v.Validate(req, secret)).ThenReturn([]byte(event), nil)
repo := models.Repo{}
pull := models.PullRequest{State: models.Closed}
- When(p.ParseGithubPull(matchers.AnyPtrToGithubPullRequest())).ThenReturn(pull, repo, nil)
- When(p.ParseGithubRepo(matchers.AnyPtrToGithubRepository())).ThenReturn(repo, nil)
+ When(p.ParseGithubPullEvent(matchers.AnyPtrToGithubPullRequestEvent())).ThenReturn(pull, repo, repo, models.User{}, nil)
When(c.CleanUpPull(repo, pull)).ThenReturn(nil)
w := httptest.NewRecorder()
e.Post(w, req)
@@ -398,12 +436,68 @@ 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, nil)
+ When(p.ParseGitlabMergeEvent(gitlabMergeEvent)).ThenReturn(pullRequest, repo, repo, models.User{}, nil)
w := httptest.NewRecorder()
e.Post(w, req)
responseContains(t, w, http.StatusOK, "Pull request cleaned successfully")
}
+func TestPost_PullOpenedOrUpdated(t *testing.T) {
+ cases := []struct {
+ Description string
+ HostType models.VCSHostType
+ Action string
+ }{
+ {
+ "github opened",
+ models.Github,
+ "opened",
+ },
+ {
+ "gitlab opened",
+ models.Gitlab,
+ "open",
+ },
+ {
+ "github synchronized",
+ models.Github,
+ "synchronize",
+ },
+ {
+ "gitlab update",
+ models.Gitlab,
+ "update",
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.Description, func(t *testing.T) {
+ e, v, gl, p, cr, _, _, _ := setup(t)
+ req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
+ switch c.HostType {
+ case models.Gitlab:
+ req.Header.Set(gitlabHeader, "value")
+ gitlabMergeEvent.ObjectAttributes.Action = c.Action
+ 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)
+ 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)
+ }
+ w := httptest.NewRecorder()
+ e.Post(w, req)
+ responseContains(t, w, http.StatusOK, "Processing...")
+ cr.VerifyWasCalledOnce().RunAutoplanCommand(models.Repo{}, models.Repo{}, models.PullRequest{State: models.Closed}, models.User{})
+ })
+ }
+}
+
func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValidator, *mocks.MockGitlabRequestParserValidator, *emocks.MockEventParsing, *emocks.MockCommandRunner, *emocks.MockPullCleaner, *vcsmocks.MockClientProxy, *emocks.MockCommentParsing) {
RegisterMockTestingT(t)
v := mocks.NewMockGithubRequestValidator()
@@ -414,6 +508,7 @@ func setup(t *testing.T) (server.EventsController, *mocks.MockGithubRequestValid
c := emocks.NewMockPullCleaner()
vcsmock := vcsmocks.NewMockClientProxy()
e := server.EventsController{
+ TestingMode: true,
Logger: logging.NewNoopLogger(),
GithubRequestValidator: v,
Parser: p,
diff --git a/server/router_test.go b/server/router_test.go
new file mode 100644
index 000000000..91bf9f3fc
--- /dev/null
+++ b/server/router_test.go
@@ -0,0 +1,27 @@
+package server_test
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/gorilla/mux"
+ "github.com/runatlantis/atlantis/server"
+ . "github.com/runatlantis/atlantis/testing"
+)
+
+func TestRouter_GenerateLockURL(t *testing.T) {
+ queryParam := "queryparam"
+ routeName := "routename"
+ atlantisURL := "https://example.com"
+
+ underlyingRouter := mux.NewRouter()
+ underlyingRouter.HandleFunc("/lock", func(_ http.ResponseWriter, _ *http.Request) {}).Methods("GET").Queries(queryParam, "{queryparam}").Name(routeName)
+
+ router := &server.Router{
+ AtlantisURL: atlantisURL,
+ LockViewRouteIDQueryParam: queryParam,
+ LockViewRouteName: routeName,
+ Underlying: underlyingRouter,
+ }
+ Equals(t, "https://example.com/lock?queryparam=myid", router.GenerateLockURL("myid"))
+}
diff --git a/server/server.go b/server/server.go
index fe97620d5..1980ba252 100644
--- a/server/server.go
+++ b/server/server.go
@@ -63,7 +63,7 @@ type Server struct {
AtlantisVersion string
Router *mux.Router
Port int
- CommandHandler *events.CommandHandler
+ CommandRunner *events.DefaultCommandRunner
Logger *logging.SimpleLogger
Locker locking.Locker
AtlantisURL string
@@ -221,45 +221,43 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
GitlabToken: userConfig.GitlabToken,
}
defaultTfVersion := terraformClient.Version()
- commandHandler := &events.CommandHandler{
- EventParser: eventParser,
+ commandRunner := &events.DefaultCommandRunner{
VCSClient: vcsClient,
GithubPullGetter: githubClient,
GitlabMergeRequestGetter: gitlabClient,
CommitStatusUpdater: commitStatusUpdater,
- AtlantisWorkspaceLocker: workspaceLocker,
+ EventParser: eventParser,
MarkdownRenderer: markdownRenderer,
Logger: logger,
AllowForkPRs: userConfig.AllowForkPRs,
AllowForkPRsFlag: config.AllowForkPRsFlag,
- PullRequestOperator: &events.DefaultPullRequestOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTfVersion,
- ParserValidator: &yaml.ParserValidator{},
- ProjectFinder: &events.DefaultProjectFinder{},
- VCSClient: vcsClient,
- Workspace: workspace,
- ProjectOperator: events.ProjectOperator{
- Locker: projectLocker,
- LockURLGenerator: router,
- InitStepOperator: runtime.InitStepOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTfVersion,
- },
- PlanStepOperator: runtime.PlanStepOperator{
- TerraformExecutor: terraformClient,
- DefaultTFVersion: defaultTfVersion,
- },
- ApplyStepOperator: runtime.ApplyStepOperator{
- TerraformExecutor: terraformClient,
- },
- RunStepOperator: runtime.RunStepOperator{},
- ApprovalOperator: runtime.ApprovalOperator{
- VCSClient: vcsClient,
- },
- Workspace: workspace,
- Webhooks: webhooksManager,
+ ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
+ ParserValidator: &yaml.ParserValidator{},
+ ProjectFinder: &events.DefaultProjectFinder{},
+ VCSClient: vcsClient,
+ Workspace: workspace,
+ },
+ ProjectCommandRunner: &events.ProjectCommandRunner{
+ Locker: projectLocker,
+ LockURLGenerator: router,
+ InitStepRunner: runtime.InitStepRunner{
+ TerraformExecutor: terraformClient,
+ DefaultTFVersion: defaultTfVersion,
},
+ PlanStepRunner: runtime.PlanStepRunner{
+ TerraformExecutor: terraformClient,
+ DefaultTFVersion: defaultTfVersion,
+ },
+ ApplyStepRunner: runtime.ApplyStepRunner{
+ TerraformExecutor: terraformClient,
+ },
+ RunStepRunner: runtime.RunStepRunner{},
+ PullApprovedChecker: runtime.PullApprovedChecker{
+ VCSClient: vcsClient,
+ },
+ Workspace: workspace,
+ Webhooks: webhooksManager,
+ AtlantisWorkspaceLocker: workspaceLocker,
},
}
repoWhitelist := &events.RepoWhitelistChecker{
@@ -273,7 +271,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
LockDetailTemplate: lockTemplate,
}
eventsController := &EventsController{
- CommandRunner: commandHandler,
+ CommandRunner: commandRunner,
PullCleaner: pullClosedExecutor,
Parser: eventParser,
CommentParser: commentParser,
@@ -285,14 +283,12 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
RepoWhitelistChecker: repoWhitelist,
SupportedVCSHosts: supportedVCSHosts,
VCSClient: vcsClient,
- AtlantisGithubUser: models.User{Username: userConfig.GithubUser},
- AtlantisGitlabUser: models.User{Username: userConfig.GitlabUser},
}
return &Server{
AtlantisVersion: config.AtlantisVersion,
Router: underlyingRouter,
Port: userConfig.Port,
- CommandHandler: commandHandler,
+ CommandRunner: commandRunner,
Logger: logger,
Locker: lockingClient,
AtlantisURL: userConfig.AtlantisURL,
@@ -313,7 +309,8 @@ func (s *Server) Start() error {
s.Router.PathPrefix("/static/").Handler(http.FileServer(&assetfs.AssetFS{Asset: static.Asset, AssetDir: static.AssetDir, AssetInfo: static.AssetInfo}))
s.Router.HandleFunc("/events", s.EventsController.Post).Methods("POST")
s.Router.HandleFunc("/locks", s.LocksController.DeleteLock).Methods("DELETE").Queries("id", "{id:.*}")
- s.Router.HandleFunc("/lock", s.LocksController.GetLock).Methods("GET").Queries(LockViewRouteIDQueryParam, "{id}").Name(LockViewRouteName)
+ s.Router.HandleFunc("/lock", s.LocksController.GetLock).Methods("GET").
+ Queries(LockViewRouteIDQueryParam, fmt.Sprintf("{%s}", LockViewRouteIDQueryParam)).Name(LockViewRouteName)
n := negroni.New(&negroni.Recovery{
Logger: log.New(os.Stdout, "", log.LstdFlags),
PrintStack: false,
diff --git a/server/server_test.go b/server/server_test.go
index 54776cfe9..cec7a4fcb 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -103,6 +103,7 @@ func TestIndex_Success(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)
Assert(t, strings.Contains(string(body), bodySubstr), "exp %q to be contained in %q", bodySubstr, string(body))
diff --git a/server/testfixtures/githubPullRequestClosedEvent.json b/server/testfixtures/githubPullRequestClosedEvent.json
index 1fd568761..cc281a8d7 100644
--- a/server/testfixtures/githubPullRequestClosedEvent.json
+++ b/server/testfixtures/githubPullRequestClosedEvent.json
@@ -1,15 +1,15 @@
{
"action": "closed",
- "number": 1,
+ "number": 2,
"pull_request": {
- "url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1",
+ "url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2",
"id": 193308707,
"node_id": "MDExOlB1bGxSZXF1ZXN0MTkzMzA4NzA3",
- "html_url": "https://github.com/runatlantis/atlantis-tests/pull/1",
- "diff_url": "https://github.com/runatlantis/atlantis-tests/pull/1.diff",
- "patch_url": "https://github.com/runatlantis/atlantis-tests/pull/1.patch",
- "issue_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1",
- "number": 1,
+ "html_url": "https://github.com/runatlantis/atlantis-tests/pull/2",
+ "diff_url": "https://github.com/runatlantis/atlantis-tests/pull/2.diff",
+ "patch_url": "https://github.com/runatlantis/atlantis-tests/pull/2.patch",
+ "issue_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/2",
+ "number": 2,
"state": "closed",
"locked": false,
"title": "Add new project layouts",
@@ -53,10 +53,10 @@
],
"milestone": null,
- "commits_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1/commits",
- "review_comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1/comments",
+ "commits_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2/commits",
+ "review_comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2/comments",
"review_comment_url": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/comments{/number}",
- "comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1/comments",
+ "comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/2/comments",
"statuses_url": "https://api.github.com/repos/runatlantis/atlantis-tests/statuses/5e2d140b2d74bf61675677f01dc947ae8512e18e",
"head": {
"label": "runatlantis:atlantisyaml",
@@ -308,25 +308,25 @@
},
"_links": {
"self": {
- "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1"
+ "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2"
},
"html": {
- "href": "https://github.com/runatlantis/atlantis-tests/pull/1"
+ "href": "https://github.com/runatlantis/atlantis-tests/pull/2"
},
"issue": {
- "href": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1"
+ "href": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/2"
},
"comments": {
- "href": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/1/comments"
+ "href": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/2/comments"
},
"review_comments": {
- "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1/comments"
+ "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2/comments"
},
"review_comment": {
"href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/comments{/number}"
},
"commits": {
- "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/1/commits"
+ "href": "https://api.github.com/repos/runatlantis/atlantis-tests/pulls/2/commits"
},
"statuses": {
"href": "https://api.github.com/repos/runatlantis/atlantis-tests/statuses/5e2d140b2d74bf61675677f01dc947ae8512e18e"
diff --git a/server/testfixtures/githubPullRequestOpenedEvent.json b/server/testfixtures/githubPullRequestOpenedEvent.json
index 0b969438e..03ee106b5 100644
--- a/server/testfixtures/githubPullRequestOpenedEvent.json
+++ b/server/testfixtures/githubPullRequestOpenedEvent.json
@@ -12,7 +12,7 @@
"number": 2,
"state": "open",
"locked": false,
- "title": "Noyaml",
+ "title": "branch",
"user": {
"login": "runatlantis",
"id": 1034429,
@@ -59,8 +59,8 @@
"comments_url": "https://api.github.com/repos/runatlantis/atlantis-tests/issues/2/comments",
"statuses_url": "https://api.github.com/repos/runatlantis/atlantis-tests/statuses/c31fd9ea6f557ad2ea659944c3844a059b83bc5d",
"head": {
- "label": "runatlantis:noyaml",
- "ref": "noyaml",
+ "label": "runatlantis:branch",
+ "ref": "branch",
"sha": "c31fd9ea6f557ad2ea659944c3844a059b83bc5d",
"user": {
"login": "runatlantis",
diff --git a/server/testfixtures/test-repos/tfvars-yaml/exp-output-apply-default.txt.act b/server/testfixtures/test-repos/tfvars-yaml/exp-output-apply-default.txt.act
deleted file mode 100644
index aa4f73472..000000000
--- a/server/testfixtures/test-repos/tfvars-yaml/exp-output-apply-default.txt.act
+++ /dev/null
@@ -1,7 +0,0 @@
-Ran Apply in dir: `.` workspace: `default`
-**Apply Error**
-```
-no plan found at path "." and workspace "default"–did you run plan?
-```
-
-
diff --git a/server/testfixtures/test-repos/tfvars-yaml/exp-output-autoplan.txt.act b/server/testfixtures/test-repos/tfvars-yaml/exp-output-autoplan.txt.act
deleted file mode 100644
index 0da9bc5a5..000000000
--- a/server/testfixtures/test-repos/tfvars-yaml/exp-output-autoplan.txt.act
+++ /dev/null
@@ -1,61 +0,0 @@
-Ran Plan for 2 projects:
-1. workspace: `default` path: `.`
-1. workspace: `default` path: `.`
-
-### 1. workspace: `default` path: `.`
-**Plan Error**
-```
-exit status 1: running "sh -c terraform init -no-color -backend-config=default.backend.tfvars" in "/var/folders/z1/4z__7jv12y7f9yz__nwy55z40000gp/T/201347935/repos/runatlantis/atlantis-tests/1/default":
-
-Initializing the backend...
-
-Successfully configured the backend "local"! Terraform will automatically
-use this backend unless the backend configuration changes.
-
-Initializing provider plugins...
-- Checking for available provider plugins on https://releases.hashicorp.com...
-
-Error installing provider "null": Get https://releases.hashicorp.com/terraform-provider-null/: net/http: TLS handshake timeout.
-
-Terraform analyses the configuration and state and automatically downloads
-plugins for the providers used. However, when attempting to download this
-plugin an unexpected error occured.
-
-This may be caused if for some reason Terraform is unable to reach the
-plugin repository. The repository may be unreachable if access is blocked
-by a firewall.
-
-If automatic installation is not possible or desirable in your environment,
-you may alternatively manually install plugins by downloading a suitable
-distribution package and placing the plugin's executable file in the
-following directory:
- terraform.d/plugins/darwin_amd64
-
-
-```
-
----
-### 2. workspace: `default` path: `.`
-```diff
-Refreshing Terraform state in-memory prior to plan...
-The refreshed state will be used to calculate this plan, but will not be
-persisted to local or remote state storage.
-
-
-------------------------------------------------------------------------
-
-An execution plan has been generated and is shown below.
-Resource actions are indicated with the following symbols:
- + create
-
-Terraform will perform the following actions:
-
-+ null_resource.simple
- id:
-Plan: 1 to add, 0 to change, 0 to destroy.
-
-```
-
-* To **discard** this plan click [here](lock-url).
----
-