bramble

package module
v1.1.7 Latest Latest
Warning

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

Go to latest
Published: May 21, 2021 License: MIT Imports: 35 Imported by: 4

README

Bramble

Go Reference Go Report Card

Full documentation

Bramble is a production-ready GraphQL federation gateway. It is built to be a simple, reliable and scalable way to aggregate GraphQL services together.

overview

Features

Bramble supports:

  • Shared types across services
  • Namespaces
  • Field-level permissions
  • Plugins:
    • JWT, Open tracing, CORS, ...
    • Or add your own
  • Hot reloading of configuration

It is also stateless and scales very easily.

Future work/not currently supported

There is currently no support for subscriptions.

Contributing

Contributions are always welcome!

If you wish to contribute please open a pull request. Please make sure to:

  • include a brief description or link to the relevant issue
  • (if applicable) add tests for the behaviour you're adding/modifying
  • commit messages are descriptive

Before making a significant change we recommend opening an issue to discuss the issue you're facing and the proposed solution.

Building and testing

Prerequisite: Go 1.16 or newer

To build the bramble command:

go build -o bramble ./cmd
./bramble -conf config.json

To run the tests:

go test ./...

Comparison with other projects

  • Apollo Server

    While Apollo Server is a popular tool we felt is was not the right tool for us as:

    • the federation specification is more complex than necessary
    • it is written in NodeJS where we favour Go
  • Nautilus

    Nautilus provided a lot of inspiration for Bramble.

    Although the approach to federation was initially similar, Bramble now uses a different approach and supports for a few more things: fine-grained permissions, namespaces, easy plugin configuration, configuration hot-reloading...

    Bramble is also a central piece of software for Movio products and thus is actively maintained and developed.

Documentation

Index

Constants

View Source
const DebugKey contextKey = "debug"

Variables

This section is empty.

Functions

func AddField

func AddField(ctx context.Context, name string, value interface{})

func AddFields

func AddFields(ctx context.Context, fields EventFields)

func AddOutgoingRequestsHeaderToContext

func AddOutgoingRequestsHeaderToContext(ctx context.Context, key, value string) context.Context

AddOutgoingRequestsHeaderToContext adds a header to all outgoings requests for the current query

func AddPermissionsToContext

func AddPermissionsToContext(ctx context.Context, perms OperationPermissions) context.Context

AddPermissionsToContext adds permissions to the request context. If permissions are set the execution will check them against the query.

func GetOutgoingRequestHeadersFromContext

func GetOutgoingRequestHeadersFromContext(ctx context.Context) http.Header

GetOutgoingRequestHeadersFromContext get the headers that should be added to outgoing requests

func Main

func Main()

func MergeSchemas

func MergeSchemas(schemas ...*ast.Schema) (*ast.Schema, error)

func NewMetricsHandler

func NewMetricsHandler() http.Handler

func Plan

func Plan(ctx *PlanningContext) (*queryPlan, error)

func RegisterMetrics

func RegisterMetrics()

func RegisterPlugin

func RegisterPlugin(p Plugin)

func RegisteredPlugins

func RegisteredPlugins() map[string]Plugin

func TraceIDFromContext

func TraceIDFromContext(ctx context.Context) string

func ValidateSchema

func ValidateSchema(schema *ast.Schema) error

Types

type AllowedFields

type AllowedFields struct {
	AllowAll         bool
	AllowedSubfields map[string]AllowedFields
}

func MergeAllowedFields added in v1.1.3

func MergeAllowedFields(allowedFields ...AllowedFields) AllowedFields

MergeAllowedFields merges the given AllowedFields. The result is the union of all the allowed fields.

func (AllowedFields) IsAllowed

func (a AllowedFields) IsAllowed(fieldName string) (bool, AllowedFields)

IsAllowed returns whether the sub field is allowed along with the permissions for its own subfields

func (AllowedFields) MarshalJSON

func (a AllowedFields) MarshalJSON() ([]byte, error)

func (*AllowedFields) UnmarshalJSON

func (a *AllowedFields) UnmarshalJSON(input []byte) error

type BasePlugin

type BasePlugin struct{}

func (*BasePlugin) ApplyMiddlewarePrivateMux

func (p *BasePlugin) ApplyMiddlewarePrivateMux(h http.Handler) http.Handler

func (*BasePlugin) ApplyMiddlewarePublicMux

func (p *BasePlugin) ApplyMiddlewarePublicMux(h http.Handler) http.Handler

func (*BasePlugin) Configure

func (p *BasePlugin) Configure(*Config, json.RawMessage) error

func (*BasePlugin) GraphqlQueryPath

func (p *BasePlugin) GraphqlQueryPath() (bool, string)

