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

feat(cmds): add version check command #8839

Closed
wants to merge 1 commit into from
Closed
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
149 changes: 144 additions & 5 deletions core/commands/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import (
"fmt"
"io"
"runtime/debug"
"strings"

version "github.com/ipfs/kubo"
"github.com/ipfs/kubo/core/commands/cmdenv"

cmds "github.com/ipfs/go-ipfs-cmds"

versioncmp "github.com/hashicorp/go-version"
"github.com/libp2p/go-libp2p-kad-dht/fullrt"
pstore "github.com/libp2p/go-libp2p/core/peerstore"
)

const (
versionNumberOptionName = "number"
versionCommitOptionName = "commit"
versionRepoOptionName = "repo"
versionAllOptionName = "all"
versionNumberOptionName = "number"
versionCommitOptionName = "commit"
versionRepoOptionName = "repo"
versionAllOptionName = "all"
versionCompareNewFractionOptionName = "--newer-fraction"
)

var VersionCmd = &cmds.Command{
Expand All @@ -24,7 +31,8 @@ var VersionCmd = &cmds.Command{
ShortDescription: "Returns the current version of IPFS and exits.",
},
Subcommands: map[string]*cmds.Command{
"deps": depsVersionCommand,
"deps": depsVersionCommand,
"check": checkVersionCommand,
},

Options: []cmds.Option{
Expand Down Expand Up @@ -130,3 +138,134 @@ Print out all dependencies and their versions.`,
}),
},
}

type CheckOutput struct {
PeersCounted int
GreatestVersion string
OldVersion bool
}

var checkVersionCommand = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Checks IPFS version against network (online only).",
ShortDescription: `
Checks node versions in our DHT to compare if we're running an older version.`,
},
Options: []cmds.Option{
cmds.FloatOption(versionCompareNewFractionOptionName, "f", "Fraction of peers with new version to generate update warning.").WithDefault(0.1),
},
Type: CheckOutput{},

Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
nd, err := cmdenv.GetNode(env)
if err != nil {
return err
}

if !nd.IsOnline {
return ErrNotOnline
}

if nd.DHT == nil {
return ErrNotDHT
}

ourVersion, err := versioncmp.NewVersion(strings.Replace(version.CurrentVersionNumber, "-dev", "", -1))
if err != nil {
return fmt.Errorf("could not parse our own version %s: %w",
version.CurrentVersionNumber, err)
}

greatestVersionSeen := ourVersion
totalPeersCounted := 1 // Us (and to avoid division-by-zero edge case).
withGreaterVersion := 0

recordPeerVersion := func(agentVersion string) {
// We process the version as is it assembled in GetUserAgentVersion.
segments := strings.Split(agentVersion, "/")
if len(segments) < 2 {
return
}
if segments[0] != "kubo" {
return
}
versionNumber := segments[1] // As in our CurrentVersionNumber.

// Ignore development releases.
if strings.Contains(versionNumber, "-dev") {
return
}
if strings.Contains(versionNumber, "-rc") {
return
}

peerVersion, err := versioncmp.NewVersion(versionNumber)
if err != nil {
// Do not error on invalid remote versions, just ignore.
return
}

// Valid peer version number.
totalPeersCounted += 1
if ourVersion.LessThan(peerVersion) {
withGreaterVersion += 1
}
if peerVersion.GreaterThan(greatestVersionSeen) {
greatestVersionSeen = peerVersion
}
}

// Logic taken from `ipfs stats dht` command.
if nd.DHTClient != nd.DHT {
client, ok := nd.DHTClient.(*fullrt.FullRT)
if !ok {
return cmds.Errorf(cmds.ErrClient, "could not generate stats for the WAN DHT client type")
}
for _, p := range client.Stat() {
if ver, err := nd.Peerstore.Get(p, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if err == pstore.ErrNotFound {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
} else {
for _, pi := range nd.DHT.WAN.RoutingTable().GetPeerInfos() {
if ver, err := nd.Peerstore.Get(pi.Id, "AgentVersion"); err == nil {
recordPeerVersion(ver.(string))
} else if err == pstore.ErrNotFound {
// ignore
} else {
// this is a bug, usually.
log.Errorw(
"failed to get agent version from peerstore",
"error", err,
)
}
}
}

newerFraction, _ := req.Options[versionCompareNewFractionOptionName].(float64)
if err := cmds.EmitOnce(res, CheckOutput{
PeersCounted: totalPeersCounted,
GreatestVersion: greatestVersionSeen.String(),
OldVersion: (float64(withGreaterVersion) / float64(totalPeersCounted)) > newerFraction,
}); err != nil {
return err
}
return nil
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, checkOutput CheckOutput) error {
if checkOutput.OldVersion {
fmt.Fprintf(w, "⚠️WARNING: this Kubo node is running an outdated version compared to other peers, update to %s\n", checkOutput.GreatestVersion)
}
return nil
}),
},
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/ipfs/boxo v0.10.2-0.20230629140307-fdad9f921191
github.com/ipfs/go-block-format v0.1.2
github.com/ipfs/go-cid v0.4.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
Expand Down