Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major refactor of subcontexts #88

Merged
merged 1 commit into from
Feb 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion api/writer/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 10 additions & 2 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions httperror/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
105 changes: 63 additions & 42 deletions parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
if version == nil {
return result, nil
}

path := url.Path
path = multiSlashRegexp.ReplaceAllString(path, "/")
version, prefix, parts, subContext := parseVersionAndSubContext(schemas, path)

parts := strings.SplitN(path[len(version.Path):], "/", 4)
prefix, parts, subContext := parseSubContext(schemas, version, parts)
if version == nil {
return result, nil
}

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
}
Expand Down Expand Up @@ -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
Expand All @@ -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{}
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
}

for len(parts) > 3 && version != nil && parts[3] != "" {
resourceType := parts[1]
resourceID := parts[2]
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]

if !version.SubContexts[resourceType] {
break
}
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}

subSchema := schemas.Schema(version, parts[3])
if subSchema == nil {
break
}
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

result[resourceType] = resourceID
subContext = subContext + "/" + resourceType + "/" + resourceID
parts = append(parts[:1], parts[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 subContext, parts, result
return version, "/" + paths[0], paths[1:], attrs
}

func DefaultResolver(typeName string, apiContext *types.APIContext) error {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions parse/subcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/subscribe/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Loading