func TestExternalToInternalMapping(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &InternalOptionalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &ExternalOptionalExtensionType{}) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) table := []struct { obj runtime.Object encoded string }{ { &InternalOptionalExtensionType{Extension: nil}, `{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`, }, } for i, item := range table { gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a)) } } }
func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"} externalGVK := externalGV.WithKind("ObjectTest") s := runtime.NewScheme() s.AddKnownTypes(internalGV, &ObjectTest{}) s.AddKnownTypeWithName(externalGVK, &ObjectTestExternal{}) codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) obj, gvk, err := codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{}]}`), nil, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } test := obj.(*ObjectTest) if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != "{}" { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { t.Fatalf("unexpected kind: %#v", gvk) } obj, gvk, err = codec.Decode([]byte(`{"kind":"`+externalGVK.Kind+`","apiVersion":"`+externalGV.String()+`","items":[{"kind":"Other","apiVersion":"v1"}]}`), nil, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } test = obj.(*ObjectTest) if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { t.Fatalf("unexpected kind: %#v", gvk) } }
// EncodeParameters converts the provided object into the to version, then converts that object to url.Values. // Returns an error if conversion is not possible. func (c *parameterCodec) EncodeParameters(obj Object, to unversioned.GroupVersion) (url.Values, error) { gvk, _, err := c.typer.ObjectKind(obj) if err != nil { return nil, err } if to != gvk.GroupVersion() { out, err := c.convertor.ConvertToVersion(obj, to.String()) if err != nil { return nil, err } obj = out } return queryparams.Convert(obj) }
// SwaggerSchema retrieves and parses the swagger API schema the server supports. func (d *DiscoveryClient) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) { if version.IsEmpty() { return nil, fmt.Errorf("groupVersion cannot be empty") } groupList, err := d.ServerGroups() if err != nil { return nil, err } groupVersions := ExtractGroupVersions(groupList) // This check also takes care the case that kubectl is newer than the running endpoint if stringDoesntExistIn(version.String(), groupVersions) { return nil, fmt.Errorf("API version: %v is not supported by the server. Use one of: %v", version, groupVersions) } var path string if version == v1.SchemeGroupVersion { path = "/swaggerapi/api/" + version.Version } else { path = "/swaggerapi/apis/" + version.Group + "/" + version.Version } body, err := d.Get().AbsPath(path).Do().Raw() if err != nil { return nil, err } var schema swagger.ApiDeclaration err = json.Unmarshal(body, &schema) if err != nil { return nil, fmt.Errorf("got '%s': %v", string(body), err) } return &schema, nil }
func TestMetaValues(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"} s := runtime.NewScheme() s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) internalToExternalCalls := 0 externalToInternalCalls := 0 // Register functions to verify that scope.Meta() gets set correctly. err := s.AddConversionFuncs( func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { t.Logf("internal -> external") if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a { t.Fatalf("Expected '%v', got '%v'", e, a) } if e, a := externalGV.String(), scope.Meta().DestVersion; e != a { t.Fatalf("Expected '%v', got '%v'", e, a) } scope.Convert(&in.TestString, &out.TestString, 0) internalToExternalCalls++ return nil }, func(in *ExternalSimple, out *InternalSimple, scope conversion.Scope) error { t.Logf("external -> internal") if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) } if e, a := internalGV.String(), scope.Meta().DestVersion; e != a { t.Fatalf("Expected '%v', got '%v'", e, a) } scope.Convert(&in.TestString, &out.TestString, 0) externalToInternalCalls++ return nil }, ) if err != nil { t.Fatalf("unexpected error: %v", err) } simple := &InternalSimple{ TestString: "foo", } s.Log(t) out, err := s.ConvertToVersion(simple, externalGV.String()) if err != nil { t.Fatalf("unexpected error: %v", err) } internal, err := s.ConvertToVersion(out, internalGV.String()) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := simple, internal; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } if e, a := 1, internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := 1, externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } }
func TestScheme(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) // If set, would clear TypeMeta during conversion. //scheme.AddIgnoredConversionType(&TypeMeta{}, &TypeMeta{}) // test that scheme is an ObjectTyper var _ runtime.ObjectTyper = scheme internalToExternalCalls := 0 externalToInternalCalls := 0 // Register functions to verify that scope.Meta() gets set correctly. err := scheme.AddConversionFuncs( func(in *InternalSimple, out *ExternalSimple, scope conversion.Scope) error { if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) } if e, a := externalGV.String(), scope.Meta().DestVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) } scope.Convert(&in.TypeMeta, &out.TypeMeta, 0) scope.Convert(&in.TestString, &out.TestString, 0) internalToExternalCalls++ return nil }, func(in *ExternalSimple, out *InternalSimple, scope conversion.Scope) error { if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) } if e, a := internalGV.String(), scope.Meta().DestVersion; e != a { t.Errorf("Expected '%v', got '%v'", e, a) } scope.Convert(&in.TypeMeta, &out.TypeMeta, 0) scope.Convert(&in.TestString, &out.TestString, 0) externalToInternalCalls++ return nil }, ) if err != nil { t.Fatalf("unexpected error: %v", err) } codecs := serializer.NewCodecFactory(scheme) codec := codecs.LegacyCodec(externalGV) jsonserializer, _ := codecs.SerializerForFileExtension("json") simple := &InternalSimple{ TestString: "foo", } // Test Encode, Decode, DecodeInto, and DecodeToVersion obj := runtime.Object(simple) data, err := runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } obj2, err := runtime.Decode(codec, data) if err != nil { t.Fatal(err) } if _, ok := obj2.(*InternalSimple); !ok { t.Fatalf("Got wrong type") } if e, a := simple, obj2; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj3 := &InternalSimple{} if err := runtime.DecodeInto(codec, data, obj3); err != nil { t.Fatal(err) } // clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion // does not automatically clear TypeMeta anymore). simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()} if e, a := simple, obj3; !reflect.DeepEqual(e, a) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a) } obj4, err := runtime.Decode(jsonserializer, data) if err != nil { t.Fatal(err) } if _, ok := obj4.(*ExternalSimple); !ok { t.Fatalf("Got wrong type") } // Test Convert external := &ExternalSimple{} err = scheme.Convert(simple, external) if err != nil { t.Fatalf("Unexpected error: %v", err) } if e, a := simple.TestString, external.TestString; e != a { t.Errorf("Expected %v, got %v", e, a) } // Encode and Convert should each have caused an increment. if e, a := 2, internalToExternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } // DecodeInto and Decode should each have caused an increment because of a conversion if e, a := 2, externalToInternalCalls; e != a { t.Errorf("Expected %v, got %v", e, a) } }
func TestExtensionMapping(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("ExtensionType"), &InternalExtensionType{}) scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &InternalOptionalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("ExtensionType"), &ExternalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &ExternalOptionalExtensionType{}) // register external first when the object is the same in both schemes, so ObjectVersionAndKind reports the // external version. scheme.AddKnownTypeWithName(externalGV.WithKind("A"), &ExtensionA{}) scheme.AddKnownTypeWithName(externalGV.WithKind("B"), &ExtensionB{}) scheme.AddKnownTypeWithName(internalGV.WithKind("A"), &ExtensionA{}) scheme.AddKnownTypeWithName(internalGV.WithKind("B"), &ExtensionB{}) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) table := []struct { obj runtime.Object expected runtime.Object encoded string }{ { &InternalExtensionType{ Extension: runtime.NewEncodable(codec, &ExtensionA{TestString: "foo"}), }, &InternalExtensionType{ Extension: &runtime.Unknown{ RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), }, }, // apiVersion is set in the serialized object for easier consumption by clients `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}} `, }, { &InternalExtensionType{Extension: runtime.NewEncodable(codec, &ExtensionB{TestString: "bar"})}, &InternalExtensionType{ Extension: &runtime.Unknown{ RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), }, }, // apiVersion is set in the serialized object for easier consumption by clients `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}} `, }, { &InternalExtensionType{Extension: nil}, &InternalExtensionType{ Extension: nil, }, `{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":null} `, }, } for i, item := range table { gotEncoded, err := runtime.Encode(codec, item.obj) if err != nil { t.Errorf("unexpected error '%v' (%#v)", err, item.obj) } else if e, a := item.encoded, string(gotEncoded); e != a { t.Errorf("expected\n%#v\ngot\n%#v\n", e, a) } gotDecoded, err := runtime.Decode(codec, []byte(item.encoded)) if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) { t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a)) } } }
// NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of // preference. // - If version is provided, but not default config (explicitly requested via // commandline flag), and is unsupported by the server, print a warning to // stderr and try client's registered versions in order of preference. // - If version is config default, and the server does not support it, // return an error. func NegotiateVersion(client *Client, c *Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { var err error if client == nil { client, err = New(c) if err != nil { return nil, err } } clientVersions := sets.String{} for _, gv := range clientRegisteredGVs { clientVersions.Insert(gv.String()) } groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. return nil, err } versions := ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // If no version requested, use config version (may also be empty). // make a copy of the original so we don't risk mutating input here or in the returned value var preferredGV *unversioned.GroupVersion switch { case requestedGV != nil: t := *requestedGV preferredGV = &t case c.GroupVersion != nil: t := *c.GroupVersion preferredGV = &t } // If version explicitly requested verify that both client and server support it. // If server does not support warn, but try to negotiate a lower version. if preferredGV != nil { if !clientVersions.Has(preferredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientVersions) } if serverVersions.Has(preferredGV.String()) { return preferredGV, nil } // If we are using an explicit config version the server does not support, fail. if (c.GroupVersion != nil) && (*preferredGV == *c.GroupVersion) { return nil, fmt.Errorf("server does not support API version %q", preferredGV) } } for _, clientGV := range clientRegisteredGVs { if serverVersions.Has(clientGV.String()) { // Version was not explicitly requested in command config (--api-version). // Ok to fall back to a supported version with a warning. // TODO: caesarxuchao: enable the warning message when we have // proper fix. Please refer to issue #14895. // if len(version) != 0 { // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) // } t := clientGV return &t, nil } } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }
// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is // successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an // into that matches the serialized version. func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { versioned, isVersioned := into.(*runtime.VersionedObjects) if isVersioned { into = versioned.Last() } obj, gvk, err := c.serializer.Decode(data, defaultGVK, into) if err != nil { return nil, gvk, err } // if we specify a target, use generic conversion. if into != nil { if into == obj { if isVersioned { return versioned, gvk, nil } return into, gvk, nil } if err := c.convertor.Convert(obj, into); err != nil { return nil, gvk, err } if isVersioned { versioned.Objects = []runtime.Object{obj, into} return versioned, gvk, nil } return into, gvk, nil } // invoke a version conversion group := gvk.Group if defaultGVK != nil { group = defaultGVK.Group } var targetGV unversioned.GroupVersion if c.decodeVersion == nil { // convert to internal by default targetGV.Group = group targetGV.Version = runtime.APIVersionInternal } else { gv, ok := c.decodeVersion[group] if !ok { // unknown objects are left in their original version if isVersioned { versioned.Objects = []runtime.Object{obj} return versioned, gvk, nil } return obj, gvk, nil } targetGV = gv } if gvk.GroupVersion() == targetGV { if isVersioned { versioned.Objects = []runtime.Object{obj} return versioned, gvk, nil } return obj, gvk, nil } if isVersioned { // create a copy, because ConvertToVersion does not guarantee non-mutation of objects copied, err := c.copier.Copy(obj) if err != nil { copied = obj } versioned.Objects = []runtime.Object{copied} } // Convert if needed. out, err := c.convertor.ConvertToVersion(obj, targetGV.String()) if err != nil { return nil, gvk, err } if isVersioned { versioned.Objects = append(versioned.Objects, out) return versioned, gvk, nil } return out, gvk, nil }
// generateConvertMeta constructs the meta value we pass to Convert. func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) { flags, meta := s.converter.DefaultMeta(reflect.TypeOf(in)) meta.SrcVersion = srcGroupVersion.String() meta.DestVersion = destGroupVersion.String() return flags, meta }