mirror of
https://github.com/niusmallnan/steve.git
synced 2025-09-01 13:18:25 +00:00
Vendor
This commit is contained in:
@@ -3,20 +3,20 @@ package publicapi
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rancher/norman/v2/pkg/api"
|
||||
"github.com/rancher/norman/v2/pkg/auth"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/norman/v2/pkg/urlbuilder"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
k8sproxy "github.com/rancher/steve/pkg/proxy"
|
||||
"github.com/rancher/steve/pkg/resources/schema"
|
||||
"github.com/rancher/steve/pkg/server/router"
|
||||
"github.com/rancher/norman/pkg/api"
|
||||
"github.com/rancher/norman/pkg/types"
|
||||
"github.com/rancher/norman/pkg/urlbuilder"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func NewHandler(cfg *rest.Config, sf schema.Factory) (http.Handler, error) {
|
||||
func NewHandler(cfg *rest.Config, sf schema.Factory, authMiddleware auth.Middleware, next http.Handler) (http.Handler, error) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
@@ -32,11 +32,13 @@ func NewHandler(cfg *rest.Config, sf schema.Factory) (http.Handler, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := authMiddleware.Wrap
|
||||
return router.Routes(router.Handlers{
|
||||
K8sResource: a.apiHandler(k8sAPI),
|
||||
GenericResource: a.apiHandler(nil),
|
||||
K8sProxy: proxy,
|
||||
APIRoot: a.apiHandler(apiRoot),
|
||||
Next: next,
|
||||
K8sResource: w(a.apiHandler(k8sAPI)),
|
||||
GenericResource: w(a.apiHandler(nil)),
|
||||
K8sProxy: w(proxy),
|
||||
APIRoot: w(a.apiHandler(apiRoot)),
|
||||
}), nil
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@ package publicapi
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/resources/schema"
|
||||
"github.com/rancher/norman/pkg/types"
|
||||
runtimeschema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
71
pkg/server/resources/apigroups/apigroup.go
Normal file
71
pkg/server/resources/apigroups/apigroup.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package apigroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/norman/v2/pkg/data"
|
||||
"github.com/rancher/norman/v2/pkg/store/empty"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
func Register(schemas *types.Schemas, discovery discovery.DiscoveryInterface) {
|
||||
schemas.MustImportAndCustomize(v1.APIGroup{}, func(schema *types.Schema) {
|
||||
schema.CollectionMethods = []string{http.MethodGet}
|
||||
schema.ResourceMethods = []string{http.MethodGet}
|
||||
schema.Store = NewStore(discovery)
|
||||
schema.Formatter = func(request *types.APIRequest, resource *types.RawResource) {
|
||||
resource.ID = data.Object(resource.Values).String("name")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
empty.Store
|
||||
|
||||
discovery discovery.DiscoveryInterface
|
||||
}
|
||||
|
||||
func NewStore(discovery discovery.DiscoveryInterface) types.Store {
|
||||
return &Store{
|
||||
Store: empty.Store{},
|
||||
discovery: discovery,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Store) ByID(apiOp *types.APIRequest, schema *types.Schema, id string) (types.APIObject, error) {
|
||||
groupList, err := e.discovery.ServerGroups()
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
if id == "core" {
|
||||
id = ""
|
||||
}
|
||||
|
||||
for _, group := range groupList.Groups {
|
||||
if group.Name == id {
|
||||
return types.ToAPI(group), nil
|
||||
}
|
||||
}
|
||||
|
||||
return types.APIObject{}, nil
|
||||
}
|
||||
|
||||
func (e *Store) List(apiOp *types.APIRequest, schema *types.Schema, opt *types.QueryOptions) (types.APIObject, error) {
|
||||
groupList, err := e.discovery.ServerGroups()
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
var result []interface{}
|
||||
for _, item := range groupList.Groups {
|
||||
if item.Name == "" {
|
||||
item.Name = "core"
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
return types.ToAPI(result), nil
|
||||
}
|
37
pkg/server/resources/common/defaultcolumns.go
Normal file
37
pkg/server/resources/common/defaultcolumns.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/table"
|
||||
)
|
||||
|
||||
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 {
|
||||
types.EmptyMapper
|
||||
}
|
||||
|
||||
func (d *DefaultColumns) ModifySchema(schema *types.Schema, schemas *types.Schemas) error {
|
||||
if attributes.Columns(schema) == nil {
|
||||
attributes.SetColumns(schema, []table.Column{
|
||||
NameColumn,
|
||||
CreatedColumn,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
33
pkg/server/resources/common/formatter.go
Normal file
33
pkg/server/resources/common/formatter.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/rancher/norman/v2/pkg/store/proxy"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/norman/v2/pkg/types/convert"
|
||||
"github.com/rancher/norman/v2/pkg/types/values"
|
||||
"github.com/rancher/steve/pkg/resources/schema"
|
||||
)
|
||||
|
||||
func Register(collection *schema.Collection, clientGetter proxy.ClientGetter) error {
|
||||
collection.AddTemplate(&schema.Template{
|
||||
Store: proxy.NewProxyStore(clientGetter),
|
||||
Formatter: Formatter,
|
||||
Mapper: &DefaultColumns{},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Formatter(request *types.APIRequest, resource *types.RawResource) {
|
||||
selfLink := convert.ToString(values.GetValueN(resource.Values, "metadata", "selfLink"))
|
||||
if selfLink == "" {
|
||||
return
|
||||
}
|
||||
|
||||
u := request.URLBuilder.RelativeToRoot(selfLink)
|
||||
resource.Links["view"] = u
|
||||
|
||||
if _, ok := resource.Links["update"]; !ok {
|
||||
resource.Links["update"] = u
|
||||
}
|
||||
}
|
259
pkg/server/resources/counts/counts.go
Normal file
259
pkg/server/resources/counts/counts.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package counts
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/rancher/norman/v2/pkg/store/empty"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
ignore = map[string]bool{
|
||||
"count": true,
|
||||
"schema": true,
|
||||
"apiRoot": true,
|
||||
}
|
||||
)
|
||||
|
||||
func Register(schemas *types.Schemas, ccache clustercache.ClusterCache) {
|
||||
schemas.MustImportAndCustomize(Count{}, func(schema *types.Schema) {
|
||||
schema.CollectionMethods = []string{http.MethodGet}
|
||||
schema.ResourceMethods = []string{http.MethodGet}
|
||||
schema.Attributes["access"] = accesscontrol.AccessListMap{
|
||||
"watch": accesscontrol.AccessList{
|
||||
{
|
||||
Namespace: "*",
|
||||
ResourceName: "*",
|
||||
},
|
||||
},
|
||||
}
|
||||
schema.Store = &Store{
|
||||
ccache: ccache,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type Count struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Counts map[string]ItemCount `json:"counts"`
|
||||
}
|
||||
|
||||
type ItemCount struct {
|
||||
Count int `json:"count,omitempty"`
|
||||
Namespaces map[string]int `json:"namespaces,omitempty"`
|
||||
Revision int `json:"revision,omitempty"`
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
empty.Store
|
||||
ccache clustercache.ClusterCache
|
||||
}
|
||||
|
||||
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.Schema, id string) (types.APIObject, error) {
|
||||
c := s.getCount(apiOp)
|
||||
return types.ToAPI(c), nil
|
||||
}
|
||||
|
||||
func (s *Store) List(apiOp *types.APIRequest, schema *types.Schema, opt *types.QueryOptions) (types.APIObject, error) {
|
||||
c := s.getCount(apiOp)
|
||||
return types.ToAPI([]interface{}{c}), nil
|
||||
}
|
||||
|
||||
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.Schema, w types.WatchRequest) (chan types.APIEvent, error) {
|
||||
var (
|
||||
result = make(chan types.APIEvent, 100)
|
||||
counts map[string]ItemCount
|
||||
gvrToSchema = map[schema2.GroupVersionResource]*types.Schema{}
|
||||
countLock sync.Mutex
|
||||
)
|
||||
|
||||
counts = s.getCount(apiOp).Counts
|
||||
for id := range counts {
|
||||
schema := apiOp.Schemas.Schema(id)
|
||||
if schema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
gvrToSchema[attributes.GVR(schema)] = schema
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-apiOp.Context().Done()
|
||||
countLock.Lock()
|
||||
close(result)
|
||||
result = nil
|
||||
countLock.Unlock()
|
||||
}()
|
||||
|
||||
onChange := func(add bool, gvr schema2.GroupVersionResource, _ string, obj runtime.Object) error {
|
||||
countLock.Lock()
|
||||
defer countLock.Unlock()
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
ns := map[string]int{}
|
||||
for i, j := range v.Namespaces {
|
||||
ns[i] = j
|
||||
}
|
||||
countsCopy[k] = ItemCount{
|
||||
Count: v.Count,
|
||||
Revision: v.Revision,
|
||||
Namespaces: ns,
|
||||
}
|
||||
}
|
||||
|
||||
result <- types.APIEvent{
|
||||
Name: "resource.change",
|
||||
ResourceType: "counts",
|
||||
Object: types.ToAPI(Count{
|
||||
ID: "count",
|
||||
Counts: countsCopy,
|
||||
}),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
s.ccache.OnAdd(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
|
||||
return onChange(true, gvr, key, obj)
|
||||
})
|
||||
s.ccache.OnRemove(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
|
||||
return onChange(false, gvr, key, obj)
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) schemasToWatch(apiOp *types.APIRequest) (result []*types.Schema) {
|
||||
for _, schema := range apiOp.Schemas.Schemas() {
|
||||
if ignore[schema.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
if attributes.PreferredVersion(schema) != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if attributes.PreferredGroup(schema) != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if schema.Store == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if apiOp.AccessControl.CanList(apiOp, schema) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if apiOp.AccessControl.CanWatch(apiOp, schema) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
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{
|
||||
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,
|
||||
}
|
||||
}
|
38
pkg/server/resources/schema.go
Normal file
38
pkg/server/resources/schema.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/rancher/norman/v2/pkg/store/apiroot"
|
||||
"github.com/rancher/norman/v2/pkg/subscribe"
|
||||
"github.com/rancher/norman/v2/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/client"
|
||||
"github.com/rancher/steve/pkg/clustercache"
|
||||
"github.com/rancher/steve/pkg/resources/apigroups"
|
||||
"github.com/rancher/steve/pkg/resources/common"
|
||||
"github.com/rancher/steve/pkg/resources/core"
|
||||
"github.com/rancher/steve/pkg/resources/counts"
|
||||
"github.com/rancher/steve/pkg/resources/schema"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func SchemaFactory(
|
||||
cf *client.Factory,
|
||||
as *accesscontrol.AccessStore,
|
||||
k8s kubernetes.Interface,
|
||||
ccache clustercache.ClusterCache,
|
||||
) (*schema.Collection, error) {
|
||||
baseSchema := types.EmptySchemas()
|
||||
collection := schema.NewCollection(baseSchema, as)
|
||||
|
||||
core.Register(collection)
|
||||
counts.Register(baseSchema, ccache)
|
||||
subscribe.Register(baseSchema)
|
||||
apigroups.Register(baseSchema, k8s.Discovery())
|
||||
apiroot.Register(baseSchema, []string{"v1"}, []string{"proxy:/apis"})
|
||||
|
||||
if err := common.Register(collection, cf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collection, nil
|
||||
}
|
Reference in New Issue
Block a user