-
-
Notifications
You must be signed in to change notification settings - Fork 380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cli updater #3382
Merged
Merged
Cli updater #3382
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
3c3ccc7
Add cli updater
anbraten 630d2f8
enable background update check
anbraten dff11cf
wait for updater
anbraten d3a5133
only check prod
anbraten 042c079
cleanup
anbraten 1db6f40
improve
anbraten 139b33d
improve update check
anbraten cd0d144
Merge branch 'main' into cli-updater
anbraten 58ce31f
finish update
anbraten ed79d9e
skip update-check when already updating
anbraten 54ce995
Merge branch 'cli-updater' of github.com:anbraten/woodpecker into cli…
anbraten a2e676a
Merge branch 'main' into cli-updater
anbraten 3cbb44c
use string comparison
anbraten 2011f6b
apply review comments
anbraten eeed65a
handle symlinks
anbraten 6b7b020
adjust update successful msg
anbraten 8b9bcdb
use const for directory mode
anbraten File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
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 context.Context | ||
cancelWaitForUpdate context.CancelCauseFunc | ||
) | ||
|
||
func Before(c *cli.Context) error { | ||
if err := SetupGlobalLogger(c); err != nil { | ||
return err | ||
} | ||
|
||
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 | ||
} | ||
|
||
waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background()) | ||
defer cancelWaitForUpdate(errors.New("update check finished")) | ||
|
||
log.Debug().Msg("Checking for updates ...") | ||
|
||
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, 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. Update by running: %s update", newVersion.Version, c.App.Name) | ||
} else { | ||
log.Debug().Msgf("No update required") | ||
} | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func After(_ *cli.Context) error { | ||
if waitForUpdateCheck != nil { | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package update | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/rs/zerolog/log" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
// Command exports the update 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.Context, 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) | ||
|
||
executablePathOrSymlink, err := os.Executable() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
executablePath, err := filepath.EvalSymlinks(executablePathOrSymlink) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := os.Rename(binFile, executablePath); err != nil { | ||
return err | ||
} | ||
|
||
log.Info().Msgf("woodpecker-cli has been updated to version %s successfully!", newVersion.Version) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package update | ||
|
||
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 { | ||
return err | ||
} | ||
defer gzr.Close() | ||
|
||
tr := tar.NewReader(gzr) | ||
|
||
for { | ||
header, err := tr.Next() | ||
|
||
switch { | ||
case err == io.EOF: | ||
return nil | ||
|
||
case err != nil: | ||
return err | ||
|
||
case header == nil: | ||
continue | ||
} | ||
|
||
target := filepath.Join(dst, header.Name) | ||
|
||
switch header.Typeflag { | ||
case tar.TypeDir: | ||
if _, err := os.Stat(target); err != nil { | ||
if err := os.MkdirAll(target, tarDirectoryMode); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
case tar.TypeReg: | ||
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := io.Copy(f, tr); err != nil { | ||
return err | ||
} | ||
|
||
f.Close() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package update | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"path" | ||
"runtime" | ||
|
||
"github.com/rs/zerolog/log" | ||
|
||
"go.woodpecker-ci.org/woodpecker/v2/version" | ||
) | ||
|
||
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(ctx, "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) | ||
|
||
assetURL := "" | ||
fileName := fmt.Sprintf("woodpecker-cli_%s_%s.tar.gz", runtime.GOOS, runtime.GOARCH) | ||
for _, asset := range release.Assets { | ||
if fileName == 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why without
WOODPECKER_
prefix?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed that too... @anbraten can you fix this?