TUN-3463: Let users run a named tunnel via config file setting
This commit is contained in:
parent
acd03e36e6
commit
051908aaef
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue