From 3b385fb24e2818bde010302ee78b0ef6555e377a Mon Sep 17 00:00:00 2001 From: Kyle Hodgetts Date: Tue, 9 Nov 2021 18:28:30 +0200 Subject: [PATCH] Literal Rewrite Flag (#187) --- .gitignore | 1 + generators/ambassador/ambassador.go | 19 ++- generators/ambassador/mapping_template.go | 5 +- generators/ambassador/v1/ambassador_test.go | 145 +++++++++++++++++++ generators/ambassador/v1/mapping_template.go | 2 + generators/ambassador/v2/ambassador_test.go | 145 +++++++++++++++++++ generators/ambassador/v2/mapping_template.go | 2 + options/path.go | 4 + 8 files changed, 317 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 28ca7e8..d28805e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ kusk site/ dist/ +.vscode diff --git a/generators/ambassador/ambassador.go b/generators/ambassador/ambassador.go index 2deca1c..e2711bd 100644 --- a/generators/ambassador/ambassador.go +++ b/generators/ambassador/ambassador.go @@ -40,6 +40,12 @@ func (*AbstractGenerator) Flags() *pflag.FlagSet { "a prefix to trim from the URL before forwarding to the upstream Service", ) + fs.String( + "path.rewrite", + "", + "rewrite your base path before forwarding to the upstream service", + ) + fs.Bool( "path.split", false, @@ -91,10 +97,7 @@ func (a *AbstractGenerator) Generate(opts *options.Options, spec *openapi3.T) (s if shouldSplit(opts, spec) { // generate a mapping for each operation - basePath := opts.Path.Base - if basePath == "/" { - basePath = "" - } + basePath := strings.TrimSuffix(opts.Path.Base, "/") host := opts.Host @@ -118,12 +121,18 @@ func (a *AbstractGenerator) Generate(opts *options.Options, spec *openapi3.T) (s mappingPath, regex := generateMappingPath(path, operation) mappingName := generateMappingName(opts.Service.Name, method, path, operation) + var pathRewrite string + if opts.Path.RewriteBase != "" { + pathRewrite = strings.TrimSuffix(opts.Path.RewriteBase, "/") + mappingPath + } + op := mappingTemplateData{ MappingName: mappingName, MappingNamespace: opts.Namespace, ServiceURL: serviceURL, BasePath: basePath, TrimPrefix: opts.Path.TrimPrefix, + PathRewrite: pathRewrite, Method: method, Path: mappingPath, Regex: regex, @@ -211,6 +220,7 @@ func (a *AbstractGenerator) Generate(opts *options.Options, spec *openapi3.T) (s ServiceURL: serviceURL, BasePath: opts.Path.Base, TrimPrefix: opts.Path.TrimPrefix, + PathRewrite: strings.TrimSuffix(opts.Path.RewriteBase, "/"), RequestTimeout: opts.Timeouts.RequestTimeout * 1000, IdleTimeout: opts.Timeouts.IdleTimeout * 1000, Host: opts.Host, @@ -296,6 +306,7 @@ func (a *AbstractGenerator) Generate(opts *options.Options, spec *openapi3.T) (s // and whether the regex should be used func generateMappingPath(path string, op *openapi3.Operation) (string, bool) { containsPathParameter := false + for _, param := range op.Parameters { if param.Value.In == "path" { containsPathParameter = true diff --git a/generators/ambassador/mapping_template.go b/generators/ambassador/mapping_template.go index 26f5881..bb6fbcc 100644 --- a/generators/ambassador/mapping_template.go +++ b/generators/ambassador/mapping_template.go @@ -6,8 +6,9 @@ type mappingTemplateData struct { MappingNamespace string ServiceURL string - BasePath string - TrimPrefix string + BasePath string + TrimPrefix string + PathRewrite string Method string Path string diff --git a/generators/ambassador/v1/ambassador_test.go b/generators/ambassador/v1/ambassador_test.go index 7dea3f0..e94c8bd 100644 --- a/generators/ambassador/v1/ambassador_test.go +++ b/generators/ambassador/v1/ambassador_test.go @@ -1725,6 +1725,151 @@ spec: method: POST service: petstore.default:80 rewrite: "" +`, + }, + { + name: "rewrite literal specified at global level", + spec: ` +openapi: 3.0.1 +x-kusk: + namespace: booksapp + host: "*" + path: + base: /my-bookstore + rewrite_base: /bookstore/ + service: + name: webapp + namespace: booksapp + port: 7000 +paths: + /: + get: {} + + /books: + post: {} + + /books/{id}: + get: {} +`, + options: options.Options{ + Namespace: "booksapp", + Host: "*", + Path: options.PathOptions{ + Base: "/my-bookstore", + RewriteBase: "/bookstore/", + }, + Service: options.ServiceOptions{ + Namespace: "booksapp", + Name: "webapp", + Port: 7000, + }, + }, + res: ` +--- +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: webapp + namespace: booksapp +spec: + prefix: "/my-bookstore" + host: * + service: webapp.booksapp:7000 + rewrite: "/bookstore" +`, + }, + { + name: "rewrite literal specified at global level with path split", + spec: ` +openapi: 3.0.1 +x-kusk: + namespace: booksapp + host: "*" + path: + base: /my-bookstore/ + rewrite_base: /bookstore/ + service: + name: webapp + namespace: booksapp + port: 7000 +paths: + /: + get: {} + + /books: + post: {} + + /books/{id}: + get: + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + + /disabled-path: + x-kusk: + disabled: true + get: {} +`, + options: options.Options{ + Namespace: "booksapp", + Host: "*", + Path: options.PathOptions{ + Base: "/my-bookstore/", + RewriteBase: "/bookstore/", + }, + Service: options.ServiceOptions{ + Namespace: "booksapp", + Name: "webapp", + Port: 7000, + }, + PathSubOptions: map[string]options.SubOptions{ + "/disabled-path": { + Disabled: &trueValue, + }, + }, + }, + res: ` +--- +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: webapp-get + namespace: booksapp +spec: + prefix: "/my-bookstore/" + host: * + method: GET + service: webapp.booksapp:7000 + rewrite: "/bookstore/" +--- +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: webapp-getbooksid + namespace: booksapp +spec: + prefix: "/my-bookstore/books/([a-zA-Z0-9]*)" + prefix_regex: true + host: * + method: GET + service: webapp.booksapp:7000 + rewrite: "/bookstore/books/([a-zA-Z0-9]*)" +--- +apiVersion: getambassador.io/v2 +kind: Mapping +metadata: + name: webapp-postbooks + namespace: booksapp +spec: + prefix: "/my-bookstore/books" + host: * + method: POST + service: webapp.booksapp:7000 + rewrite: "/bookstore/books" `, }, } diff --git a/generators/ambassador/v1/mapping_template.go b/generators/ambassador/v1/mapping_template.go index aedd6b3..f8174e4 100644 --- a/generators/ambassador/v1/mapping_template.go +++ b/generators/ambassador/v1/mapping_template.go @@ -28,6 +28,8 @@ spec: regex_rewrite: pattern: '{{.TrimPrefix}}(.*)' substitution: '\1' + {{else if .PathRewrite}} + rewrite: "{{.PathRewrite}}" {{else}} rewrite: "" {{end}} diff --git a/generators/ambassador/v2/ambassador_test.go b/generators/ambassador/v2/ambassador_test.go index be9ffdc..f7ffd9e 100644 --- a/generators/ambassador/v2/ambassador_test.go +++ b/generators/ambassador/v2/ambassador_test.go @@ -1807,6 +1807,151 @@ spec: method: POST service: petstore.default:80 rewrite: "" +`, + }, + { + name: "rewrite literal specified at global level", + spec: ` +openapi: 3.0.1 +x-kusk: + namespace: booksapp + host: "*" + path: + base: /my-bookstore + rewrite_base: /bookstore/ + service: + name: webapp + namespace: booksapp + port: 7000 +paths: + /: + get: {} + + /books: + post: {} + + /books/{id}: + get: {} +`, + options: options.Options{ + Namespace: "booksapp", + Host: "*", + Path: options.PathOptions{ + Base: "/my-bookstore", + RewriteBase: "/bookstore/", + }, + Service: options.ServiceOptions{ + Namespace: "booksapp", + Name: "webapp", + Port: 7000, + }, + }, + res: ` +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: webapp + namespace: booksapp +spec: + prefix: "/my-bookstore" + hostname: '*' + service: webapp.booksapp:7000 + rewrite: "/bookstore" +`, + }, + { + name: "rewrite literal specified at global level with path split", + spec: ` +openapi: 3.0.1 +x-kusk: + namespace: booksapp + host: "*" + path: + base: /my-bookstore/ + rewrite_base: /bookstore/ + service: + name: webapp + namespace: booksapp + port: 7000 +paths: + /: + get: {} + + /books: + post: {} + + /books/{id}: + get: + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + + /disabled-path: + x-kusk: + disabled: true + get: {} +`, + options: options.Options{ + Namespace: "booksapp", + Host: "*", + Path: options.PathOptions{ + Base: "/my-bookstore/", + RewriteBase: "/bookstore/", + }, + Service: options.ServiceOptions{ + Namespace: "booksapp", + Name: "webapp", + Port: 7000, + }, + PathSubOptions: map[string]options.SubOptions{ + "/disabled-path": { + Disabled: &trueValue, + }, + }, + }, + res: ` +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: webapp-get + namespace: booksapp +spec: + prefix: "/my-bookstore/" + hostname: '*' + method: GET + service: webapp.booksapp:7000 + rewrite: "/bookstore/" +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: webapp-getbooksid + namespace: booksapp +spec: + prefix: "/my-bookstore/books/([a-zA-Z0-9]*)" + prefix_regex: true + hostname: '*' + method: GET + service: webapp.booksapp:7000 + rewrite: "/bookstore/books/([a-zA-Z0-9]*)" +--- +apiVersion: getambassador.io/v3alpha1 +kind: Mapping +metadata: + name: webapp-postbooks + namespace: booksapp +spec: + prefix: "/my-bookstore/books" + hostname: '*' + method: POST + service: webapp.booksapp:7000 + rewrite: "/bookstore/books" `, }, } diff --git a/generators/ambassador/v2/mapping_template.go b/generators/ambassador/v2/mapping_template.go index 6be46c8..18c15dc 100644 --- a/generators/ambassador/v2/mapping_template.go +++ b/generators/ambassador/v2/mapping_template.go @@ -28,6 +28,8 @@ spec: regex_rewrite: pattern: '{{.TrimPrefix}}(.*)' substitution: '\1' + {{else if .PathRewrite}} + rewrite: "{{.PathRewrite}}" {{else}} rewrite: "" {{end}} diff --git a/options/path.go b/options/path.go index 628482a..d15cfd8 100644 --- a/options/path.go +++ b/options/path.go @@ -15,6 +15,10 @@ type PathOptions struct { // is "/api/v3/pets". TrimPrefix string `yaml:"trim_prefix,omitempty" json:"trim_prefix,omitempty"` + // RewriteBase is the rewrite value that should replace the Base path before being forwarded to the + // upstream service + RewriteBase string `yaml:"rewrite_base,omitempty" json:"rewrite_base,omitempty"` + // Split forces Kusk to generate a separate resource for each Path or Operation, where appropriate. Split bool `yaml:"split,omitempty" json:"split,omitempty"` }