func TestRESTMapperResourceSingularizer(t *testing.T) { testGroupVersion := schema.GroupVersion{Group: "tgroup", Version: "test"} testCases := []struct { Kind string Plural string Singular string }{ {Kind: "Pod", Plural: "pods", Singular: "pod"}, {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, {Kind: "Status", Plural: "statuses", Singular: "status"}, {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, // TODO this test is broken. This updates to reflect actual behavior. Kinds are expected to be singular // old (incorrect), coment: Don't add extra s if the original object is already plural {Kind: "lowercases", Plural: "lowercaseses", Singular: "lowercases"}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}, fakeInterfaces) // create singular/plural mapping mapper.Add(testGroupVersion.WithKind(testCase.Kind), RESTScopeNamespace) singular, err := mapper.ResourceSingularizer(testCase.Plural) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) } if singular != testCase.Singular { t.Errorf("%d: mismatched singular: got %v, expected %v", i, singular, testCase.Singular) } } }
func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.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.Raw) != "{}" || unk.ContentType != runtime.ContentTypeJSON { 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.Raw) != `{"kind":"Other","apiVersion":"v1"}` || unk.ContentType != runtime.ContentTypeJSON { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { t.Fatalf("unexpected kind: %#v", gvk) } }
// SwaggerSchema retrieves and parses the swagger API schema the server supports. func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) { if version.Empty() { return nil, fmt.Errorf("groupVersion cannot be empty") } groupList, err := d.ServerGroups() if err != nil { return nil, err } groupVersions := unversioned.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 len(d.LegacyPrefix) > 0 && version == v1.SchemeGroupVersion { path = "/swaggerapi" + d.LegacyPrefix + "/" + version.Version } else { path = "/swaggerapi/apis/" + version.Group + "/" + version.Version } body, err := d.restClient.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 (c *RESTClient) request(verb string) *restclient.Request { config := restclient.ContentConfig{ ContentType: runtime.ContentTypeJSON, GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: c.NegotiatedSerializer, } groupName := api.GroupName if c.GroupName != "" { groupName = c.GroupName } ns := c.NegotiatedSerializer info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON) internalVersion := schema.GroupVersion{ Group: registered.GroupOrDie(groupName).GroupVersion.Group, Version: runtime.APIVersionInternal, } internalVersion.Version = runtime.APIVersionInternal serializers := restclient.Serializers{ Encoder: ns.EncoderForVersion(info.Serializer, registered.GroupOrDie(api.GroupName).GroupVersion), Decoder: ns.DecoderToVersion(info.Serializer, internalVersion), } if info.StreamSerializer != nil { serializers.StreamingSerializer = info.StreamSerializer.Serializer serializers.Framer = info.StreamSerializer.Framer } return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", config, serializers, nil, nil) }
// serverPreferredResources returns the supported resources with the version preferred by the // server. If namespaced is true, only namespaced resources will be returned. func (d *DiscoveryClient) serverPreferredResources(namespaced bool) ([]schema.GroupVersionResource, error) { // retry in case the groups supported by the server change after ServerGroup() returns. const maxRetries = 2 var failedGroups map[schema.GroupVersion]error var results []schema.GroupVersionResource var resources map[schema.GroupResource]string RetrieveGroups: for i := 0; i < maxRetries; i++ { results = []schema.GroupVersionResource{} resources = map[schema.GroupResource]string{} failedGroups = make(map[schema.GroupVersion]error) serverGroupList, err := d.ServerGroups() if err != nil { return results, err } for _, apiGroup := range serverGroupList.Groups { versions := apiGroup.Versions for _, version := range versions { groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion) if err != nil { if i < maxRetries-1 { continue RetrieveGroups } failedGroups[groupVersion] = err continue } for _, apiResource := range apiResourceList.APIResources { // ignore the root scoped resources if "namespaced" is true. if namespaced && !apiResource.Namespaced { continue } if strings.Contains(apiResource.Name, "/") { continue } gvr := groupVersion.WithResource(apiResource.Name) if _, ok := resources[gvr.GroupResource()]; ok { if gvr.Version != apiGroup.PreferredVersion.Version { continue } // remove previous entry, because it will be replaced with a preferred one for i := range results { if results[i].GroupResource() == gvr.GroupResource() { results = append(results[:i], results[i+1:]...) } } } resources[gvr.GroupResource()] = gvr.Version results = append(results, gvr) } } } if len(failedGroups) == 0 { return results, nil } } return results, &ErrGroupDiscoveryFailed{Groups: failedGroups} }
// 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 and the server does not support it, // return an error. func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion, clientRegisteredGVs []schema.GroupVersion) (*schema.GroupVersion, error) { 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 := metav1.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // 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 requiredGV != nil { if !clientVersions.Has(requiredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions) } // If the server supports no versions, then we should just use the preferredGV // This can happen because discovery fails due to 403 Forbidden errors if len(serverVersions) == 0 { return requiredGV, nil } if serverVersions.Has(requiredGV.String()) { return requiredGV, nil } // If we are using an explicit config version the server does not support, fail. return nil, fmt.Errorf("server does not support API version %q", requiredGV) } 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 } } // if we have no server versions and we have no required version, choose the first clientRegisteredVersion if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 { return &clientRegisteredGVs[0], nil } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }
// RunExplain executes the appropriate steps to print a model's documentation func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, args []string) error { if len(args) == 0 { fmt.Fprint(cmdErr, "You must specify the type of resource to explain. ", valid_resources) return cmdutil.UsageError(cmd, "Required resource not specified.") } if len(args) > 1 { return cmdutil.UsageError(cmd, "We accept only this format: explain RESOURCE") } recursive := cmdutil.GetFlagBool(cmd, "recursive") apiVersionString := cmdutil.GetFlagString(cmd, "api-version") apiVersion := schema.GroupVersion{} mapper, _ := f.Object() // TODO: After we figured out the new syntax to separate group and resource, allow // the users to use it in explain (kubectl explain <group><syntax><resource>). // Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax. inModel, fieldsPath, err := kubectl.SplitAndParseResourceRequest(args[0], mapper) if err != nil { return err } // TODO: We should deduce the group for a resource by discovering the supported resources at server. fullySpecifiedGVR, groupResource := schema.ParseResourceArg(inModel) gvk := schema.GroupVersionKind{} if fullySpecifiedGVR != nil { gvk, _ = mapper.KindFor(*fullySpecifiedGVR) } if gvk.Empty() { gvk, err = mapper.KindFor(groupResource.WithVersion("")) if err != nil { return err } } if len(apiVersionString) == 0 { groupMeta, err := registered.Group(gvk.Group) if err != nil { return err } apiVersion = groupMeta.GroupVersion } else { apiVersion, err = schema.ParseGroupVersion(apiVersionString) if err != nil { return nil } } schema, err := f.SwaggerSchema(apiVersion.WithKind(gvk.Kind)) if err != nil { return err } return kubectl.PrintModelDescription(inModel, fieldsPath, out, schema, recursive) }
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}, unmatchedVersionInterfaces) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) _, err := mapper.RESTMapping(internalObjectGK, expectedGroupVersion1.Version) if err == nil { t.Errorf("unexpected non-error") } }
// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules. // Whenever an object of this type is serialized, it is serialized with the provided group version and is not // converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an // API group and version that would never be updated. // // TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into // every version with particular schemas. Resolve this method at that point. func (s *Scheme) AddUnversionedTypes(version schema.GroupVersion, types ...Object) { s.AddKnownTypes(version, types...) for _, obj := range types { t := reflect.TypeOf(obj).Elem() gvk := version.WithKind(t.Name()) s.unversionedTypes[t] = gvk if _, ok := s.unversionedKinds[gvk.Kind]; ok { panic(fmt.Sprintf("%v has already been registered as unversioned kind %q - kind name must be unique", reflect.TypeOf(t), gvk.Kind)) } s.unversionedKinds[gvk.Kind] = t } }
func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { mappings, err := m.Delegate.RESTMappings(gk) if err != nil { return nil, err } // any versions the user provides take priority priorities := m.KindPriority if len(versions) > 0 { priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions)) for _, version := range versions { gv := schema.GroupVersion{ Version: version, Group: gk.Group, } priorities = append(priorities, gv.WithKind(AnyKind)) } priorities = append(priorities, m.KindPriority...) } remaining := append([]*RESTMapping{}, mappings...) for _, pattern := range priorities { var matching []*RESTMapping for _, m := range remaining { if kindMatches(pattern, m.GroupVersionKind) { matching = append(matching, m) } } switch len(matching) { case 0: // if you have no matches, then nothing matched this pattern just move to the next continue case 1: // one match, return return matching[0], nil default: // more than one match, use the matched hits as the list moving to the next pattern. // this way you can have a series of selection criteria remaining = matching } } if len(remaining) == 1 { return remaining[0], nil } var kinds []schema.GroupVersionKind for _, m := range mappings { kinds = append(kinds, m.GroupVersionKind) } return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} }
// AddToGroupVersion registers the watch external and internal kinds with the scheme, and ensures the proper // conversions are in place. func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) { scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &Event{}) scheme.AddKnownTypeWithName( schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}.WithKind(WatchEventKind), &InternalEvent{}, ) scheme.AddConversionFuncs( Convert_versioned_Event_to_watch_Event, Convert_versioned_InternalEvent_to_versioned_Event, Convert_watch_Event_to_versioned_Event, Convert_versioned_Event_to_versioned_InternalEvent, ) }
func TestExternalToInternalMapping(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.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, diff.ObjectGoPrintSideBySide(e, a)) } } }
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for // unstructred objects based on discovery information. func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *UnstructuredObjectTyper { dot := &UnstructuredObjectTyper{registered: make(map[schema.GroupVersionKind]bool)} for _, group := range groupResources { for _, discoveryVersion := range group.Group.Versions { resources, ok := group.VersionedResources[discoveryVersion.Version] if !ok { continue } gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} for _, resource := range resources { dot.registered[gv.WithKind(resource.Kind)] = true } } } return dot }
func TestKindToResource(t *testing.T) { testCases := []struct { Kind string Plural, Singular string }{ {Kind: "Pod", Plural: "pods", Singular: "pod"}, {Kind: "ReplicationController", Plural: "replicationcontrollers", Singular: "replicationcontroller"}, // Add "ies" when ending with "y" {Kind: "ImageRepository", Plural: "imagerepositories", Singular: "imagerepository"}, // Add "es" when ending with "s" {Kind: "miss", Plural: "misses", Singular: "miss"}, // Add "s" otherwise {Kind: "lowercase", Plural: "lowercases", Singular: "lowercase"}, } for i, testCase := range testCases { version := schema.GroupVersion{} plural, singular := KindToResource(version.WithKind(testCase.Kind)) if singular != version.WithResource(testCase.Singular) || plural != version.WithResource(testCase.Plural) { t.Errorf("%d: unexpected plural and singular: %v %v", i, plural, singular) } } }
// AddKnownTypes registers all types passed in 'types' as being members of version 'version'. // All objects passed to types should be pointers to structs. The name that go reports for // the struct becomes the "kind" field when encoding. Version may not be empty - use the // APIVersionInternal constant if you have a type that does not have a formal version. func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) { if len(gv.Version) == 0 { panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0])) } for _, obj := range types { t := reflect.TypeOf(obj) if t.Kind() != reflect.Ptr { panic("All types must be pointers to structs.") } t = t.Elem() if t.Kind() != reflect.Struct { panic("All types must be pointers to structs.") } gvk := gv.WithKind(t.Name()) s.gvkToType[gvk] = t s.typeToGVK[t] = append(s.typeToGVK[t], gvk) } }
func TestRESTMapper(t *testing.T) { gv := schema.GroupVersion{Group: "", Version: "v1"} rcGVK := gv.WithKind("ReplicationController") podTemplateGVK := gv.WithKind("PodTemplate") if gvk, err := registered.RESTMapper().KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } if m, err := registered.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(podTemplateGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != podTemplateGVK || m.Resource != "podtemplates" { t.Errorf("unexpected version mapping: %#v %v", m, err) } for _, version := range registered.GroupOrDie(internal.GroupName).GroupVersions { mapping, err := registered.GroupOrDie(internal.GroupName).RESTMapper.RESTMapping(rcGVK.GroupKind(), version.Version) if err != nil { t.Errorf("unexpected error: %v", err) } if mapping.Resource != "replicationControllers" && mapping.Resource != "replicationcontrollers" { t.Errorf("incorrect resource name: %#v", mapping) } if mapping.GroupVersionKind.GroupVersion() != version { t.Errorf("incorrect version: %v", mapping) } interfaces, _ := registered.GroupOrDie(internal.GroupName).InterfacesFor(version) if mapping.ObjectConvertor != interfaces.ObjectConvertor { t.Errorf("unexpected: %#v, expected: %#v", mapping, interfaces) } rc := &internal.ReplicationController{ObjectMeta: internal.ObjectMeta{Name: "foo"}} name, err := mapping.MetadataAccessor.Name(rc) if err != nil { t.Errorf("unexpected error: %v", err) } if name != "foo" { t.Errorf("unable to retrieve object meta with: %v", mapping.MetadataAccessor) } } }
// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then // converts that object to into (if necessary). Returns an error if the operation cannot be completed. func (c *parameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into Object) error { if len(parameters) == 0 { return nil } targetGVKs, _, err := c.typer.ObjectKinds(into) if err != nil { return err } targetGVK := targetGVKs[0] if targetGVK.GroupVersion() == from { return c.convertor.Convert(¶meters, into, nil) } input, err := c.creator.New(from.WithKind(targetGVK.Kind)) if err != nil { return err } if err := c.convertor.Convert(¶meters, input, nil); err != nil { return err } return c.convertor.Convert(input, into, nil) }
func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) { internalGV := schema.GroupVersion{Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Version: "v1"} s := runtime.NewScheme() // create two names internally, with TestType1 being preferred s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &TestType1{}) s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &TestType1{}) // create two names externally, with TestType1 being preferred s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &ExternalTestType1{}) ext := &ExternalTestType1{} ext.APIVersion = "v1" ext.ObjectKind = "OtherType1" ext.A = "test" data, err := json.Marshal(ext) if err != nil { t.Fatalf("unexpected error: %v", err) } expect := &TestType1{A: "test"} codec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})).LegacyCodec(schema.GroupVersion{Version: "v1"}) obj, err := runtime.Decode(codec, data) if err != nil { t.Fatalf("unexpected error: %v", err) } if !semantic.DeepEqual(expect, obj) { t.Errorf("unexpected object: %#v", obj) } into := &TestType1{} if err := runtime.DecodeInto(codec, data, into); err != nil { t.Fatalf("unexpected error: %v", err) } if !semantic.DeepEqual(expect, into) { t.Errorf("unexpected object: %#v", obj) } }
func TestEncode(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) test := &InternalSimple{ TestString: "I'm the same", } obj := runtime.Object(test) data, err := runtime.Encode(codec, obj) obj2, gvk, err2 := codec.Decode(data, nil, nil) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) } if _, ok := obj2.(*InternalSimple); !ok { t.Fatalf("Got wrong type") } if !reflect.DeepEqual(obj2, test) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) } if !reflect.DeepEqual(gvk, &schema.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "Simple"}) { t.Errorf("unexpected gvk returned by decode: %#v", gvk) } }
// TestDeepCopyOfRuntimeObject checks to make sure that runtime.Objects's can be passed through DeepCopy with fidelity func TestDeepCopyOfRuntimeObject(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "v1test"} embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") s := runtime.NewScheme() s.AddKnownTypes(internalGV, &EmbeddedTest{}) s.AddKnownTypeWithName(embeddedTestExternalGVK, &EmbeddedTestExternal{}) original := &EmbeddedTest{ ID: "outer", Object: &EmbeddedTest{ ID: "inner", }, } codec := serializer.NewCodecFactory(s).LegacyCodec(externalGV) originalData, err := runtime.Encode(codec, original) if err != nil { t.Errorf("unexpected error: %v", err) } t.Logf("originalRole = %v\n", string(originalData)) copyOfOriginal, err := s.DeepCopy(original) if err != nil { t.Fatalf("unexpected error: %v", err) } copiedData, err := runtime.Encode(codec, copyOfOriginal.(runtime.Object)) if err != nil { t.Errorf("unexpected error: %v", err) } t.Logf("copyOfRole = %v\n", string(copiedData)) if !reflect.DeepEqual(original, copyOfOriginal) { t.Errorf("expected \n%v\n, got \n%v", string(originalData), string(copiedData)) } }
// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object // returned by the response implements rest.ResourceStreamer that interface will be used to render the // response. The Accept header and current API version will be passed in, and the output will be copied // directly to the response body. If content type is returned it is used, otherwise the content type will // be "application/octet-stream". All other objects are sent to standard JSON serialization. func write(statusCode int, gv schema.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) { stream, ok := object.(rest.ResourceStreamer) if !ok { writeNegotiated(s, gv, w, req, statusCode, object) return } out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept")) if err != nil { errorNegotiated(err, s, gv, w, req) return } if out == nil { // No output provided - return StatusNoContent w.WriteHeader(http.StatusNoContent) return } defer out.Close() if wsstream.IsWebSocketRequest(req) { r := wsstream.NewReader(out, true, wsstream.NewDefaultReaderProtocols()) if err := r.Copy(w, req); err != nil { utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err)) } return } if len(contentType) == 0 { contentType = "application/octet-stream" } w.Header().Set("Content-Type", contentType) w.WriteHeader(statusCode) writer := w.(io.Writer) if flush { writer = flushwriter.Wrap(w) } io.Copy(writer, out) }
func TestRESTMapperVersionAndKindForResource(t *testing.T) { testGroup := "test.group" testVersion := "test" testGroupVersion := schema.GroupVersion{Group: testGroup, Version: testVersion} testCases := []struct { Resource schema.GroupVersionResource GroupVersionToRegister schema.GroupVersion ExpectedGVK schema.GroupVersionKind Err bool }{ {Resource: schema.GroupVersionResource{Resource: "internalobjec"}, Err: true}, {Resource: schema.GroupVersionResource{Resource: "internalObjec"}, Err: true}, {Resource: schema.GroupVersionResource{Resource: "internalobject"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, {Resource: schema.GroupVersionResource{Resource: "internalobjects"}, ExpectedGVK: testGroupVersion.WithKind("InternalObject")}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper([]schema.GroupVersion{testGroupVersion}, fakeInterfaces) if len(testCase.ExpectedGVK.Kind) != 0 { mapper.Add(testCase.ExpectedGVK, RESTScopeNamespace) } actualGVK, err := mapper.KindFor(testCase.Resource) hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: unexpected error behavior %t: %v", i, testCase.Err, err) continue } if err != nil { continue } if actualGVK != testCase.ExpectedGVK { t.Errorf("%d: unexpected version and kind: e=%s a=%s", i, testCase.ExpectedGVK, actualGVK) } } }
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { expectedGroupVersion1 := schema.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := schema.GroupVersion{Group: "tgroup", Version: "test2"} expectedGroupVersion3 := schema.GroupVersion{Group: "tgroup", Version: "test3"} internalObjectGK := schema.GroupKind{Group: "tgroup", Kind: "InternalObject"} otherObjectGK := schema.GroupKind{Group: "tgroup", Kind: "OtherObject"} mapper := NewDefaultRESTMapper([]schema.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}, fakeInterfaces) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace) mapper.Add(expectedGroupVersion2.WithKind("OtherObject"), RESTScopeNamespace) // pick default matching object kind based on search order mapping, err := mapper.RESTMapping(otherObjectGK) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != "otherobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { t.Errorf("unexpected mapping: %#v", mapping) } mapping, err = mapper.RESTMapping(internalObjectGK) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != "internalobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion1 { t.Errorf("unexpected mapping: %#v", mapping) } // mismatch of version mapping, err = mapper.RESTMapping(internalObjectGK, expectedGroupVersion2.Version) if err == nil { t.Errorf("unexpected non-error") } mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion1.Version) if err == nil { t.Errorf("unexpected non-error") } // not in the search versions mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version) if err == nil { t.Errorf("unexpected non-error") } // explicit search order mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion1.Version) if err == nil { t.Errorf("unexpected non-error") } mapping, err = mapper.RESTMapping(otherObjectGK, expectedGroupVersion3.Version, expectedGroupVersion2.Version) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != "otherobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { t.Errorf("unexpected mapping: %#v", mapping) } }
// AsVersionedObjects converts a list of infos into versioned objects. The provided // version will be preferred as the conversion target, but the Object's mapping version will be // used if that version is not present. func AsVersionedObjects(infos []*Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) { objects := []runtime.Object{} for _, info := range infos { if info.Object == nil { continue } // TODO: use info.VersionedObject as the value? switch obj := info.Object.(type) { case *extensions.ThirdPartyResourceData: objects = append(objects, &runtime.Unknown{Raw: obj.Data}) continue } // objects that are not part of api.Scheme must be converted to JSON // TODO: convert to map[string]interface{}, attach to runtime.Unknown? if !version.Empty() { if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) { // TODO: ideally this would encode to version, but we don't expose multiple codecs here. data, err := runtime.Encode(encoder, info.Object) if err != nil { return nil, err } // TODO: Set ContentEncoding and ContentType. objects = append(objects, &runtime.Unknown{Raw: data}) continue } } converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { return nil, err } objects = append(objects, converted) } return objects, nil }
func TestMetaValues(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: "__internal"} externalGV := schema.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") scope.Convert(&in.TestString, &out.TestString, 0) internalToExternalCalls++ return nil }, func(in *ExternalSimple, out *InternalSimple, scope conversion.Scope) error { t.Logf("external -> internal") 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) if err != nil { t.Fatalf("unexpected error: %v", err) } internal, err := s.ConvertToVersion(out, internalGV) 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 TestThirdPartyResourceDataListEncoding(t *testing.T) { gv := schema.GroupVersion{Group: "stable.foo.faz", Version: "v1"} gvk := gv.WithKind("Bar") e := &thirdPartyResourceDataEncoder{delegate: testapi.Extensions.Codec(), gvk: gvk} subject := &extensions.ThirdPartyResourceDataList{} buf := bytes.NewBuffer([]byte{}) err := e.Encode(subject, buf) if err != nil { t.Errorf("encoding unexpected error: %v", err) } targetOutput := struct { Kind string `json:"kind,omitempty"` Items []json.RawMessage `json:"items"` Metadata unversioned.ListMeta `json:"metadata,omitempty"` APIVersion string `json:"apiVersion,omitempty"` }{} err = json.Unmarshal(buf.Bytes(), &targetOutput) if err != nil { t.Errorf("unmarshal unexpected error: %v", err) } if expectedKind := gvk.Kind + "List"; expectedKind != targetOutput.Kind { t.Errorf("unexpected kind on list got %s expected %s", targetOutput.Kind, expectedKind) } if targetOutput.Metadata != subject.ListMeta { t.Errorf("metadata mismatch %v != %v", targetOutput.Metadata, subject.ListMeta) } if targetOutput.APIVersion != gv.String() { t.Errorf("apiversion mismatch %v != %v", targetOutput.APIVersion, gv.String()) } }
// Returns a new Scheme set up with the test objects. func GetTestScheme() *runtime.Scheme { internalGV := schema.GroupVersion{Version: "__internal"} externalGV := schema.GroupVersion{Version: "v1"} alternateExternalGV := schema.GroupVersion{Group: "custom", Version: "v1"} differentExternalGV := schema.GroupVersion{Group: "other", Version: "v2"} s := runtime.NewScheme() // Ordinarily, we wouldn't add TestType2, but because this is a test and // both types are from the same package, we need to get it into the system // so that converter will match it with ExternalType2. s.AddKnownTypes(internalGV, &TestType1{}, &TestType2{}, &ExternalInternalSame{}) s.AddKnownTypes(externalGV, &ExternalInternalSame{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{}) s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType4"), &ExternalTestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType3"), &ExternalTestType1{}) s.AddKnownTypeWithName(alternateExternalGV.WithKind("TestType5"), &ExternalTestType1{}) s.AddKnownTypeWithName(differentExternalGV.WithKind("TestType1"), &ExternalTestType1{}) s.AddUnversionedTypes(externalGV, &UnversionedType{}) return s }
func TestScheme(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.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 { 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 { 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) info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) jsonserializer := info.Serializer 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, nil) 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 TestUnversionedTypes(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.GroupVersion{Group: "test.group", Version: "testExternal"} otherGV := schema.GroupVersion{Group: "group", Version: "other"} scheme := runtime.NewScheme() scheme.AddUnversionedTypes(externalGV, &InternalSimple{}) scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) scheme.AddKnownTypeWithName(otherGV.WithKind("Simple"), &ExternalSimple{}) codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) if unv, ok := scheme.IsUnversioned(&InternalSimple{}); !unv || !ok { t.Fatalf("type not unversioned and in scheme: %t %t", unv, ok) } kinds, _, err := scheme.ObjectKinds(&InternalSimple{}) if err != nil { t.Fatal(err) } kind := kinds[0] if kind != externalGV.WithKind("InternalSimple") { t.Fatalf("unexpected: %#v", kind) } test := &InternalSimple{ TestString: "I'm the same", } obj := runtime.Object(test) data, err := runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } obj2, gvk, err := codec.Decode(data, nil, nil) if err != nil { t.Fatal(err) } if _, ok := obj2.(*InternalSimple); !ok { t.Fatalf("Got wrong type") } if !reflect.DeepEqual(obj2, test) { t.Errorf("Expected:\n %#v,\n Got:\n %#v", test, obj2) } // object is serialized as an unversioned object (in the group and version it was defined in) if !reflect.DeepEqual(gvk, &schema.GroupVersionKind{Group: "test.group", Version: "testExternal", Kind: "InternalSimple"}) { t.Errorf("unexpected gvk returned by decode: %#v", gvk) } // when serialized to a different group, the object is kept in its preferred name codec = serializer.NewCodecFactory(scheme).LegacyCodec(otherGV) data, err = runtime.Encode(codec, obj) if err != nil { t.Fatal(err) } if string(data) != `{"apiVersion":"test.group/testExternal","kind":"InternalSimple","testString":"I'm the same"}`+"\n" { t.Errorf("unexpected data: %s", data) } }
func TestExtensionMapping(t *testing.T) { internalGV := schema.GroupVersion{Group: "test.group", Version: runtime.APIVersionInternal} externalGV := schema.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{ Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), ContentType: runtime.ContentTypeJSON, }, }, // 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{ Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), ContentType: runtime.ContentTypeJSON, }, }, // 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, diff.ObjectGoPrintSideBySide(e, a)) } } }