Skip to content

Commit

Permalink
Merge pull request #558 from uber/flexible-default-middleware
Browse files Browse the repository at this point in the history
Support for flexible default middleware
  • Loading branch information
Ryanfsdf committed Feb 12, 2019
2 parents a21d589 + c5b7d2c commit 7abaf91
Show file tree
Hide file tree
Showing 94 changed files with 2,413 additions and 211 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ example-gateway # root directory
│   ├── endpoints # generated mocks and module initializers for endpoints
│   ├── gen-code # generated structs and (de)serializers by Thrift compiler
│   ├── middlewares # generated module initializers for middlewares
│   │ └── default # generated module initializers for default middlewares
│   └── services # generated mocks and module intialziers for services
├── build.yaml # config file for Zanzibar code generation, see below for details
├── clients # config directory for modules of client module class
Expand All @@ -142,7 +143,10 @@ example-gateway # root directory
│   ├── clients # idl directory for client thrift files
│   └── endpoints # idl directory for endpoint thrift files
├── middlewares # config directory for modules of middleware module class
│   └── transform-response # config directory for a middleware named 'transform-response'
│   ├── transform-response # config directory for a middleware named 'transform-response'
│   ├── default # directory for all default middlewares
│   │ └── log-publisher # config directory for a default middleware named 'log-publisher'
│   └── default.yaml # config file describing default middlewares and their execution order
└── services # config directory for modules of service module class
└── example-gateway # config directory for a service named 'example-gateway'
```
Expand Down Expand Up @@ -289,6 +293,12 @@ Besides the module configs, Zanzibar also expects a YAML file that configures ne

Unlike the module configs, there is no restriction on how this config file should be named. It can be named `{$appName}.yaml` or `build.yaml` as it is in [example-gateway](https://github.com/uber/zanzibar/blob/master/examples/example-gateway/build.yaml), as long as it is passed correctly as an argument to the code generation [runner](https://github.com/uber/zanzibar/blob/master/codegen/runner/runner.go).

In this config file, you can specify the paths from which to discover modules. You can also specify `default dependencies`.

`Default Dependencies` allow module classes to include instances of other module classes as default dependencies. This means that no explicit configurations are required for certain module instances to be included as a dependency. e.g., we can include `clients/logger` as a default dependency for `endpoint`, and every endpoint will have `clients/logger` as a dependency in its `module/dependencies.go` file, even if the endpoint's `endpoint-config.yaml` file does not list `clients/logger` as a dependency.

Note that these paths support `Glob` patterns.

### Code Generation
Zanzibar provides HTTP and TChannel runtime components for both clients and servers. Once all the configs are properly defined, Zanzibar is able to parse the config files and generate code and wire it up with the runime components to produce a full application. All generated code is placed in the `build` directory.

Expand Down
87 changes: 83 additions & 4 deletions codegen/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,70 @@ func NewEndpointSpec(
ClientMethod: clientMethod,
}

return augmentEndpointSpec(espec, endpointConfigObj, midSpecs)
defaultMidSpecs, err := getOrderedDefaultMiddlewareSpecs(
h.ConfigRoot(),
h.DefaultMiddlewareSpecs(),
endpointType.(string))
if err != nil {
return nil, errors.Wrap(
err, "error getting ordered default middleware specs",
)
}

return augmentEndpointSpec(espec, endpointConfigObj, midSpecs, defaultMidSpecs)
}

func getOrderedDefaultMiddlewareSpecs(
cfgDir string,
middlewareSpecs map[string]*MiddlewareSpec,
classType string,
) ([]MiddlewareSpec, error) {
middlewareObj := map[string][]string{}

middlewareOrderingFile := filepath.Join(cfgDir, "middlewares/default.yaml")
if _, err := os.Stat(middlewareOrderingFile); os.IsNotExist(err) {
// Cannot find yaml file, use json file instead
middlewareOrderingFile = filepath.Join(cfgDir, "middlewares/default.json")
if _, err := os.Stat(middlewareOrderingFile); os.IsNotExist(err) {
// This file is not required so it is okay to skip
return nil, nil
}
}

bytes, err := ioutil.ReadFile(middlewareOrderingFile)
if err != nil {
return nil, errors.Wrapf(
err, "could not read default middleware ordering file: %s", middlewareOrderingFile,
)
}
err = yaml.Unmarshal(bytes, &middlewareObj)
if err != nil {
return nil, errors.Wrapf(
err, "could not parse default middleware ordering file: %s", middlewareOrderingFile,
)
}
middlewareOrderingObj := middlewareObj[classType]

return sortByMiddlewareOrdering(middlewareOrderingObj, middlewareSpecs)
}

// sortByMiddlewareOrdering sorts middlewareSpecs using the ordering from middlewareOrderingObj
func sortByMiddlewareOrdering(
middlewareOrderingObj []string,
middlewareSpecs map[string]*MiddlewareSpec,
) ([]MiddlewareSpec, error) {
middlewares := make([]MiddlewareSpec, 0)

for _, middlewareName := range middlewareOrderingObj {
middlewareSpec, ok := middlewareSpecs[middlewareName]
if !ok {
return nil, errors.Errorf("could not find middleware %s", middlewareName)
}

middlewares = append(middlewares, *middlewareSpec)
}

return middlewares, nil
}

func testFixtures(endpointConfigObj map[interface{}]interface{}) (map[string]*EndpointTestFixture, error) {
Expand Down Expand Up @@ -602,15 +665,18 @@ func augmentEndpointSpec(
espec *EndpointSpec,
endpointConfigObj map[interface{}]interface{},
midSpecs map[string]*MiddlewareSpec,
defaultMidSpecs []MiddlewareSpec,
) (*EndpointSpec, error) {
middlewares := defaultMidSpecs

if _, ok := endpointConfigObj["middlewares"]; ok {
endpointMids, ok := endpointConfigObj["middlewares"].([]interface{})
if !ok {
return nil, errors.Errorf(
"Unable to parse middlewares field",
)
}
var middlewares []MiddlewareSpec

for _, middleware := range endpointMids {
middlewareObj, ok := middleware.(map[interface{}]interface{})
if !ok {
Expand Down Expand Up @@ -711,10 +777,10 @@ func augmentEndpointSpec(
PrettyOptions: prettyOpts,
})
}

espec.Middlewares = middlewares
}

espec.Middlewares = middlewares

if "http" == endpointConfigObj["endpointType"] {
testFixtures, err := testFixtures(endpointConfigObj)
if err != nil {
Expand Down Expand Up @@ -917,6 +983,19 @@ func parseEndpointYamls(
return endpointYamls, nil
}

func parseDefaultMiddlewareConfig(
defaultMiddlewareConfigDir string,
configDirName string,
) (map[string]*MiddlewareSpec, error) {
fullMiddlewareDir := filepath.Join(configDirName, defaultMiddlewareConfigDir)
_, err := ioutil.ReadDir(fullMiddlewareDir)
if err != nil {
return nil, nil
}

return parseMiddlewareConfig(defaultMiddlewareConfigDir, configDirName)
}

func parseMiddlewareConfig(
middlewareConfigDir string,
configDirName string,
Expand Down
Loading

0 comments on commit 7abaf91

Please sign in to comment.