1
0
mirror of https://github.com/rancher/steve.git synced 2025-08-02 07:12:36 +00:00

Refactor schema IDs and paths

This commit is contained in:
Darren Shepherd 2019-08-13 16:36:03 -07:00
parent 0baa096865
commit ad67c46055
15 changed files with 595 additions and 427 deletions

View File

@ -0,0 +1,86 @@
package schema
import (
"context"
"sync"
"sync/atomic"
"time"
schema2 "github.com/rancher/naok/pkg/resources/schema"
"github.com/rancher/naok/pkg/resources/schema/converter"
apiextcontrollerv1beta1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io/v1"
"github.com/sirupsen/logrus"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/client-go/discovery"
apiv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
)
type handler struct {
sync.Mutex
toSync int32
schemas *schema2.Collection
client discovery.DiscoveryInterface
}
func Register(ctx context.Context,
discovery discovery.DiscoveryInterface,
crd apiextcontrollerv1beta1.CustomResourceDefinitionController,
apiService v1.APIServiceController,
schemas *schema2.Collection) {
h := &handler{
client: discovery,
schemas: schemas,
}
apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
crd.OnChange(ctx, "schema", h.OnChangeCRD)
}
func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) {
return crd, h.queueRefresh()
}
func (h *handler) OnChangeAPIService(key string, api *apiv1.APIService) (*apiv1.APIService, error) {
return api, h.queueRefresh()
}
func (h *handler) queueRefresh() error {
atomic.StoreInt32(&h.toSync, 1)
go func() {
time.Sleep(500 * time.Millisecond)
if err := h.refreshAll(); err != nil {
logrus.Errorf("failed to sync schemas: %v", err)
atomic.StoreInt32(&h.toSync, 1)
}
}()
return nil
}
func (h *handler) refreshAll() error {
h.Lock()
defer h.Unlock()
if !h.needToSync() {
return nil
}
logrus.Info("Refreshing all schemas")
schemas, err := converter.ToSchemas(h.client)
if err != nil {
return err
}
h.schemas.Reset(schemas)
return nil
}
func (h *handler) needToSync() bool {
old := atomic.SwapInt32(&h.toSync, 0)
return old == 1
}

View File

@ -50,11 +50,35 @@ func Handler(prefix string, cfg *rest.Config) (http.Handler, error) {
proxy.UpgradeTransport = upgradeTransport
proxy.UseRequestLocation = true
if len(prefix) > 2 {
return stripLeaveSlash(prefix, proxy), nil
handler := setHost(target.Host, proxy)
if len(target.Path) > 1 {
handler = prependPath(target.Path[:len(target.Path)-1], handler)
}
return proxy, nil
if len(prefix) > 2 {
return stripLeaveSlash(prefix, handler), nil
}
return handler, nil
}
func setHost(host string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req.Host = host
h.ServeHTTP(w, req)
})
}
func prependPath(prefix string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if len(req.URL.Path) > 1 {
req.URL.Path = prefix + req.URL.Path
} else {
req.URL.Path = prefix
}
h.ServeHTTP(w, req)
})
}
// like http.StripPrefix, but always leaves an initial slash. (so that our

View File

