mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-07 04:08:31 +00:00
chore: make e2e code generic to vcs (#4732)
This commit is contained in:
63
e2e/e2e.go
63
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)
|
||||
}
|
||||
|
||||
142
e2e/github.go
142
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
|
||||
}
|
||||
|
||||
72
e2e/main.go
72
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)
|
||||
|
||||
26
e2e/vcs.go
Normal file
26
e2e/vcs.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user