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

config: allow multiple -inline-routes value #3383

Closed
wants to merge 1 commit into from
Closed
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
24 changes: 12 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type Config struct {
EtcdPassword string `yaml:"etcd-password"`
RoutesFile string `yaml:"routes-file"`
RoutesURLs *listFlag `yaml:"routes-urls"`
InlineRoutes string `yaml:"inline-routes"`
InlineRoutes multiFlag `yaml:"inline-routes"`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change for yaml-based configuration.

AppendFilters *defaultFiltersFlags `yaml:"default-filters-append"`
PrependFilters *defaultFiltersFlags `yaml:"default-filters-prepend"`
DisabledFilters *listFlag `yaml:"disabled-filters"`
Expand Down Expand Up @@ -434,7 +434,7 @@ func NewConfig() *Config {
flag.StringVar(&cfg.EtcdPassword, "etcd-password", "", "optional password for basic authentication with etcd")
flag.StringVar(&cfg.RoutesFile, "routes-file", "", "file containing route definitions")
flag.Var(cfg.RoutesURLs, "routes-urls", "comma separated URLs to route definitions in eskip format")
flag.StringVar(&cfg.InlineRoutes, "inline-routes", "", "inline routes in eskip format")
flag.Var(&cfg.InlineRoutes, "inline-routes", "inline routes in eskip format. Can be specified multiple times")
flag.Int64Var(&cfg.SourcePollTimeout, "source-poll-timeout", int64(3000), "polling timeout of the routing data sources, in milliseconds")
flag.Var(cfg.AppendFilters, "default-filters-append", "set of default filters to apply to append to all filters of all routes")
flag.Var(cfg.PrependFilters, "default-filters-prepend", "set of default filters to apply to prepend to all filters of all routes")
Expand Down Expand Up @@ -833,16 +833,16 @@ func (c *Config) ToOptions() skipper.Options {
SuppressRouteUpdateLogs: c.SuppressRouteUpdateLogs,

// route sources:
EtcdUrls: eus,
EtcdPrefix: c.EtcdPrefix,
EtcdWaitTimeout: c.EtcdTimeout,
EtcdInsecure: c.EtcdInsecure,
EtcdOAuthToken: c.EtcdOAuthToken,
EtcdUsername: c.EtcdUsername,
EtcdPassword: c.EtcdPassword,
WatchRoutesFile: c.RoutesFile,
RoutesURLs: c.RoutesURLs.values,
InlineRoutes: c.InlineRoutes,
EtcdUrls: eus,
EtcdPrefix: c.EtcdPrefix,
EtcdWaitTimeout: c.EtcdTimeout,
EtcdInsecure: c.EtcdInsecure,
EtcdOAuthToken: c.EtcdOAuthToken,
EtcdUsername: c.EtcdUsername,
EtcdPassword: c.EtcdPassword,
WatchRoutesFile: c.RoutesFile,
RoutesURLs: c.RoutesURLs.values,
InlineRoutesList: c.InlineRoutes,
DefaultFilters: &eskip.DefaultFilters{
Prepend: c.PrependFilters.filters,
Append: c.AppendFilters.filters,
Expand Down
15 changes: 15 additions & 0 deletions dataclients/routestring/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
package routestring

import (
"fmt"

"github.com/zalando/skipper/eskip"
"github.com/zalando/skipper/routing"
)
Expand All @@ -22,7 +24,20 @@ func New(r string) (routing.DataClient, error) {
if err != nil {
return nil, err
}
return &routes{parsed: parsed}, nil
}

// NewList creates a data client that parses a list of strings of eskip routes and
// serves it for the routing package.
func NewList(rs []string) (routing.DataClient, error) {
var parsed []*eskip.Route
for i, r := range rs {
pr, err := eskip.Parse(r)
if err != nil {
return nil, fmt.Errorf("#%d: %w", i, err)
}
parsed = append(parsed, pr...)
}
return &routes{parsed: parsed}, nil
}

Expand Down
95 changes: 95 additions & 0 deletions dataclients/routestring/string_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package routestring

import (
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zalando/skipper/eskip"
)

Expand Down Expand Up @@ -106,3 +109,95 @@ func TestRouteString(t *testing.T) {
})
}
}

func TestNewList(t *testing.T) {
for _, tc := range []struct {
defs []string
expected []*eskip.Route
}{
{
defs: nil,
expected: nil,
},
{
defs: []string{},
expected: nil,
},
{
defs: []string{""},
expected: nil,
},
{
defs: []string{`* -> static("/", "/var/www") -> <shunt>`},
expected: eskip.MustParse(`* -> static("/", "/var/www") -> <shunt>`),
},
{
defs: []string{
`* -> static("/", "/var/www") -> <shunt>`,
`Path("/foo") -> status(404) -> <shunt>`,
},
// multiple routes require route ids so use append instead of single eskip.MustParse
expected: append(
eskip.MustParse(`* -> static("/", "/var/www") -> <shunt>`),
eskip.MustParse(`Path("/foo") -> status(404) -> <shunt>`)...,
),
},
{
defs: []string{
`* -> static("/", "/var/www") -> <shunt>`,
`r1: Path("/foo") -> status(404) -> <shunt>;
r2: Path("/bar") -> status(404) -> <shunt>;`,
},
// multiple routes require route ids so use append instead of single eskip.MustParse
expected: append(
eskip.MustParse(`* -> static("/", "/var/www") -> <shunt>`),
eskip.MustParse(`r1: Path("/foo") -> status(404) -> <shunt>;
r2: Path("/bar") -> status(404) -> <shunt>;`)...,
),
},
} {
t.Run(strings.Join(tc.defs, ";"), func(t *testing.T) {
dc, err := NewList(tc.defs)
require.NoError(t, err)

routes, err := dc.LoadAll()
require.NoError(t, err)

if !cmp.Equal(tc.expected, routes) {
t.Errorf("invalid routes received\n %s", cmp.Diff(tc.expected, routes))
}
})
}
}

func TestNewListError(t *testing.T) {
for _, tc := range []struct {
defs []string
expected string
}{
{
defs: []string{`* ->`},
expected: "#0: parse failed after token ->, position 4: syntax error",
},
{
defs: []string{
`* -> <shunt>`,
`Path("/") ->`,
},
expected: "#1: parse failed after token ->, position 12: syntax error",
},
{
// multiple routes require route ids
defs: []string{
`Path("/foo") -> status(404) -> <shunt>;
Path("/bar") -> status(404) -> <shunt>;`,
},
expected: "#0: parse failed after token ;, position 39: syntax error",
},
} {
t.Run(strings.Join(tc.defs, ";"), func(t *testing.T) {
_, err := NewList(tc.defs)
assert.EqualError(t, err, tc.expected)
})
}
}
21 changes: 11 additions & 10 deletions fuzz/fuzz_targets/FuzzServer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/sirupsen/logrus"
"github.com/zalando/skipper"
"github.com/zalando/skipper/config"
)

