Skip to content

Commit

Permalink
Detects if master branch has diverged and warn to pull new commits in…
Browse files Browse the repository at this point in the history
… comment
  • Loading branch information
MRinalducci authored and lkysow committed Nov 27, 2019
1 parent 9cfa8d9 commit 52f900a
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 36 deletions.
7 changes: 5 additions & 2 deletions server/events/markdown_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,17 @@ var multiProjectApplyTmpl = template.Must(template.New("").Funcs(sprig.TxtFuncMa
var planSuccessUnwrappedTmpl = template.Must(template.New("").Parse(
"```diff\n" +
"{{.TerraformOutput}}\n" +
"```\n\n" + planNextSteps))
"```\n\n" + planNextSteps +
"{{ if .HasDiverged }}\n\n:warning: Master branch is ahead, it is recommended to pull new commits first.{{end}}"))

var planSuccessWrappedTmpl = template.Must(template.New("").Parse(
"<details><summary>Show Output</summary>\n\n" +
"```diff\n" +
"{{.TerraformOutput}}\n" +
"```\n\n" +
planNextSteps + "\n" +
"</details>"))
"</details>" +
"{{ if .HasDiverged }}\n\n:warning: Master branch is ahead, it is recommended to pull new commits first.{{end}}"))

// planNextSteps are instructions appended after successful plans as to what
// to do next.
Expand Down
36 changes: 36 additions & 0 deletions server/events/markdown_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,42 @@ $$$
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
`,
},
{
"single successful plan with master ahead",
models.PlanCommand,
[]models.ProjectResult{
{
PlanSuccess: &models.PlanSuccess{
TerraformOutput: "terraform-output",
LockURL: "lock-url",
RePlanCmd: "atlantis plan -d path -w workspace",
ApplyCmd: "atlantis apply -d path -w workspace",
HasDiverged: true,
},
Workspace: "workspace",
RepoRelDir: "path",
},
},
models.Github,
`Ran Plan for dir: $path$ workspace: $workspace$
$$$diff
terraform-output
$$$
* :arrow_forward: To **apply** this plan, comment:
* $atlantis apply -d path -w workspace$
* :put_litter_in_its_place: To **delete** this plan click [here](lock-url)
* :repeat: To **plan** this project again, comment:
* $atlantis plan -d path -w workspace$
:warning: Master branch is ahead, it is recommended to pull new commits first.
---
* :fast_forward: To **apply** all unapplied plans from this pull request, comment:
* $atlantis apply$
Expand Down
9 changes: 5 additions & 4 deletions server/events/mock_workingdir_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions server/events/mocks/mock_working_dir.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ type PlanSuccess struct {
RePlanCmd string
// ApplyCmd is the command that users should run to apply this plan.
ApplyCmd string
// Indicates if remote master has diverged
HasDiverged bool
}

// PullStatus is the current status of a pull request that is in progress.
Expand Down
4 changes: 2 additions & 2 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))

repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -176,7 +176,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *CommandConte
defer unlockFn()