func (*BasePlugin) Init

func (p *BasePlugin) Init(s *ExecutableSchema)

func (*BasePlugin) ModifyExtensions

func (p *BasePlugin) ModifyExtensions(ctx context.Context, e *QueryExecution, extensions map[string]interface{}) error

func (*BasePlugin) SetupPrivateMux

func (p *BasePlugin) SetupPrivateMux(mux *http.ServeMux)

func (*BasePlugin) SetupPublicMux

func (p *BasePlugin) SetupPublicMux(mux *http.ServeMux)

type BoundaryQueriesMap added in v1.1.0

type BoundaryQueriesMap map[string]map[string]BoundaryQuery

func (BoundaryQueriesMap) Query added in v1.1.0

func (m BoundaryQueriesMap) Query(serviceURL, typeName string) BoundaryQuery

func (BoundaryQueriesMap) RegisterQuery added in v1.1.0

func (m BoundaryQueriesMap) RegisterQuery(serviceURL, typeName, query string, array bool)

type BoundaryQuery added in v1.1.0

type BoundaryQuery struct {
	Query string
	// Whether the query is in the array format
	Array bool
}

type ClientOpt added in v1.1.0

type ClientOpt func(*GraphQLClient)

func WithMaxResponseSize added in v1.1.0

func WithMaxResponseSize(maxResponseSize int64) ClientOpt

func WithUserAgent added in v1.1.7

func WithUserAgent(userAgent string) ClientOpt

type Config

type Config struct {
	GatewayPort            int       `json:"gateway-port"`
	MetricsPort            int       `json:"metrics-port"`
	PrivatePort            int       `json:"private-port"`
	Services               []string  `json:"services"`
	LogLevel               log.Level `json:"loglevel"`
	PollInterval           string    `json:"poll-interval"`
	PollIntervalDuration   time.Duration
	MaxRequestsPerQuery    int64 `json:"max-requests-per-query"`
	MaxServiceResponseSize int64 `json:"max-service-response-size"`
	Plugins                []PluginConfig
	// Config extensions that can be shared among plugins
	Extensions map[string]json.RawMessage
	// contains filtered or unexported fields
}

Config contains the gateway configuration

func GetConfig

func GetConfig(configFiles []string) (*Config, error)

GetConfig returns operational config for the gateway

func (*Config) ConfigurePlugins

func (c *Config) ConfigurePlugins() []Plugin

func (*Config) GatewayAddress

func (c *Config) GatewayAddress() string

GatewayAddress returns the host:port string of the gateway

func (*Config) Init

func (c *Config) Init() error

func (*Config) Load

func (cfg *Config) Load() error

func (*Config) MetricAddress

func (c *Config) MetricAddress() string

func (*Config) PrivateAddress

func (c *Config) PrivateAddress() string

func (*Config) Watch

func (cfg *Config) Watch()

type DebugInfo

type DebugInfo struct {
	Variables bool
	Query     bool
	Plan      bool
	Timing    bool
	TraceID   bool
}

type EventFields

type EventFields map[string]interface{}

type ExecutableSchema

type ExecutableSchema struct {
	MergedSchema        *ast.Schema
	Locations           FieldURLMap
	IsBoundary          map[string]bool
	Services            map[string]*Service
	BoundaryQueries     BoundaryQueriesMap
	GraphqlClient       *GraphQLClient
	Tracer              opentracing.Tracer
	MaxRequestsPerQuery int64
	// contains filtered or unexported fields
}

func (*ExecutableSchema) Complexity

func (s *ExecutableSchema) Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool)

func (*ExecutableSchema) Exec

func (*ExecutableSchema) ExecuteQuery

func (s *ExecutableSchema) ExecuteQuery(ctx context.Context) *graphql.Response

func (*ExecutableSchema) Schema

func (s *ExecutableSchema) Schema() *ast.Schema

func (*ExecutableSchema) UpdateSchema

func (s *ExecutableSchema) UpdateSchema(forceRebuild bool) error

func (*ExecutableSchema) UpdateServiceList

func (s *ExecutableSchema) UpdateServiceList(services []string) error

type FieldURLMap

type FieldURLMap map[string]string

func (FieldURLMap) KeyFor

func (m FieldURLMap) KeyFor(parent string, field string) string

func (FieldURLMap) RegisterURL

func (m FieldURLMap) RegisterURL(parent string, field string, location string)

func (FieldURLMap) UrlFor

func (m FieldURLMap) UrlFor(parent, parentLocation, field string) (string, error)

type Gateway

type Gateway struct {
	ExecutableSchema *ExecutableSchema
	// contains filtered or unexported fields
}

func NewGateway

func NewGateway(executableSchema *ExecutableSchema, plugins []Plugin) *Gateway

