usher

package module
v0.0.0-...-89bf0d2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2018 License: MIT Imports: 12 Imported by: 0

README

Usher is a REST framework written in Go (or Golang) and has an unconventional design inspired by Kubernetes apimachinery.

Go Report Card

Install

With Go installed:

go get -u github.com/spy16/usher

Getting Started

  • See example/main.go to understand how to build REST applications.
  • See cmd/README.md for usage of CLI

Features

  • Uniform API Objects: Usher uses Object struct to represent all API resources throughout the system. Only Spec field will be formatted according to the resource registered.

    // See types.go for each field description
    type Object struct {
        ObjectID `json:",inline"`
        Meta `json:",inline"`
        Tags Tags `json:"tags"`
        State string `json:"state"`
        Spec Spec `json:"spec"`    
    }
    
    • Due to this, all API resources will appear similar to the sample below to clients. Only value of spec key will change for different resources.

      {
        "kind": "Test",
        "name": "hello",
        "parents": [],
        "created_on": "2017-08-03T20:03:33.184295276+05:30",
        "modified_on": "2017-08-03T20:03:33.184295313+05:30",
        "owner": "",
        "state": "READY",
        "tags": {},
        "spec": {
          "body": "Hello World"
        }
      }
      
  • Automatic data validation: Usher performs basic validations based on the specification struct registered along with its controller.

      // This is safe inside the controller. No need to use assertion check
      // to stay away from panics. Usher allows requests to reach controller
      // only if the request data conforms to `MySpec`
      spec := obj.Spec.(*MySpec)
    
  • Because of the uniformity, a single client library (e.g. See client package) can be used to manage all resources via REST API.

  • A single CLI (similar to kubectl) to remotely manage all resources.

  • Easy error handling through helpers: Error struct is used throughout the system to manage errors. For example, an unauthorized error as generated by usher.ErrUnauthorized(ctx) helper:

    {
      "code": 403,
      "type": "Unauthorized",
      "details": {
        "action": "CREATE",
        "kind": "Test",
        "name": "hello",
        "parents": []
      },
      "message": "You are not allowed to perform requested action"
    }
    
  • Middleware support: Any existing net/http compatible middlewares (e.g. gorilla/handlers) can be used with Usher.

  • Easy to implement Authentication and Authorization

  • Automatic REST resource nesting

Documentation

Index

Constants

View Source
const (
	// ActionGet represents GET request action.
	ActionGet = "GET"

	// ActionCreate represents POST request action
	ActionCreate = "CREATE"

	// ActionModify represents PUT and PATCH request action
	ActionModify = "MODIFY"

	// ActionDelete represents DELETE request action
	ActionDelete = "DELETE"
)
View Source
const (

	// StateReady represents the Ready state for an object.
	StateReady = "READY"
)
View Source
const (
	// SystemUser is a special user id which is used whenever
	// a logical user doesn't exist.
	SystemUser = "system"
)

Variables

View Source
var EmptyMap = map[string]interface{}{}

EmptyMap is default empty map

Functions

func ObjectFromRequest

func ObjectFromRequest(grp *APIGroup, r *http.Request, ps httprouter.Params) (Object, *Error)

ObjectFromRequest builds object by reading query params and body data. JSON encoding is assumed for body.

func ResolveParentList

func ResolveParentList(refs *ParentRefs, grp *APIGroup, ps httprouter.Params)

ResolveParentList traverses the APIGroup parent hierarchy and populates the refs in order.

func ToAPIName

func ToAPIName(kn string) string

ToAPIName converts KindName to API Name

func ToKindName

func ToKindName(apiEntry string) string

ToKindName converts kind-name from format `projects` to `Project`

Types

type APIGroup

type APIGroup struct {

	// ParentGroup
	ParentGroup *APIGroup

	// APIName is the API partial for the resource (e.g. projects)
	APIName string

	// Kind is the type of the API resource (e.g. Project)
	Kind string
	// contains filtered or unexported fields
}

APIGroup is group of APIs for one type of resource.

func NewAPIGroup

func NewAPIGroup(apiName string, spec Spec, ctrl Controller) *APIGroup

