From 9909e9d63c8275ccd255e2b292932a091248ebcc Mon Sep 17 00:00:00 2001 From: Sudarsan Reddy Date: Tue, 22 Feb 2022 15:51:43 +0000 Subject: [PATCH] TUN-5754: Allow ingress validate to take plaintext option Ingress validate currently validates config from a file. This PR adds a new --json/-j flag to provide the ingress/config data as a plaintext command line argument. --- cmd/cloudflared/tunnel/ingress_subcommands.go | 36 +++++++++++++-- .../tunnel/subcommand_context_test.go | 46 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/cmd/cloudflared/tunnel/ingress_subcommands.go b/cmd/cloudflared/tunnel/ingress_subcommands.go index 22a5944b..cc55e7a1 100644 --- a/cmd/cloudflared/tunnel/ingress_subcommands.go +++ b/cmd/cloudflared/tunnel/ingress_subcommands.go @@ -1,6 +1,7 @@ package tunnel import ( + "encoding/json" "fmt" "net/url" @@ -12,6 +13,15 @@ import ( "github.com/urfave/cli/v2" ) +const ingressDataJSONFlagName = "json" + +var ingressDataJSON = &cli.StringFlag{ + Name: ingressDataJSONFlagName, + Aliases: []string{"j"}, + Usage: `Accepts data in the form of json as an input rather than read from a file`, + EnvVars: []string{"TUNNEL_INGRESS_VALIDATE_JSON"}, +} + func buildIngressSubcommand() *cli.Command { return &cli.Command{ Name: "ingress", @@ -49,6 +59,7 @@ func buildValidateIngressCommand() *cli.Command { Usage: "Validate the ingress configuration ", UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate", Description: "Validates the configuration file, ensuring your ingress rules are OK.", + Flags: []cli.Flag{ingressDataJSON}, } } @@ -69,12 +80,11 @@ func buildTestURLCommand() *cli.Command { // validateIngressCommand check the syntax of the ingress rules in the cloudflared config file func validateIngressCommand(c *cli.Context, warnings string) error { - conf := config.GetConfiguration() - if conf.Source() == "" { - fmt.Println("No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files") - return nil + conf, err := getConfiguration(c) + if err != nil { + return err } - fmt.Println("Validating rules from", conf.Source()) + if _, err := ingress.ParseIngress(conf); err != nil { return errors.Wrap(err, "Validation failed") } @@ -90,6 +100,22 @@ func validateIngressCommand(c *cli.Context, warnings string) error { return nil } +func getConfiguration(c *cli.Context) (*config.Configuration, error) { + var conf *config.Configuration + if c.IsSet(ingressDataJSONFlagName) { + ingressJSON := c.String(ingressDataJSONFlagName) + fmt.Println("Validating rules from cmdline flag --json") + err := json.Unmarshal([]byte(ingressJSON), &conf) + return conf, err + } + conf = config.GetConfiguration() + if conf.Source() == "" { + return nil, errors.New("No configuration file was found. Please create one, or use the --config flag to specify its filepath. You can use the help command to learn more about configuration files") + } + fmt.Println("Validating rules from", conf.Source()) + return conf, nil +} + // testURLCommand checks which ingress rule matches the given URL. func testURLCommand(c *cli.Context) error { requestArg := c.Args().First() diff --git a/cmd/cloudflared/tunnel/subcommand_context_test.go b/cmd/cloudflared/tunnel/subcommand_context_test.go index 61a1e68b..b9a7108e 100644 --- a/cmd/cloudflared/tunnel/subcommand_context_test.go +++ b/cmd/cloudflared/tunnel/subcommand_context_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" "github.com/cloudflare/cloudflared/cfapi" @@ -322,3 +323,48 @@ func Test_subcommandContext_Delete(t *testing.T) { }) } } + +func Test_subcommandContext_ValidateIngressCommand(t *testing.T) { + var tests = []struct { + name string + c *cli.Context + wantErr bool + expectedErr error + }{ + { + name: "read a valid configuration from data", + c: func() *cli.Context { + data := `{ "warp-routing": {"enabled": true}, "originRequest" : {"connectTimeout": 10}, "ingress" : [ {"hostname": "test", "service": "https://localhost:8000" } , {"service": "http_status:404"} ]}` + flagSet := flag.NewFlagSet("json", flag.PanicOnError) + flagSet.String(ingressDataJSONFlagName, data, "") + c := cli.NewContext(cli.NewApp(), flagSet, nil) + _ = c.Set(ingressDataJSONFlagName, data) + return c + }(), + }, + { + name: "read an invalid configuration with multiple mistakes", + c: func() *cli.Context { + data := `{ "ingress" : [ {"hostname": "test", "service": "localhost:8000" } , {"service": "http_status:invalid_status"} ]}` + flagSet := flag.NewFlagSet("json", flag.PanicOnError) + flagSet.String(ingressDataJSONFlagName, data, "") + c := cli.NewContext(cli.NewApp(), flagSet, nil) + _ = c.Set(ingressDataJSONFlagName, data) + return c + }(), + wantErr: true, + expectedErr: errors.New("Validation failed: localhost:8000 is an invalid address, please make sure it has a scheme and a hostname"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateIngressCommand(tt.c, "") + if tt.wantErr { + assert.Equal(t, tt.expectedErr.Error(), err.Error()) + } else { + assert.Nil(t, err) + } + }) + } +}