Skip to content

Commit 3b4e9e5

Browse files
committed
feat: add "outdated" command to list plugins with available updates
Adds "kubectl krew outdated" command that compares installed plugin versions against the local index and displays a table of plugins with newer versions available, without performing any upgrades. Features: - Shows PLUGIN, INSTALLED, AVAILABLE columns in terminal - Outputs only plugin names when piped (for scripting) - Skips detached (manifest-installed) plugins - Gracefully handles missing index entries and unparseable versions
1 parent c02e7ed commit 3b4e9e5

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

cmd/krew/cmd/outdated.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2019 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"sort"
21+
"strings"
22+
23+
"github.com/pkg/errors"
24+
"github.com/spf13/cobra"
25+
"k8s.io/klog/v2"
26+
27+
"sigs.k8s.io/krew/internal/index/indexscanner"
28+
"sigs.k8s.io/krew/internal/installation"
29+
"sigs.k8s.io/krew/internal/installation/semver"
30+
)
31+
32+
func init() {
33+
outdatedCmd := &cobra.Command{
34+
Use: "outdated",
35+
Short: "List installed plugins with newer versions available",
36+
Long: `List all installed kubectl plugins that have newer versions available
37+
in the local index. This command does not perform any upgrades.
38+
39+
Use "kubectl krew update" to refresh the index before checking for
40+
outdated plugins.
41+
42+
To upgrade all outdated plugins, use:
43+
kubectl krew upgrade`,
44+
RunE: func(_ *cobra.Command, _ []string) error {
45+
receipts, err := installation.GetInstalledPluginReceipts(paths.InstallReceiptsPath())
46+
if err != nil {
47+
return errors.Wrap(err, "failed to find installed plugins")
48+
}
49+
50+
var rows [][]string
51+
for _, r := range receipts {
52+
indexName := indexOf(r)
53+
pluginName := r.Name
54+
55+
// Skip plugins installed from a manifest (detached)
56+
if indexName == "detached" {
57+
klog.V(2).Infof("Skipping %q: installed via manifest", pluginName)
58+
continue
59+
}
60+
61+
// Load latest version from the index
62+
indexPlugin, err := indexscanner.LoadPluginByName(paths.IndexPluginsPath(indexName), pluginName)
63+
if err != nil {
64+
if os.IsNotExist(err) {
65+
klog.V(1).Infof("Skipping %q: plugin no longer exists in index %q", pluginName, indexName)
66+
continue
67+
}
68+
return errors.Wrapf(err, "failed to load index entry for plugin %q", pluginName)
69+
}
70+
71+
curVersion := r.Spec.Version
72+
newVersion := indexPlugin.Spec.Version
73+
74+
curv, err := semver.Parse(curVersion)
75+
if err != nil {
76+
klog.V(1).Infof("Skipping %q: cannot parse installed version %q", pluginName, curVersion)
77+
continue
78+
}
79+
newv, err := semver.Parse(newVersion)
80+
if err != nil {
81+
klog.V(1).Infof("Skipping %q: cannot parse index version %q", pluginName, newVersion)
82+
continue
83+
}
84+
85+
if semver.Less(curv, newv) {
86+
rows = append(rows, []string{displayName(indexPlugin, indexName), curVersion, newVersion})
87+
}
88+
}
89+
90+
if len(rows) == 0 {
91+
fmt.Fprintln(os.Stderr, "All plugins are up to date.")
92+
return nil
93+
}
94+
95+
// Sort by plugin name
96+
sort.Slice(rows, func(i, j int) bool {
97+
return rows[i][0] < rows[j][0]
98+
})
99+
100+
// Return only names when piped
101+
if !isTerminal(os.Stdout) {
102+
var names []string
103+
for _, row := range rows {
104+
names = append(names, row[0])
105+
}
106+
fmt.Fprintln(os.Stdout, strings.Join(names, "\n"))
107+
return nil
108+
}
109+
110+
return printTable(os.Stdout, []string{"PLUGIN", "INSTALLED", "AVAILABLE"}, rows)
111+
},
112+
PreRunE: checkIndex,
113+
}
114+
115+
rootCmd.AddCommand(outdatedCmd)
116+
}

0 commit comments

Comments
 (0)