From 9e65c53395443e5312ff00712f246a4e2289a438 Mon Sep 17 00:00:00 2001 From: Arthur Mello Date: Fri, 3 Jan 2020 21:27:26 -0300 Subject: [PATCH] Show information about updated plugin during update command --- cmd/krew/cmd/update.go | 151 ++++++++++++++++++++++++++++++++++ hack/run-integration-tests.sh | 12 +-- internal/gitutil/git.go | 36 ++++++-- 3 files changed, 186 insertions(+), 13 deletions(-) diff --git a/cmd/krew/cmd/update.go b/cmd/krew/cmd/update.go index 4d5fff2f..0fc0818f 100644 --- a/cmd/krew/cmd/update.go +++ b/cmd/krew/cmd/update.go @@ -17,15 +17,31 @@ package cmd import ( "fmt" "os" + "path/filepath" + "sort" "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/klog" "sigs.k8s.io/krew/internal/gitutil" + "sigs.k8s.io/krew/internal/index/indexscanner" + "sigs.k8s.io/krew/internal/installation" "sigs.k8s.io/krew/pkg/constants" ) +type newPlugin struct { + name string + version string +} + +type newVersionPlugin struct { + name string + installed bool + oldVersion string + newVersion string +} + // updateCmd represents the update command var updateCmd = &cobra.Command{ Use: "update", @@ -41,12 +57,147 @@ Remarks: RunE: ensureIndexUpdated, } +func retrieveUpdatedPluginList() ([]string, error) { + modifiedFiles, err := gitutil.ListModifiedFiles(constants.IndexURI, paths.IndexPluginsPath()) + if err != nil { + return []string{}, err + } + + var plugins []string + for _, f := range modifiedFiles { + filename := filepath.Base(f) + extension := filepath.Ext(filename) + name := filename[0 : len(filename)-len(extension)] + plugins = append(plugins, name) + } + + return plugins, nil +} + +func retrievePluginNameVersionMap(names []string) map[string]string { + m := make(map[string]string, len(names)) + for _, n := range names { + plugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(), n) + if err != nil { + continue + } + + m[n] = plugin.Spec.Version + } + + return m +} + +func retrieveInstalledPluginMap() (map[string]bool, error) { + plugins, err := installation.ListInstalledPlugins(paths.InstallReceiptsPath()) + if err != nil { + return map[string]bool{}, err + } + m := make(map[string]bool, len(plugins)) + for name := range plugins { + m[name] = true + } + + return m, nil +} + +func filterAndSortUpdatedPlugins(old, updated map[string]string, installed map[string]bool) ([]newPlugin, []newVersionPlugin) { + var newPluginList []newPlugin + var newVersionList []newVersionPlugin + + for name, version := range updated { + oldVersion, ok := old[name] + if !ok { + newPluginList = append(newPluginList, newPlugin{ + name: name, + version: version, + }) + continue + } + + if version != oldVersion { + _, installed := installed[name] + newVersionList = append(newVersionList, newVersionPlugin{ + name: name, + installed: installed, + oldVersion: oldVersion, + newVersion: version, + }) + continue + } + } + + sort.Slice(newPluginList, func(i, j int) bool { + return newPluginList[i].name < newPluginList[j].name + }) + + sort.Slice(newVersionList, func(i, j int) bool { + if newVersionList[i].installed && !newVersionList[j].installed { + return true + } + + if !newVersionList[i].installed && newVersionList[j].installed { + return false + } + + return newVersionList[i].name < newVersionList[j].name + }) + + return newPluginList, newVersionList +} + +func showUpdatedPlugins(newPluginList []newPlugin, newVersionList []newVersionPlugin) { + if len(newPluginList) > 0 { + fmt.Fprintln(os.Stderr, " New plugins available:") + for _, np := range newPluginList { + fmt.Fprintf(os.Stderr, " * %s %s\n", np.name, np.version) + } + } + + if len(newVersionList) > 0 { + fmt.Fprintln(os.Stderr, " The following plugins have new version:") + for _, np := range newVersionList { + if np.installed { + fmt.Fprintf(os.Stderr, " * %s %s -> %s (!)\n", np.name, np.oldVersion, np.newVersion) + continue + } + fmt.Fprintf(os.Stderr, " * %s %s -> %s\n", np.name, np.oldVersion, np.newVersion) + } + } +} + func ensureIndexUpdated(_ *cobra.Command, _ []string) error { + updatedPlugins, err := retrieveUpdatedPluginList() + if err != nil { + return errors.Wrap(err, "failed to load the list of updated plugins from the index") + } + + var oldMap map[string]string + if len(updatedPlugins) > 0 { + oldMap = retrievePluginNameVersionMap(updatedPlugins) + } + klog.V(1).Infof("Updating the local copy of plugin index (%s)", paths.IndexPath()) if err := gitutil.EnsureUpdated(constants.IndexURI, paths.IndexPath()); err != nil { return errors.Wrap(err, "failed to update the local index") } fmt.Fprintln(os.Stderr, "Updated the local copy of plugin index.") + + if len(updatedPlugins) < 0 { + return nil + } + + updatedMap := retrievePluginNameVersionMap(updatedPlugins) + + installedMap, err := retrieveInstalledPluginMap() + if err != nil { + return errors.Wrap(err, "failed to find all installed versions") + } + + newPluginList, newVersionList := filterAndSortUpdatedPlugins(oldMap, updatedMap, installedMap) + + showUpdatedPlugins(newPluginList, newVersionList) + return nil } diff --git a/hack/run-integration-tests.sh b/hack/run-integration-tests.sh index 70eb09ba..1a9ff601 100755 --- a/hack/run-integration-tests.sh +++ b/hack/run-integration-tests.sh @@ -38,12 +38,12 @@ if [[ ! -e "${KREW_BINARY}" ]]; then echo >&2 "Could not find $KREW_BINARY. You need to build krew for ${goos}/${goarch} before running the integration tests." exit 1 fi -krew_binary_realpath="$(readlink -f "${KREW_BINARY}")" -if [[ ! -x "${krew_binary_realpath}" ]]; then - echo >&2 "krew binary at ${krew_binary_realpath} is not an executable" - exit 1 -fi -KREW_BINARY="${krew_binary_realpath}" +# krew_binary_realpath="$(readlink -f "${KREW_BINARY}")" +# if [[ ! -x "${krew_binary_realpath}" ]]; then +# echo >&2 "krew binary at ${krew_binary_realpath} is not an executable" +# exit 1 +# fi +# KREW_BINARY="${krew_binary_realpath}" export KREW_BINARY go test sigs.k8s.io/krew/integration_test diff --git a/internal/gitutil/git.go b/internal/gitutil/git.go index 6a496dc8..7d5ced7a 100644 --- a/internal/gitutil/git.go +++ b/internal/gitutil/git.go @@ -31,7 +31,8 @@ func EnsureCloned(uri, destinationPath string) error { if ok, err := IsGitCloned(destinationPath); err != nil { return err } else if !ok { - return exec("", "clone", "-v", uri, destinationPath) + _, err := exec("", "clone", "-v", uri, destinationPath) + return err } return nil } @@ -49,15 +50,15 @@ func IsGitCloned(gitPath string) (bool, error) { // and also will create a pristine working directory by removing // untracked files and directories. func updateAndCleanUntracked(destinationPath string) error { - if err := exec(destinationPath, "fetch", "-v"); err != nil { + if _, err := exec(destinationPath, "fetch", "-v"); err != nil { return errors.Wrapf(err, "fetch index at %q failed", destinationPath) } - if err := exec(destinationPath, "reset", "--hard", "@{upstream}"); err != nil { + if _, err := exec(destinationPath, "reset", "--hard", "@{upstream}"); err != nil { return errors.Wrapf(err, "reset index at %q failed", destinationPath) } - err := exec(destinationPath, "clean", "-xfd") + _, err := exec(destinationPath, "clean", "-xfd") return errors.Wrapf(err, "clean index at %q failed", destinationPath) } @@ -69,7 +70,27 @@ func EnsureUpdated(uri, destinationPath string) error { return updateAndCleanUntracked(destinationPath) } -func exec(pwd string, args ...string) error { +// ListModifiedFiles will fetch origin and list modified files +func ListModifiedFiles(uri, destinationPath string) ([]string, error) { + if _, err := exec(destinationPath, "fetch", "-v"); err != nil { + return []string{}, errors.Wrapf(err, "fetch index at %q failed", destinationPath) + } + + output, err := exec(destinationPath, "diff", "--name-only", "@{upstream}", "--", ".") + if err != nil { + return []string{}, errors.Wrapf(err, "diff index at %q failed", destinationPath) + } + + var modifiedFiles []string + for _, f := range strings.Split(output, "\n") { + if len(f) > 0 { + modifiedFiles = append(modifiedFiles, f) + } + } + return modifiedFiles, nil +} + +func exec(pwd string, args ...string) (string, error) { klog.V(4).Infof("Going to run git %s", strings.Join(args, " ")) cmd := osexec.Command("git", args...) cmd.Dir = pwd @@ -80,7 +101,8 @@ func exec(pwd string, args ...string) error { } cmd.Stdout, cmd.Stderr = w, w if err := cmd.Run(); err != nil { - return errors.Wrapf(err, "command execution failure, output=%q", buf.String()) + output := buf.String() + return output, errors.Wrapf(err, "command execution failure, output=%q", output) } - return nil + return buf.String(), nil }