NewGateway returns the graphql gateway server mux

func (*Gateway) PrivateRouter

func (g *Gateway) PrivateRouter() http.Handler

func (*Gateway) Router

func (g *Gateway) Router() http.Handler

func (*Gateway) UpdateSchemas

func (g *Gateway) UpdateSchemas(interval time.Duration)

type GraphQLClient

type GraphQLClient struct {
	HTTPClient      *http.Client
	MaxResponseSize int64
	Tracer          opentracing.Tracer
	UserAgent       string
}

func NewClient

func NewClient(opts ...ClientOpt) *GraphQLClient

func (*GraphQLClient) Request

func (c *GraphQLClient) Request(ctx context.Context, url string, request *Request, out interface{}) error

type GraphqlError

type GraphqlError struct {
	Message    string                 `json:"message"`
	Extensions map[string]interface{} `json:"extensions"`
}

type GraphqlErrors

type GraphqlErrors []GraphqlError

func (GraphqlErrors) Error

func (e GraphqlErrors) Error() string

type OperationPermissions

type OperationPermissions struct {
	AllowedRootQueryFields        AllowedFields `json:"query"`
	AllowedRootMutationFields     AllowedFields `json:"mutation"`
	AllowedRootSubscriptionFields AllowedFields `json:"subscription"`
}

OperationPermissions represents the user permissions for all operation types

func GetPermissionsFromContext

func GetPermissionsFromContext(ctx context.Context) (OperationPermissions, bool)

GetPermissionsFromContext returns the permissions stored in the context

func MergePermissions added in v1.1.3

func MergePermissions(perms ...OperationPermissions) OperationPermissions

MergePermissions merges the given permissions. The result permissions are the union of the given permissions (allow everything that is allowed in any of the given permissions).

func (*OperationPermissions) FilterAuthorizedFields

func (o *OperationPermissions) FilterAuthorizedFields(op *ast.OperationDefinition) gqlerror.List

FilterAuthorizedFields filters the operation's selection set and removes all fields that are not explicitly authorized. Every unauthorized field is returned as an error.

func (*OperationPermissions) FilterSchema

func (o *OperationPermissions) FilterSchema(schema *ast.Schema) *ast.Schema

FilterSchema returns a copy of the given schema stripped of any unauthorized fields and types

func (OperationPermissions) MarshalJSON

func (o OperationPermissions) MarshalJSON() ([]byte, error)

type PlanningContext

type PlanningContext struct {
	Operation  *ast.OperationDefinition
	Schema     *ast.Schema
	Locations  FieldURLMap
	IsBoundary map[string]bool
	Services   map[string]*Service
}

type Plugin

type Plugin interface {
	// ID must return the plugin identifier (name). This is the id used to match
	// the plugin in the configuration.
	ID() string
	// Configure is called during initialization and every time the config is modified.
	// The pluginCfg argument is the raw json contained in the "config" key for that plugin.
	Configure(cfg *Config, pluginCfg json.RawMessage) error
	// Init is called once on initialization
	Init(schema *ExecutableSchema)
	SetupPublicMux(mux *http.ServeMux)
	SetupPrivateMux(mux *http.ServeMux)
	// Should return true and the query path if the plugin is a service that
	// should be federated by Bramble
	GraphqlQueryPath() (bool, string)
	ApplyMiddlewarePublicMux(http.Handler) http.Handler
	ApplyMiddlewarePrivateMux(http.Handler) http.Handler
	ModifyExtensions(ctx context.Context, e *QueryExecution, extensions map[string]interface{}) error
}

type PluginConfig

type PluginConfig struct {
	Name   string
	Config json.RawMessage
}

type QueryExecution

type QueryExecution struct {
	Schema       *ast.Schema
	Errors       []*gqlerror.Error
	RequestCount int64
	// contains filtered or unexported fields
}

type Request

type Request struct {
	Query         string                 `json:"query"`
	OperationName string                 `json:"operationName,omitempty"`
	Variables     map[string]interface{} `json:"variables,omitempty"`
	Headers       http.Header            `json:"-"`
}

func NewRequest

func NewRequest(body string) *Request

type Response

type Response struct {
	Errors GraphqlErrors `json:"errors"`
	Data   interface{}
}

type Service

type Service struct {
	ServiceURL   string
	Name         string
	Version      string
	SchemaSource string
	Schema       *ast.Schema
	Status       string
	// contains filtered or unexported fields
}

Service ...

func NewService

func NewService(serviceURL string) *Service

func (*Service) Update

func (s *Service) Update() (bool, error)

Directories

Path Synopsis
cmd
examples
slow-service Module

Jump to

Keyboard shortcuts

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