NewAPIGroup initializes an API Group

func (*APIGroup) SetParent

func (grp *APIGroup) SetParent(p *APIGroup)

SetParent can be used to add a parent api group to this API Group. This allows nesting of resources which is a core concept in REST conventions. When nested, each object or query will be populated with parent details in the `Meta.Parents` and `Parents` field respectively. The list of parents will be in the order the groups were registered.

type AuthenticatorFunc

type AuthenticatorFunc func(ctx Context, id, secret string) (bool, Context)

AuthenticatorFunc will be used to authenticate requests globally or per-APIGroup basis. If, function returns true, authentication will pass and `id` will be injected into request context with key `SubjectKey` type.

type AuthorizationContext

type AuthorizationContext struct {

	// Subject is the entity that is performing the action.
	Subject string `json:"subject"`

	// These 3 fields together identify the entity on which
	// action is being performed.
	Kind    string      `json:"kind"`
	Name    string      `json:"name"`
	Parents []ParentRef `json:"parent"`

	// Action is the operation being performed on the object.
	Action string `json:"action"`
}

AuthorizationContext is used to pass information to Authorizer when certain action requires Authorization.

type AuthorizerFunc

type AuthorizerFunc func(reqCtx Context, authCtx AuthorizationContext) bool

AuthorizerFunc is responsible for authorizing a request. This function should answer the question "Is `Subject` Allowed to perform `Action` on `Object` ?".

type ByName

type ByName QueryClause

ByName when used should filter the results by name.

type ByOwner

type ByOwner QueryClause

ByOwner when used should filter the results by owner.

type ByState

type ByState QueryClause

ByState when used should filter the results by state.

type Context

type Context context.Context

Context is used to exchange request information between controllers and middlewares.

func ContextFromRequest

func ContextFromRequest(r *http.Request) Context

ContextFromRequest extracts relevant information from request object.

type ContextKey

type ContextKey string

ContextKey type is used as type of key for Request Context values.

var ParamsKey ContextKey = "params"

ParamsKey is used to inject params into request context.

var SubjectKey ContextKey = "subject"

SubjectKey will be used as key in request context to store user identity during a request.

type Controller

type Controller interface {

	// GetAll should fetch all the objects matching the given query. If
	// query fails for any reason, an appropriate error should be returned
	// in which case the first return value can be nil-pointer.
	// GetAll maps to `GET /{kind}?filter1=value&...`
	GetAll(query Query) (*[]Object, *Error)

	// Create should store the object. This matches to `POST /{kind}`
	// endpoints.
	Create(obj Object) (*Object, *Error)

	// Resource Operations
	// Get should return the object identifying the `id`. This maps
	// to `GET /{kind}/{name}` requests.
	Get(id ObjectID) (*Object, *Error)

	// Exists should return nil-pointer if an object exists which
	// can be identified using `id`. If the object is not found,
	// a not-found error should be returned. Other errors can be
	// returned, but note that, in some places all errors might
	// be considered as NotFound error.
	// This method maps to `HEAD /{kind}/{name}`
	Exists(id ObjectID) *Error

	// Update should replace the existing object identified by the
	// `id` by the newObject completely. Note that, fields (Kind,
	// Name, Parents, Meta) should never be changed for an object.
	// This method maps to `PUT /{kind}/{name}` request.
	Update(id ObjectID, newObject Object) (*Object, *Error)

	// PartialUpdate should partially update the object identified by
	// the `id`. Update should consider only the fields specified in
	// the `updates` array for modifications. Other fields should be
	// left untouched.
	// This method maps to `PATCH /{kind}/{name}`.
	PartialUpdate(id ObjectID, updates []UpdateClause) (*Object, *Error)

	// Delete should remove the resource from the server completely.
	// This method maps to `DELETE /{kind}/{name}`.
	Delete(id ObjectID) (*Object, *Error)
}

Controller interface can be implemented to add business logic around resources. Every request, will be resolved to a controller based on the kind and the request will be delegated to an appropriate method in this interface. A method can be disabled by returning error MethodNotSupported (Hint: Use `usher.ErrMethodNotAllowed()`) NOTE: Controllers are re-used across requests. So no request-scoped data should be stored in the controller.

