From a4b84d86ec558b25f36aedbd47ad4de8deac86f7 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 30 Aug 2022 16:32:10 -0700 Subject: [PATCH] Validate segments with client-go/dynamic Kubernetes-commit: 7d64440dd1edf2acb8e71ecd6726904962c6bd02 --- dynamic/client_test.go | 105 +++++++++++++++++++++++++++++++++++++++++ dynamic/simple.go | 46 +++++++++++++++++- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/dynamic/client_test.go b/dynamic/client_test.go index 46a8f827..2f16abb8 100644 --- a/dynamic/client_test.go +++ b/dynamic/client_test.go @@ -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) + } +} diff --git a/dynamic/simple.go b/dynamic/simple.go index 9dc0fb5c..47adf7bf 100644 --- a/dynamic/simple.go +++ b/dynamic/simple.go @@ -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 {