@ -1,11 +1,20 @@
package resources
package common
import (
"github.com/rancher/naok/pkg/resources/schema"
"github.com/rancher/norman/pkg/store/proxy"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/norman/pkg/types/convert"
"github.com/rancher/norman/pkg/types/values"
)
func Register(collection *schema.Collection, clientGetter proxy.ClientGetter) {
collection.AddTemplate(&schema.Template{
Store: proxy.NewProxyStore(clientGetter),
Formatter: Formatter,
})
}
func Formatter(request *types.APIRequest, resource *types.RawResource) {
selfLink := convert.ToString(values.GetValueN(resource.Values, "metadata", "selfLink"))
if selfLink == "" {

23
pkg/resources/schema.go Normal file
View File

@ -0,0 +1,23 @@
package resources
import (
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/counts"
"github.com/rancher/naok/pkg/resources/common"
"github.com/rancher/naok/pkg/resources/schema"
"github.com/rancher/norman/pkg/store/proxy"
"github.com/rancher/norman/pkg/subscribe"
"github.com/rancher/norman/pkg/types"
)
func SchemaFactory(getter proxy.ClientGetter, as *accesscontrol.AccessStore) *schema.Collection {
baseSchema := types.EmptySchemas()
collection := schema.NewCollection(baseSchema, as)
counts.Register(baseSchema)
subscribe.Register(baseSchema)
common.Register(collection, getter)
return collection
}

View File

@ -0,0 +1,83 @@
package schema
import (
"strings"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/norman/pkg/types"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authentication/user"
)
type Factory interface {
Schemas(user user.Info) (*types.Schemas, error)
ByGVR(gvr schema.GroupVersionResource) string
}
type Collection struct {
toSync int32
baseSchema *types.Schemas
schemas map[string]*types.Schema
templates map[string]*Template
byGVR map[schema.GroupVersionResource]string
as *accesscontrol.AccessStore
}
type Template struct {
Group string
Kind string
ID string
Formatter types.Formatter
Store types.Store
Mapper types.Mapper
}
func NewCollection(baseSchema *types.Schemas, access *accesscontrol.AccessStore) *Collection {
return &Collection{
baseSchema: baseSchema,
schemas: map[string]*types.Schema{},
templates: map[string]*Template{},
byGVR: map[schema.GroupVersionResource]string{},
as: access,
}
}
func (c *Collection) Reset(schemas map[string]*types.Schema) {
byGVR := map[schema.GroupVersionResource]string{}
for _, s := range schemas {
gvr := attributes.GVR(s)
if gvr.Resource != "" {
gvr.Resource = strings.ToLower(gvr.Resource)
byGVR[gvr] = s.ID
}
kind := attributes.Kind(s)
if kind != "" {
gvr.Resource = strings.ToLower(kind)
byGVR[gvr] = s.ID
}
}
c.schemas = schemas
c.byGVR = byGVR
}
func (c *Collection) ByGVR(gvr schema.GroupVersionResource) string {
gvr.Resource = strings.ToLower(gvr.Resource)
return c.byGVR[gvr]
}
func (c *Collection) AddTemplate(template *Template) {
if template.Kind != "" {
c.templates[template.Group+"/"+template.Kind] = template
}
if template.ID != "" {
c.templates[template.ID] = template
}
if template.Kind == "" && template.Group == "" && template.ID == "" {
c.templates[""] = template
}
}

View File

@ -0,0 +1,76 @@
package converter
import (
"strings"
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
func AddDiscovery(client discovery.DiscoveryInterface, schemas map[string]*types.Schema) error {
logrus.Info("Refreshing all schemas")
_, resourceLists, err := client.ServerGroupsAndResources()
if err != nil {
return err
}
var errs []error
for _, resourceList := range resourceLists {
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
errs = append(errs, err)
}
if err := refresh(gv, resourceList, schemas); err != nil {
errs = append(errs, err)
}
}
return merr.NewErrors(errs...)
}
func refresh(gv schema.GroupVersion, resources *metav1.APIResourceList, schemas map[string]*types.Schema) error {
for _, resource := range resources.APIResources {
if strings.Contains(resource.Name, "/") {
continue
}
gvk := schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: resource.Kind,
}
gvr := gvk.GroupVersion().WithResource(resource.Name)
logrus.Infof("APIVersion %s/%s Kind %s", gvk.Group, gvk.Version, gvk.Kind)
schema := schemas[gvkToSchemaID(gvk)]
if schema == nil {
schema = &types.Schema{
Type: "schema",
Dynamic: true,
}
attributes.SetGVK(schema, gvk)
}
schema.PluralName = resource.Name
attributes.SetAPIResource(schema, resource)
// switch ID to be GVR, not GVK
if schema.ID != "" {
delete(schemas, schema.ID)
}
schema.ID = GVRToSchemaID(gvr)
schemas[schema.ID] = schema
}
return nil
}

View File

@ -0,0 +1,37 @@
package converter
import (
"fmt"
"github.com/rancher/norman/pkg/types"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
)
func gvkToSchemaID(gvk schema.GroupVersionKind) string {
if gvk.Group == "" {
return fmt.Sprintf("apis/core/%s/%s", gvk.Version, gvk.Kind)
}
return fmt.Sprintf("apis/%s/%s/%s", gvk.Group, gvk.Version, gvk.Kind)
}
func GVRToSchemaID(gvk schema.GroupVersionResource) string {
if gvk.Group == "" {
return fmt.Sprintf("apis/core/%s/%s", gvk.Version, gvk.Resource)
}
return fmt.Sprintf("apis/%s/%s/%s", gvk.Group, gvk.Version, gvk.Resource)
}
func ToSchemas(client discovery.DiscoveryInterface) (map[string]*types.Schema, error) {
result := map[string]*types.Schema{}
if err := AddOpenAPI(client, result); err != nil {
return nil, err
}
if err := AddDiscovery(client, result); err != nil {
return nil, err
}
return result, nil
}

View File

@ -0,0 +1,104 @@
package converter
import (
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/norman/pkg/types/convert"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/kube-openapi/pkg/util/proto"
)
func modelToSchema(modelName string, k *proto.Kind) *types.Schema {
s := types.Schema{
ID: modelName,
Type: "schema",
ResourceFields: map[string]types.Field{},
Attributes: map[string]interface{}{},
Description: k.GetDescription(),
Dynamic: true,
}
for fieldName, schemaField := range k.Fields {
s.ResourceFields[fieldName] = toField(schemaField)
}
for _, fieldName := range k.RequiredFields {
if f, ok := s.ResourceFields[fieldName]; ok {
f.Required = true
s.ResourceFields[fieldName] = f
}
}
if ms, ok := k.Extensions["x-kubernetes-group-version-kind"].([]interface{}); ok {
for _, mv := range ms {
if m, ok := mv.(map[interface{}]interface{}); ok {
gvk := schema.GroupVersionKind{
Group: convert.ToString(m["group"]),
Version: convert.ToString(m["version"]),
Kind: convert.ToString(m["kind"]),
}
s.ID = gvkToSchemaID(gvk)
attributes.SetGVK(&s, gvk)
}
}
}
return &s
}
func AddOpenAPI(client discovery.DiscoveryInterface, schemas map[string]*types.Schema) error {
openapi, err := client.OpenAPISchema()
if err != nil {
return err
}
models, err := proto.NewOpenAPIData(openapi)
if err != nil {
return err
}
for _, modelName := range models.ListModels() {
model := models.LookupModel(modelName)
if k, ok := model.(*proto.Kind); ok {
schema := modelToSchema(modelName, k)
schemas[schema.ID] = schema
}
}
return nil
}
func toField(schema proto.Schema) types.Field {
f := types.Field{
Description: schema.GetDescription(),
Nullable: true,
Create: true,
Update: true,
}
switch v := schema.(type) {
case *proto.Array:
f.Type = "array[" + toField(v.SubType).Type + "]"
case *proto.Primitive:
if v.Type == "number" {
f.Type = "int"
} else {
f.Type = v.Type
}
case *proto.Map:
f.Type = "map[" + toField(v.SubType).Type + "]"
case *proto.Kind:
parts := v.Path.Get()
f.Type = parts[len(parts)-1]
case proto.Reference:
f.Type = v.SubSchema().GetPath().String()
case *proto.Arbitrary:
default:
logrus.Errorf("unknown type: %v", schema)
f.Type = "json"
}
return f
}

View File

@ -1,4 +1,4 @@
package server
package schema
import (
"fmt"
@ -17,8 +17,10 @@ type defaultMapper struct {
}
func (d *defaultMapper) FromInternal(data map[string]interface{}) {
if t, ok := data["type"]; ok {
data["_type"] = t
if data["kind"] != "" && data["apiVersion"] != "" {
if t, ok := data["type"]; ok && data != nil {
data["_type"] = t
}
}
if _, ok := data["id"]; ok || data == nil {

View File

@ -0,0 +1,112 @@
package schema
import (
"fmt"
"net/http"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/norman/pkg/api/builtin"
"github.com/rancher/norman/pkg/types"
"k8s.io/apiserver/pkg/authentication/user"
)
func newSchemas() (*types.Schemas, error) {
s, err := types.NewSchemas(builtin.Schemas)
if err != nil {
return nil, err
}
s.DefaultMapper = func() types.Mapper {
return newDefaultMapper()
}
return s, nil
}
func (c *Collection) Schemas(user user.Info) (*types.Schemas, error) {
access := c.as.AccessFor(user)
return c.schemasForSubject("", access)
}
func (c *Collection) schemasForSubject(subjectKey string, access *accesscontrol.AccessSet) (*types.Schemas, error) {
result, err := newSchemas()
if err != nil {
return nil, err
}
if _, err := result.AddSchemas(c.baseSchema); err != nil {
return nil, err
}
for _, s := range c.schemas {
gr := attributes.GR(s)
if gr.Resource == "" {
if err := result.AddSchema(*s); err != nil {
return nil, err
}
continue
}
verbs := attributes.Verbs(s)
verbAccess := accesscontrol.AccessListMap{}
for _, verb := range verbs {
a := access.AccessListFor(verb, gr)
if len(a) > 0 {
verbAccess[verb] = a
}
}
if len(verbAccess) == 0 {
continue
}
s = s.DeepCopy()
attributes.SetAccess(s, verbAccess)
if verbAccess.AnyVerb("list", "get") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodGet)
s.CollectionMethods = append(s.CollectionMethods, http.MethodGet)
}
if verbAccess.AnyVerb("delete") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodDelete)
}
if verbAccess.AnyVerb("update") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodPut)
}
if verbAccess.AnyVerb("create") {
s.CollectionMethods = append(s.CollectionMethods, http.MethodPost)
}
c.applyTemplates(s)
if err := result.AddSchema(*s); err != nil {
return nil, err
}
}
return result, nil
}
func (c *Collection) applyTemplates(schema *types.Schema) {
templates := []*Template{
c.templates[schema.ID],
c.templates[fmt.Sprintf("%s/%s", attributes.Group(schema), attributes.Kind(schema))],
c.templates[""],
}
for _, t := range templates {
if t == nil {
continue
}
if schema.Mapper == nil {
schema.Mapper = t.Mapper
}
if schema.Formatter == nil {
schema.Formatter = t.Formatter
}
if schema.Store == nil {
schema.Store = t.Store
}
}
}

