mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
apiserver: Fail if dry-run query param is specified
This commit is contained in:
parent
f13fa1e3af
commit
98613924ea
@ -875,6 +875,71 @@ func TestUnimplementedRESTStorage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnlyGetRESTStorage struct {
|
||||||
|
UnimplementedRESTStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OnlyGetRESTStorage) Get(ctx context.Context, id string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OnlyGetRESTStorage) NewList() runtime.Object {
|
||||||
|
return &genericapitesting.SimpleList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (OnlyGetRESTStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSomeUnimplementedRESTStorage ensures that if a rest.Storage does
|
||||||
|
// not implement a given method, that it is literally not registered
|
||||||
|
// with the server. We need to have at least one verb supported inorder
|
||||||
|
// to get a MethodNotAllowed rather than NotFound error.
|
||||||
|
func TestSomeUnimplementedRESTStorage(t *testing.T) {
|
||||||
|
type T struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
ErrCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]T{
|
||||||
|
"groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
|
||||||
|
"groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
|
||||||
|
"groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
|
||||||
|
"groupless DELETE collection": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
|
||||||
|
"POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
|
||||||
|
"PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
|
||||||
|
"DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", http.StatusMethodNotAllowed},
|
||||||
|
"DELETE collection": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo", http.StatusMethodNotAllowed},
|
||||||
|
}
|
||||||
|
handler := handle(map[string]rest.Storage{
|
||||||
|
"foo": OnlyGetRESTStorage{},
|
||||||
|
})
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
defer server.Close()
|
||||||
|
client := http.Client{}
|
||||||
|
for k, v := range cases {
|
||||||
|
request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
data, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if response.StatusCode != v.ErrCode {
|
||||||
|
t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
url string
|
url string
|
||||||
@ -3767,6 +3832,92 @@ func TestUpdateChecksAPIVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runRequest is used by TestDryRun since it runs the test twice in a
|
||||||
|
// row with a slightly different URL (one has ?dryRun, one doesn't).
|
||||||
|
func runRequest(t *testing.T, path, verb string, data []byte, contentType string) *http.Response {
|
||||||
|
request, err := http.NewRequest(verb, path, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if contentType != "" {
|
||||||
|
request.Header.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
response, err := http.DefaultClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeOrFatal is used by TestDryRun to parse an object and stop right
|
||||||
|
// away if it fails.
|
||||||
|
func encodeOrFatal(t *testing.T, obj runtime.Object) []byte {
|
||||||
|
data, err := runtime.Encode(testCodec, obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleRESTStorageWithDeleteCollection struct {
|
||||||
|
SimpleRESTStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete collection doesn't do much, but let us test this path.
|
||||||
|
func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
storage.checkContext(ctx)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDryRun(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
verb string
|
||||||
|
data []byte
|
||||||
|
contentType string
|
||||||
|
}{
|
||||||
|
{path: "/namespaces/default/simples", verb: "POST", data: encodeOrFatal(t, &genericapitesting.Simple{Other: "bar"})},
|
||||||
|
{path: "/namespaces/default/simples/id", verb: "PUT", data: encodeOrFatal(t, &genericapitesting.Simple{ObjectMeta: metav1.ObjectMeta{Name: "id"}, Other: "bar"})},
|
||||||
|
{path: "/namespaces/default/simples/id", verb: "PATCH", data: []byte(`{"labels":{"foo":"bar"}}`), contentType: "application/merge-patch+json; charset=UTF-8"},
|
||||||
|
{path: "/namespaces/default/simples/id", verb: "DELETE"},
|
||||||
|
{path: "/namespaces/default/simples", verb: "DELETE"},
|
||||||
|
{path: "/namespaces/default/simples/id/subsimple", verb: "DELETE"},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewServer(handle(map[string]rest.Storage{
|
||||||
|
"simples": &SimpleRESTStorageWithDeleteCollection{
|
||||||
|
SimpleRESTStorage{
|
||||||
|
item: genericapitesting.Simple{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "id",
|
||||||
|
Namespace: "",
|
||||||
|
UID: "uid",
|
||||||
|
},
|
||||||
|
Other: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"simples/subsimple": &SimpleXGSubresourceRESTStorage{
|
||||||
|
item: genericapitesting.SimpleXGSubresource{
|
||||||
|
SubresourceInfo: "foo",
|
||||||
|
},
|
||||||
|
itemGVK: testGroup2Version.WithKind("SimpleXGSubresource"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
for _, test := range tests {
|
||||||
|
baseUrl := server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version
|
||||||
|
response := runRequest(t, baseUrl+test.path, test.verb, test.data, test.contentType)
|
||||||
|
if response.StatusCode == http.StatusBadRequest {
|
||||||
|
t.Fatalf("unexpected BadRequest: %#v", response)
|
||||||
|
}
|
||||||
|
response = runRequest(t, baseUrl+test.path+"?dryRun", test.verb, test.data, test.contentType)
|
||||||
|
if response.StatusCode != http.StatusBadRequest {
|
||||||
|
t.Fatalf("unexpected non BadRequest: %#v", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleXGSubresourceRESTStorage struct {
|
type SimpleXGSubresourceRESTStorage struct {
|
||||||
item genericapitesting.SimpleXGSubresource
|
item genericapitesting.SimpleXGSubresource
|
||||||
itemGVK schema.GroupVersionKind
|
itemGVK schema.GroupVersionKind
|
||||||
@ -3782,6 +3933,10 @@ func (storage *SimpleXGSubresourceRESTStorage) Get(ctx context.Context, id strin
|
|||||||
return storage.item.DeepCopyObject(), nil
|
return storage.item.DeepCopyObject(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
|
func (storage *SimpleXGSubresourceRESTStorage) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind {
|
||||||
return storage.itemGVK
|
return storage.itemGVK
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,11 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
|||||||
trace := utiltrace.New("Create " + req.URL.Path)
|
trace := utiltrace.New("Create " + req.URL.Path)
|
||||||
defer trace.LogIfLong(500 * time.Millisecond)
|
defer trace.LogIfLong(500 * time.Millisecond)
|
||||||
|
|
||||||
|
if isDryRun(req.URL) {
|
||||||
|
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
@ -41,6 +41,11 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||||||
trace := utiltrace.New("Delete " + req.URL.Path)
|
trace := utiltrace.New("Delete " + req.URL.Path)
|
||||||
defer trace.LogIfLong(500 * time.Millisecond)
|
defer trace.LogIfLong(500 * time.Millisecond)
|
||||||
|
|
||||||
|
if isDryRun(req.URL) {
|
||||||
|
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
@ -167,6 +172,11 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope RequestSco
|
|||||||
// DeleteCollection returns a function that will handle a collection deletion
|
// DeleteCollection returns a function that will handle a collection deletion
|
||||||
func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if isDryRun(req.URL) {
|
||||||
|
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
@ -48,6 +48,11 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
|||||||
trace := utiltrace.New("Patch " + req.URL.Path)
|
trace := utiltrace.New("Patch " + req.URL.Path)
|
||||||
defer trace.LogIfLong(500 * time.Millisecond)
|
defer trace.LogIfLong(500 * time.Millisecond)
|
||||||
|
|
||||||
|
if isDryRun(req.URL) {
|
||||||
|
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Do this first, otherwise name extraction can fail for unrecognized content types
|
// Do this first, otherwise name extraction can fail for unrecognized content types
|
||||||
// TODO: handle this in negotiation
|
// TODO: handle this in negotiation
|
||||||
contentType := req.Header.Get("Content-Type")
|
contentType := req.Header.Get("Content-Type")
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -318,3 +319,7 @@ func parseTimeout(str string) time.Duration {
|
|||||||
}
|
}
|
||||||
return 30 * time.Second
|
return 30 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDryRun(url *url.URL) bool {
|
||||||
|
return len(url.Query()["dryRun"]) != 0
|
||||||
|
}
|
||||||
|
@ -40,6 +40,11 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
|||||||
trace := utiltrace.New("Update " + req.URL.Path)
|
trace := utiltrace.New("Update " + req.URL.Path)
|
||||||
defer trace.LogIfLong(500 * time.Millisecond)
|
defer trace.LogIfLong(500 * time.Millisecond)
|
||||||
|
|
||||||
|
if isDryRun(req.URL) {
|
||||||
|
scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
|
||||||
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
timeout := parseTimeout(req.URL.Query().Get("timeout"))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user