mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-28 12:59:57 +00:00
TUN-5361: Commands for managing virtual networks
This commit is contained in:
@@ -102,6 +102,8 @@ func Commands() []*cli.Command {
|
||||
buildLoginSubcommand(false),
|
||||
buildCreateCommand(),
|
||||
buildRouteCommand(),
|
||||
// TODO TUN-5477 this should not be hidden
|
||||
buildVirtualNetworkSubcommand(true),
|
||||
buildRunCommand(),
|
||||
buildListCommand(),
|
||||
buildInfoCommand(),
|
||||
|
40
cmd/cloudflared/tunnel/subcommand_context_vnets.go
Normal file
40
cmd/cloudflared/tunnel/subcommand_context_vnets.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/vnet"
|
||||
)
|
||||
|
||||
func (sc *subcommandContext) addVirtualNetwork(newVnet vnet.NewVirtualNetwork) (vnet.VirtualNetwork, error) {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return vnet.VirtualNetwork{}, errors.Wrap(err, noClientMsg)
|
||||
}
|
||||
return client.CreateVirtualNetwork(newVnet)
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) listVirtualNetworks(filter *vnet.Filter) ([]*vnet.VirtualNetwork, error) {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, noClientMsg)
|
||||
}
|
||||
return client.ListVirtualNetworks(filter)
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID) error {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, noClientMsg)
|
||||
}
|
||||
return client.DeleteVirtualNetwork(vnetId)
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) updateVirtualNetwork(vnetId uuid.UUID, updates vnet.UpdateVirtualNetwork) error {
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, noClientMsg)
|
||||
}
|
||||
return client.UpdateVirtualNetwork(vnetId, updates)
|
||||
}
|
@@ -329,7 +329,7 @@ func listCommand(c *cli.Context) error {
|
||||
if len(tunnels) > 0 {
|
||||
formatAndPrintTunnelList(tunnels, c.Bool("show-recently-disconnected"))
|
||||
} else {
|
||||
fmt.Println("You have no tunnels, use 'cloudflared tunnel create' to define a new tunnel")
|
||||
fmt.Println("No tunnels were found for the given filter flags. You can use 'cloudflared tunnel create' to create a tunnel.")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -98,7 +98,7 @@ func showRoutesCommand(c *cli.Context) error {
|
||||
if len(routes) > 0 {
|
||||
formatAndPrintRouteList(routes)
|
||||
} else {
|
||||
fmt.Println("You have no routes, use 'cloudflared tunnel route ip add' to add a route")
|
||||
fmt.Println("No routes were found for the given filter flags. You can use 'cloudflared tunnel route ip add' to add a route.")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
285
cmd/cloudflared/tunnel/vnets_subcommands.go
Normal file
285
cmd/cloudflared/tunnel/vnets_subcommands.go
Normal file
@@ -0,0 +1,285 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/vnet"
|
||||
)
|
||||
|
||||
var (
|
||||
makeDefaultFlag = &cli.BoolFlag{
|
||||
Name: "default",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "The virtual network becomes the default one for the account. This means that all operations that " +
|
||||
"omit a virtual network will now implicitly be using this virtual network (i.e., the default one) such " +
|
||||
"as new IP routes that are created. When this flag is not set, the virtual network will not become the " +
|
||||
"default one in the account.",
|
||||
}
|
||||
newNameFlag = &cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "The new name for the virtual network.",
|
||||
}
|
||||
newCommentFlag = &cli.StringFlag{
|
||||
Name: "comment",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "A new comment describing the purpose of the virtual network.",
|
||||
}
|
||||
)
|
||||
|
||||
func buildVirtualNetworkSubcommand(hidden bool) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "network",
|
||||
Usage: "Configure and query virtual networks to manage private IP routes with overlapping IPs.",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network COMMAND [arguments...]",
|
||||
Description: `cloudflared allows to manage IP routes that expose origins in your private network space via their IP directly
|
||||
to clients outside (e.g. using WARP client) --- those are configurable via "cloudflared tunnel route ip" commands.
|
||||
By default, all those IP routes live in the same virtual network. Managing virtual networks (e.g. by creating a
|
||||
new one) becomes relevant when you have different private networks that have overlapping IPs. E.g.: if you have
|
||||
a private network A running Tunnel 1, and private network B running Tunnel 2, it is possible that both Tunnels
|
||||
expose the same IP space (say 10.0.0.0/8); to handle that, you have to add each IP Route (one that points to
|
||||
Tunnel 1 and another that points to Tunnel 2) in different Virtual Networks. That way, if your clients are on
|
||||
Virtual Network X, they will see Tunnel 1 (via Route A) and not see Tunnel 2 (since its Route B is associated
|
||||
to another Virtual Network Y).`,
|
||||
Hidden: hidden,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Action: cliutil.ConfiguredAction(addVirtualNetworkCommand),
|
||||
Usage: "Add a new virtual network to which IP routes can be attached",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network add [flags] NAME [\"comment\"]",
|
||||
Description: `Adds a new virtual network. You can then attach IP routes to this virtual network with "cloudflared tunnel route ip"
|
||||
commands. By doing so, such route(s) become segregated from route(s) in another virtual networks. Note that all
|
||||
routes exist within some virtual network. If you do not specify any, then the system pre-creates a default virtual
|
||||
network to which all routes belong. That is fine if you do not have overlapping IPs within different physical
|
||||
private networks in your infrastructure exposed via Cloudflare Tunnel. Note: if a virtual network is added as
|
||||
the new default, then the previous existing default virtual network will be automatically modified to no longer
|
||||
be the current default.`,
|
||||
Flags: []cli.Flag{makeDefaultFlag},
|
||||
Hidden: hidden,
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Action: cliutil.ConfiguredAction(listVirtualNetworksCommand),
|
||||
Usage: "Lists the virtual networks",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network list [flags]",
|
||||
Description: "Lists the virtual networks based on the given filter flags.",
|
||||
Flags: listVirtualNetworksFlags(),
|
||||
Hidden: hidden,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Action: cliutil.ConfiguredAction(deleteVirtualNetworkCommand),
|
||||
Usage: "Delete a virtual network",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network delete VIRTUAL_NETWORK",
|
||||
Description: `Deletes the virtual network (given its ID or name). This is only possible if that virtual network is unused.
|
||||
A virtual network may be used by IP routes or by WARP devices.`,
|
||||
Hidden: hidden,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Action: cliutil.ConfiguredAction(updateVirtualNetworkCommand),
|
||||
Usage: "Update a virtual network",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] network update [flags] VIRTUAL_NETWORK",
|
||||
Description: `Updates the virtual network (given its ID or name). If this virtual network is updated to become the new
|
||||
default, then the previously existing default virtual network will also be modified to no longer be the default.
|
||||
You cannot update a virtual network to not be the default anymore directly. Instead, you should create a new
|
||||
default or update an existing one to become the default.`,
|
||||
Flags: []cli.Flag{newNameFlag, newCommentFlag, makeDefaultFlag},
|
||||
Hidden: hidden,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func listVirtualNetworksFlags() []cli.Flag {
|
||||
flags := make([]cli.Flag, 0)
|
||||
flags = append(flags, vnet.FilterFlags...)
|
||||
flags = append(flags, outputFormatFlag)
|
||||
return flags
|
||||
}
|
||||
|
||||
func addVirtualNetworkCommand(c *cli.Context) error {
|
||||
sc, err := newSubcommandContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.NArg() < 1 {
|
||||
return errors.New("You must supply at least 1 argument, the name of the virtual network you wish to add.")
|
||||
}
|
||||
|
||||
warningChecker := updater.StartWarningCheck(c)
|
||||
defer warningChecker.LogWarningIfAny(sc.log)
|
||||
|
||||
args := c.Args()
|
||||
|
||||
name := args.Get(0)
|
||||
|
||||
comment := ""
|
||||
if c.NArg() >= 2 {
|
||||
comment = args.Get(1)
|
||||
}
|
||||
|
||||
newVnet := vnet.NewVirtualNetwork{
|
||||
Name: name,
|
||||
Comment: comment,
|
||||
IsDefault: c.Bool(makeDefaultFlag.Name),
|
||||
}
|
||||
createdVnet, err := sc.addVirtualNetwork(newVnet)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not add virtual network")
|
||||
}
|
||||
|
||||
extraMsg := ""
|
||||
if createdVnet.IsDefault {
|
||||
extraMsg = " (as the new default for this account) "
|
||||
}
|
||||
fmt.Printf(
|
||||
"Successfully added virtual 'network' %s with ID: %s%s\n"+
|
||||
"You can now add IP routes attached to this virtual network. See `cloudflared tunnel route ip add -help`\n",
|
||||
name, createdVnet.ID, extraMsg,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func listVirtualNetworksCommand(c *cli.Context) error {
|
||||
sc, err := newSubcommandContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warningChecker := updater.StartWarningCheck(c)
|
||||
defer warningChecker.LogWarningIfAny(sc.log)
|
||||
|
||||
filter, err := vnet.NewFromCLI(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid flags for filtering virtual networks")
|
||||
}
|
||||
|
||||
vnets, err := sc.listVirtualNetworks(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
|
||||
return renderOutput(outputFormat, vnets)
|
||||
}
|
||||
|
||||
if len(vnets) > 0 {
|
||||
formatAndPrintVnetsList(vnets)
|
||||
} else {
|
||||
fmt.Println("No virtual networks were found for the given filter flags. You can use 'cloudflared tunnel network add' to add a virtual network.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteVirtualNetworkCommand(c *cli.Context) error {
|
||||
sc, err := newSubcommandContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.NArg() != 1 {
|
||||
return errors.New("You must supply exactly one argument, either the ID or name of the virtual network to delete")
|
||||
}
|
||||
|
||||
input := c.Args().Get(0)
|
||||
vnetId, err := getVnetId(sc, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sc.deleteVirtualNetwork(vnetId); err != nil {
|
||||
return errors.Wrap(err, "API error")
|
||||
}
|
||||
fmt.Printf("Successfully deleted virtual network '%s'\n", input)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateVirtualNetworkCommand(c *cli.Context) error {
|
||||
sc, err := newSubcommandContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.NArg() != 1 {
|
||||
return errors.New(" You must supply exactly one argument, either the ID or (current) name of the virtual network to update")
|
||||
}
|
||||
|
||||
input := c.Args().Get(0)
|
||||
vnetId, err := getVnetId(sc, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updates := vnet.UpdateVirtualNetwork{}
|
||||
|
||||
if c.IsSet(newNameFlag.Name) {
|
||||
newName := c.String(newNameFlag.Name)
|
||||
updates.Name = &newName
|
||||
}
|
||||
if c.IsSet(newCommentFlag.Name) {
|
||||
newComment := c.String(newCommentFlag.Name)
|
||||
updates.Comment = &newComment
|
||||
}
|
||||
if c.IsSet(makeDefaultFlag.Name) {
|
||||
isDefault := c.Bool(makeDefaultFlag.Name)
|
||||
updates.IsDefault = &isDefault
|
||||
}
|
||||
|
||||
if err := sc.updateVirtualNetwork(vnetId, updates); err != nil {
|
||||
return errors.Wrap(err, "API error")
|
||||
}
|
||||
fmt.Printf("Successfully updated virtual network '%s'\n", input)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVnetId(sc *subcommandContext, input string) (uuid.UUID, error) {
|
||||
val, err := uuid.Parse(input)
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
filter := vnet.NewFilter()
|
||||
filter.WithDeleted(false)
|
||||
filter.ByName(input)
|
||||
|
||||
vnets, err := sc.listVirtualNetworks(filter)
|
||||
if err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
|
||||
if len(vnets) != 1 {
|
||||
return uuid.Nil, fmt.Errorf("there should only be 1 non-deleted virtual network named %s", input)
|
||||
}
|
||||
|
||||
return vnets[0].ID, nil
|
||||
}
|
||||
|
||||
func formatAndPrintVnetsList(vnets []*vnet.VirtualNetwork) {
|
||||
const (
|
||||
minWidth = 0
|
||||
tabWidth = 8
|
||||
padding = 1
|
||||
padChar = ' '
|
||||
flags = 0
|
||||
)
|
||||
|
||||
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
|
||||
defer writer.Flush()
|
||||
|
||||
_, _ = fmt.Fprintln(writer, "ID\tNAME\tIS DEFAULT\tCOMMENT\tCREATED\tDELETED\t")
|
||||
|
||||
for _, virtualNetwork := range vnets {
|
||||
formattedStr := virtualNetwork.TableString()
|
||||
_, _ = fmt.Fprintln(writer, formattedStr)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user