Fix --resource-version handling in kubectl

Kubernetes-commit: 0ac8345d3a4c5ee38fd3fc2c40976ba876d815e7
This commit is contained in:
Jordan Liggitt 2019-11-14 09:24:42 -05:00 committed by Kubernetes Publisher
parent 0f4ee8ce68
commit 25e7825a5b
5 changed files with 184 additions and 5 deletions

View File

@ -264,6 +264,16 @@ func (o AnnotateOptions) RunAnnotate() error {
outputObj = obj
} else {
name, namespace := info.Name, info.Namespace
if len(o.resourceVersion) != 0 {
// ensure resourceVersion is always sent in the patch by clearing it from the starting JSON
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
accessor.SetResourceVersion("")
}
oldData, err := json.Marshal(obj)
if err != nil {
return err

View File

@ -17,12 +17,14 @@ limitations under the License.
package annotate
import (
"bytes"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -502,6 +504,73 @@ func TestAnnotateObject(t *testing.T) {
}
}
func TestAnnotateResourceVersion(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{
StatusCode: http.StatusOK,
Header: cmdtesting.DefaultHeader(),
Body: ioutil.NopCloser(bytes.NewBufferString(
`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"10"}}`,
))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(body, []byte(`{"metadata":{"annotations":{"a":"b"},"resourceVersion":"10"}}`)) {
t.Fatalf("expected patch with resourceVersion set, got %s", string(body))
}
return &http.Response{
StatusCode: http.StatusOK,
Header: cmdtesting.DefaultHeader(),
Body: ioutil.NopCloser(bytes.NewBufferString(
`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"11"}}`,
))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdAnnotate("kubectl", tf, iostreams)
cmd.SetOutput(bufOut)
options := NewAnnotateOptions(iostreams)
options.resourceVersion = "10"
args := []string{"pods/foo", "a=b"}
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestAnnotateObjectFromFile(t *testing.T) {
pods, _, _ := cmdtesting.TestData()

View File

@ -253,6 +253,16 @@ func (o *LabelOptions) RunLabel() error {
var outputObj runtime.Object
var dataChangeMsg string
obj := info.Object
if len(o.resourceVersion) != 0 {
// ensure resourceVersion is always sent in the patch by clearing it from the starting JSON
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
accessor.SetResourceVersion("")
}
oldData, err := json.Marshal(obj)
if err != nil {
return err

View File

@ -18,6 +18,7 @@ package label
import (
"bytes"
"io/ioutil"
"net/http"
"reflect"
"strings"
@ -26,6 +27,7 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/rest/fake"
@ -496,3 +498,70 @@ func TestLabelMultipleObjects(t *testing.T) {
t.Errorf("not all labels are set: %s", buf.String())
}
}
func TestLabelResourceVersion(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tf.UnstructuredClient = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "testgroup", Version: "v1"},
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{
StatusCode: http.StatusOK,
Header: cmdtesting.DefaultHeader(),
Body: ioutil.NopCloser(bytes.NewBufferString(
`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"10"}}`,
))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(body, []byte(`{"metadata":{"labels":{"a":"b"},"resourceVersion":"10"}}`)) {
t.Fatalf("expected patch with resourceVersion set, got %s", string(body))
}
return &http.Response{
StatusCode: http.StatusOK,
Header: cmdtesting.DefaultHeader(),
Body: ioutil.NopCloser(bytes.NewBufferString(
`{"kind":"Pod","apiVersion":"v1","metadata":{"name":"foo","namespace":"test","resourceVersion":"11"}}`,
))}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
default:
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdLabel(tf, iostreams)
cmd.SetOutput(bufOut)
options := NewLabelOptions(iostreams)
options.resourceVersion = "10"
args := []string{"pods/foo", "a=b"}
if err := options.Complete(tf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunLabel(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

View File

@ -22,7 +22,8 @@ import (
"github.com/spf13/cobra"
"k8s.io/klog"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@ -46,8 +47,9 @@ type SetSelectorOptions struct {
dryrun bool
// set by args
resources []string
selector *metav1.LabelSelector
resources []string
selector *metav1.LabelSelector
resourceVersion string
// computed
WriteToServer bool
@ -111,7 +113,7 @@ func NewCmdSelector(f cmdutil.Factory, streams genericclioptions.IOStreams) *cob
o.PrintFlags.AddFlags(cmd)
o.RecordFlags.AddFlags(cmd)
cmd.Flags().String("resource-version", "", "If non-empty, the selectors update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
cmd.Flags().StringVarP(&o.resourceVersion, "resource-version", "", o.resourceVersion, "If non-empty, the selectors update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
cmdutil.AddDryRunFlag(cmd)
return cmd
@ -163,7 +165,26 @@ func (o *SetSelectorOptions) RunSelector() error {
return r.Visit(func(info *resource.Info, err error) error {
patch := &Patch{Info: info}
if len(o.resourceVersion) != 0 {
// ensure resourceVersion is always sent in the patch by clearing it from the starting JSON
accessor, err := meta.Accessor(info.Object)
if err != nil {
return err
}
accessor.SetResourceVersion("")
}
CalculatePatch(patch, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
if len(o.resourceVersion) != 0 {
accessor, err := meta.Accessor(info.Object)
if err != nil {
return nil, err
}
accessor.SetResourceVersion(o.resourceVersion)
}
selectErr := updateSelectorForObject(info.Object, *o.selector)
if selectErr != nil {
return nil, selectErr