runtime: make kata-check check for newer release

Update `kata-check` to see if there is a newer version available for
download. Useful for users installing static packages (without a package
manager).

Fixes: #734.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
James O. D. Hunt 2020-09-16 12:01:09 +01:00
parent 7e33e36f4a
commit 1a77f69e15
5 changed files with 998 additions and 3 deletions

View File

@ -43,7 +43,8 @@ include $(ARCH_FILE)
PROJECT_TYPE = kata
PROJECT_NAME = Kata Containers
PROJECT_TAG = kata-containers
PROJECT_URL = https://github.com/kata-containers
PROJECT_ORG = $(PROJECT_TAG)
PROJECT_URL = https://github.com/$(PROJECT_ORG)
PROJECT_BUG_URL = $(PROJECT_URL)/kata-containers/issues/new
# list of scripts to install
@ -628,6 +629,7 @@ $(GENERATED_FILES): %: %.in $(MAKEFILE_LIST) VERSION .git-commit
-e "s|@PKGRUNDIR@|$(PKGRUNDIR)|g" \
-e "s|@NETMONPATH@|$(NETMONPATH)|g" \
-e "s|@PROJECT_BUG_URL@|$(PROJECT_BUG_URL)|g" \
-e "s|@PROJECT_ORG@|$(PROJECT_ORG)|g" \
-e "s|@PROJECT_URL@|$(PROJECT_URL)|g" \
-e "s|@PROJECT_NAME@|$(PROJECT_NAME)|g" \
-e "s|@PROJECT_TAG@|$(PROJECT_TAG)|g" \

View File

@ -25,6 +25,9 @@ const projectPrefix = "@PROJECT_TYPE@"
// original URL for this project
const projectURL = "@PROJECT_URL@"
// Project URL's organisation name
const projectORG = "@PROJECT_ORG@"
const defaultRootDirectory = "@PKGRUNDIR@"
// commit is the git commit the runtime is compiled from.

View File

