mirror of
https://github.com/rancher/steve.git
synced 2025-05-06 06:56:19 +00:00
Adding schema definitions endpoint
Introduces new schema definitions endpoint, allowing the caller to get the fields/types/descriptions for a given kubernetes resource.
This commit is contained in:
parent
3f65942a6a
commit
40f6b10fc7
3
go.mod
3
go.mod
@ -12,7 +12,7 @@ replace (
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/gnostic v0.5.7-v3refs
|
||||
github.com/google/gnostic-models v0.6.8
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/pborman/uuid v1.2.1
|
||||
@ -58,7 +58,6 @@ require (
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -73,7 +73,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
@ -165,8 +164,6 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -347,7 +344,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@ -733,7 +729,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
|
||||
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
|
||||
@ -794,7 +789,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/rancher/steve/pkg/resources/formatters"
|
||||
"github.com/rancher/steve/pkg/resources/userpreferences"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
steveschema "github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
"github.com/rancher/steve/pkg/summarycache"
|
||||
corecontrollers "github.com/rancher/wrangler/v2/pkg/generated/controllers/core/v1"
|
||||
@ -25,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache clustercache.ClusterCache,
|
||||
cg proxy.ClientGetter, schemaFactory steveschema.Factory, serverVersion string) error {
|
||||
cg proxy.ClientGetter, schemaFactory schema.Factory, serverVersion string) error {
|
||||
counts.Register(baseSchema, ccache)
|
||||
subscribe.Register(baseSchema, func(apiOp *types.APIRequest) *types.APISchemas {
|
||||
user, ok := request.UserFrom(apiOp.Context())
|
||||
|
@ -23,7 +23,7 @@ var (
|
||||
)
|
||||
|
||||
// addDiscovery uses a k8s discovery client to create very basic schemas for all registered groups/resources. Other
|
||||
// functions, such as AddCustomResources are used to add more details to these schemas later on.
|
||||
// functions, such as addCustomResources are used to add more details to these schemas later on.
|
||||
func addDiscovery(client discovery.DiscoveryInterface, schemasMap map[string]*types.APISchema) error {
|
||||
groups, resourceLists, err := client.ServerGroupsAndResources()
|
||||
if gd, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok {
|
||||
|
@ -70,7 +70,7 @@ func GetGVKForKind(kind *proto.Kind) *schema.GroupVersionKind {
|
||||
}
|
||||
|
||||
// ToSchemas creates the schemas for a K8s server, using client to discover groups/resources, and crd to potentially
|
||||
// add additional information about new fields/resources. Mostly ties together AddDiscovery and AddCustomResources.
|
||||
// add additional information about new fields/resources. Mostly ties together addDiscovery and addCustomResources.
|
||||
func ToSchemas(crd v1.CustomResourceDefinitionClient, client discovery.DiscoveryInterface) (map[string]*types.APISchema, error) {
|
||||
result := map[string]*types.APISchema{}
|
||||
|
||||
|
197
pkg/schema/definitions/handler.go
Normal file
197
pkg/schema/definitions/handler.go
Normal file
@ -0,0 +1,197 @@
|
||||
package definitions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/apierror"
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/schema/converter"
|
||||
"github.com/rancher/wrangler/v2/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
internalServerErrorCode = validation.ErrorCode{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: "InternalServerError",
|
||||
}
|
||||
notRefreshedErrorCode = validation.ErrorCode{
|
||||
Status: http.StatusServiceUnavailable,
|
||||
Code: "SchemasNotRefreshed",
|
||||
}
|
||||
)
|
||||
|
||||
// schemaDefinitionHandler is a byID handler for a specific schema, which provides field definitions for all schemas.
|
||||
// Does not implement any method allowing a caller to list definitions for all schemas.
|
||||
type schemaDefinitionHandler struct {
|
||||
sync.RWMutex
|
||||
|
||||
// lastRefresh is the last time that the handler retrieved models from kubernetes.
|
||||
lastRefresh time.Time
|
||||
// refreshStale is the duration between lastRefresh and the next refresh of models.
|
||||
refreshStale time.Duration
|
||||
// client is the discovery client used to get the groups/resources/fields from kubernetes.
|
||||
client discovery.DiscoveryInterface
|
||||
// models are the cached models from the last response from kubernetes.
|
||||
models *proto.Models
|
||||
// schemaToModel is a map of the schema name to the model for that schema. Can be used to load the
|
||||
// top-level definition for a schema, which can then be processed by the schemaFieldVisitor.
|
||||
schemaToModel map[string]string
|
||||
}
|
||||
|
||||
// byIDHandler is the Handler method for a request to get the schema definition for a specifc schema. Will use the
|
||||
// cached models found during the last refresh as part of this process.
|
||||
func (s *schemaDefinitionHandler) byIDHandler(request *types.APIRequest) (types.APIObject, error) {
|
||||
// pseudo-access check, designed to make sure that users have access to the schema for the definition that they
|
||||
// are accessing.
|
||||
requestSchema := request.Schemas.LookupSchema(request.Name)
|
||||
if requestSchema == nil {
|
||||
return types.APIObject{}, apierror.NewAPIError(validation.NotFound, "no such schema")
|
||||
}
|
||||
|
||||
if s.needsRefresh() {
|
||||
err := s.refresh()
|
||||
if err != nil {
|
||||
logrus.Errorf("error refreshing schemas %s", err.Error())
|
||||
return types.APIObject{}, apierror.NewAPIError(internalServerErrorCode, "error refreshing schemas")
|
||||
}
|
||||
}
|
||||
|
||||
// lock only in read-mode so that we don't read while refresh writes. Only use a read-lock - using a write lock
|
||||
// would make this endpoint only usable by one caller at a time
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if s.models == nil {
|
||||
return types.APIObject{}, apierror.NewAPIError(notRefreshedErrorCode, "schema definitions not yet refreshed")
|
||||
}
|
||||
models := *s.models
|
||||
modelName, ok := s.schemaToModel[requestSchema.ID]
|
||||
if !ok {
|
||||
return types.APIObject{}, apierror.NewAPIError(notRefreshedErrorCode, "no model found for schema, try again after refresh")
|
||||
}
|
||||
model := models.LookupModel(modelName)
|
||||
protoKind, ok := model.(*proto.Kind)
|
||||
if !ok {
|
||||
errorMsg := fmt.Sprintf("model for %s was type %T, not a proto.Kind", modelName, model)
|
||||
return types.APIObject{}, apierror.NewAPIError(internalServerErrorCode, errorMsg)
|
||||
}
|
||||
definitions := map[string]definition{}
|
||||
visitor := schemaFieldVisitor{
|
||||
definitions: definitions,
|
||||
models: models,
|
||||
}
|
||||
protoKind.Accept(&visitor)
|
||||
|
||||
return types.APIObject{
|
||||
ID: request.Name,
|
||||
Type: "schemaDefinition",
|
||||
Object: schemaDefinition{
|
||||
DefinitionType: modelName,
|
||||
Definitions: definitions,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// needsRefresh readLocks and checks if the cache needs to be refreshed.
|
||||
func (s *schemaDefinitionHandler) needsRefresh() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
if s.lastRefresh.IsZero() {
|
||||
return true
|
||||
}
|
||||
return s.lastRefresh.Add(s.refreshStale).Before(time.Now())
|
||||
}
|
||||
|
||||
// refresh writeLocks and updates the cache with new schemaDefinitions. Will result in a call to kubernetes to retrieve
|
||||
// the openAPI schemas.
|
||||
func (s *schemaDefinitionHandler) refresh() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
openapi, err := s.client.OpenAPISchema()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch openapi definition: %w", err)
|
||||
}
|
||||
models, err := proto.NewOpenAPIData(openapi)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse openapi definition into models: %w", err)
|
||||
}
|
||||
s.models = &models
|
||||
nameIndex, err := s.indexSchemaNames(models)
|
||||
// indexSchemaNames may successfully refresh some definitions, but still return an error
|
||||
// in these cases, store what we could find, but still return up an error
|
||||
if nameIndex != nil {
|
||||
s.schemaToModel = nameIndex
|
||||
s.lastRefresh = time.Now()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to index schema name to model name: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// indexSchemaNames returns a map of schemaID to the modelName for a given schema. Will use the preferred version of a
|
||||
// resource if possible. May return a map and an error if it was able to index some schemas but not others.
|
||||
func (s *schemaDefinitionHandler) indexSchemaNames(models proto.Models) (map[string]string, error) {
|
||||
_, resourceLists, err := s.client.ServerGroupsAndResources()
|
||||
// this may occasionally fail to discover certain groups, but we still can refresh the others in those cases
|
||||
if _, ok := err.(*discovery.ErrGroupDiscoveryFailed); err != nil && !ok {
|
||||
return nil, fmt.Errorf("unable to retrieve groups and resources: %w", err)
|
||||
}
|
||||
preferredResourceVersions := map[schema.GroupKind]string{}
|
||||
for _, resourceList := range resourceLists {
|
||||
if resourceList == nil {
|
||||
continue
|
||||
}
|
||||
groupVersion, gvErr := schema.ParseGroupVersion(resourceList.GroupVersion)
|
||||
// we may fail to parse the GV of one group, but can still parse out the others
|
||||
if gvErr != nil {
|
||||
err = errors.Join(err, fmt.Errorf("unable to parse group version %s: %w", resourceList.GroupVersion, gvErr))
|
||||
continue
|
||||
}
|
||||
for _, resource := range resourceList.APIResources {
|
||||
gk := schema.GroupKind{
|
||||
Group: groupVersion.Group,
|
||||
Kind: resource.Kind,
|
||||
}
|
||||
// per the resource docs, if the resource.Version is empty, the preferred version for
|
||||
// this resource is the version of the APIResourceList it is in
|
||||
if resource.Version == "" || resource.Version == groupVersion.Version {
|
||||
preferredResourceVersions[gk] = groupVersion.Version
|
||||
}
|
||||
}
|
||||
}
|
||||
schemaToModel := map[string]string{}
|
||||
for _, modelName := range models.ListModels() {
|
||||
protoKind, ok := models.LookupModel(modelName).(*proto.Kind)
|
||||
if !ok {
|
||||
// no need to process models that aren't kinds
|
||||
continue
|
||||
}
|
||||
gvk := converter.GetGVKForKind(protoKind)
|
||||
if gvk == nil {
|
||||
// not all kinds are for top-level resources, since these won't have a schema,
|
||||
// we can safely continue
|
||||
continue
|
||||
}
|
||||
gk := schema.GroupKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
prefVersion, ok := preferredResourceVersions[gk]
|
||||
// if we don't have a known preferred version for this group or we are the preferred version
|
||||
// add this as the model name for the schema
|
||||
if !ok || prefVersion == gvk.Version {
|
||||
schemaID := converter.GVKToSchemaID(*gvk)
|
||||
schemaToModel[schemaID] = modelName
|
||||
}
|
||||
}
|
||||
return schemaToModel, err
|
||||
}
|
51
pkg/schema/definitions/schema.go
Normal file
51
pkg/schema/definitions/schema.go
Normal file
@ -0,0 +1,51 @@
|
||||
package definitions
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler/v2/pkg/schemas"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
const (
|
||||
gvkExtensionName = "x-kubernetes-group-version-kind"
|
||||
gvkExtensionGroup = "group"
|
||||
gvkExtensionVersion = "version"
|
||||
gvkExtensionKind = "kind"
|
||||
defaultDuration = time.Second * 5
|
||||
)
|
||||
|
||||
// Register registers the schemaDefinition schema.
|
||||
func Register(baseSchema *types.APISchemas, client discovery.DiscoveryInterface) {
|
||||
handler := schemaDefinitionHandler{
|
||||
client: client,
|
||||
refreshStale: defaultDuration,
|
||||
}
|
||||
baseSchema.MustAddSchema(types.APISchema{
|
||||
Schema: &schemas.Schema{
|
||||
ID: "schemaDefinition",
|
||||
PluralName: "schemaDefinitions",
|
||||
ResourceMethods: []string{"GET"},
|
||||
},
|
||||
ByIDHandler: handler.byIDHandler,
|
||||
})
|
||||
}
|
||||
|
||||
type schemaDefinition struct {
|
||||
DefinitionType string `json:"definitionType"`
|
||||
Definitions map[string]definition `json:"definitions"`
|
||||
}
|
||||
|
||||
type definition struct {
|
||||
ResourceFields map[string]definitionField `json:"resourceFields"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type definitionField struct {
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subtype,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
122
pkg/schema/definitions/visitor.go
Normal file
122
pkg/schema/definitions/visitor.go
Normal file
@ -0,0 +1,122 @@
|
||||
package definitions
|
||||
|
||||
import (
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
// schemaFieldVisitor implements proto.SchemaVisitor and turns a given schema into a definitionField.
|
||||
type schemaFieldVisitor struct {
|
||||
field definitionField
|
||||
definitions map[string]definition
|
||||
models proto.Models
|
||||
}
|
||||
|
||||
// VisitArray turns an array into a definitionField (stored on the receiver). For arrays of complex types, will also
|
||||
// visit the subtype.
|
||||
func (s *schemaFieldVisitor) VisitArray(array *proto.Array) {
|
||||
field := definitionField{
|
||||
Description: array.GetDescription(),
|
||||
}
|
||||
// this currently is not recursive and provides little information for nested types- while this isn't optimal,
|
||||
// it was kept this way to provide backwards compat with previous endpoints.
|
||||
array.SubType.Accept(s)
|
||||
subField := s.field
|
||||
field.Type = "array"
|
||||
field.SubType = subField.Type
|
||||
s.field = field
|
||||
}
|
||||
|
||||
// VisitMap turns a map into a definitionField (stored on the receiver). For maps of complex types, will also visit the
|
||||
// subtype.
|
||||
func (s *schemaFieldVisitor) VisitMap(protoMap *proto.Map) {
|
||||
field := definitionField{
|
||||
Description: protoMap.GetDescription(),
|
||||
}
|
||||
// this currently is not recursive and provides little information for nested types- while this isn't optimal,
|
||||
// it was kept this way to provide backwards compat with previous endpoints.
|
||||
protoMap.SubType.Accept(s)
|
||||
subField := s.field
|
||||
field.Type = "map"
|
||||
field.SubType = subField.Type
|
||||
s.field = field
|
||||
}
|
||||
|
||||
// VisitPrimitive turns a primitive into a definitionField (stored on the receiver).
|
||||
func (s *schemaFieldVisitor) VisitPrimitive(primitive *proto.Primitive) {
|
||||
field := definitionField{
|
||||
Description: primitive.GetDescription(),
|
||||
}
|
||||
if primitive.Type == "number" || primitive.Type == "integer" {
|
||||
field.Type = "int"
|
||||
} else {
|
||||
field.Type = primitive.Type
|
||||
}
|
||||
s.field = field
|
||||
}
|
||||
|
||||
// VisitKind turns a kind into a definitionField and a definition. Both are stored on the receiver.
|
||||
func (s *schemaFieldVisitor) VisitKind(kind *proto.Kind) {
|
||||
path := kind.Path.String()
|
||||
field := definitionField{
|
||||
Description: kind.GetDescription(),
|
||||
Type: path,
|
||||
}
|
||||
if _, ok := s.definitions[path]; ok {
|
||||
// if we have already seen this kind, we don't want to re-evaluate the definition. Some kinds can be
|
||||
// recursive through use of references, so this circuit-break is necessary to avoid infinite loops
|
||||
s.field = field
|
||||
return
|
||||
}
|
||||
schemaDefinition := definition{
|
||||
ResourceFields: map[string]definitionField{},
|
||||
Type: path,
|
||||
Description: kind.GetDescription(),
|
||||
}
|
||||
// this definition may refer to itself, so we mark this as seen to not infinitely recurse
|
||||
s.definitions[path] = definition{}
|
||||
for fieldName, schemaField := range kind.Fields {
|
||||
schemaField.Accept(s)
|
||||
schemaDefinition.ResourceFields[fieldName] = s.field
|
||||
}
|
||||
for _, field := range kind.RequiredFields {
|
||||
current, ok := schemaDefinition.ResourceFields[field]
|
||||
if !ok {
|
||||
// this does silently ignore inconsistent kinds that list
|
||||
continue
|
||||
}
|
||||
current.Required = true
|
||||
schemaDefinition.ResourceFields[field] = current
|
||||
}
|
||||
s.definitions[path] = schemaDefinition
|
||||
// the visitor may have set the field multiple times while evaluating kind fields, so we only set the final
|
||||
// kind-based field at the end
|
||||
s.field = field
|
||||
}
|
||||
|
||||
// VisitReference turns a reference into a definitionField. Will also visit the referred type.
|
||||
func (s *schemaFieldVisitor) VisitReference(ref proto.Reference) {
|
||||
sub := ref.SubSchema()
|
||||
if sub == nil {
|
||||
// if we don't have a sub-schema defined, we can't extract much meaningful information
|
||||
field := definitionField{
|
||||
Description: ref.GetDescription(),
|
||||
Type: ref.Reference(),
|
||||
}
|
||||
s.field = field
|
||||
return
|
||||
}
|
||||
sub.Accept(s)
|
||||
field := s.field
|
||||
field.Description = ref.GetDescription()
|
||||
s.field = field
|
||||
}
|
||||
|
||||
// VisitArbitrary turns an abitrary (item with no type) into a definitionField (stored on the receiver).
|
||||
func (s *schemaFieldVisitor) VisitArbitrary(arb *proto.Arbitrary) {
|
||||
// In certain cases k8s seems to not provide a type for certain fields. We assume for the
|
||||
// purposes of this visitor that all of these have a type of string.
|
||||
s.field = definitionField{
|
||||
Description: arb.GetDescription(),
|
||||
Type: "string",
|
||||
}
|
||||
}
|
@ -2,9 +2,10 @@ package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/wrangler/v2/pkg/schemas"
|
||||
k8sSchema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
"github.com/rancher/steve/pkg/resources/schemas"
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
"github.com/rancher/steve/pkg/schema/definitions"
|
||||
"github.com/rancher/steve/pkg/server/handler"
|
||||
"github.com/rancher/steve/pkg/server/router"
|
||||
"github.com/rancher/steve/pkg/summarycache"
|
||||
@ -141,6 +142,7 @@ func setup(ctx context.Context, server *Server) error {
|
||||
if err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, server.ClientFactory, sf, server.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
definitions.Register(server.BaseSchemas, server.controllers.K8s.Discovery())
|
||||
|
||||
summaryCache := summarycache.New(sf, ccache)
|
||||
summaryCache.Start(ctx)
|
||||
|
Loading…
Reference in New Issue
Block a user