Split out typed config from legacy command-line switches; refactor ingress commands and fix tests
This commit is contained in:
parent
eaf03305bd
commit
ca4887fb19
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue