diff --git a/api/build/variables.go b/api/build/variables.go new file mode 100644 index 000000000..d20f64c42 --- /dev/null +++ b/api/build/variables.go @@ -0,0 +1,9 @@ +package build + +// Variables to be set during the build time +var BuildNumber string +var ImageTag string +var NodejsVersion string +var YarnVersion string +var WebpackVersion string +var GoVersion string diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 68806b9aa..88c6d0b31 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -16,6 +16,7 @@ import ( "github.com/portainer/libhelm" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/apikey" + "github.com/portainer/portainer/api/build" "github.com/portainer/portainer/api/chisel" "github.com/portainer/portainer/api/cli" "github.com/portainer/portainer/api/crypto" @@ -743,7 +744,15 @@ func main() { for { server := buildServer(flags) - logrus.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion) + logrus.WithFields(logrus.Fields{ + "Version": portainer.APIVersion, + "BuildNumber": build.BuildNumber, + "ImageTag": build.ImageTag, + "NodejsVersion": build.NodejsVersion, + "YarnVersion": build.YarnVersion, + "WebpackVersion": build.WebpackVersion, + "GoVersion": build.GoVersion}, + ).Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion) err := server.Start() logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err) } diff --git a/api/http/handler/status/handler.go b/api/http/handler/status/handler.go index 19c64b4bb..b0cd04bab 100644 --- a/api/http/handler/status/handler.go +++ b/api/http/handler/status/handler.go @@ -27,7 +27,7 @@ func NewHandler(bouncer *security.RequestBouncer, status *portainer.Status, demo h.Handle("/status", bouncer.PublicAccess(httperror.LoggerHandler(h.statusInspect))).Methods(http.MethodGet) h.Handle("/status/version", - bouncer.AuthenticatedAccess(http.HandlerFunc(h.statusInspectVersion))).Methods(http.MethodGet) + bouncer.AuthenticatedAccess(http.HandlerFunc(h.version))).Methods(http.MethodGet) return h } diff --git a/api/http/handler/status/status_inspect_version.go b/api/http/handler/status/status_inspect_version.go deleted file mode 100644 index 99c5a4282..000000000 --- a/api/http/handler/status/status_inspect_version.go +++ /dev/null @@ -1,62 +0,0 @@ -package status - -import ( - "encoding/json" - "net/http" - - "github.com/coreos/go-semver/semver" - - portainer "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/http/client" - - "github.com/portainer/libhttp/response" -) - -type inspectVersionResponse struct { - // Whether portainer has an update available - UpdateAvailable bool `json:"UpdateAvailable" example:"false"` - // The latest version available - LatestVersion string `json:"LatestVersion" example:"2.0.0"` -} - -type githubData struct { - TagName string `json:"tag_name"` -} - -// @id StatusInspectVersion -// @summary Check for portainer updates -// @description Check if portainer has an update available -// @description **Access policy**: authenticated -// @security ApiKeyAuth -// @security jwt -// @tags status -// @produce json -// @success 200 {object} inspectVersionResponse "Success" -// @router /status/version [get] -func (handler *Handler) statusInspectVersion(w http.ResponseWriter, r *http.Request) { - motd, err := client.Get(portainer.VersionCheckURL, 5) - if err != nil { - response.JSON(w, &inspectVersionResponse{UpdateAvailable: false}) - return - } - - var data githubData - err = json.Unmarshal(motd, &data) - if err != nil { - response.JSON(w, &inspectVersionResponse{UpdateAvailable: false}) - return - } - - resp := inspectVersionResponse{ - UpdateAvailable: false, - } - - currentVersion := semver.New(portainer.APIVersion) - latestVersion := semver.New(data.TagName) - if currentVersion.LessThan(*latestVersion) { - resp.UpdateAvailable = true - resp.LatestVersion = data.TagName - } - - response.JSON(w, &resp) -} diff --git a/api/http/handler/status/version.go b/api/http/handler/status/version.go new file mode 100644 index 000000000..27de4277a --- /dev/null +++ b/api/http/handler/status/version.go @@ -0,0 +1,105 @@ +package status + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/coreos/go-semver/semver" + + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/build" + "github.com/portainer/portainer/api/http/client" + + "github.com/portainer/libhttp/response" + log "github.com/sirupsen/logrus" +) + +type versionResponse struct { + // Whether portainer has an update available + UpdateAvailable bool `json:"UpdateAvailable" example:"false"` + // The latest version available + LatestVersion string `json:"LatestVersion" example:"2.0.0"` + + ServerVersion string + DatabaseVersion string + Build BuildInfo +} + +type BuildInfo struct { + BuildNumber string + ImageTag string + NodejsVersion string + YarnVersion string + WebpackVersion string + GoVersion string +} + +// @id Version +// @summary Check for portainer updates +// @description Check if portainer has an update available +// @description **Access policy**: authenticated +// @security ApiKeyAuth +// @security jwt +// @tags status +// @produce json +// @success 200 {object} versionResponse "Success" +// @router /status/version [get] +func (handler *Handler) version(w http.ResponseWriter, r *http.Request) { + result := &versionResponse{ + ServerVersion: portainer.APIVersion, + DatabaseVersion: strconv.Itoa(portainer.DBVersion), + Build: BuildInfo{ + BuildNumber: build.BuildNumber, + ImageTag: build.ImageTag, + NodejsVersion: build.NodejsVersion, + YarnVersion: build.YarnVersion, + WebpackVersion: build.WebpackVersion, + GoVersion: build.GoVersion, + }, + } + + latestVersion := getLatestVersion() + if hasNewerVersion(portainer.APIVersion, latestVersion) { + result.UpdateAvailable = true + result.LatestVersion = latestVersion + } + + response.JSON(w, &result) +} + +func getLatestVersion() string { + motd, err := client.Get(portainer.VersionCheckURL, 5) + if err != nil { + log.WithError(err).Debug("couldn't fetch latest Portainer release version") + return "" + } + + var data struct { + TagName string `json:"tag_name"` + } + + err = json.Unmarshal(motd, &data) + if err != nil { + log.WithError(err).Debug("couldn't parse latest Portainer version") + return "" + } + + return data.TagName +} + +func hasNewerVersion(currentVersion, latestVersion string) bool { + currentVersionSemver, err := semver.NewVersion(currentVersion) + if err != nil { + log.WithField("version", currentVersion).Debug("current Portainer version isn't a semver") + return false + } + + latestVersionSemver, err := semver.NewVersion(latestVersion) + if err != nil { + log.WithField("version", latestVersion).Debug("latest Portainer version isn't a semver") + return false + } + + return currentVersionSemver.LessThan(*latestVersionSemver) +}