Skip to content
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

Clean up stale krew installations on windows #314

Merged
merged 3 commits into from
Aug 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions cmd/krew/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/environment"
"sigs.k8s.io/krew/pkg/gitutil"
"sigs.k8s.io/krew/pkg/installation"
"sigs.k8s.io/krew/pkg/installation/receipt"
"sigs.k8s.io/krew/pkg/receiptsmigration"
)

Expand All @@ -40,19 +43,9 @@ var rootCmd = &cobra.Command{
Short: "krew is the kubectl plugin manager",
Long: `krew is the kubectl plugin manager.
You can invoke krew through kubectl: "kubectl krew [command]..."`,
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
isMigrated, err := receiptsmigration.Done(paths)
if err != nil {
return err
}
if isMigrated || cmd.Use == "receipts-upgrade" {
return nil
}
fmt.Fprintln(os.Stderr, "You need to perform a migration to continue using krew.\nPlease run `kubectl krew system receipts-upgrade`")
return fmt.Errorf("krew home outdated")
},
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: preRun,
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down Expand Up @@ -87,6 +80,42 @@ func init() {
}
}

func preRun(cmd *cobra.Command, _ []string) error {
// detect if receipts migration (v0.2.x->v0.3.x) is complete
isMigrated, err := receiptsmigration.Done(paths)
if err != nil {
return err
}
if !isMigrated && cmd.Use != "receipts-upgrade" {
fmt.Fprintln(os.Stderr, "You need to perform a migration to continue using krew.\nPlease run `kubectl krew system receipts-upgrade`")
return fmt.Errorf("krew home outdated")
}

if installation.IsWindows() {
glog.V(4).Infof("detected windows, will check for old krew installations to clean up")
err := cleanupStaleKrewInstallations()
if err != nil {
glog.Warningf("Failed to clean up old installations of krew (on windows).")
glog.Warningf("You may need to clean them up manually. Error: %v", err)
}
}
return nil
}

func cleanupStaleKrewInstallations() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad we can't cover this with integration tests, because it only targets windows where we don't run tests...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are able to emulate windows very easily by doing KREW_OS=windows here.
I just didn't think it was worth adding integration tests, because the upgrade path is fairly complex.

r, err := receipt.Load(paths.PluginInstallReceiptPath(constants.KrewPluginName))
if os.IsNotExist(err) {
glog.V(1).Infof("could not find krew's own plugin receipt, skipping cleanup of stale krew installations")
return nil
} else if err != nil {
return errors.Wrap(err, "cannot load krew's own plugin receipt")
}
v := r.Spec.Version

glog.V(1).Infof("Clean up krew stale installations, current=%s", v)
return installation.CleanupStaleKrewInstallations(paths.PluginInstallPath(constants.KrewPluginName), v)
}

