mirror of
https://github.com/rancher/steve.git
synced 2025-04-27 02:51:10 +00:00
234 lines
5.2 KiB
Go
234 lines
5.2 KiB
Go
package schema
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
|
|
apiserver "github.com/rancher/apiserver/pkg/server"
|
|
"github.com/rancher/apiserver/pkg/types"
|
|
"github.com/rancher/steve/pkg/accesscontrol"
|
|
"github.com/rancher/steve/pkg/attributes"
|
|
"github.com/rancher/wrangler/v3/pkg/name"
|
|
"github.com/sirupsen/logrus"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/cache"
|
|
"k8s.io/apiserver/pkg/endpoints/request"
|
|
)
|
|
|
|
type Collection struct {
|
|
toSync int32
|
|
baseSchema *types.APISchemas
|
|
schemas map[string]*types.APISchema
|
|
templates map[string][]*Template
|
|
notifiers map[int]func()
|
|
notifierID int
|
|
byGVR map[schema.GroupVersionResource]string
|
|
byGVK map[schema.GroupVersionKind]string
|
|
cache *cache.LRUExpireCache
|
|
userCache *cache.LRUExpireCache
|
|
lock sync.RWMutex
|
|
|
|
ctx context.Context
|
|
running map[string]func()
|
|
as accesscontrol.AccessSetLookup
|
|
}
|
|
|
|
type Template struct {
|
|
Group string
|
|
Kind string
|
|
ID string
|
|
Customize func(*types.APISchema)
|
|
Formatter types.Formatter
|
|
Store types.Store
|
|
Start func(ctx context.Context) error
|
|
StoreFactory func(types.Store) types.Store
|
|
}
|
|
|
|
func WrapServer(factory Factory, server *apiserver.Server) http.Handler {
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
user, ok := request.UserFrom(req.Context())
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
schemas, err := factory.Schemas(user)
|
|
if err != nil {
|
|
logrus.Errorf("failed to lookup schemas for user %v: %v", user, err)
|
|
http.Error(rw, "schemas failed", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
server.Handle(&types.APIRequest{
|
|
Request: req,
|
|
Response: rw,
|
|
Schemas: schemas,
|
|
})
|
|
})
|
|
}
|
|
|
|
func NewCollection(ctx context.Context, baseSchema *types.APISchemas, access accesscontrol.AccessSetLookup) *Collection {
|
|
return &Collection{
|
|
baseSchema: baseSchema,
|
|
schemas: map[string]*types.APISchema{},
|
|
templates: map[string][]*Template{},
|
|
byGVR: map[schema.GroupVersionResource]string{},
|
|
byGVK: map[schema.GroupVersionKind]string{},
|
|
cache: cache.NewLRUExpireCache(1000),
|
|
userCache: cache.NewLRUExpireCache(1000),
|
|
notifiers: map[int]func(){},
|
|
ctx: ctx,
|
|
as: access,
|
|
running: map[string]func(){},
|
|
}
|
|
}
|
|
|
|
func (c *Collection) OnChange(ctx context.Context, cb func()) {
|
|
c.lock.Lock()
|
|
id := c.notifierID
|
|
c.notifierID++
|
|
c.notifiers[id] = cb
|
|
c.lock.Unlock()
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
c.lock.Lock()
|
|
delete(c.notifiers, id)
|
|
c.lock.Unlock()
|
|
}()
|
|
}
|
|
|
|
func (c *Collection) Reset(schemas map[string]*types.APISchema) {
|
|
byGVK := map[schema.GroupVersionKind]string{}
|
|
byGVR := map[schema.GroupVersionResource]string{}
|
|
|
|
for _, s := range schemas {
|
|
gvr := attributes.GVR(s)
|
|
if gvr.Resource != "" {
|
|
byGVR[gvr] = s.ID
|
|
}
|
|
gvk := attributes.GVK(s)
|
|
if gvk.Kind != "" {
|
|
byGVK[gvk] = s.ID
|
|
}
|
|
|
|
c.applyTemplates(s)
|
|
}
|
|
|
|
c.lock.Lock()
|
|
c.startStopTemplate(schemas)
|
|
c.schemas = schemas
|
|
c.byGVR = byGVR
|
|
c.byGVK = byGVK
|
|
for _, k := range c.cache.Keys() {
|
|
c.cache.Remove(k)
|
|
}
|
|
c.lock.Unlock()
|
|
c.lock.RLock()
|
|
for _, f := range c.notifiers {
|
|
f()
|
|
}
|
|
c.lock.RUnlock()
|
|
}
|
|
|
|
func start(ctx context.Context, templates []*Template) error {
|
|
for _, template := range templates {
|
|
if template.Start == nil {
|
|
continue
|
|
}
|
|
if err := template.Start(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Collection) startStopTemplate(schemas map[string]*types.APISchema) {
|
|
for id := range schemas {
|
|
if _, ok := c.running[id]; ok {
|
|
continue
|
|
}
|
|
templates := c.templates[id]
|
|
if len(templates) == 0 {
|
|
continue
|
|
}
|
|
|
|
subCtx, cancel := context.WithCancel(c.ctx)
|
|
if err := start(subCtx, templates); err != nil {
|
|
cancel()
|
|
logrus.Errorf("failed to start schema template: %s", id)
|
|
continue
|
|
}
|
|
c.running[id] = cancel
|
|
}
|
|
|
|
for id, cancel := range c.running {
|
|
if _, ok := schemas[id]; !ok {
|
|
cancel()
|
|
delete(c.running, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Collection) Schema(id string) *types.APISchema {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
|
|
return c.schemas[id]
|
|
}
|
|
|
|
func (c *Collection) IDs() (result []string) {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
|
|
seen := map[string]bool{}
|
|
for _, id := range c.byGVR {
|
|
if seen[id] {
|
|
continue
|
|
}
|
|
seen[id] = true
|
|
result = append(result, id)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Collection) ByGVR(gvr schema.GroupVersionResource) string {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
|
|
id, ok := c.byGVR[gvr]
|
|
if ok {
|
|
return id
|
|
}
|
|
gvr.Resource = name.GuessPluralName(strings.ToLower(gvr.Resource))
|
|
return c.byGVK[schema.GroupVersionKind{
|
|
Group: gvr.Group,
|
|
Version: gvr.Version,
|
|
Kind: gvr.Resource,
|
|
}]
|
|
}
|
|
|
|
func (c *Collection) ByGVK(gvk schema.GroupVersionKind) string {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
|
|
return c.byGVK[gvk]
|
|
}
|
|
|
|
func (c *Collection) AddTemplate(templates ...Template) {
|
|
c.lock.RLock()
|
|
defer c.lock.RUnlock()
|
|
|
|
for i, template := range templates {
|
|
if template.Kind != "" {
|
|
c.templates[template.Group+"/"+template.Kind] = append(c.templates[template.Group+"/"+template.Kind], &templates[i])
|
|
} else if template.ID != "" {
|
|
c.templates[template.ID] = append(c.templates[template.ID], &templates[i])
|
|
}
|
|
if template.Kind == "" && template.Group == "" && template.ID == "" {
|
|
c.templates[""] = append(c.templates[""], &templates[i])
|
|
}
|
|
}
|
|
}
|