Commit 3e169be8 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #35647 from ymqytw/patch_primitive_list

Automatic merge from submit-queue Fix strategic patch for list of primitive type with merge sementic Fix strategic patch for list of primitive type when the patch strategy is `merge`. Before: we cannot replace or delete an item in a list of primitive, e.g. string, when the patch strategy is `merge`. It will always append new items to the list. This patch will generate a map to update the list of primitive type. The server with this patch will accept either a new patch or an old patch. The client will found out the APIserver version before generate the patch. Fixes #35163, #32398 cc: @pwittrock @fabianofranz ``` release-note Fix strategic patch for list of primitive type when patch strategy is `merge` to remove deleted objects. ```
parents 35094195 34891ad9
...@@ -591,11 +591,11 @@ func patchResource( ...@@ -591,11 +591,11 @@ func patchResource(
if err != nil { if err != nil {
return nil, err return nil, err
} }
currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj) currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj, strategicpatch.SMPatchVersionLatest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj, strategicpatch.SMPatchVersionLatest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -213,7 +213,7 @@ func (tc *patchTestCase) Run(t *testing.T) { ...@@ -213,7 +213,7 @@ func (tc *patchTestCase) Run(t *testing.T) {
continue continue
case api.StrategicMergePatchType: case api.StrategicMergePatchType:
patch, err = strategicpatch.CreateStrategicMergePatch(originalObjJS, changedJS, versionedObj) patch, err = strategicpatch.CreateStrategicMergePatch(originalObjJS, changedJS, versionedObj, strategicpatch.SMPatchVersionLatest)
if err != nil { if err != nil {
t.Errorf("%s: unexpected error: %v", tc.name, err) t.Errorf("%s: unexpected error: %v", tc.name, err)
return return
......
...@@ -244,7 +244,9 @@ func (e *eventLogger) eventObserve(newEvent *api.Event) (*api.Event, []byte, err ...@@ -244,7 +244,9 @@ func (e *eventLogger) eventObserve(newEvent *api.Event) (*api.Event, []byte, err
newData, _ := json.Marshal(event) newData, _ := json.Marshal(event)
oldData, _ := json.Marshal(eventCopy2) oldData, _ := json.Marshal(eventCopy2)
patch, err = strategicpatch.CreateStrategicMergePatch(oldData, newData, event) // TODO: need to figure out if we need to let eventObserve() use the new behavior of StrategicMergePatch.
// Currently default to old behavior now. Ref: issue #35936
patch, err = strategicpatch.CreateStrategicMergePatch(oldData, newData, event, strategicpatch.SMPatchVersion_1_0)
} }
// record our new observation // record our new observation
......
...@@ -59,6 +59,10 @@ type nodeStatusUpdater struct { ...@@ -59,6 +59,10 @@ type nodeStatusUpdater struct {
} }
func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error { func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error {
smPatchVersion, err := strategicpatch.GetServerSupportedSMPatchVersion(nsu.kubeClient.Discovery())
if err != nil {
return err
}
nodesToUpdate := nsu.actualStateOfWorld.GetVolumesToReportAttached() nodesToUpdate := nsu.actualStateOfWorld.GetVolumesToReportAttached()
for nodeName, attachedVolumes := range nodesToUpdate { for nodeName, attachedVolumes := range nodesToUpdate {
nodeObj, exists, err := nsu.nodeInformer.GetStore().GetByKey(string(nodeName)) nodeObj, exists, err := nsu.nodeInformer.GetStore().GetByKey(string(nodeName))
...@@ -108,7 +112,7 @@ func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error { ...@@ -108,7 +112,7 @@ func (nsu *nodeStatusUpdater) UpdateNodeStatuses() error {
} }
patchBytes, err := patchBytes, err :=
strategicpatch.CreateStrategicMergePatch(oldData, newData, node) strategicpatch.CreateStrategicMergePatch(oldData, newData, node, smPatchVersion)
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"failed to CreateStrategicMergePatch for node %q. %v", "failed to CreateStrategicMergePatch for node %q. %v",
......
...@@ -193,6 +193,7 @@ go_test( ...@@ -193,6 +193,7 @@ go_test(
"//pkg/util/strings:go_default_library", "//pkg/util/strings:go_default_library",
"//pkg/util/term:go_default_library", "//pkg/util/term:go_default_library",
"//pkg/util/wait:go_default_library", "//pkg/util/wait:go_default_library",
"//pkg/version:go_default_library",
"//pkg/watch:go_default_library", "//pkg/watch:go_default_library",
"//pkg/watch/versioned:go_default_library", "//pkg/watch/versioned:go_default_library",
"//vendor:github.com/spf13/cobra", "//vendor:github.com/spf13/cobra",
......
...@@ -223,6 +223,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro ...@@ -223,6 +223,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
} }
outputObj = obj outputObj = obj
} else { } else {
// retrieves server version to determine which SMPatchVersion to use.
smPatchVersion, err := cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
name, namespace := info.Name, info.Namespace name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj) oldData, err := json.Marshal(obj)
if err != nil { if err != nil {
...@@ -239,7 +245,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro ...@@ -239,7 +245,7 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
if err != nil { if err != nil {
return err return err
} }
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj, smPatchVersion)
createdPatch := err == nil createdPatch := err == nil
if err != nil { if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err) glog.V(2).Infof("couldn't compute patch: %v", err)
......
...@@ -24,8 +24,6 @@ import ( ...@@ -24,8 +24,6 @@ import (
"testing" "testing"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/restclient/fake" "k8s.io/kubernetes/pkg/client/restclient/fake"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
...@@ -394,7 +392,7 @@ func TestAnnotateErrors(t *testing.T) { ...@@ -394,7 +392,7 @@ func TestAnnotateErrors(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf) cmd := NewCmdAnnotate(f, buf)
...@@ -432,6 +430,12 @@ func TestAnnotateObject(t *testing.T) { ...@@ -432,6 +430,12 @@ func TestAnnotateObject(t *testing.T) {
switch req.Method { switch req.Method {
case "GET": case "GET":
switch req.URL.Path { switch req.URL.Path {
case "/version":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case "/namespaces/test/pods/foo": case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default: default:
...@@ -453,7 +457,7 @@ func TestAnnotateObject(t *testing.T) { ...@@ -453,7 +457,7 @@ func TestAnnotateObject(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf) cmd := NewCmdAnnotate(f, buf)
...@@ -482,6 +486,12 @@ func TestAnnotateObjectFromFile(t *testing.T) { ...@@ -482,6 +486,12 @@ func TestAnnotateObjectFromFile(t *testing.T) {
switch req.Method { switch req.Method {
case "GET": case "GET":
switch req.URL.Path { switch req.URL.Path {
case "/version":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case "/namespaces/test/replicationcontrollers/cassandra": case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default: default:
...@@ -503,7 +513,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { ...@@ -503,7 +513,7 @@ func TestAnnotateObjectFromFile(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf) cmd := NewCmdAnnotate(f, buf)
...@@ -532,7 +542,7 @@ func TestAnnotateLocal(t *testing.T) { ...@@ -532,7 +542,7 @@ func TestAnnotateLocal(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf) cmd := NewCmdAnnotate(f, buf)
...@@ -562,6 +572,12 @@ func TestAnnotateMultipleObjects(t *testing.T) { ...@@ -562,6 +572,12 @@ func TestAnnotateMultipleObjects(t *testing.T) {
switch req.Method { switch req.Method {
case "GET": case "GET":
switch req.URL.Path { switch req.URL.Path {
case "/version":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case "/namespaces/test/pods": case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
default: default:
...@@ -585,7 +601,7 @@ func TestAnnotateMultipleObjects(t *testing.T) { ...@@ -585,7 +601,7 @@ func TestAnnotateMultipleObjects(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf) cmd := NewCmdAnnotate(f, buf)
......
...@@ -195,6 +195,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App ...@@ -195,6 +195,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
visitedUids := sets.NewString() visitedUids := sets.NewString()
visitedNamespaces := sets.NewString() visitedNamespaces := sets.NewString()
smPatchVersion, err := cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
count := 0 count := 0
err = r.Visit(func(info *resource.Info, err error) error { err = r.Visit(func(info *resource.Info, err error) error {
// In this method, info.Object contains the object retrieved from the server // In this method, info.Object contains the object retrieved from the server
...@@ -265,13 +270,13 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App ...@@ -265,13 +270,13 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *App
gracePeriod: options.GracePeriod, gracePeriod: options.GracePeriod,
} }
patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name) patchBytes, err := patcher.patch(info.Object, modified, info.Source, info.Namespace, info.Name, smPatchVersion)
if err != nil { if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err) return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patchBytes, info), info.Source, err)
} }
if cmdutil.ShouldRecord(cmd, info) { if cmdutil.ShouldRecord(cmd, info) {
patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) patch, err := cmdutil.ChangeResourcePatch(info, f.Command(), smPatchVersion)
if err != nil { if err != nil {
return err return err
} }
...@@ -507,7 +512,7 @@ type patcher struct { ...@@ -507,7 +512,7 @@ type patcher struct {
gracePeriod int gracePeriod int
} }
func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) { func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, namespace, name string, smPatchVersion strategicpatch.StrategicMergePatchVersion) ([]byte, error) {
// Serialize the current configuration of the object from the server. // Serialize the current configuration of the object from the server.
current, err := runtime.Encode(p.encoder, obj) current, err := runtime.Encode(p.encoder, obj)
if err != nil { if err != nil {
...@@ -531,7 +536,8 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -531,7 +536,8 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
} }
// Compute a three way strategic merge patch to send to server. // Compute a three way strategic merge patch to send to server.
patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite) patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, versionedObject, p.overwrite, smPatchVersion)
if err != nil { if err != nil {
format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:" format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
return nil, cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current), source, err) return nil, cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current), source, err)
...@@ -541,9 +547,9 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names ...@@ -541,9 +547,9 @@ func (p *patcher) patchSimple(obj runtime.Object, modified []byte, source, names
return patch, err return patch, err
} }
func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string) ([]byte, error) { func (p *patcher) patch(current runtime.Object, modified []byte, source, namespace, name string, smPatchVersion strategicpatch.StrategicMergePatchVersion) ([]byte, error) {
var getErr error var getErr error
patchBytes, err := p.patchSimple(current, modified, source, namespace, name) patchBytes, err := p.patchSimple(current, modified, source, namespace, name, smPatchVersion)
for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ { for i := 1; i <= maxPatchRetry && errors.IsConflict(err); i++ {
if i > triesBeforeBackOff { if i > triesBeforeBackOff {
p.backOff.Sleep(backOffPeriod) p.backOff.Sleep(backOffPeriod)
...@@ -552,7 +558,7 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa ...@@ -552,7 +558,7 @@ func (p *patcher) patch(current runtime.Object, modified []byte, source, namespa
if getErr != nil { if getErr != nil {
return nil, getErr return nil, getErr
} }
patchBytes, err = p.patchSimple(current, modified, source, namespace, name) patchBytes, err = p.patchSimple(current, modified, source, namespace, name, smPatchVersion)
} }
if err != nil && p.force { if err != nil && p.force {
patchBytes, err = p.deleteAndCreate(modified, namespace, name) patchBytes, err = p.deleteAndCreate(modified, namespace, name)
......
...@@ -188,6 +188,12 @@ func TestApplyObject(t *testing.T) { ...@@ -188,6 +188,12 @@ func TestApplyObject(t *testing.T) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == pathRC && m == "GET": case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
...@@ -202,6 +208,7 @@ func TestApplyObject(t *testing.T) { ...@@ -202,6 +208,7 @@ func TestApplyObject(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf) cmd := NewCmdApply(f, buf)
...@@ -230,6 +237,12 @@ func TestApplyRetry(t *testing.T) { ...@@ -230,6 +237,12 @@ func TestApplyRetry(t *testing.T) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == pathRC && m == "GET": case p == pathRC && m == "GET":
getCount++ getCount++
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
...@@ -253,6 +266,7 @@ func TestApplyRetry(t *testing.T) { ...@@ -253,6 +266,7 @@ func TestApplyRetry(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf) cmd := NewCmdApply(f, buf)
...@@ -282,6 +296,12 @@ func TestApplyNonExistObject(t *testing.T) { ...@@ -282,6 +296,12 @@ func TestApplyNonExistObject(t *testing.T) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == "/api/v1/namespaces/test" && m == "GET": case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
case p == pathNameRC && m == "GET": case p == pathNameRC && m == "GET":
...@@ -296,6 +316,7 @@ func TestApplyNonExistObject(t *testing.T) { ...@@ -296,6 +316,7 @@ func TestApplyNonExistObject(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf) cmd := NewCmdApply(f, buf)
...@@ -331,6 +352,12 @@ func testApplyMultipleObjects(t *testing.T, asList bool) { ...@@ -331,6 +352,12 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == pathRC && m == "GET": case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
...@@ -352,6 +379,7 @@ func testApplyMultipleObjects(t *testing.T, asList bool) { ...@@ -352,6 +379,7 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdApply(f, buf) cmd := NewCmdApply(f, buf)
......
...@@ -39,8 +39,13 @@ import ( ...@@ -39,8 +39,13 @@ import (
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/strings" "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/version"
) )
var serverVersion_1_5_0 = version.Info{
GitVersion: "v1.5.0",
}
func initTestErrorHandler(t *testing.T) { func initTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) { cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str) t.Errorf("Error running command (exit code %d): %s", code, str)
......
...@@ -281,7 +281,7 @@ func runEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args ...@@ -281,7 +281,7 @@ func runEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
switch editMode { switch editMode {
case NormalEditMode: case NormalEditMode:
err = visitToPatch(originalObj, updates, mapper, resourceMapper, encoder, out, errOut, defaultVersion, &results, file) err = visitToPatch(originalObj, updates, f, mapper, resourceMapper, encoder, out, errOut, defaultVersion, &results, file)
case EditBeforeCreateMode: case EditBeforeCreateMode:
err = visitToCreate(updates, mapper, resourceMapper, out, errOut, defaultVersion, &results, file) err = visitToCreate(updates, mapper, resourceMapper, out, errOut, defaultVersion, &results, file)
default: default:
...@@ -393,9 +393,22 @@ func getMapperAndResult(f cmdutil.Factory, args []string, options *resource.File ...@@ -393,9 +393,22 @@ func getMapperAndResult(f cmdutil.Factory, args []string, options *resource.File
return mapper, resourceMapper, r, cmdNamespace, err return mapper, resourceMapper, r, cmdNamespace, err
} }
func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper meta.RESTMapper, resourceMapper *resource.Mapper, encoder runtime.Encoder, out, errOut io.Writer, defaultVersion unversioned.GroupVersion, results *editResults, file string) error { func visitToPatch(originalObj runtime.Object, updates *resource.Info,
f cmdutil.Factory,
mapper meta.RESTMapper, resourceMapper *resource.Mapper,
encoder runtime.Encoder,
out, errOut io.Writer,
defaultVersion unversioned.GroupVersion,
results *editResults,
file string) error {
smPatchVersion, err := cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper) patchVisitor := resource.NewFlattenListVisitor(updates, resourceMapper)
err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error { err = patchVisitor.Visit(func(info *resource.Info, incomingErr error) error {
currOriginalObj := originalObj currOriginalObj := originalObj
// if we're editing a list, then navigate the list to find the item that we're currently trying to edit // if we're editing a list, then navigate the list to find the item that we're currently trying to edit
...@@ -456,7 +469,7 @@ func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper met ...@@ -456,7 +469,7 @@ func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper met
preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"), preconditions := []strategicpatch.PreconditionFunc{strategicpatch.RequireKeyUnchanged("apiVersion"),
strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")} strategicpatch.RequireKeyUnchanged("kind"), strategicpatch.RequireMetadataKeyUnchanged("name")}
patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, preconditions...) patch, err := strategicpatch.CreateTwoWayMergePatch(originalJS, editedJS, currOriginalObj, smPatchVersion, preconditions...)
if err != nil { if err != nil {
glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err)
if strategicpatch.IsPreconditionFailed(err) { if strategicpatch.IsPreconditionFailed(err) {
......
...@@ -192,6 +192,14 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -192,6 +192,14 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
return err return err
} }
smPatchVersion := strategicpatch.SMPatchVersionLatest
if !o.local {
smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
}
// only apply resource version locking on a single resource // only apply resource version locking on a single resource
if !one && len(o.resourceVersion) > 0 { if !one && len(o.resourceVersion) > 0 {
return fmt.Errorf("--resource-version may only be used with a single resource") return fmt.Errorf("--resource-version may only be used with a single resource")
...@@ -246,7 +254,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -246,7 +254,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
if !reflect.DeepEqual(oldData, newData) { if !reflect.DeepEqual(oldData, newData) {
dataChangeMsg = "labeled" dataChangeMsg = "labeled"
} }
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj, smPatchVersion)
createdPatch := err == nil createdPatch := err == nil
if err != nil { if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err) glog.V(2).Infof("couldn't compute patch: %v", err)
......
...@@ -354,6 +354,12 @@ func TestLabelForResourceFromFile(t *testing.T) { ...@@ -354,6 +354,12 @@ func TestLabelForResourceFromFile(t *testing.T) {
switch req.Method { switch req.Method {
case "GET": case "GET":
switch req.URL.Path { switch req.URL.Path {
case "/version":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case "/namespaces/test/replicationcontrollers/cassandra": case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default: default:
...@@ -375,7 +381,7 @@ func TestLabelForResourceFromFile(t *testing.T) { ...@@ -375,7 +381,7 @@ func TestLabelForResourceFromFile(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf) cmd := NewCmdLabel(f, buf)
...@@ -437,6 +443,12 @@ func TestLabelMultipleObjects(t *testing.T) { ...@@ -437,6 +443,12 @@ func TestLabelMultipleObjects(t *testing.T) {
switch req.Method { switch req.Method {
case "GET": case "GET":
switch req.URL.Path { switch req.URL.Path {
case "/version":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case "/namespaces/test/pods": case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
default: default:
...@@ -460,7 +472,7 @@ func TestLabelMultipleObjects(t *testing.T) { ...@@ -460,7 +472,7 @@ func TestLabelMultipleObjects(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion}} tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdLabel(f, buf) cmd := NewCmdLabel(f, buf)
......
...@@ -154,6 +154,14 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -154,6 +154,14 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err return err
} }
smPatchVersion := strategicpatch.SMPatchVersionLatest
if !options.Local {
smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
}
count := 0 count := 0
err = r.Visit(func(info *resource.Info, err error) error { err = r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
...@@ -177,7 +185,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -177,7 +185,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
// also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this
// record hint is likely to be invalid anyway, so avoid the bad hint // record hint is likely to be invalid anyway, so avoid the bad hint
patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) patch, err := cmdutil.ChangeResourcePatch(info, f.Command(), smPatchVersion)
if err == nil { if err == nil {
helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
} }
......
...@@ -34,6 +34,12 @@ func TestPatchObject(t *testing.T) { ...@@ -34,6 +34,12 @@ func TestPatchObject(t *testing.T) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default: default:
...@@ -43,6 +49,7 @@ func TestPatchObject(t *testing.T) { ...@@ -43,6 +49,7 @@ func TestPatchObject(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdPatch(f, buf) cmd := NewCmdPatch(f, buf)
...@@ -66,6 +73,12 @@ func TestPatchObjectFromFile(t *testing.T) { ...@@ -66,6 +73,12 @@ func TestPatchObjectFromFile(t *testing.T) {
NegotiatedSerializer: ns, NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; { switch p, m := req.URL.Path, req.Method; {
case p == "/version" && m == "GET":
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
default: default:
...@@ -75,6 +88,7 @@ func TestPatchObjectFromFile(t *testing.T) { ...@@ -75,6 +88,7 @@ func TestPatchObjectFromFile(t *testing.T) {
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfig = defaultClientConfig()
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdPatch(f, buf) cmd := NewCmdPatch(f, buf)
......
...@@ -38,6 +38,7 @@ import ( ...@@ -38,6 +38,7 @@ import (
type PauseConfig struct { type PauseConfig struct {
resource.FilenameOptions resource.FilenameOptions
f cmdutil.Factory
Pauser func(info *resource.Info) (bool, error) Pauser func(info *resource.Info) (bool, error)
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
...@@ -99,6 +100,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i ...@@ -99,6 +100,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i
return cmdutil.UsageError(cmd, cmd.Use) return cmdutil.UsageError(cmd, cmd.Use)
} }
o.f = f
o.Mapper, o.Typer = f.Object() o.Mapper, o.Typer = f.Object()
o.Encoder = f.JSONEncoder() o.Encoder = f.JSONEncoder()
...@@ -132,7 +134,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i ...@@ -132,7 +134,7 @@ func (o *PauseConfig) CompletePause(f cmdutil.Factory, cmd *cobra.Command, out i
func (o PauseConfig) RunPause() error { func (o PauseConfig) RunPause() error {
allErrs := []error{} allErrs := []error{}
for _, patch := range set.CalculatePatches(o.Infos, o.Encoder, o.Pauser) { for _, patch := range set.CalculatePatches(o.f, o.Infos, o.Encoder, false, o.Pauser) {
info := patch.Info info := patch.Info
if patch.Err != nil { if patch.Err != nil {
allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", info.Mapping.Resource, info.Name, patch.Err)) allErrs = append(allErrs, fmt.Errorf("error: %s %q %v", info.Mapping.Resource, info.Name, patch.Err))
......
...@@ -38,6 +38,7 @@ import ( ...@@ -38,6 +38,7 @@ import (
type ResumeConfig struct { type ResumeConfig struct {
resource.FilenameOptions resource.FilenameOptions
f cmdutil.Factory
Resumer func(object *resource.Info) (bool, error) Resumer func(object *resource.Info) (bool, error)
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
...@@ -97,6 +98,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out ...@@ -97,6 +98,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out
return cmdutil.UsageError(cmd, cmd.Use) return cmdutil.UsageError(cmd, cmd.Use)
} }
o.f = f
o.Mapper, o.Typer = f.Object() o.Mapper, o.Typer = f.Object()
o.Encoder = f.JSONEncoder() o.Encoder = f.JSONEncoder()
...@@ -136,7 +138,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out ...@@ -136,7 +138,7 @@ func (o *ResumeConfig) CompleteResume(f cmdutil.Factory, cmd *cobra.Command, out
func (o ResumeConfig) RunResume() error { func (o ResumeConfig) RunResume() error {
allErrs := []error{} allErrs := []error{}
for _, patch := range set.CalculatePatches(o.Infos, o.Encoder, o.Resumer) { for _, patch := range set.CalculatePatches(o.f, o.Infos, o.Encoder, false, o.Resumer) {
info := patch.Info info := patch.Info
if patch.Err != nil { if patch.Err != nil {
......
...@@ -139,6 +139,11 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -139,6 +139,11 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return fmt.Errorf("cannot use --resource-version with multiple resources") return fmt.Errorf("cannot use --resource-version with multiple resources")
} }
smPatchVersion, err := cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return err
}
counter := 0 counter := 0
err = r.Visit(func(info *resource.Info, err error) error { err = r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
...@@ -164,7 +169,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -164,7 +169,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err return err
} }
if cmdutil.ShouldRecord(cmd, info) { if cmdutil.ShouldRecord(cmd, info) {
patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command()) patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command(), smPatchVersion)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -23,7 +23,7 @@ import ( ...@@ -23,7 +23,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/strategicpatch"
...@@ -61,7 +61,7 @@ func handlePodUpdateError(out io.Writer, err error, resource string) { ...@@ -61,7 +61,7 @@ func handlePodUpdateError(out io.Writer, err error, resource string) {
return return
} }
} else { } else {
if ok := kcmdutil.PrintErrorWithCauses(err, out); ok { if ok := cmdutil.PrintErrorWithCauses(err, out); ok {
return return
} }
} }
...@@ -120,8 +120,20 @@ type Patch struct { ...@@ -120,8 +120,20 @@ type Patch struct {
// CalculatePatches calls the mutation function on each provided info object, and generates a strategic merge patch for // CalculatePatches calls the mutation function on each provided info object, and generates a strategic merge patch for
// the changes in the object. Encoder must be able to encode the info into the appropriate destination type. If mutateFn // the changes in the object. Encoder must be able to encode the info into the appropriate destination type. If mutateFn
// returns false, the object is not included in the final list of patches. // returns false, the object is not included in the final list of patches.
func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn func(*resource.Info) (bool, error)) []*Patch { // If local is true, it will be default to use SMPatchVersionLatest to calculate a patch without contacting the server to
// get the server supported SMPatchVersion. If you are using a patch's Patch field generated in local mode, be careful.
// If local is false, it will talk to the server to check which StategicMergePatchVersion to use.
func CalculatePatches(f cmdutil.Factory, infos []*resource.Info, encoder runtime.Encoder, local bool, mutateFn func(*resource.Info) (bool, error)) []*Patch {
var patches []*Patch var patches []*Patch
smPatchVersion := strategicpatch.SMPatchVersionLatest
var err error
if !local {
smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(f)
if err != nil {
return patches
}
}
for _, info := range infos { for _, info := range infos {
patch := &Patch{Info: info} patch := &Patch{Info: info}
patch.Before, patch.Err = runtime.Encode(encoder, info.Object) patch.Before, patch.Err = runtime.Encode(encoder, info.Object)
...@@ -156,7 +168,7 @@ func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn ...@@ -156,7 +168,7 @@ func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn
continue continue
} }
patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned) patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned, smPatchVersion)
} }
return patches return patches
} }
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors" utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/strategicpatch"
) )
// ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of // ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
...@@ -35,6 +36,7 @@ import ( ...@@ -35,6 +36,7 @@ import (
type ImageOptions struct { type ImageOptions struct {
resource.FilenameOptions resource.FilenameOptions
f cmdutil.Factory
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
Infos []*resource.Info Infos []*resource.Info
...@@ -108,6 +110,7 @@ func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command { ...@@ -108,6 +110,7 @@ func NewCmdImage(f cmdutil.Factory, out, err io.Writer) *cobra.Command {
} }
func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.f = f
o.Mapper, o.Typer = f.Object() o.Mapper, o.Typer = f.Object()
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.Encoder = f.JSONEncoder() o.Encoder = f.JSONEncoder()
...@@ -162,7 +165,7 @@ func (o *ImageOptions) Validate() error { ...@@ -162,7 +165,7 @@ func (o *ImageOptions) Validate() error {
func (o *ImageOptions) Run() error { func (o *ImageOptions) Run() error {
allErrs := []error{} allErrs := []error{}
patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) (bool, error) { patches := CalculatePatches(o.f, o.Infos, o.Encoder, o.Local, func(info *resource.Info) (bool, error) {
transformed := false transformed := false
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error {
for name, image := range o.ContainerImages { for name, image := range o.ContainerImages {
...@@ -186,6 +189,14 @@ func (o *ImageOptions) Run() error { ...@@ -186,6 +189,14 @@ func (o *ImageOptions) Run() error {
return transformed, err return transformed, err
}) })
smPatchVersion := strategicpatch.SMPatchVersionLatest
var err error
if !o.Local {
smPatchVersion, err = cmdutil.GetServerSupportedSMPatchVersionFromFactory(o.f)
if err != nil {
return err
}
}
for _, patch := range patches { for _, patch := range patches {
info := patch.Info info := patch.Info
if patch.Err != nil { if patch.Err != nil {
...@@ -212,7 +223,7 @@ func (o *ImageOptions) Run() error { ...@@ -212,7 +223,7 @@ func (o *ImageOptions) Run() error {
// record this change (for rollout history) // record this change (for rollout history)
if o.Record || cmdutil.ContainsChangeCause(info) { if o.Record || cmdutil.ContainsChangeCause(info) {
if patch, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil { if patch, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause, smPatchVersion); err == nil {
if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch); err != nil { if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch); err != nil {
fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err) fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
} }
......
...@@ -60,6 +60,7 @@ var ( ...@@ -60,6 +60,7 @@ var (
type ResourcesOptions struct { type ResourcesOptions struct {
resource.FilenameOptions resource.FilenameOptions
f cmdutil.Factory
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
Infos []*resource.Info Infos []*resource.Info
...@@ -122,6 +123,7 @@ func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra. ...@@ -122,6 +123,7 @@ func NewCmdResources(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.
} }
func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.f = f
o.Mapper, o.Typer = f.Object() o.Mapper, o.Typer = f.Object()
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
o.Encoder = f.JSONEncoder() o.Encoder = f.JSONEncoder()
...@@ -172,7 +174,7 @@ func (o *ResourcesOptions) Validate() error { ...@@ -172,7 +174,7 @@ func (o *ResourcesOptions) Validate() error {
func (o *ResourcesOptions) Run() error { func (o *ResourcesOptions) Run() error {
allErrs := []error{} allErrs := []error{}
patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) (bool, error) { patches := CalculatePatches(o.f, o.Infos, o.Encoder, cmdutil.GetDryRunFlag(o.Cmd), func(info *resource.Info) (bool, error) {
transformed := false transformed := false
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error { _, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error {
containers, _ := selectContainers(spec.Containers, o.ContainerSelector) containers, _ := selectContainers(spec.Containers, o.ContainerSelector)
......
...@@ -321,6 +321,11 @@ func (o TaintOptions) RunTaint() error { ...@@ -321,6 +321,11 @@ func (o TaintOptions) RunTaint() error {
return err return err
} }
smPatchVersion, err := cmdutil.GetServerSupportedSMPatchVersionFromFactory(o.f)
if err != nil {
return err
}
return r.Visit(func(info *resource.Info, err error) error { return r.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
...@@ -343,7 +348,7 @@ func (o TaintOptions) RunTaint() error { ...@@ -343,7 +348,7 @@ func (o TaintOptions) RunTaint() error {
if err != nil { if err != nil {
return err return err
} }
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj, smPatchVersion)
createdPatch := err == nil createdPatch := err == nil
if err != nil { if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err) glog.V(2).Infof("couldn't compute patch: %v", err)
......
...@@ -252,7 +252,6 @@ func TestTaint(t *testing.T) { ...@@ -252,7 +252,6 @@ func TestTaint(t *testing.T) {
for _, test := range tests { for _, test := range tests {
oldNode, expectNewNode := generateNodeAndTaintedNode(test.oldTaints, test.newTaints) oldNode, expectNewNode := generateNodeAndTaintedNode(test.oldTaints, test.newTaints)
new_node := &api.Node{} new_node := &api.Node{}
tainted := false tainted := false
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, ns := cmdtesting.NewAPIFactory()
...@@ -262,6 +261,12 @@ func TestTaint(t *testing.T) { ...@@ -262,6 +261,12 @@ func TestTaint(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
m := &MyReq{req} m := &MyReq{req}
switch { switch {
case m.isFor("GET", "/version"):
resp, err := genResponseWithJsonEncodedBody(serverVersion_1_5_0)
if err != nil {
t.Fatalf("error: failed to generate server version response: %#v\n", serverVersion_1_5_0)
}
return resp, nil
case m.isFor("GET", "/nodes/node-name"): case m.isFor("GET", "/nodes/node-name"):
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, oldNode)}, nil return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, oldNode)}, nil
case m.isFor("PATCH", "/nodes/node-name"), m.isFor("PUT", "/nodes/node-name"): case m.isFor("PATCH", "/nodes/node-name"), m.isFor("PUT", "/nodes/node-name"):
......
...@@ -521,7 +521,7 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error { ...@@ -521,7 +521,7 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error {
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info // ChangeResourcePatch creates a strategic merge patch between the origin input resource info
// and the annotated with change-cause input resource info. // and the annotated with change-cause input resource info.
func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) { func ChangeResourcePatch(info *resource.Info, changeCause string, smPatchVersion strategicpatch.StrategicMergePatchVersion) ([]byte, error) {
oldData, err := json.Marshal(info.Object) oldData, err := json.Marshal(info.Object)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -533,7 +533,7 @@ func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error ...@@ -533,7 +533,7 @@ func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object) return strategicpatch.CreateTwoWayMergePatch(oldData, newData, info.Object, smPatchVersion)
} }
// containsChangeCause checks if input resource info contains change-cause annotation. // containsChangeCause checks if input resource info contains change-cause annotation.
...@@ -725,3 +725,13 @@ func RequireNoArguments(c *cobra.Command, args []string) { ...@@ -725,3 +725,13 @@ func RequireNoArguments(c *cobra.Command, args []string) {
CheckErr(UsageError(c, fmt.Sprintf(`unknown command %q`, strings.Join(args, " ")))) CheckErr(UsageError(c, fmt.Sprintf(`unknown command %q`, strings.Join(args, " "))))
} }
} }
// GetServerSupportedSMPatchVersionFromFactory is a wrapper of GetServerSupportedSMPatchVersion(),
// It takes a Factory, returns the max version the server supports.
func GetServerSupportedSMPatchVersionFromFactory(f Factory) (strategicpatch.StrategicMergePatchVersion, error) {
clientSet, err := f.ClientSet()
if err != nil {
return strategicpatch.Unknown, err
}
return strategicpatch.GetServerSupportedSMPatchVersion(clientSet.Discovery())
}
...@@ -15,6 +15,7 @@ go_library( ...@@ -15,6 +15,7 @@ go_library(
srcs = ["patch.go"], srcs = ["patch.go"],
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/client/typed/discovery:go_default_library",
"//pkg/util/json:go_default_library", "//pkg/util/json:go_default_library",
"//third_party/forked/golang/json:go_default_library", "//third_party/forked/golang/json:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew", "//vendor:github.com/davecgh/go-spew/spew",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment