...

Source file src/k8s.io/apiextensions-apiserver/test/integration/objectmeta_test.go

Documentation: k8s.io/apiextensions-apiserver/test/integration

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package integration
    18  
    19  import (
    20  	"context"
    21  	"path"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"go.etcd.io/etcd/client/pkg/v3/transport"
    29  	clientv3 "go.etcd.io/etcd/client/v3"
    30  	"google.golang.org/grpc"
    31  	"sigs.k8s.io/yaml"
    32  
    33  	"k8s.io/apimachinery/pkg/api/errors"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    36  	"k8s.io/apimachinery/pkg/runtime/schema"
    37  	"k8s.io/apimachinery/pkg/util/json"
    38  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    39  	"k8s.io/client-go/dynamic"
    40  	"k8s.io/utils/pointer"
    41  
    42  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    43  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    44  	serveroptions "k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
    45  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    46  )
    47  
    48  func TestPostInvalidObjectMeta(t *testing.T) {
    49  	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  	defer tearDown()
    54  
    55  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
    56  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  
    61  	noxuResourceClient := newNamespacedCustomResourceClient("default", dynamicClient, noxuDefinition)
    62  
    63  	obj := fixtures.NewNoxuInstance("default", "foo")
    64  	unstructured.SetNestedField(obj.UnstructuredContent(), int64(42), "metadata", "unknown")
    65  	unstructured.SetNestedField(obj.UnstructuredContent(), nil, "metadata", "generation")
    66  	unstructured.SetNestedField(obj.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "metadata", "labels")
    67  	_, err = instantiateCustomResource(t, obj, noxuResourceClient, noxuDefinition)
    68  	if err == nil {
    69  		t.Fatalf("unexpected non-error, expected invalid labels to be rejected: %v", err)
    70  	}
    71  	if status, ok := err.(errors.APIStatus); !ok {
    72  		t.Fatalf("expected APIStatus error, but got: %#v", err)
    73  	} else if !errors.IsBadRequest(err) {
    74  		t.Fatalf("expected BadRequst error, but got: %v", errors.ReasonForError(err))
    75  	} else if !strings.Contains(status.Status().Message, "cannot be handled") {
    76  		t.Fatalf("expected 'cannot be handled' error message, got: %v", status.Status().Message)
    77  	}
    78  
    79  	unstructured.SetNestedField(obj.UnstructuredContent(), map[string]interface{}{"bar": "abc"}, "metadata", "labels")
    80  	obj, err = instantiateCustomResource(t, obj, noxuResourceClient, noxuDefinition)
    81  	if err != nil {
    82  		t.Fatalf("unexpected error: %v", err)
    83  	}
    84  
    85  	if unknown, found, err := unstructured.NestedInt64(obj.UnstructuredContent(), "metadata", "unknown"); err != nil {
    86  		t.Errorf("unexpected error getting metadata.unknown: %v", err)
    87  	} else if found {
    88  		t.Errorf("unexpected metadata.unknown=%#v: expected this to be pruned", unknown)
    89  	}
    90  
    91  	if generation, found, err := unstructured.NestedInt64(obj.UnstructuredContent(), "metadata", "generation"); err != nil {
    92  		t.Errorf("unexpected error getting metadata.generation: %v", err)
    93  	} else if !found {
    94  		t.Errorf("expected metadata.generation=1: got: %d", generation)
    95  	} else if generation != 1 {
    96  		t.Errorf("unexpected metadata.generation=%d: expected this to be set to 1", generation)
    97  	}
    98  }
    99  
   100  func TestInvalidObjectMetaInStorage(t *testing.T) {
   101  	tearDown, config, options, err := fixtures.StartDefaultServer(t)
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  	defer tearDown()
   106  
   107  	apiExtensionClient, err := clientset.NewForConfig(config)
   108  	if err != nil {
   109  		t.Fatal(err)
   110  	}
   111  
   112  	dynamicClient, err := dynamic.NewForConfig(config)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.NamespaceScoped)
   118  	noxuDefinition.Spec.Versions[0].Schema = &apiextensionsv1.CustomResourceValidation{
   119  		OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
   120  			Type: "object",
   121  			Properties: map[string]apiextensionsv1.JSONSchemaProps{
   122  				"embedded": {
   123  					Type:                   "object",
   124  					XEmbeddedResource:      true,
   125  					XPreserveUnknownFields: pointer.BoolPtr(true),
   126  				},
   127  			},
   128  		},
   129  	}
   130  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	RESTOptionsGetter := serveroptions.NewCRDRESTOptionsGetter(*options.RecommendedOptions.Etcd, nil, nil)
   136  	restOptions, err := RESTOptionsGetter.GetRESTOptions(schema.GroupResource{Group: noxuDefinition.Spec.Group, Resource: noxuDefinition.Spec.Names.Plural})
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	tlsInfo := transport.TLSInfo{
   141  		CertFile:      restOptions.StorageConfig.Transport.CertFile,
   142  		KeyFile:       restOptions.StorageConfig.Transport.KeyFile,
   143  		TrustedCAFile: restOptions.StorageConfig.Transport.TrustedCAFile,
   144  	}
   145  	tlsConfig, err := tlsInfo.ClientConfig()
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	etcdConfig := clientv3.Config{
   150  		Endpoints:   restOptions.StorageConfig.Transport.ServerList,
   151  		DialTimeout: 20 * time.Second,
   152  		DialOptions: []grpc.DialOption{
   153  			grpc.WithBlock(), // block until the underlying connection is up
   154  		},
   155  		TLS: tlsConfig,
   156  	}
   157  	etcdclient, err := clientv3.New(etcdConfig)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	t.Logf("Creating object with wrongly typed annotations and non-validating labels manually in etcd")
   163  
   164  	original := fixtures.NewNoxuInstance("default", "foo")
   165  	unstructured.SetNestedField(original.UnstructuredContent(), int64(42), "metadata", "unknown")
   166  	unstructured.SetNestedField(original.UnstructuredContent(), nil, "metadata", "generation")
   167  
   168  	unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "metadata", "annotations")
   169  	unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"invalid": "x y"}, "metadata", "labels")
   170  	unstructured.SetNestedField(original.UnstructuredContent(), int64(42), "embedded", "metadata", "unknown")
   171  	unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"foo": int64(42), "bar": "abc"}, "embedded", "metadata", "annotations")
   172  	unstructured.SetNestedField(original.UnstructuredContent(), map[string]interface{}{"invalid": "x y"}, "embedded", "metadata", "labels")
   173  	unstructured.SetNestedField(original.UnstructuredContent(), "Foo", "embedded", "kind")
   174  	unstructured.SetNestedField(original.UnstructuredContent(), "foo/v1", "embedded", "apiVersion")
   175  
   176  	ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
   177  	key := path.Join("/", restOptions.StorageConfig.Prefix, noxuDefinition.Spec.Group, "noxus/default/foo")
   178  	val, _ := json.Marshal(original.UnstructuredContent())
   179  	if _, err := etcdclient.Put(ctx, key, string(val)); err != nil {
   180  		t.Fatalf("unexpected error: %v", err)
   181  	}
   182  
   183  	t.Logf("Checking that invalid objects can be deleted")
   184  	noxuResourceClient := newNamespacedCustomResourceClient("default", dynamicClient, noxuDefinition)
   185  	if err := noxuResourceClient.Delete(context.TODO(), "foo", metav1.DeleteOptions{}); err != nil {
   186  		t.Fatalf("Unexpected delete error %v", err)
   187  	}
   188  	if _, err := etcdclient.Put(ctx, key, string(val)); err != nil {
   189  		t.Fatalf("unexpected error: %v", err)
   190  	}
   191  
   192  	t.Logf("Checking that ObjectMeta is pruned from unknown fields")
   193  	obj, err := noxuResourceClient.Get(context.TODO(), "foo", metav1.GetOptions{})
   194  	if err != nil {
   195  		t.Fatalf("Unexpected error: %v", err)
   196  	}
   197  	objJSON, _ := json.Marshal(obj.Object)
   198  	t.Logf("Got object: %v", string(objJSON))
   199  
   200  	if unknown, found, err := unstructured.NestedFieldNoCopy(obj.UnstructuredContent(), "metadata", "unknown"); err != nil {
   201  		t.Errorf("Unexpected error: %v", err)
   202  	} else if found {
   203  		t.Errorf("Unexpected to find metadata.unknown=%#v", unknown)
   204  	}
   205  	if unknown, found, err := unstructured.NestedFieldNoCopy(obj.UnstructuredContent(), "embedded", "metadata", "unknown"); err != nil {
   206  		t.Errorf("Unexpected error: %v", err)
   207  	} else if found {
   208  		t.Errorf("Unexpected to find embedded.metadata.unknown=%#v", unknown)
   209  	}
   210  
   211  	t.Logf("Checking that metadata.generation=1")
   212  
   213  	if generation, found, err := unstructured.NestedInt64(obj.UnstructuredContent(), "metadata", "generation"); err != nil {
   214  		t.Errorf("unexpected error getting metadata.generation: %v", err)
   215  	} else if !found {
   216  		t.Errorf("expected metadata.generation=1: got: %d", generation)
   217  	} else if generation != 1 {
   218  		t.Errorf("unexpected metadata.generation=%d: expected this to be set to 1", generation)
   219  	}
   220  
   221  	t.Logf("Checking that ObjectMeta is pruned from wrongly-typed annotations")
   222  
   223  	if annotations, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "metadata", "annotations"); err != nil {
   224  		t.Errorf("Unexpected error: %v", err)
   225  	} else if found {
   226  		t.Errorf("Unexpected to find metadata.annotations: %#v", annotations)
   227  	}
   228  	if annotations, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "embedded", "metadata", "annotations"); err != nil {
   229  		t.Errorf("Unexpected error: %v", err)
   230  	} else if found {
   231  		t.Errorf("Unexpected to find embedded.metadata.annotations: %#v", annotations)
   232  	}
   233  
   234  	t.Logf("Checking that ObjectMeta still has the non-validating labels")
   235  
   236  	if labels, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "metadata", "labels"); err != nil {
   237  		t.Errorf("unexpected error: %v", err)
   238  	} else if !found {
   239  		t.Errorf("Expected to find metadata.labels, but didn't")
   240  	} else if expected := map[string]string{"invalid": "x y"}; !reflect.DeepEqual(labels, expected) {
   241  		t.Errorf("Expected metadata.labels to be %#v, got: %#v", expected, labels)
   242  	}
   243  	if labels, found, err := unstructured.NestedStringMap(obj.UnstructuredContent(), "embedded", "metadata", "labels"); err != nil {
   244  		t.Errorf("Unexpected error: %v", err)
   245  	} else if !found {
   246  		t.Errorf("Expected to find embedded.metadata.labels, but didn't")
   247  	} else if expected := map[string]string{"invalid": "x y"}; !reflect.DeepEqual(labels, expected) {
   248  		t.Errorf("Expected embedded.metadata.labels to be %#v, got: %#v", expected, labels)
   249  	}
   250  
   251  	t.Logf("Trying to fail on updating with invalid labels")
   252  	unstructured.SetNestedField(obj.Object, "changed", "metadata", "labels", "something")
   253  	if got, err := noxuResourceClient.Update(context.TODO(), obj, metav1.UpdateOptions{}); err == nil {
   254  		objJSON, _ := json.Marshal(obj.Object)
   255  		gotJSON, _ := json.Marshal(got.Object)
   256  		t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
   257  	}
   258  
   259  	t.Logf("Trying to fail on updating with invalid embedded label")
   260  	unstructured.SetNestedField(obj.Object, "fixed", "metadata", "labels", "invalid")
   261  	if got, err := noxuResourceClient.Update(context.TODO(), obj, metav1.UpdateOptions{}); err == nil {
   262  		objJSON, _ := json.Marshal(obj.Object)
   263  		gotJSON, _ := json.Marshal(got.Object)
   264  		t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
   265  	}
   266  
   267  	t.Logf("Fixed all labels and update should work")
   268  	unstructured.SetNestedField(obj.Object, "fixed", "embedded", "metadata", "labels", "invalid")
   269  	if _, err := noxuResourceClient.Update(context.TODO(), obj, metav1.UpdateOptions{}); err != nil {
   270  		t.Errorf("Unexpected update error with fixed labels: %v", err)
   271  	}
   272  
   273  	t.Logf("Trying to fail on updating with wrongly-typed embedded label")
   274  	unstructured.SetNestedField(obj.Object, int64(42), "embedded", "metadata", "labels", "invalid")
   275  	if got, err := noxuResourceClient.Update(context.TODO(), obj, metav1.UpdateOptions{}); err == nil {
   276  		objJSON, _ := json.Marshal(obj.Object)
   277  		gotJSON, _ := json.Marshal(got.Object)
   278  		t.Fatalf("Expected update error, but didn't get one\nin: %s\nresponse: %v", string(objJSON), string(gotJSON))
   279  	}
   280  }
   281  
   282  var embeddedResourceFixture = &apiextensionsv1.CustomResourceDefinition{
   283  	ObjectMeta: metav1.ObjectMeta{Name: "foos.tests.example.com"},
   284  	Spec: apiextensionsv1.CustomResourceDefinitionSpec{
   285  		Group: "tests.example.com",
   286  		Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
   287  			{
   288  				Name:    "v1beta1",
   289  				Storage: true,
   290  				Served:  true,
   291  				Subresources: &apiextensionsv1.CustomResourceSubresources{
   292  					Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
   293  				},
   294  			},
   295  		},
   296  		Names: apiextensionsv1.CustomResourceDefinitionNames{
   297  			Plural:   "foos",
   298  			Singular: "foo",
   299  			Kind:     "Foo",
   300  			ListKind: "FooList",
   301  		},
   302  		Scope:                 apiextensionsv1.ClusterScoped,
   303  		PreserveUnknownFields: false,
   304  	},
   305  }
   306  
   307  const (
   308  	embeddedResourceSchema = `
   309  type: object
   310  properties:
   311    embedded:
   312      type: object
   313      x-kubernetes-embedded-resource: true
   314      x-kubernetes-preserve-unknown-fields: true
   315    noEmbeddedObject:
   316      type: object
   317      x-kubernetes-preserve-unknown-fields: true
   318    embeddedNested:
   319      type: object
   320      x-kubernetes-embedded-resource: true
   321      x-kubernetes-preserve-unknown-fields: true
   322      properties:
   323        embedded:
   324          type: object
   325          x-kubernetes-embedded-resource: true
   326          x-kubernetes-preserve-unknown-fields: true
   327    defaults:
   328      type: object
   329      x-kubernetes-embedded-resource: true
   330      x-kubernetes-preserve-unknown-fields: true
   331      default:
   332        apiVersion: v1
   333        kind: Pod
   334        labels:
   335          foo: bar
   336  `
   337  
   338  	embeddedResourceInstance = `
   339  kind: Foo
   340  apiVersion: tests.example.com/v1beta1
   341  embedded:
   342    apiVersion: foo/v1
   343    kind: Foo
   344    metadata:
   345      name: foo
   346      unspecified: bar
   347  noEmbeddedObject:
   348    apiVersion: foo/v1
   349    kind: Foo
   350    metadata:
   351      name: foo
   352      unspecified: bar
   353  embeddedNested:
   354    apiVersion: foo/v1
   355    kind: Foo
   356    metadata:
   357      name: foo
   358      unspecified: bar
   359    embedded:
   360      apiVersion: foo/v1
   361      kind: Foo
   362      metadata:
   363        name: foo
   364        unspecified: bar
   365  `
   366  
   367  	expectedEmbeddedResourceInstance = `
   368  kind: Foo
   369  apiVersion: tests.example.com/v1beta1
   370  embedded:
   371    apiVersion: foo/v1
   372    kind: Foo
   373    metadata:
   374      name: foo
   375  noEmbeddedObject:
   376    apiVersion: foo/v1
   377    kind: Foo
   378    metadata:
   379      name: foo
   380      unspecified: bar
   381  embeddedNested:
   382    apiVersion: foo/v1
   383    kind: Foo
   384    metadata:
   385      name: foo
   386    embedded:
   387      apiVersion: foo/v1
   388      kind: Foo
   389      metadata:
   390        name: foo
   391  defaults:
   392    apiVersion: v1
   393    kind: Pod
   394    labels:
   395      foo: bar
   396  `
   397  
   398  	wronglyTypedEmbeddedResourceInstance = `
   399  kind: Foo
   400  apiVersion: tests.example.com/v1beta1
   401  embedded:
   402    apiVersion: foo/v1
   403    kind: Foo
   404    metadata:
   405      name: instance
   406      namespace: 42
   407  `
   408  
   409  	invalidEmbeddedResourceInstance = `
   410  kind: Foo
   411  apiVersion: tests.example.com/v1beta1
   412  embedded:
   413    apiVersion: foo/v1
   414    kind: "%"
   415    metadata:
   416      name: ..
   417  embeddedNested:
   418    apiVersion: foo/v1
   419    kind: "%"
   420    metadata:
   421      name: ..
   422    embedded:
   423      apiVersion: foo/v1
   424      kind: "%"
   425      metadata:
   426        name: ..
   427  invalidDefaults: {}
   428  `
   429  )
   430  
   431  func TestEmbeddedResources(t *testing.T) {
   432  	tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	defer tearDownFn()
   437  
   438  	crd := embeddedResourceFixture.DeepCopy()
   439  	crd.Spec.Versions[0].Schema = &apiextensionsv1.CustomResourceValidation{}
   440  	if err := yaml.Unmarshal([]byte(embeddedResourceSchema), &crd.Spec.Versions[0].Schema.OpenAPIV3Schema); err != nil {
   441  		t.Fatal(err)
   442  	}
   443  
   444  	crd, err = fixtures.CreateNewV1CustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  
   449  	t.Logf("Creating CR and expect 'unspecified' fields to be pruned inside ObjectMetas")
   450  	fooClient := dynamicClient.Resource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: crd.Spec.Names.Plural})
   451  	foo := &unstructured.Unstructured{}
   452  	if err := yaml.Unmarshal([]byte(embeddedResourceInstance), &foo.Object); err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	unstructured.SetNestedField(foo.Object, "foo", "metadata", "name")
   456  	foo, err = fooClient.Create(context.TODO(), foo, metav1.CreateOptions{})
   457  	if err != nil {
   458  		t.Fatalf("Unable to create CR: %v", err)
   459  	}
   460  	t.Logf("CR created: %#v", foo.UnstructuredContent())
   461  
   462  	t.Logf("Checking that everything unknown inside ObjectMeta is gone")
   463  	delete(foo.Object, "metadata")
   464  	var expected map[string]interface{}
   465  	if err := yaml.Unmarshal([]byte(expectedEmbeddedResourceInstance), &expected); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	if !reflect.DeepEqual(expected, foo.Object) {
   469  		t.Errorf("unexpected diff: %s", cmp.Diff(expected, foo.Object))
   470  	}
   471  
   472  	t.Logf("Trying to create wrongly typed CR")
   473  	wronglyTyped := &unstructured.Unstructured{}
   474  	if err := yaml.Unmarshal([]byte(wronglyTypedEmbeddedResourceInstance), &wronglyTyped.Object); err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	unstructured.SetNestedField(wronglyTyped.Object, "invalid", "metadata", "name")
   478  	_, err = fooClient.Create(context.TODO(), wronglyTyped, metav1.CreateOptions{})
   479  	if err == nil {
   480  		t.Fatal("Expected creation to fail, but didn't")
   481  	}
   482  	t.Logf("Creation of wrongly typed object failed with: %v", err)
   483  
   484  	for _, s := range []string{
   485  		`embedded.metadata: Invalid value`,
   486  	} {
   487  		if !strings.Contains(err.Error(), s) {
   488  			t.Errorf("missing error: %s", s)
   489  		}
   490  	}
   491  
   492  	t.Logf("Trying to create invalid CR")
   493  	invalid := &unstructured.Unstructured{}
   494  	if err := yaml.Unmarshal([]byte(invalidEmbeddedResourceInstance), &invalid.Object); err != nil {
   495  		t.Fatal(err)
   496  	}
   497  	unstructured.SetNestedField(invalid.Object, "invalid", "metadata", "name")
   498  	unstructured.SetNestedField(invalid.Object, "x y", "metadata", "labels", "foo")
   499  	_, err = fooClient.Create(context.TODO(), invalid, metav1.CreateOptions{})
   500  	if err == nil {
   501  		t.Fatal("Expected creation to fail, but didn't")
   502  	}
   503  	t.Logf("Creation of invalid object failed with: %v", err)
   504  
   505  	invalidErrors := []string{
   506  		`[metadata.labels: Invalid value: "x y"`,
   507  		` embedded.kind: Invalid value: "%"`,
   508  		` embedded.metadata.name: Invalid value: ".."`,
   509  		` embeddedNested.kind: Invalid value: "%"`,
   510  		` embeddedNested.metadata.name: Invalid value: ".."`,
   511  		` embeddedNested.embedded.kind: Invalid value: "%"`,
   512  		` embeddedNested.embedded.metadata.name: Invalid value: ".."`,
   513  	}
   514  	for _, s := range invalidErrors {
   515  		if !strings.Contains(err.Error(), s) {
   516  			t.Errorf("missing error: %s", s)
   517  		}
   518  	}
   519  
   520  	t.Logf("Creating a valid CR and then updating it with invalid values, expecting the same errors")
   521  	valid := &unstructured.Unstructured{}
   522  	if err := yaml.Unmarshal([]byte(embeddedResourceInstance), &valid.Object); err != nil {
   523  		t.Fatal(err)
   524  	}
   525  	unstructured.SetNestedField(valid.Object, "valid", "metadata", "name")
   526  	valid, err = fooClient.Create(context.TODO(), valid, metav1.CreateOptions{})
   527  	if err != nil {
   528  		t.Fatalf("Unable to create CR: %v", err)
   529  	}
   530  	for k, v := range invalid.Object {
   531  		if k == "metadata" {
   532  			continue
   533  		}
   534  		valid.Object[k] = v
   535  	}
   536  	unstructured.SetNestedField(valid.Object, "x y", "metadata", "labels", "foo")
   537  	if _, err = fooClient.Update(context.TODO(), valid, metav1.UpdateOptions{}); err == nil {
   538  		t.Fatal("Expected update error, but got none")
   539  	}
   540  	t.Logf("Update failed with: %v", err)
   541  	for _, s := range invalidErrors {
   542  		if !strings.Contains(err.Error(), s) {
   543  			t.Errorf("missing error: %s", s)
   544  		}
   545  	}
   546  }
   547  

View as plain text