From 3c3ccc74ba6f232297ba6f8cde26a5d07a6d1f87 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 00:33:16 +0100 Subject: [PATCH 01/14] Add cli updater --- cli/common/before.go | 30 +++++++++ cli/update/command.go | 70 +++++++++++++++++++++ cli/update/tar.go | 71 +++++++++++++++++++++ cli/update/types.go | 16 +++++ cli/update/updater.go | 142 ++++++++++++++++++++++++++++++++++++++++++ cmd/cli/app.go | 8 ++- 6 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 cli/common/before.go create mode 100644 cli/update/command.go create mode 100644 cli/update/tar.go create mode 100644 cli/update/types.go create mode 100644 cli/update/updater.go diff --git a/cli/common/before.go b/cli/common/before.go new file mode 100644 index 0000000000..fff5d932f4 --- /dev/null +++ b/cli/common/before.go @@ -0,0 +1,30 @@ +package common + +import ( + "github.com/urfave/cli/v2" +) + +func Before(c *cli.Context) error { + if err := SetupGlobalLogger(c); err != nil { + return err + } + + // TODO: background update check + // go func() { + // log.Debug().Msg("Checking for updates ...") + + // newVersion, err := update.CheckForUpdate(c, false) + // if err != nil { + // log.Printf("Failed to check for updates: %s", err) + // return + // } + + // if newVersion != nil { + // log.Info().Msgf("A new version of woodpecker-cli is available: %s", newVersion) + // } else { + // log.Debug().Msgf("You are using the latest version of woodpecker-cli") + // } + // }() + + return nil +} diff --git a/cli/update/command.go b/cli/update/command.go new file mode 100644 index 0000000000..10fb28c199 --- /dev/null +++ b/cli/update/command.go @@ -0,0 +1,70 @@ +package update + +import ( + "fmt" + "os" + "path" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" +) + +// Command exports the info command. +var Command = &cli.Command{ + Name: "update", + Usage: "update the woodpecker-cli to the latest version", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "force", + Usage: "force update even if the latest version is already installed", + }, + }, + Action: update, +} + +func update(c *cli.Context) error { + log.Info().Msg("Checking for updates ...") + + newVersion, err := CheckForUpdate(c, c.Bool("force")) + if err != nil { + return err + } + + if newVersion == nil { + fmt.Println("You are using the latest version of woodpecker-cli") + return nil + } + + log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version) + + var tarFilePath string + tarFilePath, err = downloadNewVersion(c.Context, newVersion.AssetURL) + if err != nil { + return err + } + + log.Debug().Msgf("New version %s has been downloaded successfully! Installing ...", newVersion.Version) + + binFile, err := extractNewVersion(tarFilePath) + if err != nil { + return err + } + + log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile) + + pwd, err := os.Getwd() + if err != nil { + return err + } + dst := path.Join(pwd, path.Base(c.App.Name)) + + log.Debug().Msgf("Moving %s to %s", binFile, dst) + + // if err := os.Rename(binFile, dst); err != nil { + // return err + // } + + log.Info().Msgf("woodpecker-cli %s has been installed successfully! Please restart the CLI.", newVersion.Version) + + return nil +} diff --git a/cli/update/tar.go b/cli/update/tar.go new file mode 100644 index 0000000000..874e40cea3 --- /dev/null +++ b/cli/update/tar.go @@ -0,0 +1,71 @@ +package update + +import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "path/filepath" +) + +func Untar(dst string, r io.Reader) error { + gzr, err := gzip.NewReader(r) + if err != nil { + return err + } + defer gzr.Close() + + tr := tar.NewReader(gzr) + + for { + header, err := tr.Next() + + switch { + // if no more files are found return + case err == io.EOF: + return nil + + // return any other error + case err != nil: + return err + + // if the header is nil, just skip it (not sure how this happens) + case header == nil: + continue + } + + // the target location where the dir/file should be created + target := filepath.Join(dst, header.Name) + + // the following switch could also be done using fi.Mode(), not sure if there + // a benefit of using one vs. the other. + // fi := header.FileInfo() + + // check the file type + switch header.Typeflag { + // if its a dir and it doesn't exist create it + case tar.TypeDir: + if _, err := os.Stat(target); err != nil { + if err := os.MkdirAll(target, 0x755); err != nil { + return err + } + } + + // if it's a file create it + case tar.TypeReg: + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return err + } + + // copy over contents + if _, err := io.Copy(f, tr); err != nil { + return err + } + + // manually close here after each file operation; defering would cause each file close + // to wait until all operations have completed. + f.Close() + } + } +} diff --git a/cli/update/types.go b/cli/update/types.go new file mode 100644 index 0000000000..35ae99181d --- /dev/null +++ b/cli/update/types.go @@ -0,0 +1,16 @@ +package update + +type GithubRelease struct { + TagName string `json:"tag_name"` + Assets []struct { + Name string `json:"name"` + BrowserDownloadURL string `json:"browser_download_url"` + } `json:"assets"` +} + +type NewVersion struct { + Version string + AssetURL string +} + +const githubReleaseURL = "https://api.github.com/repos/woodpecker-ci/woodpecker/releases/latest" diff --git a/cli/update/updater.go b/cli/update/updater.go new file mode 100644 index 0000000000..8a59fe3127 --- /dev/null +++ b/cli/update/updater.go @@ -0,0 +1,142 @@ +package update + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "regexp" + "runtime" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" + + "go.woodpecker-ci.org/woodpecker/v2/version" +) + +func CheckForUpdate(c *cli.Context, force bool) (*NewVersion, error) { + log.Debug().Str("current-version", version.String()).Msg("Checking for updates ...") + + if version.String() == "dev" && !force { + log.Debug().Msgf("Skipping update check for development version") + return nil, nil + } + + req, err := http.NewRequestWithContext(c.Context, "GET", githubReleaseURL, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to fetch the latest release") + } + + var release GithubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return nil, err + } + + // using the latest release + if release.TagName == version.String() && !force { + return nil, nil + } + + log.Debug().Msgf("Latest version: %s", release.TagName) + log.Debug().Msgf("Current version: %s", version.String()) + + fileRegex, err := regexp.Compile(fmt.Sprintf("^woodpecker\\-cli\\_%s_%s\\.tar\\.gz$", runtime.GOOS, runtime.GOARCH)) + if err != nil { + return nil, err + } + + assetURL := "" + for _, asset := range release.Assets { + if fileRegex.MatchString(asset.Name) { + assetURL = asset.BrowserDownloadURL + log.Debug().Msgf("Found asset for the current OS and arch: %s", assetURL) + break + } + } + + if assetURL == "" { + return nil, errors.New("no asset found for the current OS") + } + + return &NewVersion{ + Version: release.TagName, + AssetURL: assetURL, + }, nil +} + +func downloadNewVersion(ctx context.Context, downloadURL string) (string, error) { + log.Debug().Msgf("Downloading new version from %s ...", downloadURL) + + req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil) + if err != nil { + return "", err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", errors.New("failed to download the new version") + } + + file, err := os.CreateTemp("", "woodpecker-cli-*.tar.gz") + if err != nil { + return "", err + } + defer file.Close() + + if _, err := io.Copy(file, resp.Body); err != nil { + return "", err + } + + log.Debug().Msgf("New version downloaded to %s", file.Name()) + + return file.Name(), nil +} + +func extractNewVersion(tarFilePath string) (string, error) { + log.Debug().Msgf("Extracting new version from %s ...", tarFilePath) + + tarFile, err := os.Open(tarFilePath) + if err != nil { + return "", err + } + + defer tarFile.Close() + + tmpDir, err := os.MkdirTemp("", "woodpecker-cli-*") + if err != nil { + return "", err + } + + err = Untar(tmpDir, tarFile) + if err != nil { + return "", err + } + + err = os.Remove(tarFilePath) + if err != nil { + return "", err + } + + log.Debug().Msgf("New version extracted to %s", tmpDir) + + return path.Join(tmpDir, "woodpecker-cli"), nil +} diff --git a/cmd/cli/app.go b/cmd/cli/app.go index 7205290111..39d7317d5d 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -29,6 +29,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/cli/registry" "go.woodpecker-ci.org/woodpecker/v2/cli/repo" "go.woodpecker-ci.org/woodpecker/v2/cli/secret" + "go.woodpecker-ci.org/woodpecker/v2/cli/update" "go.woodpecker-ci.org/woodpecker/v2/cli/user" "go.woodpecker-ci.org/woodpecker/v2/version" ) @@ -37,11 +38,13 @@ import ( func newApp() *cli.App { app := cli.NewApp() app.Name = "woodpecker-cli" + app.Description = "Woodpecker command line utility" app.Version = version.String() - app.Usage = "command line utility" + app.Usage = "woodpecker [global options] command [command options] [arguments...]" app.EnableBashCompletion = true app.Flags = common.GlobalFlags - app.Before = common.SetupGlobalLogger + app.Before = common.Before + app.Suggest = true app.Commands = []*cli.Command{ pipeline.Command, log.Command, @@ -55,6 +58,7 @@ func newApp() *cli.App { lint.Command, loglevel.Command, cron.Command, + update.Command, } return app From 630d2f8d04f412af17b8bf890c7439e26e76895c Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 00:42:55 +0100 Subject: [PATCH 02/14] enable background update check --- cli/common/before.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cli/common/before.go b/cli/common/before.go index fff5d932f4..d3c31b05e5 100644 --- a/cli/common/before.go +++ b/cli/common/before.go @@ -1,7 +1,10 @@ package common import ( + "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" + + "go.woodpecker-ci.org/woodpecker/v2/cli/update" ) func Before(c *cli.Context) error { @@ -10,21 +13,21 @@ func Before(c *cli.Context) error { } // TODO: background update check - // go func() { - // log.Debug().Msg("Checking for updates ...") + go func() { + log.Debug().Msg("Checking for updates ...") - // newVersion, err := update.CheckForUpdate(c, false) - // if err != nil { - // log.Printf("Failed to check for updates: %s", err) - // return - // } + newVersion, err := update.CheckForUpdate(c, false) + if err != nil { + log.Error().Err(err).Msgf("Failed to check for updates") + return + } - // if newVersion != nil { - // log.Info().Msgf("A new version of woodpecker-cli is available: %s", newVersion) - // } else { - // log.Debug().Msgf("You are using the latest version of woodpecker-cli") - // } - // }() + if newVersion != nil { + log.Warn().Msgf("A new version of woodpecker-cli is available: %s", newVersion) + } else { + log.Debug().Msgf("You are using the latest version of woodpecker-cli") + } + }() return nil } From dff11cfc2dc0edf6ef41f907a5173e15d32c7bb7 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:54:29 +0100 Subject: [PATCH 03/14] wait for updater --- cli/common/before.go | 23 +++++++++++++++++++---- cmd/cli/app.go | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cli/common/before.go b/cli/common/before.go index d3c31b05e5..e86247f327 100644 --- a/cli/common/before.go +++ b/cli/common/before.go @@ -1,33 +1,48 @@ package common import ( + "context" + "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "go.woodpecker-ci.org/woodpecker/v2/cli/update" ) +var waitForUpdateCheck chan struct{} + func Before(c *cli.Context) error { if err := SetupGlobalLogger(c); err != nil { return err } - // TODO: background update check go func() { + waitForUpdateCheck = make(chan struct{}) + log.Debug().Msg("Checking for updates ...") - newVersion, err := update.CheckForUpdate(c, false) + newVersion, err := update.CheckForUpdate(context.Background(), true) if err != nil { log.Error().Err(err).Msgf("Failed to check for updates") return } if newVersion != nil { - log.Warn().Msgf("A new version of woodpecker-cli is available: %s", newVersion) + log.Warn().Msgf("A new version of woodpecker-cli is available: %s. Update by running: %s update", newVersion.Version, c.App.Name) } else { - log.Debug().Msgf("You are using the latest version of woodpecker-cli") + log.Debug().Msgf("No update required") } + + close(waitForUpdateCheck) }() return nil } + +func After(_ *cli.Context) error { + if waitForUpdateCheck != nil { + <-waitForUpdateCheck + } + + return nil +} diff --git a/cmd/cli/app.go b/cmd/cli/app.go index 39d7317d5d..b13ad3f405 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -44,6 +44,7 @@ func newApp() *cli.App { app.EnableBashCompletion = true app.Flags = common.GlobalFlags app.Before = common.Before + app.After = common.After app.Suggest = true app.Commands = []*cli.Command{ pipeline.Command, From d3a5133ed5a97d997a96becbfb63d28351ea2a28 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:56:26 +0100 Subject: [PATCH 04/14] only check prod --- cli/common/before.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cli/common/before.go b/cli/common/before.go index e86247f327..77b208693e 100644 --- a/cli/common/before.go +++ b/cli/common/before.go @@ -1,8 +1,6 @@ package common import ( - "context" - "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" @@ -21,7 +19,7 @@ func Before(c *cli.Context) error { log.Debug().Msg("Checking for updates ...") - newVersion, err := update.CheckForUpdate(context.Background(), true) + newVersion, err := update.CheckForUpdate(c.Context, false) if err != nil { log.Error().Err(err).Msgf("Failed to check for updates") return From 042c079831b5eb1098107e02ced9eae3a6c44691 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:56:44 +0100 Subject: [PATCH 05/14] cleanup --- cli/update/updater.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cli/update/updater.go b/cli/update/updater.go index 8a59fe3127..006e9170ee 100644 --- a/cli/update/updater.go +++ b/cli/update/updater.go @@ -13,20 +13,19 @@ import ( "runtime" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" "go.woodpecker-ci.org/woodpecker/v2/version" ) -func CheckForUpdate(c *cli.Context, force bool) (*NewVersion, error) { - log.Debug().Str("current-version", version.String()).Msg("Checking for updates ...") +func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) { + log.Debug().Msgf("Current version: %s", version.String()) if version.String() == "dev" && !force { log.Debug().Msgf("Skipping update check for development version") return nil, nil } - req, err := http.NewRequestWithContext(c.Context, "GET", githubReleaseURL, nil) + req, err := http.NewRequestWithContext(ctx, "GET", githubReleaseURL, nil) if err != nil { return nil, err } @@ -52,7 +51,6 @@ func CheckForUpdate(c *cli.Context, force bool) (*NewVersion, error) { } log.Debug().Msgf("Latest version: %s", release.TagName) - log.Debug().Msgf("Current version: %s", version.String()) fileRegex, err := regexp.Compile(fmt.Sprintf("^woodpecker\\-cli\\_%s_%s\\.tar\\.gz$", runtime.GOOS, runtime.GOARCH)) if err != nil { From 1db6f4008c3584a6f56aa4133531b81fcc901f44 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:57:01 +0100 Subject: [PATCH 06/14] improve --- cli/update/command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/update/command.go b/cli/update/command.go index 10fb28c199..dc5453f8ce 100644 --- a/cli/update/command.go +++ b/cli/update/command.go @@ -9,7 +9,7 @@ import ( "github.com/urfave/cli/v2" ) -// Command exports the info command. +// Command exports the update command. var Command = &cli.Command{ Name: "update", Usage: "update the woodpecker-cli to the latest version", @@ -25,7 +25,7 @@ var Command = &cli.Command{ func update(c *cli.Context) error { log.Info().Msg("Checking for updates ...") - newVersion, err := CheckForUpdate(c, c.Bool("force")) + newVersion, err := CheckForUpdate(c.Context, c.Bool("force")) if err != nil { return err } From 139b33db4ce77e20e04a36ca8991cb6ceb5c90e0 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:25:23 +0100 Subject: [PATCH 07/14] improve update check --- cli/common/before.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cli/common/before.go b/cli/common/before.go index 77b208693e..2053ada318 100644 --- a/cli/common/before.go +++ b/cli/common/before.go @@ -1,13 +1,20 @@ package common import ( + "context" + "errors" + "time" + "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" "go.woodpecker-ci.org/woodpecker/v2/cli/update" ) -var waitForUpdateCheck chan struct{} +var ( + waitForUpdateCheck context.Context + cancelWaitForUpdate context.CancelCauseFunc +) func Before(c *cli.Context) error { if err := SetupGlobalLogger(c); err != nil { @@ -15,11 +22,12 @@ func Before(c *cli.Context) error { } go func() { - waitForUpdateCheck = make(chan struct{}) + waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background()) + defer cancelWaitForUpdate(errors.New("update check finished")) log.Debug().Msg("Checking for updates ...") - newVersion, err := update.CheckForUpdate(c.Context, false) + newVersion, err := update.CheckForUpdate(waitForUpdateCheck, true) if err != nil { log.Error().Err(err).Msgf("Failed to check for updates") return @@ -30,8 +38,6 @@ func Before(c *cli.Context) error { } else { log.Debug().Msgf("No update required") } - - close(waitForUpdateCheck) }() return nil @@ -39,7 +45,13 @@ func Before(c *cli.Context) error { func After(_ *cli.Context) error { if waitForUpdateCheck != nil { - <-waitForUpdateCheck + select { + case <-waitForUpdateCheck.Done(): + // When the actual command already finished, we still wait 250ms for the update check to finish + case <-time.After(time.Millisecond * 250): + log.Debug().Msg("Update check stopped due to timeout") + cancelWaitForUpdate(errors.New("update check timeout")) + } } return nil From 58ce31f7c30b4e88393351473f86229f57e19877 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:53:46 +0100 Subject: [PATCH 08/14] finish update --- cli/update/command.go | 14 +++++--------- cli/update/tar.go | 14 -------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/cli/update/command.go b/cli/update/command.go index dc5453f8ce..96237493b3 100644 --- a/cli/update/command.go +++ b/cli/update/command.go @@ -3,7 +3,6 @@ package update import ( "fmt" "os" - "path" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" @@ -52,19 +51,16 @@ func update(c *cli.Context) error { log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile) - pwd, err := os.Getwd() + executablePath, err := os.Executable() if err != nil { return err } - dst := path.Join(pwd, path.Base(c.App.Name)) - log.Debug().Msgf("Moving %s to %s", binFile, dst) - - // if err := os.Rename(binFile, dst); err != nil { - // return err - // } + if err := os.Rename(binFile, executablePath); err != nil { + return err + } - log.Info().Msgf("woodpecker-cli %s has been installed successfully! Please restart the CLI.", newVersion.Version) + log.Info().Msgf("woodpecker-cli %s has been updated successfully! Please restart the CLI.", newVersion.Version) return nil } diff --git a/cli/update/tar.go b/cli/update/tar.go index 874e40cea3..1def765139 100644 --- a/cli/update/tar.go +++ b/cli/update/tar.go @@ -21,29 +21,19 @@ func Untar(dst string, r io.Reader) error { header, err := tr.Next() switch { - // if no more files are found return case err == io.EOF: return nil - // return any other error case err != nil: return err - // if the header is nil, just skip it (not sure how this happens) case header == nil: continue } - // the target location where the dir/file should be created target := filepath.Join(dst, header.Name) - // the following switch could also be done using fi.Mode(), not sure if there - // a benefit of using one vs. the other. - // fi := header.FileInfo() - - // check the file type switch header.Typeflag { - // if its a dir and it doesn't exist create it case tar.TypeDir: if _, err := os.Stat(target); err != nil { if err := os.MkdirAll(target, 0x755); err != nil { @@ -51,20 +41,16 @@ func Untar(dst string, r io.Reader) error { } } - // if it's a file create it case tar.TypeReg: f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return err } - // copy over contents if _, err := io.Copy(f, tr); err != nil { return err } - // manually close here after each file operation; defering would cause each file close - // to wait until all operations have completed. f.Close() } } From ed79d9e281fdb7e9a439855345866c43eb926345 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:55:37 +0100 Subject: [PATCH 09/14] skip update-check when already updating --- cli/common/before.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cli/common/before.go b/cli/common/before.go index 2053ada318..25c772627a 100644 --- a/cli/common/before.go +++ b/cli/common/before.go @@ -22,6 +22,11 @@ func Before(c *cli.Context) error { } go func() { + // Don't check for updates when the update command is executed + if firstArg := c.Args().First(); firstArg == "update" { + return + } + waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background()) defer cancelWaitForUpdate(errors.New("update check finished")) From 3cbb44cc5a3d71934fa0b6db2e3d2a88d1cc6cbc Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Mon, 19 Feb 2024 08:48:36 +0100 Subject: [PATCH 10/14] use string comparison --- cli/update/updater.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cli/update/updater.go b/cli/update/updater.go index 006e9170ee..dce5b5d1cf 100644 --- a/cli/update/updater.go +++ b/cli/update/updater.go @@ -9,7 +9,6 @@ import ( "net/http" "os" "path" - "regexp" "runtime" "github.com/rs/zerolog/log" @@ -52,14 +51,10 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) { log.Debug().Msgf("Latest version: %s", release.TagName) - fileRegex, err := regexp.Compile(fmt.Sprintf("^woodpecker\\-cli\\_%s_%s\\.tar\\.gz$", runtime.GOOS, runtime.GOARCH)) - if err != nil { - return nil, err - } - assetURL := "" + fileName := fmt.Sprintf("woodpecker-cli_%s_%s.tar.gz", runtime.GOOS, runtime.GOARCH) for _, asset := range release.Assets { - if fileRegex.MatchString(asset.Name) { + if fileName == asset.Name { assetURL = asset.BrowserDownloadURL log.Debug().Msgf("Found asset for the current OS and arch: %s", assetURL) break From 2011f6b52d21180bb6539107bc89e59e38ac2303 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Mon, 19 Feb 2024 08:55:51 +0100 Subject: [PATCH 11/14] apply review comments --- cli/common/flags.go | 5 +++++ cli/common/{before.go => hooks.go} | 4 ++++ cmd/cli/app.go | 1 - 3 files changed, 9 insertions(+), 1 deletion(-) rename cli/common/{before.go => hooks.go} (96%) diff --git a/cli/common/flags.go b/cli/common/flags.go index 38d4817a30..39640aad6f 100644 --- a/cli/common/flags.go +++ b/cli/common/flags.go @@ -33,6 +33,11 @@ var GlobalFlags = append([]cli.Flag{ Aliases: []string{"s"}, Usage: "server address", }, + &cli.BoolFlag{ + EnvVars: []string{"DISABLE_UPDATE_CHECK"}, + Name: "disable-update-check", + Usage: "disable update check", + }, &cli.BoolFlag{ EnvVars: []string{"WOODPECKER_SKIP_VERIFY"}, Name: "skip-verify", diff --git a/cli/common/before.go b/cli/common/hooks.go similarity index 96% rename from cli/common/before.go rename to cli/common/hooks.go index 25c772627a..82a8f9d200 100644 --- a/cli/common/before.go +++ b/cli/common/hooks.go @@ -22,6 +22,10 @@ func Before(c *cli.Context) error { } go func() { + if c.Bool("disable-update-check") { + return + } + // Don't check for updates when the update command is executed if firstArg := c.Args().First(); firstArg == "update" { return diff --git a/cmd/cli/app.go b/cmd/cli/app.go index b13ad3f405..92ba17237b 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -40,7 +40,6 @@ func newApp() *cli.App { app.Name = "woodpecker-cli" app.Description = "Woodpecker command line utility" app.Version = version.String() - app.Usage = "woodpecker [global options] command [command options] [arguments...]" app.EnableBashCompletion = true app.Flags = common.GlobalFlags app.Before = common.Before From eeed65abd5e47cdb6d2cb4032c0f50f5a7033616 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:00:00 +0100 Subject: [PATCH 12/14] handle symlinks --- cli/update/command.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/update/command.go b/cli/update/command.go index 96237493b3..a255ea9240 100644 --- a/cli/update/command.go +++ b/cli/update/command.go @@ -3,6 +3,7 @@ package update import ( "fmt" "os" + "path/filepath" "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" @@ -51,7 +52,12 @@ func update(c *cli.Context) error { log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile) - executablePath, err := os.Executable() + executablePathOrSymlink, err := os.Executable() + if err != nil { + return err + } + + executablePath, err := filepath.EvalSymlinks(executablePathOrSymlink) if err != nil { return err } From 6b7b020ee15e48b81cdcf510c98e82a7fc15cf88 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:02:09 +0100 Subject: [PATCH 13/14] adjust update successful msg --- cli/update/command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/update/command.go b/cli/update/command.go index a255ea9240..786a5c62b2 100644 --- a/cli/update/command.go +++ b/cli/update/command.go @@ -66,7 +66,7 @@ func update(c *cli.Context) error { return err } - log.Info().Msgf("woodpecker-cli %s has been updated successfully! Please restart the CLI.", newVersion.Version) + log.Info().Msgf("woodpecker-cli has been updated to version %s successfully!", newVersion.Version) return nil } From 8b9bcdb7f160a5234a4fee299e66f93cc151a0b4 Mon Sep 17 00:00:00 2001 From: Anbraten <6918444+anbraten@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:04:17 +0100 Subject: [PATCH 14/14] use const for directory mode --- cli/update/tar.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/update/tar.go b/cli/update/tar.go index 1def765139..16fe2d0a23 100644 --- a/cli/update/tar.go +++ b/cli/update/tar.go @@ -4,10 +4,13 @@ import ( "archive/tar" "compress/gzip" "io" + "io/fs" "os" "path/filepath" ) +const tarDirectoryMode fs.FileMode = 0x755 + func Untar(dst string, r io.Reader) error { gzr, err := gzip.NewReader(r) if err != nil { @@ -36,7 +39,7 @@ func Untar(dst string, r io.Reader) error { switch header.Typeflag { case tar.TypeDir: if _, err := os.Stat(target); err != nil { - if err := os.MkdirAll(target, 0x755); err != nil { + if err := os.MkdirAll(target, tarDirectoryMode); err != nil { return err } }