Files
atlantis/server/locks_controller.go
Luke Kysow a57d4c6618 Comment on pull request when lock discarded.
When the lock is discarded from the Atlantis UI, comment back on pull
request so users know the lock was discarded.

- Adds the BaseRepo field to the PullRequest model.
- This change is backwards compatible with previous installations where
the DB will have a Project model with a PullRequest without the BaseRepo
field.
2018-05-30 16:38:40 +02:00

124 lines
4.3 KiB
Go

package server
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/gorilla/mux"
"github.com/runatlantis/atlantis/server/events/locking"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
"github.com/runatlantis/atlantis/server/logging"
)
// LocksController handles all requests relating to Atlantis locks.
type LocksController struct {
AtlantisVersion string
Locker locking.Locker
Logger *logging.SimpleLogger
VCSClient vcs.ClientProxy
LockDetailTemplate TemplateWriter
}
// GetLockRoute is the GET /locks/{id} route. It renders the lock detail view.
func (l *LocksController) GetLockRoute(w http.ResponseWriter, r *http.Request) {
id, ok := mux.Vars(r)["id"]
if !ok {
l.respond(w, logging.Warn, http.StatusBadRequest, "No lock id in request")
return
}
l.GetLock(w, id)
}
// GetLock handles a lock detail page view. GetLockRoute is expected to
// be called before. This function was extracted to make it testable.
func (l *LocksController) GetLock(w http.ResponseWriter, id string) {
idUnencoded, err := url.QueryUnescape(id)
if err != nil {
l.respond(w, logging.Warn, http.StatusBadRequest, "Invalid lock id: %s", err)
return
}
lock, err := l.Locker.GetLock(idUnencoded)
if err != nil {
l.respond(w, logging.Error, http.StatusInternalServerError, "Failed getting lock: %s", err)
return
}
if lock == nil {
l.respond(w, logging.Info, http.StatusNotFound, "No lock found at id %q", idUnencoded)
return
}
// Extract the repo owner and repo name.
repo := strings.Split(lock.Project.RepoFullName, "/")
viewData := LockDetailData{
LockKeyEncoded: id,
LockKey: idUnencoded,
RepoOwner: repo[0],
RepoName: repo[1],
PullRequestLink: lock.Pull.URL,
LockedBy: lock.Pull.Author,
Workspace: lock.Workspace,
AtlantisVersion: l.AtlantisVersion,
}
l.LockDetailTemplate.Execute(w, viewData) // nolint: errcheck
}
// DeleteLockRoute handles deleting the lock at id.
func (l *LocksController) DeleteLockRoute(w http.ResponseWriter, r *http.Request) {
id, ok := mux.Vars(r)["id"]
if !ok || id == "" {
l.respond(w, logging.Warn, http.StatusBadRequest, "No lock id in request")
return
}
l.DeleteLock(w, id)
}
// DeleteLock deletes the lock and comments back on the pull request that the
// lock has been deleted.
// DeleteLockRoute should be called first. This method is split out to make this route testable.
func (l *LocksController) DeleteLock(w http.ResponseWriter, id string) {
idUnencoded, err := url.PathUnescape(id)
if err != nil {
l.respond(w, logging.Warn, http.StatusBadRequest, "Invalid lock id %q. Failed with error: %s", id, err)
return
}
lock, err := l.Locker.Unlock(idUnencoded)
if err != nil {
l.respond(w, logging.Error, http.StatusInternalServerError, "deleting lock failed with: %s", err)
return
}
if lock == nil {
l.respond(w, logging.Info, http.StatusNotFound, "No lock found at id %q", idUnencoded)
return
}
// Once the lock has been deleted, comment back on the pull request.
comment := fmt.Sprintf("**Warning**: The plan for path: `%s` workspace: `%s` was **discarded** via the Atlantis UI.\n\n"+
"To `apply` you must run `plan` again.", lock.Project.Path, lock.Workspace)
// NOTE: Because BaseRepo was added to the PullRequest model later, previous
// installations of Atlantis will have locks in their DB that do not have
// this field on PullRequest. We skip commenting in this case.
if lock.Pull.BaseRepo != (models.Repo{}) {
err = l.VCSClient.CreateComment(lock.Pull.BaseRepo, lock.Pull.Num, comment)
if err != nil {
l.respond(w, logging.Error, http.StatusInternalServerError, "Failed commenting on pull request: %s", err)
return
}
} else {
l.Logger.Debug("skipping commenting on pull request that lock was deleted because BaseRepo field is empty")
}
l.respond(w, logging.Info, http.StatusOK, "Deleted lock id %q", id)
}
// respond is a helper function to respond and log the response. lvl is the log
// level to log at, code is the HTTP response code.
func (l *LocksController) respond(w http.ResponseWriter, lvl logging.LogLevel, responseCode int, format string, args ...interface{}) {
response := fmt.Sprintf(format, args...)
l.Logger.Log(lvl, response)
w.WriteHeader(responseCode)
fmt.Fprintln(w, response)
}