mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-31 22:38:45 +00:00
Automerging merges pull requests automatically if all plans have been successfully applied. * Save status of PR's to BoltDB so after each apply, we can check if there are pending plans. * Add new feature where we delete successful plans *unless* all plans have succeeded *if* automerge is enabled. This was requested by users because when automerge is enabled, they want to enforce that a pull request's changes have been fully applied. They asked that plans not be allowed to be applied "piecemeal" and instead, all plans must be generated successfully prior to allowing any plans to be applied.
627 lines
20 KiB
Go
627 lines
20 KiB
Go
package server_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/runatlantis/atlantis/server/events/db"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-github/github"
|
|
. "github.com/petergtz/pegomock"
|
|
"github.com/runatlantis/atlantis/server"
|
|
"github.com/runatlantis/atlantis/server/events"
|
|
"github.com/runatlantis/atlantis/server/events/locking"
|
|
"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/runtime"
|
|
"github.com/runatlantis/atlantis/server/events/terraform"
|
|
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
|
|
"github.com/runatlantis/atlantis/server/events/webhooks"
|
|
"github.com/runatlantis/atlantis/server/events/yaml"
|
|
"github.com/runatlantis/atlantis/server/logging"
|
|
. "github.com/runatlantis/atlantis/testing"
|
|
)
|
|
|
|
func TestGitHubWorkflow(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
cases := []struct {
|
|
Description string
|
|
// RepoDir is relative to testfixtures/test-repos.
|
|
RepoDir string
|
|
// ModifiedFiles are the list of files that have been modified in this
|
|
// pull request.
|
|
ModifiedFiles []string
|
|
// Comments are what our mock user writes to the pull request.
|
|
Comments []string
|
|
// ExpAutomerge is true if we expect Atlantis to automerge.
|
|
ExpAutomerge bool
|
|
// ExpAutoplan is true if we expect Atlantis to autoplan.
|
|
ExpAutoplan bool
|
|
// ExpReplies is a list of files containing the expected replies that
|
|
// Atlantis writes to the pull request in order.
|
|
ExpReplies []string
|
|
}{
|
|
{
|
|
Description: "simple",
|
|
RepoDir: "simple",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
Comments: []string{
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
ExpAutoplan: true,
|
|
},
|
|
{
|
|
Description: "simple with plan comment",
|
|
RepoDir: "simple",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis plan",
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with comment -var",
|
|
RepoDir: "simple",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis plan -- -var var=overridden",
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-atlantis-plan-var-overridden.txt",
|
|
"exp-output-apply-var.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with workspaces",
|
|
RepoDir: "simple",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis plan -- -var var=default_workspace",
|
|
"atlantis plan -w new_workspace -- -var var=new_workspace",
|
|
"atlantis apply -w default",
|
|
"atlantis apply -w new_workspace",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-atlantis-plan.txt",
|
|
"exp-output-atlantis-plan-new-workspace.txt",
|
|
"exp-output-apply-var-default-workspace.txt",
|
|
"exp-output-apply-var-new-workspace.txt",
|
|
"exp-output-merge-workspaces.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with workspaces and apply all",
|
|
RepoDir: "simple",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis plan -- -var var=default_workspace",
|
|
"atlantis plan -w new_workspace -- -var var=new_workspace",
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-atlantis-plan.txt",
|
|
"exp-output-atlantis-plan-new-workspace.txt",
|
|
"exp-output-apply-var-all.txt",
|
|
"exp-output-merge-workspaces.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with atlantis.yaml",
|
|
RepoDir: "simple-yaml",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis apply -w staging",
|
|
"atlantis apply -w default",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-apply-default.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with atlantis.yaml and apply all",
|
|
RepoDir: "simple-yaml",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-all.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "simple with atlantis.yaml and plan/apply all",
|
|
RepoDir: "simple-yaml",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis plan",
|
|
"atlantis apply",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-all.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "modules staging only",
|
|
RepoDir: "modules",
|
|
ModifiedFiles: []string{"staging/main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis apply -d staging",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan-only-staging.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-merge-only-staging.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "modules modules only",
|
|
RepoDir: "modules",
|
|
ModifiedFiles: []string{"modules/null/main.tf"},
|
|
ExpAutoplan: false,
|
|
Comments: []string{
|
|
"atlantis plan -d staging",
|
|
"atlantis plan -d production",
|
|
"atlantis apply -d staging",
|
|
"atlantis apply -d production",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-plan-staging.txt",
|
|
"exp-output-plan-production.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-apply-production.txt",
|
|
"exp-output-merge-all-dirs.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "modules-yaml",
|
|
RepoDir: "modules-yaml",
|
|
ModifiedFiles: []string{"modules/null/main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis apply -d staging",
|
|
"atlantis apply -d production",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-apply-production.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "tfvars-yaml",
|
|
RepoDir: "tfvars-yaml",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: true,
|
|
Comments: []string{
|
|
"atlantis apply -p staging",
|
|
"atlantis apply -p default",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-apply-default.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "tfvars no autoplan",
|
|
RepoDir: "tfvars-yaml-no-autoplan",
|
|
ModifiedFiles: []string{"main.tf"},
|
|
ExpAutoplan: false,
|
|
Comments: []string{
|
|
"atlantis plan -p staging",
|
|
"atlantis plan -p default",
|
|
"atlantis apply -p staging",
|
|
"atlantis apply -p default",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-plan-staging.txt",
|
|
"exp-output-plan-default.txt",
|
|
"exp-output-apply-staging.txt",
|
|
"exp-output-apply-default.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
{
|
|
Description: "automerge",
|
|
RepoDir: "automerge",
|
|
ExpAutomerge: true,
|
|
ExpAutoplan: true,
|
|
ModifiedFiles: []string{"dir1/main.tf", "dir2/main.tf"},
|
|
Comments: []string{
|
|
"atlantis apply -d dir1",
|
|
"atlantis apply -d dir2",
|
|
},
|
|
ExpReplies: []string{
|
|
"exp-output-autoplan.txt",
|
|
"exp-output-apply-dir1.txt",
|
|
"exp-output-apply-dir2.txt",
|
|
"exp-output-automerge.txt",
|
|
"exp-output-merge.txt",
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.Description, func(t *testing.T) {
|
|
RegisterMockTestingT(t)
|
|
|
|
ctrl, vcsClient, githubGetter, atlantisWorkspace := setupE2E(t)
|
|
// Set the repo to be cloned through the testing backdoor.
|
|
repoDir, headSHA, cleanup := initializeRepo(t, c.RepoDir)
|
|
defer cleanup()
|
|
atlantisWorkspace.TestingOverrideHeadCloneURL = fmt.Sprintf("file://%s", repoDir)
|
|
|
|
// Setup test dependencies.
|
|
w := httptest.NewRecorder()
|
|
When(githubGetter.GetPullRequest(AnyRepo(), AnyInt())).ThenReturn(GitHubPullRequestParsed(headSHA), nil)
|
|
When(vcsClient.GetModifiedFiles(AnyRepo(), matchers.AnyModelsPullRequest())).ThenReturn(c.ModifiedFiles, nil)
|
|
|
|
// First, send the open pull request event which triggers autoplan.
|
|
pullOpenedReq := GitHubPullRequestOpenedEvent(t, headSHA)
|
|
ctrl.Post(w, pullOpenedReq)
|
|
responseContains(t, w, 200, "Processing...")
|
|
|
|
// Now send any other comments.
|
|
for _, comment := range c.Comments {
|
|
commentReq := GitHubCommentEvent(t, comment)
|
|
w = httptest.NewRecorder()
|
|
ctrl.Post(w, commentReq)
|
|
responseContains(t, w, 200, "Processing...")
|
|
}
|
|
|
|
// Send the "pull closed" event which would be triggered by the
|
|
// automerge or a manual merge.
|
|
pullClosedReq := GitHubPullRequestClosedEvent(t)
|
|
w = httptest.NewRecorder()
|
|
ctrl.Post(w, pullClosedReq)
|
|
responseContains(t, w, 200, "Pull request cleaned successfully")
|
|
|
|
// Now we're ready to verify Atlantis made all the comments back
|
|
// (or replies) that we expect.
|
|
// We expect replies for each comment plus one for the locks deleted
|
|
// at the end.
|
|
expNumReplies := len(c.Comments) + 1
|
|
if c.ExpAutoplan {
|
|
expNumReplies++
|
|
}
|
|
if c.ExpAutomerge {
|
|
expNumReplies++
|
|
}
|
|
|
|
_, _, actReplies := vcsClient.VerifyWasCalled(Times(expNumReplies)).CreateComment(AnyRepo(), AnyInt(), AnyString()).GetAllCapturedArguments()
|
|
Assert(t, len(c.ExpReplies) == len(actReplies), "missing expected replies, got %d but expected %d", len(actReplies), len(c.ExpReplies))
|
|
for i, expReply := range c.ExpReplies {
|
|
assertCommentEquals(t, expReply, actReplies[i], c.RepoDir)
|
|
}
|
|
|
|
if c.ExpAutomerge {
|
|
// Verify that the merge API call was made.
|
|
vcsClient.VerifyWasCalledOnce().MergePull(matchers.AnyModelsPullRequest())
|
|
} else {
|
|
vcsClient.VerifyWasCalled(Never()).MergePull(matchers.AnyModelsPullRequest())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy, *mocks.MockGithubPullGetter, *events.FileWorkspace) {
|
|
allowForkPRs := false
|
|
dataDir, cleanup := TempDir(t)
|
|
defer cleanup()
|
|
|
|
// Mocks.
|
|
e2eVCSClient := vcsmocks.NewMockClientProxy()
|
|
e2eStatusUpdater := mocks.NewMockCommitStatusUpdater()
|
|
e2eGithubGetter := mocks.NewMockGithubPullGetter()
|
|
e2eGitlabGetter := mocks.NewMockGitlabMergeRequestGetter()
|
|
|
|
// Real dependencies.
|
|
logger := logging.NewSimpleLogger("server", true, logging.Debug)
|
|
eventParser := &events.EventParser{
|
|
GithubUser: "github-user",
|
|
GithubToken: "github-token",
|
|
GitlabUser: "gitlab-user",
|
|
GitlabToken: "gitlab-token",
|
|
}
|
|
commentParser := &events.CommentParser{
|
|
GithubUser: "github-user",
|
|
GithubToken: "github-token",
|
|
GitlabUser: "gitlab-user",
|
|
GitlabToken: "gitlab-token",
|
|
}
|
|
terraformClient, err := terraform.NewClient(dataDir, "")
|
|
Ok(t, err)
|
|
boltdb, err := db.New(dataDir)
|
|
Ok(t, err)
|
|
lockingClient := locking.NewClient(boltdb)
|
|
projectLocker := &events.DefaultProjectLocker{
|
|
Locker: lockingClient,
|
|
}
|
|
workingDir := &events.FileWorkspace{
|
|
DataDir: dataDir,
|
|
TestingOverrideHeadCloneURL: "override-me",
|
|
}
|
|
|
|
defaultTFVersion := terraformClient.Version()
|
|
locker := events.NewDefaultWorkingDirLocker()
|
|
commandRunner := &events.DefaultCommandRunner{
|
|
ProjectCommandRunner: &events.DefaultProjectCommandRunner{
|
|
Locker: projectLocker,
|
|
LockURLGenerator: &mockLockURLGenerator{},
|
|
InitStepRunner: &runtime.InitStepRunner{
|
|
TerraformExecutor: terraformClient,
|
|
DefaultTFVersion: defaultTFVersion,
|
|
},
|
|
PlanStepRunner: &runtime.PlanStepRunner{
|
|
TerraformExecutor: terraformClient,
|
|
DefaultTFVersion: defaultTFVersion,
|
|
},
|
|
ApplyStepRunner: &runtime.ApplyStepRunner{
|
|
TerraformExecutor: terraformClient,
|
|
},
|
|
RunStepRunner: &runtime.RunStepRunner{
|
|
DefaultTFVersion: defaultTFVersion,
|
|
},
|
|
PullApprovedChecker: e2eVCSClient,
|
|
WorkingDir: workingDir,
|
|
Webhooks: &mockWebhookSender{},
|
|
WorkingDirLocker: locker,
|
|
},
|
|
EventParser: eventParser,
|
|
VCSClient: e2eVCSClient,
|
|
GithubPullGetter: e2eGithubGetter,
|
|
GitlabMergeRequestGetter: e2eGitlabGetter,
|
|
CommitStatusUpdater: e2eStatusUpdater,
|
|
MarkdownRenderer: &events.MarkdownRenderer{},
|
|
Logger: logger,
|
|
AllowForkPRs: allowForkPRs,
|
|
AllowForkPRsFlag: "allow-fork-prs",
|
|
ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
|
|
ParserValidator: &yaml.ParserValidator{},
|
|
ProjectFinder: &events.DefaultProjectFinder{},
|
|
VCSClient: e2eVCSClient,
|
|
WorkingDir: workingDir,
|
|
WorkingDirLocker: locker,
|
|
AllowRepoConfigFlag: "allow-repo-config",
|
|
AllowRepoConfig: true,
|
|
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
|
|
CommentBuilder: commentParser,
|
|
},
|
|
DB: boltdb,
|
|
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
|
|
GlobalAutomerge: false,
|
|
WorkingDir: workingDir,
|
|
}
|
|
|
|
repoWhitelistChecker, err := events.NewRepoWhitelistChecker("*")
|
|
Ok(t, err)
|
|
|
|
ctrl := server.EventsController{
|
|
TestingMode: true,
|
|
CommandRunner: commandRunner,
|
|
PullCleaner: &events.PullClosedExecutor{
|
|
Locker: lockingClient,
|
|
VCSClient: e2eVCSClient,
|
|
WorkingDir: workingDir,
|
|
DB: boltdb,
|
|
},
|
|
Logger: logger,
|
|
Parser: eventParser,
|
|
CommentParser: commentParser,
|
|
GithubWebhookSecret: nil,
|
|
GithubRequestValidator: &server.DefaultGithubRequestValidator{},
|
|
GitlabRequestParserValidator: &server.DefaultGitlabRequestParserValidator{},
|
|
GitlabWebhookSecret: nil,
|
|
RepoWhitelistChecker: repoWhitelistChecker,
|
|
SupportedVCSHosts: []models.VCSHostType{models.Gitlab, models.Github, models.BitbucketCloud},
|
|
VCSClient: e2eVCSClient,
|
|
}
|
|
return ctrl, e2eVCSClient, e2eGithubGetter, workingDir
|
|
}
|
|
|
|
type mockLockURLGenerator struct{}
|
|
|
|
func (m *mockLockURLGenerator) GenerateLockURL(lockID string) string {
|
|
return "lock-url"
|
|
}
|
|
|
|
type mockWebhookSender struct{}
|
|
|
|
func (w *mockWebhookSender) Send(log *logging.SimpleLogger, result webhooks.ApplyResult) error {
|
|
return nil
|
|
}
|
|
|
|
func GitHubCommentEvent(t *testing.T, comment string) *http.Request {
|
|
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubIssueCommentEvent.json"))
|
|
Ok(t, err)
|
|
requestJSON = []byte(strings.Replace(string(requestJSON), "###comment body###", comment, 1))
|
|
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
|
|
Ok(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set(githubHeader, "issue_comment")
|
|
return req
|
|
}
|
|
|
|
func GitHubPullRequestOpenedEvent(t *testing.T, headSHA string) *http.Request {
|
|
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubPullRequestOpenedEvent.json"))
|
|
Ok(t, err)
|
|
// Replace sha with expected sha.
|
|
requestJSONStr := strings.Replace(string(requestJSON), "c31fd9ea6f557ad2ea659944c3844a059b83bc5d", headSHA, -1)
|
|
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(requestJSONStr)))
|
|
Ok(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set(githubHeader, "pull_request")
|
|
return req
|
|
}
|
|
|
|
func GitHubPullRequestClosedEvent(t *testing.T) *http.Request {
|
|
requestJSON, err := ioutil.ReadFile(filepath.Join("testfixtures", "githubPullRequestClosedEvent.json"))
|
|
Ok(t, err)
|
|
req, err := http.NewRequest("POST", "/events", bytes.NewBuffer(requestJSON))
|
|
Ok(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set(githubHeader, "pull_request")
|
|
return req
|
|
}
|
|
|
|
func GitHubPullRequestParsed(headSHA string) *github.PullRequest {
|
|
// headSHA can't be empty so default if not set.
|
|
if headSHA == "" {
|
|
headSHA = "13940d121be73f656e2132c6d7b4c8e87878ac8d"
|
|
}
|
|
return &github.PullRequest{
|
|
Number: github.Int(2),
|
|
State: github.String("open"),
|
|
HTMLURL: github.String("htmlurl"),
|
|
Head: &github.PullRequestBranch{
|
|
Repo: &github.Repository{
|
|
FullName: github.String("runatlantis/atlantis-tests"),
|
|
CloneURL: github.String("https://github.com/runatlantis/atlantis-tests.git"),
|
|
},
|
|
SHA: github.String(headSHA),
|
|
Ref: github.String("branch"),
|
|
},
|
|
Base: &github.PullRequestBranch{
|
|
Repo: &github.Repository{
|
|
FullName: github.String("runatlantis/atlantis-tests"),
|
|
CloneURL: github.String("https://github.com/runatlantis/atlantis-tests.git"),
|
|
},
|
|
Ref: github.String("master"),
|
|
},
|
|
User: &github.User{
|
|
Login: github.String("atlantisbot"),
|
|
},
|
|
}
|
|
}
|
|
|
|
// absRepoPath returns the absolute path to the test repo under dir repoDir.
|
|
func absRepoPath(t *testing.T, repoDir string) string {
|
|
path, err := filepath.Abs(filepath.Join("testfixtures", "test-repos", repoDir))
|
|
Ok(t, err)
|
|
return path
|
|
}
|
|
|
|
// initializeRepo copies the repo data from testfixtures and initializes a new
|
|
// git repo in a temp directory. It returns that directory and a function
|
|
// to run in a defer that will delete the dir.
|
|
// The purpose of this function is to create a real git repository with a branch
|
|
// called 'branch' from the files under repoDir. This is so we can check in
|
|
// those files normally to this repo without needing a .git directory.
|
|
func initializeRepo(t *testing.T, repoDir string) (string, string, func()) {
|
|
originRepo := absRepoPath(t, repoDir)
|
|
|
|
// Copy the files to the temp dir.
|
|
destDir, cleanup := TempDir(t)
|
|
runCmd(t, "", "cp", "-r", fmt.Sprintf("%s/.", originRepo), destDir)
|
|
|
|
// Initialize the git repo.
|
|
runCmd(t, destDir, "git", "init")
|
|
runCmd(t, destDir, "touch", ".gitkeep")
|
|
runCmd(t, destDir, "git", "add", ".gitkeep")
|
|
runCmd(t, destDir, "git", "config", "--local", "user.email", "atlantisbot@runatlantis.io")
|
|
runCmd(t, destDir, "git", "config", "--local", "user.name", "atlantisbot")
|
|
runCmd(t, destDir, "git", "commit", "-m", "initial commit")
|
|
runCmd(t, destDir, "git", "checkout", "-b", "branch")
|
|
runCmd(t, destDir, "git", "add", ".")
|
|
runCmd(t, destDir, "git", "commit", "-am", "branch commit")
|
|
headSHA := runCmd(t, destDir, "git", "rev-parse", "HEAD")
|
|
headSHA = strings.Trim(headSHA, "\n")
|
|
|
|
return destDir, headSHA, cleanup
|
|
}
|
|
|
|
func runCmd(t *testing.T, dir string, name string, args ...string) string {
|
|
cpCmd := exec.Command(name, args...)
|
|
cpCmd.Dir = dir
|
|
cpOut, err := cpCmd.CombinedOutput()
|
|
Assert(t, err == nil, "err running %q: %s", strings.Join(append([]string{name}, args...), " "), cpOut)
|
|
return string(cpOut)
|
|
}
|
|
|
|
func assertCommentEquals(t *testing.T, expFile string, act string, repoDir string) {
|
|
t.Helper()
|
|
exp, err := ioutil.ReadFile(filepath.Join(absRepoPath(t, repoDir), expFile))
|
|
Ok(t, err)
|
|
|
|
// Replace all 'Creation complete after 1s ID: 1111818181' strings with
|
|
// 'Creation complete after *s ID: **********' so we can do a comparison.
|
|
idRegex := regexp.MustCompile(`Creation complete after [0-9]+s \(ID: [0-9]+\)`)
|
|
act = idRegex.ReplaceAllString(act, "Creation complete after *s (ID: ******************)")
|
|
|
|
// Replace all null_resource.simple{n}: .* with null_resource.simple: because
|
|
// with multiple resources being created the logs are all out of order which
|
|
// makes comparison impossible.
|
|
resourceRegex := regexp.MustCompile(`null_resource\.simple\d?:.*`)
|
|
act = resourceRegex.ReplaceAllString(act, "null_resource.simple:")
|
|
|
|
expStr := string(exp)
|
|
// My editor adds a newline to all the files, so if the actual comment
|
|
// doesn't end with a newline then strip the last newline from the file's
|
|
// contents.
|
|
if !strings.HasSuffix(act, "\n") {
|
|
expStr = strings.TrimSuffix(expStr, "\n")
|
|
}
|
|
|
|
if expStr != act {
|
|
// If in CI, we write the diff to the console. Otherwise we write the diff
|
|
// to file so we can use our local diff viewer.
|
|
if os.Getenv("CI") == "true" {
|
|
t.Logf("exp: %s, got: %s", expStr, act)
|
|
t.FailNow()
|
|
} else {
|
|
actFile := filepath.Join(absRepoPath(t, repoDir), expFile+".act")
|
|
err := ioutil.WriteFile(actFile, []byte(act), 0600)
|
|
Ok(t, err)
|
|
cwd, err := os.Getwd()
|
|
Ok(t, err)
|
|
rel, err := filepath.Rel(cwd, actFile)
|
|
Ok(t, err)
|
|
t.Errorf("%q was different, wrote actual comment to %q", expFile, rel)
|
|
}
|
|
}
|
|
}
|