Skip to content

Commit

Permalink
Clean up data from pull request on close
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Kysow committed Jun 23, 2017
1 parent 40ce7e7 commit 518e8a5
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 49 deletions.
6 changes: 0 additions & 6 deletions plan/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,3 @@ type Plan struct {
// LocalPath is the path to the plan on disk
LocalPath string
}

type PlanKey struct {
Project models.Project
Env string
PullNum int
}
4 changes: 3 additions & 1 deletion plan/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ type Backend struct {
baseDir string
}

const planPath = "plans"

func New(baseDir string) (*Backend, error) {
baseDir = filepath.Clean(baseDir)
baseDir = filepath.Join(filepath.Clean(baseDir), planPath)
if err := os.MkdirAll(baseDir, 0755); err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions server/apply_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type ApplyExecutor struct {
githubCommentRenderer *GithubCommentRenderer
lockingClient *locking.Client
requireApproval bool
planStorage plan.Backend
planBackend plan.Backend
}

/** Result Types **/
Expand Down Expand Up @@ -63,7 +63,7 @@ func (a *ApplyExecutor) execute(ctx *CommandContext, github *GithubClient) {
res := a.setupAndApply(ctx)
res.Command = Apply
comment := a.githubCommentRenderer.render(res, ctx.Log.History.String(), ctx.Command.verbose)
github.CreateComment(ctx, comment)
github.CreateComment(ctx.Repo, ctx.Pull, comment)
}

func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {
Expand All @@ -73,7 +73,7 @@ func (a *ApplyExecutor) setupAndApply(ctx *CommandContext) ExecutionResult {

// todo: reclone repo and switch branch, don't assume it's already there
repoDir := filepath.Join(a.scratchDir, ctx.Repo.FullName, strconv.Itoa(ctx.Pull.Num))
plans, err := a.planStorage.CopyPlans(repoDir, ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num)
plans, err := a.planBackend.CopyPlans(repoDir, ctx.Repo.FullName, ctx.Command.environment, ctx.Pull.Num)
if err != nil {
errMsg := fmt.Sprintf("failed to get plans: %s", err)
ctx.Log.Err(errMsg)
Expand Down
2 changes: 1 addition & 1 deletion server/command_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (s *CommandHandler) SetDeleteLockURL(f func(id string) (url string)) {
func (s *CommandHandler) recover(ctx *CommandContext) {
if err := recover(); err != nil {
stack := recovery.Stack(3)
s.githubClient.CreateComment(ctx, fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack))
s.githubClient.CreateComment(ctx.Repo, ctx.Pull, fmt.Sprintf("**Error: goroutine panic. This is a bug.**\n```\n%s\n%s```", err, stack))
ctx.Log.Err("PANIC: %s\n%s", err, stack)
}
}
51 changes: 30 additions & 21 deletions server/event_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,9 @@ func (e *EventParser) DetermineCommand(comment *github.IssueCommentEvent) (*Comm
}

func (e *EventParser) ExtractCommentData(comment *github.IssueCommentEvent, ctx *CommandContext) error {
repoFullName := comment.Repo.GetFullName()
if repoFullName == "" {
return errors.New("repository.full_name is null")
}
repoOwner := comment.Repo.Owner.GetLogin()
if repoOwner == "" {
return errors.New("repository.owner.login is null")
}
repoName := comment.Repo.GetName()
if repoName == "" {
return errors.New("repository.name is null")
}
repoSSHURL := comment.Repo.GetSSHURL()
if repoSSHURL == "" {
return errors.New("comment.repository.ssh_url is null")
repo, err := e.ExtractRepoData(comment.Repo)
if err != nil {
return err
}
pullNum := comment.Issue.GetNumber()
if pullNum == 0 {
Expand All @@ -81,12 +69,7 @@ func (e *EventParser) ExtractCommentData(comment *github.IssueCommentEvent, ctx
if htmlURL == "" {
return errors.New("comment.issue.html_url is null")
}
ctx.Repo = models.Repo{
FullName: repoFullName,
Owner: repoOwner,
Name: repoName,
SSHURL: repoSSHURL,
}
ctx.Repo = repo
ctx.User = models.User{
Username: commentorUsername,
}
Expand Down Expand Up @@ -131,3 +114,29 @@ func (e *EventParser) ExtractPullData(pull *github.PullRequest) (models.PullRequ
Num: num,
}, nil
}

func (e *EventParser) ExtractRepoData(ghRepo *github.Repository) (models.Repo, error) {
var repo models.Repo
repoFullName := ghRepo.GetFullName()
if repoFullName == "" {
return repo, errors.New("repository.full_name is null")
}
repoOwner := ghRepo.Owner.GetLogin()
if repoOwner == "" {
return repo, errors.New("repository.owner.login is null")
}
repoName := ghRepo.GetName()
if repoName == "" {
return repo, errors.New("repository.name is null")
}
repoSSHURL := ghRepo.GetSSHURL()
if repoSSHURL == "" {
return repo, errors.New("repository.ssh_url is null")
}
return models.Repo{
Owner: repoOwner,
FullName: repoFullName,
SSHURL: repoSSHURL,
Name: repoName,
}, nil
}
4 changes: 2 additions & 2 deletions server/github_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func (g *GithubClient) GetModifiedFiles(repo models.Repo, pull models.PullReques
return files, nil
}

func (g *GithubClient) CreateComment(ctx *CommandContext, comment string) error {
_, _, err := g.client.Issues.CreateComment(g.ctx, ctx.Repo.Owner, ctx.Repo.Name, ctx.Pull.Num, &github.IssueComment{Body: &comment})
func (g *GithubClient) CreateComment(repo models.Repo, pull models.PullRequest, comment string) error {
_, _, err := g.client.Issues.CreateComment(g.ctx, repo.Owner, repo.Name, pull.Num, &github.IssueComment{Body: &comment})
return err
}

Expand Down
2 changes: 1 addition & 1 deletion server/help_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ atlantis apply

func (h *HelpExecutor) execute(ctx *CommandContext, github *GithubClient) {
ctx.Log.Info("generating help comment....")
github.CreateComment(ctx, helpComment)
github.CreateComment(ctx.Repo, ctx.Pull, helpComment)
return
}
6 changes: 3 additions & 3 deletions server/plan_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type PlanExecutor struct {
lockingClient *locking.Client
// DeleteLockURL is a function that given a lock id will return a url for deleting the lock
DeleteLockURL func(id string) (url string)
planStorage plan.Backend
planBackend plan.Backend
}

/** Result Types **/
Expand Down Expand Up @@ -76,7 +76,7 @@ func (p *PlanExecutor) execute(ctx *CommandContext, github *GithubClient) {
res := p.setupAndPlan(ctx)
res.Command = Plan
comment := p.githubCommentRenderer.render(res, ctx.Log.History.String(), ctx.Command.verbose)
github.CreateComment(ctx, comment)
github.CreateComment(ctx.Repo, ctx.Pull, comment)
}

func (p *PlanExecutor) setupAndPlan(ctx *CommandContext) ExecutionResult {
Expand Down Expand Up @@ -292,7 +292,7 @@ func (p *PlanExecutor) plan(
}
}
// Save the plan
if err := p.planStorage.SavePlan(planFile, project, tfEnv, ctx.Pull.Num); err != nil {
if err := p.planBackend.SavePlan(planFile, project, tfEnv, ctx.Pull.Num); err != nil {
ctx.Log.Err("saving plan: %s", err)
// there was an error planning so unlock
if _, err := p.lockingClient.Unlock(lockAttempt.LockKey); err != nil {
Expand Down
79 changes: 79 additions & 0 deletions server/pull_closed_executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package server

import (
"github.com/hootsuite/atlantis/locking"
"github.com/hootsuite/atlantis/models"
"github.com/pkg/errors"
"github.com/hootsuite/atlantis/plan"
"fmt"
"strings"
"text/template"
"bytes"
)

type PullClosedExecutor struct {
locking *locking.Client
github *GithubClient
planBackend plan.Backend
}

type templatedProject struct {
Path string
Envs string
}
var pullClosedTemplate = template.Must(template.New("").Parse("Locks and plans deleted for the projects and environments modified in this pull request:\n" +
"{{ range . }}\n" +
"- path: `{{ .Path }}` {{ .Envs }}{{ end }}"))

func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullRequest) error {
locks, err := p.locking.UnlockByPull(repo.FullName, pull.Num)
if err != nil {
return errors.Wrap(err, "cleaning up locks")
}

// if there are no locks then there's no need to comment
if len(locks) == 0 {
return nil
}

err = p.planBackend.DeletePlansByPull(repo.FullName, pull.Num)
if err != nil {
return errors.Wrap(err, "cleaning up plans")
}

templateData := p.buildTemplateData(locks)
var buf bytes.Buffer
if err = pullClosedTemplate.Execute(&buf, templateData); err != nil {
return errors.Wrap(err, "rendering template for comment")
}
return p.github.CreateComment(repo, pull, buf.String())
}

// buildTemplateData formats the lock data into a slice that can easily be templated
// for the GitHub comment. We organize all the environments by their respective project paths
// so the comment can look like: path: {path}, environments: {all-envs}
func (p *PullClosedExecutor) buildTemplateData(locks []models.ProjectLock) []templatedProject {
envsByPath := make(map[string][]string)
for _, l := range locks {
path := l.Project.RepoFullName + "/" + l.Project.Path
envsByPath[path] = append(envsByPath[path], l.Env)
}

var projects []templatedProject
for p, e := range envsByPath {
envsStr := fmt.Sprintf("`%s`", strings.Join(e, "`, `"))
if len(e) == 1 {
projects = append(projects, templatedProject{
Path: p,
Envs: "environment: " + envsStr,
})
} else {
projects = append(projects, templatedProject{
Path: p,
Envs: "environments: " + envsStr,
})

}
}
return projects
}
40 changes: 29 additions & 11 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Server struct {
router *mux.Router
port int
commandHandler *CommandHandler
pullClosedExecutor *PullClosedExecutor
logger *logging.SimpleLogger
eventParser *EventParser
lockingClient *locking.Client
Expand Down Expand Up @@ -170,7 +171,7 @@ func NewServer(config ServerConfig) (*Server, error) {
githubCommentRenderer: githubComments,
lockingClient: lockingClient,
requireApproval: config.RequireApproval,
planStorage: planBackend,
planBackend: planBackend,
}
planExecutor := &PlanExecutor{
github: githubClient,
Expand All @@ -181,9 +182,14 @@ func NewServer(config ServerConfig) (*Server, error) {
terraform: terraformClient,
githubCommentRenderer: githubComments,
lockingClient: lockingClient,
planStorage: planBackend,
planBackend: planBackend,
}
helpExecutor := &HelpExecutor{}
pullClosedExecutor := &PullClosedExecutor{
planBackend: planBackend,
github: githubClient,
locking: lockingClient,
}
logger := logging.NewSimpleLogger("server", log.New(os.Stderr, "", log.LstdFlags), false, logging.ToLogLevel(config.LogLevel))
eventParser := &EventParser{}
commandHandler := &CommandHandler{
Expand All @@ -199,6 +205,7 @@ func NewServer(config ServerConfig) (*Server, error) {
router: router,
port: config.Port,
commandHandler: commandHandler,
pullClosedExecutor: pullClosedExecutor,
eventParser: eventParser,
logger: logger,
lockingClient: lockingClient,
Expand Down Expand Up @@ -337,18 +344,29 @@ func (s *Server) handlePullRequestEvent(w http.ResponseWriter, pullEvent *github
fmt.Fprintln(w, "Ignoring")
return
}
repo := pullEvent.Repo.GetFullName()
pullNum := pullEvent.PullRequest.GetNumber()

s.logger.Debug("Unlocking locks for repo %s and pull %d %s", repo, pullNum, githubReqID)
locks, err := s.lockingClient.UnlockByPull(repo, pullNum)
pull, err := s.eventParser.ExtractPullData(pullEvent.PullRequest)
if err != nil {
s.logger.Err("unlocking locks for repo %s pull %d: %v", repo, pullNum, err)
w.WriteHeader(http.StatusServiceUnavailable)
fmt.Fprintf(w, "Error unlocking locks: %v\n", err)
s.logger.Err("parsing pull data: %s", err)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "error parsing request: %s", err)
return
}
repo, err := s.eventParser.ExtractRepoData(pullEvent.Repo)
if err != nil {
s.logger.Err("parsing repo data: %s", err)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "error parsing request: %s", err)
return
}

s.logger.Info("cleaning up locks and plans for repo %s and pull %d", repo.FullName, pull.Num)
if err := s.pullClosedExecutor.CleanUpPull(repo, pull); err != nil {
s.logger.Err("cleaning pull request: %s", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Error cleaning pull request: %s", err)
return
}
fmt.Fprintf(w, "Unlocked %d projects/environments", len(locks))
fmt.Fprint(w, "Pull request cleaned successfully")
}

func (s *Server) handleCommentEvent(w http.ResponseWriter, event *github.IssueCommentEvent, githubReqID string) {
Expand Down

0 comments on commit 518e8a5

Please sign in to comment.