-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add openapi spec parser and options
- Loading branch information
Kyle Hodgetts
authored
Oct 5, 2021
1 parent
5a5c56e
commit 9163747
Showing
14 changed files
with
753 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package options | ||
|
||
import ( | ||
validation "github.com/go-ozzo/ozzo-validation/v4" | ||
) | ||
|
||
type ClusterOptions struct { | ||
// ClusterDomain is the base DNS domain for the cluster. Default value is "cluster.local". | ||
ClusterDomain string `yaml:"cluster_domain,omitempty" json:"cluster_domain,omitempty"` | ||
} | ||
|
||
func (o *ClusterOptions) Validate() error { | ||
return validation.ValidateStruct(o, | ||
validation.Field(&o.ClusterDomain, validation.Required.Error("cluster_domain is required")), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package options | ||
|
||
import "reflect" | ||
|
||
type CORSOptions struct { | ||
Origins []string `yaml:"origins,omitempty" json:"origins,omitempty"` | ||
Methods []string `yaml:"methods,omitempty" json:"methods,omitempty"` | ||
Headers []string `yaml:"headers,omitempty" json:"headers,omitempty"` | ||
ExposeHeaders []string `yaml:"expose_headers,omitempty" json:"expose_headers,omitempty"` | ||
|
||
// Pointer because default value of bool is false which could have unintended side effects | ||
// Check if not nil to ensure it's been set by user | ||
Credentials *bool `yaml:"credentials,omitempty" json:"credentials,omitempty"` | ||
MaxAge int `yaml:"max_age,omitempty" json:"max_age,omitempty"` | ||
} | ||
|
||
func (o *Options) GetCORSOpts(path, method string) CORSOptions { | ||
// take global CORS options | ||
corsOpts := o.CORS | ||
|
||
// if non-zero path-level CORS options are different, override with them | ||
if pathSubOpts, ok := o.PathSubOptions[path]; ok { | ||
if !reflect.DeepEqual(CORSOptions{}, pathSubOpts.CORS) && | ||
!reflect.DeepEqual(corsOpts, pathSubOpts.CORS) { | ||
corsOpts = pathSubOpts.CORS | ||
} | ||
} | ||
|
||
// if non-zero operation-level CORS options are different, override them | ||
if opSubOpts, ok := o.OperationSubOptions[path]; ok { | ||
if !reflect.DeepEqual(CORSOptions{}, opSubOpts.CORS) && | ||
!reflect.DeepEqual(corsOpts, opSubOpts.CORS) { | ||
corsOpts = opSubOpts.CORS | ||
} | ||
} | ||
|
||
return corsOpts | ||
} | ||
|
||
func (o *CORSOptions) Validate() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package options | ||
|
||
type NGINXIngressOptions struct { | ||
// RewriteTarget is a custom rewrite target for ingress-nginx. | ||
// See https://kubernetes.github.io/ingress-nginx/examples/rewrite/ for additional documentation. | ||
RewriteTarget string `yaml:"rewrite_target,omitempty" json:"rewrite_target,omitempty"` | ||
} | ||
|
||
func (o *NGINXIngressOptions) Validate() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package options | ||
|
||
import ( | ||
v "github.com/go-ozzo/ozzo-validation/v4" | ||
) | ||
|
||
// SubOptions allow user to overwrite certain options at path/operation level | ||
// using x-kusk extension | ||
type SubOptions struct { | ||
Disabled *bool `yaml:"disabled,omitempty" json:"disabled,omitempty"` | ||
|
||
Host string `yaml:"host,omitempty" json:"host,omitempty"` | ||
CORS CORSOptions `yaml:"cors,omitempty" json:"cors,omitempty"` | ||
RateLimits RateLimitOptions `yaml:"rate_limits,omitempty" json:"rate_limits,omitempty"` | ||
Timeouts TimeoutOptions `yaml:"timeouts,omitempty" json:"timeouts,omitempty"` | ||
} | ||
|
||
type Options struct { | ||
Disabled bool `yaml:"disabled,omitempty" json:"disabled,omitempty"` | ||
|
||
// Namespace for the generated resource. Default value is "default". | ||
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` | ||
|
||
// Service is a set of options of a target service to receive traffic. | ||
Service ServiceOptions `yaml:"service,omitempty" json:"service,omitempty"` | ||
|
||
// Path is a set of options to configure service endpoints paths. | ||
Path PathOptions `yaml:"path,omitempty" json:"path,omitempty"` | ||
|
||
// Cluster is a set of cluster-wide options. | ||
Cluster ClusterOptions `yaml:"cluster,omitempty" json:"cluster,omitempty"` | ||
|
||
// Host is an ingress host rule. | ||
// See https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules for additional documentation. | ||
Host string `yaml:"host,omitempty" json:"host,omitempty"` | ||
|
||
CORS CORSOptions `yaml:"cors,omitempty" json:"cors,omitempty"` | ||
|
||
// NGINXIngress is a set of custom nginx-ingress options. | ||
NGINXIngress NGINXIngressOptions `yaml:"nginx_ingress,omitempty" json:"nginx_ingress,omitempty"` | ||
|
||
// PathSubOptions allow to overwrite specific subset of Options for a given path. | ||
// They are filled during extension parsing, the map key is path. | ||
PathSubOptions map[string]SubOptions `yaml:"-" json:"-"` | ||
|
||
// OperationSubOptions allow to overwrite specific subset of Options for a given operation. | ||
// They are filled during extension parsing, the map key is method+path. | ||
OperationSubOptions map[string]SubOptions `yaml:"-" json:"-"` | ||
|
||
RateLimits RateLimitOptions `yaml:"rate_limits,omitempty" json:"rate_limits,omitempty"` | ||
|
||
Timeouts TimeoutOptions `yaml:"timeouts,omitempty" json:"timeouts,omitempty"` | ||
} | ||
|
||
func (o *Options) fillDefaults() { | ||
if o.Namespace == "" { | ||
o.Namespace = "default" | ||
} | ||
|
||
if o.Path.Base == "" { | ||
o.Path.Base = "/" | ||
} | ||
|
||
if o.Cluster.ClusterDomain == "" { | ||
o.Cluster.ClusterDomain = "cluster.local" | ||
} | ||
|
||
if o.Service.Port == 0 { | ||
o.Service.Port = 80 | ||
} | ||
} | ||
|
||
func (o *Options) Validate() error { | ||
err := v.ValidateStruct(o, | ||
v.Field(&o.Namespace, v.Required.Error("Target namespace is required")), | ||
) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (o *Options) FillDefaultsAndValidate() error { | ||
o.fillDefaults() | ||
|
||
return v.Validate([]v.Validatable{ | ||
o, | ||
&o.Service, | ||
&o.Path, | ||
&o.Cluster, | ||
&o.CORS, | ||
&o.NGINXIngress, | ||
&o.RateLimits, | ||
&o.Timeouts, | ||
}) | ||
|
||
} | ||
|
||
func (o *Options) IsOperationDisabled(path, method string) bool { | ||
opSubOptions, ok := o.OperationSubOptions[method+path] | ||
|
||
// If the operation has an explicit value set, return that (takes precedence over the path level setting) | ||
if ok && opSubOptions.Disabled != nil { | ||
return *opSubOptions.Disabled | ||
} | ||
|
||
// No explicit value set for `Disabled` at the operation level, check the path level | ||
return o.IsPathDisabled(path) | ||
} | ||
|
||
func (o *Options) IsPathDisabled(path string) bool { | ||
pathSubOptions, ok := o.PathSubOptions[path] | ||
|
||
// If the path has an explicit value set, return that (takes precedence over the global level setting) | ||
if ok && pathSubOptions.Disabled != nil { | ||
return *pathSubOptions.Disabled | ||
} | ||
|
||
return o.Disabled | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package options | ||
|
||
import ( | ||
"github.com/go-ozzo/ozzo-validation/v4" | ||
) | ||
|
||
type PathOptions struct { | ||
// Base is the preceding prefix for the route (i.e. /your-prefix/here/rest/of/the/route). | ||
// Default value is "/". | ||
Base string `yaml:"base,omitempty" json:"base,omitempty"` | ||
|
||
// TrimPrefix is the prefix that would be omitted from the URL when request is being forwarded | ||
// to the upstream service, i.e. given that Base is set to "/petstore/api/v3", TrimPrefix is set to "/petstore", | ||
// path that would be generated is "/petstore/api/v3/pets", URL that the upstream service would receive | ||
// is "/api/v3/pets". | ||
TrimPrefix string `yaml:"trim_prefix,omitempty" json:"trim_prefix,omitempty"` | ||
|
||
// Split forces Kusk to generate a separate resource for each Path or Operation, where appropriate. | ||
Split bool `yaml:"split,omitempty" json:"split,omitempty"` | ||
} | ||
|
||
func (o *PathOptions) Validate() error { | ||
return validation.ValidateStruct(o, | ||
validation.Field(&o.Base, validation.Required.Error("Base path required")), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package options | ||
|
||
import "reflect" | ||
|
||
type RateLimitOptions struct { | ||
RPS uint32 `json:"rps,omitempty" yaml:"rps,omitempty"` | ||
Burst uint32 `json:"burst,omitempty" yaml:"burst,omitempty"` | ||
Group string `json:"group,omitempty" yaml:"group,omitempty"` | ||
} | ||
|
||
func (o *Options) GetRateLimitOpts(path, method string) RateLimitOptions { | ||
// take global rate limit options | ||
rateLimitOpts := o.RateLimits | ||
|
||
// if non-zero path-level rate limit options are different, override with them | ||
if pathSubOpts, ok := o.PathSubOptions[path]; ok && | ||
pathSubOpts.RateLimits.ShouldOverride(rateLimitOpts) { | ||
rateLimitOpts = pathSubOpts.RateLimits | ||
} | ||
|
||
// if non-zero operation-level rate limit options are different, override them | ||
if opSubOpts, ok := o.OperationSubOptions[path]; ok && | ||
opSubOpts.RateLimits.ShouldOverride(rateLimitOpts) { | ||
rateLimitOpts = opSubOpts.RateLimits | ||
} | ||
|
||
return rateLimitOpts | ||
} | ||
|
||
func (o *RateLimitOptions) ShouldOverride(opts RateLimitOptions) bool { | ||
return !reflect.DeepEqual(CORSOptions{}, o) && !reflect.DeepEqual(opts, o) | ||
} | ||
|
||
func (o *RateLimitOptions) Validate() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package options | ||
|
||
import ( | ||
v "github.com/go-ozzo/ozzo-validation/v4" | ||
) | ||
|
||
type ServiceOptions struct { | ||
// Namespace is the namespace containing the upstream Service. | ||
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` | ||
|
||
// Name is the upstream Service's name. | ||
Name string `yaml:"name,omitempty" json:"name,omitempty"` | ||
|
||
// Port is the upstream Service's port. Default value is 80. | ||
Port int32 `yaml:"port,omitempty" json:"port,omitempty"` | ||
} | ||
|
||
func (o *ServiceOptions) Validate() error { | ||
return v.ValidateStruct(o, | ||
v.Field(&o.Namespace, v.Required.Error("service.namespace is required")), | ||
v.Field(&o.Name, v.Required.Error("service.name is required")), | ||
v.Field(&o.Port, v.Required.Error("service.port is required"), v.Min(1), v.Max(65535)), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package options | ||
|
||
import "reflect" | ||
|
||
type TimeoutOptions struct { | ||
// RequestTimeout is total request timeout | ||
RequestTimeout uint32 `yaml:"request_timeout,omitempty" json:"request_timeout,omitempty"` | ||
// IdleTimeout is timeout for idle connection | ||
IdleTimeout uint32 `yaml:"idle_timeout,omitempty" json:"idle_timeout,omitempty"` | ||
} | ||
|
||
func (o *Options) GetTimeoutOpts(path, method string) TimeoutOptions { | ||
// take global timeout options | ||
timeoutOpts := o.Timeouts | ||
|
||
// if non-zero path-level timeout options are different, override them | ||
if pathSubOpts, ok := o.PathSubOptions[path]; ok { | ||
if !reflect.DeepEqual(TimeoutOptions{}, pathSubOpts.Timeouts) && | ||
!reflect.DeepEqual(timeoutOpts, pathSubOpts.Timeouts) { | ||
timeoutOpts = pathSubOpts.Timeouts | ||
} | ||
} | ||
|
||
// if non-zero operation-level timeout options are different, override them | ||
if opSubOpts, ok := o.OperationSubOptions[path]; ok { | ||
if !reflect.DeepEqual(TimeoutOptions{}, opSubOpts.Timeouts) && | ||
!reflect.DeepEqual(timeoutOpts, opSubOpts.Timeouts) { | ||
timeoutOpts = opSubOpts.Timeouts | ||
} | ||
} | ||
|
||
return timeoutOpts | ||
} | ||
|
||
func (o *TimeoutOptions) Validate() error { | ||
return nil | ||
} |
Oops, something went wrong.