Files
warmbly/internal/api/handler/admin_updates.go
T

81 lines
2.7 KiB
Go

package handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/app/updates"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/version"
)
// The update surface behind the admin panel's top bar: what is running, what
// is newest, and the one button that applies it through the host-side updater.
//
// GET /admin/instance/update state; ?log=1 keeps the job log
// POST /admin/instance/update/check refresh GitHub and the updater now
// POST /admin/instance/update/apply start an update job
// AdminUpdateState returns the cached release check plus a live read of the
// updater. Cheap enough for the top bar to poll.
func (h *Handler) AdminUpdateState(c *gin.Context) {
if h.UpdatesService == nil {
c.JSON(http.StatusOK, updates.State{Running: version.Current(), Updater: updates.UpdaterView{Status: "off"}})
return
}
withLog := c.Query("log") == "1"
c.JSON(http.StatusOK, h.UpdatesService.State(c.Request.Context(), withLog))
}
// AdminUpdateCheck re-runs both checks immediately.
func (h *Handler) AdminUpdateCheck(c *gin.Context) {
if h.UpdatesService == nil {
errx.JSON(c, errx.New(errx.BadRequest, "Update checks are not available on this deployment."))
return
}
h.audit(c, models.AuditActionCheckReleases, models.AuditEntityInstance, nil, nil)
c.JSON(http.StatusOK, h.UpdatesService.Check(c.Request.Context()))
}
type applyUpdateBody struct {
// Target is "latest" (default) or a release tag.
Target string `json:"target"`
}
// AdminUpdateApply starts an update job on the updater and returns it. The
// backend restarts as part of the job, so the caller polls the state endpoint
// until it answers again with a new version.
func (h *Handler) AdminUpdateApply(c *gin.Context) {
if h.UpdatesService == nil {
errx.JSON(c, errx.New(errx.BadRequest, "Updates are not available on this deployment."))
return
}
var body applyUpdateBody
if c.Request.ContentLength > 0 {
if err := c.ShouldBindJSON(&body); err != nil {
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
return
}
}
job, err := h.UpdatesService.Apply(c.Request.Context(), body.Target)
if err != nil {
switch {
case errors.Is(err, updates.ErrUpdaterNotConfigured), errors.Is(err, updates.ErrNothingToApply):
errx.JSON(c, errx.New(errx.BadRequest, err.Error()))
default:
errx.JSON(c, errx.New(errx.Conflict, err.Error()))
}
return
}
jobID, _ := uuid.Parse(job.ID)
h.audit(c, models.AuditActionUpgrade, models.AuditEntityInstance, &jobID, map[string]string{
"target": job.Target,
"from_commit": job.FromCommit,
"running": version.String(),
})
c.JSON(http.StatusAccepted, job)
}