mirror of
https://github.com/niusmallnan/steve.git
synced 2025-06-27 06:56:55 +00:00
Improve counts and columns
This commit is contained in:
parent
46f5e218e9
commit
f81721ef93
15
main.go
15
main.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rancher/naok/pkg/server"
|
"github.com/rancher/naok/pkg/server"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"github.com/rancher/wrangler/pkg/signals"
|
"github.com/rancher/wrangler/pkg/signals"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -49,8 +51,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
|
logging := flag.NewFlagSet("", flag.PanicOnError)
|
||||||
|
klog.InitFlags(logging)
|
||||||
if c.Bool("debug") {
|
if c.Bool("debug") {
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
if err := logging.Parse([]string{
|
||||||
|
"-v=7",
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := logging.Parse([]string{
|
||||||
|
"-v=0",
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx := signals.SetupSignalHandler(context.Background())
|
ctx := signals.SetupSignalHandler(context.Background())
|
||||||
return server.Run(ctx, config)
|
return server.Run(ctx, config)
|
||||||
|
@ -24,6 +24,10 @@ func NewFactory(cfg *rest.Config) (*Factory, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Factory) DynamicClient() dynamic.Interface {
|
||||||
|
return p.client
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Factory) Client(ctx *types.APIRequest, s *types.Schema) (dynamic.ResourceInterface, error) {
|
func (p *Factory) Client(ctx *types.APIRequest, s *types.Schema) (dynamic.ResourceInterface, error) {
|
||||||
gvr := attributes.GVR(s)
|
gvr := attributes.GVR(s)
|
||||||
if len(ctx.Namespaces) > 0 {
|
if len(ctx.Namespaces) > 0 {
|
||||||
|
29
pkg/clustercache/cancel_collection.go
Normal file
29
pkg/clustercache/cancel_collection.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package clustercache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cancelCollection struct {
|
||||||
|
id int64
|
||||||
|
items sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCollection) Add(ctx context.Context, obj interface{}) {
|
||||||
|
key := atomic.AddInt64(&c.id, 1)
|
||||||
|
c.items.Store(key, obj)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
c.items.Delete(key)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cancelCollection) List() (result []interface{}) {
|
||||||
|
c.items.Range(func(key, value interface{}) bool {
|
||||||
|
result = append(result, value)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
286
pkg/clustercache/controller.go
Normal file
286
pkg/clustercache/controller.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package clustercache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
meta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
|
||||||
|
"github.com/rancher/naok/pkg/attributes"
|
||||||
|
"github.com/rancher/naok/pkg/resources/schema"
|
||||||
|
"github.com/rancher/norman/pkg/types"
|
||||||
|
"github.com/rancher/wrangler/pkg/generic"
|
||||||
|
"github.com/rancher/wrangler/pkg/merr"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logOnce = sync.Once{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error
|
||||||
|
|
||||||
|
type ClusterCache interface {
|
||||||
|
AddController(gvk schema2.GroupVersionKind, informer cache.SharedIndexInformer)
|
||||||
|
List(gvr schema2.GroupVersionResource) []interface{}
|
||||||
|
OnAdd(ctx context.Context, handler Handler)
|
||||||
|
OnRemove(ctx context.Context, handler Handler)
|
||||||
|
OnChange(ctx context.Context, handler Handler)
|
||||||
|
OnSchemas(schemas *schema.Collection) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type event struct {
|
||||||
|
add bool
|
||||||
|
gvr schema2.GroupVersionResource
|
||||||
|
obj runtime.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type watcher struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancel func()
|
||||||
|
informer cache.SharedIndexInformer
|
||||||
|
gvk schema2.GroupVersionKind
|
||||||
|
gvr schema2.GroupVersionResource
|
||||||
|
start bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type clusterCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
typed map[schema2.GroupVersionKind]cache.SharedIndexInformer
|
||||||
|
informerFactory dynamicinformer.DynamicSharedInformerFactory
|
||||||
|
controllerFactory generic.ControllerManager
|
||||||
|
watchers map[schema2.GroupVersionResource]*watcher
|
||||||
|
workqueue workqueue.DelayingInterface
|
||||||
|
|
||||||
|
addHandlers cancelCollection
|
||||||
|
removeHandlers cancelCollection
|
||||||
|
changeHandlers cancelCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClusterCache(ctx context.Context, client dynamic.Interface) ClusterCache {
|
||||||
|
c := &clusterCache{
|
||||||
|
ctx: ctx,
|
||||||
|
typed: map[schema2.GroupVersionKind]cache.SharedIndexInformer{},
|
||||||
|
informerFactory: dynamicinformer.NewDynamicSharedInformerFactory(client, 2*time.Hour),
|
||||||
|
watchers: map[schema2.GroupVersionResource]*watcher{},
|
||||||
|
workqueue: workqueue.NewNamedDelayingQueue("cluster-cache"),
|
||||||
|
}
|
||||||
|
go c.start()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) AddController(gvk schema2.GroupVersionKind, informer cache.SharedIndexInformer) {
|
||||||
|
h.typed[gvk] = informer
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSchema(schema *types.Schema) bool {
|
||||||
|
canList := false
|
||||||
|
canWatch := false
|
||||||
|
for _, verb := range attributes.Verbs(schema) {
|
||||||
|
switch verb {
|
||||||
|
case "list":
|
||||||
|
canList = true
|
||||||
|
case "watch":
|
||||||
|
canWatch = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canList || !canWatch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if attributes.PreferredVersion(schema) != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if attributes.PreferredGroup(schema) != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) addResourceEventHandler(gvr schema2.GroupVersionResource, informer cache.SharedIndexInformer) {
|
||||||
|
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
if rObj, ok := obj.(runtime.Object); ok {
|
||||||
|
h.workqueue.Add(event{
|
||||||
|
add: true,
|
||||||
|
obj: rObj,
|
||||||
|
gvr: gvr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
if rObj, ok := obj.(runtime.Object); ok {
|
||||||
|
h.workqueue.Add(event{
|
||||||
|
obj: rObj,
|
||||||
|
gvr: gvr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) OnSchemas(schemas *schema.Collection) error {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
var (
|
||||||
|
toStart = map[schema2.GroupVersionResource]*watcher{}
|
||||||
|
gvrs = map[schema2.GroupVersionResource]bool{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, id := range schemas.IDs() {
|
||||||
|
schema := schemas.Schema(id)
|
||||||
|
if !validSchema(schema) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gvr := attributes.GVR(schema)
|
||||||
|
gvk := attributes.GVK(schema)
|
||||||
|
gvrs[gvr] = true
|
||||||
|
|
||||||
|
w := h.watchers[gvr]
|
||||||
|
if w != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(h.ctx)
|
||||||
|
w = &watcher{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
gvk: gvk,
|
||||||
|
gvr: gvr,
|
||||||
|
informer: h.typed[gvk],
|
||||||
|
}
|
||||||
|
toStart[gvr] = w
|
||||||
|
|
||||||
|
if w.informer == nil {
|
||||||
|
w.informer = h.informerFactory.ForResource(gvr).Informer()
|
||||||
|
w.start = true
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Infof("Watching counts for %s", gvk.String())
|
||||||
|
h.addResourceEventHandler(gvr, w.informer)
|
||||||
|
name := fmt.Sprintf("meta %s", gvk)
|
||||||
|
h.controllerFactory.AddHandler(ctx, gvk, w.informer, name, func(key string, obj runtime.Object) (object runtime.Object, e error) {
|
||||||
|
return callAll(h.changeHandlers.List(), gvr, key, obj)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for gvr, w := range h.watchers {
|
||||||
|
if !gvrs[gvr] {
|
||||||
|
logrus.Infof("Stopping count watch on %s", gvr)
|
||||||
|
w.cancel()
|
||||||
|
delete(h.watchers, gvr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range toStart {
|
||||||
|
if !w.start {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go w.informer.Run(w.ctx.Done())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range toStart {
|
||||||
|
cache.WaitForCacheSync(w.ctx.Done(), w.informer.HasSynced)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, w := range toStart {
|
||||||
|
if err := h.controllerFactory.EnsureStart(w.ctx, w.gvk, 5); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
h.watchers[w.gvr] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
return merr.NewErrors(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) List(gvr schema2.GroupVersionResource) []interface{} {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
|
||||||
|
w, ok := h.watchers[gvr]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.informer.GetStore().List()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) start() {
|
||||||
|
for {
|
||||||
|
eventObj, ok := h.workqueue.Get()
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
event := eventObj.(event)
|
||||||
|
w := h.watchers[event.gvr]
|
||||||
|
if w == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := toKey(event.obj)
|
||||||
|
if event.add {
|
||||||
|
_, err := callAll(h.addHandlers.List(), event.gvr, key, event.obj)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("failed to handle add event: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err := callAll(h.removeHandlers.List(), event.gvr, key, event.obj)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("failed to handle remove event: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toKey(obj runtime.Object) string {
|
||||||
|
meta, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ns := meta.GetNamespace()
|
||||||
|
if ns == "" {
|
||||||
|
return meta.GetName()
|
||||||
|
}
|
||||||
|
return ns + "/" + meta.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) OnAdd(ctx context.Context, handler Handler) {
|
||||||
|
h.addHandlers.Add(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) OnRemove(ctx context.Context, handler Handler) {
|
||||||
|
h.removeHandlers.Add(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *clusterCache) OnChange(ctx context.Context, handler Handler) {
|
||||||
|
h.changeHandlers.Add(ctx, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func callAll(handlers []interface{}, gvr schema2.GroupVersionResource, key string, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
var errs []error
|
||||||
|
for _, handler := range handlers {
|
||||||
|
f := handler.(Handler)
|
||||||
|
if err := f(gvr, key, obj); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, merr.NewErrors(errs...)
|
||||||
|
}
|
34
pkg/controllers/helmrelease/handler.go
Normal file
34
pkg/controllers/helmrelease/handler.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package helmrelease
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HelmRelease struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
releases map[string]HelmRelease
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnConfigMapChange(key string, obj *v1.ConfigMap) (*v1.ConfigMap, error) {
|
||||||
|
if !h.isRelease(obj) {
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) OnSecretChange(key string, obj *v1.Secret) (*v1.Secret, error) {
|
||||||
|
if !h.isRelease(obj) {
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *handler) isRelease(obj metav1.Object) bool {
|
||||||
|
if obj == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return obj.GetLabels()["OWNER"] == "TILLER"
|
||||||
|
}
|
@ -16,6 +16,10 @@ import (
|
|||||||
apiv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
apiv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SchemasHandler interface {
|
||||||
|
OnSchemas(schemas *schema2.Collection) error
|
||||||
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
@ -23,33 +27,43 @@ type handler struct {
|
|||||||
schemas *schema2.Collection
|
schemas *schema2.Collection
|
||||||
client discovery.DiscoveryInterface
|
client discovery.DiscoveryInterface
|
||||||
crd apiextcontrollerv1beta1.CustomResourceDefinitionClient
|
crd apiextcontrollerv1beta1.CustomResourceDefinitionClient
|
||||||
|
handler SchemasHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(ctx context.Context,
|
func Register(ctx context.Context,
|
||||||
discovery discovery.DiscoveryInterface,
|
discovery discovery.DiscoveryInterface,
|
||||||
crd apiextcontrollerv1beta1.CustomResourceDefinitionController,
|
crd apiextcontrollerv1beta1.CustomResourceDefinitionController,
|
||||||
apiService v1.APIServiceController,
|
apiService v1.APIServiceController,
|
||||||
schemas *schema2.Collection) {
|
schemasHandler SchemasHandler,
|
||||||
|
schemas *schema2.Collection) (init func() error) {
|
||||||
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
client: discovery,
|
client: discovery,
|
||||||
schemas: schemas,
|
schemas: schemas,
|
||||||
|
handler: schemasHandler,
|
||||||
crd: crd,
|
crd: crd,
|
||||||
}
|
}
|
||||||
|
|
||||||
apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
|
apiService.OnChange(ctx, "schema", h.OnChangeAPIService)
|
||||||
crd.OnChange(ctx, "schema", h.OnChangeCRD)
|
crd.OnChange(ctx, "schema", h.OnChangeCRD)
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
h.queueRefresh()
|
||||||
|
return h.refreshAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) {
|
func (h *handler) OnChangeCRD(key string, crd *v1beta1.CustomResourceDefinition) (*v1beta1.CustomResourceDefinition, error) {
|
||||||
return crd, h.queueRefresh()
|
h.queueRefresh()
|
||||||
|
return crd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) OnChangeAPIService(key string, api *apiv1.APIService) (*apiv1.APIService, error) {
|
func (h *handler) OnChangeAPIService(key string, api *apiv1.APIService) (*apiv1.APIService, error) {
|
||||||
return api, h.queueRefresh()
|
h.queueRefresh()
|
||||||
|
return api, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) queueRefresh() error {
|
func (h *handler) queueRefresh() {
|
||||||
atomic.StoreInt32(&h.toSync, 1)
|
atomic.StoreInt32(&h.toSync, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -59,8 +73,6 @@ func (h *handler) queueRefresh() error {
|
|||||||
atomic.StoreInt32(&h.toSync, 1)
|
atomic.StoreInt32(&h.toSync, 1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) refreshAll() error {
|
func (h *handler) refreshAll() error {
|
||||||
@ -78,6 +90,9 @@ func (h *handler) refreshAll() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.schemas.Reset(schemas)
|
h.schemas.Reset(schemas)
|
||||||
|
if h.handler != nil {
|
||||||
|
return h.handler.OnSchemas(h.schemas)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -85,7 +84,6 @@ func prependPath(prefix string, h http.Handler) http.Handler {
|
|||||||
// regexps will work.)
|
// regexps will work.)
|
||||||
func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
|
func stripLeaveSlash(prefix string, h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
fmt.Println(req.Method, req.URL.Path)
|
|
||||||
p := strings.TrimPrefix(req.URL.Path, prefix)
|
p := strings.TrimPrefix(req.URL.Path, prefix)
|
||||||
if len(p) >= len(req.URL.Path) {
|
if len(p) >= len(req.URL.Path) {
|
||||||
http.NotFound(w, req)
|
http.NotFound(w, req)
|
||||||
|
@ -6,6 +6,21 @@ import (
|
|||||||
"github.com/rancher/norman/pkg/types"
|
"github.com/rancher/norman/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
NameColumn = table.Column{
|
||||||
|
Name: "Name",
|
||||||
|
Field: "metadata.name",
|
||||||
|
Type: "string",
|
||||||
|
Format: "name",
|
||||||
|
}
|
||||||
|
CreatedColumn = table.Column{
|
||||||
|
Name: "Created",
|
||||||
|
Field: "metadata.creationTimestamp",
|
||||||
|
Type: "string",
|
||||||
|
Format: "date",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type DefaultColumns struct {
|
type DefaultColumns struct {
|
||||||
types.EmptyMapper
|
types.EmptyMapper
|
||||||
}
|
}
|
||||||
@ -13,18 +28,8 @@ type DefaultColumns struct {
|
|||||||
func (d *DefaultColumns) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
|
func (d *DefaultColumns) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
|
||||||
if attributes.Columns(schema) == nil {
|
if attributes.Columns(schema) == nil {
|
||||||
attributes.SetColumns(schema, []table.Column{
|
attributes.SetColumns(schema, []table.Column{
|
||||||
{
|
NameColumn,
|
||||||
Name: "Name",
|
CreatedColumn,
|
||||||
Field: "metadata.name",
|
|
||||||
Type: "string",
|
|
||||||
Format: "name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Created",
|
|
||||||
Field: "metadata.creationTimestamp",
|
|
||||||
Type: "string",
|
|
||||||
Format: "date",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
173
pkg/resources/core/resources.go
Normal file
173
pkg/resources/core/resources.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rancher/naok/pkg/resources/common"
|
||||||
|
"github.com/rancher/naok/pkg/resources/schema"
|
||||||
|
"github.com/rancher/naok/pkg/table"
|
||||||
|
"github.com/rancher/norman/pkg/data"
|
||||||
|
"github.com/rancher/norman/pkg/types/convert"
|
||||||
|
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(collection *schema.Collection) {
|
||||||
|
collection.AddTemplate(&schema.Template{
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
ComputedColumns: func(obj data.Object) {
|
||||||
|
var fields []string
|
||||||
|
for field := range obj.Map("data") {
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.SetNested(len(obj.Map("data")), "metadata", "computed", "data")
|
||||||
|
obj.SetNested(fields, "metadata", "computed", "fields")
|
||||||
|
},
|
||||||
|
Columns: []table.Column{
|
||||||
|
common.NameColumn,
|
||||||
|
{
|
||||||
|
Name: "Data",
|
||||||
|
Field: "metadata.computed.data",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Fields",
|
||||||
|
Field: "metadata.computed.fields",
|
||||||
|
Type: "array[string]",
|
||||||
|
},
|
||||||
|
common.CreatedColumn,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
collection.AddTemplate(&schema.Template{
|
||||||
|
Group: "apps",
|
||||||
|
Kind: "ControllerRevision",
|
||||||
|
ComputedColumns: func(obj data.Object) {
|
||||||
|
for _, owner := range obj.Map("metadata").Slice("ownerReferences") {
|
||||||
|
if owner.Bool("controller") {
|
||||||
|
obj.SetNested(getReference(collection, obj, owner), "metadata", "computed", "controller")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Columns: []table.Column{
|
||||||
|
common.NameColumn,
|
||||||
|
{
|
||||||
|
Name: "Controller",
|
||||||
|
Field: "metadata.computed.controller",
|
||||||
|
Type: "reference",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Revision",
|
||||||
|
Field: "revision",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
common.CreatedColumn,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
collection.AddTemplate(&schema.Template{
|
||||||
|
Group: "apps",
|
||||||
|
Kind: "DaemonSet",
|
||||||
|
Columns: []table.Column{
|
||||||
|
common.NameColumn,
|
||||||
|
{
|
||||||
|
Name: "Desired",
|
||||||
|
Field: "status.desiredNumberScheduled",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Current",
|
||||||
|
Field: "status.currentNumberScheduled",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Ready",
|
||||||
|
Field: "status.numberReady",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Up-to-date",
|
||||||
|
Field: "status.updatedNumberScheduled",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Available",
|
||||||
|
Field: "status.numberAvailable",
|
||||||
|
Type: "int",
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// Name: "Node Selector",
|
||||||
|
// Field: "metadata.computed.nodeSelector",
|
||||||
|
// Type: "selector",
|
||||||
|
//},
|
||||||
|
common.CreatedColumn,
|
||||||
|
},
|
||||||
|
//ComputedColumns: func(obj data.Object) {
|
||||||
|
// obj.SetNested(podSelector(obj.String("metadata", "namespace"), obj.Map("spec", "selector")), "metadata", "computed", "nodeSelector")
|
||||||
|
//},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReference(collection *schema.Collection, obj data.Object, owner data.Object) map[string]interface{} {
|
||||||
|
apiVersion := owner.String("apiVersion")
|
||||||
|
kind := owner.String("kind")
|
||||||
|
name := owner.String("name")
|
||||||
|
namespace := obj.String("metadata", "namespace")
|
||||||
|
gvk := schema2.FromAPIVersionAndKind(apiVersion, kind)
|
||||||
|
typeName := collection.ByGVK(gvk)
|
||||||
|
id := fmt.Sprintf("%s/%s", namespace, name)
|
||||||
|
if namespace == "" {
|
||||||
|
id = name
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
"type": typeName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type selector struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Namespace string `json:"namespace,omitempty"`
|
||||||
|
Terms []string `json:"terms,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func podSelector(namespace string, obj data.Object) (result selector) {
|
||||||
|
result.Type = "core.v1.pod"
|
||||||
|
result.Namespace = namespace
|
||||||
|
|
||||||
|
for k, v := range obj.Map("matchLabels") {
|
||||||
|
vStr := convert.ToString(v)
|
||||||
|
if vStr == "" {
|
||||||
|
result.Terms = append(result.Terms, k)
|
||||||
|
} else {
|
||||||
|
result.Terms = append(result.Terms, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, term := range obj.Slice("matchExpressions") {
|
||||||
|
key := term.String("key")
|
||||||
|
values := term.StringSlice("values")
|
||||||
|
switch term.String("operator") {
|
||||||
|
case "In":
|
||||||
|
if len(values) == 1 {
|
||||||
|
result.Terms = append(result.Terms, fmt.Sprintf("%s=%s", key, values[0]))
|
||||||
|
} else {
|
||||||
|
result.Terms = append(result.Terms, fmt.Sprintf("%s in (%s)", key, strings.Join(values, ",")))
|
||||||
|
}
|
||||||
|
case "Not In":
|
||||||
|
if len(values) == 1 {
|
||||||
|
result.Terms = append(result.Terms, fmt.Sprintf("%s!=%s", key, values[0]))
|
||||||
|
} else {
|
||||||
|
result.Terms = append(result.Terms, fmt.Sprintf("%s notin (%s)", key, strings.Join(values, ",")))
|
||||||
|
}
|
||||||
|
case "Exists":
|
||||||
|
result.Terms = append(result.Terms, key)
|
||||||
|
case "NotExists":
|
||||||
|
result.Terms = append(result.Terms, "!"+key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -1,18 +1,19 @@
|
|||||||
package counts
|
package counts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rancher/naok/pkg/attributes"
|
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"github.com/rancher/naok/pkg/accesscontrol"
|
"github.com/rancher/naok/pkg/accesscontrol"
|
||||||
|
"github.com/rancher/naok/pkg/attributes"
|
||||||
|
"github.com/rancher/naok/pkg/clustercache"
|
||||||
"github.com/rancher/norman/pkg/store/empty"
|
"github.com/rancher/norman/pkg/store/empty"
|
||||||
"github.com/rancher/norman/pkg/types"
|
"github.com/rancher/norman/pkg/types"
|
||||||
"github.com/sirupsen/logrus"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"golang.org/x/sync/errgroup"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -21,14 +22,9 @@ var (
|
|||||||
"schema": true,
|
"schema": true,
|
||||||
"apiRoot": true,
|
"apiRoot": true,
|
||||||
}
|
}
|
||||||
slow = map[string]bool{
|
|
||||||
"io.k8s.api.management.cattle.io.v3.CatalogTemplateVersion": true,
|
|
||||||
"io.k8s.api.management.cattle.io.v3.CatalogTemplate": true,
|
|
||||||
}
|
|
||||||
listTimeout = 1750 * time.Millisecond
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(schemas *types.Schemas) {
|
func Register(schemas *types.Schemas, ccache clustercache.ClusterCache) {
|
||||||
schemas.MustImportAndCustomize(Count{}, func(schema *types.Schema) {
|
schemas.MustImportAndCustomize(Count{}, func(schema *types.Schema) {
|
||||||
schema.CollectionMethods = []string{http.MethodGet}
|
schema.CollectionMethods = []string{http.MethodGet}
|
||||||
schema.ResourceMethods = []string{http.MethodGet}
|
schema.ResourceMethods = []string{http.MethodGet}
|
||||||
@ -40,137 +36,126 @@ func Register(schemas *types.Schemas) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
schema.Store = &Store{}
|
schema.Store = &Store{
|
||||||
|
ccache: ccache,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Count struct {
|
type Count struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Counts map[string]ItemCount `json:"counts,omitempty"`
|
Counts map[string]ItemCount `json:"counts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemCount struct {
|
type ItemCount struct {
|
||||||
Count int `json:"count,omitempty"`
|
Count int `json:"count,omitempty"`
|
||||||
Namespaces map[string]int `json:"namespaces,omitempty"`
|
Namespaces map[string]int `json:"namespaces,omitempty"`
|
||||||
Revision string `json:"revision,omitempty"`
|
Revision int `json:"revision,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
empty.Store
|
empty.Store
|
||||||
|
ccache clustercache.ClusterCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.Schema, id string) (types.APIObject, error) {
|
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.Schema, id string) (types.APIObject, error) {
|
||||||
c, err := s.getCount(apiOp, listTimeout, true)
|
c := s.getCount(apiOp)
|
||||||
return types.ToAPI(c), err
|
return types.ToAPI(c), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) List(apiOp *types.APIRequest, schema *types.Schema, opt *types.QueryOptions) (types.APIObject, error) {
|
func (s *Store) List(apiOp *types.APIRequest, schema *types.Schema, opt *types.QueryOptions) (types.APIObject, error) {
|
||||||
c, err := s.getCount(apiOp, listTimeout, true)
|
c := s.getCount(apiOp)
|
||||||
return types.ToAPI([]interface{}{c}), err
|
return types.ToAPI([]interface{}{c}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.Schema, w types.WatchRequest) (chan types.APIEvent, error) {
|
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.Schema, w types.WatchRequest) (chan types.APIEvent, error) {
|
||||||
c, err := s.getCount(apiOp, listTimeout*10, false)
|
var (
|
||||||
if err != nil {
|
result = make(chan types.APIEvent, 100)
|
||||||
return nil, err
|
counts map[string]ItemCount
|
||||||
}
|
gvrToSchema = map[schema2.GroupVersionResource]*types.Schema{}
|
||||||
|
countLock sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
counts = s.getCount(apiOp).Counts
|
||||||
ctx, cancel := context.WithCancel(apiOp.Context())
|
for id := range counts {
|
||||||
|
schema := apiOp.Schemas.Schema(id)
|
||||||
|
if schema == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
child := make(chan Count)
|
gvrToSchema[attributes.GVR(schema)] = schema
|
||||||
for name, countItem := range c.Counts {
|
|
||||||
wg.Add(1)
|
|
||||||
name := name
|
|
||||||
countItem := countItem
|
|
||||||
go func() {
|
|
||||||
s.watchItem(apiOp.WithContext(ctx), name, countItem, cancel, child)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
<-apiOp.Context().Done()
|
||||||
close(child)
|
close(result)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
result := make(chan types.APIEvent)
|
onChange := func(add bool, gvr schema2.GroupVersionResource, _ string, obj runtime.Object) error {
|
||||||
go func() {
|
countLock.Lock()
|
||||||
defer close(result)
|
defer countLock.Unlock()
|
||||||
|
|
||||||
|
schema := gvrToSchema[gvr]
|
||||||
|
if schema == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiObj := apiOp.Filter(nil, schema, types.ToAPI(obj))
|
||||||
|
if apiObj.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, namespace, revision, ok := getInfo(obj)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
itemCount := counts[schema.ID]
|
||||||
|
if revision <= itemCount.Revision {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if add {
|
||||||
|
itemCount.Count++
|
||||||
|
if namespace != "" {
|
||||||
|
itemCount.Namespaces[namespace]++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemCount.Count--
|
||||||
|
if namespace != "" {
|
||||||
|
itemCount.Namespaces[namespace]--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counts[schema.ID] = itemCount
|
||||||
|
countsCopy := map[string]ItemCount{}
|
||||||
|
for k, v := range counts {
|
||||||
|
countsCopy[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
result <- types.APIEvent{
|
result <- types.APIEvent{
|
||||||
Name: "resource.create",
|
Name: "resource.change",
|
||||||
ResourceType: "count",
|
ResourceType: "counts",
|
||||||
Object: types.ToAPI(c),
|
Object: types.ToAPI(Count{
|
||||||
|
ID: "count",
|
||||||
|
Counts: countsCopy,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
for change := range child {
|
return nil
|
||||||
for k, v := range change.Counts {
|
}
|
||||||
c.Counts[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
result <- types.APIEvent{
|
s.ccache.OnAdd(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
|
||||||
Name: "resource.change",
|
return onChange(true, gvr, key, obj)
|
||||||
ResourceType: "count",
|
})
|
||||||
Object: types.ToAPI(c),
|
s.ccache.OnRemove(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
|
||||||
}
|
return onChange(false, gvr, key, nil)
|
||||||
}
|
})
|
||||||
}()
|
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) watchItem(apiOp *types.APIRequest, schemaID string, start ItemCount, cancel func(), counts chan Count) {
|
func (s *Store) schemasToWatch(apiOp *types.APIRequest) (result []*types.Schema) {
|
||||||
schema := apiOp.Schemas.Schema(schemaID)
|
|
||||||
if schema == nil || schema.Store == nil || apiOp.AccessControl.CanWatch(apiOp, schema) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
logrus.Debugf("watching %s for count", schemaID)
|
|
||||||
defer logrus.Debugf("close watching %s for count", schemaID)
|
|
||||||
w, err := schema.Store.Watch(apiOp, schema, types.WatchRequest{Revision: start.Revision})
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failed to watch %s for counts: %v", schema.ID, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for event := range w {
|
|
||||||
if event.Revision == start.Revision {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := types.Namespace(event.Object.Map())
|
|
||||||
write := false
|
|
||||||
if event.Name == "resource.create" {
|
|
||||||
start.Count++
|
|
||||||
write = true
|
|
||||||
if ns != "" {
|
|
||||||
start.Namespaces[ns]++
|
|
||||||
}
|
|
||||||
} else if event.Name == "resource.remove" {
|
|
||||||
start.Count--
|
|
||||||
write = true
|
|
||||||
if ns != "" {
|
|
||||||
start.Namespaces[ns]--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if write {
|
|
||||||
counts <- Count{Counts: map[string]ItemCount{
|
|
||||||
schemaID: start,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) getCount(apiOp *types.APIRequest, timeout time.Duration, ignoreSlow bool) (Count, error) {
|
|
||||||
var countLock sync.Mutex
|
|
||||||
counts := map[string]ItemCount{}
|
|
||||||
|
|
||||||
errCtx, cancel := context.WithTimeout(apiOp.Context(), timeout)
|
|
||||||
eg, errCtx := errgroup.WithContext(errCtx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
for _, schema := range apiOp.Schemas.Schemas() {
|
for _, schema := range apiOp.Schemas.Schemas() {
|
||||||
if ignore[schema.ID] {
|
if ignore[schema.ID] {
|
||||||
continue
|
continue
|
||||||
@ -184,10 +169,6 @@ func (s *Store) getCount(apiOp *types.APIRequest, timeout time.Duration, ignoreS
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ignoreSlow && slow[schema.ID] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if schema.Store == nil {
|
if schema.Store == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -196,73 +177,69 @@ func (s *Store) getCount(apiOp *types.APIRequest, timeout time.Duration, ignoreS
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
current := schema
|
if apiOp.AccessControl.CanWatch(apiOp, schema) != nil {
|
||||||
eg.Go(func() error {
|
continue
|
||||||
list, err := current.Store.List(apiOp, current, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if list.IsNil() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
itemCount := ItemCount{
|
|
||||||
Namespaces: map[string]int{},
|
|
||||||
Revision: list.ListRevision,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range list.List() {
|
|
||||||
itemCount.Count++
|
|
||||||
ns := types.Namespace(item)
|
|
||||||
if ns != "" {
|
|
||||||
itemCount.Namespaces[ns]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
countLock.Lock()
|
|
||||||
counts[current.ID] = itemCount
|
|
||||||
countLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err = <-future(eg.Wait):
|
|
||||||
case <-errCtx.Done():
|
|
||||||
err = errCtx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
|
||||||
return Count{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// in the case of cancellation go routines could still be running so we copy the map
|
|
||||||
// to avoid returning a map that might get modified
|
|
||||||
countLock.Lock()
|
|
||||||
result := Count{
|
|
||||||
ID: "count",
|
|
||||||
Counts: map[string]ItemCount{},
|
|
||||||
}
|
|
||||||
for k, v := range counts {
|
|
||||||
result.Counts[k] = v
|
|
||||||
}
|
|
||||||
countLock.Unlock()
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func future(f func() error) chan error {
|
|
||||||
result := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer close(result)
|
|
||||||
if err := f(); err != nil {
|
|
||||||
result <- err
|
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
return result
|
result = append(result, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInfo(obj interface{}) (name string, namespace string, revision int, ok bool) {
|
||||||
|
r, ok := obj.(runtime.Object)
|
||||||
|
if !ok {
|
||||||
|
return "", "", 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
meta, err := meta.Accessor(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
revision, err = strconv.Atoi(meta.GetResourceVersion())
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta.GetName(), meta.GetNamespace(), revision, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) getCount(apiOp *types.APIRequest) Count {
|
||||||
|
counts := map[string]ItemCount{}
|
||||||
|
|
||||||
|
for _, schema := range s.schemasToWatch(apiOp) {
|
||||||
|
gvr := attributes.GVR(schema)
|
||||||
|
|
||||||
|
rev := 0
|
||||||
|
itemCount := ItemCount{
|
||||||
|
Count: 1,
|
||||||
|
Namespaces: map[string]int{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, obj := range s.ccache.List(gvr) {
|
||||||
|
_, ns, revision, ok := getInfo(obj)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision > rev {
|
||||||
|
rev = revision
|
||||||
|
}
|
||||||
|
|
||||||
|
itemCount.Count++
|
||||||
|
if ns != "" {
|
||||||
|
itemCount.Namespaces[ns]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemCount.Revision = rev
|
||||||
|
counts[schema.ID] = itemCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return Count{
|
||||||
|
ID: "count",
|
||||||
|
Counts: counts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
64
pkg/resources/helmrelease/convert.go
Normal file
64
pkg/resources/helmrelease/convert.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package helmrelease
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/helm/pkg/proto/hapi/release"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToRelease(data, name string) (*HelmRelease, error) {
|
||||||
|
bytes, err := base64.StdEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hr release.Release
|
||||||
|
if err := proto.Unmarshal(bytes, &hr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hr.Chart == nil || hr.Chart.Metadata == nil {
|
||||||
|
return nil, fmt.Errorf("invalid chart, missing chart or metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
hrVersion := HelmRelease{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: hr.Namespace,
|
||||||
|
},
|
||||||
|
ID: fmt.Sprintf("%s:%s", hr.Namespace, name),
|
||||||
|
Name: hr.Name,
|
||||||
|
FirstDeployed: toTime(hr.Info.FirstDeployed),
|
||||||
|
LastDeployed: toTime(hr.Info.LastDeployed),
|
||||||
|
Deleted: toTime(hr.Info.Deleted),
|
||||||
|
Metadata: *hr.Chart.Metadata,
|
||||||
|
Status: release.Status_Code_name[int32(hr.Info.Status.Code)],
|
||||||
|
Manifest: hr.Manifest,
|
||||||
|
Version: hr.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
if hr.Info.Status != nil {
|
||||||
|
hrVersion.Status = release.Status_Code_name[int32(hr.Info.Status.Code)]
|
||||||
|
for _, template := range hr.Chart.Templates {
|
||||||
|
if strings.EqualFold("readme.md", template.Name) {
|
||||||
|
hrVersion.ReadMe = string(template.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &hrVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTime(t *timestamp.Timestamp) *metav1.Time {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time := metav1.NewTime(time.Unix(t.Seconds, int64(t.Nanos)))
|
||||||
|
return &time
|
||||||
|
}
|
36
pkg/resources/helmrelease/helmrelease.go
Normal file
36
pkg/resources/helmrelease/helmrelease.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package helmrelease
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rancher/norman/pkg/types"
|
||||||
|
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HelmRelease struct {
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
chart.Metadata `json:",inline"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
FirstDeployed *metav1.Time `json:"firstDeployed,omitempty"`
|
||||||
|
LastDeployed *metav1.Time `json:"lastDeployed,omitempty"`
|
||||||
|
Deleted *metav1.Time `json:"deleted,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
Manifest string `json:"manifest,omitempty"`
|
||||||
|
ReadMe string `json:"readMe,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Version int32 `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(schemas *types.Schemas, configMaps v1.ConfigMapClient, secrets v1.SecretClient) {
|
||||||
|
schemas.MustImportAndCustomize(HelmRelease{}, func(schema *types.Schema) {
|
||||||
|
schema.CollectionMethods = []string{http.MethodGet}
|
||||||
|
schema.ResourceMethods = []string{http.MethodGet}
|
||||||
|
schema.Store = &Store{
|
||||||
|
configMaps: configMaps,
|
||||||
|
secrets: secrets,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
62
pkg/resources/helmrelease/store.go
Normal file
62
pkg/resources/helmrelease/store.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package helmrelease
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rancher/norman/pkg/store/empty"
|
||||||
|
"github.com/rancher/norman/pkg/types"
|
||||||
|
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||||
|
"github.com/rancher/wrangler/pkg/kv"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
empty.Store
|
||||||
|
configMaps v1.ConfigMapClient
|
||||||
|
secrets v1.SecretClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.Schema, id string) (types.APIObject, error) {
|
||||||
|
var (
|
||||||
|
data string
|
||||||
|
namespace, name = kv.Split(id, ":")
|
||||||
|
)
|
||||||
|
|
||||||
|
secret, err := s.secrets.Get(namespace, name, metav1.GetOptions{})
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return types.APIObject{}, err
|
||||||
|
} else if errors.IsNotFound(err) {
|
||||||
|
secret = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret == nil {
|
||||||
|
configMap, err := s.configMaps.Get(apiOp.Namespaces[0], id, metav1.GetOptions{})
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return types.APIObject{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if configMap == nil {
|
||||||
|
return types.APIObject{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data = configMap.Data["release"]
|
||||||
|
name = configMap.Name
|
||||||
|
} else {
|
||||||
|
data = string(secret.Data["release"])
|
||||||
|
name = secret.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
hr, err := ToRelease(data, name)
|
||||||
|
if err != nil || hr == nil {
|
||||||
|
return types.APIObject{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ToAPI(hr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (s *Store) List(apiOp *types.APIRequest, schema *types.Schema, opt *types.QueryOptions) (types.APIObject, error) {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (s *Store) Watch(apiOp *types.APIRequest, schema *types.Schema, w types.WatchRequest) (chan types.APIEvent, error) {
|
||||||
|
//
|
||||||
|
//}
|
@ -2,25 +2,38 @@ package resources
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rancher/naok/pkg/accesscontrol"
|
"github.com/rancher/naok/pkg/accesscontrol"
|
||||||
|
"github.com/rancher/naok/pkg/clustercache"
|
||||||
"github.com/rancher/naok/pkg/resources/apigroups"
|
"github.com/rancher/naok/pkg/resources/apigroups"
|
||||||
"github.com/rancher/naok/pkg/resources/common"
|
"github.com/rancher/naok/pkg/resources/common"
|
||||||
|
"github.com/rancher/naok/pkg/resources/core"
|
||||||
"github.com/rancher/naok/pkg/resources/counts"
|
"github.com/rancher/naok/pkg/resources/counts"
|
||||||
|
"github.com/rancher/naok/pkg/resources/helmrelease"
|
||||||
"github.com/rancher/naok/pkg/resources/schema"
|
"github.com/rancher/naok/pkg/resources/schema"
|
||||||
"github.com/rancher/norman/pkg/store/apiroot"
|
"github.com/rancher/norman/pkg/store/apiroot"
|
||||||
"github.com/rancher/norman/pkg/store/proxy"
|
"github.com/rancher/norman/pkg/store/proxy"
|
||||||
"github.com/rancher/norman/pkg/subscribe"
|
"github.com/rancher/norman/pkg/subscribe"
|
||||||
"github.com/rancher/norman/pkg/types"
|
"github.com/rancher/norman/pkg/types"
|
||||||
|
corev1controller "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SchemaFactory(getter proxy.ClientGetter, as *accesscontrol.AccessStore, k8s kubernetes.Interface) *schema.Collection {
|
func SchemaFactory(getter proxy.ClientGetter,
|
||||||
|
as *accesscontrol.AccessStore,
|
||||||
|
k8s kubernetes.Interface,
|
||||||
|
ccache clustercache.ClusterCache,
|
||||||
|
configMaps corev1controller.ConfigMapClient,
|
||||||
|
secrets corev1controller.SecretClient,
|
||||||
|
) *schema.Collection {
|
||||||
baseSchema := types.EmptySchemas()
|
baseSchema := types.EmptySchemas()
|
||||||
collection := schema.NewCollection(baseSchema, as)
|
collection := schema.NewCollection(baseSchema, as)
|
||||||
|
|
||||||
counts.Register(baseSchema)
|
core.Register(collection)
|
||||||
|
|
||||||
|
counts.Register(baseSchema, ccache)
|
||||||
subscribe.Register(baseSchema)
|
subscribe.Register(baseSchema)
|
||||||
apigroups.Register(baseSchema, k8s.Discovery())
|
apigroups.Register(baseSchema, k8s.Discovery())
|
||||||
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
||||||
|
helmrelease.Register(baseSchema, configMaps, secrets)
|
||||||
|
|
||||||
common.Register(collection, getter)
|
common.Register(collection, getter)
|
||||||
|
|
||||||
|
@ -5,7 +5,10 @@ import (
|
|||||||
|
|
||||||
"github.com/rancher/naok/pkg/accesscontrol"
|
"github.com/rancher/naok/pkg/accesscontrol"
|
||||||
"github.com/rancher/naok/pkg/attributes"
|
"github.com/rancher/naok/pkg/attributes"
|
||||||
|
"github.com/rancher/naok/pkg/table"
|
||||||
|
"github.com/rancher/norman/pkg/data"
|
||||||
"github.com/rancher/norman/pkg/types"
|
"github.com/rancher/norman/pkg/types"
|
||||||
|
"github.com/rancher/wrangler/pkg/name"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
@ -21,19 +24,22 @@ type Collection struct {
|
|||||||
schemas map[string]*types.Schema
|
schemas map[string]*types.Schema
|
||||||
templates map[string]*Template
|
templates map[string]*Template
|
||||||
byGVR map[schema.GroupVersionResource]string
|
byGVR map[schema.GroupVersionResource]string
|
||||||
|
byGVK map[schema.GroupVersionKind]string
|
||||||
|
|
||||||
as *accesscontrol.AccessStore
|
as *accesscontrol.AccessStore
|
||||||
}
|
}
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
Group string
|
Group string
|
||||||
Kind string
|
Kind string
|
||||||
ID string
|
ID string
|
||||||
RegisterType interface{}
|
RegisterType interface{}
|
||||||
Customize func(*types.Schema)
|
Customize func(*types.Schema)
|
||||||
Formatter types.Formatter
|
Formatter types.Formatter
|
||||||
Store types.Store
|
Store types.Store
|
||||||
Mapper types.Mapper
|
Mapper types.Mapper
|
||||||
|
Columns []table.Column
|
||||||
|
ComputedColumns func(data.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCollection(baseSchema *types.Schemas, access *accesscontrol.AccessStore) *Collection {
|
func NewCollection(baseSchema *types.Schemas, access *accesscontrol.AccessStore) *Collection {
|
||||||
@ -42,36 +48,60 @@ func NewCollection(baseSchema *types.Schemas, access *accesscontrol.AccessStore)
|
|||||||
schemas: map[string]*types.Schema{},
|
schemas: map[string]*types.Schema{},
|
||||||
templates: map[string]*Template{},
|
templates: map[string]*Template{},
|
||||||
byGVR: map[schema.GroupVersionResource]string{},
|
byGVR: map[schema.GroupVersionResource]string{},
|
||||||
|
byGVK: map[schema.GroupVersionKind]string{},
|
||||||
as: access,
|
as: access,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Collection) Reset(schemas map[string]*types.Schema) {
|
func (c *Collection) Reset(schemas map[string]*types.Schema) {
|
||||||
byGVR := map[schema.GroupVersionResource]string{}
|
byGVR := map[schema.GroupVersionResource]string{}
|
||||||
|
byGVK := map[schema.GroupVersionKind]string{}
|
||||||
|
|
||||||
for _, s := range schemas {
|
for _, s := range schemas {
|
||||||
gvr := attributes.GVR(s)
|
gvr := attributes.GVR(s)
|
||||||
if gvr.Resource != "" {
|
if gvr.Resource != "" {
|
||||||
gvr.Resource = strings.ToLower(gvr.Resource)
|
|
||||||
byGVR[gvr] = s.ID
|
byGVR[gvr] = s.ID
|
||||||
}
|
}
|
||||||
|
gvk := attributes.GVK(s)
|
||||||
kind := attributes.Kind(s)
|
if gvk.Kind != "" {
|
||||||
if kind != "" {
|
byGVK[gvk] = s.ID
|
||||||
gvr.Resource = strings.ToLower(kind)
|
|
||||||
byGVR[gvr] = s.ID
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.schemas = schemas
|
c.schemas = schemas
|
||||||
c.byGVR = byGVR
|
c.byGVR = byGVR
|
||||||
|
c.byGVK = byGVK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collection) Schema(id string) *types.Schema {
|
||||||
|
return c.schemas[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collection) IDs() (result []string) {
|
||||||
|
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 {
|
func (c *Collection) ByGVR(gvr schema.GroupVersionResource) string {
|
||||||
gvr.Resource = strings.ToLower(gvr.Resource)
|
id, ok := c.byGVR[gvr]
|
||||||
|
if ok {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
gvr.Resource = name.GuessPluralName(strings.ToLower(gvr.Resource))
|
||||||
return c.byGVR[gvr]
|
return c.byGVR[gvr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Collection) ByGVK(gvk schema.GroupVersionKind) string {
|
||||||
|
return c.byGVK[gvk]
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Collection) AddTemplate(template *Template) {
|
func (c *Collection) AddTemplate(template *Template) {
|
||||||
if template.Kind != "" {
|
if template.Kind != "" {
|
||||||
c.templates[template.Group+"/"+template.Kind] = template
|
c.templates[template.Group+"/"+template.Kind] = template
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rancher/naok/pkg/table"
|
||||||
|
|
||||||
"github.com/rancher/naok/pkg/accesscontrol"
|
"github.com/rancher/naok/pkg/accesscontrol"
|
||||||
"github.com/rancher/naok/pkg/attributes"
|
"github.com/rancher/naok/pkg/attributes"
|
||||||
"github.com/rancher/norman/pkg/api/builtin"
|
"github.com/rancher/norman/pkg/api/builtin"
|
||||||
@ -121,5 +123,8 @@ func (c *Collection) applyTemplates(schemas *types.Schemas, schema *types.Schema
|
|||||||
if t.Customize != nil {
|
if t.Customize != nil {
|
||||||
t.Customize(schema)
|
t.Customize(schema)
|
||||||
}
|
}
|
||||||
|
if len(t.Columns) > 0 {
|
||||||
|
schemas.AddMapper(schema.ID, table.NewColumns(t.ComputedColumns, t.Columns...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rancher/wrangler/pkg/generic"
|
||||||
|
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
"github.com/rancher/naok/pkg/clustercache"
|
||||||
|
|
||||||
|
"github.com/rancher/wrangler-api/pkg/generated/controllers/core"
|
||||||
|
|
||||||
"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/controllers/schema"
|
"github.com/rancher/naok/pkg/controllers/schema"
|
||||||
@ -35,6 +42,11 @@ func Run(ctx context.Context, cfg Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core, err := core.NewFactoryFromConfig(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
k8s, err := kubernetes.NewForConfig(restConfig)
|
k8s, err := kubernetes.NewForConfig(restConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -55,14 +67,20 @@ func Run(ctx context.Context, cfg Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient())
|
||||||
|
|
||||||
sf := resources.SchemaFactory(cf,
|
sf := resources.SchemaFactory(cf,
|
||||||
accesscontrol.NewAccessStore(rbac.Rbac().V1()),
|
accesscontrol.NewAccessStore(rbac.Rbac().V1()),
|
||||||
k8s)
|
k8s,
|
||||||
|
ccache,
|
||||||
|
core.Core().V1().ConfigMap(),
|
||||||
|
core.Core().V1().Secret())
|
||||||
|
|
||||||
schema.Register(ctx,
|
sync := schema.Register(ctx,
|
||||||
k8s.Discovery(),
|
k8s.Discovery(),
|
||||||
crd.Apiextensions().V1beta1().CustomResourceDefinition(),
|
crd.Apiextensions().V1beta1().CustomResourceDefinition(),
|
||||||
api.Apiregistration().V1().APIService(),
|
api.Apiregistration().V1().APIService(),
|
||||||
|
ccache,
|
||||||
sf)
|
sf)
|
||||||
|
|
||||||
handler, err := publicapi.NewHandler(restConfig, sf)
|
handler, err := publicapi.NewHandler(restConfig, sf)
|
||||||
@ -70,10 +88,24 @@ func Run(ctx context.Context, cfg Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, controllers := range []controllers{api, crd, rbac} {
|
||||||
|
for gvk, controller := range controllers.Controllers() {
|
||||||
|
ccache.AddController(gvk, controller.Informer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := start.All(ctx, 5, api, crd, rbac); err != nil {
|
if err := start.All(ctx, 5, api, crd, rbac); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := sync(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Infof("listening on %s", cfg.ListenAddress)
|
logrus.Infof("listening on %s", cfg.ListenAddress)
|
||||||
return http.ListenAndServe(cfg.ListenAddress, handler)
|
return http.ListenAndServe(cfg.ListenAddress, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type controllers interface {
|
||||||
|
Controllers() map[schema2.GroupVersionKind]*generic.Controller
|
||||||
|
}
|
||||||
|
@ -23,8 +23,16 @@ type ColumnMapper struct {
|
|||||||
types.EmptyMapper
|
types.EmptyMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewColumns(computed func(data.Object), columns ...Column) *ColumnMapper {
|
||||||
|
return &ColumnMapper{
|
||||||
|
definition: Table{
|
||||||
|
Columns: columns,
|
||||||
|
Computed: computed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *ColumnMapper) FromInternal(d data.Object) {
|
func (t *ColumnMapper) FromInternal(d data.Object) {
|
||||||
d.Map("metadata").Set("columns", t.definition.Columns)
|
|
||||||
if t.definition.Computed != nil {
|
if t.definition.Computed != nil {
|
||||||
t.definition.Computed(d)
|
t.definition.Computed(d)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user