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

Checking for updates #31

Closed
wants to merge 12 commits into from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# ipfs can generate profiling dump files
cpu.prof

*.swp
.ipfsconfig
*.out
Expand Down
7 changes: 7 additions & 0 deletions cmd/ipfs/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
config "github.com/jbenet/go-ipfs/config"
ci "github.com/jbenet/go-ipfs/crypto"
spipe "github.com/jbenet/go-ipfs/crypto/spipe"
updates "github.com/jbenet/go-ipfs/updates"
u "github.com/jbenet/go-ipfs/util"
)

Expand Down Expand Up @@ -135,6 +136,12 @@ func initCmd(c *commander.Command, inp []string) error {
},
}

// tracking ipfs version used to generate the init folder and adding update checker default setting.
cfg.Version = config.Version{
Check: "error",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe these values ("error", "ignore", etc) should be constants in the update package:

const CheckError = "error"
const CheckIgnore = "ignore"
...

Current: updates.Version,
}

err = config.WriteConfigFile(filename, cfg)
if err != nil {
return err
Expand Down
18 changes: 17 additions & 1 deletion cmd/ipfs/ipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
config "github.com/jbenet/go-ipfs/config"
core "github.com/jbenet/go-ipfs/core"
daemon "github.com/jbenet/go-ipfs/daemon"
updates "github.com/jbenet/go-ipfs/updates"
u "github.com/jbenet/go-ipfs/util"
)

