does not auto update for package managers (#1850)

This commit is contained in:
Noah Campbell
2025-09-18 12:56:12 -05:00
committed by GitHub
parent b1830ff515
commit 7bd7eca528
6 changed files with 227 additions and 2 deletions

View File

@@ -53,7 +53,7 @@ that a cluster meets the requirements to run an application.`,
_ = updater.CheckAndUpdate(cmd.Context(), updater.Options{
BinaryName: "preflight",
CurrentPath: exe,
Printf: func(f string, a ...interface{}) { klog.V(1).Infof(f, a...) },
Printf: func(f string, a ...interface{}) { fmt.Fprintf(os.Stderr, f, a...) },
})
}
}

View File

@@ -56,7 +56,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by
_ = updater.CheckAndUpdate(cmd.Context(), updater.Options{
BinaryName: "support-bundle",
CurrentPath: exe,
Printf: func(f string, a ...interface{}) { klog.V(1).Infof(f, a...) },
Printf: func(f string, a ...interface{}) { fmt.Fprintf(os.Stderr, f, a...) },
})
}
}

View File

@@ -0,0 +1,74 @@
package pkgmgr
import (
"encoding/json"
"fmt"
"os/exec"
)
// HomebrewPackageManager detects if a binary was installed via Homebrew
type HomebrewPackageManager struct{
formula string
}
var _ PackageManager = (*HomebrewPackageManager)(nil)
type homebrewInfoOutput struct {
Installed []struct {
Version string `json:"version"`
InstalledOn bool `json:"installed_on_request"`
LinkedKeg string `json:"linked_keg"`
} `json:"installed"`
}
// NewHomebrewPackageManager creates a new Homebrew package manager detector
func NewHomebrewPackageManager(formula string) PackageManager {
return &HomebrewPackageManager{
formula: formula,
}
}
// Name returns the human-readable name of the package manager
func (h *HomebrewPackageManager) Name() string {
return "Homebrew"
}
// IsInstalled checks if the formula is installed via Homebrew
func (h *HomebrewPackageManager) IsInstalled() (bool, error) {
// First check if brew command exists
brewPath, err := exec.LookPath("brew")
if err != nil {
// No brew command found, definitely not installed via brew
return false, nil
}
// Check if the formula is installed
out, err := exec.Command(brewPath, "info", h.formula, "--json").Output()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
if exitError.ExitCode() == 1 {
// brew info with an invalid (not installed) package name returns an error
return false, nil
}
}
return false, err
}
var info []homebrewInfoOutput
if err := json.Unmarshal(out, &info); err != nil {
return false, err
}
if len(info) == 0 {
return false, nil
}
// Check if the formula has any installed versions
return len(info[0].Installed) > 0, nil
}
// UpgradeCommand returns the command to upgrade the package
func (h *HomebrewPackageManager) UpgradeCommand() string {
return fmt.Sprintf("brew upgrade %s", h.formula)
}

View File

@@ -0,0 +1,69 @@
package pkgmgr
import (
"fmt"
"os/exec"
"strings"
)
// KrewPackageManager detects if a binary was installed via kubectl krew
type KrewPackageManager struct{
pluginName string
}
var _ PackageManager = (*KrewPackageManager)(nil)
// NewKrewPackageManager creates a new Krew package manager detector
func NewKrewPackageManager(pluginName string) PackageManager {
return &KrewPackageManager{
pluginName: pluginName,
}
}
// Name returns the human-readable name of the package manager
func (k *KrewPackageManager) Name() string {
return "kubectl krew"
}
// IsInstalled checks if the plugin is installed via krew
func (k *KrewPackageManager) IsInstalled() (bool, error) {
// First check if kubectl krew command exists
_, err := exec.LookPath("kubectl")
if err != nil {
return false, nil
}
// Check if krew plugin is available
out, err := exec.Command("kubectl", "krew", "version").Output()
if err != nil {
// krew not installed
return false, nil
}
if !strings.Contains(string(out), "krew") {
return false, nil
}
// Check if the plugin is installed by listing installed plugins
listOut, err := exec.Command("kubectl", "krew", "list").Output()
if err != nil {
return false, err
}
// Check if our plugin is in the installed list
installedPlugins := strings.Split(string(listOut), "\n")
for _, line := range installedPlugins {
// Lines are in format: "PLUGIN VERSION"
if strings.HasPrefix(strings.TrimSpace(line), k.pluginName+" ") || strings.TrimSpace(line) == k.pluginName {
return true, nil
}
}
return false, nil
}
// UpgradeCommand returns the command to upgrade the plugin
func (k *KrewPackageManager) UpgradeCommand() string {
return fmt.Sprintf("kubectl krew upgrade %s", k.pluginName)
}

View File

@@ -0,0 +1,11 @@
package pkgmgr
// PackageManager represents an external package manager that can manage the binary
type PackageManager interface {
// IsInstalled returns true if the package/formula is installed via this package manager
IsInstalled() (bool, error)
// UpgradeCommand returns the command the user should run to upgrade
UpgradeCommand() string
// Name returns the human-readable name of the package manager
Name() string
}

View File

@@ -17,6 +17,7 @@ import (
"time"
hv "github.com/hashicorp/go-version"
"github.com/replicatedhq/troubleshoot/pkg/updater/pkgmgr"
"github.com/replicatedhq/troubleshoot/pkg/version"
)
@@ -47,6 +48,8 @@ func (o *Options) client() *http.Client {
// CheckAndUpdate checks GitHub releases for a newer version and, if newer, downloads
// the corresponding tar.gz asset, extracts the binary, and atomically replaces CurrentPath.
// If the binary was installed via a package manager (brew, krew), it will display the
// appropriate upgrade command instead of performing the update.
func CheckAndUpdate(ctx context.Context, o Options) error {
if o.Skip {
return nil
@@ -80,6 +83,15 @@ func CheckAndUpdate(ctx context.Context, o Options) error {
return nil
}
// Check if installed via package manager - only show message if newer version exists
if pkgMgr := detectPackageManager(o.BinaryName); pkgMgr != nil {
if o.Printf != nil {
o.Printf("A newer version (%s) is available. Please run: %s\n",
latest, pkgMgr.UpgradeCommand())
}
return nil
}
if o.Printf != nil {
o.Printf("Updating %s from %s to %s...\n", o.BinaryName, current, latest)
}
@@ -259,3 +271,62 @@ func sanityCheckBinary(path string) error {
_ = hex.EncodeToString(h.Sum(nil))
return nil
}
// detectPackageManager checks if the binary was installed via a known package manager
func detectPackageManager(binaryName string) pkgmgr.PackageManager {
// Map binary names to their package manager formula/plugin names
formulaName := getHomebrewFormulaName(binaryName)
pluginName := getKrewPluginName(binaryName)
// List of package managers to check
packageManagers := []pkgmgr.PackageManager{
pkgmgr.NewHomebrewPackageManager(formulaName),
pkgmgr.NewKrewPackageManager(pluginName),
}
for _, pm := range packageManagers {
installed, err := pm.IsInstalled()
if err != nil {
// Continue checking other package managers if one fails
continue
}
if installed {
return pm
}
}
// No package manager detected
return nil
}
// getHomebrewFormulaName maps binary names to Homebrew formula names
func getHomebrewFormulaName(binaryName string) string {
formulaMap := map[string]string{
"preflight": "preflight",
"support-bundle": "support-bundle",
"troubleshoot": "troubleshoot",
}
if formula, exists := formulaMap[binaryName]; exists {
return formula
}
// Default to the binary name if not in the map
return binaryName
}
// getKrewPluginName maps binary names to krew plugin names
func getKrewPluginName(binaryName string) string {
pluginMap := map[string]string{
"preflight": "preflight",
"support-bundle": "support-bundle",
"troubleshoot": "troubleshoot",
}
if plugin, exists := pluginMap[binaryName]; exists {
return plugin
}
// Default to the binary name
return binaryName
}