mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-05 10:48:37 +00:00
Implement atlantis unlock
This command is run on a pr and deletes all locks for the pr
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,4 +13,3 @@ helm/test-values.yaml
|
||||
*.swp
|
||||
golangci-lint
|
||||
atlantis
|
||||
temp.sh
|
||||
@@ -105,6 +105,7 @@ type DefaultCommandRunner struct {
|
||||
WorkingDir WorkingDir
|
||||
DB *db.BoltDB
|
||||
Drainer *Drainer
|
||||
DeleteLockCommand DeleteLockCommand
|
||||
}
|
||||
|
||||
// RunAutoplanCommand runs plan when a pull request is opened or updated.
|
||||
@@ -247,6 +248,19 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
|
||||
return
|
||||
}
|
||||
|
||||
if cmd.Name == models.UnlockCommand {
|
||||
vcsMessage := "All Atlantis locks for this PR have been unlocked and plans discarded"
|
||||
err := c.DeleteLockCommand.DeleteLocksByPull(baseRepo.FullName, pullNum)
|
||||
if err != nil {
|
||||
vcsMessage = "Failed to delete PR locks"
|
||||
log.Err("failed to delete locks by pull %s", err.Error())
|
||||
}
|
||||
if commentErr := c.VCSClient.CreateComment(baseRepo, pullNum, vcsMessage); commentErr != nil {
|
||||
log.Err("unable to comment: %s", commentErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if cmd.CommandName() == models.ApplyCommand {
|
||||
// Get the mergeable status before we set any build statuses of our own.
|
||||
// We do this here because when we set a "Pending" status, if users have
|
||||
@@ -273,8 +287,6 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
|
||||
projectCmds, err = c.ProjectCommandBuilder.BuildPlanCommands(ctx, cmd)
|
||||
case models.ApplyCommand:
|
||||
projectCmds, err = c.ProjectCommandBuilder.BuildApplyCommands(ctx, cmd)
|
||||
case models.DiscardCommand:
|
||||
projectCmds, err = c.ProjectCommandBuilder.BuildDiscardCommands(ctx, cmd)
|
||||
default:
|
||||
ctx.Log.Err("failed to determine desired command, neither plan nor apply")
|
||||
return
|
||||
@@ -305,13 +317,6 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
|
||||
result.PlansDeleted = true
|
||||
}
|
||||
|
||||
// If this was a successful discard command, delete plans anyway
|
||||
if cmd.Name == models.DiscardCommand && !result.HasErrors() {
|
||||
c.deletePlans(ctx)
|
||||
result.PlansDeleted = true
|
||||
}
|
||||
|
||||
// TODO: check here for updating PR with discard
|
||||
c.updatePull(
|
||||
ctx,
|
||||
cmd,
|
||||
@@ -435,8 +440,6 @@ func (c *DefaultCommandRunner) runProjectCmds(cmds []models.ProjectCommandContex
|
||||
res = c.ProjectCommandRunner.Plan(pCmd)
|
||||
case models.ApplyCommand:
|
||||
res = c.ProjectCommandRunner.Apply(pCmd)
|
||||
case models.DiscardCommand:
|
||||
res = c.ProjectCommandRunner.Discard(pCmd)
|
||||
}
|
||||
results = append(results, res)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
. "github.com/petergtz/pegomock"
|
||||
"github.com/runatlantis/atlantis/server/events"
|
||||
"github.com/runatlantis/atlantis/server/events/mocks"
|
||||
eventmocks "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"
|
||||
@@ -45,6 +46,7 @@ var pullLogger *logging.SimpleLogger
|
||||
var workingDir events.WorkingDir
|
||||
var pendingPlanFinder *mocks.MockPendingPlanFinder
|
||||
var drainer *events.Drainer
|
||||
var deleteLockCommand *mocks.MockDeleteLockCommand
|
||||
|
||||
func setup(t *testing.T) *vcsmocks.MockClient {
|
||||
RegisterMockTestingT(t)
|
||||
@@ -60,6 +62,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
|
||||
workingDir = mocks.NewMockWorkingDir()
|
||||
pendingPlanFinder = mocks.NewMockPendingPlanFinder()
|
||||
drainer = &events.Drainer{}
|
||||
deleteLockCommand = eventmocks.NewMockDeleteLockCommand()
|
||||
When(logger.GetLevel()).ThenReturn(logging.Info)
|
||||
When(logger.NewLogger("runatlantis/atlantis#1", true, logging.Info)).
|
||||
ThenReturn(pullLogger)
|
||||
@@ -80,6 +83,7 @@ func setup(t *testing.T) *vcsmocks.MockClient {
|
||||
WorkingDir: workingDir,
|
||||
DisableApplyAll: false,
|
||||
Drainer: drainer,
|
||||
DeleteLockCommand: deleteLockCommand,
|
||||
}
|
||||
return vcsClient
|
||||
}
|
||||
@@ -200,6 +204,42 @@ func TestRunCommentCommand_ClosedPull(t *testing.T) {
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
|
||||
}
|
||||
|
||||
func TestRunUnlockCommand_VCSComment(t *testing.T) {
|
||||
t.Log("if unlock PR command is run, atlantis should" +
|
||||
" invoke the delete command and comment on PR accordingly")
|
||||
|
||||
vcsClient := setup(t)
|
||||
pull := &github.PullRequest{
|
||||
State: github.String("open"),
|
||||
}
|
||||
modelPull := models.PullRequest{State: models.OpenPullState}
|
||||
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
|
||||
When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, fixtures.GithubRepo, nil)
|
||||
|
||||
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: models.UnlockCommand})
|
||||
|
||||
deleteLockCommand.VerifyWasCalledOnce().DeleteLocksByPull(fixtures.GithubRepo.FullName, fixtures.Pull.Num)
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, fixtures.Pull.Num, "All Atlantis locks for this PR have been unlocked and plans discarded")
|
||||
}
|
||||
|
||||
func TestRunUnlockCommandFail_VCSComment(t *testing.T) {
|
||||
t.Log("if unlock PR command is run and delete fails, atlantis should" +
|
||||
" invoke comment on PR with error message")
|
||||
|
||||
vcsClient := setup(t)
|
||||
pull := &github.PullRequest{
|
||||
State: github.String("open"),
|
||||
}
|
||||
modelPull := models.PullRequest{State: models.OpenPullState}
|
||||
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
|
||||
When(eventParsing.ParseGithubPull(pull)).ThenReturn(modelPull, modelPull.BaseRepo, fixtures.GithubRepo, nil)
|
||||
When(deleteLockCommand.DeleteLocksByPull(fixtures.GithubRepo.FullName, fixtures.Pull.Num)).ThenReturn(errors.New("err"))
|
||||
|
||||
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, &events.CommentCommand{Name: models.UnlockCommand})
|
||||
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, fixtures.Pull.Num, "Failed to delete PR locks")
|
||||
}
|
||||
|
||||
// Test that if one plan fails and we are using automerge, that
|
||||
// we delete the plans.
|
||||
func TestRunAutoplanCommand_DeletePlans(t *testing.T) {
|
||||
|
||||
@@ -61,8 +61,6 @@ type CommentBuilder interface {
|
||||
BuildPlanComment(repoRelDir string, workspace string, project string, commentArgs []string) string
|
||||
// BuildApplyComment builds an apply comment for the specified args.
|
||||
BuildApplyComment(repoRelDir string, workspace string, project string) string
|
||||
// BuildDiscardComment builds a discard comment for the specified args.
|
||||
BuildDiscardComment(repoRelDir string, workspace string, project string, commentArgs []string) string
|
||||
}
|
||||
|
||||
// CommentParser implements CommentParsing
|
||||
@@ -158,8 +156,8 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
|
||||
return CommentParseResult{CommentResponse: HelpComment}
|
||||
}
|
||||
|
||||
// Need to have a plan or apply at this point.
|
||||
if !e.stringInSlice(command, []string{models.PlanCommand.String(), models.ApplyCommand.String(), models.DiscardCommand.String()}) {
|
||||
// Need to have a plan, apply or unlock at this point.
|
||||
if !e.stringInSlice(command, []string{models.PlanCommand.String(), models.ApplyCommand.String(), models.UnlockCommand.String()}) {
|
||||
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nError: unknown command %q.\nRun 'atlantis --help' for usage.\n```", command)}
|
||||
}
|
||||
|
||||
@@ -188,13 +186,10 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
|
||||
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Apply the plan for this directory, relative to root of repo, ex. 'child/dir'.")
|
||||
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Apply the plan for this project. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", yaml.AtlantisYAMLFilename))
|
||||
flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.")
|
||||
case models.DiscardCommand.String():
|
||||
name = models.DiscardCommand
|
||||
flagSet = pflag.NewFlagSet(models.DiscardCommand.String(), pflag.ContinueOnError)
|
||||
case models.UnlockCommand.String():
|
||||
name = models.UnlockCommand
|
||||
flagSet = pflag.NewFlagSet(models.UnlockCommand.String(), pflag.ContinueOnError)
|
||||
flagSet.SetOutput(ioutil.Discard)
|
||||
flagSet.StringVarP(&workspace, workspaceFlagLong, workspaceFlagShort, "", "Switch to this Terraform workspace before planning.")
|
||||
flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run plan in relative to root of repo, ex. 'child/dir'.")
|
||||
flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Which project to discard the plan for. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", yaml.AtlantisYAMLFilename))
|
||||
|
||||
default:
|
||||
return CommentParseResult{CommentResponse: fmt.Sprintf("Error: unknown command %q – this is a bug", command)}
|
||||
@@ -207,6 +202,9 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen
|
||||
return CommentParseResult{CommentResponse: fmt.Sprintf("```\nUsage of %s:\n%s\n```", command, flagSet.FlagUsagesWrapped(usagesCols))}
|
||||
}
|
||||
if err != nil {
|
||||
if command == models.UnlockCommand.String() {
|
||||
return CommentParseResult{CommentResponse: UnlockUsage}
|
||||
}
|
||||
return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), command, flagSet)}
|
||||
}
|
||||
|
||||
@@ -274,22 +272,6 @@ func (e *CommentParser) BuildApplyComment(repoRelDir string, workspace string, p
|
||||
return fmt.Sprintf("%s %s%s", atlantisExecutable, models.ApplyCommand.String(), flags)
|
||||
}
|
||||
|
||||
// BuildDiscardComment builds discard comment for the specified args.
|
||||
func (e *CommentParser) BuildDiscardComment(repoRelDir string, workspace string, project string, commentArgs []string) string {
|
||||
flags := e.buildFlags(repoRelDir, workspace, project)
|
||||
commentFlags := ""
|
||||
if len(commentArgs) > 0 {
|
||||
var flagsWithoutQuotes []string
|
||||
for _, f := range commentArgs {
|
||||
f = strings.TrimPrefix(f, "\"")
|
||||
f = strings.TrimSuffix(f, "\"")
|
||||
flagsWithoutQuotes = append(flagsWithoutQuotes, f)
|
||||
}
|
||||
commentFlags = fmt.Sprintf(" -- %s", strings.Join(flagsWithoutQuotes, " "))
|
||||
}
|
||||
return fmt.Sprintf("%s %s%s%s", atlantisExecutable, models.DiscardCommand.String(), flags, commentFlags)
|
||||
}
|
||||
|
||||
func (e *CommentParser) buildFlags(repoRelDir string, workspace string, project string) string {
|
||||
// Add quotes if dir has spaces.
|
||||
if strings.Contains(repoRelDir, " ") {
|
||||
@@ -371,9 +353,9 @@ Commands:
|
||||
plan Runs 'terraform plan' for the changes in this pull request.
|
||||
To plan a specific project, use the -d, -w and -p flags.
|
||||
apply Runs 'terraform apply' on all unapplied plans from this pull request.
|
||||
To only apply a specific plan, use the -d, -w and -p flags.
|
||||
discard Discards a previous plan as well as the atlantis lock.
|
||||
To discard a specific plan and atlantis lock use the -d flag.
|
||||
To only apply a specific plan, use the -d, -w and -p flags.
|
||||
unlock Removes all atlantis locks and discards all plans for this PR.
|
||||
To unlock a specific plan you can use the Atlantis UI.
|
||||
help View help.
|
||||
|
||||
Flags:
|
||||
@@ -385,3 +367,14 @@ Use "atlantis [command] --help" for more information about a command.` +
|
||||
// DidYouMeanAtlantisComment is the comment we add to the pull request when
|
||||
// someone runs a command with terraform instead of atlantis.
|
||||
var DidYouMeanAtlantisComment = "Did you mean to use `atlantis` instead of `terraform`?"
|
||||
|
||||
// UnlockUsage is the comment we add to the pull request when someone runs
|
||||
// `atlantis unlock` with flags.
|
||||
|
||||
var UnlockUsage = "`Usage of unlock:`\n\n ```cmake\n" +
|
||||
`atlantis unlock
|
||||
|
||||
Unlocks the entire PR and discards all plans in this PR.
|
||||
Arguments or flags are not supported at the moment.
|
||||
If you need to unlock a specific project please use the atlantis UI.` +
|
||||
"\n```"
|
||||
|
||||
@@ -126,6 +126,13 @@ func TestParse_UnusedArguments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_UnknownShorthandFlag(t *testing.T) {
|
||||
comment := "atlantis unlock -d ."
|
||||
r := commentParser.Parse(comment, models.Github)
|
||||
|
||||
Equals(t, UnlockUsage, r.CommentResponse)
|
||||
}
|
||||
|
||||
func TestParse_DidYouMeanAtlantis(t *testing.T) {
|
||||
t.Log("given a comment that should result in a 'did you mean atlantis'" +
|
||||
"response, should set CommentParseResult.CommentResult")
|
||||
@@ -693,3 +700,10 @@ var ApplyUsage = `Usage of apply:
|
||||
--verbose Append Atlantis log to comment.
|
||||
-w, --workspace string Apply the plan for this Terraform workspace.
|
||||
`
|
||||
var UnlockUsage = "`Usage of unlock:`\n\n ```cmake\n" +
|
||||
`atlantis unlock
|
||||
|
||||
Unlocks the entire PR and discards all plans in this PR.
|
||||
Arguments or flags are not supported at the moment.
|
||||
If you need to unlock a specific project please use the atlantis UI.` +
|
||||
"\n```"
|
||||
|
||||
@@ -623,7 +623,6 @@ func TestPullStatus_UpdateMerge(t *testing.T) {
|
||||
LockURL: "lock-url",
|
||||
RePlanCmd: "plan command",
|
||||
ApplyCmd: "apply command",
|
||||
DiscardCmd: "discard command",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
79
server/events/delete_lock_command.go
Normal file
79
server/events/delete_lock_command.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/runatlantis/atlantis/server/events/db"
|
||||
"github.com/runatlantis/atlantis/server/events/locking"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
)
|
||||
|
||||
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_delete_lock_command.go DeleteLockCommand
|
||||
|
||||
// DeleteLockCommand is the first step after a command request has been parsed.
|
||||
type DeleteLockCommand interface {
|
||||
DeleteLock(id string) (*models.ProjectLock, error)
|
||||
DeleteLocksByPull(repoFullName string, pullNum int) error
|
||||
}
|
||||
|
||||
// DefaultDeleteLockCommand deletes a specific lock after a request from the LocksController.
|
||||
type DefaultDeleteLockCommand struct {
|
||||
Locker locking.Locker
|
||||
Logger *logging.SimpleLogger
|
||||
WorkingDir WorkingDir
|
||||
WorkingDirLocker WorkingDirLocker
|
||||
DB *db.BoltDB
|
||||
}
|
||||
|
||||
// DeleteLock handles deleting the lock at id
|
||||
func (l *DefaultDeleteLockCommand) DeleteLock(id string) (*models.ProjectLock, error) {
|
||||
lock, err := l.Locker.Unlock(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lock == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
l.deleteWorkingDir(*lock)
|
||||
return lock, nil
|
||||
}
|
||||
|
||||
// DeleteLocksByPull handles deleting all locks for the pull request
|
||||
func (l *DefaultDeleteLockCommand) DeleteLocksByPull(repoFullName string, pullNum int) error {
|
||||
locks, err := l.Locker.UnlockByPull(repoFullName, pullNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(locks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(locks); i++ {
|
||||
lock := locks[i]
|
||||
l.deleteWorkingDir(lock)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *DefaultDeleteLockCommand) deleteWorkingDir(lock models.ProjectLock) {
|
||||
// NOTE: Because BaseRepo was added to the PullRequest model later, previous
|
||||
// installations of Atlantis will have locks in their DB that do not have
|
||||
// this field on PullRequest. We skip deleting the working dir in this case.
|
||||
if lock.Pull.BaseRepo == (models.Repo{}) {
|
||||
return
|
||||
}
|
||||
unlock, err := l.WorkingDirLocker.TryLock(lock.Pull.BaseRepo.FullName, lock.Pull.Num, lock.Workspace)
|
||||
if err != nil {
|
||||
l.Logger.Err("unable to obtain working dir lock when trying to delete old plans: %s", err)
|
||||
} else {
|
||||
defer unlock()
|
||||
// nolint: vetshadow
|
||||
if err := l.WorkingDir.DeleteForWorkspace(lock.Pull.BaseRepo, lock.Pull, lock.Workspace); err != nil {
|
||||
l.Logger.Err("unable to delete workspace: %s", err)
|
||||
}
|
||||
}
|
||||
if err := l.DB.DeleteProjectStatus(lock.Pull, lock.Workspace, lock.Project.Path); err != nil {
|
||||
l.Logger.Err("unable to delete project status: %s", err)
|
||||
}
|
||||
}
|
||||
135
server/events/delete_lock_command_test.go
Normal file
135
server/events/delete_lock_command_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package events_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
. "github.com/petergtz/pegomock"
|
||||
"github.com/runatlantis/atlantis/server/events"
|
||||
"github.com/runatlantis/atlantis/server/events/db"
|
||||
lockmocks "github.com/runatlantis/atlantis/server/events/locking/mocks"
|
||||
"github.com/runatlantis/atlantis/server/events/models"
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
. "github.com/runatlantis/atlantis/testing"
|
||||
)
|
||||
|
||||
func TestDeleteLock_LockerErr(t *testing.T) {
|
||||
t.Log("If there is an error retrieving the lock, we return the error")
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(nil, errors.New("err"))
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
_, err := dlc.DeleteLock("id")
|
||||
ErrEquals(t, "err", err)
|
||||
}
|
||||
|
||||
func TestDeleteLock_None(t *testing.T) {
|
||||
t.Log("If there is no lock at that ID we return nil")
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(nil, nil)
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
lock, err := dlc.DeleteLock("id")
|
||||
Ok(t, err)
|
||||
Assert(t, lock == nil, "lock was not nil")
|
||||
}
|
||||
|
||||
func TestDeleteLock_OldFormat(t *testing.T) {
|
||||
t.Log("If the lock doesn't have BaseRepo set it is deleted successfully")
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{}, nil)
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
lock, err := dlc.DeleteLock("id")
|
||||
Ok(t, err)
|
||||
Assert(t, lock != nil, "lock was nil")
|
||||
}
|
||||
|
||||
func TestDeleteLock_Success(t *testing.T) {
|
||||
t.Log("Delete lock deletes successfully the working dir")
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{}, nil)
|
||||
workingDir := events.NewMockWorkingDir()
|
||||
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
||||
pull := models.PullRequest{
|
||||
BaseRepo: models.Repo{FullName: "owner/repo"},
|
||||
}
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{
|
||||
Pull: pull,
|
||||
Workspace: "workspace",
|
||||
Project: models.Project{
|
||||
Path: "path",
|
||||
RepoFullName: "owner/repo",
|
||||
},
|
||||
}, nil)
|
||||
tmp, cleanup := TempDir(t)
|
||||
defer cleanup()
|
||||
db, err := db.New(tmp)
|
||||
Ok(t, err)
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
DB: db,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
WorkingDir: workingDir,
|
||||
}
|
||||
lock, err := dlc.DeleteLock("id")
|
||||
Ok(t, err)
|
||||
Assert(t, lock != nil, "lock was nil")
|
||||
workingDir.VerifyWasCalledOnce().DeleteForWorkspace(pull.BaseRepo, pull, "workspace")
|
||||
}
|
||||
|
||||
func TestDeleteLocksByPull_LockerErr(t *testing.T) {
|
||||
t.Log("If there is an error retrieving the lock, returned a failed status")
|
||||
repoName := "reponame"
|
||||
pullNum := 2
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.UnlockByPull(repoName, pullNum)).ThenReturn(nil, errors.New("err"))
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
err := dlc.DeleteLocksByPull(repoName, pullNum)
|
||||
ErrEquals(t, "err", err)
|
||||
}
|
||||
|
||||
func TestDeleteLocksByPull_None(t *testing.T) {
|
||||
t.Log("If there is no lock at that ID there is no error")
|
||||
repoName := "reponame"
|
||||
pullNum := 2
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.UnlockByPull(repoName, pullNum)).ThenReturn([]models.ProjectLock{}, nil)
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
err := dlc.DeleteLocksByPull(repoName, pullNum)
|
||||
Ok(t, err)
|
||||
}
|
||||
|
||||
func TestDeleteLocksByPull_OldFormat(t *testing.T) {
|
||||
t.Log("If the lock doesn't have BaseRepo set it is deleted successfully")
|
||||
repoName := "reponame"
|
||||
pullNum := 2
|
||||
RegisterMockTestingT(t)
|
||||
l := lockmocks.NewMockLocker()
|
||||
When(l.UnlockByPull(repoName, pullNum)).ThenReturn([]models.ProjectLock{{}}, nil)
|
||||
dlc := events.DefaultDeleteLockCommand{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
err := dlc.DeleteLocksByPull(repoName, pullNum)
|
||||
Ok(t, err)
|
||||
}
|
||||
@@ -24,9 +24,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
planCommandTitle = "Plan"
|
||||
applyCommandTitle = "Apply"
|
||||
discardCommandTitle = "Discard"
|
||||
planCommandTitle = "Plan"
|
||||
applyCommandTitle = "Apply"
|
||||
// maxUnwrappedLines is the maximum number of lines the Terraform output
|
||||
// can be before we wrap it in an expandable template.
|
||||
maxUnwrappedLines = 12
|
||||
@@ -144,9 +143,6 @@ func (m *MarkdownRenderer) renderProjectResults(results []models.ProjectResult,
|
||||
} else {
|
||||
resultData.Rendered = m.renderTemplate(applyUnwrappedSuccessTmpl, struct{ Output string }{result.ApplySuccess})
|
||||
}
|
||||
|
||||
} else if result.DiscardSuccess != "" {
|
||||
resultData.Rendered = m.renderTemplate(discardUnwrappedSuccessTmpl, struct{ Output string }{result.DiscardSuccess})
|
||||
} else {
|
||||
resultData.Rendered = "Found no template. This is a bug!"
|
||||
}
|
||||
@@ -159,8 +155,6 @@ func (m *MarkdownRenderer) renderProjectResults(results []models.ProjectResult,
|
||||
tmpl = singleProjectPlanSuccessTmpl
|
||||
case len(resultsTmplData) == 1 && common.Command == planCommandTitle && numPlanSuccesses == 0:
|
||||
tmpl = singleProjectPlanUnsuccessfulTmpl
|
||||
case len(resultsTmplData) == 1 && common.Command == discardCommandTitle:
|
||||
tmpl = singleProjectDiscardTmpl
|
||||
case len(resultsTmplData) == 1 && common.Command == applyCommandTitle:
|
||||
tmpl = singleProjectApplyTmpl
|
||||
case common.Command == planCommandTitle:
|
||||
@@ -210,12 +204,12 @@ var singleProjectPlanSuccessTmpl = template.Must(template.New("").Parse(
|
||||
"\n" +
|
||||
"{{ if ne .DisableApplyAll true }}---\n" +
|
||||
"* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n" +
|
||||
" * `atlantis apply`{{ end }}" + logTmpl))
|
||||
" * `atlantis apply`\n" +
|
||||
"* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:\n" +
|
||||
" * `atlantis unlock`{{ end }}" + logTmpl))
|
||||
var singleProjectPlanUnsuccessfulTmpl = template.Must(template.New("").Parse(
|
||||
"{{$result := index .Results 0}}Ran {{.Command}} for dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n" +
|
||||
"{{$result.Rendered}}\n" + logTmpl))
|
||||
var singleProjectDiscardTmpl = template.Must(template.New("").Parse(
|
||||
"{{$result := index .Results 0}}Ran {{.Command}} for {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n\n{{$result.Rendered}}\n" + logTmpl))
|
||||
var multiProjectPlanTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
|
||||
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
|
||||
"{{ range $result := .Results }}" +
|
||||
@@ -225,7 +219,10 @@ var multiProjectPlanTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap
|
||||
"### {{add $i 1}}. {{ if $result.ProjectName }}project: `{{$result.ProjectName}}` {{ end }}dir: `{{$result.RepoRelDir}}` workspace: `{{$result.Workspace}}`\n" +
|
||||
"{{$result.Rendered}}\n\n" +
|
||||
"{{ if ne $disableApplyAll true }}---\n{{end}}{{end}}{{ if ne .DisableApplyAll true }}{{ if and (gt (len .Results) 0) (not .PlansDeleted) }}* :fast_forward: To **apply** all unapplied plans from this pull request, comment:\n" +
|
||||
" * `atlantis apply`{{end}}{{end}}" +
|
||||
" * `atlantis apply`\n" +
|
||||
"* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:\n" +
|
||||
" * `atlantis unlock`" +
|
||||
"{{end}}{{end}}" +
|
||||
logTmpl))
|
||||
var multiProjectApplyTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMap()).Parse(
|
||||
"Ran {{.Command}} for {{ len .Results }} projects:\n\n" +
|
||||
@@ -256,8 +253,7 @@ var planSuccessWrappedTmpl = template.Must(template.New("").Parse(
|
||||
// to do next.
|
||||
var planNextSteps = "{{ if .PlanWasDeleted }}This plan was not saved because one or more projects failed and automerge requires all plans pass.{{ else }}* :arrow_forward: To **apply** this plan, comment:\n" +
|
||||
" * `{{.ApplyCmd}}`\n" +
|
||||
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}}), or comment:\n" +
|
||||
" * `{{.DiscardCmd}}`\n" +
|
||||
"* :put_litter_in_its_place: To **delete** this plan click [here]({{.LockURL}})\n" +
|
||||
"* :repeat: To **plan** this project again, comment:\n" +
|
||||
" * `{{.RePlanCmd}}`{{end}}"
|
||||
var applyUnwrappedSuccessTmpl = template.Must(template.New("").Parse(
|
||||
@@ -270,10 +266,6 @@ var applyWrappedSuccessTmpl = template.Must(template.New("").Parse(
|
||||
"{{.Output}}\n" +
|
||||
"```\n" +
|
||||
"</details>"))
|
||||
var discardUnwrappedSuccessTmpl = template.Must(template.New("").Parse(
|
||||
"```diff\n" +
|
||||
"{{.Output}}\n" +
|
||||
"```"))
|
||||
var unwrappedErrTmplText = "**{{.Command}} Error**\n" +
|
||||
"```\n" +
|
||||
"{{.Error}}\n" +
|
||||
|
||||
@@ -159,6 +159,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -195,6 +197,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -229,6 +233,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -328,6 +334,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -462,6 +470,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -982,6 +992,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`
|
||||
} else {
|
||||
exp = `Ran Plan for dir: $.$ workspace: $default$
|
||||
@@ -999,6 +1011,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`
|
||||
}
|
||||
case models.ApplyCommand:
|
||||
@@ -1141,6 +1155,8 @@ $$$
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* $atlantis apply$
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* $atlantis unlock$
|
||||
`
|
||||
expWithBackticks := strings.Replace(exp, "$", "`", -1)
|
||||
Equals(t, expWithBackticks, rendered)
|
||||
|
||||
20
server/events/mocks/matchers/ptr_to_models_projectlock.go
Normal file
20
server/events/mocks/matchers/ptr_to_models_projectlock.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Code generated by pegomock. DO NOT EDIT.
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/petergtz/pegomock"
|
||||
models "github.com/runatlantis/atlantis/server/events/models"
|
||||
)
|
||||
|
||||
func AnyPtrToModelsProjectLock() *models.ProjectLock {
|
||||
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(*models.ProjectLock))(nil)).Elem()))
|
||||
var nullValue *models.ProjectLock
|
||||
return nullValue
|
||||
}
|
||||
|
||||
func EqPtrToModelsProjectLock(value *models.ProjectLock) *models.ProjectLock {
|
||||
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
|
||||
var nullValue *models.ProjectLock
|
||||
return nullValue
|
||||
}
|
||||
155
server/events/mocks/mock_delete_lock_command.go
Normal file
155
server/events/mocks/mock_delete_lock_command.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/runatlantis/atlantis/server/events (interfaces: DeleteLockCommand)
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
models "github.com/runatlantis/atlantis/server/events/models"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockDeleteLockCommand struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockDeleteLockCommand(options ...pegomock.Option) *MockDeleteLockCommand {
|
||||
mock := &MockDeleteLockCommand{}
|
||||
for _, option := range options {
|
||||
option.Apply(mock)
|
||||
}
|
||||
return mock
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) SetFailHandler(fh pegomock.FailHandler) { mock.fail = fh }
|
||||
func (mock *MockDeleteLockCommand) FailHandler() pegomock.FailHandler { return mock.fail }
|
||||
|
||||
func (mock *MockDeleteLockCommand) DeleteLock(id string) (*models.ProjectLock, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockDeleteLockCommand().")
|
||||
}
|
||||
params := []pegomock.Param{id}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("DeleteLock", params, []reflect.Type{reflect.TypeOf((**models.ProjectLock)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 *models.ProjectLock
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(*models.ProjectLock)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) DeleteLocksByPull(repoFullName string, pullNum int) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockDeleteLockCommand().")
|
||||
}
|
||||
params := []pegomock.Param{repoFullName, pullNum}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("DeleteLocksByPull", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) VerifyWasCalledOnce() *VerifierMockDeleteLockCommand {
|
||||
return &VerifierMockDeleteLockCommand{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMockDeleteLockCommand {
|
||||
return &VerifierMockDeleteLockCommand{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMockDeleteLockCommand {
|
||||
return &VerifierMockDeleteLockCommand{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockDeleteLockCommand) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMockDeleteLockCommand {
|
||||
return &VerifierMockDeleteLockCommand{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierMockDeleteLockCommand struct {
|
||||
mock *MockDeleteLockCommand
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockDeleteLockCommand) DeleteLock(id string) *MockDeleteLockCommand_DeleteLock_OngoingVerification {
|
||||
params := []pegomock.Param{id}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DeleteLock", params, verifier.timeout)
|
||||
return &MockDeleteLockCommand_DeleteLock_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockDeleteLockCommand_DeleteLock_OngoingVerification struct {
|
||||
mock *MockDeleteLockCommand
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockDeleteLockCommand_DeleteLock_OngoingVerification) GetCapturedArguments() string {
|
||||
id := c.GetAllCapturedArguments()
|
||||
return id[len(id)-1]
|
||||
}
|
||||
|
||||
func (c *MockDeleteLockCommand_DeleteLock_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMockDeleteLockCommand) DeleteLocksByPull(repoFullName string, pullNum int) *MockDeleteLockCommand_DeleteLocksByPull_OngoingVerification {
|
||||
params := []pegomock.Param{repoFullName, pullNum}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DeleteLocksByPull", params, verifier.timeout)
|
||||
return &MockDeleteLockCommand_DeleteLocksByPull_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MockDeleteLockCommand_DeleteLocksByPull_OngoingVerification struct {
|
||||
mock *MockDeleteLockCommand
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MockDeleteLockCommand_DeleteLocksByPull_OngoingVerification) GetCapturedArguments() (string, int) {
|
||||
repoFullName, pullNum := c.GetAllCapturedArguments()
|
||||
return repoFullName[len(repoFullName)-1], pullNum[len(pullNum)-1]
|
||||
}
|
||||
|
||||
func (c *MockDeleteLockCommand_DeleteLocksByPull_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []int) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(c.methodInvocations))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]int, len(c.methodInvocations))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(int)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -316,9 +316,6 @@ type ProjectCommandContext struct {
|
||||
AutoplanEnabled bool
|
||||
// BaseRepo is the repository that the pull request will be merged into.
|
||||
BaseRepo Repo
|
||||
// DiscardCmd is the command that users should run to discard a plan.
|
||||
// If this is an apply then this will be empty.
|
||||
DiscardCmd string
|
||||
// EscapedCommentArgs are the extra arguments that were added to the atlantis
|
||||
// command, ex. atlantis plan -- -target=resource. We then escape them
|
||||
// by adding a \ before each character so that they can be used within
|
||||
@@ -378,15 +375,14 @@ func SplitRepoFullName(repoFullName string) (owner string, repo string) {
|
||||
|
||||
// ProjectResult is the result of executing a plan/apply for a specific project.
|
||||
type ProjectResult struct {
|
||||
Command CommandName
|
||||
RepoRelDir string
|
||||
Workspace string
|
||||
Error error
|
||||
Failure string
|
||||
PlanSuccess *PlanSuccess
|
||||
ApplySuccess string
|
||||
DiscardSuccess string
|
||||
ProjectName string
|
||||
Command CommandName
|
||||
RepoRelDir string
|
||||
Workspace string
|
||||
Error error
|
||||
Failure string
|
||||
PlanSuccess *PlanSuccess
|
||||
ApplySuccess string
|
||||
ProjectName string
|
||||
}
|
||||
|
||||
// CommitStatus returns the vcs commit status of this project result.
|
||||
@@ -439,8 +435,6 @@ type PlanSuccess struct {
|
||||
RePlanCmd string
|
||||
// ApplyCmd is the command that users should run to apply this plan.
|
||||
ApplyCmd string
|
||||
// DiscardCmd is the command that users should run to discard this plan.
|
||||
DiscardCmd string
|
||||
// HasDiverged is true if we're using the checkout merge strategy and the
|
||||
// branch we're merging into has been updated since we cloned and merged
|
||||
// it.
|
||||
@@ -518,8 +512,8 @@ const (
|
||||
ApplyCommand CommandName = iota
|
||||
// PlanCommand is a command to run terraform plan.
|
||||
PlanCommand
|
||||
// DiscardCommand is a command to discard a previous plan as well as the atlantis lock.
|
||||
DiscardCommand
|
||||
// UnlockCommand is a command to discard previous plans as well as the atlantis locks.
|
||||
UnlockCommand
|
||||
// Adding more? Don't forget to update String() below
|
||||
)
|
||||
|
||||
@@ -530,8 +524,8 @@ func (c CommandName) String() string {
|
||||
return "apply"
|
||||
case PlanCommand:
|
||||
return "plan"
|
||||
case DiscardCommand:
|
||||
return "discard"
|
||||
case UnlockCommand:
|
||||
return "unlock"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -460,3 +460,21 @@ func TestPullStatus_StatusCount(t *testing.T) {
|
||||
Equals(t, 1, ps.StatusCount(models.ErroredApplyStatus))
|
||||
Equals(t, 0, ps.StatusCount(models.ErroredPlanStatus))
|
||||
}
|
||||
|
||||
func TestApplyCommand_String(t *testing.T) {
|
||||
uc := models.ApplyCommand
|
||||
|
||||
Equals(t, "apply", uc.String())
|
||||
}
|
||||
|
||||
func TestPlanCommand_String(t *testing.T) {
|
||||
uc := models.PlanCommand
|
||||
|
||||
Equals(t, "plan", uc.String())
|
||||
}
|
||||
|
||||
func TestUnlockCommand_String(t *testing.T) {
|
||||
uc := models.UnlockCommand
|
||||
|
||||
Equals(t, "unlock", uc.String())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -46,10 +47,6 @@ type ProjectCommandBuilder interface {
|
||||
// comment doesn't specify one project then there may be multiple commands
|
||||
// to be run.
|
||||
BuildApplyCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error)
|
||||
// BuildDiscardCommands builds project discard commands for ctx and comment. If
|
||||
// comment doesn't specify one project then there may be multiple commands
|
||||
// to be run.
|
||||
BuildDiscardCommands(ctx *CommandContext, comment *CommentCommand) ([]models.ProjectCommandContext, error)
|
||||
}
|
||||
|
||||
// DefaultProjectCommandBuilder implements ProjectCommandBuilder.
|
||||
@@ -101,15 +98,6 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, c
|
||||
return []models.ProjectCommandContext{pac}, err
|
||||
}
|
||||
|
||||
// See ProjectCommandBuilder.BuildDiscardCommands.
|
||||
func (p *DefaultProjectCommandBuilder) BuildDiscardCommands(ctx *CommandContext, cmd *CommentCommand) ([]models.ProjectCommandContext, error) {
|
||||
//if !cmd.IsForSpecificProject() {
|
||||
// return p.buildDiscardAllCommands(ctx, cmd.Flags, cmd.Verbose)
|
||||
//}
|
||||
pcc, err := p.buildProjectDiscardCommand(ctx, cmd)
|
||||
return []models.ProjectCommandContext{pcc}, err
|
||||
}
|
||||
|
||||
// buildPlanAllCommands builds plan contexts for all projects we determine were
|
||||
// modified in this ctx.
|
||||
func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext, commentFlags []string, verbose bool) ([]models.ProjectCommandContext, error) {
|
||||
@@ -254,7 +242,9 @@ func (p *DefaultProjectCommandBuilder) buildProjectApplyCommand(ctx *CommandCont
|
||||
defer unlockFn()
|
||||
|
||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, workspace)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
return projCtx, errors.New("no working directory found–did you run plan?")
|
||||
} else if err != nil {
|
||||
return projCtx, err
|
||||
}
|
||||
|
||||
@@ -266,35 +256,6 @@ func (p *DefaultProjectCommandBuilder) buildProjectApplyCommand(ctx *CommandCont
|
||||
return p.buildProjectCommandCtx(ctx, models.ApplyCommand, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace, cmd.Verbose)
|
||||
}
|
||||
|
||||
// cmd must be for only one project.
|
||||
func (p *DefaultProjectCommandBuilder) buildProjectDiscardCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
|
||||
workspace := DefaultWorkspace
|
||||
if cmd.Workspace != "" {
|
||||
workspace = cmd.Workspace
|
||||
}
|
||||
|
||||
var pcc models.ProjectCommandContext
|
||||
ctx.Log.Debug("building plan command")
|
||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, ctx.Pull.Num, workspace)
|
||||
if err != nil {
|
||||
return pcc, err
|
||||
}
|
||||
defer unlockFn()
|
||||
|
||||
ctx.Log.Debug("cloning repository")
|
||||
repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
|
||||
if err != nil {
|
||||
return pcc, err
|
||||
}
|
||||
|
||||
repoRelDir := DefaultRepoRelDir
|
||||
if cmd.RepoRelDir != "" {
|
||||
repoRelDir = cmd.RepoRelDir
|
||||
}
|
||||
|
||||
return p.buildProjectCommandCtx(ctx, models.PlanCommand, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace, cmd.Verbose)
|
||||
}
|
||||
|
||||
// buildProjectCommandCtx builds a context for a single project identified
|
||||
// by the parameters.
|
||||
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(
|
||||
|
||||
@@ -87,8 +87,6 @@ type ProjectCommandRunner interface {
|
||||
Plan(ctx models.ProjectCommandContext) models.ProjectResult
|
||||
// Apply runs terraform apply for the project described by ctx.
|
||||
Apply(ctx models.ProjectCommandContext) models.ProjectResult
|
||||
// Discard discards the plan for project described by ctx.
|
||||
Discard(ctx models.ProjectCommandContext) models.ProjectResult
|
||||
}
|
||||
|
||||
// DefaultProjectCommandRunner implements ProjectCommandRunner.
|
||||
@@ -98,7 +96,6 @@ type DefaultProjectCommandRunner struct {
|
||||
InitStepRunner StepRunner
|
||||
PlanStepRunner StepRunner
|
||||
ApplyStepRunner StepRunner
|
||||
DiscardStepRunner StepRunner
|
||||
RunStepRunner CustomStepRunner
|
||||
EnvStepRunner EnvStepRunner
|
||||
PullApprovedChecker runtime.PullApprovedChecker
|
||||
@@ -135,20 +132,6 @@ func (p *DefaultProjectCommandRunner) Apply(ctx models.ProjectCommandContext) mo
|
||||
}
|
||||
}
|
||||
|
||||
// Discard deletes the atlantis plan and discards the lock for the project described by ctx.
|
||||
func (p *DefaultProjectCommandRunner) Discard(ctx models.ProjectCommandContext) models.ProjectResult {
|
||||
discardOut, failure, err := p.doDiscard(ctx)
|
||||
return models.ProjectResult{
|
||||
Command: models.PlanCommand,
|
||||
Error: err,
|
||||
Failure: failure,
|
||||
DiscardSuccess: discardOut,
|
||||
RepoRelDir: ctx.RepoRelDir,
|
||||
Workspace: ctx.Workspace,
|
||||
ProjectName: ctx.ProjectName,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (*models.PlanSuccess, string, error) {
|
||||
// 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.RepoRelDir))
|
||||
@@ -193,7 +176,6 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (
|
||||
TerraformOutput: strings.Join(outputs, "\n"),
|
||||
RePlanCmd: ctx.RePlanCmd,
|
||||
ApplyCmd: ctx.ApplyCmd,
|
||||
DiscardCmd: ctx.DiscardCmd,
|
||||
HasDiverged: hasDiverged,
|
||||
}, "", nil
|
||||
}
|
||||
@@ -281,68 +263,3 @@ func (p *DefaultProjectCommandRunner) doApply(ctx models.ProjectCommandContext)
|
||||
}
|
||||
return strings.Join(outputs, "\n"), "", nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) doDiscard(ctx models.ProjectCommandContext) (discardOut string, failure string, err error) {
|
||||
// Definitely need this to prevent applying. But need to do something more to
|
||||
// Lead to generation of message: plan is required
|
||||
//if err := p.WorkingDir.Delete(ctx.BaseRepo, ctx.Pull); err != nil {
|
||||
// return "", "", errors.Wrap(err, "cleaning workspace")
|
||||
//}
|
||||
|
||||
// TryLock is idembpotent so OK to run even if lock already exists - which will normally be the case
|
||||
// TODO try to expose a method to identify if a lock is there to begin with, if not error
|
||||
lockAttempt, err := p.Locker.TryLock(ctx.Log, ctx.Pull, ctx.User, ctx.Workspace, models.NewProject(ctx.BaseRepo.FullName, ctx.RepoRelDir))
|
||||
ctx.Log.Err("discard: attempting to lock")
|
||||
if err != nil {
|
||||
ctx.Log.Err("discard: failed to lock: %v", err)
|
||||
return "", "", errors.Wrap(err, "acquiring lock")
|
||||
}
|
||||
|
||||
if !lockAttempt.LockAcquired {
|
||||
return "", lockAttempt.LockFailureReason, nil
|
||||
}
|
||||
ctx.Log.Debug("discard: acquired lock for project")
|
||||
ctx.Log.Debug("discard: attempting to unlock project")
|
||||
if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil {
|
||||
ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
|
||||
}
|
||||
|
||||
/*
|
||||
if ctx.BaseRepo != (models.Repo{}) {
|
||||
unlock, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, ctx.Pull.Num, ctx.Workspace)
|
||||
if err != nil {
|
||||
ctx.Log.Err("unable to obtain working dir lock when trying to delete plans: %s", err)
|
||||
} else {
|
||||
defer unlock()
|
||||
// nolint: vetshadow
|
||||
if err := p.WorkingDir.DeleteForWorkspace(ctx.BaseRepo, ctx.Pull, ctx.Workspace); err != nil {
|
||||
ctx.Log.Err("unable to delete workspace: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.DB.DeleteProjectStatus(lock.Pull, lock.Workspace, lock.Project.Path); err != nil {
|
||||
l.Logger.Err("unable to delete project status: %s", err)
|
||||
}
|
||||
|
||||
// Once the lock has been deleted, comment back on the pull request.
|
||||
comment := fmt.Sprintf("**Warning**: The plan for dir: `%s` workspace: `%s` was **discarded** via the Atlantis UI.\n\n"+
|
||||
"To `apply` this plan you must run `plan` again.", lock.Project.Path, lock.Workspace)
|
||||
err = l.VCSClient.CreateComment(lock.Pull.BaseRepo, lock.Pull.Num, comment)
|
||||
if err != nil {
|
||||
l.respond(w, logging.Error, http.StatusInternalServerError, "Failed commenting on pull request: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
return "discard successful", "", nil
|
||||
|
||||
// Finally, delete locks. We do this last because when someone
|
||||
// unlocks a project, right now we don't actually delete the plan
|
||||
// so we might have plans laying around but no locks.
|
||||
//locks, err := p.Locker(repo.FullName, pull.Num)
|
||||
//if err != nil {
|
||||
// return nil, "", errors.Wrap(err, "cleaning up locks")
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type LocksController struct {
|
||||
WorkingDir events.WorkingDir
|
||||
WorkingDirLocker events.WorkingDirLocker
|
||||
DB *db.BoltDB
|
||||
DeleteLockCommand events.DeleteLockCommand
|
||||
}
|
||||
|
||||
// GetLock is the GET /locks/{id} route. It renders the lock detail view.
|
||||
@@ -84,11 +85,13 @@ func (l *LocksController) DeleteLock(w http.ResponseWriter, r *http.Request) {
|
||||
l.respond(w, logging.Warn, http.StatusBadRequest, "Invalid lock id %q. Failed with error: %s", id, err)
|
||||
return
|
||||
}
|
||||
lock, err := l.Locker.Unlock(idUnencoded)
|
||||
|
||||
lock, err := l.DeleteLockCommand.DeleteLock(idUnencoded)
|
||||
if err != nil {
|
||||
l.respond(w, logging.Error, http.StatusInternalServerError, "deleting lock failed with: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if lock == nil {
|
||||
l.respond(w, logging.Info, http.StatusNotFound, "No lock found at id %q", idUnencoded)
|
||||
return
|
||||
@@ -96,29 +99,12 @@ func (l *LocksController) DeleteLock(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// NOTE: Because BaseRepo was added to the PullRequest model later, previous
|
||||
// installations of Atlantis will have locks in their DB that do not have
|
||||
// this field on PullRequest. We skip commenting and deleting the working dir in this case.
|
||||
// this field on PullRequest. We skip commenting in this case.
|
||||
if lock.Pull.BaseRepo != (models.Repo{}) {
|
||||
unlock, err := l.WorkingDirLocker.TryLock(lock.Pull.BaseRepo.FullName, lock.Pull.Num, lock.Workspace)
|
||||
if err != nil {
|
||||
l.Logger.Err("unable to obtain working dir lock when trying to delete old plans: %s", err)
|
||||
} else {
|
||||
defer unlock()
|
||||
// nolint: vetshadow
|
||||
if err := l.WorkingDir.DeleteForWorkspace(lock.Pull.BaseRepo, lock.Pull, lock.Workspace); err != nil {
|
||||
l.Logger.Err("unable to delete workspace: %s", err)
|
||||
}
|
||||
}
|
||||
if err := l.DB.DeleteProjectStatus(lock.Pull, lock.Workspace, lock.Project.Path); err != nil {
|
||||
l.Logger.Err("unable to delete project status: %s", err)
|
||||
}
|
||||
|
||||
// Once the lock has been deleted, comment back on the pull request.
|
||||
comment := fmt.Sprintf("**Warning**: The plan for dir: `%s` workspace: `%s` was **discarded** via the Atlantis UI.\n\n"+
|
||||
"To `apply` this plan you must run `plan` again.", lock.Project.Path, lock.Workspace)
|
||||
err = l.VCSClient.CreateComment(lock.Pull.BaseRepo, lock.Pull.Num, comment)
|
||||
if err != nil {
|
||||
l.respond(w, logging.Error, http.StatusInternalServerError, "Failed commenting on pull request: %s", err)
|
||||
return
|
||||
if err = l.VCSClient.CreateComment(lock.Pull.BaseRepo, lock.Pull.Num, comment); err != nil {
|
||||
l.Logger.Warn("failed commenting on pull request: %s", err)
|
||||
}
|
||||
} else {
|
||||
l.Logger.Debug("skipping commenting on pull request and deleting workspace because BaseRepo field is empty")
|
||||
|
||||
@@ -143,11 +143,11 @@ func TestDeleteLock_InvalidLockID(t *testing.T) {
|
||||
func TestDeleteLock_LockerErr(t *testing.T) {
|
||||
t.Log("If there is an error retrieving the lock, a 500 is returned")
|
||||
RegisterMockTestingT(t)
|
||||
l := mocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(nil, errors.New("err"))
|
||||
dlc := mocks2.NewMockDeleteLockCommand()
|
||||
When(dlc.DeleteLock("id")).ThenReturn(nil, errors.New("err"))
|
||||
lc := server.LocksController{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
DeleteLockCommand: dlc,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
||||
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
||||
@@ -159,11 +159,11 @@ func TestDeleteLock_LockerErr(t *testing.T) {
|
||||
func TestDeleteLock_None(t *testing.T) {
|
||||
t.Log("If there is no lock at that ID we get a 404")
|
||||
RegisterMockTestingT(t)
|
||||
l := mocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(nil, nil)
|
||||
dlc := mocks2.NewMockDeleteLockCommand()
|
||||
When(dlc.DeleteLock("id")).ThenReturn(nil, nil)
|
||||
lc := server.LocksController{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
DeleteLockCommand: dlc,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
||||
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
||||
@@ -175,14 +175,13 @@ func TestDeleteLock_None(t *testing.T) {
|
||||
func TestDeleteLock_OldFormat(t *testing.T) {
|
||||
t.Log("If the lock doesn't have BaseRepo set it is deleted successfully")
|
||||
RegisterMockTestingT(t)
|
||||
|
||||
cp := vcsmocks.NewMockClient()
|
||||
l := mocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{}, nil)
|
||||
dlc := mocks2.NewMockDeleteLockCommand()
|
||||
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{}, nil)
|
||||
lc := server.LocksController{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
DeleteLockCommand: dlc,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
||||
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
||||
@@ -193,50 +192,46 @@ func TestDeleteLock_OldFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeleteLock_CommentFailed(t *testing.T) {
|
||||
t.Log("If the commenting fails we return an error")
|
||||
t.Log("If the commenting fails we still return success")
|
||||
RegisterMockTestingT(t)
|
||||
|
||||
cp := vcsmocks.NewMockClient()
|
||||
workingDir := mocks2.NewMockWorkingDir()
|
||||
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
||||
When(cp.CreateComment(AnyRepo(), AnyInt(), AnyString())).ThenReturn(errors.New("err"))
|
||||
l := mocks.NewMockLocker()
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{
|
||||
dlc := mocks2.NewMockDeleteLockCommand()
|
||||
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{
|
||||
Pull: models.PullRequest{
|
||||
BaseRepo: models.Repo{FullName: "owner/repo"},
|
||||
},
|
||||
}, nil)
|
||||
cp := vcsmocks.NewMockClient()
|
||||
workingDir := mocks2.NewMockWorkingDir()
|
||||
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
||||
When(cp.CreateComment(AnyRepo(), AnyInt(), AnyString())).ThenReturn(errors.New("err"))
|
||||
tmp, cleanup := TempDir(t)
|
||||
defer cleanup()
|
||||
db, err := db.New(tmp)
|
||||
Ok(t, err)
|
||||
lc := server.LocksController{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
WorkingDir: workingDir,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
DB: db,
|
||||
DeleteLockCommand: dlc,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
WorkingDir: workingDir,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
DB: db,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
||||
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
||||
w := httptest.NewRecorder()
|
||||
lc.DeleteLock(w, req)
|
||||
responseContains(t, w, http.StatusInternalServerError, "Failed commenting on pull request: err")
|
||||
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
||||
}
|
||||
|
||||
func TestDeleteLock_CommentSuccess(t *testing.T) {
|
||||
t.Log("We should comment back on the pull request if the lock is deleted")
|
||||
RegisterMockTestingT(t)
|
||||
|
||||
cp := vcsmocks.NewMockClient()
|
||||
l := mocks.NewMockLocker()
|
||||
workingDir := mocks2.NewMockWorkingDir()
|
||||
workingDirLocker := events.NewDefaultWorkingDirLocker()
|
||||
dlc := mocks2.NewMockDeleteLockCommand()
|
||||
pull := models.PullRequest{
|
||||
BaseRepo: models.Repo{FullName: "owner/repo"},
|
||||
}
|
||||
When(l.Unlock("id")).ThenReturn(&models.ProjectLock{
|
||||
When(dlc.DeleteLock("id")).ThenReturn(&models.ProjectLock{
|
||||
Pull: pull,
|
||||
Workspace: "workspace",
|
||||
Project: models.Project{
|
||||
@@ -249,12 +244,10 @@ func TestDeleteLock_CommentSuccess(t *testing.T) {
|
||||
db, err := db.New(tmp)
|
||||
Ok(t, err)
|
||||
lc := server.LocksController{
|
||||
Locker: l,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
WorkingDir: workingDir,
|
||||
DB: db,
|
||||
DeleteLockCommand: dlc,
|
||||
Logger: logging.NewNoopLogger(),
|
||||
VCSClient: cp,
|
||||
DB: db,
|
||||
}
|
||||
req, _ := http.NewRequest("GET", "", bytes.NewBuffer(nil))
|
||||
req = mux.SetURLVars(req, map[string]string{"id": "id"})
|
||||
@@ -264,5 +257,4 @@ func TestDeleteLock_CommentSuccess(t *testing.T) {
|
||||
cp.VerifyWasCalled(Once()).CreateComment(pull.BaseRepo, pull.Num,
|
||||
"**Warning**: The plan for dir: `path` workspace: `workspace` was **discarded** via the Atlantis UI.\n\n"+
|
||||
"To `apply` this plan you must run `plan` again.")
|
||||
workingDir.VerifyWasCalledOnce().DeleteForWorkspace(pull.BaseRepo, pull, "workspace")
|
||||
}
|
||||
|
||||
@@ -251,6 +251,14 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
Locker: lockingClient,
|
||||
VCSClient: vcsClient,
|
||||
}
|
||||
deleteLockCommand := &events.DefaultDeleteLockCommand{
|
||||
Locker: lockingClient,
|
||||
Logger: logger,
|
||||
WorkingDir: workingDir,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
DB: boltdb,
|
||||
}
|
||||
|
||||
parsedURL, err := ParseAtlantisURL(userConfig.AtlantisURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err,
|
||||
@@ -371,6 +379,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
WorkingDir: workingDir,
|
||||
PendingPlanFinder: pendingPlanFinder,
|
||||
DB: boltdb,
|
||||
DeleteLockCommand: deleteLockCommand,
|
||||
GlobalAutomerge: userConfig.Automerge,
|
||||
Drainer: drainer,
|
||||
}
|
||||
@@ -388,6 +397,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
|
||||
WorkingDir: workingDir,
|
||||
WorkingDirLocker: workingDirLocker,
|
||||
DB: boltdb,
|
||||
DeleteLockCommand: deleteLockCommand,
|
||||
}
|
||||
eventsController := &EventsController{
|
||||
CommandRunner: commandRunner,
|
||||
|
||||
@@ -61,3 +61,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -61,3 +61,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -29,3 +29,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -29,3 +29,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -29,3 +29,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -67,3 +67,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -65,3 +65,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -39,3 +39,5 @@ Plan: 3 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -39,3 +39,5 @@ Plan: 3 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -39,3 +39,5 @@ Plan: 3 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -39,3 +39,5 @@ Plan: 3 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -29,3 +29,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -29,3 +29,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
@@ -63,3 +63,5 @@ Plan: 1 to add, 0 to change, 0 to destroy.
|
||||
---
|
||||
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
|
||||
* `atlantis apply`
|
||||
* :put_litter_in_its_place: To delete all plans and locks for the PR, comment:
|
||||
* `atlantis unlock`
|
||||
|
||||
Reference in New Issue
Block a user