Expand All @@ -24,6 +25,7 @@ var CmdIpfs = &commander.Command{

Basic commands:

init Initialize ipfs local configuration.
add <path> Add an object to ipfs.
cat <ref> Show ipfs object data.
ls <ref> List links from an object.
Expand Down Expand Up @@ -67,7 +69,8 @@ var log = u.Logger("cmd/ipfs")
func init() {
config, err := config.PathRoot()
if err != nil {
config = ""
u.POut("Failure initializing the default Config Directory: ", err)
os.Exit(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dborzov this should probably not fail if the -c flag is provided to a run, which we don't know about here. I'll leave this in for now, until the -c flag works throughout commands. (doesn't atm i think)

}
CmdIpfs.Flag.String("c", config, "specify config directory")
}
Expand Down Expand Up @@ -117,6 +120,19 @@ func localNode(confdir string, online bool) (*core.IpfsNode, error) {
return nil, err
}

if cfg.Version.EligibleForUpdateCheck() {
obsolete := updates.CheckForUpdates()
if obsolete != nil {
if cfg.Version.Check == config.CheckError {
return nil, obsolete
}
log.Warning(fmt.Sprintf("%v", obsolete)) // when "warn" version.check mode we just show warning message
} else {
// update most recent check timestamp in config
cfg.RecordCurrentUpdateCheck(filename)
}
}

return core.NewIpfsNode(cfg, online)
}

Expand Down
6 changes: 2 additions & 4 deletions cmd/ipfs/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package main

import (
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander"
updates "github.com/jbenet/go-ipfs/updates"
u "github.com/jbenet/go-ipfs/util"
)

// The IPFS version.
const Version = "0.1.0"

var cmdIpfsVersion = &commander.Command{
UsageLine: "version",
Short: "Show ipfs version information.",
Expand All @@ -27,6 +25,6 @@ func versionCmd(c *commander.Command, _ []string) error {
if !number {
u.POut("ipfs version ")
}
u.POut("%s\n", Version)
u.POut("%s\n", updates.Version)
return nil
}
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Config struct {
Identity Identity // local node's peer identity
Datastore Datastore // local node's storage
Addresses Addresses // local node's addresses
Version Version // local node's version management
Mounts Mounts // local node's mount points
Bootstrap []*BootstrapPeer // local nodes's bootstrap peers
}
Expand Down
39 changes: 39 additions & 0 deletions config/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package config

import "time"

// Version regulates checking if the most recent version is run
type Version struct {
Check string // "ignore" for do not check, "warn" and "error" for reacting when obsolete
Current string // ipfs version for which config was generated
UpdateCheckedTime time.Time // timestamp for the last time API endpoint was checked for updates
UpdateCheckPeriod time.Duration // time duration over which the update check will not be performed
}

// supported Version.Check values
const (
CheckError = "error" // value for Version.Check to raise error and exit if version is obsolete
CheckWarn = "warn" // value for Version.Check to show warning message if version is obsolete
CheckIgnore = "ignore" // value for Version.Check to not perform update check
)

var defaultUpdateCheckPeriod = time.Hour * 48

// EligibleForUpdateCheck returns if update check API endpoint is needed for this specific runtime
func (v *Version) EligibleForUpdateCheck() bool {
if v.Check == CheckIgnore || v.UpdateCheckedTime.Add(v.UpdateCheckPeriod).After(time.Now()) {
return false
}
return true
}

// RecordCurrentUpdateCheck is called to record that update check was performed and showed that the running version is the most recent one
func (cfg *Config) RecordCurrentUpdateCheck(filename string) {
cfg.Version.UpdateCheckedTime = time.Now()
if cfg.Version.UpdateCheckPeriod == time.Duration(0) {
// UpdateCheckPeriod was not initialized for some reason (e.g. config file used is broken)
cfg.Version.UpdateCheckPeriod = defaultUpdateCheckPeriod
}

WriteConfigFile(filename, cfg)
}
83 changes: 83 additions & 0 deletions updates/updates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package updates

import (
"encoding/json"
"fmt"
"net/http"
"os"

"github.com/coreos/go-semver/semver"
u "github.com/jbenet/go-ipfs/util"
)

const (
Version = "0.1.0" // actual current application's version literal
EndpointURLLatestReleases = "https://api.github.com/repos/jbenet/go-ipfs/tags"
VersionErrorShort = `Warning: You are running version %s of go-ipfs. The latest version is %s.`
VersionErrorLong = `
Warning: You are running version %s of go-ipfs. The latest version is %s.
Since this is alpha software, it is strongly recommended you update.

You can update go-ipfs by running

ipfs version update

You can silence this message by running

ipfs config update.check ignore

`
)

var log = u.Logger("updates")

var currentVersion *semver.Version

func init() {
var err error
currentVersion, err = semver.NewVersion(Version)
if err != nil {
u.PErr("The const Version literal in version.go needs to be in semver format: %s \n", Version)
os.Exit(1)
}
}

func CheckForUpdates() error {
resp, err := http.Get(EndpointURLLatestReleases)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to speedup commands for users, we could limit update checks to once per day (store the last time checked in the config or something). Otherwise ipfs will feel slow, having to wait for a full HTTP RTT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func CheckForUpdates() error {
  if !shouldCheck() {
    return nil
  }

  ...

  storeUpdateCheckTime()
  return nil
}

Or something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckForUpdates() is config ambguous at the moment. I added these flags to Config.Version and put in all that checking and latest run timestamp logic into respective Config methods.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, either doing it outside of this function or inside, either way should not do an http request every single time we run the tool, once / twice per day is enough. nevermind just saw changes + understood :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 yep, hadn't seen those yet, thanks!

if err != nil {
// can't reach the endpoint, coud be firewall, or no internet connection or something else
log.Error("update check: error connecting to API endpoint for newer versions: %v", err)
return nil
}
var body interface{}
_ = json.NewDecoder(resp.Body).Decode(&body)
releases, ok := body.([]interface{})
if !ok {
// the response body does not seem to meet specified Github API format
// https://developer.github.com/v3/repos/#list-tags
log.Error("update check: API endpoint for newer versions does not seem to be in Github API specified format")
return nil
}
for _, r := range releases {
release, ok := r.(map[string]interface{})
if !ok {
continue
}
tagName, ok := release["name"].(string)
if !ok {
continue
}
if len(tagName) > 0 && tagName[0] == 'v' {
// both 'v0.1.0' and '0.1.0' semver tagname conventions can be encountered
tagName = tagName[1:]
}
releaseVersion, err := semver.NewVersion(tagName)
if err != nil {
continue
}
if currentVersion.LessThan(*releaseVersion) {
return fmt.Errorf(VersionErrorLong, Version, tagName)
}
}
return nil
}