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) }