mirror of
https://github.com/rancher/steve.git
synced 2025-08-18 14:18:04 +00:00
Refactor schema IDs and paths
This commit is contained in:
parent
0baa096865
commit
ad67c46055
86
pkg/controllers/schema/schemas.go
Normal file
86
pkg/controllers/schema/schemas.go
Normal 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
|
||||||
|
}
|
@ -50,11 +50,35 @@ func Handler(prefix string, cfg *rest.Config) (http.Handler, error) {
|
|||||||
proxy.UpgradeTransport = upgradeTransport
|
proxy.UpgradeTransport = upgradeTransport
|
||||||
proxy.UseRequestLocation = true
|
proxy.UseRequestLocation = true
|
||||||
|
|
||||||
if len(prefix) > 2 {
|
handler := setHost(target.Host, proxy)
|
||||||
return stripLeaveSlash(prefix, proxy), nil
|
|
||||||
|
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
|
// like http.StripPrefix, but always leaves an initial slash. (so that our
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
package resources
|
package common
|
||||||
|
|
||||||
import (
|
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"
|
||||||
"github.com/rancher/norman/pkg/types/convert"
|
"github.com/rancher/norman/pkg/types/convert"
|
||||||
"github.com/rancher/norman/pkg/types/values"
|
"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) {
|
func Formatter(request *types.APIRequest, resource *types.RawResource) {
|
||||||
selfLink := convert.ToString(values.GetValueN(resource.Values, "metadata", "selfLink"))
|
selfLink := convert.ToString(values.GetValueN(resource.Values, "metadata", "selfLink"))
|
||||||
if selfLink == "" {
|
if selfLink == "" {
|
23
pkg/resources/schema.go
Normal file
23
pkg/resources/schema.go
Normal 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
|
||||||
|
}
|
83
pkg/resources/schema/collection.go
Normal file
83
pkg/resources/schema/collection.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
76
pkg/resources/schema/converter/discovery.go
Normal file
76
pkg/resources/schema/converter/discovery.go
Normal 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
|
||||||
|
}
|
37
pkg/resources/schema/converter/k8stonorman.go
Normal file
37
pkg/resources/schema/converter/k8stonorman.go
Normal 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
|
||||||
|
}
|
104
pkg/resources/schema/converter/openapi.go
Normal file
104
pkg/resources/schema/converter/openapi.go
Normal 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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package server
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -17,8 +17,10 @@ type defaultMapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *defaultMapper) FromInternal(data map[string]interface{}) {
|
func (d *defaultMapper) FromInternal(data map[string]interface{}) {
|
||||||
if t, ok := data["type"]; ok {
|
if data["kind"] != "" && data["apiVersion"] != "" {
|
||||||
data["_type"] = t
|
if t, ok := data["type"]; ok && data != nil {
|
||||||
|
data["_type"] = t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := data["id"]; ok || data == nil {
|
if _, ok := data["id"]; ok || data == nil {
|
112
pkg/resources/schema/factory.go
Normal file
112
pkg/resources/schema/factory.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -2,40 +2,31 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"github.com/rancher/naok/pkg/counts"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rancher/naok/pkg/accesscontrol"
|
"github.com/rancher/naok/pkg/accesscontrol"
|
||||||
"github.com/rancher/naok/pkg/attributes"
|
"github.com/rancher/naok/pkg/attributes"
|
||||||
k8sproxy "github.com/rancher/naok/pkg/proxy"
|
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/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/types"
|
||||||
"github.com/rancher/norman/pkg/urlbuilder"
|
"github.com/rancher/norman/pkg/urlbuilder"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/client-go/rest"
|
"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 (
|
var (
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
a := &apiServer{
|
a := &apiServer{
|
||||||
Router: mux.NewRouter(),
|
Router: mux.NewRouter(),
|
||||||
cf: cf,
|
sf: sf,
|
||||||
as: as,
|
server: api.NewAPIServer(),
|
||||||
sf: sf,
|
|
||||||
server: api.NewAPIServer(),
|
|
||||||
baseSchemas: types.EmptySchemas(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
counts.Register(a.baseSchemas)
|
|
||||||
subscribe.Register(a.baseSchemas)
|
|
||||||
|
|
||||||
a.Router.NotFoundHandler, err = k8sproxy.Handler("/", cfg)
|
a.Router.NotFoundHandler, err = k8sproxy.Handler("/", cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -48,22 +39,8 @@ func newAPIServer(cfg *rest.Config, cf proxy.ClientGetter, as *accesscontrol.Acc
|
|||||||
|
|
||||||
type apiServer struct {
|
type apiServer struct {
|
||||||
*mux.Router
|
*mux.Router
|
||||||
cf proxy.ClientGetter
|
sf schema.Factory
|
||||||
as *accesscontrol.AccessStore
|
server *api.Server
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiServer) common(rw http.ResponseWriter, req *http.Request) (*types.APIRequest, bool) {
|
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"},
|
Groups: []string{"system:masters"},
|
||||||
}
|
}
|
||||||
|
|
||||||
accessSet := a.as.AccessFor(user)
|
schemas, err := a.sf.Schemas(user)
|
||||||
schemas, err := a.sf.Schemas("", accessSet, a.newSchemas)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rw.Write([]byte(err.Error()))
|
rw.Write([]byte(err.Error()))
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
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 {
|
func (a *apiServer) Schema(base string, schema *types.Schema) string {
|
||||||
gvr := attributes.GVR(schema)
|
gvr := attributes.GVR(schema)
|
||||||
|
if gvr.Resource == "" {
|
||||||
if gvr.Group == "" && gvr.Version != "" && gvr.Resource != "" {
|
return urlbuilder.ConstructBasicURL(base, "v1", schema.PluralName)
|
||||||
return urlbuilder.ConstructBasicURL(base, gvr.Version, gvr.Resource)
|
|
||||||
}
|
}
|
||||||
|
return urlbuilder.ConstructBasicURL(base, "v1", strings.ToLower(schema.ID))
|
||||||
if gvr.Resource != "" {
|
|
||||||
return urlbuilder.ConstructBasicURL(base, "v1", "apis", gvr.Group, gvr.Version, gvr.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
return urlbuilder.ConstructBasicURL(base, "v1", schema.PluralName)
|
|
||||||
}
|
}
|
||||||
|
@ -12,18 +12,12 @@ import (
|
|||||||
type APIFunc func(*types.APIRequest)
|
type APIFunc func(*types.APIRequest)
|
||||||
|
|
||||||
func (a *apiServer) routes() error {
|
func (a *apiServer) routes() error {
|
||||||
a.Path("/v1/{type:schemas}").Handler(a.handle(nil))
|
a.Path("/v1/{type}").Handler(a.handle(nil))
|
||||||
a.Path("/v1/{type:schemas}/{name}").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}/{name}").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/apis/{group}/{version}/{resource}").Handler(a.handle(a.k8sAPI))
|
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}/{nameorns}").Handler(a.handle(a.k8sAPI))
|
||||||
|
a.Path("/v1/apis/{group}/{version}/{resource}/{namespace}/{name}").Handler(a.handle(a.k8sAPI))
|
||||||
|
|
||||||
return nil
|
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) {
|
func (a *apiServer) k8sAPI(apiOp *types.APIRequest) {
|
||||||
vars := mux.Vars(apiOp.Request)
|
vars := mux.Vars(apiOp.Request)
|
||||||
|
group := vars["group"]
|
||||||
|
if group == "core" {
|
||||||
|
group = ""
|
||||||
|
}
|
||||||
|
|
||||||
apiOp.Name = vars["name"]
|
apiOp.Name = vars["name"]
|
||||||
apiOp.Type = a.sf.ByGVR(schema.GroupVersionResource{
|
apiOp.Type = a.sf.ByGVR(schema.GroupVersionResource{
|
||||||
Version: vars["version"],
|
Version: vars["version"],
|
||||||
Group: vars["group"],
|
Group: group,
|
||||||
Resource: vars["resource"],
|
Resource: vars["resource"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,9 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"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/accesscontrol"
|
||||||
"github.com/rancher/naok/pkg/client"
|
"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/apiextensions.k8s.io"
|
||||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io"
|
"github.com/rancher/wrangler-api/pkg/generated/controllers/apiregistration.k8s.io"
|
||||||
rbaccontroller "github.com/rancher/wrangler-api/pkg/generated/controllers/rbac"
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sf := schemas.Register(ctx,
|
as := accesscontrol.NewAccessStore(rbac.Rbac().V1())
|
||||||
cf,
|
sf := resources.SchemaFactory(cf, as)
|
||||||
|
|
||||||
|
schema.Register(ctx,
|
||||||
k8s.Discovery(),
|
k8s.Discovery(),
|
||||||
crd.Apiextensions().V1beta1().CustomResourceDefinition(),
|
crd.Apiextensions().V1beta1().CustomResourceDefinition(),
|
||||||
api.Apiregistration().V1().APIService(),
|
api.Apiregistration().V1().APIService(),
|
||||||
)
|
sf)
|
||||||
|
|
||||||
as := accesscontrol.NewAccessStore(rbac.Rbac().V1())
|
|
||||||
|
|
||||||
return func() error {
|
return func() error {
|
||||||
handler, err := newAPIServer(restConfig, cf, as, sf)
|
handler, err := newAPIServer(restConfig, sf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user