diff --git a/e2e/e2e.go b/e2e/e2e.go index 6d1ebe5f1a..5c19d501b0 100644 --- a/e2e/e2e.go +++ b/e2e/e2e.go @@ -20,24 +20,19 @@ import ( "os" "os/exec" "time" - - "github.com/google/go-github/v59/github" ) type E2ETester struct { - githubClient *GithubClient - repoURL string - ownerName string - repoName string + vcsClient VCSClient hookID int64 cloneDirRoot string projectType Project } type E2EResult struct { - projectType string - githubPullRequestURL string - testResult string + projectType string + pullRequestURL string + testResult string } var testFileData = ` @@ -59,11 +54,9 @@ func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) { return e2eResult, fmt.Errorf("failed to create dir %q prior to cloning, attempting to continue: %v", cloneDir, err) } - cloneCmd := exec.Command("git", "clone", t.repoURL, cloneDir) - // git clone the repo - log.Printf("git cloning into %q", cloneDir) - if output, err := cloneCmd.CombinedOutput(); err != nil { - return e2eResult, fmt.Errorf("failed to clone repository: %v: %s", err, string(output)) + err := t.vcsClient.Clone(cloneDir) + if err != nil { + return e2eResult, err } // checkout a new branch for the project @@ -78,7 +71,7 @@ func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) { randomData := []byte(testFileData) filePath := fmt.Sprintf("%s/%s/%s", cloneDir, t.projectType.Name, testFileName) log.Printf("creating file to commit %q", filePath) - err := os.WriteFile(filePath, randomData, 0644) + err = os.WriteFile(filePath, randomData, 0644) if err != nil { return e2eResult, fmt.Errorf("couldn't write file %s: %v", filePath, err) } @@ -109,23 +102,19 @@ func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) { // create a new pr title := fmt.Sprintf("This is a test pull request for atlantis e2e test for %s project type", t.projectType.Name) - head := fmt.Sprintf("%s:%s", t.ownerName, branchName) - body := "" - base := "main" - newPullRequest := &github.NewPullRequest{Title: &title, Head: &head, Body: &body, Base: &base} + url, pullId, err := t.vcsClient.CreatePullRequest(ctx, title, branchName) - pull, _, err := t.githubClient.client.PullRequests.Create(ctx, t.ownerName, t.repoName, newPullRequest) if err != nil { - return e2eResult, fmt.Errorf("error while creating new pull request: %v", err) + return e2eResult, err } // set pull request url - e2eResult.githubPullRequestURL = pull.GetHTMLURL() + e2eResult.pullRequestURL = url - log.Printf("created pull request %s", pull.GetHTMLURL()) + log.Printf("created pull request %s", url) // defer closing pull request and delete remote branch - defer cleanUp(ctx, t, pull.GetNumber(), branchName) // nolint: errcheck + defer cleanUp(ctx, t, pullId, branchName) // nolint: errcheck // wait for atlantis to respond to webhook and autoplan. time.Sleep(2 * time.Second) @@ -136,7 +125,7 @@ func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) { i := 0 for ; i < maxLoops && checkStatus(state); i++ { time.Sleep(2 * time.Second) - state, _ = getAtlantisStatus(ctx, t, branchName) + state, _ = t.vcsClient.GetAtlantisStatus(ctx, branchName) if state == "" { log.Println("atlantis run hasn't started") continue @@ -157,22 +146,6 @@ func (t *E2ETester) Start(ctx context.Context) (*E2EResult, error) { return e2eResult, nil } -func getAtlantisStatus(ctx context.Context, t *E2ETester, branchName string) (string, error) { - // check repo status - combinedStatus, _, err := t.githubClient.client.Repositories.GetCombinedStatus(ctx, t.ownerName, t.repoName, branchName, nil) - if err != nil { - return "", err - } - - for _, status := range combinedStatus.Statuses { - if status.GetContext() == "atlantis/plan" { - return status.GetState(), nil - } - } - - return "", nil -} - func checkStatus(state string) bool { for _, s := range []string{"success", "error", "failure"} { if state == s { @@ -184,14 +157,14 @@ func checkStatus(state string) bool { func cleanUp(ctx context.Context, t *E2ETester, pullRequestNumber int, branchName string) error { // clean up - pullClosed, _, err := t.githubClient.client.PullRequests.Edit(ctx, t.ownerName, t.repoName, pullRequestNumber, &github.PullRequest{State: github.String("closed")}) + err := t.vcsClient.ClosePullRequest(ctx, pullRequestNumber) if err != nil { - return fmt.Errorf("error while closing new pull request: %v", err) + return err } - log.Printf("closed pull request %d", pullClosed.GetNumber()) + log.Printf("closed pull request %d", pullRequestNumber) deleteBranchName := fmt.Sprintf("%s/%s", "heads", branchName) - _, err = t.githubClient.client.Git.DeleteRef(ctx, t.ownerName, t.repoName, deleteBranchName) + err = t.vcsClient.DeleteBranch(ctx, deleteBranchName) if err != nil { return fmt.Errorf("error while deleting branch %s: %v", deleteBranchName, err) } diff --git a/e2e/github.go b/e2e/github.go index bab3778be2..393777ae52 100644 --- a/e2e/github.go +++ b/e2e/github.go @@ -14,10 +14,148 @@ package main import ( + "context" + "fmt" + "log" + "os" + "os/exec" + "strings" + "github.com/google/go-github/v59/github" ) type GithubClient struct { - client *github.Client - username string + client *github.Client + username string + ownerName string + repoName string + token string +} + +func NewGithubClient() *GithubClient { + + githubUsername := os.Getenv("ATLANTISBOT_GITHUB_USERNAME") + if githubUsername == "" { + log.Fatalf("ATLANTISBOT_GITHUB_USERNAME cannot be empty") + } + githubToken := os.Getenv("ATLANTISBOT_GITHUB_TOKEN") + if githubToken == "" { + log.Fatalf("ATLANTISBOT_GITHUB_TOKEN cannot be empty") + } + ownerName := os.Getenv("GITHUB_REPO_OWNER_NAME") + if ownerName == "" { + ownerName = "runatlantis" + } + repoName := os.Getenv("GITHUB_REPO_NAME") + if repoName == "" { + repoName = "atlantis-tests" + } + + // create github client + tp := github.BasicAuthTransport{ + Username: strings.TrimSpace(githubUsername), + Password: strings.TrimSpace(githubToken), + } + ghClient := github.NewClient(tp.Client()) + + return &GithubClient{ + client: ghClient, + username: githubUsername, + ownerName: ownerName, + repoName: repoName, + token: githubToken, + } + +} + +func (g GithubClient) Clone(cloneDir string) error { + + repoURL := fmt.Sprintf("https://%s:%s@github.com/%s/%s.git", g.username, g.token, g.ownerName, g.repoName) + cloneCmd := exec.Command("git", "clone", repoURL, cloneDir) + // git clone the repo + log.Printf("git cloning into %q", cloneDir) + if output, err := cloneCmd.CombinedOutput(); err != nil { + return fmt.Errorf("failed to clone repository: %v: %s", err, string(output)) + } + return nil +} + +func (g GithubClient) CreateAtlantisWebhook(ctx context.Context, hookURL string) (int64, error) { + // create atlantis hook + atlantisHook := &github.Hook{ + Events: []string{"issue_comment", "pull_request", "push"}, + Config: map[string]interface{}{ + "url": hookURL, + "content_type": "json", + }, + Active: github.Bool(true), + } + + hook, _, err := g.client.Repositories.CreateHook(ctx, g.ownerName, g.repoName, atlantisHook) + if err != nil { + return 0, err + } + log.Println(hook.GetURL()) + + return hook.GetID(), nil +} + +func (g GithubClient) DeleteAtlantisHook(ctx context.Context, hookID int64) error { + _, err := g.client.Repositories.DeleteHook(ctx, g.ownerName, g.repoName, hookID) + if err != nil { + return err + } + log.Printf("deleted webhook id %d", hookID) + + return nil +} + +func (g GithubClient) CreatePullRequest(ctx context.Context, title, branchName string) (string, int, error) { + head := fmt.Sprintf("%s:%s", g.ownerName, branchName) + body := "" + base := "main" + newPullRequest := &github.NewPullRequest{Title: &title, Head: &head, Body: &body, Base: &base} + + pull, _, err := g.client.PullRequests.Create(ctx, g.ownerName, g.repoName, newPullRequest) + if err != nil { + return "", 0, fmt.Errorf("error while creating new pull request: %v", err) + } + + // set pull request url + return pull.GetHTMLURL(), pull.GetNumber(), nil + +} + +func (g GithubClient) GetAtlantisStatus(ctx context.Context, branchName string) (string, error) { + // check repo status + combinedStatus, _, err := g.client.Repositories.GetCombinedStatus(ctx, g.ownerName, g.repoName, branchName, nil) + if err != nil { + return "", err + } + + for _, status := range combinedStatus.Statuses { + if status.GetContext() == "atlantis/plan" { + return status.GetState(), nil + } + } + + return "", nil +} + +func (g GithubClient) ClosePullRequest(ctx context.Context, pullRequestNumber int) error { + // clean up + _, _, err := g.client.PullRequests.Edit(ctx, g.ownerName, g.repoName, pullRequestNumber, &github.PullRequest{State: github.String("closed")}) + if err != nil { + return fmt.Errorf("error while closing new pull request: %v", err) + } + return nil + +} +func (g GithubClient) DeleteBranch(ctx context.Context, branchName string) error { + + _, err := g.client.Git.DeleteRef(ctx, g.ownerName, g.repoName, branchName) + if err != nil { + return fmt.Errorf("error while deleting branch %s: %v", branchName, err) + } + return nil } diff --git a/e2e/main.go b/e2e/main.go index 977cc826b1..808ca7e9e4 100644 --- a/e2e/main.go +++ b/e2e/main.go @@ -17,11 +17,9 @@ import ( "context" "log" "os" - "strings" "fmt" - "github.com/google/go-github/v59/github" multierror "github.com/hashicorp/go-multierror" ) @@ -38,30 +36,13 @@ type Project struct { func main() { - githubUsername := os.Getenv("ATLANTISBOT_GITHUB_USERNAME") - if githubUsername == "" { - log.Fatalf("ATLANTISBOT_GITHUB_USERNAME cannot be empty") - } - githubToken := os.Getenv("ATLANTISBOT_GITHUB_TOKEN") - if githubToken == "" { - log.Fatalf("ATLANTISBOT_GITHUB_TOKEN cannot be empty") - } atlantisURL := os.Getenv("ATLANTIS_URL") if atlantisURL == "" { atlantisURL = defaultAtlantisURL } // add /events to the url atlantisURL = fmt.Sprintf("%s/events", atlantisURL) - ownerName := os.Getenv("GITHUB_REPO_OWNER_NAME") - if ownerName == "" { - ownerName = "runatlantis" - } - repoName := os.Getenv("GITHUB_REPO_NAME") - if repoName == "" { - repoName = "atlantis-tests" - } - // using https to clone the repo - repoURL := fmt.Sprintf("https://%s:%s@github.com/%s/%s.git", githubUsername, githubToken, ownerName, repoName) + cloneDirRoot := os.Getenv("CLONE_DIR") if cloneDirRoot == "" { cloneDirRoot = "/tmp/atlantis-tests" @@ -74,28 +55,18 @@ func main() { log.Fatalf("failed to clean dir %q before cloning, attempting to continue: %v", cloneDirRoot, err) } - // create github client - tp := github.BasicAuthTransport{ - Username: strings.TrimSpace(githubUsername), - Password: strings.TrimSpace(githubToken), - } - ghClient := github.NewClient(tp.Client()) - - githubClient := &GithubClient{client: ghClient, username: githubUsername} + githubClient := NewGithubClient() ctx := context.Background() // we create atlantis hook once for the repo, since the atlantis server can handle multiple requests log.Printf("creating atlantis webhook with %s url", atlantisURL) - hookID, err := createAtlantisWebhook(ctx, githubClient, ownerName, repoName, atlantisURL) + hookID, err := githubClient.CreateAtlantisWebhook(ctx, atlantisURL) if err != nil { log.Fatalf("error creating atlantis webhook: %v", err) } // create e2e test e2e := E2ETester{ - githubClient: githubClient, - repoURL: repoURL, - ownerName: ownerName, - repoName: repoName, + vcsClient: githubClient, hookID: hookID, cloneDirRoot: cloneDirRoot, } @@ -105,7 +76,7 @@ func main() { log.Printf("Test Results\n---------------------------\n") for _, result := range results { fmt.Printf("Project Type: %s \n", result.projectType) - fmt.Printf("Pull Request Link: %s \n", result.githubPullRequestURL) + fmt.Printf("Pull Request Link: %s \n", result.pullRequestURL) fmt.Printf("Atlantis Run Status: %s \n", result.testResult) fmt.Println("---------------------------") } @@ -115,37 +86,6 @@ func main() { } -func createAtlantisWebhook(ctx context.Context, g *GithubClient, ownerName string, repoName string, hookURL string) (int64, error) { - // create atlantis hook - atlantisHook := &github.Hook{ - Events: []string{"issue_comment", "pull_request", "push"}, - Config: map[string]interface{}{ - "url": hookURL, - "content_type": "json", - }, - Active: github.Bool(true), - } - - // moved to github.go - hook, _, err := g.client.Repositories.CreateHook(ctx, ownerName, repoName, atlantisHook) - if err != nil { - return 0, err - } - log.Println(hook.GetURL()) - - return hook.GetID(), nil -} - -func deleteAtlantisHook(ctx context.Context, g *GithubClient, ownerName string, repoName string, hookID int64) error { - _, err := g.client.Repositories.DeleteHook(ctx, ownerName, repoName, hookID) - if err != nil { - return err - } - log.Printf("deleted webhook id %d", hookID) - - return nil -} - func cleanDir(path string) error { return os.RemoveAll(path) } @@ -154,7 +94,7 @@ func startTests(ctx context.Context, e2e E2ETester) ([]*E2EResult, error) { var testResults []*E2EResult var testErrors *multierror.Error // delete webhook when we are done running tests - defer deleteAtlantisHook(ctx, e2e.githubClient, e2e.ownerName, e2e.repoName, e2e.hookID) // nolint: errcheck + defer e2e.vcsClient.DeleteAtlantisHook(ctx, e2e.hookID) // nolint: errcheck for _, projectType := range projectTypes { log.Printf("starting e2e test for project type %q", projectType.Name) diff --git a/e2e/vcs.go b/e2e/vcs.go new file mode 100644 index 0000000000..79f3deb419 --- /dev/null +++ b/e2e/vcs.go @@ -0,0 +1,26 @@ +// Copyright 2017 HootSuite Media Inc. +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// Modified hereafter by contributors to runatlantis/atlantis. + +package main + +import "context" + +type VCSClient interface { + Clone(cloneDir string) error + CreateAtlantisWebhook(ctx context.Context, hookURL string) (int64, error) + DeleteAtlantisHook(ctx context.Context, hookID int64) error + CreatePullRequest(ctx context.Context, title, branchName string) (string, int, error) + GetAtlantisStatus(ctx context.Context, branchName string) (string, error) + ClosePullRequest(ctx context.Context, pullRequestNumber int) error + DeleteBranch(ctx context.Context, branchName string) error +}