mirror of
https://github.com/rancher/norman.git
synced 2025-09-02 15:54:32 +00:00
Updates
This commit is contained in:
@@ -112,7 +112,7 @@ func (a *APIRootStore) List(apiContext *types.APIContext, schema *types.Schema,
|
|||||||
|
|
||||||
func apiVersionToAPIRootMap(version types.APIVersion) map[string]interface{} {
|
func apiVersionToAPIRootMap(version types.APIVersion) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"type": "/v1-meta/schemas/apiRoot",
|
"type": "/meta/schemas/apiRoot",
|
||||||
"apiVersion": map[string]interface{}{
|
"apiVersion": map[string]interface{}{
|
||||||
"version": version.Version,
|
"version": version.Version,
|
||||||
"group": version.Group,
|
"group": version.Group,
|
||||||
|
@@ -11,7 +11,7 @@ var (
|
|||||||
Version = types.APIVersion{
|
Version = types.APIVersion{
|
||||||
Group: "meta.cattle.io",
|
Group: "meta.cattle.io",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Path: "/v1-meta",
|
Path: "/meta",
|
||||||
}
|
}
|
||||||
|
|
||||||
Schema = types.Schema{
|
Schema = types.Schema{
|
||||||
|
@@ -29,6 +29,7 @@ type Server struct {
|
|||||||
schemas *types.Schemas
|
schemas *types.Schemas
|
||||||
QueryFilter types.QueryFilter
|
QueryFilter types.QueryFilter
|
||||||
StoreWrapper StoreWrapper
|
StoreWrapper StoreWrapper
|
||||||
|
URLParser parse.URLParser
|
||||||
Defaults Defaults
|
Defaults Defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ func NewAPIServer() *Server {
|
|||||||
ErrorHandler: httperror.ErrorHandler,
|
ErrorHandler: httperror.ErrorHandler,
|
||||||
},
|
},
|
||||||
StoreWrapper: wrapper.Wrap,
|
StoreWrapper: wrapper.Wrap,
|
||||||
|
URLParser: parse.DefaultURLParser,
|
||||||
QueryFilter: handler.QueryFilter,
|
QueryFilter: handler.QueryFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ func NewAPIServer() *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
|
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
|
||||||
ctx, err := parse.Parse(rw, req, s.schemas, s.Resolver)
|
ctx, err := parse.Parse(rw, req, s.schemas, s.URLParser, s.Resolver)
|
||||||
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
|
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
|
||||||
if ctx.ResponseWriter == nil {
|
if ctx.ResponseWriter == nil {
|
||||||
ctx.ResponseWriter = s.ResponseWriters["json"]
|
ctx.ResponseWriter = s.ResponseWriters["json"]
|
||||||
|
@@ -122,7 +122,14 @@ func (j *JSONResponseWriter) convert(b *builder.Builder, context *types.APIConte
|
|||||||
|
|
||||||
func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema, context *types.APIContext, input map[string]interface{}, rawResource *types.RawResource) {
|
func (j *JSONResponseWriter) addLinks(b *builder.Builder, schema *types.Schema, context *types.APIContext, input map[string]interface{}, rawResource *types.RawResource) {
|
||||||
if rawResource.ID != "" {
|
if rawResource.ID != "" {
|
||||||
rawResource.Links["self"] = context.URLBuilder.ResourceLink(rawResource)
|
self := context.URLBuilder.ResourceLink(rawResource)
|
||||||
|
rawResource.Links["self"] = self
|
||||||
|
if schema.CanUpdate() {
|
||||||
|
rawResource.Links["update"] = self
|
||||||
|
}
|
||||||
|
if schema.CanDelete() {
|
||||||
|
rawResource.Links["remove"] = self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@@ -47,6 +48,15 @@ func (p *ObjectClient) Create(o runtime.Object) (runtime.Object, error) {
|
|||||||
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
|
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
|
||||||
ns = obj.GetNamespace()
|
ns = obj.GetNamespace()
|
||||||
}
|
}
|
||||||
|
if t, err := meta.TypeAccessor(o); err == nil {
|
||||||
|
if t.GetKind() == "" {
|
||||||
|
t.SetKind(p.gvk.Kind)
|
||||||
|
}
|
||||||
|
if t.GetAPIVersion() == "" {
|
||||||
|
apiVersion, _ := p.gvk.ToAPIVersionAndKind()
|
||||||
|
t.SetAPIVersion(apiVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
result := p.Factory.Object()
|
result := p.Factory.Object()
|
||||||
err := p.restClient.Post().
|
err := p.restClient.Post().
|
||||||
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
||||||
|
@@ -135,6 +135,36 @@ func generateType(outputDir string, schema *types.Schema, schemas *types.Schemas
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateLifecycle(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
|
||||||
|
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_lifecycle_adapter.go")
|
||||||
|
output, err := os.Create(path.Join(outputDir, filePath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer output.Close()
|
||||||
|
|
||||||
|
typeTemplate, err := template.New("lifecycle.template").
|
||||||
|
Funcs(funcs()).
|
||||||
|
Parse(strings.Replace(lifecycleTemplate, "%BACK%", "`", -1))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
importPackage := ""
|
||||||
|
prefix := ""
|
||||||
|
if external {
|
||||||
|
parts := strings.Split(schema.PkgName, "/vendor/")
|
||||||
|
importPackage = fmt.Sprintf("\"%s\"", parts[len(parts)-1])
|
||||||
|
prefix = schema.Version.Version + "."
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeTemplate.Execute(output, map[string]interface{}{
|
||||||
|
"schema": schema,
|
||||||
|
"importPackage": importPackage,
|
||||||
|
"prefix": prefix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func generateController(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
|
func generateController(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
|
||||||
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_controller.go")
|
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_controller.go")
|
||||||
output, err := os.Create(path.Join(outputDir, filePath))
|
output, err := os.Create(path.Join(outputDir, filePath))
|
||||||
@@ -226,6 +256,10 @@ func GenerateControllerForTypes(version *types.APIVersion, k8sOutputPackage stri
|
|||||||
if err := generateController(true, k8sDir, schema, schemas); err != nil {
|
if err := generateController(true, k8sDir, schema, schemas); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := generateLifecycle(true, k8sDir, schema, schemas); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
|
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
|
||||||
@@ -267,6 +301,9 @@ func Generate(schemas *types.Schemas, cattleOutputPackage, k8sOutputPackage stri
|
|||||||
if err := generateController(false, k8sDir, schema, schemas); err != nil {
|
if err := generateController(false, k8sDir, schema, schemas); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := generateLifecycle(false, k8sDir, schema, schemas); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generated = append(generated, schema)
|
generated = append(generated, schema)
|
||||||
|
43
generator/lifecycle_template.go
Normal file
43
generator/lifecycle_template.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package generator
|
||||||
|
|
||||||
|
var lifecycleTemplate = `package {{.schema.Version.Version}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
{{.importPackage}}
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"github.com/rancher/norman/lifecycle"
|
||||||
|
)
|
||||||
|
|
||||||
|
type {{.schema.CodeName}}Lifecycle interface {
|
||||||
|
Initialize(obj *{{.prefix}}{{.schema.CodeName}}) error
|
||||||
|
Remove(obj *{{.prefix}}{{.schema.CodeName}}) error
|
||||||
|
Updated(obj *{{.prefix}}{{.schema.CodeName}}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type {{.schema.ID}}LifecycleAdapter struct {
|
||||||
|
lifecycle {{.schema.CodeName}}Lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *{{.schema.ID}}LifecycleAdapter) Initialize(obj runtime.Object) error {
|
||||||
|
return w.lifecycle.Initialize(obj.(*{{.prefix}}{{.schema.CodeName}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *{{.schema.ID}}LifecycleAdapter) Finalize(obj runtime.Object) error {
|
||||||
|
return w.lifecycle.Remove(obj.(*{{.prefix}}{{.schema.CodeName}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *{{.schema.ID}}LifecycleAdapter) Updated(obj runtime.Object) error {
|
||||||
|
return w.lifecycle.Updated(obj.(*{{.prefix}}{{.schema.CodeName}}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func New{{.schema.CodeName}}LifecycleAdapter(name string, client {{.schema.CodeName}}Interface, l {{.schema.CodeName}}Lifecycle) {{.schema.CodeName}}HandlerFunc {
|
||||||
|
adapter := &{{.schema.ID}}LifecycleAdapter{lifecycle: l}
|
||||||
|
syncFn := lifecycle.NewObjectLifecycleAdapter(name, adapter, client.ObjectClient())
|
||||||
|
return func(key string, obj *{{.prefix}}{{.schema.CodeName}}) error {
|
||||||
|
if obj == nil {
|
||||||
|
return syncFn(key, nil)
|
||||||
|
}
|
||||||
|
return syncFn(key, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@@ -23,7 +23,7 @@ func ErrorHandler(request *types.APIContext, err error) {
|
|||||||
|
|
||||||
func toError(apiError *APIError) map[string]interface{} {
|
func toError(apiError *APIError) map[string]interface{} {
|
||||||
e := map[string]interface{}{
|
e := map[string]interface{}{
|
||||||
"type": "/v1-meta/schemas/error",
|
"type": "/meta/schemas/error",
|
||||||
"code": apiError.code.code,
|
"code": apiError.code.code,
|
||||||
"message": apiError.message,
|
"message": apiError.message,
|
||||||
}
|
}
|
||||||
|
119
lifecycle/object.go
Normal file
119
lifecycle/object.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package lifecycle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rancher/norman/clientbase"
|
||||||
|
"github.com/rancher/norman/types/slice"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
initialized = "io.cattle.lifecycle.initialized"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObjectLifecycle interface {
|
||||||
|
Initialize(obj runtime.Object) error
|
||||||
|
Finalize(obj runtime.Object) error
|
||||||
|
Updated(obj runtime.Object) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectLifecycleAdapter struct {
|
||||||
|
name string
|
||||||
|
lifecycle ObjectLifecycle
|
||||||
|
objectClient *clientbase.ObjectClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObjectLifecycleAdapter(name string, lifecycle ObjectLifecycle, objectClient *clientbase.ObjectClient) func(key string, obj runtime.Object) error {
|
||||||
|
o := objectLifecycleAdapter{
|
||||||
|
name: name,
|
||||||
|
lifecycle: lifecycle,
|
||||||
|
objectClient: objectClient,
|
||||||
|
}
|
||||||
|
return o.sync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *objectLifecycleAdapter) sync(key string, obj runtime.Object) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cont, err := o.finalize(metadata, obj); err != nil || !cont {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cont, err := o.initialize(metadata, obj); err != nil || !cont {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.lifecycle.Updated(obj.DeepCopyObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *objectLifecycleAdapter) finalize(metadata metav1.Object, obj runtime.Object) (bool, error) {
|
||||||
|
// Check finalize
|
||||||
|
if metadata.GetDeletionTimestamp() == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slice.ContainsString(metadata.GetFinalizers(), o.name) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = obj.DeepCopyObject()
|
||||||
|
metadata, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalizers []string
|
||||||
|
for _, finalizer := range metadata.GetFinalizers() {
|
||||||
|
if finalizer == o.name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
finalizers = append(finalizers, finalizer)
|
||||||
|
}
|
||||||
|
metadata.SetFinalizers(finalizers)
|
||||||
|
|
||||||
|
if err := o.lifecycle.Finalize(obj); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = o.objectClient.Update(metadata.GetName(), obj)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *objectLifecycleAdapter) initializeKey() string {
|
||||||
|
return initialized + "." + o.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *objectLifecycleAdapter) initialize(metadata metav1.Object, obj runtime.Object) (bool, error) {
|
||||||
|
initialized := o.initializeKey()
|
||||||
|
|
||||||
|
if metadata.GetLabels()[initialized] == "true" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = obj.DeepCopyObject()
|
||||||
|
metadata, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.GetLabels() == nil {
|
||||||
|
metadata.SetLabels(map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.SetFinalizers(append(metadata.GetFinalizers(), o.name))
|
||||||
|
metadata.GetLabels()[initialized] = "true"
|
||||||
|
if err := o.lifecycle.Initialize(obj); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = o.objectClient.Update(metadata.GetName(), obj)
|
||||||
|
return false, err
|
||||||
|
}
|
@@ -83,8 +83,12 @@ func (b *Builder) copyInputs(schema *types.Schema, input map[string]interface{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
if op == List {
|
if op == List {
|
||||||
result["type"] = input["type"]
|
if !convert.IsEmpty(input["type"]) {
|
||||||
result["id"] = input["id"]
|
result["type"] = input["type"]
|
||||||
|
}
|
||||||
|
if !convert.IsEmpty(input["id"]) {
|
||||||
|
result["id"] = input["id"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
165
parse/parse.go
165
parse/parse.go
@@ -2,11 +2,11 @@ package parse
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rancher/norman/api/builtin"
|
"github.com/rancher/norman/api/builtin"
|
||||||
"github.com/rancher/norman/httperror"
|
|
||||||
"github.com/rancher/norman/types"
|
"github.com/rancher/norman/types"
|
||||||
"github.com/rancher/norman/urlbuilder"
|
"github.com/rancher/norman/urlbuilder"
|
||||||
)
|
)
|
||||||
@@ -23,20 +23,83 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ParsedURL struct {
|
||||||
|
Version string
|
||||||
|
Type string
|
||||||
|
ID string
|
||||||
|
Link string
|
||||||
|
Method string
|
||||||
|
Action string
|
||||||
|
SubContext map[string]string
|
||||||
|
SubContextPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
type ResolverFunc func(typeName string, context *types.APIContext) error
|
type ResolverFunc func(typeName string, context *types.APIContext) error
|
||||||
|
|
||||||
func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, resolverFunc ResolverFunc) (*types.APIContext, error) {
|
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, "/")
|
||||||
|
|
||||||
|
parts := strings.SplitN(path[len(version.Path):], "/", 4)
|
||||||
|
prefix, parts, subContext := parseSubContext(version, parts)
|
||||||
|
|
||||||
|
result.Version = version.Path
|
||||||
|
result.SubContext = subContext
|
||||||
|
result.SubContextPrefix = prefix
|
||||||
|
result.Action, result.Method = parseAction(url)
|
||||||
|
|
||||||
|
result.Type = safeIndex(parts, 1)
|
||||||
|
result.ID = safeIndex(parts, 2)
|
||||||
|
result.Link = safeIndex(parts, 3)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, urlParser URLParser, resolverFunc ResolverFunc) (*types.APIContext, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
result := &types.APIContext{
|
result := &types.APIContext{
|
||||||
Request: req,
|
Schemas: schemas,
|
||||||
Response: rw,
|
Request: req,
|
||||||
|
Response: rw,
|
||||||
|
Method: parseMethod(req),
|
||||||
|
ResponseFormat: parseResponseFormat(req),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.URLBuilder, _ = urlbuilder.New(req, types.APIVersion{}, schemas)
|
||||||
|
|
||||||
// The response format is guarenteed to be set even in the event of an error
|
// The response format is guarenteed to be set even in the event of an error
|
||||||
result.ResponseFormat = parseResponseFormat(req)
|
parsedURL, err := urlParser(schemas, req.URL)
|
||||||
result.Version = parseVersion(schemas, req.URL.Path)
|
// wait to check error, want to set as much as possible
|
||||||
result.Schemas = schemas
|
|
||||||
|
result.SubContext = parsedURL.SubContext
|
||||||
|
result.Type = parsedURL.Type
|
||||||
|
result.ID = parsedURL.ID
|
||||||
|
result.Link = parsedURL.Link
|
||||||
|
result.Action = parsedURL.Action
|
||||||
|
if parsedURL.Method != "" {
|
||||||
|
result.Method = parsedURL.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, version := range schemas.Versions() {
|
||||||
|
if version.Path == parsedURL.Version {
|
||||||
|
result.Version = &schemas.Versions()[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
if result.Version == nil {
|
if result.Version == nil {
|
||||||
result.Method = http.MethodGet
|
result.Method = http.MethodGet
|
||||||
@@ -46,15 +109,16 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Method = parseMethod(req)
|
|
||||||
result.Action, result.Method = parseAction(req, result.Method)
|
|
||||||
|
|
||||||
result.URLBuilder, err = urlbuilder.New(req, *result.Version, result.Schemas)
|
result.URLBuilder, err = urlbuilder.New(req, *result.Version, result.Schemas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := parsePath(result, req, resolverFunc); err != nil {
|
if parsedURL.SubContextPrefix != "" {
|
||||||
|
result.URLBuilder.SetSubContext(parsedURL.SubContextPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resolverFunc(result.Type, result); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +130,8 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.Type = result.Schema.ID
|
||||||
|
|
||||||
if err := ValidateMethod(result); err != nil {
|
if err := ValidateMethod(result); err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -73,33 +139,24 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSubContext(parts []string, apiRequest *types.APIContext) []string {
|
func parseSubContext(version *types.APIVersion, parts []string) (string, []string, map[string]string) {
|
||||||
subContext := ""
|
subContext := ""
|
||||||
apiRequest.SubContext = map[string]string{}
|
result := map[string]string{}
|
||||||
apiRequest.Attributes = map[string]interface{}{}
|
|
||||||
|
|
||||||
for len(parts) > 3 && apiRequest.Version != nil && parts[3] != "" {
|
for len(parts) > 3 && version != nil && parts[3] != "" {
|
||||||
resourceType := parts[1]
|
resourceType := parts[1]
|
||||||
resourceID := parts[2]
|
resourceID := parts[2]
|
||||||
|
|
||||||
if !apiRequest.Version.SubContexts[resourceType] {
|
if !version.SubContexts[resourceType] {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if apiRequest.ReferenceValidator != nil && !apiRequest.ReferenceValidator.Validate(resourceType, resourceID) {
|
result[resourceType] = resourceID
|
||||||
return parts
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRequest.SubContext[resourceType] = resourceID
|
|
||||||
subContext = subContext + "/" + resourceType + "/" + resourceID
|
subContext = subContext + "/" + resourceType + "/" + resourceID
|
||||||
parts = append(parts[:1], parts[3:]...)
|
parts = append(parts[:1], parts[3:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subContext != "" {
|
return subContext, parts, result
|
||||||
apiRequest.URLBuilder.SetSubContext(subContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultResolver(typeName string, apiContext *types.APIContext) error {
|
func DefaultResolver(typeName string, apiContext *types.APIContext) error {
|
||||||
@@ -120,50 +177,6 @@ func DefaultResolver(typeName string, apiContext *types.APIContext) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePath(apiRequest *types.APIContext, request *http.Request, resolverFunc ResolverFunc) error {
|
|
||||||
if apiRequest.Version == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
path := request.URL.Path
|
|
||||||
path = multiSlashRegexp.ReplaceAllString(path, "/")
|
|
||||||
|
|
||||||
versionPrefix := apiRequest.Version.Path
|
|
||||||
if !strings.HasPrefix(path, versionPrefix) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(path[len(versionPrefix):], "/")
|
|
||||||
parts = parseSubContext(parts, apiRequest)
|
|
||||||
|
|
||||||
if len(parts) > 4 {
|
|
||||||
return httperror.NewAPIError(httperror.NotFound, "No handler for path")
|
|
||||||
}
|
|
||||||
|
|
||||||
typeName := safeIndex(parts, 1)
|
|
||||||
id := safeIndex(parts, 2)
|
|
||||||
link := safeIndex(parts, 3)
|
|
||||||
|
|
||||||
if err := resolverFunc(typeName, apiRequest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if apiRequest.Schema == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRequest.Type = apiRequest.Schema.ID
|
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
apiRequest.ID = id
|
|
||||||
apiRequest.Link = link
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeIndex(slice []string, index int) string {
|
func safeIndex(slice []string, index int) string {
|
||||||
if index >= len(slice) {
|
if index >= len(slice) {
|
||||||
return ""
|
return ""
|
||||||
@@ -198,20 +211,16 @@ func parseMethod(req *http.Request) string {
|
|||||||
return method
|
return method
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAction(req *http.Request, method string) (string, string) {
|
func parseAction(url *url.URL) (string, string) {
|
||||||
if req.Method != http.MethodPost {
|
action := url.Query().Get("action")
|
||||||
return "", method
|
|
||||||
}
|
|
||||||
|
|
||||||
action := req.URL.Query().Get("action")
|
|
||||||
if action == "remove" {
|
if action == "remove" {
|
||||||
return "", http.MethodDelete
|
return "", http.MethodDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
return action, method
|
return action, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVersion(schemas *types.Schemas, path string) *types.APIVersion {
|
func Version(schemas *types.Schemas, path string) *types.APIVersion {
|
||||||
path = multiSlashRegexp.ReplaceAllString(path, "/")
|
path = multiSlashRegexp.ReplaceAllString(path, "/")
|
||||||
for _, version := range schemas.Versions() {
|
for _, version := range schemas.Versions() {
|
||||||
if version.Path == "" {
|
if version.Path == "" {
|
||||||
|
@@ -15,7 +15,7 @@ func NewMetadataMapper() types.Mapper {
|
|||||||
Move{From: "deletionTimestamp", To: "removed"},
|
Move{From: "deletionTimestamp", To: "removed"},
|
||||||
Drop{"deletionGracePeriodSeconds"},
|
Drop{"deletionGracePeriodSeconds"},
|
||||||
Drop{"initializers"},
|
Drop{"initializers"},
|
||||||
Drop{"finalizers"},
|
//Drop{"finalizers"},
|
||||||
Drop{"clusterName"},
|
Drop{"clusterName"},
|
||||||
ReadOnly{Field: "*"},
|
ReadOnly{Field: "*"},
|
||||||
Access{
|
Access{
|
||||||
|
@@ -24,3 +24,11 @@ func (v *APIVersion) Equals(other *APIVersion) bool {
|
|||||||
func (s *Schema) CanList() bool {
|
func (s *Schema) CanList() bool {
|
||||||
return slice.ContainsString(s.CollectionMethods, http.MethodGet)
|
return slice.ContainsString(s.CollectionMethods, http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Schema) CanUpdate() bool {
|
||||||
|
return slice.ContainsString(s.ResourceMethods, http.MethodPut)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schema) CanDelete() bool {
|
||||||
|
return slice.ContainsString(s.ResourceMethods, http.MethodDelete)
|
||||||
|
}
|
||||||
|
@@ -60,7 +60,7 @@ func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) AddSchema(schema *Schema) *Schemas {
|
func (s *Schemas) AddSchema(schema *Schema) *Schemas {
|
||||||
schema.Type = "/v1-meta/schemas/schema"
|
schema.Type = "/meta/schemas/schema"
|
||||||
if schema.ID == "" {
|
if schema.ID == "" {
|
||||||
s.errors = append(s.errors, fmt.Errorf("ID is not set on schema: %v", schema))
|
s.errors = append(s.errors, fmt.Errorf("ID is not set on schema: %v", schema))
|
||||||
return s
|
return s
|
||||||
|
@@ -83,7 +83,7 @@ type APIContext struct {
|
|||||||
URLBuilder URLBuilder
|
URLBuilder URLBuilder
|
||||||
AccessControl AccessControl
|
AccessControl AccessControl
|
||||||
SubContext map[string]string
|
SubContext map[string]string
|
||||||
Attributes map[string]interface{}
|
//Attributes map[string]interface{}
|
||||||
|
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Response http.ResponseWriter
|
Response http.ResponseWriter
|
||||||
@@ -140,6 +140,7 @@ type URLBuilder interface {
|
|||||||
SubContextCollection(subContext *Schema, contextName string, schema *Schema) string
|
SubContextCollection(subContext *Schema, contextName string, schema *Schema) string
|
||||||
SchemaLink(schema *Schema) string
|
SchemaLink(schema *Schema) string
|
||||||
ResourceLink(resource *RawResource) string
|
ResourceLink(resource *RawResource) string
|
||||||
|
Link(linkName string, resource *RawResource) string
|
||||||
RelativeToRoot(path string) string
|
RelativeToRoot(path string) string
|
||||||
Version(version APIVersion) string
|
Version(version APIVersion) string
|
||||||
Marker(marker string) string
|
Marker(marker string) string
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package values
|
package values
|
||||||
|
|
||||||
|
import "github.com/rancher/norman/types/convert"
|
||||||
|
|
||||||
func RemoveValue(data map[string]interface{}, keys ...string) (interface{}, bool) {
|
func RemoveValue(data map[string]interface{}, keys ...string) (interface{}, bool) {
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if i == len(keys)-1 {
|
if i == len(keys)-1 {
|
||||||
@@ -13,6 +15,30 @@ func RemoveValue(data map[string]interface{}, keys ...string) (interface{}, bool
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStringSlice(data map[string]interface{}, keys ...string) ([]string, bool) {
|
||||||
|
val, ok := GetValue(data, keys...)
|
||||||
|
if !ok {
|
||||||
|
return nil, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
slice, typeOk := val.([]string)
|
||||||
|
if typeOk {
|
||||||
|
return slice, typeOk
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceNext, typeOk := val.([]interface{})
|
||||||
|
if !typeOk {
|
||||||
|
return nil, typeOk
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []string
|
||||||
|
for _, item := range sliceNext {
|
||||||
|
result = append(result, convert.ToString(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
func GetSlice(data map[string]interface{}, keys ...string) ([]map[string]interface{}, bool) {
|
func GetSlice(data map[string]interface{}, keys ...string) ([]map[string]interface{}, bool) {
|
||||||
val, ok := GetValue(data, keys...)
|
val, ok := GetValue(data, keys...)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@@ -53,6 +53,14 @@ func (u *urlBuilder) SchemaLink(schema *types.Schema) string {
|
|||||||
return u.constructBasicURL(schema.Version, "schemas", schema.ID)
|
return u.constructBasicURL(schema.Version, "schemas", schema.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *urlBuilder) Link(linkName string, resource *types.RawResource) string {
|
||||||
|
if resource.ID == "" || linkName == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.constructBasicURL(resource.Schema.Version, resource.Schema.PluralName, resource.ID, strings.ToLower(linkName))
|
||||||
|
}
|
||||||
|
|
||||||
func (u *urlBuilder) ResourceLink(resource *types.RawResource) string {
|
func (u *urlBuilder) ResourceLink(resource *types.RawResource) string {
|
||||||
if resource.ID == "" {
|
if resource.ID == "" {
|
||||||
return ""
|
return ""
|
||||||
|
Reference in New Issue
Block a user