1
0
mirror of https://github.com/rancher/norman.git synced 2025-08-01 23:41:24 +00:00

Major refactor of subcontexts

This commit is contained in:
Darren Shepherd 2018-02-09 13:31:12 -07:00
parent b62e043067
commit 722cedfe01
17 changed files with 319 additions and 300 deletions

View File

@ -147,17 +147,26 @@ func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema,
rawResource.Links["remove"] = self
}
subContextVersion := context.Schemas.SubContextVersionForSchema(schema)
for _, backRef := range context.Schemas.References(schema) {
if !backRef.Schema.CanList(context) {
continue
}
if schema.SubContext == "" {
if subContextVersion == nil {
rawResource.Links[backRef.Schema.PluralName] = context.URLBuilder.FilterLink(backRef.Schema, backRef.FieldName, rawResource.ID)
} else {
rawResource.Links[backRef.Schema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, backRef.Schema)
}
}
if subContextVersion != nil {
for _, subSchema := range context.Schemas.SchemasForVersion(*subContextVersion) {
if subSchema.CanList(context) {
rawResource.Links[subSchema.PluralName] = context.URLBuilder.SubContextCollection(schema, rawResource.ID, subSchema)
}
}
}
}
func newCollection(apiContext *types.APIContext) *types.GenericCollection {

View File

@ -1,12 +1,14 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
"github.com/rancher/norman/api"
"github.com/rancher/norman/store/crd"
"github.com/rancher/norman/store/proxy"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/factory"
"k8s.io/client-go/tools/clientcmd"
@ -39,13 +41,19 @@ func main() {
panic(err)
}
store, err := crd.NewCRDStoreFromConfig(*kubeConfig)
client, err := proxy.NewClientGetterFromConfig(*kubeConfig)
if err != nil {
panic(err)
}
crdFactory := crd.Factory{
ClientGetter: client,
}
Schemas.MustImportAndCustomize(&version, Foo{}, func(schema *types.Schema) {
schema.Store = store
if err := crdFactory.AssignStores(context.Background(), types.DefaultStorageContext, schema); err != nil {
panic(err)
}
})
server := api.NewAPIServer()

View File

@ -24,6 +24,7 @@ var (
ActionNotAvailable = ErrorCode{"ActionNotAvailable", 404}
InvalidState = ErrorCode{"InvalidState", 422}
ServerError = ErrorCode{"ServerError", 500}
ClusterUnavailable = ErrorCode{"ClusterUnavailable", 503}
PermissionDenied = ErrorCode{"PermissionDenied", 403}
MethodNotAllowed = ErrorCode{"MethodNotAllow", 405}

View File

@ -6,7 +6,10 @@ import (
"regexp"
"strings"
"sort"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/urlbuilder"
)
@ -42,26 +45,23 @@ type URLParser func(schema *types.Schemas, url *url.URL) (ParsedURL, error)
func DefaultURLParser(schemas *types.Schemas, url *url.URL) (ParsedURL, error) {
result := ParsedURL{}
version := Version(schemas, url.Path)
path := url.Path
path = multiSlashRegexp.ReplaceAllString(path, "/")
version, prefix, parts, subContext := parseVersionAndSubContext(schemas, path)
if version == nil {
return result, nil
}
path := url.Path
path = multiSlashRegexp.ReplaceAllString(path, "/")
parts := strings.SplitN(path[len(version.Path):], "/", 4)
prefix, parts, subContext := parseSubContext(schemas, version, parts)
result.Version = version.Path
result.SubContext = subContext
result.SubContextPrefix = prefix
result.Action, result.Method = parseAction(url)
result.Query = url.Query()
result.Type = safeIndex(parts, 1)
result.ID = safeIndex(parts, 2)
result.Link = safeIndex(parts, 3)
result.Type = safeIndex(parts, 0)
result.ID = safeIndex(parts, 1)
result.Link = safeIndex(parts, 2)
return result, nil
}
@ -121,11 +121,14 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, ur
}
if result.Schema == nil {
if result.Type != "" {
err = httperror.NewAPIError(httperror.NotFound, "failed to find schema "+result.Type)
}
result.Method = http.MethodGet
result.Type = "apiRoot"
result.Schema = result.Schemas.Schema(&builtin.Version, "apiRoot")
result.ID = result.Version.Path
return result, nil
return result, err
}
result.Type = result.Schema.ID
@ -137,29 +140,61 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, ur
return result, nil
}
func parseSubContext(schemas *types.Schemas, version *types.APIVersion, parts []string) (string, []string, map[string]string) {
subContext := ""
result := map[string]string{}
for len(parts) > 3 && version != nil && parts[3] != "" {
resourceType := parts[1]
resourceID := parts[2]
if !version.SubContexts[resourceType] {
break
func versionsForPath(schemas *types.Schemas, path string) []types.APIVersion {
var matchedVersion []types.APIVersion
for _, version := range schemas.Versions() {
if strings.HasPrefix(path, version.Path) {
matchedVersion = append(matchedVersion, version)
}
}
sort.Slice(matchedVersion, func(i, j int) bool {
return len(matchedVersion[i].Path) > len(matchedVersion[j].Path)
})
return matchedVersion
}
subSchema := schemas.Schema(version, parts[3])
if subSchema == nil {
break
}
func parseVersionAndSubContext(schemas *types.Schemas, path string) (*types.APIVersion, string, []string, map[string]string) {
versions := versionsForPath(schemas, path)
if len(versions) == 0 {
return nil, "", nil, nil
}
version := &versions[0]
result[resourceType] = resourceID
subContext = subContext + "/" + resourceType + "/" + resourceID
parts = append(parts[:1], parts[3:]...)
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
return subContext, parts, result
versionParts := strings.Split(version.Path, "/")
pathParts := strings.Split(path, "/")
paths := pathParts[len(versionParts):]
if !version.SubContext || len(versions) < 2 {
return version, "", paths, nil
}
if len(paths) < 2 {
// Handle case like /v3/clusters/foo where /v3 and /v3/clusters are API versions.
// In this situation you want the version to be /v3 and the path "clusters", "foo"
return &versions[1], "", pathParts[len(versionParts)-1:], nil
}
// Length is always >= 3
attrs := map[string]string{
version.SubContextSchema: paths[0],
}
for i, version := range versions {
schema := schemas.Schema(&version, paths[1])
if schema != nil {
if i == 0 {
break
}
return &version, "", paths[1:], attrs
}
}
return version, "/" + paths[0], paths[1:], attrs
}
func DefaultResolver(typeName string, apiContext *types.APIContext) error {
@ -223,20 +258,6 @@ func parseAction(url *url.URL) (string, string) {
return action, ""
}
func Version(schemas *types.Schemas, path string) *types.APIVersion {
path = multiSlashRegexp.ReplaceAllString(path, "/")
for _, version := range schemas.Versions() {
if version.Path == "" {
continue
}
if strings.HasPrefix(path, version.Path) {
return &version
}
}
return nil
}
func Body(req *http.Request) (map[string]interface{}, error) {
req.ParseMultipartForm(maxFormSize)
if req.MultipartForm != nil {

View File

@ -29,8 +29,8 @@ func (d *DefaultSubContextAttributeProvider) Create(apiContext *types.APIContext
func (d *DefaultSubContextAttributeProvider) create(apiContext *types.APIContext, schema *types.Schema) map[string]string {
result := map[string]string{}
for subContext, value := range apiContext.SubContext {
subContextSchema := apiContext.Schemas.SubContext(subContext)
for subContextSchemaID, value := range apiContext.SubContext {
subContextSchema := apiContext.Schemas.Schema(nil, subContextSchemaID)
if subContextSchema == nil {
continue
}

View File

@ -34,14 +34,10 @@ func Handler(apiContext *types.APIContext, _ types.RequestHandler) error {
}
func getMatchingSchemas(apiContext *types.APIContext) []*types.Schema {
apiVersions := apiContext.Request.URL.Query()["apiVersions"]
resourceTypes := apiContext.Request.URL.Query()["resourceTypes"]
var schemas []*types.Schema
for _, schema := range apiContext.Schemas.Schemas() {
if !matches(apiVersions, schema.Version.Path) {
continue
}
for _, schema := range apiContext.Schemas.SchemasForVersion(*apiContext.Version) {
if !matches(resourceTypes, schema.ID) {
continue
}
@ -151,9 +147,14 @@ func streamStore(ctx context.Context, eg *errgroup.Group, apiContext *types.APIC
opts := parse.QueryOptions(apiContext, schema)
events, err := schema.Store.Watch(apiContext, schema, &opts)
if err != nil || events == nil {
if err != nil {
logrus.Errorf("failed on subscribe %s: %v", schema.ID, err)
}
return err
}
logrus.Debugf("watching %s", schema.ID)
for e := range events {
result <- e
}

View File

@ -1,136 +0,0 @@
package crd
import (
"context"
"strings"
"github.com/rancher/norman/store/proxy"
"github.com/rancher/norman/types"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apiextclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
type Store struct {
Factory *Factory
k8sClient rest.Interface
schemaStores map[string]types.Store
}
func NewCRDStoreFromConfig(config rest.Config) (*Store, error) {
dynamicConfig := config
if dynamicConfig.NegotiatedSerializer == nil {
configConfig := dynamic.ContentConfig()
dynamicConfig.NegotiatedSerializer = configConfig.NegotiatedSerializer
}
k8sClient, err := rest.UnversionedRESTClientFor(&dynamicConfig)
if err != nil {
return nil, err
}
apiExtClient, err := clientset.NewForConfig(&dynamicConfig)
if err != nil {
return nil, err
}
return NewCRDStoreFromClients(apiExtClient, k8sClient), nil
}
func NewCRDStoreFromClients(apiExtClientSet apiextclientset.Interface, k8sClient rest.Interface) *Store {
return &Store{
Factory: &Factory{
APIExtClientSet: apiExtClientSet,
},
k8sClient: k8sClient,
schemaStores: map[string]types.Store{},
}
}
func key(schema *types.Schema) string {
if !strings.EqualFold(schema.BaseType, schema.ID) {
return schema.Version.Path + "/" + schema.BaseType
}
return schema.Version.Path + "/" + schema.ID
}
func (c *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.ByID(apiContext, schema, id)
}
func (c *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.Delete(apiContext, schema, id)
}
func (c *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.List(apiContext, schema, opt)
}
func (c *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.Watch(apiContext, schema, opt)
}
func (c *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.Update(apiContext, schema, data, id)
}
func (c *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
store, ok := c.schemaStores[key(schema)]
if !ok {
return nil, nil
}
return store.Create(apiContext, schema, data)
}
func (c *Store) AddSchemas(ctx context.Context, schemas ...*types.Schema) error {
schemaStatus := map[*types.Schema]*apiext.CustomResourceDefinition{}
var allSchemas []*types.Schema
for _, schema := range schemas {
if schema.Store != nil || !schema.CanList(nil) || !strings.EqualFold(schema.BaseType, schema.ID) {
continue
}
schema.Store = c
allSchemas = append(allSchemas, schema)
}
schemaStatus, err := c.Factory.AddSchemas(ctx, allSchemas...)
if err != nil {
return err
}
for schema, crd := range schemaStatus {
c.schemaStores[key(schema)] = proxy.NewProxyStore(c.k8sClient,
[]string{"apis"},
crd.Spec.Group,
crd.Spec.Version,
crd.Status.AcceptedNames.Kind,
crd.Status.AcceptedNames.Plural)
}
return nil
}

View File

@ -5,6 +5,9 @@ import (
"strings"
"time"
"fmt"
"github.com/rancher/norman/store/proxy"
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/sirupsen/logrus"
@ -13,48 +16,58 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
type Factory struct {
APIExtClientSet clientset.Interface
ClientGetter proxy.ClientGetter
}
func NewFactoryFromConfig(config rest.Config) (*Factory, error) {
dynamicConfig := config
if dynamicConfig.NegotiatedSerializer == nil {
configConfig := dynamic.ContentConfig()
dynamicConfig.NegotiatedSerializer = configConfig.NegotiatedSerializer
func (c *Factory) AssignStores(ctx context.Context, storageContext types.StorageContext, schemas ...*types.Schema) error {
schemaStatus, err := c.CreateCRDs(ctx, storageContext, schemas...)
if err != nil {
return err
}
apiExtClient, err := clientset.NewForConfig(&dynamicConfig)
for _, schema := range schemas {
crd, ok := schemaStatus[schema]
if !ok {
return fmt.Errorf("failed to create create/find CRD for %s", schema.ID)
}
schema.Store = proxy.NewProxyStore(c.ClientGetter,
storageContext,
[]string{"apis"},
crd.Spec.Group,
crd.Spec.Version,
crd.Status.AcceptedNames.Kind,
crd.Status.AcceptedNames.Plural)
}
return nil
}
func (c *Factory) CreateCRDs(ctx context.Context, storageContext types.StorageContext, schemas ...*types.Schema) (map[*types.Schema]*apiext.CustomResourceDefinition, error) {
schemaStatus := map[*types.Schema]*apiext.CustomResourceDefinition{}
apiClient, err := c.ClientGetter.APIExtClient(nil, storageContext)
if err != nil {
return nil, err
}
return &Factory{
APIExtClientSet: apiExtClient,
}, nil
}
func (c *Factory) AddSchemas(ctx context.Context, schemas ...*types.Schema) (map[*types.Schema]*apiext.CustomResourceDefinition, error) {
schemaStatus := map[*types.Schema]*apiext.CustomResourceDefinition{}
ready, err := c.getReadyCRDs()
ready, err := c.getReadyCRDs(apiClient)
if err != nil {
return nil, err
}
for _, schema := range schemas {
crd, err := c.createCRD(schema, ready)
crd, err := c.createCRD(apiClient, schema, ready)
if err != nil {
return nil, err
}
schemaStatus[schema] = crd
}
ready, err = c.getReadyCRDs()
ready, err = c.getReadyCRDs(apiClient)
if err != nil {
return nil, err
}
@ -63,7 +76,7 @@ func (c *Factory) AddSchemas(ctx context.Context, schemas ...*types.Schema) (map
if readyCrd, ok := ready[crd.Name]; ok {
schemaStatus[schema] = readyCrd
} else {
if err := c.waitCRD(ctx, crd.Name, schema, schemaStatus); err != nil {
if err := c.waitCRD(ctx, apiClient, crd.Name, schema, schemaStatus); err != nil {
return nil, err
}
}
@ -72,7 +85,7 @@ func (c *Factory) AddSchemas(ctx context.Context, schemas ...*types.Schema) (map
return schemaStatus, nil
}
func (c *Factory) waitCRD(ctx context.Context, crdName string, schema *types.Schema, schemaStatus map[*types.Schema]*apiext.CustomResourceDefinition) error {
func (c *Factory) waitCRD(ctx context.Context, apiClient clientset.Interface, crdName string, schema *types.Schema, schemaStatus map[*types.Schema]*apiext.CustomResourceDefinition) error {
logrus.Infof("Waiting for CRD %s to become available", crdName)
defer logrus.Infof("Done waiting for CRD %s to become available", crdName)
@ -83,7 +96,7 @@ func (c *Factory) waitCRD(ctx context.Context, crdName string, schema *types.Sch
}
first = false
crd, err := c.APIExtClientSet.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
crd, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
if err != nil {
return false, err
}
@ -106,7 +119,7 @@ func (c *Factory) waitCRD(ctx context.Context, crdName string, schema *types.Sch
})
}
func (c *Factory) createCRD(schema *types.Schema, ready map[string]*apiext.CustomResourceDefinition) (*apiext.CustomResourceDefinition, error) {
func (c *Factory) createCRD(apiClient clientset.Interface, schema *types.Schema, ready map[string]*apiext.CustomResourceDefinition) (*apiext.CustomResourceDefinition, error) {
plural := strings.ToLower(schema.PluralName)
name := strings.ToLower(plural + "." + schema.Version.Group)
@ -136,15 +149,15 @@ func (c *Factory) createCRD(schema *types.Schema, ready map[string]*apiext.Custo
}
logrus.Infof("Creating CRD %s", name)
crd, err := c.APIExtClientSet.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
crd, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
if errors.IsAlreadyExists(err) {
return crd, nil
}
return crd, err
}
func (c *Factory) getReadyCRDs() (map[string]*apiext.CustomResourceDefinition, error) {
list, err := c.APIExtClientSet.ApiextensionsV1beta1().CustomResourceDefinitions().List(metav1.ListOptions{})
func (c *Factory) getReadyCRDs(apiClient clientset.Interface) (map[string]*apiext.CustomResourceDefinition, error) {
list, err := apiClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(metav1.ListOptions{})
if err != nil {
return nil, err
}

View File

@ -7,6 +7,10 @@ import (
type Store struct {
}
func (e *Store) Context() types.StorageContext {
return types.DefaultStorageContext
}
func (e *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
return nil, nil
}

View File

@ -8,6 +8,8 @@ import (
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/values"
"github.com/sirupsen/logrus"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -28,8 +30,57 @@ var (
}
)
type ClientGetter interface {
Config(apiContext *types.APIContext, context types.StorageContext) (rest.Config, error)
UnversionedClient(apiContext *types.APIContext, context types.StorageContext) (rest.Interface, error)
APIExtClient(apiContext *types.APIContext, context types.StorageContext) (clientset.Interface, error)
}
type simpleClientGetter struct {
restConfig rest.Config
client rest.Interface
apiExtClient clientset.Interface
}
func NewClientGetterFromConfig(config rest.Config) (ClientGetter, error) {
dynamicConfig := config
if dynamicConfig.NegotiatedSerializer == nil {
configConfig := dynamic.ContentConfig()
dynamicConfig.NegotiatedSerializer = configConfig.NegotiatedSerializer
}
unversionedClient, err := rest.UnversionedRESTClientFor(&dynamicConfig)
if err != nil {
return nil, err
}
apiExtClient, err := clientset.NewForConfig(&dynamicConfig)
if err != nil {
return nil, err
}
return &simpleClientGetter{
restConfig: config,
client: unversionedClient,
apiExtClient: apiExtClient,
}, nil
}
func (s *simpleClientGetter) Config(apiContext *types.APIContext, context types.StorageContext) (rest.Config, error) {
return s.restConfig, nil
}
func (s *simpleClientGetter) UnversionedClient(apiContext *types.APIContext, context types.StorageContext) (rest.Interface, error) {
return s.client, nil
}
func (s *simpleClientGetter) APIExtClient(apiContext *types.APIContext, context types.StorageContext) (clientset.Interface, error) {
return s.apiExtClient, nil
}
type Store struct {
k8sClient rest.Interface
clientGetter ClientGetter
storageContext types.StorageContext
prefix []string
group string
version string
@ -38,11 +89,12 @@ type Store struct {
authContext map[string]string
}
func NewProxyStore(k8sClient rest.Interface,
func NewProxyStore(clientGetter ClientGetter, storageContext types.StorageContext,
prefix []string, group, version, kind, resourcePlural string) types.Store {
return &errorStore{
Store: &Store{
k8sClient: k8sClient,
clientGetter: clientGetter,
storageContext: storageContext,
prefix: prefix,
group: group,
version: version,
@ -56,10 +108,11 @@ func NewProxyStore(k8sClient rest.Interface,
}
}
func NewRawProxyStore(k8sClient rest.Interface,
func NewRawProxyStore(clientGetter ClientGetter, storageContext types.StorageContext,
prefix []string, group, version, kind, resourcePlural string) *Store {
return &Store{
k8sClient: k8sClient,
clientGetter: clientGetter,
storageContext: storageContext,
prefix: prefix,
group: group,
version: version,
@ -83,6 +136,10 @@ func (p *Store) doAuthed(apiContext *types.APIContext, request *rest.Request) re
return request.Do()
}
func (p *Store) k8sClient(apiContext *types.APIContext) (rest.Interface, error) {
return p.clientGetter.UnversionedClient(apiContext, p.storageContext)
}
func (p *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
_, result, err := p.byID(apiContext, schema, id)
return result, err
@ -91,19 +148,33 @@ func (p *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id stri
func (p *Store) byID(apiContext *types.APIContext, schema *types.Schema, id string) (string, map[string]interface{}, error) {
namespace, id := splitID(id)
req := p.common(namespace, p.k8sClient.Get()).
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return "", nil, err
}
req := p.common(namespace, k8sClient.Get()).
Name(id)
return p.singleResult(apiContext, schema, req)
}
func (p *Store) Context() types.StorageContext {
return p.storageContext
}
func (p *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
namespace := getNamespace(apiContext, opt)
req := p.common(namespace, p.k8sClient.Get())
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return nil, err
}
req := p.common(namespace, k8sClient.Get())
resultList := &unstructured.UnstructuredList{}
err := req.Do().Into(resultList)
err = req.Do().Into(resultList)
if err != nil {
return nil, err
}
@ -120,8 +191,13 @@ func (p *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *ty
func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
namespace := getNamespace(apiContext, opt)
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return nil, err
}
timeout := int64(60 * 60)
req := p.common(namespace, p.k8sClient.Get())
req := p.common(namespace, k8sClient.Get())
req.VersionedParams(&metav1.ListOptions{
Watch: true,
TimeoutSeconds: &timeout,
@ -139,6 +215,7 @@ func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *t
go func() {
<-apiContext.Request.Context().Done()
logrus.Debugf("stopping watcher for %s", schema.ID)
watcher.Stop()
}()
@ -152,6 +229,7 @@ func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *t
}
result <- apiContext.AccessControl.Filter(apiContext, data.Object, p.authContext)
}
logrus.Debugf("closing watcher for %s", schema.ID)
close(result)
}()
@ -196,7 +274,12 @@ func (p *Store) Create(apiContext *types.APIContext, schema *types.Schema, data
}
}
req := p.common(namespace, p.k8sClient.Post()).
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return nil, err
}
req := p.common(namespace, k8sClient.Post()).
Body(&unstructured.Unstructured{
Object: data,
})
@ -219,8 +302,13 @@ func (p *Store) toInternal(mapper types.Mapper, data map[string]interface{}) {
}
func (p *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return nil, err
}
namespace, id := splitID(id)
req := p.common(namespace, p.k8sClient.Get()).
req := p.common(namespace, k8sClient.Get()).
Name(id)
resourceVersion, existing, err := p.singleResultRaw(apiContext, schema, req)
@ -235,7 +323,7 @@ func (p *Store) Update(apiContext *types.APIContext, schema *types.Schema, data
values.PutValue(existing, namespace, "metadata", "namespace")
values.PutValue(existing, id, "metadata", "name")
req = p.common(namespace, p.k8sClient.Put()).
req = p.common(namespace, k8sClient.Put()).
Body(&unstructured.Unstructured{
Object: existing,
}).
@ -246,16 +334,21 @@ func (p *Store) Update(apiContext *types.APIContext, schema *types.Schema, data
}
func (p *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
k8sClient, err := p.k8sClient(apiContext)
if err != nil {
return nil, err
}
namespace, id := splitID(id)
prop := metav1.DeletePropagationForeground
req := p.common(namespace, p.k8sClient.Delete()).
req := p.common(namespace, k8sClient.Delete()).
Body(&metav1.DeleteOptions{
PropagationPolicy: &prop,
}).
Name(id)
err := p.doAuthed(apiContext, req).Error()
err = p.doAuthed(apiContext, req).Error()
if err != nil {
return nil, err
}

View File

@ -18,29 +18,33 @@ type Store struct {
StreamTransformer StreamTransformerFunc
}
func (t *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := t.Store.ByID(apiContext, schema, id)
if err != nil {
return nil, err
}
if t.Transformer == nil {
return data, nil
}
return t.Transformer(apiContext, data)
func (s *Store) Context() types.StorageContext {
return s.Store.Context()
}
func (t *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
c, err := t.Store.Watch(apiContext, schema, opt)
func (s *Store) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := s.Store.ByID(apiContext, schema, id)
if err != nil {
return nil, err
}
if s.Transformer == nil {
return data, nil
}
return s.Transformer(apiContext, data)
}
func (s *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) (chan map[string]interface{}, error) {
c, err := s.Store.Watch(apiContext, schema, opt)
if err != nil {
return nil, err
}
if t.StreamTransformer != nil {
return t.StreamTransformer(apiContext, c)
if s.StreamTransformer != nil {
return s.StreamTransformer(apiContext, c)
}
return convert.Chan(c, func(data map[string]interface{}) map[string]interface{} {
item, err := t.Transformer(apiContext, data)
item, err := s.Transformer(apiContext, data)
if err != nil {
return nil
}
@ -48,23 +52,23 @@ func (t *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt *t
}), nil
}
func (t *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
data, err := t.Store.List(apiContext, schema, opt)
func (s *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *types.QueryOptions) ([]map[string]interface{}, error) {
data, err := s.Store.List(apiContext, schema, opt)
if err != nil {
return nil, err
}
if t.ListTransformer != nil {
return t.ListTransformer(apiContext, data)
if s.ListTransformer != nil {
return s.ListTransformer(apiContext, data)
}
if t.Transformer == nil {
if s.Transformer == nil {
return data, nil
}
var result []map[string]interface{}
for _, item := range data {
item, err := t.Transformer(apiContext, item)
item, err := s.Transformer(apiContext, item)
if err != nil {
return nil, err
}
@ -76,28 +80,28 @@ func (t *Store) List(apiContext *types.APIContext, schema *types.Schema, opt *ty
return result, nil
}
func (t *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
data, err := t.Store.Create(apiContext, schema, data)
func (s *Store) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
data, err := s.Store.Create(apiContext, schema, data)
if err != nil {
return nil, err
}
if t.Transformer == nil {
if s.Transformer == nil {
return data, nil
}
return t.Transformer(apiContext, data)
return s.Transformer(apiContext, data)
}
func (t *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
data, err := t.Store.Update(apiContext, schema, data, id)
func (s *Store) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
data, err := s.Store.Update(apiContext, schema, data, id)
if err != nil {
return nil, err
}
if t.Transformer == nil {
if s.Transformer == nil {
return data, nil
}
return t.Transformer(apiContext, data)
return s.Transformer(apiContext, data)
}
func (t *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
return t.Store.Delete(apiContext, schema, id)
func (s *Store) Delete(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
return s.Store.Delete(apiContext, schema, id)
}

View File

@ -16,6 +16,10 @@ type StoreWrapper struct {
store types.Store
}
func (s *StoreWrapper) Context() types.StorageContext {
return s.store.Context()
}
func (s *StoreWrapper) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
data, err := s.store.ByID(apiContext, schema, id)
if err != nil {

View File

@ -131,10 +131,6 @@ func (s *Schemas) MustCustomizeType(version *APIVersion, obj interface{}, f func
f(schema)
if schema.SubContext != "" {
s.schemasBySubContext[schema.SubContext] = schema
}
return s
}

View File

@ -29,28 +29,26 @@ type BackReference struct {
type Schemas struct {
sync.Mutex
typeNames map[reflect.Type]string
schemasByPath map[string]map[string]*Schema
schemasBySubContext map[string]*Schema
mappers map[string]map[string][]Mapper
references map[string][]BackReference
embedded map[string]*Schema
DefaultMappers MappersFactory
DefaultPostMappers MappersFactory
versions []APIVersion
schemas []*Schema
AddHook SchemaHook
errors []error
typeNames map[reflect.Type]string
schemasByPath map[string]map[string]*Schema
mappers map[string]map[string][]Mapper
references map[string][]BackReference
embedded map[string]*Schema
DefaultMappers MappersFactory
DefaultPostMappers MappersFactory
versions []APIVersion
schemas []*Schema
AddHook SchemaHook
errors []error
}
func NewSchemas() *Schemas {
return &Schemas{
typeNames: map[reflect.Type]string{},
schemasByPath: map[string]map[string]*Schema{},
schemasBySubContext: map[string]*Schema{},
mappers: map[string]map[string][]Mapper{},
references: map[string][]BackReference{},
embedded: map[string]*Schema{},
typeNames: map[reflect.Type]string{},
schemasByPath: map[string]map[string]*Schema{},
mappers: map[string]map[string][]Mapper{},
references: map[string][]BackReference{},
embedded: map[string]*Schema{},
}
}
@ -62,12 +60,6 @@ func (s *Schemas) Err() error {
return NewErrors(s.errors)
}
func (s *Schemas) SubContext(subContext string) *Schema {
s.Lock()
defer s.Unlock()
return s.schemasBySubContext[subContext]
}
func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
for _, schema := range schema.Schemas() {
s.AddSchema(*schema)
@ -86,8 +78,6 @@ func (s *Schemas) doRemoveSchema(schema Schema) *Schemas {
s.removeReferences(&schema)
delete(s.schemasBySubContext, schema.SubContext)
if schema.Embed {
s.removeEmbed(&schema)
}
@ -145,10 +135,6 @@ func (s *Schemas) doAddSchema(schema Schema) *Schemas {
}
}
if schema.SubContext != "" {
s.schemasBySubContext[schema.SubContext] = &schema
}
if schema.Embed {
s.embed(&schema)
}
@ -343,6 +329,16 @@ func (s *Schemas) doSchema(version *APIVersion, name string, lock bool) *Schema
return nil
}
func (s *Schemas) SubContextVersionForSchema(schema *Schema) *APIVersion {
fullName := fmt.Sprintf("%s/schemas/%s", schema.Version.Path, schema.ID)
for _, version := range s.Versions() {
if version.SubContextSchema == fullName {
return &version
}
}
return nil
}
type multiErrors struct {
errors []error
}

View File

@ -186,7 +186,12 @@ type URLBuilder interface {
ResourceLinkByID(schema *Schema, id string) string
}
type StorageContext string
var DefaultStorageContext StorageContext
type Store interface {
Context() StorageContext
ByID(apiContext *APIContext, schema *Schema, id string) (map[string]interface{}, error)
List(apiContext *APIContext, schema *Schema, opt *QueryOptions) ([]map[string]interface{}, error)
Create(apiContext *APIContext, schema *Schema, data map[string]interface{}) (map[string]interface{}, error)

View File

@ -69,10 +69,11 @@ type Resource struct {
}
type APIVersion struct {
Group string `json:"group,omitempty"`
Version string `json:"version,omitempty"`
Path string `json:"path,omitempty"`
SubContexts map[string]bool `json:"subContext,omitempty"`
Group string `json:"group,omitempty"`
Version string `json:"version,omitempty"`
Path string `json:"path,omitempty"`
SubContext bool `json:"subContext,omitempty"`
SubContextSchema string `json:"filterField,omitempty"`
}
type Namespaced struct{}
@ -90,7 +91,6 @@ type Schema struct {
PkgName string `json:"-"`
Type string `json:"type,omitempty"`
BaseType string `json:"baseType,omitempty"`
SubContext string `json:"-,omitempty"`
Links map[string]string `json:"links"`
Version APIVersion `json:"version"`
PluralName string `json:"pluralName,omitempty"`

View File

@ -122,7 +122,7 @@ func (u *urlBuilder) Collection(schema *types.Schema, versionOverride *types.API
}
func (u *urlBuilder) SubContextCollection(subContext *types.Schema, contextName string, schema *types.Schema) string {
return u.constructBasicURL(schema.Version, subContext.SubContext, contextName, u.getPluralName(schema))
return u.constructBasicURL(subContext.Version, subContext.PluralName, contextName, u.getPluralName(schema))
}
func (u *urlBuilder) Version(version types.APIVersion) string {