type Error

type Error struct {
	// Code is generally a 4xx or 5xx HTTP Standard code
	Code int `json:"code"`

	// Type is a single word, camel-cased name for the error. This helps in
	// differentiating between common error codes. For example, code can be
	// 400 to represent bad request and type name an be InvalidValue, InvalidType
	// MissingParameter etc.
	Type string `json:"type"`

	// Details provides any additional information abou the error. For example,
	// in case of MissingParameter error, details will contain the name of the
	// missing parameter.
	Details map[string]interface{} `json:"details"`

	// Message is a string which can be directly shown to user. This should
	// not contain any technical errors.
	Message string `json:"message"`

	// Err object if this struct was initialized in response to an `error`.
	Err error `json:"error,omitempty"`
}

Error is used to communicate different errors throughout usher framework. Generally, instead of directly creating instance of Error object, common helper methods such as `ErrUnauthenticated` are defined in this package which can be used easily.

func CheckAuthorization

func CheckAuthorization(action string, grp *APIGroup, ctx Context, id ObjectID) *Error

CheckAuthorization checks if the request can be allowed under given context. Context should contain the Subject id under key 'SubjectKey'.

func ErrBadData

func ErrBadData() *Error

ErrBadData represents a bad request with non-parsable data

func ErrBadRequest

func ErrBadRequest(reason string) *Error

ErrBadRequest represents a generic bad request

func ErrBadSpec

func ErrBadSpec(v interface{}) *Error

ErrBadSpec is returned when specification is invalid

func ErrConflict

func ErrConflict(rid string, rtype string) *Error

ErrConflict represents a conflicting resource

func ErrConnection

func ErrConnection() *Error

ErrConnection is returned when connection fails

func ErrConversion

func ErrConversion(err error) *Error

ErrConversion can be used represent conversion error.s

func ErrMethodNotAllowed

func ErrMethodNotAllowed() *Error

ErrMethodNotAllowed represents 405

func ErrMissingParam

func ErrMissingParam(param string) *Error

ErrMissingParam represents a missing parameter

func ErrNotFound

func ErrNotFound(rid string, rtype string) *Error

ErrNotFound represents an access to non-existent resource

func ErrPathNotFound

func ErrPathNotFound(path string) *Error

ErrPathNotFound represents an access to non-existent resource

func ErrUnauthenticated

func ErrUnauthenticated() *Error

ErrUnauthenticated represents 401

func ErrUnauthorized

func ErrUnauthorized(authCtx AuthorizationContext) *Error

ErrUnauthorized represents 403

func ErrUnexpected

func ErrUnexpected(err error) *Error

ErrUnexpected represents an unexpected internal error Err field will be populated in this case

func (*Error) String

func (e *Error) String() string

String formats the error

func (Error) Write

func (e Error) Write(w http.ResponseWriter) error

Write appropriately formats the error object and writes it to the ResponseWriter. The `Code` field will also be sent as StatusCode in the response.

type MapSpec

type MapSpec map[string]interface{}

MapSpec is a type alias for map that is used to initialize Object.Spec field.

type Meta

type Meta struct {

	// CreatedOn should contain the time of creation of this object.
	CreatedOn time.Time `json:"created_on"`

	// ModifiedOn should contain the time of last modification of this
	// object. While creating the object, this should be same as the
	// CreatedOn timestamp.
	ModifiedOn time.Time `json:"modified_on"`

	// Owner can be used to store information regarding the subject who
	// created the resource to which this meta is attached.
	Owner string `json:"owner"`

	// SelfURI contains self link to the resource
	SelfURI string `json:"self_uri"`
}

Meta is used to represent meta information about an object which includes creation and modification timestamps, tags, parent information etc.

type Middleware

type Middleware func(h http.HandlerFunc) http.HandlerFunc

Middleware can be used to intercept request between handlers.

type NoOpController

type NoOpController struct {
}

NoOpController is a default implementation of Controller interface. By default NoOpController performs no operation and returns `ErrMethodNotAllowed`.

