func TestRESTMapper(t *testing.T) { gv := unversioned.GroupVersion{Group: "componentconfig", Version: "v1alpha1"} proxyGVK := gv.WithKind("KubeProxyConfiguration") if gvk, err := latest.GroupOrDie("componentconfig").RESTMapper.KindFor("kubeproxyconfiguration"); err != nil || gvk != proxyGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } if m, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping(proxyGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != proxyGVK || m.Resource != "kubeproxyconfigurations" { t.Errorf("unexpected version mapping: %#v %v", m, err) } for _, version := range latest.GroupOrDie("componentconfig").Versions { mapping, err := latest.GroupOrDie("componentconfig").RESTMapper.RESTMapping(proxyGVK.GroupKind(), version) if err != nil { t.Errorf("unexpected error: %v", err) continue } if mapping.Resource != "kubeproxyconfigurations" { t.Errorf("incorrect resource name: %#v", mapping) } if mapping.GroupVersionKind.GroupVersion() != gv { t.Errorf("incorrect groupVersion: %v", mapping) } interfaces, _ := latest.GroupOrDie("componentconfig").InterfacesFor(gv.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } } }
// OutputVersionFromGroupVersion returns the preferred output version for generic content (JSON, YAML, or templates) func OutputVersionFromGroupVersion(cmd *cobra.Command, defaultGV *unversioned.GroupVersion) string { outputVersion := GetFlagString(cmd, "output-version") if len(outputVersion) == 0 && defaultGV != nil { outputVersion = defaultGV.String() } return outputVersion }
func TestEncode(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddInternalGroupVersion(internalGV) scheme.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{}) scheme.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{}) codec := runtime.CodecFor(scheme, externalGV.String()) test := &InternalSimple{ TestString: "I'm the same", } obj := runtime.Object(test) data, err := runtime.Encode(codec, obj) obj2, err2 := runtime.Decode(codec, data) 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) } }
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.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) } }
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, diff.ObjectGoPrintSideBySide(e, a)) } } }
func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"} externalGVK := externalGV.WithKind("ObjectTest") s := runtime.NewScheme() s.AddInternalGroupVersion(internalGV) s.AddKnownTypes(internalGV, &ObjectTest{}) s.AddKnownTypeWithName(externalGVK, &ObjectTestExternal{}) obj, err := s.Decode([]byte(`{"kind":"` + externalGVK.Kind + `","apiVersion":"` + externalGV.String() + `","items":[{}]}`)) 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]) } obj, err = s.Decode([]byte(`{"kind":"` + externalGVK.Kind + `","apiVersion":"` + externalGV.String() + `","items":[{"kind":"Other","apiVersion":"v1"}]}`)) if err != nil { t.Fatalf("unexpected error: %v", err) } test = obj.(*ObjectTest) if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "Other" || unk.APIVersion != "v1" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` { t.Fatalf("unexpected object: %#v", test.Items[0]) } }
func TestExternalToInternalMapping(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "testExternal"} scheme := runtime.NewScheme() scheme.AddInternalGroupVersion(internalGV) scheme.AddKnownTypeWithName(internalGV.WithKind("OptionalExtensionType"), &InternalOptionalExtensionType{}) scheme.AddKnownTypeWithName(externalGV.WithKind("OptionalExtensionType"), &ExternalOptionalExtensionType{}) table := []struct { obj runtime.Object encoded string }{ { &InternalOptionalExtensionType{Extension: runtime.EmbeddedObject{Object: nil}}, `{"kind":"OptionalExtensionType","apiVersion":"` + externalGV.String() + `"}`, }, } for _, item := range table { gotDecoded, err := runtime.Decode(scheme, []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) { var eEx, aEx runtime.Object if obj, ok := e.(*InternalOptionalExtensionType); ok { eEx = obj.Extension.Object } if obj, ok := a.(*InternalOptionalExtensionType); ok { aEx = obj.Extension.Object } t.Errorf("expected %#v, got %#v (%#v, %#v)", e, a, eEx, aEx) } } }
// applyDefaults roundtrips the config to v1 and back to ensure proper defaults are set. func applyDefaults(config runtime.Object, version unversioned.GroupVersion) (runtime.Object, error) { ext, err := configapi.Scheme.ConvertToVersion(config, version.String()) if err != nil { return nil, err } return configapi.Scheme.ConvertToVersion(ext, configapi.SchemeGroupVersion.String()) }
// generateConvertMeta constructs the meta value we pass to Convert. func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) { t := reflect.TypeOf(in) return s.converter.inputDefaultFlags[t], &Meta{ SrcVersion: srcGroupVersion.String(), DestVersion: destGroupVersion.String(), KeyNameMapping: s.converter.inputFieldMappingFuncs[t], } }
// 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 *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.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 := unversioned.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) }
func TestRESTMapperReportsErrorOnBadVersion(t *testing.T) { expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"} mapper := NewDefaultRESTMapper([]unversioned.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}, unmatchedVersionInterfaces) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace, false) _, err := mapper.RESTMapping("InternalObject", expectedGroupVersion1.String()) if err == nil { t.Errorf("unexpected non-error") } }
// ClientConfigForVersion returns the correct config for a server func (c *clientCache) ClientConfigForVersion(version *unversioned.GroupVersion) (*kclient.Config, error) { if c.defaultConfig == nil { config, err := c.loader.ClientConfig() if err != nil { return nil, err } c.defaultConfig = config } // TODO: have a better config copy method cacheKey := "" if version != nil { cacheKey = version.String() } if config, ok := c.configs[cacheKey]; ok { return config, nil } if c.negotiatingClient == nil { // TODO: We want to reuse the upstream negotiation logic, which is coupled // to a concrete kube Client. The negotiation will ultimately try and // build an unversioned URL using the config prefix to ask for supported // server versions. If we use the default kube client config, the prefix // will be /api, while we need to use the OpenShift prefix to ask for the // OpenShift server versions. For now, set OpenShift defaults on the // config to ensure the right prefix gets used. The client cache and // negotiation logic should be refactored upstream to support downstream // reuse so that we don't need to do any of this cache or negotiation // duplication. negotiatingConfig := *c.defaultConfig client.SetOpenShiftDefaults(&negotiatingConfig) negotiatingClient, err := kclient.New(&negotiatingConfig) if err != nil { return nil, err } c.negotiatingClient = negotiatingClient } config := *c.defaultConfig negotiatedVersion, err := negotiateVersion(c.negotiatingClient, &config, version, latest.Versions) if err != nil { return nil, err } config.GroupVersion = negotiatedVersion client.SetOpenShiftDefaults(&config) c.configs[cacheKey] = &config // `version` does not necessarily equal `config.Version`. However, we know that we call this method again with // `config.Version`, we should get the the config we've just built. configCopy := config c.configs[config.GroupVersion.String()] = &configCopy return &config, nil }
func TestRESTMapperVersionAndKindForResource(t *testing.T) { testGroup := "test.group" testVersion := "test" testGroupVersion := unversioned.GroupVersion{Group: testGroup, Version: testVersion} testCases := []struct { Resource string GroupVersionToRegister unversioned.GroupVersion ExpectedGVK unversioned.GroupVersionKind MixedCase bool Err bool }{ {Resource: "internalobjec", Err: true, GroupVersionToRegister: testGroupVersion}, {Resource: "internalObjec", Err: true, GroupVersionToRegister: testGroupVersion}, {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, {Resource: "internalobject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, {Resource: "internalobjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, {Resource: "internalObject", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, {Resource: "internalObjects", GroupVersionToRegister: testGroupVersion, MixedCase: true, ExpectedGVK: unversioned.NewGroupVersionKind(testGroupVersion, "InternalObject")}, } for i, testCase := range testCases { mapper := NewDefaultRESTMapper(testGroup, []string{testGroupVersion.String()}, fakeInterfaces) mapper.Add(RESTScopeNamespace, testCase.ExpectedGVK.Kind, testCase.GroupVersionToRegister.String(), testCase.MixedCase) v, k, err := mapper.VersionAndKindForResource(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 } actualGV, err := unversioned.ParseGroupVersion(v) if err != nil { t.Errorf("%d: unexpected error: %v", i, err) continue } actualGVK := unversioned.NewGroupVersionKind(actualGV, k) if actualGVK != testCase.ExpectedGVK { t.Errorf("%d: unexpected version and kind: e=%s a=%s", i, testCase.ExpectedGVK, actualGVK) } } }
// 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) }
// getAPIGroupVersionOverrides builds the overrides in the format expected by master.Config.APIGroupVersionOverrides func getAPIGroupVersionOverrides(options configapi.MasterConfig) map[string]genericapiserver.APIGroupVersionOverride { apiGroupVersionOverrides := map[string]genericapiserver.APIGroupVersionOverride{} for group := range options.KubernetesMasterConfig.DisabledAPIGroupVersions { for _, version := range configapi.GetDisabledAPIVersionsForGroup(*options.KubernetesMasterConfig, group) { gv := unversioned.GroupVersion{Group: group, Version: version} if group == "" { // TODO: when rebasing, check the parseRuntimeConfig impl to make sure we're still building the right magic container // Create "disabled" key for v1 identically to k8s.io/kubernetes/cmd/kube-apiserver/app/server.go#parseRuntimeConfig gv.Group = "api" } apiGroupVersionOverrides[gv.String()] = genericapiserver.APIGroupVersionOverride{Disable: true} } } return apiGroupVersionOverrides }
// SwaggerSchema retrieves and parses the swagger API schema the server supports. func (c *Client) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) { if version.IsEmpty() { return nil, fmt.Errorf("groupVersion cannot be empty") } groupList, err := c.Discovery().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 := c.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 TestRESTMapper(t *testing.T) { gv := unversioned.GroupVersion{Group: "", Version: "v1"} rcGVK := gv.WithKind("ReplicationController") podTemplateGVK := gv.WithKind("PodTemplate") if gvk, err := latest.GroupOrDie("").RESTMapper.KindFor("replicationcontrollers"); err != nil || gvk != rcGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } if m, err := latest.GroupOrDie("").RESTMapper.RESTMapping(podTemplateGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != podTemplateGVK || m.Resource != "podtemplates" { t.Errorf("unexpected version mapping: %#v %v", m, err) } for _, version := range latest.GroupOrDie("").Versions { currGroupVersion := unversioned.GroupVersion{Version: version} mapping, err := latest.GroupOrDie("").RESTMapper.RESTMapping(rcGVK.GroupKind(), currGroupVersion.String()) 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() != currGroupVersion { t.Errorf("incorrect version: %v", mapping) } interfaces, _ := latest.GroupOrDie("").InterfacesFor(currGroupVersion.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#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) } } }
// convertItemsForDisplay returns a new list that contains parallel elements that have been converted to the most preferred external version func convertItemsForDisplay(objs []runtime.Object, preferredVersions ...unversioned.GroupVersion) ([]runtime.Object, error) { ret := []runtime.Object{} for i := range objs { obj := objs[i] kind, err := kapi.Scheme.ObjectKind(obj) if err != nil { return nil, err } groupMeta, err := registered.Group(kind.Group) if err != nil { return nil, err } requestedVersion := unversioned.GroupVersion{} for _, preferredVersion := range preferredVersions { if preferredVersion.Group == kind.Group { requestedVersion = preferredVersion break } } actualOutputVersion := unversioned.GroupVersion{} for _, externalVersion := range groupMeta.GroupVersions { if externalVersion == requestedVersion { actualOutputVersion = externalVersion break } if actualOutputVersion.IsEmpty() { actualOutputVersion = externalVersion } } convertedObject, err := kapi.Scheme.ConvertToVersion(obj, actualOutputVersion.String()) if err != nil { return nil, err } ret = append(ret, convertedObject) } return ret, nil }
// ClientForVersion initializes or reuses a client for the specified version, or returns an // error if that is not possible func (c *clientCache) ClientForVersion(version *unversioned.GroupVersion) (*client.Client, error) { cacheKey := "" if version != nil { cacheKey = version.String() } if client, ok := c.clients[cacheKey]; ok { return client, nil } config, err := c.ClientConfigForVersion(version) if err != nil { return nil, err } client, err := client.New(config) if err != nil { return nil, err } c.clients[config.GroupVersion.String()] = client return client, nil }
func TestRESTMapper(t *testing.T) { gv := unversioned.GroupVersion{Group: "extensions", Version: "v1beta1"} hpaGVK := gv.WithKind("HorizontalPodAutoscaler") daemonSetGVK := gv.WithKind("DaemonSet") if gvk, err := latest.GroupOrDie("extensions").RESTMapper.KindFor("horizontalpodautoscalers"); err != nil || gvk != hpaGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } if m, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping(daemonSetGVK.GroupKind(), ""); err != nil || m.GroupVersionKind != daemonSetGVK || m.Resource != "daemonsets" { t.Errorf("unexpected version mapping: %#v %v", m, err) } for _, version := range latest.GroupOrDie("extensions").Versions { mapping, err := latest.GroupOrDie("extensions").RESTMapper.RESTMapping(hpaGVK.GroupKind(), version) if err != nil { t.Errorf("unexpected error: %v", err) } if mapping.Resource != "horizontalpodautoscalers" { t.Errorf("incorrect resource name: %#v", mapping) } if mapping.GroupVersionKind.GroupVersion() != gv { t.Errorf("incorrect groupVersion: %v", mapping) } interfaces, _ := latest.GroupOrDie("extensions").InterfacesFor(gv.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } rc := &extensions.HorizontalPodAutoscaler{ObjectMeta: api.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) } } }
func TestRESTMapper(t *testing.T) { if v, k, err := latest.GroupOrDie("").RESTMapper.VersionAndKindForResource("replicationcontrollers"); err != nil || v != "v1" || k != "ReplicationController" { t.Errorf("unexpected version mapping: %s %s %v", v, k, err) } expectedGroupVersion := unversioned.GroupVersion{Version: "v1"} if m, err := latest.GroupOrDie("").RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.GroupVersionKind.GroupVersion() != expectedGroupVersion || m.Resource != "podtemplates" { t.Errorf("unexpected version mapping: %#v %v", m, err) } for _, version := range latest.GroupOrDie("").Versions { currGroupVersion := unversioned.GroupVersion{Version: version} mapping, err := latest.GroupOrDie("").RESTMapper.RESTMapping("ReplicationController", currGroupVersion.String()) 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() != currGroupVersion { t.Errorf("incorrect version: %v", mapping) } interfaces, _ := latest.GroupOrDie("").InterfacesFor(currGroupVersion.String()) if mapping.Codec != interfaces.Codec { t.Errorf("unexpected codec: %#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) } } }
// TestDeepCopyOfEmbeddedObject checks to make sure that EmbeddedObject's can be passed through DeepCopy with fidelity func TestDeepCopyOfEmbeddedObject(t *testing.T) { internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""} externalGV := unversioned.GroupVersion{Group: "test.group", Version: "v1test"} embeddedTestExternalGVK := externalGV.WithKind("EmbeddedTest") s := runtime.NewScheme() s.AddInternalGroupVersion(internalGV) s.AddKnownTypes(internalGV, &EmbeddedTest{}) s.AddKnownTypeWithName(embeddedTestExternalGVK, &EmbeddedTestExternal{}) original := &EmbeddedTest{ ID: "outer", Object: runtime.EmbeddedObject{ Object: &EmbeddedTest{ ID: "inner", }, }, } originalData, err := s.EncodeToVersion(original, externalGV.String()) if err != nil { t.Errorf("unexpected error: %v", err) } t.Logf("originalRole = %v\n", string(originalData)) copyOfOriginal, err := api.Scheme.DeepCopy(original) if err != nil { t.Fatalf("unexpected error: %v", err) } copiedData, err := s.EncodeToVersion(copyOfOriginal.(runtime.Object), externalGV.String()) 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)) } }
// DecodeToVersion converts a JSON string back into a pointer to an api object. // Deduces the type based upon the fields added by the MetaInsertionFactory // technique. The object will be converted, if necessary, into the versioned // type before being returned. Decode will not decode objects without version // set unless version is also "". // a GroupVersion with .IsEmpty() == true is means "use the internal version for // the object's group" func (s *Scheme) DecodeToVersion(data []byte, gv unversioned.GroupVersion) (interface{}, error) { obj, sourceVersion, kind, err := s.DecodeToVersionedObject(data) if err != nil { return nil, err } // Version and Kind should be blank in memory. if err := s.SetVersionAndKind("", "", obj); err != nil { return nil, err } sourceGV, err := unversioned.ParseGroupVersion(sourceVersion) if err != nil { return nil, err } // if the gv is empty, then we want the internal version, but the internal version varies by // group. We can lookup the group now because we have knowledge of the group if gv.IsEmpty() { exists := false gv, exists = s.InternalVersions[sourceGV.Group] if !exists { return nil, fmt.Errorf("no internalVersion specified for %v", gv) } } // Convert if needed. if gv != sourceGV { objOut, err := s.NewObject(gv.String(), kind) if err != nil { return nil, err } flags, meta := s.generateConvertMeta(sourceGV, gv, obj) if err := s.converter.Convert(obj, objOut, flags, meta); err != nil { return nil, err } obj = objOut } return obj, nil }
// 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 unversioned.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 TestThirdPartyResourceDataListEncoding(t *testing.T) { gv := unversioned.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()) } }
func TestRESTMapperResourceSingularizer(t *testing.T) { testCases := []struct { Kind string MixedCase bool Plural string Singular string }{ {Kind: "Pod", MixedCase: true, Plural: "pods", Singular: "pod"}, {Kind: "Pod", MixedCase: false, Plural: "pods", Singular: "pod"}, {Kind: "ReplicationController", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"}, {Kind: "ReplicationController", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"}, {Kind: "ImageRepository", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"}, {Kind: "ImageRepository", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"}, {Kind: "Status", MixedCase: true, Plural: "statuses", Singular: "status"}, {Kind: "Status", MixedCase: false, Plural: "statuses", Singular: "status"}, {Kind: "lowercase", MixedCase: false, Plural: "lowercases", Singular: "lowercase"}, // Don't add extra s if the original object is already plural {Kind: "lowercases", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, } for i, testCase := range testCases { testGroupVersion := unversioned.GroupVersion{Group: "tgroup", Version: "test"} mapper := NewDefaultRESTMapper(testGroupVersion.Group, []string{testGroupVersion.String()}, fakeInterfaces) // create singular/plural mapping mapper.Add(RESTScopeNamespace, testCase.Kind, testGroupVersion.String(), testCase.MixedCase) singular, _ := mapper.ResourceSingularizer(testCase.Plural) if singular != testCase.Singular { t.Errorf("%d: mismatched singular: %s, should be %s", i, singular, testCase.Singular) } } }
// 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, groupVersion unversioned.GroupVersion, codec runtime.Codec, object runtime.Object, w http.ResponseWriter, req *http.Request) { if stream, ok := object.(rest.ResourceStreamer); ok { out, flush, contentType, err := stream.InputStream(groupVersion.String(), req.Header.Get("Accept")) if err != nil { errorJSONFatal(err, codec, w) 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) if err := r.Copy(w, req); err != nil { util.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) return } writeJSON(statusCode, codec, object, w, isPrettyPrint(req)) }
// 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()) } apiVersions, err := client.ServerAPIVersions() 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 } serverVersions := sets.String{} for _, v := range apiVersions.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) }
// SupportedResourcesHandler returns a handler which will list the provided resources as available. func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion unversioned.GroupVersion, apiResources []unversioned.APIResource) restful.RouteFunction { return func(req *restful.Request, resp *restful.Response) { writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIResourceList{GroupVersion: groupVersion.String(), APIResources: apiResources}) } }
func TestRESTMapperRESTMappingSelectsVersion(t *testing.T) { expectedGroupVersion1 := unversioned.GroupVersion{Group: "tgroup", Version: "test1"} expectedGroupVersion2 := unversioned.GroupVersion{Group: "tgroup", Version: "test2"} expectedGroupVersion3 := unversioned.GroupVersion{Group: "tgroup", Version: "test3"} mapper := NewDefaultRESTMapper([]unversioned.GroupVersion{expectedGroupVersion1, expectedGroupVersion2}, fakeInterfaces) mapper.Add(expectedGroupVersion1.WithKind("InternalObject"), RESTScopeNamespace, false) mapper.Add(expectedGroupVersion2.WithKind("OtherObject"), RESTScopeNamespace, false) // pick default matching object kind based on search order mapping, err := mapper.RESTMapping("OtherObject") 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("InternalObject") 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("InternalObject", expectedGroupVersion2.String()) if err == nil { t.Errorf("unexpected non-error") } mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion1.String()) if err == nil { t.Errorf("unexpected non-error") } // not in the search versions mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String()) if err == nil { t.Errorf("unexpected non-error") } // explicit search order mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion1.String()) if err == nil { t.Errorf("unexpected non-error") } mapping, err = mapper.RESTMapping("OtherObject", expectedGroupVersion3.String(), expectedGroupVersion2.String()) if err != nil { t.Fatalf("unexpected error: %v", err) } if mapping.Resource != "otherobjects" || mapping.GroupVersionKind.GroupVersion() != expectedGroupVersion2 { t.Errorf("unexpected mapping: %#v", mapping) } }