View File

@ -1,75 +0,0 @@
package schemas
import (
"net/http"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/norman/pkg/api/builtin"
"github.com/rancher/norman/pkg/types"
)
func DefaultSchemaFactory() (*types.Schemas, error) {
return types.NewSchemas(builtin.Schemas)
}
func (s *schemas) Schemas(subjectKey string, access *accesscontrol.AccessSet, schemasFactory func() (*types.Schemas, error)) (*types.Schemas, error) {
cached, ok := s.access.Load(subjectKey)
if ok {
return cached.(*types.Schemas), nil
}
if schemasFactory == nil {
schemasFactory = DefaultSchemaFactory
}
result, err := schemasFactory()
if err != nil {
return nil, err
}
for _, s := range s.openSchemas {
if err := result.AddSchema(*s); err != nil {
return nil, err
}
}
for _, s := range s.schemas {
gr := attributes.GR(s)
verbs := attributes.Verbs(s)
verbAccess := accesscontrol.AccessListMap{}
for _, verb := range verbs {
a := access.AccessListFor(verb, gr)
if len(a) > 0 {
verbAccess[verb] = a
}
}
if len(verbAccess) == 0 {
continue
}
s = s.DeepCopy()
attributes.SetAccess(s, verbAccess)
if verbAccess.AnyVerb("list", "get") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodGet)
s.CollectionMethods = append(s.CollectionMethods, http.MethodGet)
}
if verbAccess.AnyVerb("delete") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodDelete)
}
if verbAccess.AnyVerb("update") {
s.ResourceMethods = append(s.ResourceMethods, http.MethodPut)
}
if verbAccess.AnyVerb("create") {
s.CollectionMethods = append(s.CollectionMethods, http.MethodPost)
}
if err := result.AddSchema(*s); err != nil {
return nil, err
}
}
return result, nil
}