func (*NoOpController) Create

func (*NoOpController) Create(obj Object) (*Object, *Error)

func (*NoOpController) Delete

func (*NoOpController) Delete(id ObjectID) (*Object, *Error)

func (*NoOpController) Exists

func (*NoOpController) Exists(id ObjectID) *Error

func (*NoOpController) Get

func (*NoOpController) Get(id ObjectID) (*Object, *Error)

func (*NoOpController) GetAll

func (*NoOpController) GetAll(query Query) (*[]Object, *Error)

func (*NoOpController) PartialUpdate

func (*NoOpController) PartialUpdate(id ObjectID, updates []UpdateClause) (*Object, *Error)

func (*NoOpController) Update

func (*NoOpController) Update(id ObjectID, newObject Object) (*Object, *Error)

type Object

type Object struct {

	// ObjectID contains all the information required to uniquey and completely
	// identify an object in Usher.
	ObjectID `json:",inline"`

	// Meta is used to represent meta information about an object which
	// includes creation and modification timestamps, tags, parent information
	// etc.
	Meta `json:",inline"`

	// Tags can be used to store additional information for the object
	// which can later be used to filter search results.
	Tags Tags `json:"tags"`

	// State represents the state of object. State will not be used by
	// usher, but is a field to be used by usher applications.
	State string `json:"state"`

	// Spec should contain the specification for the object. This is the
	// field that actually describes the intent/data of the object. The
	// specification model registered with the resource will be used to
	// verify and populate this field. So, inside the controller, simple
	// assertion will be enough without error handling to load this to
	// a variable of that type.
	Spec Spec `json:"spec"`
}

Object is the core type in usher. Every component is built around object and represents any/all REST resources.

func (*Object) Validate

func (obj *Object) Validate() *Error

Validate checks validity of Name and Kind fields. If these fields have appropriate values, nil is returned.

type ObjectID

type ObjectID struct {
	// Kind represents the type of the object. Kind will be CamelCased.
	// API name for a kind will be generated by converting it to plural.
	// For example, for kind `Test` the api endpoint will be `/tests`.
	// Look at ToAPIName() and ToKindName() for information on how the
	// conversion is done.
	Kind string `json:"kind"`

	// Name represents a unique name for the object. Name is not
	// exclusively unique. Name will be unique in a (Kind, Parents)
	// scope.
	Name string `json:"name"`

	// Parents represents all the logical parents of this resource. The
	// array is ordered according to the hierarchy of APIGroups.
	Parents ParentRefs `json:"parents"`
}

ObjectID contains all the information required to uniquey and completely identify an object in Usher.

func IDFromRequest

func IDFromRequest(grp *APIGroup, r *http.Request, ps httprouter.Params) ObjectID

IDFromRequest extracts ObjectID from a resource request. This method will return zero-value of ObjectID for collection-level requests. (i.e., when name=="").

func (*ObjectID) CollectionPath

func (id *ObjectID) CollectionPath() string

CollectionPath creates the path for the collection of objects of given kind.

func (*ObjectID) IsValid

func (id *ObjectID) IsValid() *Error

IsValid verifies if ObjectID can be used to identify an object. This simply means verifying that all fields of ObjectID have a required values.

func (*ObjectID) Path

func (id *ObjectID) Path() string

Path creates the URI for the object.

func (*ObjectID) ToString

func (id *ObjectID) ToString() string

ToString method is part of fmt.Stringer interface.

type ParentRef

type ParentRef struct {
	Kind string `json:"kind"`
	Name string `json:"name"`
}

ParentRef is used to identify parents of a resource. All parents are appended automatically by Usher request handlers and passed down to the controller.

type ParentRefs

type ParentRefs []ParentRef

ParentRefs is an ordered array of `ParentRef` types which represents parents of an object in a hierarchy.

func (*ParentRefs) Get

func (prs *ParentRefs) Get(kind string) ObjectID

Get returns the ObjectID for the parent of given `kind`.

func (*ParentRefs) Path

func (prs *ParentRefs) Path() string

Path generates the uri-like path for the object representing the parent hierarchy.

type Query

