Fix --resource-version handling in kubectl
Kubernetes-commit: 0ac8345d3a4c5ee38fd3fc2c40976ba876d815e7
This commit is contained in:
parent
0f4ee8ce68
commit
25e7825a5b
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue