mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-10 05:30:26 +00:00
Can't replace a generic resource that is cluster scoped
It should be allowed to invoke kubectl replace with a JSON file that has no resource version set. Namespaced resources were working correctly, but cluster resources were silently failing to lookup the current state of the object to get the resource version because we weren't using NamespaceIfScoped(). Added a failing test.
This commit is contained in:
@@ -494,6 +494,31 @@ runTests() {
|
|||||||
#cleaning
|
#cleaning
|
||||||
rm /tmp/tmp-valid-pod.json
|
rm /tmp/tmp-valid-pod.json
|
||||||
|
|
||||||
|
## replace of a cluster scoped resource can succeed
|
||||||
|
# Pre-condition: a node exists
|
||||||
|
kubectl create -f - "${kube_flags[@]}" << __EOF__
|
||||||
|
{
|
||||||
|
"kind": "Node",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "node-${version}-test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__EOF__
|
||||||
|
kubectl replace -f - "${kube_flags[@]}" << __EOF__
|
||||||
|
{
|
||||||
|
"kind": "Node",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "node-${version}-test",
|
||||||
|
"annotations": {"a":"b"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__EOF__
|
||||||
|
# Post-condition: the node command succeeds
|
||||||
|
kube::test::get_object_assert "node node-${version}-test" "{{.metadata.annotations.a}}" 'b'
|
||||||
|
kubectl delete node node-${version}-test
|
||||||
|
|
||||||
## kubectl edit can update the image field of a POD. tmp-editor.sh is a fake editor
|
## kubectl edit can update the image field of a POD. tmp-editor.sh is a fake editor
|
||||||
echo -e '#!/bin/bash\nsed -i "s/nginx/gcr.io\/google_containers\/serve_hostname/g" $1' > /tmp/tmp-editor.sh
|
echo -e '#!/bin/bash\nsed -i "s/nginx/gcr.io\/google_containers\/serve_hostname/g" $1' > /tmp/tmp-editor.sh
|
||||||
chmod +x /tmp/tmp-editor.sh
|
chmod +x /tmp/tmp-editor.sh
|
||||||
|
@@ -144,7 +144,7 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
|
|||||||
}
|
}
|
||||||
if version == "" && overwrite {
|
if version == "" && overwrite {
|
||||||
// Retrieve the current version of the object to overwrite the server object
|
// Retrieve the current version of the object to overwrite the server object
|
||||||
serverObj, err := c.Get().Namespace(namespace).Resource(m.Resource).Name(name).Do().Get()
|
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The object does not exist, but we want it to be created
|
// The object does not exist, but we want it to be created
|
||||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||||
|
@@ -357,18 +357,13 @@ func TestHelperList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHelperReplace(t *testing.T) {
|
func TestHelperReplace(t *testing.T) {
|
||||||
expectPut := func(req *http.Request) bool {
|
expectPut := func(path string, req *http.Request) bool {
|
||||||
if req.Method != "PUT" {
|
if req.Method != "PUT" {
|
||||||
t.Errorf("unexpected method: %#v", req)
|
t.Errorf("unexpected method: %#v", req)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
parts := splitPath(req.URL.Path)
|
if req.URL.Path != path {
|
||||||
if parts[1] != "bar" {
|
t.Errorf("unexpected url: %v", req.URL)
|
||||||
t.Errorf("url doesn't contain namespace: %#v", req.URL)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if parts[2] != "foo" {
|
|
||||||
t.Errorf("url doesn't contain name: %#v", req)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -380,16 +375,23 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
HttpErr error
|
HttpErr error
|
||||||
Overwrite bool
|
Overwrite bool
|
||||||
Object runtime.Object
|
Object runtime.Object
|
||||||
|
Namespace string
|
||||||
|
NamespaceScoped bool
|
||||||
|
|
||||||
|
ExpectPath string
|
||||||
ExpectObject runtime.Object
|
ExpectObject runtime.Object
|
||||||
Err bool
|
Err bool
|
||||||
Req func(*http.Request) bool
|
Req func(string, *http.Request) bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
Namespace: "bar",
|
||||||
|
NamespaceScoped: true,
|
||||||
HttpErr: errors.New("failure"),
|
HttpErr: errors.New("failure"),
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Namespace: "bar",
|
||||||
|
NamespaceScoped: true,
|
||||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
Resp: &http.Response{
|
Resp: &http.Response{
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
@@ -398,7 +400,10 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Namespace: "bar",
|
||||||
|
NamespaceScoped: true,
|
||||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
|
ExpectPath: "/namespaces/bar/foo",
|
||||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||||
Resp: &http.Response{
|
Resp: &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
@@ -406,11 +411,15 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Req: expectPut,
|
Req: expectPut,
|
||||||
},
|
},
|
||||||
|
// namespace scoped resource
|
||||||
{
|
{
|
||||||
|
Namespace: "bar",
|
||||||
|
NamespaceScoped: true,
|
||||||
Object: &api.Pod{
|
Object: &api.Pod{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||||
},
|
},
|
||||||
|
ExpectPath: "/namespaces/bar/foo",
|
||||||
ExpectObject: &api.Pod{
|
ExpectObject: &api.Pod{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||||
@@ -424,8 +433,29 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
Req: expectPut,
|
Req: expectPut,
|
||||||
},
|
},
|
||||||
|
// cluster scoped resource
|
||||||
{
|
{
|
||||||
|
Object: &api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||||
|
},
|
||||||
|
ExpectObject: &api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||||
|
},
|
||||||
|
Overwrite: true,
|
||||||
|
ExpectPath: "/foo",
|
||||||
|
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.Method == "PUT" {
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, nil
|
||||||
|
}
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Node{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||||
|
}),
|
||||||
|
Req: expectPut,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Namespace: "bar",
|
||||||
|
NamespaceScoped: true,
|
||||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||||
|
ExpectPath: "/namespaces/bar/foo",
|
||||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||||
Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})},
|
Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})},
|
||||||
Req: expectPut,
|
Req: expectPut,
|
||||||
@@ -441,23 +471,22 @@ func TestHelperReplace(t *testing.T) {
|
|||||||
modifier := &Helper{
|
modifier := &Helper{
|
||||||
RESTClient: client,
|
RESTClient: client,
|
||||||
Versioner: testapi.Default.MetadataAccessor(),
|
Versioner: testapi.Default.MetadataAccessor(),
|
||||||
NamespaceScoped: true,
|
NamespaceScoped: test.NamespaceScoped,
|
||||||
}
|
}
|
||||||
_, err := modifier.Replace("bar", "foo", test.Overwrite, test.Object)
|
_, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object)
|
||||||
if (err != nil) != test.Err {
|
if (err != nil) != test.Err {
|
||||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if test.Req != nil && !test.Req(client.Req) {
|
if test.Req != nil && !test.Req(test.ExpectPath, client.Req) {
|
||||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(client.Req.Body)
|
body, err := ioutil.ReadAll(client.Req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||||
}
|
}
|
||||||
t.Logf("got body: %s", string(body))
|
|
||||||
expect := []byte{}
|
expect := []byte{}
|
||||||
if test.ExpectObject != nil {
|
if test.ExpectObject != nil {
|
||||||
expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
|
expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
|
||||||
|
Reference in New Issue
Block a user