| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "log" |
| 6 | "net/http" |
| 7 | ) |
| 8 | |
| 9 | // ErrTemporaryApp is the store operations' refusal (RenameAppIn, MoveApp, |
| 10 | // SetAppControlPlane(…, true)) for a row with expires_at > 0. A temporary |
| 11 | // row cannot change its name, its account, or its protection class |
| 12 | // (temporary static sites spec §4): rename and move would point it at bucket |
| 13 | // state it did not create, and the control-plane flag would take it out of |
| 14 | // automatic destruction's reach. |
| 15 | var ErrTemporaryApp = errors.New("the app is temporary. Make it permanent first with carlos apps keep") |
| 16 | |
| 17 | // temporaryRefusal answers "may this door proceed?" for the §4 table. msg is |
| 18 | // "" when the app is not temporary; otherwise the sentence to show, naming |
| 19 | // `carlos apps keep`, and the status a JSON door sends. A row that cannot be |
| 20 | // read refuses with 503: "could not check" is not "permanent". |
| 21 | // |
| 22 | // Callers MUST be past their own membership gate first, for the same reason |
| 23 | // controlPlaneRefusal gives: consulted earlier, this is an oracle. |
| 24 | // |
| 25 | // action is a whole clause ("ship a binary release to it"), so the sentence |
| 26 | // reads as English for every door. |
| 27 | func (s *Server) temporaryRefusal(acct *Account, app, action string) (msg string, status int) { |
| 28 | life, err := s.store.AppLife(acct.ID, app) |
| 29 | if err != nil { |
| 30 | log.Printf("console: temporary check %s/%s: %v", acct.Sqid, app, err) |
| 31 | return "Could not check whether " + app + " is temporary. Nothing was changed. Try again.", |
| 32 | http.StatusServiceUnavailable |
| 33 | } |
| 34 | if life == nil || !life.Temporary() { |
| 35 | return "", 0 |
| 36 | } |
| 37 | return app + " is temporary, so you cannot " + action + ". " + |
| 38 | "Make it permanent first with: carlos apps keep --app " + app, |
| 39 | http.StatusConflict |
| 40 | } |
| 41 | |
| 42 | // refuseTemporary is temporaryRefusal for the JSON doors: it writes the |
| 43 | // refusal and returns true when the caller must stop. |
| 44 | func (s *Server) refuseTemporary(w http.ResponseWriter, acct *Account, app, action string) bool { |
| 45 | msg, status := s.temporaryRefusal(acct, app, action) |
| 46 | if msg == "" { |
| 47 | return false |
| 48 | } |
| 49 | jsonError(w, status, msg) |
| 50 | return true |
| 51 | } |
| 52 | |