Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAS server path as base path for matchers #45

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/oas3/petstore-with-kuadrant-extensions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ info:
- name: istio-ingressgateway
namespace: istio-system
servers:
- url: https://example.io/v1
- url: https://example.io/api/v1
paths:
/cat:
x-kuadrant: ## Path level Kuadrant Extension
Expand Down
11 changes: 8 additions & 3 deletions pkg/gatewayapi/http_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func HTTPRouteRulesFromOAS(doc *openapi3.T) []gatewayapiv1beta1.HTTPRouteRule {
// TODO(eguzki): consider about grouping operations as HTTPRouteMatch objects in fewer HTTPRouteRule objects
rules := make([]gatewayapiv1beta1.HTTPRouteRule, 0)

basePath, err := utils.BasePathFromOpenAPI(doc)
if err != nil {
panic(err)
}

// Paths
for path, pathItem := range doc.Paths {
kuadrantPathExtension, err := utils.NewKuadrantOASPathExtension(pathItem)
Expand Down Expand Up @@ -104,7 +109,7 @@ func HTTPRouteRulesFromOAS(doc *openapi3.T) []gatewayapiv1beta1.HTTPRouteRule {
backendRefs = kuadrantOperationExtension.BackendRefs
}

rules = append(rules, buildHTTPRouteRule(path, pathItem, verb, operation, backendRefs))
rules = append(rules, buildHTTPRouteRule(basePath, path, pathItem, verb, operation, backendRefs))
}
}

Expand Down Expand Up @@ -137,8 +142,8 @@ func ExtractLabelsFromOAS(doc *openapi3.T) (map[string]string, bool) {
return nil, false
}

