all: implement self-upgrade flag from API

So upgrading don't have to be initiated manually, helping large
deployments to upgrade to latest ctrld version easily.
This commit is contained in:
Cuong Manh Le
2025-03-17 20:44:03 +07:00
committed by Cuong Manh Le
parent f27cbe3525
commit c60cf33af3
9 changed files with 175 additions and 4 deletions
+57
View File
@@ -11,6 +11,7 @@ import (
"net/netip"
"net/url"
"os"
"os/exec"
"runtime"
"slices"
"sort"
@@ -21,6 +22,7 @@ import (
"syscall"
"time"
"github.com/Masterminds/semver/v3"
"github.com/kardianos/service"
"github.com/rs/zerolog"
"github.com/spf13/viper"
@@ -304,6 +306,16 @@ func (p *prog) apiConfigReload() {
logger := mainLog.Load().With().Str("mode", "api-reload").Logger()
logger.Debug().Msg("starting custom config reload timer")
lastUpdated := time.Now().Unix()
curVerStr := curVersion()
curVer, err := semver.NewVersion(curVerStr)
isStable := curVer != nil && curVer.Prerelease() == ""
if err != nil || !isStable {
l := mainLog.Load().Warn()
if err != nil {
l = l.Err(err)
}
l.Msgf("current version is not stable, skipping self-upgrade: %s", curVerStr)
}
doReloadApiConfig := func(forced bool, logger zerolog.Logger) {
resolverConfig, err := controld.FetchResolverConfig(cdUID, rootCmd.Version, cdDev)
@@ -313,6 +325,11 @@ func (p *prog) apiConfigReload() {
return
}
// Performing self-upgrade check for production version.
if isStable {
selfUpgradeCheck(resolverConfig.Ctrld.VersionTarget, curVer, &logger)
}
if resolverConfig.DeactivationPin != nil {
newDeactivationPin := *resolverConfig.DeactivationPin
curDeactivationPin := cdDeactivationPin.Load()
@@ -1422,6 +1439,46 @@ func selfUninstallCheck(uninstallErr error, p *prog, logger zerolog.Logger) {
}
}
// selfUpgradeCheck checks if the version target vt is greater
// than the current one cv, perform self-upgrade then.
//
// The callers must ensure curVer and logger are non-nil.
func selfUpgradeCheck(vt string, cv *semver.Version, logger *zerolog.Logger) {
if vt == "" {
logger.Debug().Msg("no version target set, skipped checking self-upgrade")
return
}
vts := vt
if !strings.HasPrefix(vts, "v") {
vts = "v" + vts
}
targetVer, err := semver.NewVersion(vts)
if err != nil {
logger.Warn().Err(err).Msgf("invalid target version, skipped self-upgrade: %s", vt)
return
}
if !targetVer.GreaterThan(cv) {
logger.Debug().
Str("target", vt).
Str("current", cv.String()).
Msgf("target version is not greater than current one, skipped self-upgrade")
return
}
exe, err := os.Executable()
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to get executable path, skipped self-upgrade")
return
}
cmd := exec.Command(exe, "upgrade", "prod", "-vv")
cmd.SysProcAttr = sysProcAttrForSelfUpgrade()
if err := cmd.Start(); err != nil {
mainLog.Load().Error().Err(err).Msg("failed to start self-upgrade")
return
}
mainLog.Load().Debug().Msgf("self-upgrade triggered, version target: %s", vts)
}
// leakOnUpstreamFailure reports whether ctrld should initiate a recovery flow
// when upstream failures occur.
func (p *prog) leakOnUpstreamFailure() bool {