Skip to content

Commit

Permalink
RSDK-9761 - Add tunnel command to CLI (viamrobotics#4760)
Browse files Browse the repository at this point in the history
  • Loading branch information
cheukt authored Jan 30, 2025
1 parent dae1a24 commit 7be8421
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 133 deletions.
46 changes: 46 additions & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ const (
cpFlagRecursive = "recursive"
cpFlagPreserve = "preserve"

tunnelFlagLocalPort = "local-port"
tunnelFlagDestinationPort = "destination-port"

organizationFlagSupportEmail = "support-email"
organizationBillingAddress = "address"
organizationFlagLogoPath = "logo-path"
Expand Down Expand Up @@ -2136,6 +2139,49 @@ Copy multiple files from the machine to a local destination with recursion and k
},
Action: createCommandWithT[machinesPartCopyFilesArgs](MachinesPartCopyFilesAction),
},
{
Name: "tunnel",
Usage: "tunnel connections to the specified port on a machine part",
UsageText: createUsageText("machines part tunnel", []string{
generalFlagPart, tunnelFlagLocalPort, tunnelFlagDestinationPort,
}, true, false),
Flags: []cli.Flag{
&AliasStringFlag{
cli.StringFlag{
Name: generalFlagPart,
Aliases: []string{generalFlagPartID, generalFlagPartName},
Required: true,
},
},
&AliasStringFlag{
cli.StringFlag{
Name: generalFlagOrganization,
Aliases: []string{generalFlagAliasOrg, generalFlagOrgID, generalFlagAliasOrgName},
},
},
&AliasStringFlag{
cli.StringFlag{
Name: generalFlagLocation,
Aliases: []string{generalFlagLocationID, generalFlagAliasLocationName},
},
},
&AliasStringFlag{
cli.StringFlag{
Name: generalFlagMachine,
Aliases: []string{generalFlagAliasRobot, generalFlagMachineID, generalFlagMachineName},
},
},
&cli.IntFlag{
Name: tunnelFlagLocalPort,
Required: true,
},
&cli.IntFlag{
Name: tunnelFlagDestinationPort,
Required: true,
},
},
Action: createCommandWithT[robotsPartTunnelArgs](RobotsPartTunnelAction),
},
},
},
},
Expand Down
110 changes: 107 additions & 3 deletions cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import (
"os/signal"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"

"github.com/Masterminds/semver/v3"
Expand Down Expand Up @@ -1355,6 +1357,94 @@ func (c *viamClient) machinesPartCopyFilesAction(
return nil
}

type robotsPartTunnelArgs struct {
Organization string
Location string
Machine string
Part string
LocalPort int
DestinationPort int
}

// RobotsPartTunnelAction is the corresponding Action for 'machines part tunnel'.
func RobotsPartTunnelAction(c *cli.Context, args robotsPartTunnelArgs) error {
client, err := newViamClient(c)
if err != nil {
return err
}

return client.robotPartTunnel(c, args)
}

func tunnelTraffic(ctx *cli.Context, robotClient *client.RobotClient, local, dest int) error {
li, err := net.Listen("tcp", net.JoinHostPort("localhost", strconv.Itoa(local)))
if err != nil {
return fmt.Errorf("failed to create listener %w", err)
}
infof(ctx.App.Writer, "tunneling connections from local port %v to destination port %v on machine part...", local, dest)
defer func() {
if err := li.Close(); err != nil {
warningf(ctx.App.ErrWriter, "error closing listener: %s", err)
}
}()

var wg sync.WaitGroup
for {
if ctx.Err() != nil {
break
}
conn, err := li.Accept()
if err != nil {
warningf(ctx.App.ErrWriter, "failed to accept connection: %s", err)
continue
}

wg.Add(1)
go func() {
defer wg.Done()
// call tunnel once per connection, the connection passed in will be closed
// by Tunnel.
if err := robotClient.Tunnel(ctx.Context, conn, dest); err != nil {
printf(ctx.App.Writer, "error while tunneling connection: %s", err)
}
}()
}
wg.Wait()
return nil
}

func (c *viamClient) robotPartTunnel(cCtx *cli.Context, args robotsPartTunnelArgs) error {
if err := c.ensureLoggedIn(); err != nil {
return err
}

orgStr := args.Organization
locStr := args.Location
robotStr := args.Machine
partStr := args.Part

// Create logger based on presence of debugFlag.
logger := logging.FromZapCompatible(zap.NewNop().Sugar())
globalArgs, err := getGlobalArgs(cCtx)
if err != nil {
return err
}
if globalArgs.Debug {
logger = logging.NewDebugLogger("cli")
}

dialCtx, fqdn, rpcOpts, err := c.prepareDial(orgStr, locStr, robotStr, partStr, globalArgs.Debug)
if err != nil {
return err
}

robotClient, err := c.connectToRobot(dialCtx, fqdn, rpcOpts, globalArgs.Debug, logger)
if err != nil {
return err
}
return tunnelTraffic(cCtx, robotClient, args.LocalPort, args.DestinationPort)
}

// checkUpdateResponse holds the values used to hold release information.
type getLatestReleaseResponse struct {
Name string `json:"name"`
Expand Down Expand Up @@ -2185,19 +2275,33 @@ func (c *viamClient) connectToShellServiceFqdn(
return c.connectToShellServiceInner(dialCtx, fqdn, rpcOpts, debug, logger)
}

func (c *viamClient) connectToShellServiceInner(
func (c *viamClient) connectToRobot(
dialCtx context.Context,
fqdn string,
rpcOpts []rpc.DialOption,
debug bool,
logger logging.Logger,
) (shell.Service, func(ctx context.Context) error, error) {
) (*client.RobotClient, error) {
if debug {
printf(c.c.App.Writer, "Establishing connection...")
}
robotClient, err := client.New(dialCtx, fqdn, logger, client.WithDialOptions(rpcOpts...))
if err != nil {
return nil, nil, errors.Wrap(err, "could not connect to machine part")
return nil, errors.Wrap(err, "could not connect to machine part")
}
return robotClient, nil
}

func (c *viamClient) connectToShellServiceInner(
dialCtx context.Context,
fqdn string,
rpcOpts []rpc.DialOption,
debug bool,
logger logging.Logger,
) (shell.Service, func(ctx context.Context) error, error) {
robotClient, err := c.connectToRobot(dialCtx, fqdn, rpcOpts, debug, logger)
if err != nil {
return nil, nil, err
}

var successful bool
Expand Down
8 changes: 0 additions & 8 deletions examples/tunnel/README.md

This file was deleted.

122 changes: 0 additions & 122 deletions examples/tunnel/tunnel.go

This file was deleted.

0 comments on commit 7be8421

Please sign in to comment.