From ca4887fb19005d94c0e857d6d1b3aa0b0fecce2c Mon Sep 17 00:00:00 2001 From: Igor Postelnik Date: Tue, 20 Oct 2020 09:29:13 -0500 Subject: [PATCH] Split out typed config from legacy command-line switches; refactor ingress commands and fix tests --- cmd/cloudflared/config/configuration.go | 49 +-- cmd/cloudflared/tunnel/cmd.go | 110 ----- cmd/cloudflared/tunnel/configuration.go | 2 +- cmd/cloudflared/tunnel/ingress_subcommands.go | 110 +++++ ingress/ingress.go | 12 - ingress/ingress_test.go | 390 +++++++++--------- 6 files changed, 326 insertions(+), 347 deletions(-) create mode 100644 cmd/cloudflared/tunnel/ingress_subcommands.go diff --git a/cmd/cloudflared/config/configuration.go b/cmd/cloudflared/config/configuration.go index 11859aac..9d0cdd53 100644 --- a/cmd/cloudflared/config/configuration.go +++ b/cmd/cloudflared/config/configuration.go @@ -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 diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index ac7d7b3b..71aa8660 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -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 '. - - 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) -} diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index 2ede0d91..fa8c4711 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -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 } diff --git a/cmd/cloudflared/tunnel/ingress_subcommands.go b/cmd/cloudflared/tunnel/ingress_subcommands.go new file mode 100644 index 00000000..10bde06d --- /dev/null +++ b/cmd/cloudflared/tunnel/ingress_subcommands.go @@ -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 '. + + 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 +} diff --git a/ingress/ingress.go b/ingress/ingress.go index 6fb39c42..f5ad1452 100644 --- a/ingress/ingress.go +++ b/ingress/ingress.go @@ -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 -} diff --git a/ingress/ingress_test.go b/ingress/ingress_test.go index 628bd401..c388cd64 100644 --- a/ingress/ingress_test.go +++ b/ingress/ingress_test.go @@ -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 +}