View File

@ -1,285 +0,0 @@
package schemas
import (
"context"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/attributes"
"github.com/rancher/naok/pkg/resources"
"github.com/rancher/norman/pkg/store/proxy"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/norman/pkg/types/convert"
apiextcontrollerv1beta1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io/v1beta1"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io/v1"
"github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
apiv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
"k8s.io/kube-openapi/pkg/util/proto"
)
type SchemaFactory interface {
Schemas(subjectKey string, access *accesscontrol.AccessSet, schemasFactory func() (*types.Schemas, error)) (*types.Schemas, error)
ByGVR(gvr schema.GroupVersionResource) string
}
type handler struct {
formatter types.Formatter
schemas *schemas
schemaStore types.Store
client discovery.DiscoveryInterface
}
type schemas struct {
toSync int32
sync.Mutex
apiGroups []*metav1.APIGroup
gvkToName map[schema.GroupVersionKind]string
gvrToName map[schema.GroupVersionResource]string
openSchemas map[string]*types.Schema
schemas map[string]*types.Schema
access sync.Map
}
func (s *schemas) reset() {
s.apiGroups = nil
s.gvkToName = map[schema.GroupVersionKind]string{}
s.gvrToName = map[schema.GroupVersionResource]string{}
s.openSchemas = map[string]*types.Schema{}
s.schemas = map[string]*types.Schema{}
s.access.Range(func(key, value interface{}) bool {
s.access.Delete(key)
return true
})
}
func (s *schemas) ByGVR(gvr schema.GroupVersionResource) string {
return s.gvrToName[gvr]
}
func Register(ctx context.Context, clientGetter proxy.ClientGetter, discovery discovery.DiscoveryInterface, crd apiextcontrollerv1beta1.CustomResourceDefinitionController,
apiService v1.APIServiceController) SchemaFactory {
h := &handler{
formatter: resources.Formatter,
client: discovery,
schemas: &schemas{},
schemaStore: proxy.NewProxyStore(clientGetter),
}
apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
crd.OnChange(ctx, "schema", h.OnChangeCRD)
return h.schemas
}
func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) {
return crd, h.queueRefresh()
}
func (h *handler) OnChangeAPIService(key string, api *apiv1.APIService) (*apiv1.APIService, error) {
return api, h.queueRefresh()
}
func schemaID(gvk schema.GroupVersionKind) string {
if gvk.Group == "" {
return fmt.Sprintf("io.k8s.api.core.%s.%s", gvk.Version, gvk.Kind)
}
return fmt.Sprintf("io.k8s.api.%s.%s.%s", gvk.Group, gvk.Version, gvk.Kind)
}
func (h *handler) queueRefresh() error {
atomic.StoreInt32(&h.schemas.toSync, 1)
go func() {
time.Sleep(500 * time.Millisecond)
if err := h.refreshAll(); err != nil {
logrus.Errorf("failed to sync schemas: %v", err)
}
}()
return nil
}
func (h *handler) refreshAll() error {
h.schemas.Lock()
defer h.schemas.Unlock()
if !h.needToSync() {
return nil
}
logrus.Info("Refreshing all schemas")
groups, resourceLists, err := h.client.ServerGroupsAndResources()
if err != nil {
return err
}
openapi, err := h.client.OpenAPISchema()
if err != nil {
return err
}
h.schemas.reset()
h.schemas.apiGroups = groups
if err := populate(openapi, h.schemas); err != nil {
return err
}
var errs []error
for _, resourceList := range resourceLists {
gv, err := schema.ParseGroupVersion(resourceList.GroupVersion)
if err != nil {
errs = append(errs, err)
}
if err := h.refresh(gv, resourceList); err != nil {
errs = append(errs, err)
}
}
return merr.NewErrors(errs...)
}
func (h *handler) needToSync() bool {
old := atomic.SwapInt32(&h.schemas.toSync, 0)
return old == 1
}
func (h *handler) refresh(gv schema.GroupVersion, resources *metav1.APIResourceList) error {
for _, resource := range resources.APIResources {
if strings.Contains(resource.Name, "/") {
continue
}
gvk := schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: resource.Kind,
}
gvr := gvk.GroupVersion().WithResource(resource.Name)
logrus.Infof("APIVersion %s/%s Kind %s", gvk.Group, gvk.Version, gvk.Kind)
schema := h.schemas.openSchemas[h.schemas.gvkToName[gvk]]
if schema == nil {
schema = &types.Schema{
ID: schemaID(gvk),
Type: "schema",
Dynamic: true,
}
attributes.SetGVK(schema, gvk)
}
schema.PluralName = resource.Name
attributes.SetAPIResource(schema, resource)
schema.Store = h.schemaStore
schema.Formatter = h.formatter
h.schemas.schemas[schema.ID] = schema
h.schemas.gvkToName[gvk] = schema.ID
h.schemas.gvrToName[gvr] = schema.ID
}
return nil
}
func toField(schema proto.Schema) types.Field {
f := types.Field{
Description: schema.GetDescription(),
Nullable: true,
Create: true,
Update: true,
}
switch v := schema.(type) {
case *proto.Array:
f.Type = "array[" + toField(v.SubType).Type + "]"
case *proto.Primitive:
if v.Type == "number" {
f.Type = "int"
} else {
f.Type = v.Type
}
case *proto.Map:
f.Type = "map[" + toField(v.SubType).Type + "]"
case *proto.Kind:
parts := v.Path.Get()
f.Type = parts[len(parts)-1]
case proto.Reference:
f.Type = v.SubSchema().GetPath().String()
case *proto.Arbitrary:
default:
logrus.Errorf("unknown type: %v", schema)
f.Type = "json"
}
return f
}
func modelToSchema(modelName string, k *proto.Kind, schemas *schemas) {
s := types.Schema{
ID: modelName,
Type: "schema",
ResourceFields: map[string]types.Field{},
Attributes: map[string]interface{}{},
Description: k.GetDescription(),
Dynamic: true,
}
for fieldName, schemaField := range k.Fields {
s.ResourceFields[fieldName] = toField(schemaField)
}
for _, fieldName := range k.RequiredFields {
if f, ok := s.ResourceFields[fieldName]; ok {
f.Required = true
s.ResourceFields[fieldName] = f
}
}
if ms, ok := k.Extensions["x-kubernetes-group-version-kind"].([]interface{}); ok {
for _, mv := range ms {
if m, ok := mv.(map[interface{}]interface{}); ok {
gvk := schema.GroupVersionKind{
Group: convert.ToString(m["group"]),
Version: convert.ToString(m["version"]),
Kind: convert.ToString(m["kind"]),
}
attributes.SetGVK(&s, gvk)
schemas.gvkToName[gvk] = s.ID
}
}
}
schemas.openSchemas[s.ID] = &s
}
func populate(openapi *openapi_v2.Document, schemas *schemas) error {
models, err := proto.NewOpenAPIData(openapi)
if err != nil {
return err
}
for _, modelName := range models.ListModels() {
model := models.LookupModel(modelName)
if k, ok := model.(*proto.Kind); ok {
modelToSchema(modelName, k, schemas)
}
}
return nil
}

