diff --git a/cfapi/client.go b/cfapi/client.go index fa5e7cb2..192d64ce 100644 --- a/cfapi/client.go +++ b/cfapi/client.go @@ -28,7 +28,7 @@ type IPRouteClient interface { type VnetClient interface { CreateVirtualNetwork(newVnet NewVirtualNetwork) (VirtualNetwork, error) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, error) - DeleteVirtualNetwork(id uuid.UUID) error + DeleteVirtualNetwork(id uuid.UUID, force bool) error UpdateVirtualNetwork(id uuid.UUID, updates UpdateVirtualNetwork) error } diff --git a/cfapi/virtual_network.go b/cfapi/virtual_network.go index e346678a..fce5d504 100644 --- a/cfapi/virtual_network.go +++ b/cfapi/virtual_network.go @@ -80,9 +80,16 @@ func (r *RESTClient) ListVirtualNetworks(filter *VnetFilter) ([]*VirtualNetwork, return nil, r.statusCodeToError("list virtual networks", resp) } -func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID) error { +func (r *RESTClient) DeleteVirtualNetwork(id uuid.UUID, force bool) error { endpoint := r.baseEndpoints.accountVnets endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String())) + + queryParams := url.Values{} + if force { + queryParams.Set("force", strconv.FormatBool(force)) + } + endpoint.RawQuery = queryParams.Encode() + resp, err := r.sendRequest("DELETE", endpoint, nil) if err != nil { return errors.Wrap(err, "REST request failed") diff --git a/cmd/cloudflared/tunnel/subcommand_context_vnets.go b/cmd/cloudflared/tunnel/subcommand_context_vnets.go index 14e055fe..c6e9adca 100644 --- a/cmd/cloudflared/tunnel/subcommand_context_vnets.go +++ b/cmd/cloudflared/tunnel/subcommand_context_vnets.go @@ -23,12 +23,12 @@ func (sc *subcommandContext) listVirtualNetworks(filter *cfapi.VnetFilter) ([]*c return client.ListVirtualNetworks(filter) } -func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID) error { +func (sc *subcommandContext) deleteVirtualNetwork(vnetId uuid.UUID, force bool) error { client, err := sc.client() if err != nil { return errors.Wrap(err, noClientMsg) } - return client.DeleteVirtualNetwork(vnetId) + return client.DeleteVirtualNetwork(vnetId, force) } func (sc *subcommandContext) updateVirtualNetwork(vnetId uuid.UUID, updates cfapi.UpdateVirtualNetwork) error { diff --git a/cmd/cloudflared/tunnel/vnets_subcommands.go b/cmd/cloudflared/tunnel/vnets_subcommands.go index 4a65ad96..57c350c9 100644 --- a/cmd/cloudflared/tunnel/vnets_subcommands.go +++ b/cmd/cloudflared/tunnel/vnets_subcommands.go @@ -33,6 +33,12 @@ var ( Aliases: []string{"c"}, Usage: "A new comment describing the purpose of the virtual network.", } + vnetForceDeleteFlag = &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Force the deletion of the virtual network even if it is being relied upon by other resources. Those" + + "resources will either be deleted (e.g. IP Routes) or moved to the current default virutal network.", + } ) func buildVirtualNetworkSubcommand(hidden bool) *cli.Command { @@ -82,6 +88,7 @@ be the current default.`, 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.`, + Flags: []cli.Flag{vnetForceDeleteFlag}, Hidden: hidden, }, { @@ -188,7 +195,7 @@ func deleteVirtualNetworkCommand(c *cli.Context) error { if err != nil { return err } - if c.NArg() != 1 { + if c.NArg() < 1 { return errors.New("You must supply exactly one argument, either the ID or name of the virtual network to delete") } @@ -198,7 +205,12 @@ func deleteVirtualNetworkCommand(c *cli.Context) error { return err } - if err := sc.deleteVirtualNetwork(vnetId); err != nil { + forceDelete := false + if c.IsSet(vnetForceDeleteFlag.Name) { + forceDelete = c.Bool(vnetForceDeleteFlag.Name) + } + + if err := sc.deleteVirtualNetwork(vnetId, forceDelete); err != nil { return errors.Wrap(err, "API error") } fmt.Printf("Successfully deleted virtual network '%s'\n", input)