236 lines
7.1 KiB
Go
236 lines
7.1 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package resource
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/discovery"
|
|
"k8s.io/kube-openapi/pkg/util/proto"
|
|
"k8s.io/kubectl/pkg/framework/openapi"
|
|
)
|
|
|
|
// Parser is an interface for an object that can be used to discover
|
|
// resources from an API server and parse them into indexed data
|
|
// structures.
|
|
type Parser interface {
|
|
Resources() (Resources, error)
|
|
}
|
|
|
|
// parser is an object type that can be used to discover resources from
|
|
// an API server and parse them into indexed data structures.
|
|
type parser struct {
|
|
resources openapi.Resources
|
|
discovery discovery.DiscoveryInterface
|
|
apiGroup string
|
|
apiVersion string
|
|
}
|
|
|
|
// NewParser populates the fields of and returns a new parser object.
|
|
func NewParser(resources openapi.Resources, discovery discovery.DiscoveryInterface, apiGroup string, apiVersion string) Parser {
|
|
return &parser{
|
|
resources,
|
|
discovery,
|
|
apiGroup,
|
|
apiVersion,
|
|
}
|
|
}
|
|
|
|
// Resources discovers and indexes resources from the API server.
|
|
// It returns a Resources object: a map of resource names and their
|
|
// associated Resource objects, ordered by preference as reported by the
|
|
// server.
|
|
func (p *parser) Resources() (Resources, error) {
|
|
gvs, err := p.discovery.ServerResources()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resources, byGVR := p.indexResources(gvs)
|
|
err = p.attachSubResources(gvs, resources, byGVR)
|
|
return resources, err
|
|
}
|
|
|
|
// subResource returns a resource name, subresource name pair, and true
|
|
// if the resource is a subresource.
|
|
// It returns a resource name, an empty string, and false if the resource
|
|
// is not a subresource.
|
|
func (*parser) subResource(resource *v1.APIResource) (resourceName, subResourceName string, isSubResource bool) {
|
|
parts := strings.Split(resource.Name, "/")
|
|
if len(parts) > 1 {
|
|
resourceName, subResourceName, isSubResource = parts[0], parts[1], true
|
|
return
|
|
}
|
|
resourceName, subResourceName, isSubResource = parts[0], "", false
|
|
return
|
|
}
|
|
|
|
// resource returns the resource name and true if it is a resource (not
|
|
// a subresource).
|
|
// It returns the resource name and false it if it is a subresource.
|
|
func (p *parser) resource(resource *v1.APIResource) (resourceName string, isResource bool) {
|
|
resourceName, _, isSubResource := p.subResource(resource)
|
|
isResource = !isSubResource
|
|
return
|
|
}
|
|
|
|
// copyGroupVersion copies the group and version from source to
|
|
// destination if either is missing on the destination.
|
|
// If the source group is empty and the destination group is empty, sets
|
|
// the destination group to "core".
|
|
func (*parser) copyGroupVersion(src *v1.APIResource, dest *v1.APIResource) {
|
|
if len(dest.Group) == 0 {
|
|
dest.Group = src.Group
|
|
}
|
|
if len(dest.Group) == 0 {
|
|
dest.Group = "core"
|
|
}
|
|
if len(dest.Version) == 0 {
|
|
dest.Version = src.Version
|
|
}
|
|
}
|
|
|
|
// splitGroupVersion splits the groupVersion string into its group and
|
|
// version components.
|
|
func (*parser) splitGroupVersion(groupVersion string) (string, string) {
|
|
parts := strings.Split(groupVersion, "/")
|
|
var group, version string
|
|
if len(parts) > 1 {
|
|
group = parts[0]
|
|
}
|
|
if len(parts) > 1 {
|
|
version = parts[1]
|
|
} else if len(parts) > 0 {
|
|
version = parts[0]
|
|
} else {
|
|
version = "v1"
|
|
}
|
|
return group, version
|
|
}
|
|
|
|
// defaultGroupVersion sets the group and version to the API group and
|
|
// version if they're missing.
|
|
func (p *parser) defaultGroupVersion(resource *v1.APIResource, group, version string) {
|
|
if len(resource.Group) == 0 {
|
|
resource.Group = group
|
|
}
|
|
if len(resource.Version) == 0 {
|
|
resource.Group = version
|
|
}
|
|
}
|
|
|
|
// isGroupVersionMatch returns false if either group or version doesn't
|
|
// match with the API.
|
|
func (p *parser) isGroupVersionMatch(group, version string) bool {
|
|
if len(p.apiGroup) > 0 && p.apiGroup != group {
|
|
return false
|
|
}
|
|
if len(p.apiVersion) > 0 && p.apiVersion != version {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// getOpenAPI retrieves a schema object from the API based on a
|
|
// GroupVersionResource triplet.
|
|
func (p *parser) getOpenAPI(group, version, kind string) proto.Schema {
|
|
schema := p.resources.LookupResource(schema.GroupVersionKind{
|
|
Group: group,
|
|
Version: version,
|
|
Kind: kind})
|
|
if schema == nil {
|
|
return nil
|
|
}
|
|
return schema
|
|
}
|
|
|
|
// indexResources indexes into maps the resources from the API resource
|
|
// list. It returns a map indexed by resource name, and a map indexed by
|
|
// GroupVersionResource objects.
|
|
func (p *parser) indexResources(gvs []*v1.APIResourceList) (Resources, map[schema.GroupVersionResource]*Resource) {
|
|
resources := Resources{}
|
|
bygvr := map[schema.GroupVersionResource]*Resource{}
|
|
// Find all resources
|
|
for _, gv := range gvs {
|
|
group, version := p.splitGroupVersion(gv.GroupVersion)
|
|
if !p.isGroupVersionMatch(group, version) {
|
|
continue
|
|
}
|
|
for _, r := range gv.APIResources {
|
|
p.defaultGroupVersion(&r, group, version)
|
|
name, isRes := p.resource(&r)
|
|
if !isRes {
|
|
continue
|
|
}
|
|
newSchema := p.getOpenAPI(group, version, r.Kind)
|
|
if newSchema == nil {
|
|
continue
|
|
}
|
|
newResource := &Resource{
|
|
Resource: r,
|
|
apiGroupVersion: schema.GroupVersion{Group: group, Version: version},
|
|
schema: newSchema,
|
|
}
|
|
resources[name] = append(resources[name], newResource)
|
|
bygvr[schema.GroupVersionResource{Group: group, Version: version, Resource: r.Kind}] = newResource
|
|
}
|
|
}
|
|
return resources, bygvr
|
|
}
|
|
|
|
// attachSubResources grabs the subresources in the gvs argument and
|
|
// attaches them to the parent resources in the maps present in the next
|
|
// two arguments.
|
|
func (p *parser) attachSubResources(
|
|
gvs []*v1.APIResourceList,
|
|
resources Resources,
|
|
bygvr map[schema.GroupVersionResource]*Resource) error {
|
|
// Find all subresources and attach to parents
|
|
for _, gv := range gvs {
|
|
group, version := p.splitGroupVersion(gv.GroupVersion)
|
|
if !p.isGroupVersionMatch(group, version) {
|
|
continue
|
|
}
|
|
for _, r := range gv.APIResources {
|
|
p.defaultGroupVersion(&r, group, version)
|
|
resourceName, _, isSubResource := p.subResource(&r)
|
|
if !isSubResource {
|
|
continue
|
|
}
|
|
newSchema := p.getOpenAPI(group, version, r.Kind)
|
|
if newSchema == nil {
|
|
continue
|
|
}
|
|
// Make sure the Parent resource wasn't filtered out
|
|
gvr := schema.GroupVersionResource{Group: group, Version: version, Resource: resourceName}
|
|
if _, found := bygvr[gvr]; !found {
|
|
continue
|
|
}
|
|
parent := bygvr[gvr]
|
|
subRes := &SubResource{
|
|
Resource: r,
|
|
parent: parent,
|
|
apiGroupVersion: schema.GroupVersion{Group: group, Version: version},
|
|
schema: newSchema,
|
|
}
|
|
parent.SubResources = append(parent.SubResources, subRes)
|
|
}
|
|
}
|
|
return nil
|
|
}
|