@ -71,6 +71,9 @@ const (
genericCPUFlagsTag = "flags" // nolint: varcheck, unused, deadcode
genericCPUVendorField = "vendor_id" // nolint: varcheck, unused, deadcode
genericCPUModelField = "model name" // nolint: varcheck, unused, deadcode
// If set, do not perform any network checks
noNetworkEnvVar = "KATA_CHECK_NO_NETWORK"
)
// variables rather than consts to allow tests to modify them
@ -307,14 +310,71 @@ var kataCheckCLICommand = cli.Command{
Usage: "tests if system can run " + project,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "verbose, v",
Usage: "display the list of checks performed",
Name: "check-version-only",
Usage: "Only compare the current and latest available versions (requires network, non-root only)",
},
cli.BoolFlag{
Name: "include-all-releases",
Usage: "Don't filter out pre-release release versions",
},
cli.BoolFlag{
Name: "no-network-checks, n",
Usage: "Do not run any checks using the network",
},
cli.BoolFlag{
Name: "only-list-releases",
Usage: "Only list newer available releases (non-root only)",
},
cli.BoolFlag{
Name: "strict, s",
Usage: "perform strict checking",
},
cli.BoolFlag{
Name: "verbose, v",
Usage: "display the list of checks performed",
},
},
Description: fmt.Sprintf(`tests if system can run %s and version is current.
ENVIRONMENT VARIABLES:
- %s: If set to any value, act as if "--no-network-checks" was specified.
EXAMPLES:
- Perform basic checks:
$ %s %s
- Local basic checks only:
$ %s %s --no-network-checks
- Perform further checks:
$ sudo %s %s
- Just check if a newer version is available:
$ %s %s --check-version-only
- List available releases (shows output in format "version;release-date;url"):
$ %s %s --only-list-releases
- List all available releases (includes pre-release versions):
$ %s %s --only-list-releases --include-all-releases
`,
project,
noNetworkEnvVar,
name, checkCmd,
name, checkCmd,
name, checkCmd,
name, checkCmd,
name, checkCmd,
name, checkCmd,
),
Action: func(context *cli.Context) error {
verbose := context.Bool("verbose")
@ -329,6 +389,28 @@ var kataCheckCLICommand = cli.Command{
span, _ := katautils.Trace(ctx, "kata-check")
defer span.Finish()
if context.Bool("no-network-checks") == false && os.Getenv(noNetworkEnvVar) == "" {
cmd := RelCmdCheck
if context.Bool("only-list-releases") {
cmd = RelCmdList
}
if os.Geteuid() == 0 {
kataLog.Warn("Not running network checks as super user")
} else {
err = HandleReleaseVersions(cmd, version, context.Bool("include-all-releases"))
if err != nil {
return err
}
}
}
if context.Bool("check-version-only") || context.Bool("only-list-releases") {
return nil
}
runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
if !ok {
return errors.New("kata-check: cannot determine runtime config")

410
src/runtime/cli/release.go Normal file
View File

@ -0,0 +1,410 @@
// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/blang/semver"
)
type ReleaseCmd int
type releaseDetails struct {
version semver.Version
date string
url string
filename string
}
const (
// A release URL is expected to be prefixed with this value
projectAPIURL = "https://api.github.com/repos/" + projectORG
releasesSuffix = "/releases"
downloadsSuffix = releasesSuffix + "/download"
// Kata 1.x
kata1xRepo = "runtime"
kataLegacyReleaseURL = projectAPIURL + "/" + kata1xRepo + releasesSuffix
kataLegacyDownloadURL = projectURL + "/" + kata1xRepo + downloadsSuffix
// Kata 2.x or newer
kata2xRepo = "kata-containers"
kataReleaseURL = projectAPIURL + "/" + kata2xRepo + releasesSuffix
kataDownloadURL = projectURL + "/" + kata2xRepo + downloadsSuffix
// Environment variable that can be used to override a release URL
ReleaseURLEnvVar = "KATA_RELEASE_URL"
RelCmdList ReleaseCmd = iota
RelCmdCheck ReleaseCmd = iota
msgNoReleases = "No releases available"
msgNoNewerRelease = "No newer release available"
errNoNetChecksAsRoot = "No network checks allowed running as super user"
)
func (c ReleaseCmd) Valid() bool {
switch c {
case RelCmdCheck, RelCmdList:
return true
default:
return false
}
}
func downloadURLIsValid(url string) error {
if url == "" {
return errors.New("URL cannot be blank")
}
if strings.HasPrefix(url, kataDownloadURL) ||
strings.HasPrefix(url, kataLegacyDownloadURL) {
return nil
}
return fmt.Errorf("Download URL %q is not valid", url)
}
func releaseURLIsValid(url string) error {
if url == "" {
return errors.New("URL cannot be blank")
}
if url == kataReleaseURL || url == kataLegacyReleaseURL {
return nil
}
return fmt.Errorf("Release URL %q is not valid", url)
}
func getReleaseURL(currentVersion semver.Version) (url string, err error) {
major := currentVersion.Major
if major == 0 {
return "", fmt.Errorf("invalid current version: %v", currentVersion)
} else if major == 1 {
url = kataLegacyReleaseURL
} else {
url = kataReleaseURL
}
if value := os.Getenv(ReleaseURLEnvVar); value != "" {
url = value
}
if err := releaseURLIsValid(url); err != nil {
return "", err
}
return url, nil
}
func ignoreRelease(release releaseDetails, includeAll bool) bool {
if includeAll {
return false
}
if len(release.version.Pre) > 0 {
// Pre-releases are ignored by default
return true
}
return false
}
// Returns a release version and release object from the specified map.
func makeRelease(release map[string]interface{}) (version string, details releaseDetails, err error) {
key := "tag_name"
version, ok := release[key].(string)
if ok != true {
return "", details, fmt.Errorf("failed to find key %s in release data", key)
}
if version == "" {
return "", details, fmt.Errorf("release version cannot be blank")
}
releaseSemver, err := semver.Make(version)
if err != nil {
return "", details, fmt.Errorf("release %q has invalid semver version: %v", version, err)
}
key = "assets"
assetsArray, ok := release[key].([]interface{})
if ok != true {
return "", details, fmt.Errorf("failed to find key %s in release version %q data", key, version)
}
if len(assetsArray) == 0 {
// GitHub auto-creates the source assets, but binaries have to
// be built and uploaded for a release.
return "", details, fmt.Errorf("no binary assets for release %q", version)
}
var createDate string
var filename string
var downloadURL string
assets := assetsArray[0]
key = "browser_download_url"
downloadURL, ok = assets.(map[string]interface{})[key].(string)
if ok != true {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if err := downloadURLIsValid(downloadURL); err != nil {
return "", details, err
}
key = "name"
filename, ok = assets.(map[string]interface{})[key].(string)
if ok != true {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if filename == "" {
return "", details, fmt.Errorf("Release %q asset missing filename", version)
}
key = "created_at"
createDate, ok = assets.(map[string]interface{})[key].(string)
if ok != true {
return "", details, fmt.Errorf("failed to find key %s in release version %q asset data", key, version)
}
if createDate == "" {
return "", details, fmt.Errorf("Release %q asset missing creation date", version)
}
details = releaseDetails{
version: releaseSemver,
date: createDate,
url: downloadURL,
filename: filename,
}
return version, details, nil
}
func readReleases(releasesArray []map[string]interface{}, includeAll bool) (versions []semver.Version,
releases map[string]releaseDetails) {
releases = make(map[string]releaseDetails)
for _, release := range releasesArray {
version, details, err := makeRelease(release)
// Don't error if makeRelease() fails to construct a release.
// There are many reasons a release may not be considered
// valid, so just ignore the invalid ones.
if err != nil {
kataLog.WithField("version", version).WithError(err).Debug("ignoring invalid release version")
continue
}
if ignoreRelease(details, includeAll) {
continue
}
versions = append(versions, details.version)
releases[version] = details
}
semver.Sort(versions)
return versions, releases
}
// Note: Assumes versions is sorted in ascending order
func findNewestRelease(currentVersion semver.Version, versions []semver.Version) (bool, semver.Version, error) {
var candidates []semver.Version
if len(versions) == 0 {
return false, semver.Version{}, errors.New("no versions available")
}
for _, version := range versions {
if currentVersion.GTE(version) {
// Ignore older releases (and the current one!)
continue
}
candidates = append(candidates, version)
}
count := len(candidates)
if count == 0 {
return false, semver.Version{}, nil
}
return true, candidates[count-1], nil
}
func getReleases(releaseURL string, includeAll bool) ([]semver.Version, map[string]releaseDetails, error) {
kataLog.WithField("url", releaseURL).Info("Looking for releases")
if os.Geteuid() == 0 {
return nil, nil, errors.New(errNoNetChecksAsRoot)
}
client := &http.Client{}
resp, err := client.Get(releaseURL)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
releasesArray := []map[string]interface{}{}
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, nil, fmt.Errorf("failed to read release details: %v", err)
}
if err := json.Unmarshal(bytes, &releasesArray); err != nil {
return nil, nil, fmt.Errorf("failed to unpack release details: %v", err)
}
versions, releases := readReleases(releasesArray, includeAll)
return versions, releases, nil
}
func getNewReleaseType(current semver.Version, latest semver.Version) (string, error) {
if current.GT(latest) {
return "", fmt.Errorf("current version %s newer than latest %s", current, latest)
}
if current.EQ(latest) {
return "", fmt.Errorf("current version %s and latest are same", current)
}
var desc string
if latest.Major > current.Major {
if len(latest.Pre) > 0 {
desc = "major pre-release"
} else {
desc = "major"
}
} else if latest.Minor > current.Minor {
if len(latest.Pre) > 0 {
desc = "minor pre-release"
} else {
desc = "minor"
}
} else if latest.Patch > current.Patch {
if len(latest.Pre) > 0 {
desc = "patch pre-release"
} else {
desc = "patch"
}
} else if latest.Patch == current.Patch && len(latest.Pre) > 0 {
desc = "pre-release"
} else {
return "", fmt.Errorf("BUG: unhandled scenario: current version: %s, latest version: %v", current, latest)
}
return desc, nil
}
func showLatestRelease(output *os.File, current semver.Version, details releaseDetails) error {
latest := details.version
desc, err := getNewReleaseType(current, latest)
if err != nil {
return err
}
fmt.Fprintf(output, "Newer %s release available: %s (url: %v, date: %v)\n",
desc,
details.version, details.url, details.date)
return nil
}
func listReleases(output *os.File, current semver.Version, versions []semver.Version, releases map[string]releaseDetails) error {
for _, version := range versions {
details, ok := releases[version.String()]
if !ok {
return fmt.Errorf("Release %v has no details", version)
}
fmt.Fprintf(output, "%s;%s;%s\n", version, details.date, details.url)
}
return nil
}
func HandleReleaseVersions(cmd ReleaseCmd, currentVersion string, includeAll bool) error {
if !cmd.Valid() {
return fmt.Errorf("invalid release command: %v", cmd)
}
output := os.Stdout
currentSemver, err := semver.Make(currentVersion)
if err != nil {
return fmt.Errorf("BUG: Current version of %s (%s) has invalid SemVer version: %v", name, currentVersion, err)
}
releaseURL, err := getReleaseURL(currentSemver)
if err != nil {
return err
}
versions, releases, err := getReleases(releaseURL, includeAll)
if err != nil {
return err
}
if cmd == RelCmdList {
return listReleases(output, currentSemver, versions, releases)
}
if len(versions) == 0 {
fmt.Fprintf(output, "%s\n", msgNoReleases)
return nil
}
available, newest, err := findNewestRelease(currentSemver, versions)
if err != nil {
return err
}
if !available {
fmt.Fprintf(output, "%s\n", msgNoNewerRelease)
return nil
}
details, ok := releases[newest.String()]
if !ok {
return fmt.Errorf("Release %v has no details", newest)
}
if err != nil {
return err
}
return showLatestRelease(output, currentSemver, details)
}

View File

@ -0,0 +1,498 @@
// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"os"
"strings"
"testing"
"github.com/blang/semver"
"github.com/stretchr/testify/assert"
)
var currentSemver semver.Version
var expectedReleasesURL string
func init() {
var err error
currentSemver, err = semver.Make(version)
if err != nil {
panic(fmt.Sprintf("failed to create semver for testing: %v", err))
}
if currentSemver.Major == 1 {
expectedReleasesURL = kataLegacyReleaseURL
} else {
expectedReleasesURL = kataReleaseURL
}
}
func TestReleaseCmd(t *testing.T) {
assert := assert.New(t)
for i, value := range []ReleaseCmd{RelCmdCheck, RelCmdList} {
assert.True(value.Valid(), "test[%d]: %+v", i, value)
}
for i, value := range []int{-1, 2, 42, 255} {
invalid := ReleaseCmd(i)
assert.False(invalid.Valid(), "test[%d]: %+v", i, value)
}
}
func TestGetReleaseURL(t *testing.T) {
assert := assert.New(t)
const kata1xURL = "https://api.github.com/repos/kata-containers/runtime/releases"
const kata2xURL = "https://api.github.com/repos/kata-containers/kata-containers/releases"
type testData struct {
currentVersion string
expectError bool
expectedURL string
}
data := []testData{
{"0.0.0", true, ""},
{"1.0.0", false, kata1xURL},
{"1.9999.9999", false, kata1xURL},
{"2.0.0-alpha3", false, kata2xURL},
{"2.9999.9999", false, kata2xURL},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
ver, err := semver.Make(d.currentVersion)
msg = fmt.Sprintf("%s, version: %v, error: %v", msg, ver, err)
assert.NoError(err, msg)
url, err := getReleaseURL(ver)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
assert.Equal(url, d.expectedURL, msg)
assert.True(strings.HasPrefix(url, projectAPIURL), msg)
}
}
url, err := getReleaseURL(currentSemver)
assert.NoError(err)
assert.Equal(url, expectedReleasesURL)
assert.True(strings.HasPrefix(url, projectAPIURL))
}
func TestGetReleaseURLEnvVar(t *testing.T) {
assert := assert.New(t)
type testData struct {
envVarValue string
expectError bool
expectedURL string
}
data := []testData{
{"", false, expectedReleasesURL},
{"http://google.com", true, ""},
{"https://katacontainers.io", true, ""},
{"https://github.com/kata-containers/runtime/releases/latest", true, ""},
{"https://github.com/kata-containers/kata-containers/releases/latest", true, ""},
{expectedReleasesURL, false, expectedReleasesURL},
}
assert.Equal(os.Getenv("KATA_RELEASE_URL"), "")
defer os.Setenv("KATA_RELEASE_URL", "")
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := os.Setenv("KATA_RELEASE_URL", d.envVarValue)
msg = fmt.Sprintf("%s, error: %v", msg, err)
assert.NoError(err, msg)
url, err := getReleaseURL(currentSemver)
if d.expectError {
assert.Errorf(err, msg)
} else {
assert.NoErrorf(err, msg)
assert.Equal(d.expectedURL, url, msg)
}
}
}
func TestMakeRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
release map[string]interface{}
expectError bool
expectedVersion string
expectedDetails releaseDetails
}
invalidRel1 := map[string]interface{}{"foo": 1}
invalidRel2 := map[string]interface{}{"foo": "bar"}
invalidRel3 := map[string]interface{}{"foo": true}
testDate := "2020-09-01T22:10:44Z"
testRelVersion := "1.2.3"
testFilename := "kata-static-1.12.0-alpha1-x86_64.tar.xz"
testURL := fmt.Sprintf("https://github.com/kata-containers/runtime/releases/download/%s/%s", testRelVersion, testFilename)
testSemver, err := semver.Make(testRelVersion)
assert.NoError(err)
invalidRelMissingVersion := map[string]interface{}{}
invalidRelInvalidVersion := map[string]interface{}{
"tag_name": "not.valid.semver",
}
invalidRelMissingAssets := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": []interface{}{},
}
invalidAssetsMissingURL := []interface{}{
map[string]interface{}{
"name": testFilename,
"created_at": testDate,
},
}
invalidAssetsMissingFile := []interface{}{
map[string]interface{}{
"browser_download_url": testURL,
"created_at": testDate,
},
}
invalidAssetsMissingDate := []interface{}{
map[string]interface{}{
"name": testFilename,
"browser_download_url": testURL,
},
}
validAssets := []interface{}{
map[string]interface{}{
"browser_download_url": testURL,
"name": testFilename,
"created_at": testDate,
},
}
invalidRelAssetsMissingURL := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingURL,
}
invalidRelAssetsMissingFile := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingFile,
}
invalidRelAssetsMissingDate := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": invalidAssetsMissingDate,
}
validRel := map[string]interface{}{
"tag_name": testRelVersion,
"name": testFilename,
"assets": validAssets,
}
validReleaseDetails := releaseDetails{
version: testSemver,
date: testDate,
url: testURL,
filename: testFilename,
}
data := []testData{
{invalidRel1, true, "", releaseDetails{}},
{invalidRel2, true, "", releaseDetails{}},
{invalidRel3, true, "", releaseDetails{}},
{invalidRelMissingVersion, true, "", releaseDetails{}},
{invalidRelInvalidVersion, true, "", releaseDetails{}},
{invalidRelMissingAssets, true, "", releaseDetails{}},
{invalidRelAssetsMissingURL, true, "", releaseDetails{}},
{invalidRelAssetsMissingFile, true, "", releaseDetails{}},
{invalidRelAssetsMissingDate, true, "", releaseDetails{}},
{validRel, false, testRelVersion, validReleaseDetails},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
version, details, err := makeRelease(d.release)
msg = fmt.Sprintf("%s, version: %v, details: %+v, error: %v", msg, version, details, err)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
assert.Equal(d.expectedVersion, version, msg)
assert.Equal(d.expectedDetails, details, msg)
}
}
func TestReleaseURLIsValid(t *testing.T) {
assert := assert.New(t)
type testData struct {
url string
expectError bool
}
data := []testData{
{"", true},
{"foo", true},
{"foo bar", true},
{"https://google.com", true},
{projectAPIURL, true},
{kataLegacyReleaseURL, false},
{kataReleaseURL, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := releaseURLIsValid(d.url)
msg = fmt.Sprintf("%s, error: %v", msg, err)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
}
}
}
func TestDownloadURLIsValid(t *testing.T) {
assert := assert.New(t)
type testData struct {
url string
expectError bool
}
validKata1xDownload := "https://github.com/kata-containers/runtime/releases/download/1.12.0-alpha1/kata-static-1.12.0-alpha1-x86_64.tar.xz"
validKata2xDownload := "https://github.com/kata-containers/kata-containers/releases/download/2.0.0-alpha3/kata-static-2.0.0-alpha3-x86_64.tar.xz"
data := []testData{
{"", true},
{"foo", true},
{"foo bar", true},
{"https://google.com", true},
{projectURL, true},
{validKata1xDownload, false},
{validKata2xDownload, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
err := downloadURLIsValid(d.url)
msg = fmt.Sprintf("%s, error: %v", msg, err)
if d.expectError {
assert.Error(err, msg)
} else {
assert.NoError(err, msg)
}
}
}
func TestIgnoreRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
details releaseDetails
includeAll bool
expectIgnore bool
}
verWithoutPreRelease, err := semver.Make("2.0.0")
assert.NoError(err)
verWithPreRelease, err := semver.Make("2.0.0-alpha3")
assert.NoError(err)
relWithoutPreRelease := releaseDetails{
version: verWithoutPreRelease,
}
relWithPreRelease := releaseDetails{
version: verWithPreRelease,
}
data := []testData{
{relWithoutPreRelease, false, false},
{relWithoutPreRelease, true, false},
{relWithPreRelease, false, true},
{relWithPreRelease, true, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
ignore := ignoreRelease(d.details, d.includeAll)
if d.expectIgnore {
assert.True(ignore, msg)
} else {
assert.False(ignore, msg)
}
}
}
func TestGetReleases(t *testing.T) {
assert := assert.New(t)
url := "foo"
expectedErrMsg := "unsupported protocol scheme"
for _, includeAll := range []bool{true, false} {
euid := os.Geteuid()
msg := fmt.Sprintf("includeAll: %v, euid: %v", includeAll, euid)
_, _, err := getReleases(url, includeAll)
msg = fmt.Sprintf("%s, error: %v", msg, err)
assert.Error(err, msg)
if euid == 0 {
assert.Equal(err.Error(), errNoNetChecksAsRoot, msg)
} else {
assert.True(strings.Contains(err.Error(), expectedErrMsg), msg)
}
}
}
func TestFindNewestRelease(t *testing.T) {
assert := assert.New(t)
type testData struct {
currentVer semver.Version
versions []semver.Version
expectAvailable bool
expectVersion semver.Version
expectError bool
}
ver1, err := semver.Make("1.11.1")
assert.NoError(err)
ver2, err := semver.Make("1.11.3")
assert.NoError(err)
ver3, err := semver.Make("2.0.0")
assert.NoError(err)
data := []testData{
{semver.Version{}, []semver.Version{}, false, semver.Version{}, true},
{ver1, []semver.Version{}, false, semver.Version{}, true},
{ver1, []semver.Version{ver1}, false, semver.Version{}, false},
{ver2, []semver.Version{ver1}, false, semver.Version{}, false},
{ver1, []semver.Version{ver2}, true, ver2, false},
{ver1, []semver.Version{ver3}, true, ver3, false},
{ver1, []semver.Version{ver2, ver3}, true, ver3, false},
{ver2, []semver.Version{ver1, ver3}, true, ver3, false},
{ver2, []semver.Version{ver1}, false, semver.Version{}, false},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
available, version, err := findNewestRelease(d.currentVer, d.versions)
msg = fmt.Sprintf("%s, available: %v, version: %v, error: %v", msg, available, version, err)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
if !d.expectAvailable {
assert.False(available, msg)
continue
}
assert.Equal(d.expectVersion, version)
}
}
func TestGetNewReleaseType(t *testing.T) {
assert := assert.New(t)
type testData struct {
currentVer string
latestVer string
expectError bool
result string
}
data := []testData{
{"2.0.0-alpha3", "1.0.0", true, ""},
{"1.0.0", "1.0.0", true, ""},
{"2.0.0", "1.0.0", true, ""},
{"1.0.0", "2.0.0", false, "major"},
{"2.0.0-alpha3", "2.0.0-alpha4", false, "pre-release"},
{"1.0.0", "2.0.0-alpha3", false, "major pre-release"},
{"1.0.0", "1.1.2", false, "minor"},
{"1.0.0", "1.1.2-pre2", false, "minor pre-release"},
{"1.0.0", "1.1.2-foo", false, "minor pre-release"},
{"1.0.0", "1.0.3", false, "patch"},
{"1.0.0-beta29", "1.0.0-beta30", false, "pre-release"},
{"1.0.0", "1.0.3-alpha99.1b", false, "patch pre-release"},
}
for i, d := range data {
msg := fmt.Sprintf("test[%d]: %+v", i, d)
current, err := semver.Make(d.currentVer)
msg = fmt.Sprintf("%s, current: %v, error: %v", msg, current, err)
assert.NoError(err, msg)
latest, err := semver.Make(d.latestVer)
assert.NoError(err, msg)
desc, err := getNewReleaseType(current, latest)
if d.expectError {
assert.Error(err, msg)
continue
}
assert.NoError(err, msg)
assert.Equal(d.result, desc, msg)
}
}