Validate segments with client-go/dynamic

Kubernetes-commit: 7d64440dd1edf2acb8e71ecd6726904962c6bd02
This commit is contained in:
Antoine Pelisse 2022-08-30 16:32:10 -07:00 committed by Kubernetes Publisher
parent d83ec9e727
commit a4b84d86ec
2 changed files with 150 additions and 1 deletions

View File

@ -24,6 +24,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -672,3 +673,107 @@ func TestPatch(t *testing.T) {
}
}
}
func TestInvalidSegments(t *testing.T) {
name := "bad/name"
namespace := "bad/namespace"
resource := schema.GroupVersionResource{Group: "gtest", Version: "vtest", Resource: "rtest"}
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "vtest",
"kind": "vkind",
"metadata": map[string]interface{}{
"name": name,
},
},
}
cl, err := NewForConfig(&restclient.Config{
Host: "127.0.0.1",
})
if err != nil {
t.Fatalf("Failed to create config: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Update(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Update(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).UpdateStatus(context.TODO(), obj, metav1.UpdateOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
err = cl.Resource(resource).Delete(context.TODO(), name, metav1.DeleteOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
err = cl.Resource(resource).Namespace(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
err = cl.Resource(resource).Namespace(namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Watch(context.TODO(), metav1.ListOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte("{}"), metav1.PatchOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte("{}"), metav1.PatchOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).Apply(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).Apply(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
_, err = cl.Resource(resource).ApplyStatus(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid resource name") {
t.Fatalf("Expected `invalid resource name` error, got: %v", err)
}
_, err = cl.Resource(resource).Namespace(namespace).ApplyStatus(context.TODO(), name, obj, metav1.ApplyOptions{})
if err == nil || !strings.Contains(err.Error(), "invalid namespace") {
t.Fatalf("Expected `invalid namespace` error, got: %v", err)
}
}

View File

@ -120,6 +120,9 @@ func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Un
return nil, fmt.Errorf("name is required")
}
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.
Post().
@ -152,6 +155,9 @@ func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Un
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@ -188,7 +194,9 @@ func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructu
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@ -220,6 +228,9 @@ func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts me
if len(name) == 0 {
return fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return err
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
if err != nil {
return err
@ -235,6 +246,10 @@ func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts me
}
func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return err
}
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts)
if err != nil {
return err
@ -254,6 +269,9 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
if err := result.Error(); err != nil {
return nil, err
@ -270,6 +288,9 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
}
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do(ctx)
if err := result.Error(); err != nil {
return nil, err
@ -295,6 +316,9 @@ func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOption
func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
opts.Watch = true
if err := validateNamespaceWithOptionalName(c.namespace); err != nil {
return nil, err
}
return c.client.client.Get().AbsPath(c.makeURLSegments("")...).
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
Watch(ctx)
@ -304,6 +328,9 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
result := c.client.client.
Patch(pt).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
@ -328,6 +355,9 @@ func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *uns
if len(name) == 0 {
return nil, fmt.Errorf("name is required")
}
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
@ -366,6 +396,20 @@ func (c *dynamicResourceClient) ApplyStatus(ctx context.Context, name string, ob
return c.Apply(ctx, name, obj, opts, "status")
}
func validateNamespaceWithOptionalName(namespace string, name ...string) error {
if msgs := rest.IsValidPathSegmentName(namespace); len(msgs) != 0 {
return fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
}
if len(name) > 1 {
panic("Invalid number of names")
} else if len(name) == 1 {
if msgs := rest.IsValidPathSegmentName(name[0]); len(msgs) != 0 {
return fmt.Errorf("invalid resource name %q: %v", name[0], msgs)
}
}
return nil
}
func (c *dynamicResourceClient) makeURLSegments(name string) []string {
url := []string{}
if len(c.resource.Group) == 0 {