mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Fix third party APIResource reporting
This commit is contained in:
parent
b5ce23c48d
commit
b3658c7b16
@ -1036,14 +1036,39 @@ __EOF__
|
|||||||
# Post-Condition: assertion object exist
|
# Post-Condition: assertion object exist
|
||||||
kube::test::get_object_assert thirdpartyresources "{{range.items}}{{$id_field}}:{{end}}" 'foo.company.com:'
|
kube::test::get_object_assert thirdpartyresources "{{range.items}}{{$id_field}}:{{end}}" 'foo.company.com:'
|
||||||
|
|
||||||
|
kubectl "${kube_flags[@]}" create -f - "${kube_flags[@]}" << __EOF__
|
||||||
|
{
|
||||||
|
"kind": "ThirdPartyResource",
|
||||||
|
"apiVersion": "extensions/v1beta1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "bar.company.com"
|
||||||
|
},
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"name": "v1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
__EOF__
|
||||||
|
|
||||||
|
# Post-Condition: assertion object exist
|
||||||
|
kube::test::get_object_assert thirdpartyresources "{{range.items}}{{$id_field}}:{{end}}" 'bar.company.com:foo.company.com:'
|
||||||
|
|
||||||
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/apis/company.com/v1" "third party api"
|
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/apis/company.com/v1" "third party api"
|
||||||
|
|
||||||
# Test that we can list this new third party resource
|
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/apis/company.com/v1/foos" "third party api Foo"
|
||||||
|
|
||||||
|
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/apis/company.com/v1/bars" "third party api Bar"
|
||||||
|
|
||||||
|
# Test that we can list this new third party resource (foos)
|
||||||
kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
|
kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
|
||||||
|
# Test that we can list this new third party resource (bars)
|
||||||
|
kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
|
||||||
# Test that we can create a new resource of type Foo
|
# Test that we can create a new resource of type Foo
|
||||||
kubectl "${kube_flags[@]}" create -f - "${kube_flags[@]}" << __EOF__
|
kubectl "${kube_flags[@]}" create -f - "${kube_flags[@]}" << __EOF__
|
||||||
{
|
{
|
||||||
"kind": "Foo",
|
"kind": "Foo",
|
||||||
"apiVersion": "company.com/v1",
|
"apiVersion": "company.com/v1",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
@ -1063,8 +1088,31 @@ __EOF__
|
|||||||
# Make sure it's gone
|
# Make sure it's gone
|
||||||
kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
|
kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
|
||||||
|
# Test that we can create a new resource of type Bar
|
||||||
|
kubectl "${kube_flags[@]}" create -f - "${kube_flags[@]}" << __EOF__
|
||||||
|
{
|
||||||
|
"kind": "Bar",
|
||||||
|
"apiVersion": "company.com/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test"
|
||||||
|
},
|
||||||
|
"some-field": "field1",
|
||||||
|
"other-field": "field2"
|
||||||
|
}
|
||||||
|
__EOF__
|
||||||
|
|
||||||
|
# Test that we can list this new third party resource
|
||||||
|
kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" 'test:'
|
||||||
|
|
||||||
|
# Delete the resource
|
||||||
|
kubectl "${kube_flags[@]}" delete bars test
|
||||||
|
|
||||||
|
# Make sure it's gone
|
||||||
|
kube::test::get_object_assert bars "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
|
||||||
# teardown
|
# teardown
|
||||||
kubectl delete thirdpartyresources foo.company.com "${kube_flags[@]}"
|
kubectl delete thirdpartyresources foo.company.com "${kube_flags[@]}"
|
||||||
|
kubectl delete thirdpartyresources bar.company.com "${kube_flags[@]}"
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
# Recursive Resources via directory #
|
# Recursive Resources via directory #
|
||||||
|
@ -58,6 +58,10 @@ type Mux interface {
|
|||||||
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
|
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type APIResourceLister interface {
|
||||||
|
ListAPIResources() []unversioned.APIResource
|
||||||
|
}
|
||||||
|
|
||||||
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful
|
||||||
// It handles URLs of the form:
|
// It handles URLs of the form:
|
||||||
// /${storage_key}[/${object_name}]
|
// /${storage_key}[/${object_name}]
|
||||||
@ -104,6 +108,10 @@ type APIGroupVersion struct {
|
|||||||
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
// the subresource. The key of this map should be the path of the subresource. The keys here should
|
||||||
// match the keys in the Storage map above for subresources.
|
// match the keys in the Storage map above for subresources.
|
||||||
SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind
|
SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind
|
||||||
|
|
||||||
|
// ResourceLister is an interface that knows how to list resources
|
||||||
|
// for this API Group.
|
||||||
|
ResourceLister APIResourceLister
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyDialerFunc func(network, addr string) (net.Conn, error)
|
type ProxyDialerFunc func(network, addr string) (net.Conn, error)
|
||||||
@ -116,6 +124,17 @@ const (
|
|||||||
MaxTimeoutSecs = 600
|
MaxTimeoutSecs = 600
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// staticLister implements the APIResourceLister interface
|
||||||
|
type staticLister struct {
|
||||||
|
list []unversioned.APIResource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticLister) ListAPIResources() []unversioned.APIResource {
|
||||||
|
return s.list
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ APIResourceLister = &staticLister{}
|
||||||
|
|
||||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||||
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
||||||
// in a slash.
|
// in a slash.
|
||||||
@ -123,7 +142,11 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
|
|||||||
installer := g.newInstaller()
|
installer := g.newInstaller()
|
||||||
ws := installer.NewWebService()
|
ws := installer.NewWebService()
|
||||||
apiResources, registrationErrors := installer.Install(ws)
|
apiResources, registrationErrors := installer.Install(ws)
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, apiResources)
|
lister := g.ResourceLister
|
||||||
|
if lister == nil {
|
||||||
|
lister = staticLister{apiResources}
|
||||||
|
}
|
||||||
|
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||||
container.Add(ws)
|
container.Add(ws)
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
}
|
}
|
||||||
@ -147,7 +170,11 @@ func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
|||||||
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix))
|
||||||
}
|
}
|
||||||
apiResources, registrationErrors := installer.Install(ws)
|
apiResources, registrationErrors := installer.Install(ws)
|
||||||
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, apiResources)
|
lister := g.ResourceLister
|
||||||
|
if lister == nil {
|
||||||
|
lister = staticLister{apiResources}
|
||||||
|
}
|
||||||
|
AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, lister)
|
||||||
return utilerrors.NewAggregate(registrationErrors)
|
return utilerrors.NewAggregate(registrationErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +363,7 @@ func AddGroupWebService(s runtime.NegotiatedSerializer, container *restful.Conta
|
|||||||
|
|
||||||
// Adds a service to return the supported resources, E.g., a such web service
|
// Adds a service to return the supported resources, E.g., a such web service
|
||||||
// will be registered at /apis/extensions/v1.
|
// will be registered at /apis/extensions/v1.
|
||||||
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion unversioned.GroupVersion, apiResources []unversioned.APIResource) {
|
func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion unversioned.GroupVersion, lister APIResourceLister) {
|
||||||
ss := s
|
ss := s
|
||||||
if keepUnversioned(groupVersion.Group) {
|
if keepUnversioned(groupVersion.Group) {
|
||||||
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
// Because in release 1.1, /apis/extensions/v1beta1 returns response
|
||||||
@ -344,7 +371,7 @@ func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful
|
|||||||
// keep the response backwards compatible.
|
// keep the response backwards compatible.
|
||||||
ss = StripVersionNegotiatedSerializer{s}
|
ss = StripVersionNegotiatedSerializer{s}
|
||||||
}
|
}
|
||||||
resourceHandler := SupportedResourcesHandler(ss, groupVersion, apiResources)
|
resourceHandler := SupportedResourcesHandler(ss, groupVersion, lister)
|
||||||
ws.Route(ws.GET("/").To(resourceHandler).
|
ws.Route(ws.GET("/").To(resourceHandler).
|
||||||
Doc("get available resources").
|
Doc("get available resources").
|
||||||
Operation("getAPIResources").
|
Operation("getAPIResources").
|
||||||
@ -381,9 +408,9 @@ func GroupHandler(s runtime.NegotiatedSerializer, group unversioned.APIGroup) re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SupportedResourcesHandler returns a handler which will list the provided resources as available.
|
// 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 {
|
func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion unversioned.GroupVersion, lister APIResourceLister) restful.RouteFunction {
|
||||||
return func(req *restful.Request, resp *restful.Response) {
|
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})
|
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIResourceList{GroupVersion: groupVersion.String(), APIResources: lister.ListAPIResources()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ type Master struct {
|
|||||||
// storage for third party objects
|
// storage for third party objects
|
||||||
thirdPartyStorage storage.Interface
|
thirdPartyStorage storage.Interface
|
||||||
// map from api path to a tuple of (storage for the objects, APIGroup)
|
// map from api path to a tuple of (storage for the objects, APIGroup)
|
||||||
thirdPartyResources map[string]thirdPartyEntry
|
thirdPartyResources map[string]*thirdPartyEntry
|
||||||
// protects the map
|
// protects the map
|
||||||
thirdPartyResourcesLock sync.RWMutex
|
thirdPartyResourcesLock sync.RWMutex
|
||||||
// Useful for reliable testing. Shouldn't be used otherwise.
|
// Useful for reliable testing. Shouldn't be used otherwise.
|
||||||
@ -156,7 +156,8 @@ type Master struct {
|
|||||||
// thirdPartyEntry combines objects storage and API group into one struct
|
// thirdPartyEntry combines objects storage and API group into one struct
|
||||||
// for easy lookup.
|
// for easy lookup.
|
||||||
type thirdPartyEntry struct {
|
type thirdPartyEntry struct {
|
||||||
storage *thirdpartyresourcedataetcd.REST
|
// Map from plural resource name to entry
|
||||||
|
storage map[string]*thirdpartyresourcedataetcd.REST
|
||||||
group unversioned.APIGroup
|
group unversioned.APIGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ func (m *Master) InstallAPIs(c *Config) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Error getting third party storage: %v", err)
|
glog.Fatalf("Error getting third party storage: %v", err)
|
||||||
}
|
}
|
||||||
m.thirdPartyResources = map[string]thirdPartyEntry{}
|
m.thirdPartyResources = map[string]*thirdPartyEntry{}
|
||||||
}
|
}
|
||||||
|
|
||||||
restOptionsGetter := func(resource unversioned.GroupResource) generic.RESTOptions {
|
restOptionsGetter := func(resource unversioned.GroupResource) generic.RESTOptions {
|
||||||
@ -510,37 +511,60 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
|||||||
|
|
||||||
// HasThirdPartyResource returns true if a particular third party resource currently installed.
|
// HasThirdPartyResource returns true if a particular third party resource currently installed.
|
||||||
func (m *Master) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) {
|
func (m *Master) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) {
|
||||||
_, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
path := makeThirdPartyPath(group)
|
path := makeThirdPartyPath(group)
|
||||||
services := m.HandlerContainer.RegisteredWebServices()
|
|
||||||
for ix := range services {
|
|
||||||
if services[ix].RootPath() == path {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Master) removeThirdPartyStorage(path string) error {
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
m.thirdPartyResourcesLock.Lock()
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
defer m.thirdPartyResourcesLock.Unlock()
|
||||||
storage, found := m.thirdPartyResources[path]
|
entry := m.thirdPartyResources[path]
|
||||||
if found {
|
if entry == nil {
|
||||||
if err := m.removeAllThirdPartyResources(storage.storage); err != nil {
|
return false, nil
|
||||||
return err
|
}
|
||||||
}
|
plural, _ := meta.KindToResource(unversioned.GroupVersionKind{
|
||||||
|
Group: group,
|
||||||
|
Version: rsrc.Versions[0].Name,
|
||||||
|
Kind: kind,
|
||||||
|
})
|
||||||
|
_, found := entry.storage[plural.Resource]
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Master) removeThirdPartyStorage(path, resource string) error {
|
||||||
|
m.thirdPartyResourcesLock.Lock()
|
||||||
|
defer m.thirdPartyResourcesLock.Unlock()
|
||||||
|
entry, found := m.thirdPartyResources[path]
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
storage, found := entry.storage[resource]
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := m.removeAllThirdPartyResources(storage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(entry.storage, resource)
|
||||||
|
if len(entry.storage) == 0 {
|
||||||
delete(m.thirdPartyResources, path)
|
delete(m.thirdPartyResources, path)
|
||||||
m.RemoveAPIGroupForDiscovery(getThirdPartyGroupName(path))
|
m.RemoveAPIGroupForDiscovery(getThirdPartyGroupName(path))
|
||||||
|
} else {
|
||||||
|
m.thirdPartyResources[path] = entry
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data
|
// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data
|
||||||
func (m *Master) RemoveThirdPartyResource(path string) error {
|
func (m *Master) RemoveThirdPartyResource(path string) error {
|
||||||
if err := m.removeThirdPartyStorage(path); err != nil {
|
ix := strings.LastIndex(path, "/")
|
||||||
|
if ix == -1 {
|
||||||
|
return fmt.Errorf("expected <api-group>/<resource-plural-name>, saw: %s", path)
|
||||||
|
}
|
||||||
|
resource := path[ix+1:]
|
||||||
|
path = path[0:ix]
|
||||||
|
|
||||||
|
if err := m.removeThirdPartyStorage(path, resource); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,28 +598,58 @@ func (m *Master) removeAllThirdPartyResources(registry *thirdpartyresourcedataet
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListThirdPartyResources lists all currently installed third party resources
|
// ListThirdPartyResources lists all currently installed third party resources
|
||||||
|
// The format is <path>/<resource-plural-name>
|
||||||
func (m *Master) ListThirdPartyResources() []string {
|
func (m *Master) ListThirdPartyResources() []string {
|
||||||
m.thirdPartyResourcesLock.RLock()
|
m.thirdPartyResourcesLock.RLock()
|
||||||
defer m.thirdPartyResourcesLock.RUnlock()
|
defer m.thirdPartyResourcesLock.RUnlock()
|
||||||
result := []string{}
|
result := []string{}
|
||||||
for key := range m.thirdPartyResources {
|
for key := range m.thirdPartyResources {
|
||||||
result = append(result, key)
|
for rsrc := range m.thirdPartyResources[key].storage {
|
||||||
|
result = append(result, key+"/"+rsrc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Master) hasThirdPartyResourceStorage(path string) bool {
|
func (m *Master) getExistingThirdPartyResources(path string) []unversioned.APIResource {
|
||||||
|
result := []unversioned.APIResource{}
|
||||||
|
m.thirdPartyResourcesLock.Lock()
|
||||||
|
defer m.thirdPartyResourcesLock.Unlock()
|
||||||
|
entry := m.thirdPartyResources[path]
|
||||||
|
if entry != nil {
|
||||||
|
for key, obj := range entry.storage {
|
||||||
|
result = append(result, unversioned.APIResource{
|
||||||
|
Name: key,
|
||||||
|
Namespaced: true,
|
||||||
|
Kind: obj.Kind(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Master) hasThirdPartyGroupStorage(path string) bool {
|
||||||
m.thirdPartyResourcesLock.Lock()
|
m.thirdPartyResourcesLock.Lock()
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
defer m.thirdPartyResourcesLock.Unlock()
|
||||||
_, found := m.thirdPartyResources[path]
|
_, found := m.thirdPartyResources[path]
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Master) addThirdPartyResourceStorage(path string, storage *thirdpartyresourcedataetcd.REST, apiGroup unversioned.APIGroup) {
|
func (m *Master) addThirdPartyResourceStorage(path, resource string, storage *thirdpartyresourcedataetcd.REST, apiGroup unversioned.APIGroup) {
|
||||||
m.thirdPartyResourcesLock.Lock()
|
m.thirdPartyResourcesLock.Lock()
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
defer m.thirdPartyResourcesLock.Unlock()
|
||||||
m.thirdPartyResources[path] = thirdPartyEntry{storage, apiGroup}
|
entry, found := m.thirdPartyResources[path]
|
||||||
m.AddAPIGroupForDiscovery(apiGroup)
|
if entry == nil {
|
||||||
|
entry = &thirdPartyEntry{
|
||||||
|
group: apiGroup,
|
||||||
|
storage: map[string]*thirdpartyresourcedataetcd.REST{},
|
||||||
|
}
|
||||||
|
m.thirdPartyResources[path] = entry
|
||||||
|
}
|
||||||
|
entry.storage[resource] = storage
|
||||||
|
if !found {
|
||||||
|
m.AddAPIGroupForDiscovery(apiGroup)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is
|
// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is
|
||||||
@ -617,17 +671,6 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
|
|||||||
})
|
})
|
||||||
path := makeThirdPartyPath(group)
|
path := makeThirdPartyPath(group)
|
||||||
|
|
||||||
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.hasThirdPartyResourceStorage(path) {
|
|
||||||
return thirdparty.UpdateREST(m.HandlerContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := thirdparty.InstallREST(m.HandlerContainer); err != nil {
|
|
||||||
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
|
||||||
}
|
|
||||||
groupVersion := unversioned.GroupVersionForDiscovery{
|
groupVersion := unversioned.GroupVersionForDiscovery{
|
||||||
GroupVersion: group + "/" + rsrc.Versions[0].Name,
|
GroupVersion: group + "/" + rsrc.Versions[0].Name,
|
||||||
Version: rsrc.Versions[0].Name,
|
Version: rsrc.Versions[0].Name,
|
||||||
@ -637,9 +680,22 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
|
|||||||
Versions: []unversioned.GroupVersionForDiscovery{groupVersion},
|
Versions: []unversioned.GroupVersionForDiscovery{groupVersion},
|
||||||
PreferredVersion: 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].(*thirdpartyresourcedataetcd.REST), apiGroup)
|
||||||
|
return thirdparty.UpdateREST(m.HandlerContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := thirdparty.InstallREST(m.HandlerContainer); err != nil {
|
||||||
|
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
||||||
|
}
|
||||||
apiserver.AddGroupWebService(api.Codecs, m.HandlerContainer, path, apiGroup)
|
apiserver.AddGroupWebService(api.Codecs, m.HandlerContainer, path, apiGroup)
|
||||||
|
|
||||||
m.addThirdPartyResourceStorage(path, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup)
|
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedataetcd.REST), apiGroup)
|
||||||
apiserver.InstallServiceErrorHandler(api.Codecs, m.HandlerContainer, m.NewRequestInfoResolver(), []string{thirdparty.GroupVersion.String()})
|
apiserver.InstallServiceErrorHandler(api.Codecs, m.HandlerContainer, m.NewRequestInfoResolver(), []string{thirdparty.GroupVersion.String()})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -655,8 +711,6 @@ func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *api
|
|||||||
kind,
|
kind,
|
||||||
)
|
)
|
||||||
|
|
||||||
apiRoot := makeThirdPartyPath("")
|
|
||||||
|
|
||||||
storage := map[string]rest.Storage{
|
storage := map[string]rest.Storage{
|
||||||
pluralResource: resourceStorage,
|
pluralResource: resourceStorage,
|
||||||
}
|
}
|
||||||
@ -665,6 +719,7 @@ func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *api
|
|||||||
internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}
|
internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}
|
||||||
externalVersion := unversioned.GroupVersion{Group: group, Version: version}
|
externalVersion := unversioned.GroupVersion{Group: group, Version: version}
|
||||||
|
|
||||||
|
apiRoot := makeThirdPartyPath("")
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
Root: apiRoot,
|
Root: apiRoot,
|
||||||
GroupVersion: externalVersion,
|
GroupVersion: externalVersion,
|
||||||
@ -686,6 +741,8 @@ func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *api
|
|||||||
Context: m.RequestContextMapper,
|
Context: m.RequestContextMapper,
|
||||||
|
|
||||||
MinRequestTimeout: m.MinRequestTimeout,
|
MinRequestTimeout: m.MinRequestTimeout,
|
||||||
|
|
||||||
|
ResourceLister: dynamicLister{m, makeThirdPartyPath(group)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +508,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"}
|
thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"}
|
||||||
master.addThirdPartyResourceStorage("/apis/company.com/v1", nil,
|
master.addThirdPartyResourceStorage("/apis/company.com/v1", "foos", nil,
|
||||||
unversioned.APIGroup{
|
unversioned.APIGroup{
|
||||||
Name: "company.com",
|
Name: "company.com",
|
||||||
Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV},
|
Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV},
|
||||||
@ -575,10 +575,18 @@ func initThirdPartyMultiple(t *testing.T, versions, names []string) (*Master, *e
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := master.InstallThirdPartyResource(api)
|
hasRsrc, err := master.HasThirdPartyResource(api)
|
||||||
if !assert.NoError(err) {
|
if err != nil {
|
||||||
t.Logf("Failed to install API: %v", err)
|
t.Errorf("Unexpected error: %v", err)
|
||||||
t.FailNow()
|
}
|
||||||
|
if !hasRsrc {
|
||||||
|
err := master.InstallThirdPartyResource(api)
|
||||||
|
if !assert.NoError(err) {
|
||||||
|
t.Errorf("Failed to install API: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("Expected %s: %v not to be present!", names[ix], api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1078,7 +1086,7 @@ func testInstallThirdPartyResourceRemove(t *testing.T, version string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := makeThirdPartyPath("company.com")
|
path := makeThirdPartyPath("company.com")
|
||||||
master.RemoveThirdPartyResource(path)
|
master.RemoveThirdPartyResource(path + "/foos")
|
||||||
|
|
||||||
resp, err = http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test")
|
resp, err = http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test")
|
||||||
if !assert.NoError(err) {
|
if !assert.NoError(err) {
|
||||||
|
@ -21,7 +21,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
expapi "k8s.io/kubernetes/pkg/apis/extensions"
|
expapi "k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
"k8s.io/kubernetes/pkg/apiserver"
|
||||||
thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd"
|
thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd"
|
||||||
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
|
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
@ -30,6 +32,19 @@ import (
|
|||||||
|
|
||||||
const thirdpartyprefix = "/apis"
|
const thirdpartyprefix = "/apis"
|
||||||
|
|
||||||
|
// dynamicLister is used to list resources for dynamic third party
|
||||||
|
// apis. It implements the apiserver.APIResourceLister interface
|
||||||
|
type dynamicLister struct {
|
||||||
|
m *Master
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dynamicLister) ListAPIResources() []unversioned.APIResource {
|
||||||
|
return d.m.getExistingThirdPartyResources(d.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ apiserver.APIResourceLister = &dynamicLister{}
|
||||||
|
|
||||||
func makeThirdPartyPath(group string) string {
|
func makeThirdPartyPath(group string) string {
|
||||||
if len(group) == 0 {
|
if len(group) == 0 {
|
||||||
return thirdpartyprefix
|
return thirdpartyprefix
|
||||||
@ -44,13 +59,14 @@ func getThirdPartyGroupName(path string) string {
|
|||||||
// resourceInterface is the interface for the parts of the master that know how to add/remove
|
// resourceInterface is the interface for the parts of the master that know how to add/remove
|
||||||
// third party resources. Extracted into an interface for injection for testing.
|
// third party resources. Extracted into an interface for injection for testing.
|
||||||
type resourceInterface interface {
|
type resourceInterface interface {
|
||||||
// Remove a third party resource based on the RESTful path for that resource
|
// Remove a third party resource based on the RESTful path for that resource, the path is <api-group-path>/<resource-plural-name>
|
||||||
RemoveThirdPartyResource(path string) error
|
RemoveThirdPartyResource(path string) error
|
||||||
// Install a third party resource described by 'rsrc'
|
// Install a third party resource described by 'rsrc'
|
||||||
InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) error
|
InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) error
|
||||||
// Is a particular third party resource currently installed?
|
// Is a particular third party resource currently installed?
|
||||||
HasThirdPartyResource(rsrc *expapi.ThirdPartyResource) (bool, error)
|
HasThirdPartyResource(rsrc *expapi.ThirdPartyResource) (bool, error)
|
||||||
// List all currently installed third party resources
|
// List all currently installed third party resources, the returned
|
||||||
|
// names are of the form <api-group-path>/<resource-plural-name>
|
||||||
ListThirdPartyResources() []string
|
ListThirdPartyResources() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user