mirror of
https://github.com/containers/podman.git
synced 2025-06-23 02:18:13 +08:00
create apiutils package
Move SupportedVersion() and IsLibpodRequest() to separate package to avoid import cycle when using it in libpod. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
@ -17,6 +17,7 @@ import (
|
||||
"github.com/containers/podman/v4/libpod"
|
||||
"github.com/containers/podman/v4/pkg/api/handlers"
|
||||
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
||||
"github.com/containers/podman/v4/pkg/api/handlers/utils/apiutil"
|
||||
api "github.com/containers/podman/v4/pkg/api/types"
|
||||
"github.com/containers/podman/v4/pkg/auth"
|
||||
"github.com/containers/podman/v4/pkg/channel"
|
||||
@ -80,7 +81,7 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
if _, err := utils.SupportedVersion(r, "< 4.0.0"); err == utils.ErrVersionNotSupported {
|
||||
if _, err := utils.SupportedVersion(r, "< 4.0.0"); err == apiutil.ErrVersionNotSupported {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
|
||||
|
69
pkg/api/handlers/utils/apiutil/apiutil.go
Normal file
69
pkg/api/handlers/utils/apiutil/apiutil.go
Normal file
@ -0,0 +1,69 @@
|
||||
package apiutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/containers/podman/v4/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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 {
|
||||
split := strings.Split(r.URL.String(), "/")
|
||||
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, fmt.Errorf("unable to unescape given API version: %q: %w", val, err)
|
||||
}
|
||||
version, err = semver.ParseTolerant(safeVal)
|
||||
if err != nil {
|
||||
return version, fmt.Errorf("unable to parse given API version: %q from %q: %w", safeVal, val, err)
|
||||
}
|
||||
|
||||
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 := version.Compat
|
||||
if IsLibpodRequest(r) {
|
||||
tree = version.Libpod
|
||||
}
|
||||
|
||||
return SupportedVersion(r,
|
||||
fmt.Sprintf(">=%s <=%s", version.APIVersion[tree][version.MinimalAPI].String(),
|
||||
version.APIVersion[tree][version.CurrentAPI].String()))
|
||||
}
|
140
pkg/api/handlers/utils/apiutil/apiutil_test.go
Normal file
140
pkg/api/handlers/utils/apiutil/apiutil_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
package apiutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v4/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func TestSupportedVersion(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
fmt.Sprintf("/v%s/libpod/testing/versions", version.APIVersion[version.Libpod][version.CurrentAPI]),
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = mux.SetURLVars(req, map[string]string{"version": version.APIVersion[version.Libpod][version.CurrentAPI].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(http.MethodGet,
|
||||
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(http.MethodGet,
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,79 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/containers/podman/v4/version"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/schema"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/containers/podman/v4/pkg/api/handlers/utils/apiutil"
|
||||
api "github.com/containers/podman/v4/pkg/api/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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 {
|
||||
split := strings.Split(r.URL.String(), "/")
|
||||
return len(split) >= 3 && split[2] == "libpod"
|
||||
return apiutil.IsLibpodRequest(r)
|
||||
}
|
||||
|
||||
// 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, fmt.Errorf("unable to unescape given API version: %q: %w", val, err)
|
||||
}
|
||||
version, err = semver.ParseTolerant(safeVal)
|
||||
if err != nil {
|
||||
return version, fmt.Errorf("unable to parse given API version: %q from %q: %w", safeVal, val, err)
|
||||
}
|
||||
|
||||
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 := version.Compat
|
||||
if IsLibpodRequest(r) {
|
||||
tree = version.Libpod
|
||||
}
|
||||
|
||||
return SupportedVersion(r,
|
||||
fmt.Sprintf(">=%s <=%s", version.APIVersion[tree][version.MinimalAPI].String(),
|
||||
version.APIVersion[tree][version.CurrentAPI].String()))
|
||||
return apiutil.SupportedVersion(r, condition)
|
||||
}
|
||||
|
||||
// WriteResponse encodes the given value as JSON or string and renders it for http client
|
||||
|
@ -1,144 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v4/version"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func TestSupportedVersion(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet,
|
||||
fmt.Sprintf("/v%s/libpod/testing/versions", version.APIVersion[version.Libpod][version.CurrentAPI]),
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = mux.SetURLVars(req, map[string]string{"version": version.APIVersion[version.Libpod][version.CurrentAPI].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(http.MethodGet,
|
||||
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(http.MethodGet,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorEncoderFuncOmit(t *testing.T) {
|
||||
data, err := json.Marshal(struct {
|
||||
Err error `json:"err,omitempty"`
|
||||
|
Reference in New Issue
Block a user