forked from runatlantis/atlantis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pull_closed_executor.go
106 lines (91 loc) · 3.32 KB
/
pull_closed_executor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package events
import (
"bytes"
"fmt"
"sort"
"strings"
"text/template"
"github.com/hootsuite/atlantis/server/events/locking"
"github.com/hootsuite/atlantis/server/events/models"
"github.com/hootsuite/atlantis/server/events/vcs"
"github.com/pkg/errors"
)
//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_pull_cleaner.go PullCleaner
// PullCleaner cleans up pull requests after they're closed/merged.
type PullCleaner interface {
// CleanUpPull deletes the workspaces used by the pull request on disk
// and deletes any locks associated with this pull request for all workspaces.
CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error
}
// PullClosedExecutor executes the tasks required to clean up a closed pull
// request.
type PullClosedExecutor struct {
Locker locking.Locker
VCSClient vcs.ClientProxy
Workspace AtlantisWorkspace
}
type templatedProject struct {
Path string
Workspaces string
}
var pullClosedTemplate = template.Must(template.New("").Parse(
"Locks and plans deleted for the projects and workspaces modified in this pull request:\n" +
"{{ range . }}\n" +
"- path: `{{ .Path }}` {{ .Workspaces }}{{ end }}"))
// CleanUpPull cleans up after a closed pull request.
func (p *PullClosedExecutor) CleanUpPull(repo models.Repo, pull models.PullRequest, host vcs.Host) error {
if err := p.Workspace.Delete(repo, pull); err != nil {
return errors.Wrap(err, "cleaning workspace")
}
// Finally, delete locks. We do this last because when someone
// unlocks a project, right now we don't actually delete the plan
// so we might have plans laying around but no locks.
locks, err := p.Locker.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
}
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.VCSClient.CreateComment(repo, pull, buf.String(), host)
}
// buildTemplateData formats the lock data into a slice that can easily be
// templated for the VCS comment. We organize all the workspaces by their
// respective project paths so the comment can look like:
// path: {path}, workspaces: {all-workspaces}
func (p *PullClosedExecutor) buildTemplateData(locks []models.ProjectLock) []templatedProject {
workspacesByPath := make(map[string][]string)
for _, l := range locks {
path := l.Project.RepoFullName + "/" + l.Project.Path
workspacesByPath[path] = append(workspacesByPath[path], l.Workspace)
}
// sort keys so we can write deterministic tests
var sortedPaths []string
for p := range workspacesByPath {
sortedPaths = append(sortedPaths, p)
}
sort.Strings(sortedPaths)
var projects []templatedProject
for _, p := range sortedPaths {
workspace := workspacesByPath[p]
workspacesStr := fmt.Sprintf("`%s`", strings.Join(workspace, "`, `"))
if len(workspace) == 1 {
projects = append(projects, templatedProject{
Path: p,
Workspaces: "workspace: " + workspacesStr,
})
} else {
projects = append(projects, templatedProject{
Path: p,
Workspaces: "workspaces: " + workspacesStr,
})
}
}
return projects
}