TUN-3452: Fix loading of flags from config file for tunnel run subcommand. This change also cleans up building of tunnel subcommand list, hides deprecated subcommands and improves help.

This commit is contained in:
Igor Postelnik 2020-10-09 12:07:08 -05:00
parent 86a7af3dc4
commit 53a1fa46a8
6 changed files with 127 additions and 120 deletions

View File

@ -9,7 +9,6 @@ import (
homedir "github.com/mitchellh/go-homedir"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/validation"
@ -91,14 +90,6 @@ func FileExists(path string) (bool, error) {
return true, nil
}
// FindInputSourceContext pulls the input source from the config flag.
func FindInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, error) {
if context.String("config") != "" {
return altsrc.NewYamlSourceFromFile(context.String("config"))
}
return nil, nil
}
// FindDefaultConfigPath returns the first path that contains a config file.
// If none of the combination of DefaultConfigSearchDirectories() and DefaultConfigFiles
// contains a config file, return empty string.

View File

@ -13,6 +13,7 @@ import (
log "github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/overwatch"
"github.com/cloudflare/cloudflared/tunneldns"
"github.com/cloudflare/cloudflared/watcher"
raven "github.com/getsentry/raven-go"
@ -76,7 +77,7 @@ func main() {
and configure access control.`
app.Flags = flags()
app.Action = action(Version, shutdownC, graceShutdownC)
app.Before = tunnel.Before
app.Before = tunnel.SetFlagsFromConfigFile
app.Commands = commands(cli.ShowVersion)
tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command...
@ -129,6 +130,7 @@ To determine if an update happened in a script, check for error code 64.`,
},
}
cmds = append(cmds, tunnel.Commands()...)
cmds = append(cmds, tunneldns.Command(false))
cmds = append(cmds, access.Commands()...)
return cmds
}

View File

