Skip to content

Commit a703321

Browse files
committed
feature: implement <forward>
For migration purposes it is useful that the operator can override the backend target. Users of routing tools like stackset https://github.com/zalando-incubator/stackset-controller can set the backend to something that the operators decide. Signed-off-by: Sandor Szücs <[email protected]>
1 parent eb5ed0f commit a703321

File tree

9 files changed

+731
-44
lines changed

9 files changed

+731
-44
lines changed

config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ type Config struct {
128128
RoutesFile string `yaml:"routes-file"`
129129
RoutesURLs *listFlag `yaml:"routes-urls"`
130130
InlineRoutes string `yaml:"inline-routes"`
131+
ForwardURL string `yaml:"forward-url"`
131132
AppendFilters *defaultFiltersFlags `yaml:"default-filters-append"`
132133
PrependFilters *defaultFiltersFlags `yaml:"default-filters-prepend"`
133134
DisabledFilters *listFlag `yaml:"disabled-filters"`
@@ -445,6 +446,7 @@ func NewConfig() *Config {
445446
flag.StringVar(&cfg.RoutesFile, "routes-file", "", "file containing route definitions")
446447
flag.Var(cfg.RoutesURLs, "routes-urls", "comma separated URLs to route definitions in eskip format")
447448
flag.StringVar(&cfg.InlineRoutes, "inline-routes", "", "inline routes in eskip format")
449+
flag.StringVar(&cfg.ForwardURL, "forward-url", "", "target url of the <forward> backend")
448450
flag.Int64Var(&cfg.SourcePollTimeout, "source-poll-timeout", int64(3000), "polling timeout of the routing data sources, in milliseconds")
449451
flag.Var(cfg.AppendFilters, "default-filters-append", "set of default filters to apply to append to all filters of all routes")
450452
flag.Var(cfg.PrependFilters, "default-filters-prepend", "set of default filters to apply to prepend to all filters of all routes")
@@ -861,6 +863,7 @@ func (c *Config) ToOptions() skipper.Options {
861863
WatchRoutesFile: c.RoutesFile,
862864
RoutesURLs: c.RoutesURLs.values,
863865
InlineRoutes: c.InlineRoutes,
866+
ForwardURL: c.ForwardURL,
864867
DefaultFilters: &eskip.DefaultFilters{
865868
Prepend: c.PrependFilters.filters,
866869
Append: c.AppendFilters.filters,

eskip/eskip.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package eskip
55
import (
66
"errors"
77
"fmt"
8+
"maps"
89
"math/rand"
910
"net/url"
1011
"regexp"
@@ -209,8 +210,31 @@ const (
209210
LoopBackend
210211
DynamicBackend
211212
LBBackend
213+
ForwardBackend
212214
)
213215

216+
type fwdBackend struct {
217+
target string
218+
}
219+
220+
func ForwardPreProcessor(s string) *fwdBackend {
221+
return &fwdBackend{
222+
target: s,
223+
}
224+
}
225+
226+
func (fb *fwdBackend) Do(routes []*Route) []*Route {
227+
for i := range routes {
228+
switch routes[i].BackendType {
229+
case ForwardBackend:
230+
routes[i].Backend = fb.target
231+
routes[i].BackendType = NetworkBackend
232+
}
233+
}
234+
235+
return routes
236+
}
237+
214238
var errMixedProtocols = errors.New("loadbalancer endpoints cannot have mixed protocols")
215239

216240
// Route definition used during the parser processes the raw routing
@@ -223,6 +247,7 @@ type parsedRoute struct {
223247
loopback bool
224248
dynamic bool
225249
lbBackend bool
250+
forward bool
226251
backend string
227252
lbAlgorithm string
228253
lbEndpoints []string
@@ -372,9 +397,7 @@ func (r *Route) Copy() *Route {
372397

373398
if len(r.Headers) > 0 {
374399
c.Headers = make(map[string]string)
375-
for k, v := range r.Headers {
376-
c.Headers[k] = v
377-
}
400+
maps.Copy(c.Headers, r.Headers)
378401
}
379402

380403
if len(r.HeaderRegexps) > 0 {
@@ -420,6 +443,8 @@ func BackendTypeFromString(s string) (BackendType, error) {
420443
return DynamicBackend, nil
421444
case "lb":
422445
return LBBackend, nil
446+
case "forward":
447+
return ForwardBackend, nil
423448
default:
424449
return -1, fmt.Errorf("unsupported backend type: %s", s)
425450
}
@@ -438,6 +463,8 @@ func (t BackendType) String() string {
438463
return "dynamic"
439464
case LBBackend:
440465
return "lb"
466+
case ForwardBackend:
467+
return "forward"
441468
default:
442469
return "unknown"
443470
}
@@ -578,6 +605,8 @@ func newRouteDefinition(r *parsedRoute) (*Route, error) {
578605
rd.BackendType = DynamicBackend
579606
case r.lbBackend:
580607
rd.BackendType = LBBackend
608+
case r.forward:
609+
rd.BackendType = ForwardBackend
581610
default:
582611
rd.BackendType = NetworkBackend
583612
}
@@ -630,7 +659,7 @@ func parse(start int, code string) ([]*parsedRoute, []*Predicate, []*Filter, err
630659
return lexer.routes, lexer.predicates, lexer.filters, lexer.err
631660
}
632661

633-
// Parses a route expression or a routing document to a set of route definitions.
662+
// Parse a route expression or a routing document to a set of route definitions.
634663
func Parse(code string) ([]*Route, error) {
635664
parsedRoutes, err := parseDocument(code)
636665
if err != nil {
@@ -680,7 +709,7 @@ func MustParseFilters(s string) []*Filter {
680709
return p
681710
}
682711

683-
// Parses a filter chain into a list of parsed filter definitions.
712+
// ParseFilters parses a filter chain into a list of parsed filter definitions.
684713
func ParseFilters(f string) ([]*Filter, error) {
685714
f = strings.TrimSpace(f)
686715
if f == "" {
@@ -726,7 +755,7 @@ const (
726755
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
727756
)
728757

729-
// generate weak random id for a route if
758+
// GenerateIfNeeded generates a weak random id for a route if
730759
// it doesn't have one.
731760
//
732761
// Deprecated: do not use, generate valid route id that matches [a-zA-Z_] yourself.

eskip/eskip_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ func TestParse(t *testing.T) {
166166
BackendType: DynamicBackend,
167167
}},
168168
"",
169+
}, {
170+
"forward",
171+
`* -> setRequestHeader("X-Foo", "bar") -> <forward>`,
172+
[]*Route{{
173+
Filters: []*Filter{
174+
{Name: "setRequestHeader", Args: []interface{}{"X-Foo", "bar"}},
175+
},
176+
BackendType: ForwardBackend,
177+
}},
178+
"",
169179
}, {
170180
"multiple routes",
171181
`r1: Path("/foo") -> <shunt>; r2: Path("/bar") -> "https://www.example.org"`,

eskip/example_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ package eskip_test
1717
import (
1818
"fmt"
1919
"log"
20+
"net/http"
21+
"net/http/httptest"
2022

2123
"github.com/zalando/skipper/eskip"
24+
"github.com/zalando/skipper/filters"
25+
"github.com/zalando/skipper/filters/builtin"
26+
"github.com/zalando/skipper/proxy/proxytest"
27+
"github.com/zalando/skipper/routing"
28+
"github.com/zalando/skipper/routing/testdataclient"
2229
)
2330

2431
func Example() {
@@ -220,3 +227,43 @@ func ExampleParseFilters() {
220227
// second filter, first arg: 3.14
221228
// second filter, second arg: Hello, world!
222229
}
230+
231+
func ExampleForwardBackend() {
232+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
233+
w.WriteHeader(http.StatusOK)
234+
}))
235+
defer backend.Close()
236+
237+
doc := `r: * -> <forward>;`
238+
routes := eskip.MustParse(doc)
239+
240+
spec := builtin.NewStatus()
241+
fr := make(filters.Registry)
242+
fr.Register(spec)
243+
244+
dc := testdataclient.New(routes)
245+
defer dc.Close()
246+
247+
proxy := proxytest.WithRoutingOptions(fr, routing.Options{
248+
DataClients: []routing.DataClient{dc},
249+
PreProcessors: []routing.PreProcessor{eskip.ForwardPreProcessor(backend.URL)},
250+
})
251+
defer proxy.Close()
252+
253+
client := proxy.Client()
254+
255+
rsp, err := client.Get("http://hello.world")
256+
if err != nil {
257+
fmt.Printf("Failed to GET hello.world: %v\n", err)
258+
return
259+
}
260+
defer rsp.Body.Close()
261+
if rsp.StatusCode != http.StatusOK {
262+
fmt.Printf("Failed to GET OK from http://hello.world, got: %v\n", rsp.StatusCode)
263+
return
264+
}
265+
fmt.Println("hello skipper")
266+
267+
// output:
268+
// hello skipper
269+
}

eskip/lexer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ var openarrowPrefixedTokens = []*fixedScanner{
5959
{shunt, "<shunt>"},
6060
{loopback, "<loopback>"},
6161
{dynamic, "<dynamic>"},
62+
{forward, "<forward>"},
6263
}
6364

6465
func (fs *fixedScanner) scan(code string) (t token, rest string, err error) {

0 commit comments

Comments
 (0)