Skip to content

Commit 8836ee1

Browse files
committed
TUN-3156: Add route subcommand under tunnel
1 parent 7afde79 commit 8836ee1

File tree

4 files changed

+230
-49
lines changed

4 files changed

+230
-49
lines changed

cmd/cloudflared/tunnel/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func Commands() []*cli.Command {
174174
subcommands = append(subcommands, buildDeleteCommand())
175175
subcommands = append(subcommands, buildRunCommand())
176176
subcommands = append(subcommands, buildCleanupCommand())
177+
subcommands = append(subcommands, buildRouteCommand())
177178

178179
cmds = append(cmds, &cli.Command{
179180
Name: "tunnel",

cmd/cloudflared/tunnel/subcommands.go

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var (
5858
forceDeleteFlag = &cli.BoolFlag{
5959
Name: "force",
6060
Aliases: []string{"f"},
61-
Usage: "Allows you to delete a tunnel, even if it has active connections.",
61+
Usage: "Allows you to delete a tunnel, even if it has active connections.",
6262
}
6363
)
6464

@@ -131,13 +131,13 @@ func createTunnel(c *cli.Context) error {
131131
return nil
132132
}
133133

134-
func tunnelFilePath(tunnelID, directory string) (string, error) {
134+
func tunnelFilePath(tunnelID uuid.UUID, directory string) (string, error) {
135135
fileName := fmt.Sprintf("%v.json", tunnelID)
136136
filePath := filepath.Clean(fmt.Sprintf("%s/%s", directory, fileName))
137137
return homedir.Expand(filePath)
138138
}
139139

140-
func writeTunnelCredentials(tunnelID, accountID, originCertPath string, tunnelSecret []byte, logger logger.Service) error {
140+
func writeTunnelCredentials(tunnelID uuid.UUID, accountID, originCertPath string, tunnelSecret []byte, logger logger.Service) error {
141141
originCertDir := filepath.Dir(originCertPath)
142142
filePath, err := tunnelFilePath(tunnelID, originCertDir)
143143
if err != nil {
@@ -155,7 +155,7 @@ func writeTunnelCredentials(tunnelID, accountID, originCertPath string, tunnelSe
155155
return ioutil.WriteFile(filePath, body, 400)
156156
}
157157

158-
func readTunnelCredentials(c *cli.Context, tunnelID string, logger logger.Service) (*pogs.TunnelAuth, error) {
158+
func readTunnelCredentials(c *cli.Context, tunnelID uuid.UUID, logger logger.Service) (*pogs.TunnelAuth, error) {
159159
filePath, err := tunnelCredentialsPath(c, tunnelID, logger)
160160
if err != nil {
161161
return nil, err
@@ -172,7 +172,7 @@ func readTunnelCredentials(c *cli.Context, tunnelID string, logger logger.Servic
172172
return &auth, nil
173173
}
174174

175-
func tunnelCredentialsPath(c *cli.Context, tunnelID string, logger logger.Service) (string, error) {
175+
func tunnelCredentialsPath(c *cli.Context, tunnelID uuid.UUID, logger logger.Service) (string, error) {
176176
if filePath := c.String("credentials-file"); filePath != "" {
177177
if validFilePath(filePath) {
178178
return filePath, nil
@@ -322,7 +322,10 @@ func deleteTunnel(c *cli.Context) error {
322322
if c.NArg() != 1 {
323323
return cliutil.UsageError(`"cloudflared tunnel delete" requires exactly 1 argument, the ID of the tunnel to delete.`)
324324
}
325-
id := c.Args().First()
325+
tunnelID, err := uuid.Parse(c.Args().First())
326+
if err != nil {
327+
return errors.Wrap(err, "error parsing tunnel ID")
328+
}
326329

327330
logger, err := logger.New()
328331
if err != nil {
@@ -337,9 +340,9 @@ func deleteTunnel(c *cli.Context) error {
337340

338341
forceFlagSet := c.Bool("force")
339342

340-
tunnel, err := client.GetTunnel(id)
343+
tunnel, err := client.GetTunnel(tunnelID)
341344
if err != nil {
342-
return errors.Wrapf(err, "Can't get tunnel information. Please check tunnel id: %s", id)
345+
return errors.Wrapf(err, "Can't get tunnel information. Please check tunnel id: %s", tunnelID)
343346
}
344347

345348
// Check if tunnel DeletedAt field has already been set
@@ -351,17 +354,17 @@ func deleteTunnel(c *cli.Context) error {
351354
if !forceFlagSet {
352355
return errors.New("You can not delete this tunnel because it has active connections. To see connections run the 'list' command. If you believe the tunnel is not active, you can use a -f / --force flag with this command.")
353356
}
354-
355-
if err := client.CleanupConnections(id); err != nil {
356-
return errors.Wrapf(err, "Error cleaning up connections for tunnel %s", id)
357+
358+
if err := client.CleanupConnections(tunnelID); err != nil {
359+
return errors.Wrapf(err, "Error cleaning up connections for tunnel %s", tunnelID)
357360
}
358361
}
359362

360-
if err := client.DeleteTunnel(id); err != nil {
361-
return errors.Wrapf(err, "Error deleting tunnel %s", id)
363+
if err := client.DeleteTunnel(tunnelID); err != nil {
364+
return errors.Wrapf(err, "Error deleting tunnel %s", tunnelID)
362365
}
363366

364-
tunnelCredentialsPath, err := tunnelCredentialsPath(c, id, logger)
367+
tunnelCredentialsPath, err := tunnelCredentialsPath(c, tunnelID, logger)
365368
if err != nil {
366369
logger.Infof("Cannot locate tunnel credentials to delete, error: %v. Please delete the file manually", err)
367370
return nil
@@ -388,7 +391,7 @@ func renderOutput(format string, v interface{}) error {
388391
}
389392

390393
func newTunnelstoreClient(c *cli.Context, cert *certutil.OriginCert, logger logger.Service) tunnelstore.Client {
391-
client := tunnelstore.NewRESTClient(c.String("api-url"), cert.AccountID, cert.ServiceKey, logger)
394+
client := tunnelstore.NewRESTClient(c.String("api-url"), cert.AccountID, cert.ZoneID, cert.ServiceKey, logger)
392395
return client
393396
}
394397

@@ -428,8 +431,8 @@ func runTunnel(c *cli.Context) error {
428431
if c.NArg() != 1 {
429432
return cliutil.UsageError(`"cloudflared tunnel run" requires exactly 1 argument, the ID of the tunnel to run.`)
430433
}
431-
id := c.Args().First()
432-
tunnelID, err := uuid.Parse(id)
434+
435+
tunnelID, err := uuid.Parse(c.Args().First())
433436
if err != nil {
434437
return errors.Wrap(err, "error parsing tunnel ID")
435438
}
@@ -439,7 +442,7 @@ func runTunnel(c *cli.Context) error {
439442
return errors.Wrap(err, "error setting up logger")
440443
}
441444

442-
credentials, err := readTunnelCredentials(c, id, logger)
445+
credentials, err := readTunnelCredentials(c, tunnelID, logger)
443446
if err != nil {
444447
return err
445448
}
@@ -474,12 +477,108 @@ func cleanupConnections(c *cli.Context) error {
474477
client := newTunnelstoreClient(c, cert, logger)
475478

476479
for i := 0; i < c.NArg(); i++ {
477-
id := c.Args().Get(i)
478-
logger.Infof("Cleanup connection for tunnel %s", id)
479-
if err := client.CleanupConnections(id); err != nil {
480-
logger.Errorf("Error cleaning up connections for tunnel %s, error :%v", id, err)
480+
tunnelID, err := uuid.Parse(c.Args().Get(i))
481+
if err != nil {
482+
logger.Errorf("Failed to parse argument %d as tunnelID, error :%v", i, err)
483+
continue
484+
}
485+
logger.Infof("Cleanup connection for tunnel %s", tunnelID)
486+
if err := client.CleanupConnections(tunnelID); err != nil {
487+
logger.Errorf("Error cleaning up connections for tunnel %v, error :%v", tunnelID, err)
481488
}
482489
}
483490

484491
return nil
485492
}
493+
494+
func buildRouteCommand() *cli.Command {
495+
return &cli.Command{
496+
Name: "route",
497+
Action: cliutil.ErrorHandler(routeTunnel),
498+
Usage: "Define what hostname or load balancer can route to this tunnel",
499+
Description: `The route defines what hostname or load balancer can route to this tunnel.
500+
To route a hostname: cloudflared tunnel route dns <tunnel ID> <hostname>
501+
To route a load balancer: cloudflared tunnel route lb <tunnel ID> <load balancer name> <load balancer pool>
502+
If you don't specify a load balancer pool, we will create a new pool called tunnel:<tunnel ID>`,
503+
ArgsUsage: "dns|lb TUNNEL-ID HOSTNAME [LB-POOL]",
504+
Hidden: hideSubcommands,
505+
}
506+
}
507+
508+
func routeTunnel(c *cli.Context) error {
509+
if c.NArg() < 2 {
510+
return cliutil.UsageError(`"cloudflared tunnel route" requires the first argument to be the route type(dns or lb), followed by the ID of the tunnel`)
511+
}
512+
const tunnelIDIndex = 1
513+
tunnelID, err := uuid.Parse(c.Args().Get(tunnelIDIndex))
514+
if err != nil {
515+
return errors.Wrap(err, "error parsing tunnel ID")
516+
}
517+
518+
logger, err := logger.New()
519+
if err != nil {
520+
return errors.Wrap(err, "error setting up logger")
521+
}
522+
523+
routeType := c.Args().First()
524+
var route tunnelstore.Route
525+
switch routeType {
526+
case "dns":
527+
route, err = dnsRouteFromArg(c, tunnelID)
528+
if err != nil {
529+
return err
530+
}
531+
case "lb":
532+
route, err = lbRouteFromArg(c, tunnelID, logger)
533+
if err != nil {
534+
return err
535+
}
536+
default:
537+
return cliutil.UsageError("%s is not a recognized route type. Supported route types are dns and lb", routeType)
538+
}
539+
540+
cert, _, err := getOriginCertFromContext(c, logger)
541+
if err != nil {
542+
return err
543+
}
544+
545+
client := newTunnelstoreClient(c, cert, logger)
546+
return client.RouteTunnel(tunnelID, route)
547+
}
548+
549+
func dnsRouteFromArg(c *cli.Context, tunnelID uuid.UUID) (tunnelstore.Route, error) {
550+
const (
551+
userHostnameIndex = 2
552+
expectArgs = 3
553+
)
554+
if c.NArg() != expectArgs {
555+
return nil, cliutil.UsageError("Expect %d arguments, got %d", expectArgs, c.NArg())
556+
}
557+
userHostname := c.Args().Get(userHostnameIndex)
558+
if userHostname == "" {
559+
return nil, cliutil.UsageError("The third argument should be the hostname")
560+
}
561+
return tunnelstore.NewDNSRoute(userHostname), nil
562+
}
563+
564+
func lbRouteFromArg(c *cli.Context, tunnelID uuid.UUID, logger logger.Service) (tunnelstore.Route, error) {
565+
const (
566+
lbNameIndex = 2
567+
lbPoolIndex = 3
568+
expectMinArgs = 3
569+
)
570+
if c.NArg() < expectMinArgs {
571+
return nil, cliutil.UsageError("Expect at least %d arguments, got %d", expectMinArgs, c.NArg())
572+
}
573+
lbName := c.Args().Get(lbNameIndex)
574+
if lbName == "" {
575+
return nil, cliutil.UsageError("The third argument should be the load balancer name")
576+
}
577+
lbPool := c.Args().Get(lbPoolIndex)
578+
if lbPool == "" {
579+
lbPool = fmt.Sprintf("tunnel:%v", tunnelID)
580+
logger.Infof("Generate pool name %s", lbPool)
581+
}
582+
583+
return tunnelstore.NewLBRoute(lbName, lbPool), nil
584+
}

cmd/cloudflared/tunnel/subcommands_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,13 @@ func Test_fmtConnections(t *testing.T) {
7575
}
7676

7777
func TestTunnelfilePath(t *testing.T) {
78+
tunnelID, err := uuid.Parse("f48d8918-bc23-4647-9d48-082c5b76de65")
79+
assert.NoError(t, err)
7880
originCertDir := filepath.Dir("~/.cloudflared/cert.pem")
79-
actual, err := tunnelFilePath("tunnel", originCertDir)
81+
actual, err := tunnelFilePath(tunnelID, originCertDir)
8082
assert.NoError(t, err)
8183
homeDir, err := homedir.Dir()
8284
assert.NoError(t, err)
83-
expected := fmt.Sprintf("%s/.cloudflared/tunnel.json", homeDir)
85+
expected := fmt.Sprintf("%s/.cloudflared/%v.json", homeDir, tunnelID)
8486
assert.Equal(t, expected, actual)
8587
}

0 commit comments

Comments
 (0)