@ -113,87 +113,33 @@ func Flags() []cli.Flag {
}
func Commands() []*cli.Command {
cmds := []*cli.Command{
{
Name: "login",
Action: cliutil.ErrorHandler(login),
Usage: "Generate a configuration file with your login details",
ArgsUsage: " ",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Hidden: true,
},
},
Hidden: true,
},
{
Name: "proxy-dns",
Action: cliutil.ErrorHandler(tunneldns.Run),
Usage: "Run a DNS over HTTPS proxy server.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "metrics",
Value: "localhost:",
Usage: "Listen address for metrics reporting.",
EnvVars: []string{"TUNNEL_METRICS"},
},
&cli.StringFlag{
Name: "address",
Usage: "Listen address for the DNS over HTTPS proxy server.",
Value: "localhost",
EnvVars: []string{"TUNNEL_DNS_ADDRESS"},
},
&cli.IntFlag{
Name: "port",
Usage: "Listen on given port for the DNS over HTTPS proxy server.",
Value: 53,
EnvVars: []string{"TUNNEL_DNS_PORT"},
},
&cli.StringSliceFlag{
Name: "upstream",
Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
},
&cli.StringSliceFlag{
Name: "bootstrap",
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
},
},
ArgsUsage: " ", // can't be the empty string or we get the default output
Hidden: false,
},
subcommands := []*cli.Command{
buildLoginSubcommand(false),
buildCreateCommand(),
buildRouteCommand(),
buildRunCommand(),
buildListCommand(),
buildIngressSubcommand(),
buildDeleteCommand(),
buildCleanupCommand(),
// for compatibility, allow following as tunnel subcommands
tunneldns.Command(true),
dbConnectCmd(),
}
var subcommands []*cli.Command
for _, cmd := range cmds {
c := *cmd
c.Hidden = false
subcommands = append(subcommands, &c)
return []*cli.Command {
buildTunnelCommand(subcommands),
// for compatibility, allow following as top-level subcommands
buildLoginSubcommand(true),
dbConnectCmd(),
}
subcommands = append(subcommands, buildCreateCommand())
subcommands = append(subcommands, buildListCommand())
subcommands = append(subcommands, buildDeleteCommand())
subcommands = append(subcommands, buildRunCommand())
subcommands = append(subcommands, buildCleanupCommand())
subcommands = append(subcommands, buildRouteCommand())
subcommands = append(subcommands, buildIngressSubcommand())
cmds = append(cmds, buildTunnelCommand(subcommands))
return cmds
}
func buildTunnelCommand(subcommands []*cli.Command) *cli.Command {
return &cli.Command{
Name: "tunnel",
Action: cliutil.ErrorHandler(TunnelCommand),
Before: Before,
Before: SetFlagsFromConfigFile,
Category: "Tunnel",
Usage: "Make a locally-running web service accessible over the internet using Argo Tunnel.",
ArgsUsage: " ",
@ -245,8 +191,7 @@ func buildIngressSubcommand() *cli.Command {
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.
`,
Multiple-origin routing is incompatible with the --url flag.`,
Subcommands: []*cli.Command{buildValidateCommand(), buildRuleCommand()},
Flags: tunnelFlags(false),
}
@ -624,26 +569,34 @@ func forceSetFlag(c *cli.Context, name, value string) {
}
}
func Before(c *cli.Context) error {
func SetFlagsFromConfigFile(c *cli.Context) error {
logger, err := createLogger(c, false, false)
if err != nil {
return cliutil.PrintLoggerSetupError("error setting up logger", err)
}
if c.String("config") == "" {
configFile := c.String("config")
if configFile == "" {
logger.Debugf(config.ErrNoConfigFile.Error())
return nil
}
inputSource, err := config.FindInputSourceContext(c)
inputSource, err := altsrc.NewYamlSourceFromFile(configFile)
if err != nil {
logger.Errorf("Cannot load configuration from %s: %s", c.String("config"), err)
logger.Errorf("Cannot load configuration from %s: %s", configFile, err)
return err
} else if inputSource != nil {
err := altsrc.ApplyInputSourceValues(c, inputSource, c.App.Flags)
}
if inputSource != nil {
targetFlags := c.Command.Flags
if c.Command.Name == "" {
targetFlags = c.App.Flags
}
err := altsrc.ApplyInputSourceValues(c, inputSource, targetFlags)
if err != nil {
logger.Errorf("Cannot apply configuration from %s: %s", c.String("config"), err)
logger.Errorf("Cannot apply configuration from %s: %s", configFile, err)
return err
}
logger.Debugf("Applied configuration from %s", c.String("config"))
logger.Debugf("Applied configuration from %s", configFile)
}
return nil
}
@ -735,7 +688,7 @@ func dbConnectCmd() *cli.Command {
// Override before to run tunnel validation before dbconnect validation.
cmd.Before = func(c *cli.Context) error {
err := Before(c)
err := SetFlagsFromConfigFile(c)
if err == nil {
err = dbconnect.CmdBefore(c)
}

View File

@ -7,12 +7,14 @@ import (
"path/filepath"
"syscall"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
"github.com/cloudflare/cloudflared/logger"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
cli "github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
"github.com/cloudflare/cloudflared/logger"
)
const (
@ -20,6 +22,22 @@ const (
callbackStoreURL = "https://login.argotunnel.com/"
)
func buildLoginSubcommand(hidden bool) *cli.Command {
return &cli.Command{
Name: "login",
Action: cliutil.ErrorHandler(login),
Usage: "Generate a configuration file with your login details",
ArgsUsage: " ",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Hidden: true,
},
},
Hidden: hidden,
}
}
func login(c *cli.Context) error {
logger, err := logger.New()
if err != nil {

View File

@ -17,6 +17,7 @@ import (
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"golang.org/x/net/idna"
"gopkg.in/yaml.v2"
@ -58,24 +59,24 @@ var (
Aliases: []string{"rd"},
Usage: "Include connections that have recently disconnected in the list",
}
outputFormatFlag = &cli.StringFlag{
outputFormatFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'",
}
forceFlag = &cli.BoolFlag{
})
forceFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "By default, if a tunnel is currently being run from a cloudflared, you can't " +
"simultaneously rerun it again from a second cloudflared. The --force flag lets you " +
"overwrite the previous tunnel. If you want to use a single hostname with multiple " +
"tunnels, you can do so with Cloudflare's Load Balancer product.",
}
credentialsFileFlag = &cli.StringFlag{
})
credentialsFileFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: "credentials-file",
Aliases: []string{credFileFlagAlias},
Usage: "File path of tunnel credentials",
}
})
forceDeleteFlag = &cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
@ -88,9 +89,13 @@ func buildCreateCommand() *cli.Command {
Name: "create",
Action: cliutil.ErrorHandler(createCommand),
Usage: "Create a new tunnel with given name",
UsageText: "cloudflared tunnel [tunnel command options] create [create command options]",
Description: "cloudflared tunnel create example will create a tunnel named example, and generate a credential file to run the tunnel",
ArgsUsage: "TUNNEL-NAME",
UsageText: "cloudflared tunnel [tunnel command options] create [subcommand options] NAME",
Description: `Creates a tunnel, registers it with Cloudflare edge and generates credential file used to run this tunnel.
Use "cloudflared tunnel route" subcommand to map a DNS name to this tunnel and "cloudflared tunnel run" to start the connection.
For example, to create a tunnel named 'my-tunnel' run:
$ cloudflared tunnel create my-tunnel`,
Flags: []cli.Flag{outputFormatFlag},
CustomHelpTemplate: commandHelpTemplate(),
}
@ -156,8 +161,7 @@ func buildListCommand() *cli.Command {
Action: cliutil.ErrorHandler(listCommand),
Usage: "List existing tunnels",
UsageText: "cloudflared tunnel [tunnel command options] list [subcommand options]",
Description: "cloudflared tunnel list will return all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list",
ArgsUsage: " ",
Description: "cloudflared tunnel list will display all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list",
Flags: []cli.Flag{outputFormatFlag, showDeletedFlag, listNameFlag, listExistedAtFlag, listIDFlag, showRecentlyDisconnected},
CustomHelpTemplate: commandHelpTemplate(),
}
@ -262,9 +266,8 @@ func buildDeleteCommand() *cli.Command {
Name: "delete",
Action: cliutil.ErrorHandler(deleteCommand),
Usage: "Delete existing tunnel by UUID or name",
UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options]",
UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL",
Description: "cloudflared tunnel delete will delete tunnels with the given tunnel UUIDs or names. A tunnel cannot be deleted if it has active connections. To delete the tunnel unconditionally, use -f flag.",
ArgsUsage: "TUNNEL",
Flags: []cli.Flag{credentialsFileFlag, forceDeleteFlag},
CustomHelpTemplate: commandHelpTemplate(),
}
@ -310,15 +313,16 @@ func buildRunCommand() *cli.Command {
return &cli.Command{
Name: "run",
Action: cliutil.ErrorHandler(runCommand),
Before: SetFlagsFromConfigFile,
Usage: "Proxy a local web server by running the given tunnel",
UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options]",
ArgsUsage: "TUNNEL",
UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] TUNNEL",
Description: `Runs the tunnel identified by name or UUUD, creating highly available connections
between your server and the Cloudflare edge.
between your server and the Cloudflare edge.
This command requires the tunnel credentials file created when "cloudflared tunnel create" was run,
however it does not need access to cert.pem from "cloudflared login" if you identify the tunnel by UUID.
If you experience other problems running gitthe tunnel, "cloudflared tunnel cleanup" may help by removing any old connection records.
This command requires the tunnel credentials file created when "cloudflared tunnel create" was run,
however it does not need access to cert.pem from "cloudflared login" if you identify the tunnel by UUID.
If you experience other problems running the tunnel, "cloudflared tunnel cleanup" may help by removing
any old connection records.
`,
Flags: flags,
CustomHelpTemplate: commandHelpTemplate(),
@ -347,9 +351,8 @@ func buildCleanupCommand() *cli.Command {
Name: "cleanup",
Action: cliutil.ErrorHandler(cleanupCommand),
Usage: "Cleanup tunnel connections",
UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options]",
UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options] TUNNEL",
Description: "Delete connections for tunnels with the given UUIDs or names.",
ArgsUsage: "TUNNEL",
CustomHelpTemplate: commandHelpTemplate(),
}
}
@ -377,14 +380,13 @@ func buildRouteCommand() *cli.Command {
Name: "route",
Action: cliutil.ErrorHandler(routeCommand),
Usage: "Define what hostname or load balancer can route to this tunnel",
UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options]",
UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options] dns|lb TUNNEL HOSTNAME [LB-POOL]",
Description: `The route defines what hostname or load balancer will proxy requests to this tunnel.
To route a hostname by creating a CNAME to tunnel's address:
cloudflared tunnel route dns <tunnel ID> <hostname>
To use this tunnel as a load balancer origin, creating pool and load balancer if necessary:
cloudflared tunnel route lb <tunnel ID> <load balancer name> <load balancer pool>`,
ArgsUsage: "dns|lb TUNNEL HOSTNAME [LB-POOL]",
CustomHelpTemplate: commandHelpTemplate(),
}
}
@ -513,8 +515,7 @@ DESCRIPTION:
TUNNEL COMMAND OPTIONS:
%s
SUBCOMMAND COMMAND OPTIONS:
SUBCOMMAND OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}
`

View File

@ -27,6 +27,48 @@ type Listener struct {
logger logger.Service
}
func Command(hidden bool) *cli.Command {
return &cli.Command{
Name: "proxy-dns",
Action: cliutil.ErrorHandler(Run),
Usage: "Run a DNS over HTTPS proxy server.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "metrics",
Value: "localhost:",
Usage: "Listen address for metrics reporting.",
EnvVars: []string{"TUNNEL_METRICS"},
},
&cli.StringFlag{
Name: "address",
Usage: "Listen address for the DNS over HTTPS proxy server.",
Value: "localhost",
EnvVars: []string{"TUNNEL_DNS_ADDRESS"},
},
&cli.IntFlag{
Name: "port",
Usage: "Listen on given port for the DNS over HTTPS proxy server.",
Value: 53,
EnvVars: []string{"TUNNEL_DNS_PORT"},
},
&cli.StringSliceFlag{
Name: "upstream",
Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
},
&cli.StringSliceFlag{
Name: "bootstrap",
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
},
},
ArgsUsage: " ", // can't be the empty string or we get the default output
Hidden: hidden,
}
}
// Run implements a foreground runner
func Run(c *cli.Context) error {
logDirectory, logLevel := config.FindLogSettings()