mirror of https://github.com/containers/podman.git
Merge pull request #6271 from jwhonce/wip/version
V2 API Version Support
This commit is contained in:
commit
b0bfa0e6da
1
go.mod
1
go.mod
|
|
@ -4,6 +4,7 @@ go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1
|
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/buger/goterm v0.0.0-20181115115552-c206103e1f37
|
||||||
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
|
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect
|
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/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 h1:7hqmJYuaEK3qwVjWubYiht3j93YI0WQBuysxHIfUriU=
|
||||||
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
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 h1:uxxtrnACqI9zK4ENDMf0WpXfUsHP5V8liuq5QdgDISU=
|
||||||
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
|
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=
|
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,22 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/buildah"
|
"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
|
// Ping returns headers to client about the service
|
||||||
//
|
//
|
||||||
// This handler must always be the same for the compatibility and libpod URL trees!
|
// 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.
|
// 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) {
|
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("BuildKit-Version", "")
|
||||||
w.Header().Set("Docker-Experimental", "true")
|
w.Header().Set("Docker-Experimental", "true")
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Pragma", "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", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String())
|
||||||
w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion)
|
|
||||||
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
|
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"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/api/handlers/utils"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
|
|
@ -35,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Name: "Podman Engine",
|
Name: "Podman Engine",
|
||||||
Version: versionInfo.Version,
|
Version: versionInfo.Version,
|
||||||
Details: map[string]string{
|
Details: map[string]string{
|
||||||
"APIVersion": handlers.DefaultApiVersion,
|
"APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(),
|
||||||
"Arch": goRuntime.GOARCH,
|
"Arch": goRuntime.GOARCH,
|
||||||
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
|
"BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339),
|
||||||
"Experimental": "true",
|
"Experimental": "true",
|
||||||
"GitCommit": versionInfo.GitCommit,
|
"GitCommit": versionInfo.GitCommit,
|
||||||
"GoVersion": versionInfo.GoVersion,
|
"GoVersion": versionInfo.GoVersion,
|
||||||
"KernelVersion": infoData.Host.Kernel,
|
"KernelVersion": infoData.Host.Kernel,
|
||||||
"MinAPIVersion": handlers.MinimalApiVersion,
|
"MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(),
|
||||||
"Os": goRuntime.GOOS,
|
"Os": goRuntime.GOOS,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{Version: docker.Version{
|
utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
|
||||||
Platform: struct {
|
Version: docker.Version{
|
||||||
Name string
|
Platform: struct {
|
||||||
}{
|
Name string
|
||||||
Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
|
}{
|
||||||
},
|
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"],
|
APIVersion: components[0].Details["APIVersion"],
|
||||||
BuildTime: components[0].Details["BuildTime"],
|
Arch: components[0].Details["Arch"],
|
||||||
Components: components,
|
BuildTime: components[0].Details["BuildTime"],
|
||||||
Experimental: true,
|
Components: components,
|
||||||
GitCommit: components[0].Details["GitCommit"],
|
Experimental: true,
|
||||||
GoVersion: components[0].Details["GoVersion"],
|
GitCommit: components[0].Details["GitCommit"],
|
||||||
KernelVersion: components[0].Details["KernelVersion"],
|
GoVersion: components[0].Details["GoVersion"],
|
||||||
MinAPIVersion: components[0].Details["MinAPIVersion"],
|
KernelVersion: components[0].Details["KernelVersion"],
|
||||||
Os: components[0].Details["Os"],
|
MinAPIVersion: components[0].Details["MinAPIVersion"],
|
||||||
Version: components[0].Version,
|
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"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"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
|
// IsLibpodRequest returns true if the request related to a libpod endpoint
|
||||||
// (e.g., /v2/libpod/...).
|
// (e.g., /v2/libpod/...).
|
||||||
func IsLibpodRequest(r *http.Request) bool {
|
func IsLibpodRequest(r *http.Request) bool {
|
||||||
|
|
@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool {
|
||||||
return len(split) >= 3 && split[2] == "libpod"
|
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
|
// WriteResponse encodes the given value as JSON or string and renders it for http client
|
||||||
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
|
func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
|
||||||
// RFC2616 explicitly states that the following status codes "MUST NOT
|
// RFC2616 explicitly states that the following status codes "MUST NOT
|
||||||
|
|
|
||||||
|
|
@ -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
|
package bindings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// PTrue is a convenience variable that can be used in bindings where
|
// PTrue is a convenience variable that can be used in bindings where
|
||||||
// a pointer to a bool (optional parameter) is required.
|
// a pointer to a bool (optional parameter) is required.
|
||||||
|
|
@ -17,4 +21,7 @@ var (
|
||||||
// a pointer to a bool (optional parameter) is required.
|
// a pointer to a bool (optional parameter) is required.
|
||||||
pFalse = false
|
pFalse = false
|
||||||
PFalse = &pFalse
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
"github.com/containers/libpod/pkg/api/types"
|
"github.com/containers/libpod/pkg/api/types"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/pkg/errors"
|
"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
|
// 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 {
|
func pingNewConnection(ctx context.Context) error {
|
||||||
client, err := GetClient(ctx)
|
client, err := GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -154,8 +155,20 @@ func pingNewConnection(ctx context.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.StatusCode == http.StatusOK {
|
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)
|
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
|
t GET /libpod/_ping 200 OK
|
||||||
|
|
||||||
for i in /version version; do
|
for i in /version version; do
|
||||||
t GET $i 200 \
|
t GET $i 200 \
|
||||||
.Components[0].Name="Podman Engine" \
|
.Components[0].Name="Podman Engine" \
|
||||||
.Components[0].Details.APIVersion=1.40 \
|
.Components[0].Details.APIVersion=1.0.0 \
|
||||||
.Components[0].Details.MinAPIVersion=1.24 \
|
.Components[0].Details.MinAPIVersion=1.0.0 \
|
||||||
.Components[0].Details.Os=linux \
|
.Components[0].Details.Os=linux \
|
||||||
.ApiVersion=1.40 \
|
.ApiVersion=1.0.0 \
|
||||||
.MinAPIVersion=1.24 \
|
.MinAPIVersion=1.0.0 \
|
||||||
.Os=linux
|
.Os=linux
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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=
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
semver for golang [](https://drone.io/github.com/blang/semver/latest) [](https://godoc.org/github.com/blang/semver) [](https://coveralls.io/r/blang/semver?branch=master)
|
semver for golang [](https://travis-ci.org/blang/semver) [](https://godoc.org/github.com/blang/semver) [](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`.
|
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
|
- Compare Helper Methods
|
||||||
- InPlace manipulation
|
- InPlace manipulation
|
||||||
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
|
- 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)
|
- Sortable (implements sort.Interface)
|
||||||
- database/sql compatible (sql.Scanner/Valuer)
|
- database/sql compatible (sql.Scanner/Valuer)
|
||||||
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
- 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`, `==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`.
|
- `!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:
|
A `Range` can link multiple `Ranges` separated by space:
|
||||||
|
|
||||||
Ranges can be linked by logical AND:
|
Ranges can be linked by logical AND:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,10 +2,33 @@ package semver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"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
|
type comparator func(Version, Version) bool
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -92,8 +115,12 @@ func ParseRange(s string) (Range, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
expandedParts, err := expandWildcardVersion(orParts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var orFn Range
|
var orFn Range
|
||||||
for _, p := range orParts {
|
for _, p := range expandedParts {
|
||||||
var andFn Range
|
var andFn Range
|
||||||
for _, ap := range p {
|
for _, ap := range p {
|
||||||
opStr, vStr, err := splitComparatorVersion(ap)
|
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) {
|
func splitAndTrim(s string) (result []string) {
|
||||||
last := 0
|
last := 0
|
||||||
|
var lastChar byte
|
||||||
|
excludeFromSplit := []byte{'>', '<', '='}
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] == ' ' {
|
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
||||||
if last < i-1 {
|
if last < i-1 {
|
||||||
result = append(result, s[last:i])
|
result = append(result, s[last:i])
|
||||||
}
|
}
|
||||||
last = i + 1
|
last = i + 1
|
||||||
|
} else if s[i] != ' ' {
|
||||||
|
lastChar = s[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last < len(s)-1 {
|
if last < len(s)-1 {
|
||||||
result = append(result, s[last:])
|
result = append(result, s[last:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, v := range result {
|
||||||
|
result[i] = strings.Replace(v, " ", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
// parts := strings.Split(s, " ")
|
// parts := strings.Split(s, " ")
|
||||||
// for _, x := range parts {
|
// for _, x := range parts {
|
||||||
// if s := strings.TrimSpace(x); len(s) != 0 {
|
// 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.
|
// 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.
|
// Input must be free of leading or trailing spaces.
|
||||||
func splitComparatorVersion(s string) (string, string, error) {
|
func splitComparatorVersion(s string) (string, string, error) {
|
||||||
i := strings.IndexFunc(s, unicode.IsDigit)
|
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
|
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 {
|
func parseComparator(s string) comparator {
|
||||||
switch s {
|
switch s {
|
||||||
case "==":
|
case "==":
|
||||||
|
|
@ -222,3 +405,12 @@ func parseComparator(s string) comparator {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,29 @@ func Make(s string) (Version, error) {
|
||||||
return Parse(s)
|
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
|
// Parse parses version string and returns a validated Version or error
|
||||||
func Parse(s string) (Version, error) {
|
func Parse(s string) (Version, error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ github.com/VividCortex/ewma
|
||||||
github.com/acarl005/stripansi
|
github.com/acarl005/stripansi
|
||||||
# github.com/beorn7/perks v1.0.1
|
# github.com/beorn7/perks v1.0.1
|
||||||
github.com/beorn7/perks/quantile
|
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/blang/semver
|
||||||
# github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
|
# github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
|
||||||
github.com/buger/goterm
|
github.com/buger/goterm
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue