Split out typed config from legacy command-line switches; refactor ingress commands and fix tests

This commit is contained in:
Igor Postelnik 2020-10-20 09:29:13 -05:00
parent eaf03305bd
commit ca4887fb19
6 changed files with 326 additions and 347 deletions

View File

@ -197,22 +197,23 @@ func ValidateUrl(c *cli.Context, allowFromArgs bool) (string, error) {
return validUrl, err
}
func ReadIngressRules(config *ConfigFileSettings) (ingress.Ingress, error) {
return ingress.ParseIngress(config.Ingress)
}
type ConfigFileSettings struct {
type Configuration struct {
TunnelID string `yaml:"tunnel"`
Ingress ingress.UnvalidatedIngress `yaml:",inline"`
Settings map[string]interface{} `yaml:",inline"`
sourceFile string
}
func (c *ConfigFileSettings) Source() string {
type configFileSettings struct {
Configuration `yaml:",inline"`
// Existing settings will be aggregated in the generic map, should be read via cli.Context
Settings map[string]interface{} `yaml:",inline"`
}
func (c *Configuration) Source() string {
return c.sourceFile
}
func (c *ConfigFileSettings) Int(name string) (int, error) {
func (c *configFileSettings) Int(name string) (int, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.(int); ok {
return v, nil
@ -222,7 +223,7 @@ func (c *ConfigFileSettings) Int(name string) (int, error) {
return 0, nil
}
func (c *ConfigFileSettings) Duration(name string) (time.Duration, error) {
func (c *configFileSettings) Duration(name string) (time.Duration, error) {
if raw, ok := c.Settings[name]; ok {
switch v := raw.(type) {
case time.Duration:
@ -235,70 +236,70 @@ func (c *ConfigFileSettings) Duration(name string) (time.Duration, error) {
return 0, nil
}
func (c *ConfigFileSettings) Float64(name string) (float64, error) {
func (c *configFileSettings) Float64(name string) (float64, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.(float64); ok {
return v, nil
}
return 0, fmt.Errorf("expected int found %T for %s", raw, name)
return 0, fmt.Errorf("expected float found %T for %s", raw, name)
}
return 0, nil
}
func (c *ConfigFileSettings) String(name string) (string, error) {
func (c *configFileSettings) String(name string) (string, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.(string); ok {
return v, nil
}
return "", fmt.Errorf("expected int found %T for %s", raw, name)
return "", fmt.Errorf("expected string found %T for %s", raw, name)
}
return "", nil
}
func (c *ConfigFileSettings) StringSlice(name string) ([]string, error) {
func (c *configFileSettings) StringSlice(name string) ([]string, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.([]string); ok {
return v, nil
}
return nil, fmt.Errorf("expected int found %T for %s", raw, name)
return nil, fmt.Errorf("expected string slice found %T for %s", raw, name)
}
return nil, nil
}
func (c *ConfigFileSettings) IntSlice(name string) ([]int, error) {
func (c *configFileSettings) IntSlice(name string) ([]int, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.([]int); ok {
return v, nil
}
return nil, fmt.Errorf("expected int found %T for %s", raw, name)
return nil, fmt.Errorf("expected int slice found %T for %s", raw, name)
}
return nil, nil
}
func (c *ConfigFileSettings) Generic(name string) (cli.Generic, error) {
func (c *configFileSettings) Generic(name string) (cli.Generic, error) {
return nil, errors.New("option type Generic not supported")
}
func (c *ConfigFileSettings) Bool(name string) (bool, error) {
func (c *configFileSettings) Bool(name string) (bool, error) {
if raw, ok := c.Settings[name]; ok {
if v, ok := raw.(bool); ok {
return v, nil
}
return false, fmt.Errorf("expected int found %T for %s", raw, name)
return false, fmt.Errorf("expected boolean found %T for %s", raw, name)
}
return false, nil
}
var configuration ConfigFileSettings
var configuration configFileSettings
func GetConfiguration() *ConfigFileSettings {
return &configuration
func GetConfiguration() *Configuration {
return &configuration.Configuration
}
// ReadConfigFile returns InputSourceContext initialized from the configuration file.
// On repeat calls returns with the same file, returns without reading the file again; however,
// if value of "config" flag changes, will read the new config file
func ReadConfigFile(c *cli.Context, log logger.Service) (*ConfigFileSettings, error) {
func ReadConfigFile(c *cli.Context, log logger.Service) (*configFileSettings, error) {
configFile := c.String("config")
if configuration.Source() == configFile || configFile == "" {
return &configuration, nil

View File

@ -26,7 +26,6 @@ import (
"github.com/cloudflare/cloudflared/dbconnect"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/origin"
@ -167,37 +166,6 @@ func buildTunnelCommand(subcommands []*cli.Command) *cli.Command {
}
}
func buildIngressSubcommand() *cli.Command {
return &cli.Command{
Name: "ingress",
Category: "Tunnel",
Usage: "Validate and test cloudflared tunnel's ingress configuration",
Hidden: true,
Description: `
Cloudflared lets you route traffic from the internet to multiple different addresses on your
origin. Multiple-origin routing is configured by a set of rules. Each rule matches traffic
by its hostname or path, and routes it to an address. These rules are configured under the
'ingress' key of your config.yaml, for example:
ingress:
- hostname: www.example.com
service: https://localhost:8000
- hostname: *.example.xyz
path: /[a-zA-Z]+.html
service: https://localhost:8001
- hostname: *
service: https://localhost:8002
To ensure cloudflared can route all incoming requests, the last rule must be a catch-all
rule that matches all traffic. You can validate these rules with the 'ingress validate'
command, and test which rule matches a particular URL with 'ingress rule <URL>'.
Multiple-origin routing is incompatible with the --url flag.`,
Subcommands: []*cli.Command{buildValidateCommand(), buildRuleCommand()},
Flags: tunnelFlags(false),
}
}
func TunnelCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
@ -1239,81 +1207,3 @@ reconnect [delay]
}
}
}
func buildValidateCommand() *cli.Command {
return &cli.Command{
Name: "validate",
Action: cliutil.ErrorHandler(ValidateCommand),
Usage: "Validate the ingress configuration ",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
}
}
func buildRuleCommand() *cli.Command {
return &cli.Command{
Name: "rule",
Action: cliutil.ErrorHandler(RuleCommand),
Usage: "Check which ingress rule matches a given request URL",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress rule URL",
ArgsUsage: "URL",
Description: "Check which ingress rule matches a given request URL. " +
"Ingress rules match a request's hostname and path. Hostname is " +
"optional and is either a full hostname like `www.example.com` or a " +
"hostname with a `*` for its subdomains, e.g. `*.example.com`. Path " +
"is optional and matches a regular expression, like `/[a-zA-Z0-9_]+.html`",
}
}
// Validates the ingress rules in the cloudflared config file
func ValidateCommand(c *cli.Context) error {
logger, err := createLogger(c, false, false)
if err != nil {
return err
}
configFile, err := config.ReadConfigFile(c, logger)
if err != nil {
return err
}
fmt.Println("Validating rules from", configFile.Source())
_, err = config.ReadIngressRules(configFile)
if err != nil {
return errors.Wrap(err, "Validation failed")
}
if c.IsSet("url") {
return ingress.ErrURLIncompatibleWithIngress
}
fmt.Println("OK")
return nil
}
// Checks which ingress rule matches the given URL.
func RuleCommand(c *cli.Context) error {
logger, err := createLogger(c, false, false)
if err != nil {
return err
}
configFile, err := config.ReadConfigFile(c, logger)
if err != nil {
return err
}
rules, err := config.ReadIngressRules(configFile)
if err != nil {
return err
}
requestArg := c.Args().First()
if requestArg == "" {
return errors.New("cloudflared tunnel rule expects a single argument, the URL to test")
}
requestURL, err := url.Parse(requestArg)
if err != nil {
return fmt.Errorf("%s is not a valid URL", requestArg)
}
if requestURL.Hostname() == "" && requestURL.Scheme == "" {
return fmt.Errorf("%s doesn't have a hostname, consider adding a scheme", requestArg)
}
if requestURL.Hostname() == "" {
return fmt.Errorf("%s doesn't have a hostname", requestArg)
}
return ingress.RuleCommand(rules, requestURL)
}

View File

@ -231,7 +231,7 @@ func prepareTunnelConfig(
Version: version,
Arch: fmt.Sprintf("%s_%s", buildInfo.GoOS, buildInfo.GoArch),
}
ingressRules, err = config.ReadIngressRules(config.GetConfiguration())
ingressRules, err = ingress.ParseIngress(config.GetConfiguration().Ingress)
if err != nil && err != ingress.ErrNoIngressRules {
return nil, err
}

View File

@ -0,0 +1,110 @@
package tunnel
import (
"fmt"
"net/url"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress"
)
func buildIngressSubcommand() *cli.Command {
return &cli.Command{
Name: "ingress",
Category: "Tunnel",
Usage: "Validate and test cloudflared tunnel's ingress configuration",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress COMMAND [arguments...]",
Hidden: true,
Description: ` Cloudflared lets you route traffic from the internet to multiple different addresses on your
origin. Multiple-origin routing is configured by a set of rules. Each rule matches traffic
by its hostname or path, and routes it to an address. These rules are configured under the
'ingress' key of your config.yaml, for example:
ingress:
- hostname: www.example.com
service: https://localhost:8000
- hostname: *.example.xyz
path: /[a-zA-Z]+.html
service: https://localhost:8001
- hostname: *
service: https://localhost:8002
To ensure cloudflared can route all incoming requests, the last rule must be a catch-all
rule that matches all traffic. You can validate these rules with the 'ingress validate'
command, and test which rule matches a particular URL with 'ingress rule <URL>'.
Multiple-origin routing is incompatible with the --url flag.`,
Subcommands: []*cli.Command{buildValidateIngressCommand(), buildTestURLCommand()},
}
}
func buildValidateIngressCommand() *cli.Command {
return &cli.Command{
Name: "validate",
Action: cliutil.ErrorHandler(validateIngressCommand),
Usage: "Validate the ingress configuration ",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
}
}
func buildTestURLCommand() *cli.Command {
return &cli.Command{
Name: "rule",
Action: cliutil.ErrorHandler(testURLCommand),
Usage: "Check which ingress rule matches a given request URL",
UsageText: "cloudflared tunnel [--config FILEPATH] ingress rule URL",
ArgsUsage: "URL",
Description: "Check which ingress rule matches a given request URL. " +
"Ingress rules match a request's hostname and path. Hostname is " +
"optional and is either a full hostname like `www.example.com` or a " +
"hostname with a `*` for its subdomains, e.g. `*.example.com`. Path " +
"is optional and matches a regular expression, like `/[a-zA-Z0-9_]+.html`",
}
}
// validateIngressCommand check the syntax of the ingress rules in the cloudflared config file
func validateIngressCommand(c *cli.Context) error {
conf := config.GetConfiguration()
fmt.Println("Validating rules from", conf.Source())
if _, err := ingress.ParseIngress(conf.Ingress); err != nil {
return errors.Wrap(err, "Validation failed")
}
if c.IsSet("url") {
return ingress.ErrURLIncompatibleWithIngress
}
fmt.Println("OK")
return nil
}
// testURLCommand checks which ingress rule matches the given URL.
func testURLCommand(c *cli.Context) error {
requestArg := c.Args().First()
if requestArg == "" {
return errors.New("cloudflared tunnel rule expects a single argument, the URL to test")
}
requestURL, err := url.Parse(requestArg)
if err != nil {
return fmt.Errorf("%s is not a valid URL", requestArg)
}
if requestURL.Hostname() == "" && requestURL.Scheme == "" {
return fmt.Errorf("%s doesn't have a hostname, consider adding a scheme", requestArg)
}
conf := config.GetConfiguration()
fmt.Println("Using rules from", conf.Source())
ing, err := ingress.ParseIngress(conf.Ingress)
if err != nil {
return errors.Wrap(err, "Validation failed")
}
i := ing.FindMatchingRule(requestURL.Hostname(), requestURL.Path)
fmt.Printf("Matched rule #%d\n", i+1)
fmt.Println(ing.Rules[i].MultiLineString())
return nil
}

View File

@ -13,7 +13,6 @@ var (
ErrNoIngressRules = errors.New("No ingress rules were specified in the config file")
errLastRuleNotCatchAll = errors.New("The last ingress rule must match all hostnames (i.e. it must be missing, or must be \"*\")")
errBadWildcard = errors.New("Hostname patterns can have at most one wildcard character (\"*\") and it can only be used for subdomains, e.g. \"*.example.com\"")
errNoIngressRulesMatch = errors.New("The URL didn't match any ingress rules")
ErrURLIncompatibleWithIngress = errors.New("You can't set the --url flag (or $TUNNEL_URL) when using multiple-origin ingress rules")
)
@ -166,14 +165,3 @@ func ParseIngress(ing UnvalidatedIngress) (Ingress, error) {
}
return ing.validate()
}
// RuleCommand checks which ingress rule matches the given request URL.
func RuleCommand(ing Ingress, requestURL *url.URL) error {
if requestURL.Hostname() == "" {
return fmt.Errorf("%s is malformed and doesn't have a hostname", requestURL)
}
i := ing.FindMatchingRule(requestURL.Hostname(), requestURL.Path)
fmt.Printf("Matched rule #%d\n", i+1)
fmt.Println(ing.Rules[i].MultiLineString())
return nil
}

View File

@ -2,185 +2,165 @@ package ingress
import (
"net/url"
//"reflect"
"regexp"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
//func Test_parseIngress(t *testing.T) {
// localhost8000, err := url.Parse("https://localhost:8000")
// require.NoError(t, err)
// localhost8001, err := url.Parse("https://localhost:8001")
// require.NoError(t, err)
// type args struct {
// rawYAML string
// }
// tests := []struct {
// name string
// args args
// want Ingress
// wantErr bool
// }{
// {
// name: "Empty file",
// args: args{rawYAML: ""},
// wantErr: true,
// },
// {
// name: "Multiple rules",
// args: args{rawYAML: `
//ingress:
// - hostname: tunnel1.example.com
// service: https://localhost:8000
// - hostname: "*"
// service: https://localhost:8001
//`},
// want: Ingress{Rules: []Rule{
// {
// Hostname: "tunnel1.example.com",
// Service: localhost8000,
// },
// {
// Hostname: "*",
// Service: localhost8001,
// },
// }},
// },
// {
// name: "Extra keys",
// args: args{rawYAML: `
//ingress:
// - hostname: "*"
// service: https://localhost:8000
//extraKey: extraValue
//`},
// want: Ingress{Rules: []Rule{
// {
// Hostname: "*",
// Service: localhost8000,
// },
// }},
// },
// {
// name: "Hostname can be omitted",
// args: args{rawYAML: `
//ingress:
// - service: https://localhost:8000
//`},
// want: Ingress{Rules: []Rule{
// {
// Service: localhost8000,
// },
// }},
// },
// {
// name: "Invalid service",
// args: args{rawYAML: `
//ingress:
// - hostname: "*"
// service: https://local host:8000
//`},
// wantErr: true,
// },
// {
// name: "Invalid YAML",
// args: args{rawYAML: `
//key: "value
//`},
// wantErr: true,
// },
// {
// name: "Last rule isn't catchall",
// args: args{rawYAML: `
//ingress:
// - hostname: example.com
// service: https://localhost:8000
//`},
// wantErr: true,
// },
// {
// name: "First rule is catchall",
// args: args{rawYAML: `
//ingress:
// - service: https://localhost:8000
// - hostname: example.com
// service: https://localhost:8000
//`},
// wantErr: true,
// },
// {
// name: "Catch-all rule can't have a path",
// args: args{rawYAML: `
//ingress:
// - service: https://localhost:8001
// path: /subpath1/(.*)/subpath2
//`},
// wantErr: true,
// },
// {
// name: "Invalid regex",
// args: args{rawYAML: `
//ingress:
// - hostname: example.com
// service: https://localhost:8000
// path: "*/subpath2"
// - service: https://localhost:8001
//`},
// wantErr: true,
// },
// {
// name: "Service must have a scheme",
// args: args{rawYAML: `
//ingress:
// - service: localhost:8000
//`},
// wantErr: true,
// },
// {
// name: "Wildcard not at start",
// args: args{rawYAML: `
//ingress:
// - hostname: "test.*.example.com"
// service: https://localhost:8000
//`},
// wantErr: true,
// },
// {
// name: "Can't use --url",
// args: args{rawYAML: `
//url: localhost:8080
//ingress:
// - hostname: "*.example.com"
// service: https://localhost:8000
//`},
// wantErr: true,
// },
// {
// name: "Service can't have a path",
// args: args{rawYAML: `
//ingress:
// - service: https://localhost:8000/static/
//`},
// wantErr: true,
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// got, err := ParseIngress([]byte(tt.args.rawYAML))
// if (err != nil) != tt.wantErr {
// t.Errorf("ParseIngress() error = %v, wantErr %v", err, tt.wantErr)
// return
// }
// if !reflect.DeepEqual(got, tt.want) {
// t.Errorf("ParseIngress() = %v, want %v", got, tt.want)
// }
// })
// }
//}
func Test_parseIngress(t *testing.T) {
localhost8000 := MustParseURL(t, "https://localhost:8000")
localhost8001 := MustParseURL(t, "https://localhost:8001")
type args struct {
rawYAML string
}
tests := []struct {
name string
args args
want Ingress
wantErr bool
}{
{
name: "Empty file",
args: args{rawYAML: ""},
wantErr: true,
},
{
name: "Multiple rules",
args: args{rawYAML: `
ingress:
- hostname: tunnel1.example.com
service: https://localhost:8000
- hostname: "*"
service: https://localhost:8001
`},
want: Ingress{Rules: []Rule{
{
Hostname: "tunnel1.example.com",
Service: localhost8000,
},
{
Hostname: "*",
Service: localhost8001,
},
}},
},
{
name: "Extra keys",
args: args{rawYAML: `
ingress:
- hostname: "*"
service: https://localhost:8000
extraKey: extraValue
`},
want: Ingress{Rules: []Rule{
{
Hostname: "*",
Service: localhost8000,
},
}},
},
{
name: "Hostname can be omitted",
args: args{rawYAML: `
ingress:
- service: https://localhost:8000
`},
want: Ingress{Rules: []Rule{
{
Service: localhost8000,
},
}},
},
{
name: "Invalid service",
args: args{rawYAML: `
ingress:
- hostname: "*"
service: https://local host:8000
`},
wantErr: true,
},
{
name: "Last rule isn't catchall",
args: args{rawYAML: `
ingress:
- hostname: example.com
service: https://localhost:8000
`},
wantErr: true,
},
{
name: "First rule is catchall",
args: args{rawYAML: `
ingress:
- service: https://localhost:8000
- hostname: example.com
service: https://localhost:8000
`},
wantErr: true,
},
{
name: "Catch-all rule can't have a path",
args: args{rawYAML: `
ingress:
- service: https://localhost:8001
path: /subpath1/(.*)/subpath2
`},
wantErr: true,
},
{
name: "Invalid regex",
args: args{rawYAML: `
ingress:
- hostname: example.com
service: https://localhost:8000
path: "*/subpath2"
- service: https://localhost:8001
`},
wantErr: true,
},
{
name: "Service must have a scheme",
args: args{rawYAML: `
ingress:
- service: localhost:8000
`},
wantErr: true,
},
{
name: "Wildcard not at start",
args: args{rawYAML: `
ingress:
- hostname: "test.*.example.com"
service: https://localhost:8000
`},
wantErr: true,
},
{
name: "Service can't have a path",
args: args{rawYAML: `
ingress:
- service: https://localhost:8000/static/
`},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseIngress(MustReadIngress(tt.args.rawYAML))
if (err != nil) != tt.wantErr {
t.Errorf("ParseIngress() error = %v, wantErr %v", err, tt.wantErr)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func MustParse(t *testing.T, rawURL string) *url.URL {
func MustParseURL(t *testing.T, rawURL string) *url.URL {
u, err := url.Parse(rawURL)
require.NoError(t, err)
return u
@ -207,7 +187,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "example.com",
},
args: args{
requestURL: MustParse(t, "https://example.com"),
requestURL: MustParseURL(t, "https://example.com"),
},
want: true,
},
@ -217,7 +197,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "*",
},
args: args{
requestURL: MustParse(t, "https://example.com"),
requestURL: MustParseURL(t, "https://example.com"),
},
want: true,
},
@ -227,7 +207,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "example.com",
},
args: args{
requestURL: MustParse(t, "https://foo.bar"),
requestURL: MustParseURL(t, "https://foo.bar"),
},
want: false,
},
@ -237,7 +217,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "*.example.com",
},
args: args{
requestURL: MustParse(t, "https://adam.example.com"),
requestURL: MustParseURL(t, "https://adam.example.com"),
},
want: true,
},
@ -247,7 +227,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "*.example.com",
},
args: args{
requestURL: MustParse(t, "https://tunnel.com"),
requestURL: MustParseURL(t, "https://tunnel.com"),
},
want: false,
},
@ -257,7 +237,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "*example.com",
},
args: args{
requestURL: MustParse(t, "https://www.example.com"),
requestURL: MustParseURL(t, "https://www.example.com"),
},
want: false,
},
@ -267,7 +247,7 @@ func Test_rule_matches(t *testing.T) {
Hostname: "*.example.com",
},
args: args{
requestURL: MustParse(t, "https://adam.chalmers.example.com"),
requestURL: MustParseURL(t, "https://adam.chalmers.example.com"),
},
want: true,
},
@ -278,7 +258,7 @@ func Test_rule_matches(t *testing.T) {
Path: regexp.MustCompile("/static/.*\\.html"),
},
args: args{
requestURL: MustParse(t, "https://www.example.com/static/index.html"),
requestURL: MustParseURL(t, "https://www.example.com/static/index.html"),
},
want: true,
},
@ -298,23 +278,33 @@ func Test_rule_matches(t *testing.T) {
}
}
//func BenchmarkFindMatch(b *testing.B) {
// rulesYAML := `
//ingress:
// - hostname: tunnel1.example.com
// service: https://localhost:8000
// - hostname: tunnel2.example.com
// service: https://localhost:8001
// - hostname: "*"
// service: https://localhost:8002
//`
// ing, err := ParseIngress([]byte(rulesYAML))
// if err != nil {
// b.Error(err)
// }
// for n := 0; n < b.N; n++ {
// ing.FindMatchingRule("tunnel1.example.com", "")
// ing.FindMatchingRule("tunnel2.example.com", "")
// ing.FindMatchingRule("tunnel3.example.com", "")
// }
//}
func BenchmarkFindMatch(b *testing.B) {
rulesYAML := `
ingress:
- hostname: tunnel1.example.com
service: https://localhost:8000
- hostname: tunnel2.example.com
service: https://localhost:8001
- hostname: "*"
service: https://localhost:8002
`
ing, err := ParseIngress(MustReadIngress(rulesYAML))
if err != nil {
b.Error(err)
}
for n := 0; n < b.N; n++ {
ing.FindMatchingRule("tunnel1.example.com", "")
ing.FindMatchingRule("tunnel2.example.com", "")
ing.FindMatchingRule("tunnel3.example.com", "")
}
}
func MustReadIngress(s string) UnvalidatedIngress {
var ing UnvalidatedIngress
err := yaml.Unmarshal([]byte(s), &ing)
if err != nil {
panic(err)
}
return ing
}