diff --git a/cmd/krew/cmd/update.go b/cmd/krew/cmd/update.go index 4d5fff2f..bf063e0d 100644 --- a/cmd/krew/cmd/update.go +++ b/cmd/krew/cmd/update.go @@ -17,15 +17,30 @@ package cmd import ( "fmt" "os" + "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 +56,124 @@ Remarks: RunE: ensureIndexUpdated, } +func retrievePluginNameVersionMap() (map[string]string, error) { + plugins, err := indexscanner.LoadPluginListFromFS(paths.IndexPluginsPath()) + if err != nil { + return map[string]string{}, err + } + + m := make(map[string]string, len(plugins)) + for _, p := range plugins { + m[p.Name] = p.Spec.Version + } + + return m, nil +} + +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 { + oldMap, err := retrievePluginNameVersionMap() + if err != nil { + return errors.Wrap(err, "failed to load the list of plugins from the index") + } + 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.") + + updatedMap, err := retrievePluginNameVersionMap() + if err != nil { + return errors.Wrap(err, "failed to load the list of plugins from the index") + } + + 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 }