Skip to content

Commit

Permalink
OAS server path as base path for matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
eguzki committed Nov 22, 2023
1 parent ffb7f05 commit 64db41b
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 18 deletions.
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

0 comments on commit 64db41b

Please sign in to comment.