func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *apiserver.APIGroupVersion { resourceStorage := thirdpartyresourcedataetcd.NewREST( generic.RESTOptions{ StorageConfig: m.thirdPartyStorageConfig, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: m.deleteCollectionWorkers, }, group, kind, ) storage := map[string]rest.Storage{ pluralResource: resourceStorage, } optionsExternalVersion := registered.GroupOrDie(api.GroupName).GroupVersion internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal} externalVersion := unversioned.GroupVersion{Group: group, Version: version} apiRoot := extensionsrest.MakeThirdPartyPath("") return &apiserver.APIGroupVersion{ Root: apiRoot, GroupVersion: externalVersion, RequestInfoResolver: m.NewRequestInfoResolver(), Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme), Convertor: api.Scheme, Copier: api.Scheme, Typer: api.Scheme, Mapper: thirdpartyresourcedata.NewMapper(registered.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group), Linker: registered.GroupOrDie(extensions.GroupName).SelfLinker, Storage: storage, OptionsExternalVersion: &optionsExternalVersion, Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion), ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec), Context: m.RequestContextMapper(), MinRequestTimeout: m.MinRequestTimeout(), ResourceLister: dynamicLister{m, extensionsrest.MakeThirdPartyPath(group)}, } }
// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is // installed a corresponding RESTful resource is added as a valid path in the web service provided by // the master. // // For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} } // then the following RESTful resource is created on the server: // http://<host>/apis/company.com/v1/foos/... func (m *ThirdPartyResourceServer) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error { kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) if err != nil { return err } if len(rsrc.Versions) == 0 { return fmt.Errorf("ThirdPartyResource %s has no defined versions", rsrc.Name) } plural, _ := meta.KindToResource(schema.GroupVersionKind{ Group: group, Version: rsrc.Versions[0].Name, Kind: kind, }) path := extensionsrest.MakeThirdPartyPath(group) groupVersion := metav1.GroupVersionForDiscovery{ GroupVersion: group + "/" + rsrc.Versions[0].Name, Version: rsrc.Versions[0].Name, } apiGroup := metav1.APIGroup{ Name: group, Versions: []metav1.GroupVersionForDiscovery{groupVersion}, PreferredVersion: groupVersion, } thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name, plural.Resource) // If storage exists, this group has already been added, just update // the group with the new API if m.hasThirdPartyGroupStorage(path) { m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup) return thirdparty.UpdateREST(m.genericAPIServer.HandlerContainer.Container) } if err := thirdparty.InstallREST(m.genericAPIServer.HandlerContainer.Container); err != nil { glog.Errorf("Unable to setup thirdparty api: %v", err) } m.genericAPIServer.HandlerContainer.Add(genericapi.NewGroupWebService(api.Codecs, path, apiGroup)) m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup) api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: group, Version: rsrc.Versions[0].Name}) return nil }
// HasThirdPartyResource returns true if a particular third party resource currently installed. func (m *ThirdPartyResourceServer) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) { kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) if err != nil { return false, err } path := extensionsrest.MakeThirdPartyPath(group) m.thirdPartyResourcesLock.Lock() defer m.thirdPartyResourcesLock.Unlock() entry := m.thirdPartyResources[path] if entry == nil { return false, nil } plural, _ := meta.KindToResource(unversioned.GroupVersionKind{ Group: group, Version: rsrc.Versions[0].Name, Kind: kind, }) _, found := entry.storage[plural.Resource] return found, nil }
func testInstallThirdPartyResourceRemove(t *testing.T, version string) { master, etcdserver, server, assert := initThirdParty(t, version, "foo.company.com") defer server.Close() defer etcdserver.Terminate(t) expectedObj := Foo{ ObjectMeta: api.ObjectMeta{ Name: "test", }, TypeMeta: unversioned.TypeMeta{ Kind: "Foo", }, SomeField: "test field", OtherField: 10, } s, destroyFunc := generic.NewRawStorage(master.thirdPartyStorageConfig) defer destroyFunc() if !assert.NoError(createThirdPartyObject(s, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) { t.FailNow() return } secondObj := expectedObj secondObj.Name = "bar" if !assert.NoError(createThirdPartyObject(s, "/ThirdPartyResourceData/company.com/foos/default/bar", "bar", secondObj)) { t.FailNow() return } resp, err := http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") if !assert.NoError(err) { t.FailNow() return } if resp.StatusCode != http.StatusOK { t.Errorf("unexpected status: %v", resp) } item := Foo{} if err := decodeResponse(resp, &item); err != nil { t.Errorf("unexpected error: %v", err) } // TODO: validate etcd set things here item.ObjectMeta = expectedObj.ObjectMeta if !assert.True(reflect.DeepEqual(item, expectedObj)) { t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item) } path := extensionsrest.MakeThirdPartyPath("company.com") master.RemoveThirdPartyResource(path + "/foos") resp, err = http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") if !assert.NoError(err) { return } if resp.StatusCode != http.StatusNotFound { t.Errorf("unexpected status: %v", resp) } expectedDeletedKeys := []string{ etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/test"), etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/bar"), } for _, key := range expectedDeletedKeys { thirdPartyObj := extensions.ThirdPartyResourceData{} s, destroyFunc := generic.NewRawStorage(master.thirdPartyStorageConfig) err := s.Get(context.TODO(), key, &thirdPartyObj, false) if !storage.IsNotFound(err) { t.Errorf("expected deletion didn't happen: %v", err) } destroyFunc() } installed := master.ListThirdPartyResources() if len(installed) != 0 { t.Errorf("Resource(s) still installed: %v", installed) } services := master.HandlerContainer.RegisteredWebServices() for ix := range services { if strings.HasPrefix(services[ix].RootPath(), "/apis/company.com") { t.Errorf("Web service still installed at %s: %#v", services[ix].RootPath(), services[ix]) } } }