...

Source file src/k8s.io/apiextensions-apiserver/test/integration/yaml_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  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  	"testing"
    25  
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/client-go/dynamic"
    32  
    33  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    34  	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    35  	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
    36  )
    37  
    38  func TestYAML(t *testing.T) {
    39  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  	defer tearDown()
    44  
    45  	apiExtensionClient, err := clientset.NewForConfig(config)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	dynamicClient, err := dynamic.NewForConfig(config)
    50  	if err != nil {
    51  		t.Fatal(err)
    52  	}
    53  
    54  	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
    55  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  
    60  	kind := noxuDefinition.Spec.Names.Kind
    61  	listKind := noxuDefinition.Spec.Names.ListKind
    62  	apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
    63  
    64  	rest := apiExtensionClient.Discovery().RESTClient()
    65  
    66  	// Discovery
    67  	{
    68  		result, err := rest.Get().
    69  			SetHeader("Accept", "application/yaml").
    70  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name).
    71  			DoRaw(context.TODO())
    72  		if err != nil {
    73  			t.Fatal(err, string(result))
    74  		}
    75  		obj, err := decodeYAML(result)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  		if obj.GetAPIVersion() != "v1" || obj.GetKind() != "APIResourceList" {
    80  			t.Fatalf("unexpected discovery kind: %s", string(result))
    81  		}
    82  		if v, ok, err := unstructured.NestedString(obj.Object, "groupVersion"); v != apiVersion || !ok || err != nil {
    83  			t.Fatal(v, ok, err, string(result))
    84  		}
    85  	}
    86  
    87  	// Error
    88  	{
    89  		result, err := rest.Get().
    90  			SetHeader("Accept", "application/yaml").
    91  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "missingname").
    92  			DoRaw(context.TODO())
    93  		if !errors.IsNotFound(err) {
    94  			t.Fatalf("expected not found, got %v", err)
    95  		}
    96  		obj, err := decodeYAML(result)
    97  		if err != nil {
    98  			t.Fatal(err)
    99  		}
   100  		if obj.GetAPIVersion() != "v1" || obj.GetKind() != "Status" {
   101  			t.Fatalf("unexpected discovery kind: %s", string(result))
   102  		}
   103  		if v, ok, err := unstructured.NestedString(obj.Object, "reason"); v != "NotFound" || !ok || err != nil {
   104  			t.Fatal(v, ok, err, string(result))
   105  		}
   106  	}
   107  
   108  	uid := types.UID("")
   109  	resourceVersion := ""
   110  
   111  	// Create
   112  	{
   113  		yamlBody := []byte(fmt.Sprintf(`
   114  apiVersion: %s
   115  kind: %s
   116  metadata:
   117    name: mytest
   118  values:
   119    numVal: 1
   120    boolVal: true
   121    stringVal: "1"`, apiVersion, kind))
   122  
   123  		result, err := rest.Post().
   124  			SetHeader("Accept", "application/yaml").
   125  			SetHeader("Content-Type", "application/yaml").
   126  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   127  			Body(yamlBody).
   128  			DoRaw(context.TODO())
   129  		if err != nil {
   130  			t.Fatal(err, string(result))
   131  		}
   132  		obj, err := decodeYAML(result)
   133  		if err != nil {
   134  			t.Fatal(err)
   135  		}
   136  		if obj.GetName() != "mytest" {
   137  			t.Fatalf("expected mytest, got %s", obj.GetName())
   138  		}
   139  		if obj.GetAPIVersion() != apiVersion {
   140  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   141  		}
   142  		if obj.GetKind() != kind {
   143  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   144  		}
   145  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "values", "numVal"); v != 1 || !ok || err != nil {
   146  			t.Fatal(v, ok, err, string(result))
   147  		}
   148  		if v, ok, err := unstructured.NestedBool(obj.Object, "values", "boolVal"); v != true || !ok || err != nil {
   149  			t.Fatal(v, ok, err, string(result))
   150  		}
   151  		if v, ok, err := unstructured.NestedString(obj.Object, "values", "stringVal"); v != "1" || !ok || err != nil {
   152  			t.Fatal(v, ok, err, string(result))
   153  		}
   154  		uid = obj.GetUID()
   155  		resourceVersion = obj.GetResourceVersion()
   156  	}
   157  
   158  	// Get
   159  	{
   160  		result, err := rest.Get().
   161  			SetHeader("Accept", "application/yaml").
   162  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest").
   163  			DoRaw(context.TODO())
   164  		if err != nil {
   165  			t.Fatal(err)
   166  		}
   167  		obj, err := decodeYAML(result)
   168  		if err != nil {
   169  			t.Fatal(err, string(result))
   170  		}
   171  		if obj.GetName() != "mytest" {
   172  			t.Fatalf("expected mytest, got %s", obj.GetName())
   173  		}
   174  		if obj.GetAPIVersion() != apiVersion {
   175  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   176  		}
   177  		if obj.GetKind() != kind {
   178  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   179  		}
   180  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "values", "numVal"); v != 1 || !ok || err != nil {
   181  			t.Fatal(v, ok, err, string(result))
   182  		}
   183  		if v, ok, err := unstructured.NestedBool(obj.Object, "values", "boolVal"); v != true || !ok || err != nil {
   184  			t.Fatal(v, ok, err, string(result))
   185  		}
   186  		if v, ok, err := unstructured.NestedString(obj.Object, "values", "stringVal"); v != "1" || !ok || err != nil {
   187  			t.Fatal(v, ok, err, string(result))
   188  		}
   189  	}
   190  
   191  	// List
   192  	{
   193  		result, err := rest.Get().
   194  			SetHeader("Accept", "application/yaml").
   195  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   196  			DoRaw(context.TODO())
   197  		if err != nil {
   198  			t.Fatal(err, string(result))
   199  		}
   200  		listObj, err := decodeYAML(result)
   201  		if err != nil {
   202  			t.Fatal(err)
   203  		}
   204  		if listObj.GetAPIVersion() != apiVersion {
   205  			t.Fatalf("expected %s, got %s", apiVersion, listObj.GetAPIVersion())
   206  		}
   207  		if listObj.GetKind() != listKind {
   208  			t.Fatalf("expected %s, got %s", kind, listObj.GetKind())
   209  		}
   210  		items, ok, err := unstructured.NestedSlice(listObj.Object, "items")
   211  		if !ok || err != nil || len(items) != 1 {
   212  			t.Fatalf("expected one item, got %v %v %v", items, ok, err)
   213  		}
   214  		obj := unstructured.Unstructured{Object: items[0].(map[string]interface{})}
   215  		if obj.GetName() != "mytest" {
   216  			t.Fatalf("expected mytest, got %s", obj.GetName())
   217  		}
   218  		if obj.GetAPIVersion() != apiVersion {
   219  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   220  		}
   221  		if obj.GetKind() != kind {
   222  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   223  		}
   224  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "values", "numVal"); v != 1 || !ok || err != nil {
   225  			t.Fatal(v, ok, err, string(result))
   226  		}
   227  		if v, ok, err := unstructured.NestedBool(obj.Object, "values", "boolVal"); v != true || !ok || err != nil {
   228  			t.Fatal(v, ok, err, string(result))
   229  		}
   230  		if v, ok, err := unstructured.NestedString(obj.Object, "values", "stringVal"); v != "1" || !ok || err != nil {
   231  			t.Fatal(v, ok, err, string(result))
   232  		}
   233  	}
   234  
   235  	// Watch rejects yaml (no streaming support)
   236  	{
   237  		result, err := rest.Get().
   238  			SetHeader("Accept", "application/yaml").
   239  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   240  			Param("watch", "true").
   241  			DoRaw(context.TODO())
   242  		if !errors.IsNotAcceptable(err) {
   243  			t.Fatalf("expected not acceptable error, got %v (%s)", err, string(result))
   244  		}
   245  		obj, err := decodeYAML(result)
   246  		if err != nil {
   247  			t.Fatal(err)
   248  		}
   249  		if obj.GetAPIVersion() != "v1" || obj.GetKind() != "Status" {
   250  			t.Fatalf("unexpected result: %s", string(result))
   251  		}
   252  		if v, ok, err := unstructured.NestedString(obj.Object, "reason"); v != "NotAcceptable" || !ok || err != nil {
   253  			t.Fatal(v, ok, err, string(result))
   254  		}
   255  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "code"); v != http.StatusNotAcceptable || !ok || err != nil {
   256  			t.Fatal(v, ok, err, string(result))
   257  		}
   258  	}
   259  
   260  	// Update
   261  	{
   262  		yamlBody := []byte(fmt.Sprintf(`
   263  apiVersion: %s
   264  kind: %s
   265  metadata:
   266    name: mytest
   267    uid: %s
   268    resourceVersion: "%s"
   269  values:
   270    numVal: 2
   271    boolVal: false
   272    stringVal: "2"`, apiVersion, kind, uid, resourceVersion))
   273  		result, err := rest.Put().
   274  			SetHeader("Accept", "application/yaml").
   275  			SetHeader("Content-Type", "application/yaml").
   276  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest").
   277  			Body(yamlBody).
   278  			DoRaw(context.TODO())
   279  		if err != nil {
   280  			t.Fatal(err, string(result))
   281  		}
   282  		obj, err := decodeYAML(result)
   283  		if err != nil {
   284  			t.Fatal(err)
   285  		}
   286  		if obj.GetName() != "mytest" {
   287  			t.Fatalf("expected mytest, got %s", obj.GetName())
   288  		}
   289  		if obj.GetAPIVersion() != apiVersion {
   290  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   291  		}
   292  		if obj.GetKind() != kind {
   293  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   294  		}
   295  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "values", "numVal"); v != 2 || !ok || err != nil {
   296  			t.Fatal(v, ok, err, string(result))
   297  		}
   298  		if v, ok, err := unstructured.NestedBool(obj.Object, "values", "boolVal"); v != false || !ok || err != nil {
   299  			t.Fatal(v, ok, err, string(result))
   300  		}
   301  		if v, ok, err := unstructured.NestedString(obj.Object, "values", "stringVal"); v != "2" || !ok || err != nil {
   302  			t.Fatal(v, ok, err, string(result))
   303  		}
   304  		if obj.GetUID() != uid {
   305  			t.Fatalf("uid changed: %v vs %v", uid, obj.GetUID())
   306  		}
   307  	}
   308  
   309  	// Patch rejects yaml requests (only JSON mime types are allowed)
   310  	{
   311  		yamlBody := []byte(fmt.Sprintf(`
   312  values:
   313    numVal: 3`))
   314  		result, err := rest.Patch(types.MergePatchType).
   315  			SetHeader("Accept", "application/yaml").
   316  			SetHeader("Content-Type", "application/yaml").
   317  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest").
   318  			Body(yamlBody).
   319  			DoRaw(context.TODO())
   320  		if !errors.IsUnsupportedMediaType(err) {
   321  			t.Fatalf("Expected bad request, got %v\n%s", err, string(result))
   322  		}
   323  		obj, err := decodeYAML(result)
   324  		if err != nil {
   325  			t.Fatal(err)
   326  		}
   327  		if obj.GetAPIVersion() != "v1" || obj.GetKind() != "Status" {
   328  			t.Fatalf("expected %s %s, got %s %s", "v1", "Status", obj.GetAPIVersion(), obj.GetKind())
   329  		}
   330  		if v, ok, err := unstructured.NestedString(obj.Object, "reason"); v != "UnsupportedMediaType" || !ok || err != nil {
   331  			t.Fatal(v, ok, err, string(result))
   332  		}
   333  	}
   334  
   335  	// Delete
   336  	{
   337  		result, err := rest.Delete().
   338  			SetHeader("Accept", "application/yaml").
   339  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest").
   340  			DoRaw(context.TODO())
   341  		if err != nil {
   342  			t.Fatal(err, string(result))
   343  		}
   344  		obj, err := decodeYAML(result)
   345  		if err != nil {
   346  			t.Fatal(err)
   347  		}
   348  		if obj.GetAPIVersion() != "v1" || obj.GetKind() != "Status" {
   349  			t.Fatalf("unexpected response: %s", string(result))
   350  		}
   351  		if v, ok, err := unstructured.NestedString(obj.Object, "status"); v != "Success" || !ok || err != nil {
   352  			t.Fatal(v, ok, err, string(result))
   353  		}
   354  	}
   355  }
   356  
   357  func TestYAMLSubresource(t *testing.T) {
   358  	tearDown, config, _, err := fixtures.StartDefaultServer(t)
   359  	if err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	defer tearDown()
   363  
   364  	apiExtensionClient, err := clientset.NewForConfig(config)
   365  	if err != nil {
   366  		t.Fatal(err)
   367  	}
   368  	dynamicClient, err := dynamic.NewForConfig(config)
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  
   373  	noxuDefinition := NewNoxuSubresourcesCRDs(apiextensionsv1.ClusterScoped)[0]
   374  	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  
   379  	kind := noxuDefinition.Spec.Names.Kind
   380  	apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Versions[0].Name
   381  
   382  	rest := apiExtensionClient.Discovery().RESTClient()
   383  
   384  	uid := types.UID("")
   385  	resourceVersion := ""
   386  
   387  	// Create
   388  	{
   389  		yamlBody := []byte(fmt.Sprintf(`
   390  apiVersion: %s
   391  kind: %s
   392  metadata:
   393    name: mytest
   394  spec:
   395    replicas: 3`, apiVersion, kind))
   396  
   397  		result, err := rest.Post().
   398  			SetHeader("Accept", "application/yaml").
   399  			SetHeader("Content-Type", "application/yaml").
   400  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural).
   401  			Body(yamlBody).
   402  			DoRaw(context.TODO())
   403  		if err != nil {
   404  			t.Fatal(err, string(result))
   405  		}
   406  		obj, err := decodeYAML(result)
   407  		if err != nil {
   408  			t.Fatal(err)
   409  		}
   410  		if obj.GetName() != "mytest" {
   411  			t.Fatalf("expected mytest, got %s", obj.GetName())
   412  		}
   413  		if obj.GetAPIVersion() != apiVersion {
   414  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   415  		}
   416  		if obj.GetKind() != kind {
   417  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   418  		}
   419  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil {
   420  			t.Fatal(v, ok, err, string(result))
   421  		}
   422  		uid = obj.GetUID()
   423  		resourceVersion = obj.GetResourceVersion()
   424  	}
   425  
   426  	// Get at /status
   427  	{
   428  		result, err := rest.Get().
   429  			SetHeader("Accept", "application/yaml").
   430  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest", "status").
   431  			DoRaw(context.TODO())
   432  		if err != nil {
   433  			t.Fatal(err)
   434  		}
   435  		obj, err := decodeYAML(result)
   436  		if err != nil {
   437  			t.Fatal(err, string(result))
   438  		}
   439  		if obj.GetName() != "mytest" {
   440  			t.Fatalf("expected mytest, got %s", obj.GetName())
   441  		}
   442  		if obj.GetAPIVersion() != apiVersion {
   443  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   444  		}
   445  		if obj.GetKind() != kind {
   446  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   447  		}
   448  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil {
   449  			t.Fatal(v, ok, err, string(result))
   450  		}
   451  	}
   452  
   453  	// Update at /status
   454  	{
   455  		yamlBody := []byte(fmt.Sprintf(`
   456  apiVersion: %s
   457  kind: %s
   458  metadata:
   459    name: mytest
   460    uid: %s
   461    resourceVersion: "%s"
   462  spec:
   463    replicas: 5
   464  status:
   465    replicas: 3`, apiVersion, kind, uid, resourceVersion))
   466  		result, err := rest.Put().
   467  			SetHeader("Accept", "application/yaml").
   468  			SetHeader("Content-Type", "application/yaml").
   469  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest", "status").
   470  			Body(yamlBody).
   471  			DoRaw(context.TODO())
   472  		if err != nil {
   473  			t.Fatal(err, string(result))
   474  		}
   475  		obj, err := decodeYAML(result)
   476  		if err != nil {
   477  			t.Fatal(err)
   478  		}
   479  		if obj.GetName() != "mytest" {
   480  			t.Fatalf("expected mytest, got %s", obj.GetName())
   481  		}
   482  		if obj.GetAPIVersion() != apiVersion {
   483  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   484  		}
   485  		if obj.GetKind() != kind {
   486  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   487  		}
   488  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil {
   489  			t.Fatal(v, ok, err, string(result))
   490  		}
   491  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "status", "replicas"); v != 3 || !ok || err != nil {
   492  			t.Fatal(v, ok, err, string(result))
   493  		}
   494  		if obj.GetUID() != uid {
   495  			t.Fatalf("uid changed: %v vs %v", uid, obj.GetUID())
   496  		}
   497  	}
   498  
   499  	// Get at /scale
   500  	{
   501  		result, err := rest.Get().
   502  			SetHeader("Accept", "application/yaml").
   503  			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Versions[0].Name, noxuDefinition.Spec.Names.Plural, "mytest", "scale").
   504  			DoRaw(context.TODO())
   505  		if err != nil {
   506  			t.Fatal(err)
   507  		}
   508  		obj, err := decodeYAML(result)
   509  		if err != nil {
   510  			t.Fatal(err, string(result))
   511  		}
   512  		if obj.GetName() != "mytest" {
   513  			t.Fatalf("expected mytest, got %s", obj.GetName())
   514  		}
   515  		if obj.GetAPIVersion() != "autoscaling/v1" {
   516  			t.Fatalf("expected %s, got %s", apiVersion, obj.GetAPIVersion())
   517  		}
   518  		if obj.GetKind() != "Scale" {
   519  			t.Fatalf("expected %s, got %s", kind, obj.GetKind())
   520  		}
   521  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "spec", "replicas"); v != 3 || !ok || err != nil {
   522  			t.Fatal(v, ok, err, string(result))
   523  		}
   524  		if v, ok, err := unstructured.NestedFloat64(obj.Object, "status", "replicas"); v != 3 || !ok || err != nil {
   525  			t.Fatal(v, ok, err, string(result))
   526  		}
   527  	}
   528  }
   529  
   530  func decodeYAML(data []byte) (*unstructured.Unstructured, error) {
   531  	retval := &unstructured.Unstructured{Object: map[string]interface{}{}}
   532  	// ensure this isn't JSON
   533  	if json.Unmarshal(data, &retval.Object) == nil {
   534  		return nil, fmt.Errorf("data is JSON, not YAML: %s", string(data))
   535  	}
   536  	// ensure it is YAML
   537  	retval.Object = map[string]interface{}{}
   538  	if err := yaml.Unmarshal(data, &retval.Object); err != nil {
   539  		return nil, fmt.Errorf("error decoding YAML: %v\noriginal YAML: %s", err, string(data))
   540  	}
   541  	return retval, nil
   542  }
   543  

View as plain text