ctx.Log.Debug("cloning repository")
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
if err != nil {
return pcc, err
}
Expand Down
3 changes: 2 additions & 1 deletion server/events/project_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (
defer unlockFn()

// Clone is idempotent so okay to run even if the repo was already cloned.
repoDir, cloneErr := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
repoDir, hasDiverged, cloneErr := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
if cloneErr != nil {
if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil {
ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
Expand All @@ -176,6 +176,7 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (
TerraformOutput: strings.Join(outputs, "\n"),
RePlanCmd: ctx.RePlanCmd,
ApplyCmd: ctx.ApplyCmd,
HasDiverged: hasDiverged,
}, "", nil
}

Expand Down
5 changes: 3 additions & 2 deletions server/events/project_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
package events_test

import (
"github.com/hashicorp/go-version"
"github.com/runatlantis/atlantis/server/events/runtime"
"os"
"testing"

"github.com/hashicorp/go-version"
"github.com/runatlantis/atlantis/server/events/runtime"

. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/mocks"
Expand Down
48 changes: 36 additions & 12 deletions server/events/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const workingDirPrefix = "repos"
// WorkingDir handles the workspace on disk for running commands.
type WorkingDir interface {
// Clone git clones headRepo, checks out the branch and then returns the
// absolute path to the root of the cloned repo.
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error)
// absolute path to the root of the cloned repo as well as if master branch has diverged.
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, bool, error)
// 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)
Expand Down Expand Up @@ -70,7 +70,7 @@ func (w *FileWorkspace) Clone(
baseRepo models.Repo,
headRepo models.Repo,
p models.PullRequest,
workspace string) (string, error) {
workspace string) (string, bool, error) {
cloneDir := w.cloneDir(baseRepo, p, workspace)

// If the directory already exists, check if it's at the right commit.
Expand All @@ -89,17 +89,41 @@ func (w *FileWorkspace) Clone(
}
revParseCmd := exec.Command("git", "rev-parse", pullHead) // #nosec
revParseCmd.Dir = cloneDir
output, err := revParseCmd.CombinedOutput()
outputRevParseCmd, err := revParseCmd.CombinedOutput()
if err != nil {
log.Warn("will re-clone repo, could not determine if was at correct commit: %s: %s: %s", strings.Join(revParseCmd.Args, " "), err, string(output))
log.Warn("will re-clone repo, could not determine if was at correct commit: %s: %s: %s", strings.Join(revParseCmd.Args, " "), err, string(outputRevParseCmd))
return w.forceClone(log, cloneDir, headRepo, p)
}
currCommit := strings.Trim(string(output), "\n")
currCommit := strings.Trim(string(outputRevParseCmd), "\n")

// Bring our remote refs up to date.
remoteUpdateCmd := exec.Command("git", "remote", "update")
remoteUpdateCmd.Dir = cloneDir
outputRemoteUpdate, err := remoteUpdateCmd.CombinedOutput()
if err != nil {
log.Warn("getting remote update failed: %s", string(outputRemoteUpdate))
}

// Check if remote master branch has diverged.
statusUnoCmd := exec.Command("git", "status", "--untracked-files=no")
statusUnoCmd.Dir = cloneDir
outputStatusUno, err := statusUnoCmd.CombinedOutput()
if err != nil {
log.Warn("getting repo status has failed: %s", string(outputStatusUno))
}
status := strings.Trim(string(outputStatusUno), "\n")
hasDiverged := strings.Contains(status, "have diverged")
if hasDiverged {
log.Info("remote master branch is ahead and thereby has new commits, it is recommended to pull new commits")
} else {
log.Debug("remote master branch has no new commits")
}

// We're prefix matching here because BitBucket doesn't give us the full
// commit, only a 12 character prefix.
if strings.HasPrefix(currCommit, p.HeadCommit) {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, nil
return cloneDir, hasDiverged, nil
}
log.Debug("repo was already cloned but is not at correct commit, wanted %q got %q", p.HeadCommit, currCommit)
// We'll fall through to re-clone.
Expand All @@ -112,17 +136,17 @@ func (w *FileWorkspace) Clone(
func (w *FileWorkspace) forceClone(log *logging.SimpleLogger,
cloneDir string,
headRepo models.Repo,
p models.PullRequest) (string, error) {
p models.PullRequest) (string, bool, error) {

err := os.RemoveAll(cloneDir)
if err != nil {
return "", errors.Wrapf(err, "deleting dir %q before cloning", cloneDir)
return "", false, errors.Wrapf(err, "deleting dir %q before cloning", cloneDir)
}

// Create the directory and parents if necessary.
log.Info("creating dir %q", cloneDir)
if err := os.MkdirAll(cloneDir, 0700); err != nil {
return "", errors.Wrap(err, "creating new workspace")
return "", false, errors.Wrap(err, "creating new workspace")
}

// During testing, we mock some of this out.
Expand Down Expand Up @@ -184,11 +208,11 @@ func (w *FileWorkspace) forceClone(log *logging.SimpleLogger,
sanitizedOutput := w.sanitizeGitCredentials(string(output), p.BaseRepo, headRepo)
if err != nil {
sanitizedErrMsg := w.sanitizeGitCredentials(err.Error(), p.BaseRepo, headRepo)
return "", fmt.Errorf("running %s: %s: %s", cmdStr, sanitizedOutput, sanitizedErrMsg)
return "", false, fmt.Errorf("running %s: %s: %s", cmdStr, sanitizedOutput, sanitizedErrMsg)
}
log.Debug("ran: %s. Output: %s", cmdStr, strings.TrimSuffix(sanitizedOutput, "\n"))
}
return cloneDir, nil
return cloneDir, false, nil
}

// GetWorkingDir returns the path to the workspace for this repo and pull.
Expand Down
Loading

0 comments on commit 52f900a

Please sign in to comment.