type Query struct {

	// Filter by parts of ID
	ObjectID

	// Owner can be used to filter objects by user who
	// created the resource.
	Owner string

	// State can be used to filter objects by state.
	State string

	// Tags can be used to filter objects by tags.
	Tags map[string]string

	// Params can be used to provide additional parameters to
	// controllers and ObjectStore implementations.
	Params map[string]string
}

Query is used to identify an object or collection of objects by controllers, stores etc.

func QueryFromRequest

func QueryFromRequest(grp *APIGroup, r *http.Request, ps httprouter.Params) Query

QueryFromRequest extracts query related information from the request.

func (*Query) IsValidCollectionID

func (query *Query) IsValidCollectionID() *Error

IsValidCollectionID verifies if query can be used to uniquely and completely identify a collection of objects of same kinds. This is possible only if (Kind) has appropriate value.

func (*Query) IsValidItemID

func (query *Query) IsValidItemID() *Error

IsValidItemID verifies if query can be used to uniquely and completely identify an object. In Usher, the tuple (Kind, Name, [Parents]) is required to identify an object uniquely.

type QueryClause

type QueryClause string

QueryClause is used to filter results in GetAll requests.

type SelfValidator

type SelfValidator interface {
	Validate() *Error
}

SelfValidator is an entity that can validate itself.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the main usher server.

func New

func New() *Server

New initializes the server.

func (*Server) AddMiddleware

func (srv *Server) AddMiddleware(m Middleware)

AddMiddleware can be used to register middlewares to the server. Note that, middlewares will be executed in the same order they were added in.

func (*Server) GetHandler

func (srv *Server) GetHandler() http.Handler

GetHandler generates a 'http.Handler' by combining all API Groups and all middlewares.

func (*Server) Handle

func (srv *Server) Handle(method string, path string, h httprouter.Handle)

Handle can be used to register httprouter.Handle implementations to non-resource api paths. This is `httprouter` version of the Server.HandleFunc function.

func (*Server) HandleFunc

func (srv *Server) HandleFunc(method string, path string, h http.HandlerFunc)

HandleFunc can be used to register handlers for non-resource api paths. This can be used to for registering paths for login, health check endpoints etc. This function is a shorthand for `mux.router.HandleFunc`

func (*Server) Register

func (srv *Server) Register(apiName string, spec Spec, controller Controller, ms ...Middleware) *APIGroup

Register can be used to create a new APIGroup with given apiname, spec and controller. This is just a shorthand for Server.RegisterGroup.

func (*Server) RegisterGroup

func (srv *Server) RegisterGroup(grp *APIGroup) *APIGroup

RegisterGroup can be used to register a APIGroup to the server.

func (*Server) SetAuthenticator

func (srv *Server) SetAuthenticator(f AuthenticatorFunc)

SetAuthenticator initializes the authentication middleware with given Authenticator function. If authentication function authenticates the user, the user id will be injected into the context. In addition to the user id, authentication function itself can inject additional values into the context which will be passed to the Controller. This may be additional user information etc.

func (*Server) SetAuthorizer

func (srv *Server) SetAuthorizer(f AuthorizerFunc)

SetAuthorizer sets authorizer function which will be used to authorize user actions.

func (*Server) SetBaseURL

func (srv *Server) SetBaseURL(base string)

SetBaseURL appends base to all the API endpoints.

type Spec

type Spec interface{}

Spec is the type of `Spec` field in `Object`

type Tags

type Tags map[string]string

Tags can be used to store additional information for the object which can later be used to filter search results.

type UpdateClause

type UpdateClause interface{}

UpdateClause is an alias for interface{} that is used as placeholder in Interface.Update()

type UpdateSpec

type UpdateSpec struct {
	Spec Spec
}

UpdateSpec can be used to tell store implementations to update the specification of an object.

type UpdateState

type UpdateState struct {
	State string
}

UpdateState can be used to tell store implementations to update 'state' field of the object.

type UpdateTags

type UpdateTags struct {
	Tags map[string]string
}

UpdateTags can be used to tell store implementations to update the 'tags' field of the object.

Directories

Path Synopsis
cmd
cli

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL