diff --git a/CHANGES.md b/CHANGES.md index 6588560d..1152c792 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,9 @@ - Nested commands, such as `cloudflared tunnel run`, now consider CLI arguments even if they appear earlier on the command. For instance, `cloudflared --config config.yaml tunnel run` will now behave the same as `cloudflared tunnel --config config.yaml run` +- Warnings are now shown in the output logs whenever cloudflared is running without the most recent version and + `no-autoupdate` is `true`. + ### Bug Fixes diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 0b4bae8e..38b4560a 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -286,16 +286,14 @@ func StartServer( } // update needs to be after DNS proxy is up to resolve equinox server address - if updater.IsAutoupdateEnabled(c, log) { - autoupdateFreq := c.Duration("autoupdate-freq") - log.Info().Dur("autoupdateFreq", autoupdateFreq).Msg("Autoupdate frequency is set") - wg.Add(1) - go func() { - defer wg.Done() - autoupdater := updater.NewAutoUpdater(c.Duration("autoupdate-freq"), &listeners, log) - errC <- autoupdater.Run(ctx) - }() - } + wg.Add(1) + go func() { + defer wg.Done() + autoupdater := updater.NewAutoUpdater( + c.Bool("no-autoupdate"), c.Duration("autoupdate-freq"), &listeners, log, + ) + errC <- autoupdater.Run(ctx) + }() // Serve DNS proxy stand-alone if no hostname or tag or app is going to run if dnsProxyStandAlone(c) { diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go index c8d61c98..eb57b9d4 100644 --- a/cmd/cloudflared/tunnel/subcommands.go +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -13,6 +13,7 @@ import ( "text/tabwriter" "time" + "github.com/cloudflare/cloudflared/cmd/cloudflared/updater" "github.com/google/uuid" "github.com/mitchellh/go-homedir" "github.com/pkg/errors" @@ -149,6 +150,9 @@ func createCommand(c *cli.Context) error { } name := c.Args().First() + warningChecker := updater.StartWarningCheck(c) + defer warningChecker.LogWarningIfAny(sc.log) + _, err = sc.create(name, c.String(CredFileFlag)) return errors.Wrap(err, "failed to create tunnel") } @@ -227,6 +231,9 @@ func listCommand(c *cli.Context) error { filter.ByTunnelID(tunnelID) } + warningChecker := updater.StartWarningCheck(c) + defer warningChecker.LogWarningIfAny(sc.log) + tunnels, err := sc.list(filter) if err != nil { return err @@ -271,6 +278,7 @@ func listCommand(c *cli.Context) error { } else { fmt.Println("You have no tunnels, use 'cloudflared tunnel create' to define a new tunnel") } + return nil } @@ -349,6 +357,9 @@ func deleteCommand(c *cli.Context) error { return cliutil.UsageError(`"cloudflared tunnel delete" requires at least 1 argument, the ID or name of the tunnel to delete.`) } + warningChecker := updater.StartWarningCheck(c) + defer warningChecker.LogWarningIfAny(sc.log) + tunnelIDs, err := sc.findIDs(c.Args().Slice()) if err != nil { return err diff --git a/cmd/cloudflared/tunnel/teamnet_subcommands.go b/cmd/cloudflared/tunnel/teamnet_subcommands.go index eff830ee..50b5beb5 100644 --- a/cmd/cloudflared/tunnel/teamnet_subcommands.go +++ b/cmd/cloudflared/tunnel/teamnet_subcommands.go @@ -7,6 +7,7 @@ import ( "text/tabwriter" "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" + "github.com/cloudflare/cloudflared/cmd/cloudflared/updater" "github.com/cloudflare/cloudflared/teamnet" "github.com/pkg/errors" @@ -81,6 +82,9 @@ func showRoutesCommand(c *cli.Context) error { return errors.Wrap(err, "invalid config for routing filters") } + warningChecker := updater.StartWarningCheck(c) + defer warningChecker.LogWarningIfAny(sc.log) + routes, err := sc.listRoutes(filter) if err != nil { return err @@ -95,6 +99,7 @@ func showRoutesCommand(c *cli.Context) error { } else { fmt.Println("You have no routes, use 'cloudflared tunnel route ip add' to add a route") } + return nil } diff --git a/cmd/cloudflared/updater/check.go b/cmd/cloudflared/updater/check.go new file mode 100644 index 00000000..8a98d2bb --- /dev/null +++ b/cmd/cloudflared/updater/check.go @@ -0,0 +1,49 @@ +package updater + +import ( + "github.com/rs/zerolog" + "github.com/urfave/cli/v2" +) + +type VersionWarningChecker struct { + warningChan chan string +} + +func StartWarningCheck(c *cli.Context) VersionWarningChecker { + checker := VersionWarningChecker{ + warningChan: make(chan string), + } + + go func() { + options := updateOptions{ + updateDisabled: true, + isBeta: c.Bool("beta"), + isStaging: c.Bool("staging"), + isForced: false, + intendedVersion: "", + } + checkResult, err := CheckForUpdate(options) + if err == nil { + checker.warningChan <- checkResult.UserMessage() + } + close(checker.warningChan) + }() + + return checker +} + +func (checker VersionWarningChecker) getWarning() string { + select { + case message := <-checker.warningChan: + return message + default: + // No feedback on time, we don't wait for it, since this is best-effort. + return "" + } +} + +func (checker VersionWarningChecker) LogWarningIfAny(log *zerolog.Logger) { + if warning := checker.getWarning(); warning != "" { + log.Warn().Msg(warning) + } +} diff --git a/cmd/cloudflared/updater/service.go b/cmd/cloudflared/updater/service.go index a31c8bf5..74cb1407 100644 --- a/cmd/cloudflared/updater/service.go +++ b/cmd/cloudflared/updater/service.go @@ -1,14 +1,15 @@ package updater -// Version is the functions needed to perform an update -type Version interface { +// CheckResult is the behaviour resulting from checking in with the Update Service +type CheckResult interface { Apply() error - String() string + Version() string + UserMessage() string } // Service is the functions to get check for new updates type Service interface { - Check() (Version, error) + Check() (CheckResult, error) } const ( @@ -23,4 +24,7 @@ const ( // VersionKeyName is the url parameter key to send to the checkin API to specific what version to upgrade or downgrade to VersionKeyName = "version" + + // ClientVersionName is the url parameter key to send the version that this cloudflared is currently running with + ClientVersionName = "clientVersion" ) diff --git a/cmd/cloudflared/updater/update.go b/cmd/cloudflared/updater/update.go index cc436753..e2406760 100644 --- a/cmd/cloudflared/updater/update.go +++ b/cmd/cloudflared/updater/update.go @@ -3,18 +3,17 @@ package updater import ( "context" "fmt" - "github.com/rs/zerolog" "os" "path/filepath" "runtime" "time" - "github.com/urfave/cli/v2" - "golang.org/x/crypto/ssh/terminal" - "github.com/cloudflare/cloudflared/cmd/cloudflared/config" "github.com/cloudflare/cloudflared/logger" "github.com/facebookgo/grace/gracenet" + "github.com/rs/zerolog" + "github.com/urfave/cli/v2" + "golang.org/x/crypto/ssh/terminal" ) const ( @@ -61,16 +60,18 @@ func (e *statusErr) ExitCode() int { } type updateOptions struct { - isBeta bool - isStaging bool - isForced bool - version string + updateDisabled bool + isBeta bool + isStaging bool + isForced bool + intendedVersion string } type UpdateOutcome struct { - Updated bool - Version string - Error error + Updated bool + Version string + UserMessage string + Error error } func (uo *UpdateOutcome) noUpdate() bool { @@ -81,10 +82,10 @@ func Init(v string) { version = v } -func checkForUpdateAndApply(options updateOptions) UpdateOutcome { +func CheckForUpdate(options updateOptions) (CheckResult, error) { cfdPath, err := os.Executable() if err != nil { - return UpdateOutcome{Error: err} + return nil, err } url := UpdateURL @@ -93,24 +94,22 @@ func checkForUpdateAndApply(options updateOptions) UpdateOutcome { } s := NewWorkersService(version, url, cfdPath, Options{IsBeta: options.isBeta, - IsForced: options.isForced, RequestedVersion: options.version}) + IsForced: options.isForced, RequestedVersion: options.intendedVersion}) - v, err := s.Check() + return s.Check() +} + +func applyUpdate(options updateOptions, update CheckResult) UpdateOutcome { + if update.Version() == "" || options.updateDisabled { + return UpdateOutcome{UserMessage: update.UserMessage()} + } + + err := update.Apply() if err != nil { return UpdateOutcome{Error: err} } - //already on the latest version - if v == nil { - return UpdateOutcome{} - } - - err = v.Apply() - if err != nil { - return UpdateOutcome{Error: err} - } - - return UpdateOutcome{Updated: true, Version: v.String()} + return UpdateOutcome{Updated: true, Version: update.Version(), UserMessage: update.UserMessage()} } // Update is the handler for the update command from the command line @@ -137,7 +136,13 @@ func Update(c *cli.Context) error { log.Info().Msg("cloudflared is set to upgrade to the latest publish version regardless of the current version") } - updateOutcome := loggedUpdate(log, updateOptions{isBeta: isBeta, isStaging: isStaging, isForced: isForced, version: c.String("version")}) + updateOutcome := loggedUpdate(log, updateOptions{ + updateDisabled: false, + isBeta: isBeta, + isStaging: isStaging, + isForced: isForced, + intendedVersion: c.String("version"), + }) if updateOutcome.Error != nil { return &statusErr{updateOutcome.Error} } @@ -152,12 +157,18 @@ func Update(c *cli.Context) error { // Checks for an update and applies it if one is available func loggedUpdate(log *zerolog.Logger, options updateOptions) UpdateOutcome { - updateOutcome := checkForUpdateAndApply(options) + checkResult, err := CheckForUpdate(options) + if err != nil { + log.Err(err).Msg("update check failed") + return UpdateOutcome{Error: err} + } + + updateOutcome := applyUpdate(options, checkResult) if updateOutcome.Updated { log.Info().Str(LogFieldVersion, updateOutcome.Version).Msg("cloudflared has been updated") } if updateOutcome.Error != nil { - log.Err(updateOutcome.Error).Msg("update check failed: %s") + log.Err(updateOutcome.Error).Msg("update failed to apply") } return updateOutcome @@ -177,44 +188,53 @@ type configurable struct { freq time.Duration } -func NewAutoUpdater(freq time.Duration, listeners *gracenet.Net, log *zerolog.Logger) *AutoUpdater { - updaterConfigurable := &configurable{ - enabled: true, - freq: freq, - } - if freq == 0 { - updaterConfigurable.enabled = false - updaterConfigurable.freq = DefaultCheckUpdateFreq - } +func NewAutoUpdater(updateDisabled bool, freq time.Duration, listeners *gracenet.Net, log *zerolog.Logger) *AutoUpdater { return &AutoUpdater{ - configurable: updaterConfigurable, + configurable: createUpdateConfig(updateDisabled, freq, log), listeners: listeners, updateConfigChan: make(chan *configurable), log: log, } } +func createUpdateConfig(updateDisabled bool, freq time.Duration, log *zerolog.Logger) *configurable { + if isAutoupdateEnabled(log, updateDisabled, freq) { + log.Info().Dur("autoupdateFreq", freq).Msg("Autoupdate frequency is set") + return &configurable{ + enabled: true, + freq: freq, + } + } else { + return &configurable{ + enabled: false, + freq: DefaultCheckUpdateFreq, + } + } +} + func (a *AutoUpdater) Run(ctx context.Context) error { ticker := time.NewTicker(a.configurable.freq) for { - if a.configurable.enabled { - updateOutcome := loggedUpdate(a.log, updateOptions{}) - if updateOutcome.Updated { - if IsSysV() { - // SysV doesn't have a mechanism to keep service alive, we have to restart the process - a.log.Info().Msg("Restarting service managed by SysV...") - pid, err := a.listeners.StartProcess() - if err != nil { - a.log.Err(err).Msg("Unable to restart server automatically") - return &statusErr{err: err} - } - // stop old process after autoupdate. Otherwise we create a new process - // after each update - a.log.Info().Msgf("PID of the new process is %d", pid) + updateOutcome := loggedUpdate(a.log, updateOptions{updateDisabled: !a.configurable.enabled}) + if updateOutcome.Updated { + Init(updateOutcome.Version) + if IsSysV() { + // SysV doesn't have a mechanism to keep service alive, we have to restart the process + a.log.Info().Msg("Restarting service managed by SysV...") + pid, err := a.listeners.StartProcess() + if err != nil { + a.log.Err(err).Msg("Unable to restart server automatically") + return &statusErr{err: err} } - return &statusSuccess{newVersion: updateOutcome.Version} + // stop old process after autoupdate. Otherwise we create a new process + // after each update + a.log.Info().Msgf("PID of the new process is %d", pid) } + return &statusSuccess{newVersion: updateOutcome.Version} + } else if updateOutcome.UserMessage != "" { + a.log.Warn().Msg(updateOutcome.UserMessage) } + select { case <-ctx.Done(): return ctx.Err() @@ -229,27 +249,18 @@ func (a *AutoUpdater) Run(ctx context.Context) error { } // Update is the method to pass new AutoUpdaterConfigurable to a running AutoUpdater. It is safe to be called concurrently -func (a *AutoUpdater) Update(newFreq time.Duration) { - newConfigurable := &configurable{ - enabled: true, - freq: newFreq, - } - // A ero duration means autoupdate is disabled - if newFreq == 0 { - newConfigurable.enabled = false - newConfigurable.freq = DefaultCheckUpdateFreq - } - a.updateConfigChan <- newConfigurable +func (a *AutoUpdater) Update(updateDisabled bool, newFreq time.Duration) { + a.updateConfigChan <- createUpdateConfig(updateDisabled, newFreq, a.log) } -func IsAutoupdateEnabled(c *cli.Context, log *zerolog.Logger) bool { - if !SupportAutoUpdate(log) { +func isAutoupdateEnabled(log *zerolog.Logger, updateDisabled bool, updateFreq time.Duration) bool { + if !supportAutoUpdate(log) { return false } - return !c.Bool("no-autoupdate") && c.Duration("autoupdate-freq") != 0 + return !updateDisabled && updateFreq != 0 } -func SupportAutoUpdate(log *zerolog.Logger) bool { +func supportAutoUpdate(log *zerolog.Logger) bool { if runtime.GOOS == "windows" { log.Info().Msg(noUpdateOnWindowsMessage) return false diff --git a/cmd/cloudflared/updater/update_test.go b/cmd/cloudflared/updater/update_test.go index a20c4ffa..f977b96e 100644 --- a/cmd/cloudflared/updater/update_test.go +++ b/cmd/cloudflared/updater/update_test.go @@ -2,17 +2,19 @@ package updater import ( "context" + "flag" "testing" "github.com/facebookgo/grace/gracenet" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" ) func TestDisabledAutoUpdater(t *testing.T) { listeners := &gracenet.Net{} log := zerolog.Nop() - autoupdater := NewAutoUpdater(0, listeners, &log) + autoupdater := NewAutoUpdater(false, 0, listeners, &log) ctx, cancel := context.WithCancel(context.Background()) errC := make(chan error) go func() { @@ -26,3 +28,13 @@ func TestDisabledAutoUpdater(t *testing.T) { // Make sure that autoupdater terminates after canceling the context assert.Equal(t, context.Canceled, <-errC) } + +func TestCheckInWithUpdater(t *testing.T) { + flagSet := flag.NewFlagSet(t.Name(), flag.PanicOnError) + cliCtx := cli.NewContext(cli.NewApp(), flagSet, nil) + + warningChecker := StartWarningCheck(cliCtx) + warning := warningChecker.getWarning() + // Assuming this runs either on a release or development version, then the Worker will never have anything to tell us. + assert.Empty(t, warning) +} diff --git a/cmd/cloudflared/updater/workers_service.go b/cmd/cloudflared/updater/workers_service.go index 1bfe9432..9e41eabd 100644 --- a/cmd/cloudflared/updater/workers_service.go +++ b/cmd/cloudflared/updater/workers_service.go @@ -27,6 +27,7 @@ type VersionResponse struct { Version string `json:"version"` Checksum string `json:"checksum"` IsCompressed bool `json:"compressed"` + UserMessage string `json:"userMessage"` Error string `json:"error"` } @@ -50,7 +51,7 @@ func NewWorkersService(currentVersion, url, targetPath string, opts Options) Ser } // Check does a check in with the Workers API to get a new version update -func (s *WorkersService) Check() (Version, error) { +func (s *WorkersService) Check() (CheckResult, error) { client := &http.Client{ Timeout: clientTimeout, } @@ -59,6 +60,7 @@ func (s *WorkersService) Check() (Version, error) { q := req.URL.Query() q.Add(OSKeyName, runtime.GOOS) q.Add(ArchitectureKeyName, runtime.GOARCH) + q.Add(ClientVersionName, s.currentVersion) if s.opts.IsBeta { q.Add(BetaKeyName, "true") @@ -84,11 +86,12 @@ func (s *WorkersService) Check() (Version, error) { return nil, errors.New(v.Error) } - if !s.opts.IsForced && !IsNewerVersion(s.currentVersion, v.Version) { - return nil, nil + var versionToUpdate = "" + if s.opts.IsForced || IsNewerVersion(s.currentVersion, v.Version) { + versionToUpdate = v.Version } - return NewWorkersVersion(v.URL, v.Version, v.Checksum, s.targetPath, v.IsCompressed), nil + return NewWorkersVersion(v.URL, versionToUpdate, v.Checksum, s.targetPath, v.UserMessage, v.IsCompressed), nil } // IsNewerVersion checks semantic versioning for the latest version diff --git a/cmd/cloudflared/updater/workers_service_test.go b/cmd/cloudflared/updater/workers_service_test.go index 7eae8e0d..0476aa4a 100644 --- a/cmd/cloudflared/updater/workers_service_test.go +++ b/cmd/cloudflared/updater/workers_service_test.go @@ -32,15 +32,20 @@ func respondWithData(w http.ResponseWriter, b []byte, status int) { w.Write(b) } +const mostRecentVersion = "2021.2.5" +const mostRecentBetaVersion = "2021.3.0" +const knownBuggyVersion = "2020.12.0" +const expectedUserMsg = "This message is expected when running a known buggy version" + func updateHandler(w http.ResponseWriter, r *http.Request) { - version := "2020.08.05" + version := mostRecentVersion host := fmt.Sprintf("http://%s", r.Host) url := host + "/download" query := r.URL.Query() if query.Get(BetaKeyName) == "true" { - version = "2020.08.06" + version = mostRecentBetaVersion url = host + "/beta" } @@ -59,7 +64,12 @@ func updateHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(h, version) checksum := fmt.Sprintf("%x", h.Sum(nil)) - v := VersionResponse{URL: url, Version: version, Checksum: checksum} + var userMessage = "" + if query.Get(ClientVersionName) == knownBuggyVersion { + userMessage = expectedUserMsg + } + + v := VersionResponse{URL: url, Version: version, Checksum: checksum, UserMessage: userMessage} respondWithJSON(w, v, http.StatusOK) } @@ -96,7 +106,7 @@ func compressedDownloadHandler(w http.ResponseWriter, r *http.Request) { } func downloadHandler(w http.ResponseWriter, r *http.Request) { - version := "2020.08.05" + version := mostRecentVersion requestedVersion := r.URL.Query().Get(VersionKeyName) if requestedVersion != "" { version = requestedVersion @@ -105,7 +115,7 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { } func betaHandler(w http.ResponseWriter, r *http.Request) { - respondWithData(w, []byte("2020.08.06"), http.StatusOK) + respondWithData(w, []byte(mostRecentBetaVersion), http.StatusOK) } func failureHandler(w http.ResponseWriter, r *http.Request) { @@ -124,7 +134,7 @@ func createServer() *httptest.Server { } func createTestFile(t *testing.T, path string) { - f, err := os.Create("tmpfile") + f, err := os.Create(path) require.NoError(t, err) fmt.Fprint(f, "2020.08.04") f.Close() @@ -142,13 +152,13 @@ func TestUpdateService(t *testing.T) { s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{}) v, err := s.Check() require.NoError(t, err) - require.Equal(t, v.String(), "2020.08.05") + require.Equal(t, v.Version(), mostRecentVersion) require.NoError(t, v.Apply()) dat, err := ioutil.ReadFile(testFilePath) require.NoError(t, err) - require.Equal(t, string(dat), "2020.08.05") + require.Equal(t, string(dat), mostRecentVersion) } func TestBetaUpdateService(t *testing.T) { @@ -162,13 +172,13 @@ func TestBetaUpdateService(t *testing.T) { s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{IsBeta: true}) v, err := s.Check() require.NoError(t, err) - require.Equal(t, v.String(), "2020.08.06") + require.Equal(t, v.Version(), mostRecentBetaVersion) require.NoError(t, v.Apply()) dat, err := ioutil.ReadFile(testFilePath) require.NoError(t, err) - require.Equal(t, string(dat), "2020.08.06") + require.Equal(t, string(dat), mostRecentBetaVersion) } func TestFailUpdateService(t *testing.T) { @@ -193,10 +203,11 @@ func TestNoUpdateService(t *testing.T) { createTestFile(t, testFilePath) defer os.Remove(testFilePath) - s := NewWorkersService("2020.8.5", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{}) + s := NewWorkersService(mostRecentVersion, fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{}) v, err := s.Check() require.NoError(t, err) - require.Nil(t, v) + require.NotNil(t, v) + require.Empty(t, v.Version()) } func TestForcedUpdateService(t *testing.T) { @@ -210,13 +221,13 @@ func TestForcedUpdateService(t *testing.T) { s := NewWorkersService("2020.8.5", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{IsForced: true}) v, err := s.Check() require.NoError(t, err) - require.Equal(t, v.String(), "2020.08.05") + require.Equal(t, v.Version(), mostRecentVersion) require.NoError(t, v.Apply()) dat, err := ioutil.ReadFile(testFilePath) require.NoError(t, err) - require.Equal(t, string(dat), "2020.08.05") + require.Equal(t, string(dat), mostRecentVersion) } func TestUpdateSpecificVersionService(t *testing.T) { @@ -231,7 +242,7 @@ func TestUpdateSpecificVersionService(t *testing.T) { s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{RequestedVersion: reqVersion}) v, err := s.Check() require.NoError(t, err) - require.Equal(t, reqVersion, v.String()) + require.Equal(t, reqVersion, v.Version()) require.NoError(t, v.Apply()) dat, err := ioutil.ReadFile(testFilePath) @@ -251,7 +262,7 @@ func TestCompressedUpdateService(t *testing.T) { s := NewWorkersService("2020.8.2", fmt.Sprintf("%s/compressed", ts.URL), testFilePath, Options{}) v, err := s.Check() require.NoError(t, err) - require.Equal(t, "2020.09.02", v.String()) + require.Equal(t, "2020.09.02", v.Version()) require.NoError(t, v.Apply()) dat, err := ioutil.ReadFile(testFilePath) @@ -260,6 +271,21 @@ func TestCompressedUpdateService(t *testing.T) { require.Equal(t, "2020.09.02", string(dat)) } +func TestUpdateWhenRunningKnownBuggyVersion(t *testing.T) { + ts := createServer() + defer ts.Close() + + testFilePath := "tmpfile" + createTestFile(t, testFilePath) + defer os.Remove(testFilePath) + + s := NewWorkersService(knownBuggyVersion, fmt.Sprintf("%s/updater", ts.URL), testFilePath, Options{}) + v, err := s.Check() + require.NoError(t, err) + require.Equal(t, v.Version(), mostRecentVersion) + require.Equal(t, v.UserMessage(), expectedUserMsg) +} + func TestVersionParsing(t *testing.T) { require.False(t, IsNewerVersion("2020.8.2", "2020.8.2")) require.True(t, IsNewerVersion("2020.8.2", "2020.8.3")) diff --git a/cmd/cloudflared/updater/workers_update.go b/cmd/cloudflared/updater/workers_update.go index 9fed63a9..9bc96b1a 100644 --- a/cmd/cloudflared/updater/workers_update.go +++ b/cmd/cloudflared/updater/workers_update.go @@ -54,6 +54,7 @@ type WorkersVersion struct { version string targetPath string isCompressed bool + userMessage string } // NewWorkersVersion creates a new Version object. This is normally created by a WorkersService JSON checkin response @@ -61,8 +62,17 @@ type WorkersVersion struct { // version is the version of this update // checksum is the expected checksum of the downloaded file // target path is where the file should be replace. Normally this the running cloudflared's path -func NewWorkersVersion(url, version, checksum, targetPath string, isCompressed bool) Version { - return &WorkersVersion{downloadURL: url, version: version, checksum: checksum, targetPath: targetPath, isCompressed: isCompressed} +// userMessage is a possible message to convey back to the user after having checked in with the Updater Service +// isCompressed tells whether the asset to update cloudflared is compressed or not +func NewWorkersVersion(url, version, checksum, targetPath, userMessage string, isCompressed bool) CheckResult { + return &WorkersVersion{ + downloadURL: url, + version: version, + checksum: checksum, + targetPath: targetPath, + isCompressed: isCompressed, + userMessage: userMessage, + } } // Apply does the actual verification and update logic. @@ -114,10 +124,16 @@ func (v *WorkersVersion) Apply() error { } // String returns the version number of this update/release (e.g. 2020.08.05) -func (v *WorkersVersion) String() string { +func (v *WorkersVersion) Version() string { return v.version } +// String returns a possible message to convey back to user after having checked in with the Updater Service. E.g. +// it can warn about the need to update the version currently running. +func (v *WorkersVersion) UserMessage() string { + return v.userMessage +} + // download the file from the link in the json func download(url, filepath string, isCompressed bool) error { client := &http.Client{