func checkIndex(_ *cobra.Command, _ []string) error {
if ok, err := gitutil.IsGitCloned(paths.IndexPath()); err != nil {
return errors.Wrap(err, "failed to check local index git repository")
Expand Down
18 changes: 0 additions & 18 deletions cmd/krew/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@
package cmd

import (
"fmt"
"os"

"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/environment"
"sigs.k8s.io/krew/pkg/version"
)

Expand All @@ -34,8 +30,6 @@ var versionCmd = &cobra.Command{
Long: `Show version information and diagnostics about krew itself.

Remarks:
- IsPlugin is true if krew is executed as a plugin
- ExecutedVersion is the version of the currently executed binary. This is detected through the path.
- GitTag describes the release name krew is built from.
- GitCommit describes the git revision ID which krew is built from.
- IndexURI is the URI where the index is updated from.
Expand All @@ -45,19 +39,7 @@ Remarks:
- DownloadPath is the directory for temporarily downloading plugins.
- BinPath is the directory for the symbolic links to the installed plugin executables.`,
RunE: func(cmd *cobra.Command, args []string) error {
selfPath, err := os.Executable()
if err != nil {
glog.Fatalf("failed to get the own executable path")
}

executedVersion, runningAsPlugin, err := environment.GetExecutedVersion(paths.InstallPath(), selfPath, environment.Realpath)
if err != nil {
return errors.Wrap(err, "failed to find current krew version")
}

conf := [][]string{
{"IsPlugin", fmt.Sprintf("%v", runningAsPlugin)},
{"ExecutedVersion", executedVersion},
{"GitTag", version.GitTag()},
{"GitCommit", version.GitCommit()},
{"IndexURI", constants.IndexURI},
Expand Down
2 changes: 0 additions & 2 deletions integration_test/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ func TestKrewVersion(t *testing.T) {
output := test.Krew("version").RunOrFailOutput()

requiredSubstrings := []string{
"IsPlugin",
fmt.Sprintf(`BasePath\s+%s`, test.Root()),
"ExecutedVersion",
"GitTag",
"GitCommit",
`IndexURI\s+https://github.com/kubernetes-sigs/krew-index.git`,
Expand Down
1 change: 1 addition & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
CurrentAPIVersion = "krew.googlecontainertools.github.com/v1alpha2"
PluginKind = "Plugin"
ManifestExtension = ".yaml"
KrewPluginName = "krew" // plugin name of krew itself

// IndexURI points to the upstream index.
IndexURI = "https://github.com/kubernetes-sigs/krew-index.git"
Expand Down
27 changes: 0 additions & 27 deletions pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"k8s.io/client-go/util/homedir"

"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/pathutil"
)

// Paths contains all important environment paths
Expand Down Expand Up @@ -107,32 +106,6 @@ func (p Paths) PluginVersionInstallPath(plugin, version string) string {
return filepath.Join(p.InstallPath(), plugin, version)
}

// GetExecutedVersion returns the currently executed version. If krew is
// not executed as an plugin it will return a nil error and an empty string.
func GetExecutedVersion(installPath string, executionPath string, pathResolver func(string) (string, error)) (string, bool, error) {
path, err := pathResolver(executionPath)
if err != nil {
return "", false, errors.Wrap(err, "failed to resolve path")
}

currentBinaryPath, err := filepath.Abs(path)
if err != nil {
return "", false, err
}

pluginsPath, err := filepath.Abs(filepath.Join(installPath, "krew"))
if err != nil {
return "", false, err
}

elems, ok := pathutil.IsSubPath(pluginsPath, currentBinaryPath)
if !ok || len(elems) < 2 {
return "", false, nil
}

return elems[0], true, nil
}

// Realpath evaluates symbolic links. If the path is not a symbolic link, it
// returns the cleaned path. Symbolic links with relative paths return error.
func Realpath(path string) (string, error) {
Expand Down
73 changes: 0 additions & 73 deletions pkg/environment/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,79 +80,6 @@ func TestPaths(t *testing.T) {
}
}

func TestGetExecutedVersion(t *testing.T) {
type args struct {
paths Paths
executionPath string
}
tests := []struct {
name string
args args
want string
inPath bool
wantErr bool
}{
{
name: "is in krew path",
args: args{
paths: newPaths(filepath.FromSlash("/plugins")),
executionPath: filepath.FromSlash("/plugins/store/krew/deadbeef/krew.exe"),
},
want: "deadbeef",
inPath: true,
wantErr: false,
},
{
name: "is not in krew path",
args: args{
paths: newPaths(filepath.FromSlash("/plugins")),
executionPath: filepath.FromSlash("/plugins/store/NOTKREW/deadbeef/krew.exe"),
},
want: "",
inPath: false,
wantErr: false,
},
{
name: "is in longer krew path",
args: args{
paths: newPaths(filepath.FromSlash("/plugins")),
executionPath: filepath.FromSlash("/plugins/store/krew/deadbeef/foo/krew.exe"),
},
want: "deadbeef",
inPath: true,
wantErr: false,
},
{
name: "is in smaller krew path",
args: args{
paths: newPaths(filepath.FromSlash("/plugins")),
executionPath: filepath.FromSlash("/krew.exe"),
},
want: "",
inPath: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Logf("installpath=%s", tt.args.paths.InstallPath())
got, isVersion, err := GetExecutedVersion(tt.args.paths.InstallPath(), tt.args.executionPath, func(s string) (string, error) {
return s, nil
})
if (err != nil) != tt.wantErr {
t.Errorf("GetExecutedVersion() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("GetExecutedVersion() got = %v, want %v", got, tt.want)
}
if isVersion != tt.inPath {
t.Errorf("GetExecutedVersion() isVersion = %v, want %v", isVersion, tt.inPath)
}
})
}
}

func TestRealpath(t *testing.T) {
tmpDir, cleanup := testutil.NewTempDir(t)
defer cleanup()
Expand Down
38 changes: 29 additions & 9 deletions pkg/installation/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package installation

import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
Expand All @@ -23,6 +24,7 @@ import (
"github.com/golang/glog"
"github.com/pkg/errors"

"sigs.k8s.io/krew/pkg/constants"
"sigs.k8s.io/krew/pkg/download"
"sigs.k8s.io/krew/pkg/environment"
"sigs.k8s.io/krew/pkg/index"
Expand All @@ -44,10 +46,6 @@ type installOperation struct {
binDir string
}

const (
krewPluginName = "krew"
)

// Plugin lifecycle errors
var (
ErrIsAlreadyInstalled = errors.New("can't install, the newest version is already installed")
Expand Down Expand Up @@ -151,9 +149,9 @@ func downloadAndExtract(extractDir, uri, sha256sum, overrideFile string) error {

// Uninstall will uninstall a plugin.
func Uninstall(p environment.Paths, name string) error {
if name == krewPluginName {
if name == constants.KrewPluginName {
glog.Errorf("Removing krew through krew is not supported.")
if !isWindows() { // assume POSIX-like
if !IsWindows() { // assume POSIX-like
glog.Errorf("If you’d like to uninstall krew altogether, run:\n\trm -rf -- %q", p.BasePath())
}
return errors.New("self-uninstall not allowed")
Expand All @@ -169,7 +167,7 @@ func Uninstall(p environment.Paths, name string) error {

glog.V(1).Infof("Deleting plugin %s", name)

symlinkPath := filepath.Join(p.BinPath(), pluginNameToBin(name, isWindows()))
symlinkPath := filepath.Join(p.BinPath(), pluginNameToBin(name, IsWindows()))
glog.V(3).Infof("Unlink %q", symlinkPath)
if err := removeLink(symlinkPath); err != nil {
return errors.Wrap(err, "could not uninstall symlink of plugin")
Expand All @@ -187,7 +185,7 @@ func Uninstall(p environment.Paths, name string) error {
}

func createOrUpdateLink(binDir string, binary string, plugin string) error {
dst := filepath.Join(binDir, pluginNameToBin(plugin, isWindows()))
dst := filepath.Join(binDir, pluginNameToBin(plugin, IsWindows()))

if err := removeLink(dst); err != nil {
return errors.Wrap(err, "failed to remove old symlink")
Expand Down Expand Up @@ -226,7 +224,8 @@ func removeLink(path string) error {
return nil
}

func isWindows() bool {
// IsWindows sees if KREW_OS or runtime.GOOS to find out if current execution mode is win32.
func IsWindows() bool {
goos := runtime.GOOS
if env := os.Getenv("KREW_OS"); env != "" {
goos = env
Expand All @@ -244,3 +243,24 @@ func pluginNameToBin(name string, isWindows bool) string {
}
return name
}

// CleanupStaleKrewInstallations removes the versions that aren't the current version.
func CleanupStaleKrewInstallations(dir string, currentVersion string) error {
ls, err := ioutil.ReadDir(dir)
if err != nil {
return errors.Wrap(err, "failed to read krew store directory")
}
glog.V(2).Infof("Found %d entries in krew store directory", len(ls))
for _, d := range ls {
glog.V(2).Infof("Found a krew installation: %s (%s)", d.Name(), d.Mode())
if d.IsDir() && d.Name() != currentVersion {
glog.V(1).Infof("Deleting stale krew install directory: %s", d.Name())
p := filepath.Join(dir, d.Name())
if err := os.RemoveAll(p); err != nil {
return errors.Wrapf(err, "failed to remove stale krew version at path '%s'", p)
}
glog.V(1).Infof("Stale installation directory removed")
}
}
return nil
}
Loading