Skip to content

Commit

Permalink
feat: support for release version info
Browse files Browse the repository at this point in the history
  • Loading branch information
scortier committed Sep 2, 2021
1 parent 4ff2e6b commit b58c11e
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 1 deletion.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ module github.com/odpf/salt
go 1.16

require (
github.com/gorilla/mux v1.8.0
github.com/hashicorp/go-version v1.3.0
github.com/jeremywohl/flatten v1.0.1
github.com/mcuadros/go-defaults v1.2.0
github.com/mitchellh/mapstructure v1.4.1
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.7.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
Expand All @@ -152,6 +154,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
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=
Expand Down
100 changes: 100 additions & 0 deletions version/release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package version

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"

"github.com/hashicorp/go-version"
"github.com/odpf/salt/log"
"github.com/pkg/errors"
)

var (
ReleaseInfoTimeout = time.Second * 1
Release = "https://api.github.com/repos/%s/releases/latest"
)

type Info struct {
Version string
TarURL string
}

// GithubInfo fetches details related to provided release URL
// releaseURL should point to a specific version
// for example: https://api.github.com/repos/odpf/optimus/releases/latest
func GithubInfo(releaseURL string) (*Info, error) {
httpClient := http.Client{
Timeout: ReleaseInfoTimeout,
}
req, err := http.NewRequest(http.MethodGet, releaseURL, nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to create request")
}
req.Header.Set("User-Agent", "odpf/salt")
resp, err := httpClient.Do(req)
if err != nil {
return nil, errors.Wrapf(err, "failed to reach releaseURL: %s", releaseURL)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Wrapf(err, "failed to reach releaseURL: %s, returned: %d", releaseURL, resp.StatusCode)
}
if resp.Body != nil {
defer resp.Body.Close()
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrapf(err, "failed to read response body")
}

var releaseBody struct {
TagName string `json:"tag_name"`
Tarball string `json:"tarball_url"`
}
if err = json.Unmarshal(body, &releaseBody); err != nil {
return nil, errors.Wrapf(err, "failed to parse: %s", string(body))
}

return &Info{
Version: releaseBody.TagName,
TarURL: releaseBody.Tarball,
}, nil
}

// IsCurrentLatest returns true if the current version string is greater than
// or equal to latestVersion as per semantic versioning
func IsCurrentLatest(currVersion, latestVersion string) (bool, error) {
currentV, err := version.NewVersion(currVersion)
if err != nil {
return false, errors.Wrapf(err, "failed to parse current version")
}
latestV, err := version.NewVersion(latestVersion)
if err != nil {
return false, errors.Wrapf(err, "failed to parse latest version")
}
if currentV.GreaterThanOrEqual(latestV) {
return true, nil
}
return false, nil
}

// NotifyIfNotLatest prints a notification if current application version
// is not latest
func NotifyIfNotLatest(logger log.Logger, currentVersion, githubRepo string) error {
info, err := GithubInfo(fmt.Sprintf(Release, githubRepo))
if err != nil {
return err
}

isLatest, err := IsCurrentLatest(currentVersion, info.Version)
if err != nil {
return nil
}
if isLatest {
logger.Info(fmt.Sprintf("a newer version is available: %s, consider updating the client", info.Version))
}
return nil
}
108 changes: 108 additions & 0 deletions version/release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package version_test

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/gorilla/mux"
"github.com/odpf/salt/version"
"github.com/stretchr/testify/assert"
)

func TestGithubInfo(t *testing.T) {
muxRouter := mux.NewRouter()
server := httptest.NewServer(muxRouter)

t.Run("should check for latest version availability by extracting correct version tag for valid json response on release URL", func(t *testing.T) {
muxRouter.HandleFunc("/latest", func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
TagName string `json:"tag_name"`
}{
TagName: "v0.0.2",
})
rw.Write(response)
})
info, err := version.GithubInfo("http://" + server.Listener.Addr().String() + "/latest")
assert.Nil(t, err)
assert.Equal(t, "v0.0.2", info.Version)
info, err = version.GithubInfo("http://" + server.Listener.Addr().String() + "/latest")
assert.Nil(t, err)
assert.NotEqual(t, "v0.0.1", info.Version)
})
}
func TestIsCurrentLatest(t *testing.T) {
muxRouter := mux.NewRouter()
server := httptest.NewServer(muxRouter)

t.Run("should return true for current version as the latest version ", func(t *testing.T) {
muxRouter.HandleFunc("/latest", func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
TagName string `json:"tag_name"`
}{
TagName: "v0.0.2",
})
rw.Write(response)
})
info, err := version.GithubInfo("http://" + server.Listener.Addr().String() + "/latest")
assert.Nil(t, err)
assert.Equal(t, "v0.0.2", info.Version)
res, err := version.IsCurrentLatest("v0.0.2", info.Version)
assert.Nil(t, err)
assert.True(t, res)
})
t.Run("should return false for current version not same as the latest version", func(t *testing.T) {
muxRouter.HandleFunc("/latest", func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
TagName string `json:"tag_name"`
}{
TagName: "v0.0.2",
})
rw.Write(response)
})
info, err := version.GithubInfo("http://" + server.Listener.Addr().String() + "/latest")
assert.Nil(t, err)
assert.Equal(t, "v0.0.2", info.Version)
res, err := version.IsCurrentLatest("v0.0.1", info.Version)
assert.Nil(t, err)
assert.False(t, res)
res, err = version.IsCurrentLatest("", info.Version)
assert.NotNil(t, err)
assert.False(t, res)
res, err = version.IsCurrentLatest("v0.0.3", "")
assert.NotNil(t, err)
assert.False(t, res)
})
}

func TestNotifyIfNotLatest(t *testing.T) {
muxRouter := mux.NewRouter()
server := httptest.NewServer(muxRouter)
t.Run("basic check for notify latest version", func(t *testing.T) {
muxRouter.HandleFunc("/latest", func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
TagName string `json:"tag_name"`
}{
TagName: "v0.0.1",
})
rw.Write(response)
})
info, err := version.GithubInfo("http://" + server.Listener.Addr().String() + "/latest")
assert.Nil(t, err)
assert.Equal(t, "v0.0.1", info.Version)
res, err := version.IsCurrentLatest("v0.0.1", info.Version)
assert.Nil(t, err)
assert.True(t, res)
err = version.NotifyIfNotLatest(nil, "v0.0.1", "odpf/optimus")
if err != nil {
t.Errorf("Error in notifyIfNotLatest: %v", err)
}
err = version.NotifyIfNotLatest(nil, "", "odpf/optimus")
assert.Nil(t, err)
})
}

0 comments on commit b58c11e

Please sign in to comment.