func buildHTTPRouteRule(path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation, backendRefs []gatewayapiv1beta1.HTTPBackendRef) gatewayapiv1beta1.HTTPRouteRule {
match := utils.OpenAPIMatcherFromOASOperations(path, pathItem, verb, op)
func buildHTTPRouteRule(basePath, path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation, backendRefs []gatewayapiv1beta1.HTTPBackendRef) gatewayapiv1beta1.HTTPRouteRule {
match := utils.OpenAPIMatcherFromOASOperations(basePath, path, pathItem, verb, op)

return gatewayapiv1beta1.HTTPRouteRule{
BackendRefs: backendRefs,
Expand Down
25 changes: 13 additions & 12 deletions pkg/kuadrantapi/rate_limit_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ func RateLimitPolicyLimitsFromOAS(doc *openapi3.T) map[string]kuadrantapiv1beta2

limits := make(map[string]kuadrantapiv1beta2.Limit)

basePath, err := utils.BasePathFromOpenAPI(doc)
if err != nil {
panic(err)
}

// Paths
for path, pathItem := range doc.Paths {
kuadrantPathExtension, err := utils.NewKuadrantOASPathExtension(pathItem)
Expand Down Expand Up @@ -57,7 +62,12 @@ func RateLimitPolicyLimitsFromOAS(doc *openapi3.T) map[string]kuadrantapiv1beta2

limitName := utils.OpenAPIOperationName(path, verb, operation)

limits[limitName] = buildRateLimitPolicyLimit(path, pathItem, verb, operation, rateLimit)
limits[limitName] = kuadrantapiv1beta2.Limit{
RouteSelectors: buildLimitRouteSelectors(basePath, path, pathItem, verb, operation),
When: rateLimit.When,
Counters: rateLimit.Counters,
Rates: rateLimit.Rates,
}
}
}

Expand All @@ -68,17 +78,8 @@ func RateLimitPolicyLimitsFromOAS(doc *openapi3.T) map[string]kuadrantapiv1beta2
return limits
}

func buildRateLimitPolicyLimit(path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation, rateLimit *utils.KuadrantRateLimitExtension) kuadrantapiv1beta2.Limit {
return kuadrantapiv1beta2.Limit{
RouteSelectors: buildLimitRouteSelectors(path, pathItem, verb, op),
When: rateLimit.When,
Counters: rateLimit.Counters,
Rates: rateLimit.Rates,
}
}

func buildLimitRouteSelectors(path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation) []kuadrantapiv1beta2.RouteSelector {
match := utils.OpenAPIMatcherFromOASOperations(path, pathItem, verb, op)
func buildLimitRouteSelectors(basePath, path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation) []kuadrantapiv1beta2.RouteSelector {
match := utils.OpenAPIMatcherFromOASOperations(basePath, path, pathItem, verb, op)

return []kuadrantapiv1beta2.RouteSelector{
{
Expand Down
95 changes: 93 additions & 2 deletions pkg/utils/oas_utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package utils

import (
"bytes"
"fmt"
"html/template"
"net/url"
"regexp"

"github.com/getkin/kin-openapi/openapi3"
Expand All @@ -11,9 +14,97 @@ import (
var (
// NonWordCharRegexp not word characters (== [^0-9A-Za-z_])
NonWordCharRegexp = regexp.MustCompile(`\W`)
// TemplateRegexp used to render openapi server URLs
TemplateRegexp = regexp.MustCompile(`{([\w]+)}`)
// LastSlashRegexp matches the last slash
LastSlashRegexp = regexp.MustCompile(`/$`)
)

func OpenAPIMatcherFromOASOperations(path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation) gatewayapiv1beta1.HTTPRouteMatch {
func FirstServerFromOpenAPI(obj *openapi3.T) *openapi3.Server {
if obj == nil {
return nil
}

// take only first server
// From https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md
// If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /.
server := &openapi3.Server{
URL: `/`,
Variables: map[string]*openapi3.ServerVariable{},
}

// Current constraint: only read the first item when there are multiple servers
// Maybe this should be user provided setting
if len(obj.Servers) > 0 {
server = obj.Servers[0]
}

return server
}

func RenderOpenAPIServerURLStr(server *openapi3.Server) (string, error) {
if server == nil {
return "", nil
}

data := &struct {
Data map[string]string
}{
map[string]string{},
}

for variableName, variable := range server.Variables {
data.Data[variableName] = variable.Default
}

urlTemplate := TemplateRegexp.ReplaceAllString(server.URL, `{{ index .Data "$1" }}`)

tObj, err := template.New(server.URL).Parse(urlTemplate)
if err != nil {
return "", err
}

var tpl bytes.Buffer
err = tObj.Execute(&tpl, data)
if err != nil {
return "", err
}

return tpl.String(), nil
}

func RenderOpenAPIServerURL(server *openapi3.Server) (*url.URL, error) {
serverURLStr, err := RenderOpenAPIServerURLStr(server)
if err != nil {
return nil, err
}

serverURL, err := url.Parse(serverURLStr)
if err != nil {
return nil, err
}

return serverURL, nil
}

func BasePathFromOpenAPI(obj *openapi3.T) (string, error) {
server := FirstServerFromOpenAPI(obj)
serverURL, err := RenderOpenAPIServerURL(server)
if err != nil {
return "", err
}

return serverURL.Path, nil
}

func OpenAPIMatcherFromOASOperations(basePath, path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation) gatewayapiv1beta1.HTTPRouteMatch {

// remove the last slash of the Base Path
sanitizedBasePath := LastSlashRegexp.ReplaceAllString(basePath, "")

// According OAS 3.0: path MUST begin with a slash
matchPath := fmt.Sprintf("%s%s", sanitizedBasePath, path)

pathHeadersMatch := headersMatchFromParams(pathItem.Parameters)
operationHeadersMatch := headersMatchFromParams(op.Parameters)

Expand All @@ -37,7 +128,7 @@ func OpenAPIMatcherFromOASOperations(path string, pathItem *openapi3.PathItem, v
Path: &gatewayapiv1beta1.HTTPPathMatch{
// TODO(eguzki): consider other path match types like PathPrefix
Type: &[]gatewayapiv1beta1.PathMatchType{gatewayapiv1beta1.PathMatchExact}[0],
Value: &[]string{path}[0],
Value: &[]string{matchPath}[0],
},
Headers: headersMatch,
QueryParams: queryParams,
Expand Down
Loading