View File

@ -2,40 +2,31 @@ package server
import (
"net/http"
"github.com/rancher/naok/pkg/counts"
"strings"
"github.com/gorilla/mux"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/attributes"
k8sproxy "github.com/rancher/naok/pkg/proxy"
"github.com/rancher/naok/pkg/schemas"
"github.com/rancher/naok/pkg/resources/schema"
"github.com/rancher/norman/pkg/api"
"github.com/rancher/norman/pkg/store/proxy"
"github.com/rancher/norman/pkg/subscribe"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/norman/pkg/urlbuilder"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/rest"
)
func newAPIServer(cfg *rest.Config, cf proxy.ClientGetter, as *accesscontrol.AccessStore, sf schemas.SchemaFactory) (http.Handler, error) {
func newAPIServer(cfg *rest.Config, sf schema.Factory) (http.Handler, error) {
var (
err error
)
a := &apiServer{
Router: mux.NewRouter(),
cf: cf,
as: as,
sf: sf,
server: api.NewAPIServer(),
baseSchemas: types.EmptySchemas(),
Router: mux.NewRouter(),
sf: sf,
server: api.NewAPIServer(),
}
counts.Register(a.baseSchemas)
subscribe.Register(a.baseSchemas)
a.Router.NotFoundHandler, err = k8sproxy.Handler("/", cfg)
if err != nil {
return nil, err
@ -48,22 +39,8 @@ func newAPIServer(cfg *rest.Config, cf proxy.ClientGetter, as *accesscontrol.Acc
type apiServer struct {
*mux.Router
cf proxy.ClientGetter
as *accesscontrol.AccessStore
sf schemas.SchemaFactory
server *api.Server
baseSchemas *types.Schemas
}
func (a *apiServer) newSchemas() (*types.Schemas, error) {
schemas, err := schemas.DefaultSchemaFactory()
if err != nil {
return nil, err
}
schemas.DefaultMapper = newDefaultMapper
schemas.AddSchemas(a.baseSchemas)
return schemas, nil
sf schema.Factory
server *api.Server
}
func (a *apiServer) common(rw http.ResponseWriter, req *http.Request) (*types.APIRequest, bool) {
@ -72,8 +49,7 @@ func (a *apiServer) common(rw http.ResponseWriter, req *http.Request) (*types.AP
Groups: []string{"system:masters"},
}
accessSet := a.as.AccessFor(user)
schemas, err := a.sf.Schemas("", accessSet, a.newSchemas)
schemas, err := a.sf.Schemas(user)
if err != nil {
rw.Write([]byte(err.Error()))
rw.WriteHeader(http.StatusInternalServerError)
@ -96,14 +72,8 @@ func (a *apiServer) common(rw http.ResponseWriter, req *http.Request) (*types.AP
func (a *apiServer) Schema(base string, schema *types.Schema) string {
gvr := attributes.GVR(schema)
if gvr.Group == "" && gvr.Version != "" && gvr.Resource != "" {
return urlbuilder.ConstructBasicURL(base, gvr.Version, gvr.Resource)
if gvr.Resource == "" {
return urlbuilder.ConstructBasicURL(base, "v1", schema.PluralName)
}
if gvr.Resource != "" {
return urlbuilder.ConstructBasicURL(base, "v1", "apis", gvr.Group, gvr.Version, gvr.Resource)
}
return urlbuilder.ConstructBasicURL(base, "v1", schema.PluralName)
return urlbuilder.ConstructBasicURL(base, "v1", strings.ToLower(schema.ID))
}

View File

@ -12,18 +12,12 @@ import (
type APIFunc func(*types.APIRequest)
func (a *apiServer) routes() error {
a.Path("/v1/{type:schemas}").Handler(a.handle(nil))
a.Path("/v1/{type:schemas}/{name}").Handler(a.handle(nil))
a.Path("/v1/{type:subscribe}").Handler(a.handle(nil))
a.Path("/v1/{type:counts}").Handler(a.handle(nil))
a.Path("/v1/{type:counts}/{name}").Handler(a.handle(nil))
a.Path("/{version:v1}/{resource}").Handler(a.handle(a.k8sAPI))
a.Path("/{version:v1}/{resource}/{nameorns}").Handler(a.handle(a.k8sAPI))
a.Path("/{version:v1}/{resource}/{namespace}/{name}").Handler(a.handle(a.k8sAPI))
a.Path("/v1/{type}").Handler(a.handle(nil))
a.Path("/v1/{type:schemas}/{name:.*}").Handler(a.handle(nil))
a.Path("/v1/{type}/{name}").Handler(a.handle(nil))
a.Path("/v1/apis/{group}/{version}/{resource}").Handler(a.handle(a.k8sAPI))
a.Path("/v1/apis/{group}/{version}/{resource}/{nameorns}").Handler(a.handle(a.k8sAPI))
a.Path("/v1/apis/{group}/{version}/{resource}/{namespace}/{name}").Handler(a.handle(a.k8sAPI))
return nil
}
@ -46,10 +40,15 @@ func (a *apiServer) api(rw http.ResponseWriter, req *http.Request, apiFunc APIFu
func (a *apiServer) k8sAPI(apiOp *types.APIRequest) {
vars := mux.Vars(apiOp.Request)
group := vars["group"]
if group == "core" {
group = ""
}
apiOp.Name = vars["name"]
apiOp.Type = a.sf.ByGVR(schema.GroupVersionResource{
Version: vars["version"],
Group: vars["group"],
Group: group,
Resource: vars["resource"],
})

View File

@ -4,9 +4,12 @@ import (
"context"
"net/http"
"github.com/rancher/naok/pkg/resources"
"github.com/rancher/naok/pkg/controllers/schema"
"github.com/rancher/naok/pkg/accesscontrol"
"github.com/rancher/naok/pkg/client"
"github.com/rancher/naok/pkg/schemas"
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiextensions.k8s.io"
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io"
rbaccontroller "github.com/rancher/wrangler-api/pkg/generated/controllers/rbac"
@ -74,17 +77,17 @@ func startAPI(ctx context.Context, listenAddress string, restConfig *rest.Config
return nil, err
}
sf := schemas.Register(ctx,
cf,
as := accesscontrol.NewAccessStore(rbac.Rbac().V1())
sf := resources.SchemaFactory(cf, as)
schema.Register(ctx,
k8s.Discovery(),
crd.Apiextensions().V1beta1().CustomResourceDefinition(),
api.Apiregistration().V1().APIService(),
)
as := accesscontrol.NewAccessStore(rbac.Rbac().V1())
sf)
return func() error {
handler, err := newAPIServer(restConfig, cf, as, sf)
handler, err := newAPIServer(restConfig, sf)
if err != nil {
return err
}