TUN-3463: Let users run a named tunnel via config file setting

This commit is contained in:
Igor Postelnik 2020-10-15 15:08:57 -05:00
parent acd03e36e6
commit 051908aaef
5 changed files with 104 additions and 50 deletions

View File

@ -10,9 +10,11 @@ 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/ingress"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/validation"
)
@ -199,7 +201,7 @@ func ValidateUrl(c *cli.Context, allowFromArgs bool) (string, error) {
func ReadRules(c *cli.Context) (ingress.Ingress, error) {
configFilePath := c.String("config")
if configFilePath == "" {
return ingress.Ingress{}, ErrNoConfigFile
return ingress.Ingress{}, ingress.ErrNoIngressRules
}
fmt.Printf("Reading from config file %s\n", configFilePath)
configBytes, err := ioutil.ReadFile(configFilePath)
@ -209,3 +211,31 @@ func ReadRules(c *cli.Context) (ingress.Ingress, error) {
rules, err := ingress.ParseIngress(configBytes)
return rules, err
}
var configFileInputSource struct {
lastLoadedFile string
context altsrc.InputSourceContext
}
// GetConfigFileSource 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 GetConfigFileSource(c *cli.Context, log logger.Service) (altsrc.InputSourceContext, error) {
configFile := c.String("config")
if configFileInputSource.lastLoadedFile == configFile {
if configFileInputSource.context == nil {
return nil, ErrNoConfigFile
}
return configFileInputSource.context, nil
}
configFileInputSource.lastLoadedFile = configFile
log.Debugf("Loading configuration from %s", configFile)
src, err := altsrc.NewYamlSourceFromFile(configFile)
if err != nil {
return nil, err
}
configFileInputSource.context = src
return src, nil
}

View File

@ -199,24 +199,29 @@ func buildIngressSubcommand() *cli.Command {
}
func TunnelCommand(c *cli.Context) error {
if name := c.String("name"); name != "" { // Start a named tunnel
return adhocNamedTunnel(c, name)
} else { // Start a classic tunnel
return classicTunnel(c)
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
if name := c.String("name"); name != "" { // Start a named tunnel
return runAdhocNamedTunnel(sc, name)
}
if ref, err := sc.getConfigFileTunnelRef(); err != nil {
return err
} else if ref != "" {
return runNamedTunnel(sc, ref)
}
// Start a classic tunnel
return runClassicTunnel(sc)
}
func Init(v string, s, g chan struct{}) {
version, shutdownC, graceShutdownC = v, s, g
}
// adhocNamedTunnel create, route and run a named tunnel in one command
func adhocNamedTunnel(c *cli.Context, name string) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
// runAdhocNamedTunnel create, route and run a named tunnel in one command
func runAdhocNamedTunnel(sc *subcommandContext, name string) error {
tunnel, ok, err := sc.tunnelActive(name)
if err != nil || !ok {
tunnel, err = sc.create(name)
@ -227,7 +232,7 @@ func adhocNamedTunnel(c *cli.Context, name string) error {
sc.logger.Infof("Tunnel already created with ID %s", tunnel.ID)
}
if r, ok := routeFromFlag(c); ok {
if r, ok := routeFromFlag(sc.c); ok {
if res, err := sc.route(tunnel.ID, r); err != nil {
sc.logger.Errorf("failed to create route, please create it manually. err: %v.", err)
} else {
@ -242,14 +247,9 @@ func adhocNamedTunnel(c *cli.Context, name string) error {
return nil
}
// classicTunnel creates a "classic" non-named tunnel
func classicTunnel(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return err
}
return StartServer(c, version, shutdownC, graceShutdownC, nil, sc.logger, sc.isUIEnabled)
// runClassicTunnel creates a "classic" non-named tunnel
func runClassicTunnel(sc *subcommandContext) error {
return StartServer(sc.c, version, shutdownC, graceShutdownC, nil, sc.logger, sc.isUIEnabled)
}
func routeFromFlag(c *cli.Context) (tunnelstore.Route, bool) {
@ -571,33 +571,25 @@ func forceSetFlag(c *cli.Context, name, value string) {
}
func SetFlagsFromConfigFile(c *cli.Context) error {
logger, err := createLogger(c, false, false)
log, err := createLogger(c, false, false)
if err != nil {
return cliutil.PrintLoggerSetupError("error setting up logger", err)
}
configFile := c.String("config")
if configFile == "" {
logger.Debugf(config.ErrNoConfigFile.Error())
return nil
}
inputSource, err := altsrc.NewYamlSourceFromFile(configFile)
inputSource, err := config.GetConfigFileSource(c, log)
if err != nil {
logger.Errorf("Cannot load configuration from %s: %s", configFile, err)
if err == config.ErrNoConfigFile {
return nil
}
return err
}
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", configFile, err)
return err
}
logger.Debugf("Applied configuration from %s", configFile)
targetFlags := c.Command.Flags
if c.Command.Name == "" {
targetFlags = c.App.Flags
}
if err := altsrc.ApplyInputSourceValues(c, inputSource, targetFlags); err != nil {
log.Errorf("Cannot load configuration from %s: %v", inputSource.Source(), err)
return err
}
return nil
}

View File

@ -96,7 +96,7 @@ func dnsProxyStandAlone(c *cli.Context) bool {
func findOriginCert(c *cli.Context, logger logger.Service) (string, error) {
originCertPath := c.String("origincert")
if originCertPath == "" {
logger.Infof(config.ErrNoConfigFile.Error())
logger.Infof("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigSearchDirectories())
if isRunningFromTerminal() {
logger.Errorf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl)
return "", fmt.Errorf("Client didn't specify origincert path when running from terminal")

View File

@ -8,15 +8,16 @@ import (
"path/filepath"
"strings"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/certutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/tunnelstore"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/urfave/cli/v2"
)
// subcommandContext carries structs shared between subcommands, to reduce number of arguments needed to
@ -143,6 +144,18 @@ func (sc *subcommandContext) tunnelCredentialsPath(tunnelID uuid.UUID) (string,
return "", fmt.Errorf("Tunnel credentials file not found")
}
// getConfigFileTunnelRef returns tunnel UUID or name set in the configuration file
func (sc *subcommandContext) getConfigFileTunnelRef() (string, error) {
if src, err := config.GetConfigFileSource(sc.c, sc.logger); err == nil {
if tunnelRef, err := src.String("tunnel"); err != nil {
return "", errors.Wrapf(err, "invalid tunnel ID or name")
} else {
return tunnelRef, nil
}
}
return "", nil
}
func (sc *subcommandContext) create(name string) (*tunnelstore.Tunnel, error) {
client, err := sc.client()
if err != nil {

View File

@ -315,9 +315,10 @@ func buildRunCommand() *cli.Command {
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] 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. You can provide name or UUID of tunnel to run either as the
last command line argument or in the configuration file using "tunnel: TUNNEL".
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.
@ -335,14 +336,32 @@ func runCommand(c *cli.Context) error {
return err
}
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel run" requires exactly 1 argument, the ID or name of the tunnel to run.`)
if c.NArg() > 1 {
return cliutil.UsageError(`"cloudflared tunnel run" accepts only one argument, the ID or name of the tunnel to run.`)
}
tunnelID, err := sc.findID(c.Args().First())
tunnelRef := c.Args().First()
if tunnelRef == "" {
// attempt to read from the config file
if tunnelRef, err = sc.getConfigFileTunnelRef(); err != nil {
return err
}
if tunnelRef == "" {
return cliutil.UsageError(`"cloudflared tunnel run" requires the ID or name of the tunnel to run as the last command line argument or in the configuration file.`)
}
}
return runNamedTunnel(sc, tunnelRef)
}
func runNamedTunnel(sc *subcommandContext, tunnelRef string) error {
tunnelID, err := sc.findID(tunnelRef)
if err != nil {
return errors.Wrap(err, "error parsing tunnel ID")
}
sc.logger.Infof("Starting tunnel %s", tunnelID.String())
return sc.run(tunnelID)
}