mirror of
https://github.com/containers/podman
synced 2024-10-18 16:24:34 +00:00
V2 API Version Support
* Update blang/semver to allow ParseTolerant() support * Provide helper functions for API handlers to obtain client's 'version' path variable focused on API endpoint tree: libpod vs. compat * Introduce new errors: * version not given in path, endpoints may determine if this is a hard error (ErrVersionNotGiven) * given version not supported (ErrVersionNotSupported), only a soft error if the handler is going to hijack the connection * Added unit tests for version parsing * bindings check version on connect: * client <= Server API version connection is continued * client >= Server API version connection fails Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
parent
09f8f14b4f
commit
f9c392f50a
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.12
|
|||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
|
||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -39,6 +39,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/blang/semver v3.1.0+incompatible h1:7hqmJYuaEK3qwVjWubYiht3j93YI0WQBuysxHIfUriU=
|
||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37 h1:uxxtrnACqI9zK4ENDMf0WpXfUsHP5V8liuq5QdgDISU=
|
||||
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
|
|
|
@ -5,22 +5,22 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/containers/buildah"
|
||||
"github.com/containers/libpod/pkg/api/handlers"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
)
|
||||
|
||||
// Ping returns headers to client about the service
|
||||
//
|
||||
// This handler must always be the same for the compatibility and libpod URL trees!
|
||||
// Clients will use the Header availability to test which backend engine is in use.
|
||||
// Note: Additionally handler supports GET and HEAD methods
|
||||
func Ping(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("API-Version", handlers.DefaultApiVersion)
|
||||
w.Header().Set("API-Version", utils.ApiVersion[utils.CompatTree][utils.CurrentApiVersion].String())
|
||||
w.Header().Set("BuildKit-Version", "")
|
||||
w.Header().Set("Docker-Experimental", "true")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
||||
// API-Version and Libpod-API-Version may not always be equal
|
||||
w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion)
|
||||
w.Header().Set("Libpod-API-Version", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String())
|
||||
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/api/handlers"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
docker "github.com/docker/docker/api/types"
|
||||
|
@ -35,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Name: "Podman Engine",
|
||||
Version: versionInfo.Version,
|
||||
Details: map[string]string{
|
||||
"APIVersion": handlers.DefaultApiVersion,
|
||||
"APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(),
|
||||
"Arch": goRuntime.GOARCH,
|
||||
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
|
||||
"Experimental": "true",
|
||||
"GitCommit": versionInfo.GitCommit,
|
||||
"GoVersion": versionInfo.GoVersion,
|
||||
"KernelVersion": infoData.Host.Kernel,
|
||||
"MinAPIVersion": handlers.MinimalApiVersion,
|
||||
"MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(),
|
||||
"Os": goRuntime.GOOS,
|
||||
},
|
||||
}}
|
||||
|
||||
utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{Version: docker.Version{
|
||||
Platform: struct {
|
||||
Name string
|
||||
}{
|
||||
Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
|
||||
},
|
||||
APIVersion: components[0].Details["APIVersion"],
|
||||
Arch: components[0].Details["Arch"],
|
||||
BuildTime: components[0].Details["BuildTime"],
|
||||
Components: components,
|
||||
Experimental: true,
|
||||
GitCommit: components[0].Details["GitCommit"],
|
||||
GoVersion: components[0].Details["GoVersion"],
|
||||
KernelVersion: components[0].Details["KernelVersion"],
|
||||
MinAPIVersion: components[0].Details["MinAPIVersion"],
|
||||
Os: components[0].Details["Os"],
|
||||
Version: components[0].Version,
|
||||
}})
|
||||
utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
|
||||
Version: docker.Version{
|
||||
Platform: struct {
|
||||
Name string
|
||||
}{
|
||||
Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
|
||||
},
|
||||
APIVersion: components[0].Details["APIVersion"],
|
||||
Arch: components[0].Details["Arch"],
|
||||
BuildTime: components[0].Details["BuildTime"],
|
||||
Components: components,
|
||||
Experimental: true,
|
||||
GitCommit: components[0].Details["GitCommit"],
|
||||
GoVersion: components[0].Details["GoVersion"],
|
||||
KernelVersion: components[0].Details["KernelVersion"],
|
||||
MinAPIVersion: components[0].Details["MinAPIVersion"],
|
||||
Os: components[0].Details["Os"],
|
||||
Version: components[0].Version,
|
||||
}})
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package handlers
|
||||
|
||||
const (
|
||||
DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
|
||||
MinimalApiVersion = "1.24"
|
||||
)
|
|
@ -9,11 +9,55 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type (
|
||||
// VersionTree determines which API endpoint tree for version
|
||||
VersionTree int
|
||||
// VersionLevel determines which API level, current or something from the past
|
||||
VersionLevel int
|
||||
)
|
||||
|
||||
const (
|
||||
// LibpodTree supports Libpod endpoints
|
||||
LibpodTree = VersionTree(iota)
|
||||
// CompatTree supports Libpod endpoints
|
||||
CompatTree
|
||||
|
||||
// CurrentApiVersion announces what is the current API level
|
||||
CurrentApiVersion = VersionLevel(iota)
|
||||
// MinimalApiVersion announces what is the oldest API level supported
|
||||
MinimalApiVersion
|
||||
)
|
||||
|
||||
var (
|
||||
// See https://docs.docker.com/engine/api/v1.40/
|
||||
// libpod compat handlers are expected to honor docker API versions
|
||||
|
||||
// ApiVersion provides the current and minimal API versions for compat and libpod endpoint trees
|
||||
// Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow
|
||||
// clients to shop for the Version they wish to support
|
||||
ApiVersion = map[VersionTree]map[VersionLevel]semver.Version{
|
||||
LibpodTree: {
|
||||
CurrentApiVersion: semver.MustParse("1.0.0"),
|
||||
MinimalApiVersion: semver.MustParse("1.0.0"),
|
||||
},
|
||||
CompatTree: {
|
||||
CurrentApiVersion: semver.MustParse("1.40.0"),
|
||||
MinimalApiVersion: semver.MustParse("1.24.0"),
|
||||
},
|
||||
}
|
||||
|
||||
// ErrVersionNotGiven returned when version not given by client
|
||||
ErrVersionNotGiven = errors.New("version not given in URL path")
|
||||
// ErrVersionNotSupported returned when given version is too old
|
||||
ErrVersionNotSupported = errors.New("given version is not supported")
|
||||
)
|
||||
|
||||
// IsLibpodRequest returns true if the request related to a libpod endpoint
|
||||
// (e.g., /v2/libpod/...).
|
||||
func IsLibpodRequest(r *http.Request) bool {
|
||||
|
@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool {
|
|||
return len(split) >= 3 && split[2] == "libpod"
|
||||
}
|
||||
|
||||
// SupportedVersion validates that the version provided by client is included in the given condition
|
||||
// https://github.com/blang/semver#ranges provides the details for writing conditions
|
||||
// If a version is not given in URL path, ErrVersionNotGiven is returned
|
||||
func SupportedVersion(r *http.Request, condition string) (semver.Version, error) {
|
||||
version := semver.Version{}
|
||||
val, ok := mux.Vars(r)["version"]
|
||||
if !ok {
|
||||
return version, ErrVersionNotGiven
|
||||
}
|
||||
safeVal, err := url.PathUnescape(val)
|
||||
if err != nil {
|
||||
return version, errors.Wrapf(err, "unable to unescape given API version: %q", val)
|
||||
}
|
||||
version, err = semver.ParseTolerant(safeVal)
|
||||
if err != nil {
|
||||
return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val)
|
||||
}
|
||||
|
||||
inRange, err := semver.ParseRange(condition)
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
|
||||
if inRange(version) {
|
||||
return version, nil
|
||||
}
|
||||
return version, ErrVersionNotSupported
|
||||
}
|
||||
|
||||
// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server
|
||||
// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL
|
||||
func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) {
|
||||
tree := CompatTree
|
||||
if IsLibpodRequest(r) {
|
||||
tree = LibpodTree
|
||||
}
|
||||
|
||||
return SupportedVersion(r,
|
||||
fmt.Sprintf(">=%s <=%s", ApiVersion[tree][MinimalApiVersion].String(),
|
||||
ApiVersion[tree][CurrentApiVersion].String()))
|
||||
}
|
||||
|
||||
// WriteResponse encodes the given value as JSON or string and renders it for http client
|
||||
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
|
||||
// RFC2616 explicitly states that the following status codes "MUST NOT
|
||||
|
|
139
pkg/api/handlers/utils/handler_test.go
Normal file
139
pkg/api/handlers/utils/handler_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func TestSupportedVersion(t *testing.T) {
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("/v%s/libpod/testing/versions", ApiVersion[LibpodTree][CurrentApiVersion]),
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = mux.SetURLVars(req, map[string]string{"version": ApiVersion[LibpodTree][CurrentApiVersion].String()})
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := SupportedVersionWithDefaults(r)
|
||||
switch {
|
||||
case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case errors.Is(err, ErrVersionNotSupported): // version given but not supported
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
default: // all good
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, "OK")
|
||||
}
|
||||
})
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, http.StatusOK)
|
||||
}
|
||||
|
||||
// Check the response body is what we expect.
|
||||
expected := `OK`
|
||||
if rr.Body.String() != expected {
|
||||
t.Errorf("handler returned unexpected body: got %q want %q",
|
||||
rr.Body.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedVersion(t *testing.T) {
|
||||
version := "999.999.999"
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("/v%s/libpod/testing/versions", version),
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = mux.SetURLVars(req, map[string]string{"version": version})
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := SupportedVersionWithDefaults(r)
|
||||
switch {
|
||||
case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case errors.Is(err, ErrVersionNotSupported): // version given but not supported
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
default: // all good
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, "OK")
|
||||
}
|
||||
})
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusBadRequest {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// Check the response body is what we expect.
|
||||
expected := ErrVersionNotSupported.Error()
|
||||
if rr.Body.String() != expected {
|
||||
t.Errorf("handler returned unexpected body: got %q want %q",
|
||||
rr.Body.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEqualVersion(t *testing.T) {
|
||||
version := "1.30.0"
|
||||
req, err := http.NewRequest("GET",
|
||||
fmt.Sprintf("/v%s/libpod/testing/versions", version),
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = mux.SetURLVars(req, map[string]string{"version": version})
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := SupportedVersion(r, "=="+version)
|
||||
switch {
|
||||
case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case errors.Is(err, ErrVersionNotSupported): // version given but not supported
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, err.Error())
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, err.Error())
|
||||
default: // all good
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, "OK")
|
||||
}
|
||||
})
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, http.StatusOK)
|
||||
}
|
||||
|
||||
// Check the response body is what we expect.
|
||||
expected := http.StatusText(http.StatusOK)
|
||||
if rr.Body.String() != expected {
|
||||
t.Errorf("handler returned unexpected body: got %q want %q",
|
||||
rr.Body.String(), expected)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
package bindings
|
||||
|
||||
import (
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
// PTrue is a convenience variable that can be used in bindings where
|
||||
// a pointer to a bool (optional parameter) is required.
|
||||
|
@ -17,4 +21,7 @@ var (
|
|||
// a pointer to a bool (optional parameter) is required.
|
||||
pFalse = false
|
||||
PFalse = &pFalse
|
||||
|
||||
// _*YES*- podman will fail to run if this value is wrong
|
||||
APIVersion = semver.MustParse("1.0.0")
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/containers/libpod/pkg/api/types"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -143,7 +144,7 @@ func tcpClient(_url *url.URL) (Connection, error) {
|
|||
}
|
||||
|
||||
// pingNewConnection pings to make sure the RESTFUL service is up
|
||||
// and running. it should only be used where initializing a connection
|
||||
// and running. it should only be used when initializing a connection
|
||||
func pingNewConnection(ctx context.Context) error {
|
||||
client, err := GetClient(ctx)
|
||||
if err != nil {
|
||||
|
@ -154,8 +155,20 @@ func pingNewConnection(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
return nil
|
||||
v, err := semver.ParseTolerant(response.Header.Get("Libpod-API-Version"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch APIVersion.Compare(v) {
|
||||
case 1, 0:
|
||||
// Server's job when client version is equal or older
|
||||
return nil
|
||||
case -1:
|
||||
return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), v.String())
|
||||
}
|
||||
}
|
||||
return errors.Errorf("ping response was %q", response.StatusCode)
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package bindings
|
||||
|
||||
func (c Connection) Version() {}
|
|
@ -10,13 +10,13 @@ t HEAD /_ping 200
|
|||
t GET /libpod/_ping 200 OK
|
||||
|
||||
for i in /version version; do
|
||||
t GET $i 200 \
|
||||
.Components[0].Name="Podman Engine" \
|
||||
.Components[0].Details.APIVersion=1.40 \
|
||||
.Components[0].Details.MinAPIVersion=1.24 \
|
||||
.Components[0].Details.Os=linux \
|
||||
.ApiVersion=1.40 \
|
||||
.MinAPIVersion=1.24 \
|
||||
t GET $i 200 \
|
||||
.Components[0].Name="Podman Engine" \
|
||||
.Components[0].Details.APIVersion=1.0.0 \
|
||||
.Components[0].Details.MinAPIVersion=1.0.0 \
|
||||
.Components[0].Details.Os=linux \
|
||||
.ApiVersion=1.0.0 \
|
||||
.MinAPIVersion=1.0.0 \
|
||||
.Os=linux
|
||||
done
|
||||
|
||||
|
|
21
vendor/github.com/blang/semver/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/blang/semver/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
language: go
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4.3
|
||||
- go: 1.5.4
|
||||
- go: 1.6.3
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- echo "Test and track coverage" ; $HOME/gopath/bin/goveralls -package "." -service=travis-ci
|
||||
-repotoken $COVERALLS_TOKEN
|
||||
- echo "Build examples" ; cd examples && go build
|
||||
- echo "Check if gofmt'd" ; diff -u <(echo -n) <(gofmt -d -s .)
|
||||
env:
|
||||
global:
|
||||
secure: HroGEAUQpVq9zX1b1VIkraLiywhGbzvNnTZq2TMxgK7JHP8xqNplAeF1izrR2i4QLL9nsY+9WtYss4QuPvEtZcVHUobw6XnL6radF7jS1LgfYZ9Y7oF+zogZ2I5QUMRLGA7rcxQ05s7mKq3XZQfeqaNts4bms/eZRefWuaFZbkw=
|
5
vendor/github.com/blang/semver/README.md
generated
vendored
5
vendor/github.com/blang/semver/README.md
generated
vendored
|
@ -1,4 +1,4 @@
|
|||
semver for golang [![Build Status](https://drone.io/github.com/blang/semver/status.png)](https://drone.io/github.com/blang/semver/latest) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master)
|
||||
semver for golang [![Build Status](https://travis-ci.org/blang/semver.svg?branch=master)](https://travis-ci.org/blang/semver) [![GoDoc](https://godoc.org/github.com/blang/semver?status.png)](https://godoc.org/github.com/blang/semver) [![Coverage Status](https://img.shields.io/coveralls/blang/semver.svg)](https://coveralls.io/r/blang/semver?branch=master)
|
||||
======
|
||||
|
||||
semver is a [Semantic Versioning](http://semver.org/) library written in golang. It fully covers spec version `2.0.0`.
|
||||
|
@ -41,6 +41,7 @@ Features
|
|||
- Compare Helper Methods
|
||||
- InPlace manipulation
|
||||
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
|
||||
- Wildcards `>=1.x`, `<=2.5.x`
|
||||
- Sortable (implements sort.Interface)
|
||||
- database/sql compatible (sql.Scanner/Valuer)
|
||||
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
||||
|
@ -59,6 +60,8 @@ A condition is composed of an operator and a version. The supported operators ar
|
|||
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
|
||||
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
|
||||
|
||||
Note that spaces between the operator and the version will be gracefully tolerated.
|
||||
|
||||
A `Range` can link multiple `Ranges` separated by space:
|
||||
|
||||
Ranges can be linked by logical AND:
|
||||
|
|
17
vendor/github.com/blang/semver/package.json
generated
vendored
Normal file
17
vendor/github.com/blang/semver/package.json
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"author": "blang",
|
||||
"bugs": {
|
||||
"URL": "https://github.com/blang/semver/issues",
|
||||
"url": "https://github.com/blang/semver/issues"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/blang/semver"
|
||||
},
|
||||
"gxVersion": "0.10.0",
|
||||
"language": "go",
|
||||
"license": "MIT",
|
||||
"name": "semver",
|
||||
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||
"version": "3.5.1"
|
||||
}
|
||||
|
200
vendor/github.com/blang/semver/range.go
generated
vendored
200
vendor/github.com/blang/semver/range.go
generated
vendored
|
@ -2,10 +2,33 @@ package semver
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type wildcardType int
|
||||
|
||||
const (
|
||||
noneWildcard wildcardType = iota
|
||||
majorWildcard wildcardType = 1
|
||||
minorWildcard wildcardType = 2
|
||||
patchWildcard wildcardType = 3
|
||||
)
|
||||
|
||||
func wildcardTypefromInt(i int) wildcardType {
|
||||
switch i {
|
||||
case 1:
|
||||
return majorWildcard
|
||||
case 2:
|
||||
return minorWildcard
|
||||
case 3:
|
||||
return patchWildcard
|
||||
default:
|
||||
return noneWildcard
|
||||
}
|
||||
}
|
||||
|
||||
type comparator func(Version, Version) bool
|
||||
|
||||
var (
|
||||
|
@ -92,8 +115,12 @@ func ParseRange(s string) (Range, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expandedParts, err := expandWildcardVersion(orParts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var orFn Range
|
||||
for _, p := range orParts {
|
||||
for _, p := range expandedParts {
|
||||
var andFn Range
|
||||
for _, ap := range p {
|
||||
opStr, vStr, err := splitComparatorVersion(ap)
|
||||
|
@ -164,20 +191,39 @@ func buildVersionRange(opStr, vStr string) (*versionRange, error) {
|
|||
|
||||
}
|
||||
|
||||
// splitAndTrim splits a range string by spaces and cleans leading and trailing spaces
|
||||
// inArray checks if a byte is contained in an array of bytes
|
||||
func inArray(s byte, list []byte) bool {
|
||||
for _, el := range list {
|
||||
if el == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// splitAndTrim splits a range string by spaces and cleans whitespaces
|
||||
func splitAndTrim(s string) (result []string) {
|
||||
last := 0
|
||||
var lastChar byte
|
||||
excludeFromSplit := []byte{'>', '<', '='}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == ' ' {
|
||||
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
||||
if last < i-1 {
|
||||
result = append(result, s[last:i])
|
||||
}
|
||||
last = i + 1
|
||||
} else if s[i] != ' ' {
|
||||
lastChar = s[i]
|
||||
}
|
||||
}
|
||||
if last < len(s)-1 {
|
||||
result = append(result, s[last:])
|
||||
}
|
||||
|
||||
for i, v := range result {
|
||||
result[i] = strings.Replace(v, " ", "", -1)
|
||||
}
|
||||
|
||||
// parts := strings.Split(s, " ")
|
||||
// for _, x := range parts {
|
||||
// if s := strings.TrimSpace(x); len(s) != 0 {
|
||||
|
@ -188,7 +234,6 @@ func splitAndTrim(s string) (result []string) {
|
|||
}
|
||||
|
||||
// splitComparatorVersion splits the comparator from the version.
|
||||
// Spaces between the comparator and the version are not allowed.
|
||||
// Input must be free of leading or trailing spaces.
|
||||
func splitComparatorVersion(s string) (string, string, error) {
|
||||
i := strings.IndexFunc(s, unicode.IsDigit)
|
||||
|
@ -198,6 +243,144 @@ func splitComparatorVersion(s string) (string, string, error) {
|
|||
return strings.TrimSpace(s[0:i]), s[i:], nil
|
||||
}
|
||||
|
||||
// getWildcardType will return the type of wildcard that the
|
||||
// passed version contains
|
||||
func getWildcardType(vStr string) wildcardType {
|
||||
parts := strings.Split(vStr, ".")
|
||||
nparts := len(parts)
|
||||
wildcard := parts[nparts-1]
|
||||
|
||||
possibleWildcardType := wildcardTypefromInt(nparts)
|
||||
if wildcard == "x" {
|
||||
return possibleWildcardType
|
||||
}
|
||||
|
||||
return noneWildcard
|
||||
}
|
||||
|
||||
// createVersionFromWildcard will convert a wildcard version
|
||||
// into a regular version, replacing 'x's with '0's, handling
|
||||
// special cases like '1.x.x' and '1.x'
|
||||
func createVersionFromWildcard(vStr string) string {
|
||||
// handle 1.x.x
|
||||
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
|
||||
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
|
||||
parts := strings.Split(vStr2, ".")
|
||||
|
||||
// handle 1.x
|
||||
if len(parts) == 2 {
|
||||
return vStr2 + ".0"
|
||||
}
|
||||
|
||||
return vStr2
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the major version
|
||||
// of the passed version
|
||||
func incrementMajorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[0] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// incrementMajorVersion will increment the minor version
|
||||
// of the passed version
|
||||
func incrementMinorVersion(vStr string) (string, error) {
|
||||
parts := strings.Split(vStr, ".")
|
||||
i, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
parts[1] = strconv.Itoa(i + 1)
|
||||
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// expandWildcardVersion will expand wildcards inside versions
|
||||
// following these rules:
|
||||
//
|
||||
// * when dealing with patch wildcards:
|
||||
// >= 1.2.x will become >= 1.2.0
|
||||
// <= 1.2.x will become < 1.3.0
|
||||
// > 1.2.x will become >= 1.3.0
|
||||
// < 1.2.x will become < 1.2.0
|
||||
// != 1.2.x will become < 1.2.0 >= 1.3.0
|
||||
//
|
||||
// * when dealing with minor wildcards:
|
||||
// >= 1.x will become >= 1.0.0
|
||||
// <= 1.x will become < 2.0.0
|
||||
// > 1.x will become >= 2.0.0
|
||||
// < 1.0 will become < 1.0.0
|
||||
// != 1.x will become < 1.0.0 >= 2.0.0
|
||||
//
|
||||
// * when dealing with wildcards without
|
||||
// version operator:
|
||||
// 1.2.x will become >= 1.2.0 < 1.3.0
|
||||
// 1.x will become >= 1.0.0 < 2.0.0
|
||||
func expandWildcardVersion(parts [][]string) ([][]string, error) {
|
||||
var expandedParts [][]string
|
||||
for _, p := range parts {
|
||||
var newParts []string
|
||||
for _, ap := range p {
|
||||
if strings.Index(ap, "x") != -1 {
|
||||
opStr, vStr, err := splitComparatorVersion(ap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versionWildcardType := getWildcardType(vStr)
|
||||
flatVersion := createVersionFromWildcard(vStr)
|
||||
|
||||
var resultOperator string
|
||||
var shouldIncrementVersion bool
|
||||
switch opStr {
|
||||
case ">":
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
case ">=":
|
||||
resultOperator = ">="
|
||||
case "<":
|
||||
resultOperator = "<"
|
||||
case "<=":
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "", "=", "==":
|
||||
newParts = append(newParts, ">="+flatVersion)
|
||||
resultOperator = "<"
|
||||
shouldIncrementVersion = true
|
||||
case "!=", "!":
|
||||
newParts = append(newParts, "<"+flatVersion)
|
||||
resultOperator = ">="
|
||||
shouldIncrementVersion = true
|
||||
}
|
||||
|
||||
var resultVersion string
|
||||
if shouldIncrementVersion {
|
||||
switch versionWildcardType {
|
||||
case patchWildcard:
|
||||
resultVersion, _ = incrementMinorVersion(flatVersion)
|
||||
case minorWildcard:
|
||||
resultVersion, _ = incrementMajorVersion(flatVersion)
|
||||
}
|
||||
} else {
|
||||
resultVersion = flatVersion
|
||||
}
|
||||
|
||||
ap = resultOperator + resultVersion
|
||||
}
|
||||
newParts = append(newParts, ap)
|
||||
}
|
||||
expandedParts = append(expandedParts, newParts)
|
||||
}
|
||||
|
||||
return expandedParts, nil
|
||||
}
|
||||
|
||||
func parseComparator(s string) comparator {
|
||||
switch s {
|
||||
case "==":
|
||||
|
@ -222,3 +405,12 @@ func parseComparator(s string) comparator {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustParseRange is like ParseRange but panics if the range cannot be parsed.
|
||||
func MustParseRange(s string) Range {
|
||||
r, err := ParseRange(s)
|
||||
if err != nil {
|
||||
panic(`semver: ParseRange(` + s + `): ` + err.Error())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
|
23
vendor/github.com/blang/semver/semver.go
generated
vendored
23
vendor/github.com/blang/semver/semver.go
generated
vendored
|
@ -200,6 +200,29 @@ func Make(s string) (Version, error) {
|
|||
return Parse(s)
|
||||
}
|
||||
|
||||
// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
|
||||
// specs to be parsed by this library. It does so by normalizing versions before passing them to
|
||||
// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
|
||||
// with only major and minor components specified
|
||||
func ParseTolerant(s string) (Version, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
s = strings.TrimPrefix(s, "v")
|
||||
|
||||
// Split into major.minor.(patch+pr+meta)
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
if len(parts) < 3 {
|
||||
if strings.ContainsAny(parts[len(parts)-1], "+-") {
|
||||
return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
|
||||
}
|
||||
for len(parts) < 3 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
s = strings.Join(parts, ".")
|
||||
}
|
||||
|
||||
return Parse(s)
|
||||
}
|
||||
|
||||
// Parse parses version string and returns a validated Version or error
|
||||
func Parse(s string) (Version, error) {
|
||||
if len(s) == 0 {
|
||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -34,7 +34,7 @@ github.com/VividCortex/ewma
|
|||
github.com/acarl005/stripansi
|
||||
# github.com/beorn7/perks v1.0.1
|
||||
github.com/beorn7/perks/quantile
|
||||
# github.com/blang/semver v3.1.0+incompatible
|
||||
# github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/blang/semver
|
||||
# github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
|
||||
github.com/buger/goterm
|
||||
|
|
Loading…
Reference in a new issue