mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-09 04:18:30 +00:00
First pass at naked plan/apply working on all.
This commit is contained in:
@@ -101,15 +101,7 @@ func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo
|
||||
return
|
||||
}
|
||||
|
||||
var results []ProjectResult
|
||||
for _, cmd := range projectCmds {
|
||||
res := c.ProjectCommandRunner.Plan(cmd)
|
||||
results = append(results, ProjectResult{
|
||||
ProjectCommandResult: res,
|
||||
RepoRelDir: cmd.RepoRelDir,
|
||||
Workspace: cmd.Workspace,
|
||||
})
|
||||
}
|
||||
results := c.runProjectCmds(projectCmds, PlanCommand)
|
||||
c.updatePull(ctx, AutoplanCommand{}, CommandResult{ProjectResults: results})
|
||||
}
|
||||
|
||||
@@ -152,45 +144,48 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
|
||||
BaseRepo: baseRepo,
|
||||
}
|
||||
defer c.logPanics(ctx)
|
||||
|
||||
if !c.validateCtxAndComment(ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.CommitStatusUpdater.Update(ctx.BaseRepo, ctx.Pull, models.PendingCommitStatus, cmd.CommandName()); err != nil {
|
||||
ctx.Log.Warn("unable to update commit status: %s", err)
|
||||
}
|
||||
|
||||
var result ProjectCommandResult
|
||||
var projectCmds []models.ProjectCommandContext
|
||||
switch cmd.Name {
|
||||
case PlanCommand:
|
||||
projectCmd, err := c.ProjectCommandBuilder.BuildPlanCommand(ctx, cmd)
|
||||
if err != nil {
|
||||
c.updatePull(ctx, cmd, CommandResult{Error: err})
|
||||
return
|
||||
}
|
||||
result = c.ProjectCommandRunner.Plan(projectCmd)
|
||||
projectCmds, err = c.ProjectCommandBuilder.BuildPlanCommands(ctx, cmd)
|
||||
case ApplyCommand:
|
||||
projectCmd, err := c.ProjectCommandBuilder.BuildApplyCommand(ctx, cmd)
|
||||
if err != nil {
|
||||
c.updatePull(ctx, cmd, CommandResult{Error: err})
|
||||
return
|
||||
}
|
||||
result = c.ProjectCommandRunner.Apply(projectCmd)
|
||||
projectCmds, err = c.ProjectCommandBuilder.BuildApplyCommands(ctx, cmd)
|
||||
default:
|
||||
ctx.Log.Err("failed to determine desired command, neither plan nor apply")
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.updatePull(ctx, cmd, CommandResult{Error: err})
|
||||
return
|
||||
}
|
||||
results := c.runProjectCmds(projectCmds, cmd.Name)
|
||||
c.updatePull(
|
||||
ctx,
|
||||
cmd,
|
||||
CommandResult{
|
||||
ProjectResults: []ProjectResult{{
|
||||
RepoRelDir: cmd.RepoRelDir,
|
||||
Workspace: cmd.Workspace,
|
||||
ProjectCommandResult: result,
|
||||
}}})
|
||||
ProjectResults: results})
|
||||
}
|
||||
|
||||
func (c *DefaultCommandRunner) runProjectCmds(cmds []models.ProjectCommandContext, cmdName CommandName) []ProjectResult {
|
||||
var results []ProjectResult
|
||||
for _, pCmd := range cmds {
|
||||
var res ProjectResult
|
||||
switch cmdName {
|
||||
case PlanCommand:
|
||||
res = c.ProjectCommandRunner.Plan(pCmd)
|
||||
case ApplyCommand:
|
||||
res = c.ProjectCommandRunner.Apply(pCmd)
|
||||
}
|
||||
results = append(results, res)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (c *DefaultCommandRunner) getGithubData(baseRepo models.Repo, pullNum int) (models.PullRequest, models.Repo, error) {
|
||||
|
||||
@@ -154,57 +154,3 @@ func TestRunCommentCommand_ClosedPull(t *testing.T) {
|
||||
ch.RunCommentCommand(fixtures.GithubRepo, &fixtures.GithubRepo, nil, fixtures.User, fixtures.Pull.Num, nil)
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(fixtures.GithubRepo, modelPull.Num, "Atlantis commands can't be run on closed pull requests")
|
||||
}
|
||||
|
||||
func TestRunCommentCommand_FullRun(t *testing.T) {
|
||||
pull := &github.PullRequest{
|
||||
State: github.String("closed"),
|
||||
}
|
||||
expCmdResult := events.CommandResult{
|
||||
ProjectResults: []events.ProjectResult{
|
||||
{
|
||||
RepoRelDir: ".",
|
||||
Workspace: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range []events.CommandName{events.PlanCommand, events.ApplyCommand} {
|
||||
setup(t)
|
||||
cmd := events.NewCommentCommand(".", nil, c, false, "default", "")
|
||||
When(githubGetter.GetPullRequest(fixtures.GithubRepo, fixtures.Pull.Num)).ThenReturn(pull, nil)
|
||||
When(eventParsing.ParseGithubPull(pull)).ThenReturn(fixtures.Pull, fixtures.GithubRepo, fixtures.GithubRepo, nil)
|
||||
|
||||
cmdCtx := models.ProjectCommandContext{RepoRelDir: "."}
|
||||
switch c {
|
||||
case events.PlanCommand:
|
||||
When(projectCommandBuilder.BuildPlanCommand(matchers.AnyPtrToEventsCommandContext(), matchers.AnyPtrToEventsCommentCommand())).ThenReturn(cmdCtx, nil)
|
||||
case events.ApplyCommand:
|
||||
When(projectCommandBuilder.BuildApplyCommand(matchers.AnyPtrToEventsCommandContext(), matchers.AnyPtrToEventsCommentCommand())).ThenReturn(cmdCtx, nil)
|
||||
}
|
||||
|
||||
ch.RunCommentCommand(fixtures.GithubRepo, nil, nil, fixtures.User, fixtures.Pull.Num, cmd)
|
||||
|
||||
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.PendingCommitStatus, c)
|
||||
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
|
||||
Equals(t, expCmdResult, response)
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunAutoplanCommands(t *testing.T) {
|
||||
expCmdResult := events.CommandResult{
|
||||
ProjectResults: []events.ProjectResult{
|
||||
{
|
||||
RepoRelDir: ".",
|
||||
Workspace: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
setup(t)
|
||||
When(projectCommandBuilder.BuildAutoplanCommands(matchers.AnyPtrToEventsCommandContext())).ThenReturn([]models.ProjectCommandContext{{RepoRelDir: ".", Workspace: "default"}}, nil)
|
||||
ch.RunAutoplanCommand(fixtures.GithubRepo, fixtures.GithubRepo, fixtures.Pull, fixtures.User)
|
||||
|
||||
ghStatus.VerifyWasCalledOnce().Update(fixtures.GithubRepo, fixtures.Pull, models.PendingCommitStatus, events.PlanCommand)
|
||||
_, _, response := ghStatus.VerifyWasCalledOnce().UpdateProjectResult(matchers.AnyPtrToEventsCommandContext(), matchers.AnyEventsCommandName(), matchers.AnyEventsCommandResult()).GetCapturedArguments()
|
||||
Equals(t, expCmdResult, response)
|
||||
vcsClient.VerifyWasCalledOnce().CreateComment(matchers.AnyModelsRepo(), AnyInt(), AnyString())
|
||||
}
|
||||
|
||||
20
server/events/mocks/matchers/events_projectresult.go
Normal file
20
server/events/mocks/matchers/events_projectresult.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/petergtz/pegomock"
|
||||
events "github.com/runatlantis/atlantis/server/events"
|
||||
)
|
||||
|
||||
func AnyEventsProjectResult() events.ProjectResult {
|
||||
pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(events.ProjectResult))(nil)).Elem()))
|
||||
var nullValue events.ProjectResult
|
||||
return nullValue
|
||||
}
|
||||
|
||||
func EqEventsProjectResult(value events.ProjectResult) events.ProjectResult {
|
||||
pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value})
|
||||
var nullValue events.ProjectResult
|
||||
return nullValue
|
||||
}
|
||||
@@ -35,14 +35,14 @@ func (mock *MockProjectCommandBuilder) BuildAutoplanCommands(ctx *events.Command
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockProjectCommandBuilder) BuildPlanCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) (models.ProjectCommandContext, error) {
|
||||
func (mock *MockProjectCommandBuilder) BuildPlanCommands(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
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("BuildPlanCommands", 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)
|
||||
ret0 = result[0].([]models.ProjectCommandContext)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
@@ -51,14 +51,14 @@ func (mock *MockProjectCommandBuilder) BuildPlanCommand(ctx *events.CommandConte
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockProjectCommandBuilder) BuildApplyCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) (models.ProjectCommandContext, error) {
|
||||
func (mock *MockProjectCommandBuilder) BuildApplyCommands(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
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("BuildApplyCommands", 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)
|
||||
ret0 = result[0].([]models.ProjectCommandContext)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
@@ -112,23 +112,23 @@ func (c *ProjectCommandBuilder_BuildAutoplanCommands_OngoingVerification) GetAll
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierProjectCommandBuilder) BuildPlanCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification {
|
||||
func (verifier *VerifierProjectCommandBuilder) BuildPlanCommands(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildPlanCommands_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}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildPlanCommands", params)
|
||||
return &ProjectCommandBuilder_BuildPlanCommands_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type ProjectCommandBuilder_BuildPlanCommand_OngoingVerification struct {
|
||||
type ProjectCommandBuilder_BuildPlanCommands_OngoingVerification struct {
|
||||
mock *MockProjectCommandBuilder
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification) GetCapturedArguments() (*events.CommandContext, *events.CommentCommand) {
|
||||
func (c *ProjectCommandBuilder_BuildPlanCommands_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) {
|
||||
func (c *ProjectCommandBuilder_BuildPlanCommands_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]))
|
||||
@@ -143,23 +143,23 @@ func (c *ProjectCommandBuilder_BuildPlanCommand_OngoingVerification) GetAllCaptu
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierProjectCommandBuilder) BuildApplyCommand(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildApplyCommand_OngoingVerification {
|
||||
func (verifier *VerifierProjectCommandBuilder) BuildApplyCommands(ctx *events.CommandContext, commentCommand *events.CommentCommand) *ProjectCommandBuilder_BuildApplyCommands_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}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "BuildApplyCommands", params)
|
||||
return &ProjectCommandBuilder_BuildApplyCommands_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type ProjectCommandBuilder_BuildApplyCommand_OngoingVerification struct {
|
||||
type ProjectCommandBuilder_BuildApplyCommands_OngoingVerification struct {
|
||||
mock *MockProjectCommandBuilder
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *ProjectCommandBuilder_BuildApplyCommand_OngoingVerification) GetCapturedArguments() (*events.CommandContext, *events.CommentCommand) {
|
||||
func (c *ProjectCommandBuilder_BuildApplyCommands_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) {
|
||||
func (c *ProjectCommandBuilder_BuildApplyCommands_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]))
|
||||
|
||||
@@ -19,25 +19,25 @@ func NewMockProjectCommandRunner() *MockProjectCommandRunner {
|
||||
return &MockProjectCommandRunner{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockProjectCommandRunner) Plan(ctx models.ProjectCommandContext) events.ProjectCommandResult {
|
||||
func (mock *MockProjectCommandRunner) Plan(ctx models.ProjectCommandContext) events.ProjectResult {
|
||||
params := []pegomock.Param{ctx}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Plan", params, []reflect.Type{reflect.TypeOf((*events.ProjectCommandResult)(nil)).Elem()})
|
||||
var ret0 events.ProjectCommandResult
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Plan", params, []reflect.Type{reflect.TypeOf((*events.ProjectResult)(nil)).Elem()})
|
||||
var ret0 events.ProjectResult
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(events.ProjectCommandResult)
|
||||
ret0 = result[0].(events.ProjectResult)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockProjectCommandRunner) Apply(ctx models.ProjectCommandContext) events.ProjectCommandResult {
|
||||
func (mock *MockProjectCommandRunner) Apply(ctx models.ProjectCommandContext) events.ProjectResult {
|
||||
params := []pegomock.Param{ctx}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Apply", params, []reflect.Type{reflect.TypeOf((*events.ProjectCommandResult)(nil)).Elem()})
|
||||
var ret0 events.ProjectCommandResult
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Apply", params, []reflect.Type{reflect.TypeOf((*events.ProjectResult)(nil)).Elem()})
|
||||
var ret0 events.ProjectResult
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(events.ProjectCommandResult)
|
||||
ret0 = result[0].(events.ProjectResult)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
|
||||
@@ -51,6 +51,22 @@ func (mock *MockWorkingDir) GetWorkingDir(r models.Repo, p models.PullRequest, w
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) (string, error) {
|
||||
params := []pegomock.Param{r, p}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("GetPullDir", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockWorkingDir) Delete(r models.Repo, p models.PullRequest) error {
|
||||
params := []pegomock.Param{r, p}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
@@ -171,6 +187,37 @@ func (c *WorkingDir_GetWorkingDir_OngoingVerification) GetAllCapturedArguments()
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierWorkingDir) GetPullDir(r models.Repo, p models.PullRequest) *WorkingDir_GetPullDir_OngoingVerification {
|
||||
params := []pegomock.Param{r, p}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetPullDir", params)
|
||||
return &WorkingDir_GetPullDir_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type WorkingDir_GetPullDir_OngoingVerification struct {
|
||||
mock *MockWorkingDir
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *WorkingDir_GetPullDir_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) {
|
||||
r, p := c.GetAllCapturedArguments()
|
||||
return r[len(r)-1], p[len(p)-1]
|
||||
}
|
||||
|
||||
func (c *WorkingDir_GetPullDir_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) {
|
||||
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.PullRequest, len(params[1]))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(models.PullRequest)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierWorkingDir) Delete(r models.Repo, p models.PullRequest) *WorkingDir_Delete_OngoingVerification {
|
||||
params := []pegomock.Param{r, p}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params)
|
||||
|
||||
@@ -286,4 +286,6 @@ type ProjectCommandContext struct {
|
||||
// ex. atlantis plan -- -target=resource
|
||||
CommentArgs []string
|
||||
Workspace string
|
||||
// Verbose is true when the user would like verbose output.
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/pkg/errors"
|
||||
@@ -12,12 +15,17 @@ import (
|
||||
"github.com/runatlantis/atlantis/server/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultRepoRelDir = "."
|
||||
DefaultWorkspace = "default"
|
||||
)
|
||||
|
||||
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_project_command_builder.go ProjectCommandBuilder
|
||||
|
||||
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)
|
||||
BuildPlanCommands(ctx *CommandContext, commentCommand *CommentCommand) ([]models.ProjectCommandContext, error)
|
||||
BuildApplyCommands(ctx *CommandContext, commentCommand *CommentCommand) ([]models.ProjectCommandContext, error)
|
||||
}
|
||||
|
||||
type DefaultProjectCommandBuilder struct {
|
||||
@@ -35,8 +43,25 @@ type TerraformExec interface {
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext) ([]models.ProjectCommandContext, error) {
|
||||
cmds, err := p.BuildPlanAllCommands(ctx, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Filter out projects where autoplanning is specifically disabled.
|
||||
var autoplanEnabled []models.ProjectCommandContext
|
||||
for _, cmd := range cmds {
|
||||
if cmd.ProjectConfig != nil && !cmd.ProjectConfig.Autoplan.Enabled {
|
||||
ctx.Log.Debug("ignoring project at dir %q, workspace: %q because autoplan is disabled", cmd.RepoRelDir, cmd.Workspace)
|
||||
continue
|
||||
}
|
||||
autoplanEnabled = append(autoplanEnabled, cmd)
|
||||
}
|
||||
return autoplanEnabled, nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildPlanAllCommands(ctx *CommandContext, commentFlags []string, verbose bool) ([]models.ProjectCommandContext, error) {
|
||||
// Need to lock the workspace we're about to clone to.
|
||||
workspace := "default"
|
||||
workspace := DefaultWorkspace
|
||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, workspace, ctx.Pull.Num)
|
||||
if err != nil {
|
||||
ctx.Log.Warn("workspace was locked")
|
||||
@@ -94,8 +119,9 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext
|
||||
RepoRelDir: mp.Path,
|
||||
ProjectConfig: nil,
|
||||
GlobalConfig: nil,
|
||||
CommentArgs: nil,
|
||||
Workspace: "default",
|
||||
CommentArgs: commentFlags,
|
||||
Workspace: DefaultWorkspace,
|
||||
Verbose: verbose,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -105,7 +131,7 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx.Log.Info("%d projects are to be autoplanned based on their when_modified config", len(matchingProjects))
|
||||
ctx.Log.Info("%d projects are to be planned based on their when_modified config", len(matchingProjects))
|
||||
|
||||
// Use for i instead of range because need to get the pointer to the
|
||||
// project config.
|
||||
@@ -117,55 +143,163 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext
|
||||
Pull: ctx.Pull,
|
||||
User: ctx.User,
|
||||
Log: ctx.Log,
|
||||
CommentArgs: nil,
|
||||
CommentArgs: commentFlags,
|
||||
Workspace: mp.Workspace,
|
||||
RepoRelDir: mp.Dir,
|
||||
ProjectConfig: &mp,
|
||||
GlobalConfig: &config,
|
||||
Verbose: verbose,
|
||||
})
|
||||
}
|
||||
}
|
||||
return projCtxs, nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildPlanCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
|
||||
var projCtx models.ProjectCommandContext
|
||||
func (p *DefaultProjectCommandBuilder) BuildProjectPlanCommand(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, cmd.Workspace, ctx.Pull.Num)
|
||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, workspace, ctx.Pull.Num)
|
||||
if err != nil {
|
||||
return projCtx, err
|
||||
return pcc, err
|
||||
}
|
||||
defer unlockFn()
|
||||
|
||||
ctx.Log.Debug("cloning repository")
|
||||
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, cmd.Workspace)
|
||||
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
|
||||
if err != nil {
|
||||
return projCtx, err
|
||||
return pcc, err
|
||||
}
|
||||
|
||||
return p.buildProjectCommandCtx(ctx, cmd, repoDir)
|
||||
repoRelDir := DefaultRepoRelDir
|
||||
if cmd.RepoRelDir != "" {
|
||||
repoRelDir = cmd.RepoRelDir
|
||||
}
|
||||
|
||||
return p.buildProjectCommandCtx(ctx, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace)
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildApplyCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
|
||||
var projCtx models.ProjectCommandContext
|
||||
func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *CommandContext, cmd *CommentCommand) ([]models.ProjectCommandContext, error) {
|
||||
if !cmd.IsForSpecificProject() {
|
||||
return p.BuildPlanAllCommands(ctx, cmd.Flags, cmd.Verbose)
|
||||
}
|
||||
pcc, err := p.BuildProjectPlanCommand(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []models.ProjectCommandContext{pcc}, nil
|
||||
}
|
||||
|
||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, cmd.Workspace, ctx.Pull.Num)
|
||||
func (p *DefaultProjectCommandBuilder) BuildApplyAllCommands(ctx *CommandContext, commentCmd *CommentCommand) ([]models.ProjectCommandContext, error) {
|
||||
// lock all dirs in this pull request
|
||||
unlockFn, err := p.WorkingDirLocker.TryLockPull(ctx.BaseRepo.FullName, ctx.Pull.Num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer unlockFn()
|
||||
|
||||
pullDir, err := p.WorkingDir.GetPullDir(ctx.BaseRepo, ctx.Pull)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plans, err := p.findUnappliedPlans(pullDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cmds []models.ProjectCommandContext
|
||||
for _, plan := range plans {
|
||||
cmd, err := p.buildProjectCommandCtx(ctx, commentCmd.ProjectName, commentCmd.Flags, plan.RepoDir, plan.RepoRelDir, plan.Workspace)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "building command for dir %q", plan.RepoRelDir)
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
type UnappliedPlan struct {
|
||||
RepoDir string
|
||||
RepoRelDir string
|
||||
Workspace string
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) findUnappliedPlans(pullDir string) ([]UnappliedPlan, error) {
|
||||
workspaceDirs, err := ioutil.ReadDir(pullDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var plans []UnappliedPlan
|
||||
for _, workspaceDir := range workspaceDirs {
|
||||
workspace := workspaceDir.Name()
|
||||
repoDir := filepath.Join(pullDir, workspace)
|
||||
|
||||
err := filepath.Walk(repoDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if the plan is for the right workspace,
|
||||
if !info.IsDir() && filepath.Ext(path) == ".tfplan" {
|
||||
repoRelDir, _ := filepath.Rel(repoDir, filepath.Dir(path))
|
||||
plans = append(plans, UnappliedPlan{
|
||||
RepoDir: repoDir,
|
||||
RepoRelDir: repoRelDir,
|
||||
Workspace: workspace,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "searching dir %q for unapplied plans", repoDir)
|
||||
}
|
||||
}
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, cmd *CommentCommand) ([]models.ProjectCommandContext, error) {
|
||||
if !cmd.IsForSpecificProject() {
|
||||
return p.BuildApplyAllCommands(ctx, cmd)
|
||||
}
|
||||
pac, err := p.BuildProjectApplyCommand(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []models.ProjectCommandContext{pac}, nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) BuildProjectApplyCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) {
|
||||
workspace := DefaultWorkspace
|
||||
if cmd.Workspace != "" {
|
||||
workspace = cmd.Workspace
|
||||
}
|
||||
|
||||
var projCtx models.ProjectCommandContext
|
||||
unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, workspace, ctx.Pull.Num)
|
||||
if err != nil {
|
||||
return projCtx, err
|
||||
}
|
||||
defer unlockFn()
|
||||
|
||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, cmd.Workspace)
|
||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, workspace)
|
||||
if err != nil {
|
||||
return projCtx, err
|
||||
}
|
||||
|
||||
return p.buildProjectCommandCtx(ctx, cmd, repoDir)
|
||||
repoRelDir := DefaultRepoRelDir
|
||||
if cmd.RepoRelDir != "" {
|
||||
repoRelDir = cmd.RepoRelDir
|
||||
}
|
||||
|
||||
return p.buildProjectCommandCtx(ctx, cmd.ProjectName, cmd.Flags, repoDir, repoRelDir, workspace)
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContext, cmd *CommentCommand, repoDir string) (models.ProjectCommandContext, error) {
|
||||
projCfg, globalCfg, err := p.getCfg(cmd.ProjectName, cmd.RepoRelDir, cmd.Workspace, repoDir)
|
||||
func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContext, projectName string, commentFlags []string, repoDir string, repoRelDir string, workspace string) (models.ProjectCommandContext, error) {
|
||||
projCfg, globalCfg, err := p.getCfg(projectName, repoRelDir, workspace, repoDir)
|
||||
if err != nil {
|
||||
return models.ProjectCommandContext{}, err
|
||||
}
|
||||
@@ -173,10 +307,8 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
|
||||
// Override any dir/workspace defined on the comment with what was
|
||||
// defined in config. This shouldn't matter since we don't allow comments
|
||||
// with both project name and dir/workspace.
|
||||
dir := cmd.RepoRelDir
|
||||
workspace := cmd.Workspace
|
||||
if projCfg != nil {
|
||||
dir = projCfg.Dir
|
||||
repoRelDir = projCfg.Dir
|
||||
workspace = projCfg.Workspace
|
||||
}
|
||||
|
||||
@@ -186,9 +318,9 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
|
||||
Pull: ctx.Pull,
|
||||
User: ctx.User,
|
||||
Log: ctx.Log,
|
||||
CommentArgs: cmd.Flags,
|
||||
CommentArgs: commentFlags,
|
||||
Workspace: workspace,
|
||||
RepoRelDir: dir,
|
||||
RepoRelDir: repoRelDir,
|
||||
ProjectConfig: projCfg,
|
||||
GlobalConfig: globalCfg,
|
||||
}, nil
|
||||
|
||||
@@ -395,10 +395,14 @@ projects:
|
||||
pull := models.PullRequest{}
|
||||
logger := logging.NewNoopLogger()
|
||||
workingDir := mocks.NewMockWorkingDir()
|
||||
expWorkspace := c.Cmd.Workspace
|
||||
if expWorkspace == "" {
|
||||
expWorkspace = "default"
|
||||
}
|
||||
if cmdName == events.PlanCommand {
|
||||
When(workingDir.Clone(logger, baseRepo, headRepo, pull, c.Cmd.Workspace)).ThenReturn(tmpDir, nil)
|
||||
When(workingDir.Clone(logger, baseRepo, headRepo, pull, expWorkspace)).ThenReturn(tmpDir, nil)
|
||||
} else {
|
||||
When(workingDir.GetWorkingDir(baseRepo, pull, c.Cmd.Workspace)).ThenReturn(tmpDir, nil)
|
||||
When(workingDir.GetWorkingDir(baseRepo, pull, expWorkspace)).ThenReturn(tmpDir, nil)
|
||||
}
|
||||
if c.AtlantisYAML != "" {
|
||||
err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(c.AtlantisYAML), 0600)
|
||||
@@ -426,20 +430,21 @@ projects:
|
||||
User: models.User{},
|
||||
Log: logger,
|
||||
}
|
||||
var actCtx models.ProjectCommandContext
|
||||
|
||||
var actCtxs []models.ProjectCommandContext
|
||||
if cmdName == events.PlanCommand {
|
||||
actCtx, err = builder.BuildPlanCommand(cmdCtx, &c.Cmd)
|
||||
actCtxs, err = builder.BuildPlanCommands(cmdCtx, &c.Cmd)
|
||||
} else {
|
||||
actCtx, err = builder.BuildApplyCommand(cmdCtx, &c.Cmd)
|
||||
actCtxs, err = builder.BuildApplyCommands(cmdCtx, &c.Cmd)
|
||||
}
|
||||
|
||||
if c.ExpErr != "" {
|
||||
ErrEquals(t, c.ExpErr, err)
|
||||
return
|
||||
}
|
||||
|
||||
Ok(t, err)
|
||||
|
||||
Equals(t, 1, len(actCtxs))
|
||||
actCtx := actCtxs[0]
|
||||
Equals(t, baseRepo, actCtx.BaseRepo)
|
||||
Equals(t, baseRepo, actCtx.HeadRepo)
|
||||
Equals(t, pull, actCtx.Pull)
|
||||
|
||||
@@ -55,8 +55,8 @@ type PlanSuccess struct {
|
||||
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_project_command_runner.go ProjectCommandRunner
|
||||
|
||||
type ProjectCommandRunner interface {
|
||||
Plan(ctx models.ProjectCommandContext) ProjectCommandResult
|
||||
Apply(ctx models.ProjectCommandContext) ProjectCommandResult
|
||||
Plan(ctx models.ProjectCommandContext) ProjectResult
|
||||
Apply(ctx models.ProjectCommandContext) ProjectResult
|
||||
}
|
||||
|
||||
type DefaultProjectCommandRunner struct {
|
||||
@@ -73,7 +73,25 @@ type DefaultProjectCommandRunner struct {
|
||||
RequireApprovalOverride bool
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) Plan(ctx models.ProjectCommandContext) ProjectCommandResult {
|
||||
func (p *DefaultProjectCommandRunner) Plan(ctx models.ProjectCommandContext) ProjectResult {
|
||||
result := p.doPlan(ctx)
|
||||
return ProjectResult{
|
||||
ProjectCommandResult: result,
|
||||
RepoRelDir: ctx.RepoRelDir,
|
||||
Workspace: ctx.Workspace,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) Apply(ctx models.ProjectCommandContext) ProjectResult {
|
||||
result := p.doApply(ctx)
|
||||
return ProjectResult{
|
||||
ProjectCommandResult: result,
|
||||
RepoRelDir: ctx.RepoRelDir,
|
||||
Workspace: ctx.Workspace,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) doPlan(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.RepoRelDir))
|
||||
if err != nil {
|
||||
@@ -155,7 +173,7 @@ func (p *DefaultProjectCommandRunner) runSteps(steps []valid.Step, ctx models.Pr
|
||||
return outputs, nil
|
||||
}
|
||||
|
||||
func (p *DefaultProjectCommandRunner) Apply(ctx models.ProjectCommandContext) ProjectCommandResult {
|
||||
func (p *DefaultProjectCommandRunner) doApply(ctx models.ProjectCommandContext) ProjectCommandResult {
|
||||
repoDir, err := p.WorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, ctx.Workspace)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@@ -80,10 +80,6 @@ func (p *DefaultProjectFinder) DetermineProjectsViaConfig(log *logging.SimpleLog
|
||||
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)
|
||||
if !project.Autoplan.Enabled {
|
||||
log.Debug("autoplan disabled, ignoring")
|
||||
continue
|
||||
}
|
||||
// Prepend project dir to when modified patterns because the patterns
|
||||
// are relative to the project dirs but our list of modified files is
|
||||
// relative to the repo root.
|
||||
|
||||
@@ -37,6 +37,7 @@ type WorkingDir interface {
|
||||
// GetWorkingDir 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.
|
||||
GetWorkingDir(r models.Repo, p models.PullRequest, workspace string) (string, error)
|
||||
GetPullDir(r models.Repo, p models.PullRequest) (string, error)
|
||||
// Delete deletes the workspace for this repo and pull.
|
||||
Delete(r models.Repo, p models.PullRequest) error
|
||||
DeleteForWorkspace(r models.Repo, p models.PullRequest, workspace string) error
|
||||
@@ -133,6 +134,14 @@ func (w *FileWorkspace) GetWorkingDir(r models.Repo, p models.PullRequest, works
|
||||
return repoDir, nil
|
||||
}
|
||||
|
||||
func (w *FileWorkspace) GetPullDir(r models.Repo, p models.PullRequest) (string, error) {
|
||||
dir := w.repoPullDir(r, p)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Delete deletes the workspace for this repo and pull.
|
||||
func (w *FileWorkspace) Delete(r models.Repo, p models.PullRequest) error {
|
||||
return os.RemoveAll(w.repoPullDir(r, p))
|
||||
|
||||
@@ -31,6 +31,7 @@ type WorkingDirLocker interface {
|
||||
// an error if the workspace is already locked. The error is expected to
|
||||
// be printed to the pull request.
|
||||
TryLock(repoFullName string, workspace string, pullNum int) (func(), error)
|
||||
TryLockPull(repoFullName 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)
|
||||
@@ -49,6 +50,11 @@ func NewDefaultWorkingDirLocker() *DefaultWorkingDirLocker {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultWorkingDirLocker) TryLockPull(repoFullName string, pullNum int) (func(), error) {
|
||||
// todo: implement
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, workspace string, pullNum int) (func(), error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
@@ -104,7 +104,7 @@ func (l *LocksController) DeleteLock(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 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` you must run `plan` again.", lock.Project.Path, lock.Workspace)
|
||||
"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)
|
||||
|
||||
@@ -245,6 +245,6 @@ func TestDeleteLock_CommentSuccess(t *testing.T) {
|
||||
responseContains(t, w, http.StatusOK, "Deleted lock id \"id\"")
|
||||
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` you must run `plan` again.")
|
||||
"To `apply` this plan you must run `plan` again.")
|
||||
workingDir.VerifyWasCalledOnce().DeleteForWorkspace(pull.BaseRepo, pull, "workspace")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user