var (
Expand Down Expand Up @@ -55,19 +54,21 @@ func run_server() {
os.Exit(-1)
}

cfg := config.NewConfig()
cfg.InlineRoutes = `r: * -> status(200) -> inlineContent("ok") -> <shunt>`
cfg.ApplicationLogLevel = logrus.PanicLevel
cfg.AccessLogDisabled = true
cfg.ApplicationLog = "/dev/null"
cfg.Address = addr
cfg.SupportListener = "127.0.0.1:0"
logrus.SetLevel(logrus.PanicLevel)

opts := skipper.Options{
InlineRoutes: `r: * -> status(200) -> inlineContent("ok") -> <shunt>`,
AccessLogDisabled: true,
ApplicationLogOutput: "/dev/null",
Address: addr,
SupportListener: "127.0.0.1:0",
}

go func() {
log.Fatal(skipper.Run(cfg.ToOptions()))
log.Fatal(skipper.Run(opts))
}()

address = cfg.Address
address = opts.Address
}

func FuzzServer(data []byte) int {
Expand Down
16 changes: 13 additions & 3 deletions skipper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os/signal"
"path"
"regexp"
"slices"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -322,9 +323,14 @@ type Options struct {
// RouteURLs are URLs pointing to route definitions, in eskip format, with change watching enabled.
RoutesURLs []string

// InlineRoutes can define routes as eskip text.
// InlineRoutes defines a set of routes as eskip format.
// Use InlineRoutesList to specify multiple sets of routes.
InlineRoutes string

// InlineRoutesList is similar to InlineRoutes used to specify multiple sets of routes.
// InlineRoutes and InlineRoutesList are combined and processed together.
InlineRoutesList []string

// Polling timeout of the routing data sources.
SourcePollTimeout time.Duration

Expand Down Expand Up @@ -1088,12 +1094,16 @@ func createDataClients(o Options, cr *certregistry.CertRegistry) ([]routing.Data
}
}

inlineRoutes := slices.Clone(o.InlineRoutesList)
if o.InlineRoutes != "" {
ir, err := routestring.New(o.InlineRoutes)
inlineRoutes = append(inlineRoutes, o.InlineRoutes)
}

if len(inlineRoutes) > 0 {
ir, err := routestring.NewList(inlineRoutes)
if err != nil {
return nil, fmt.Errorf("error while parsing inline routes: %w", err)
}

clients = append(clients, ir)
}

Expand Down
Loading