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 return validUrl, err
} }
func ReadIngressRules(config *ConfigFileSettings) (ingress.Ingress, error) { type Configuration struct {
return ingress.ParseIngress(config.Ingress)
}
type ConfigFileSettings struct {
TunnelID string `yaml:"tunnel"` TunnelID string `yaml:"tunnel"`
Ingress ingress.UnvalidatedIngress `yaml:",inline"` Ingress ingress.UnvalidatedIngress `yaml:",inline"`
Settings map[string]interface{} `yaml:",inline"`
sourceFile string 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 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.(int); ok { if v, ok := raw.(int); ok {
return v, nil return v, nil
@ -222,7 +223,7 @@ func (c *ConfigFileSettings) Int(name string) (int, error) {
return 0, nil 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 { if raw, ok := c.Settings[name]; ok {
switch v := raw.(type) { switch v := raw.(type) {
case time.Duration: case time.Duration:
@ -235,70 +236,70 @@ func (c *ConfigFileSettings) Duration(name string) (time.Duration, error) {
return 0, nil 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.(float64); ok { if v, ok := raw.(float64); ok {
return v, nil 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 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.(string); ok { if v, ok := raw.(string); ok {
return v, nil 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 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.([]string); ok { if v, ok := raw.([]string); ok {
return v, nil 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 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.([]int); ok { if v, ok := raw.([]int); ok {
return v, nil 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 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") 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 raw, ok := c.Settings[name]; ok {
if v, ok := raw.(bool); ok { if v, ok := raw.(bool); ok {
return v, nil 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 return false, nil
} }
var configuration ConfigFileSettings var configuration configFileSettings
func GetConfiguration() *ConfigFileSettings { func GetConfiguration() *Configuration {
return &configuration return &configuration.Configuration
} }
// ReadConfigFile returns InputSourceContext initialized from the configuration file. // ReadConfigFile returns InputSourceContext initialized from the configuration file.
// On repeat calls returns with the same file, returns without reading the file again; however, // 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 // 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") configFile := c.String("config")
if configuration.Source() == configFile || configFile == "" { if configuration.Source() == configFile || configFile == "" {
return &configuration, nil return &configuration, nil

View File

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