mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
994 lines
28 KiB
Go
994 lines
28 KiB
Go
package main
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"maps"
|
||
"os"
|
||
"sort"
|
||
"strings"
|
||
|
||
"github.com/spf13/cobra"
|
||
"go.yaml.in/yaml/v4"
|
||
|
||
"github.com/lxc/incus/v7/cmd/incus/color"
|
||
u "github.com/lxc/incus/v7/cmd/incus/usage"
|
||
"github.com/lxc/incus/v7/internal/i18n"
|
||
"github.com/lxc/incus/v7/shared/api"
|
||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||
"github.com/lxc/incus/v7/shared/termios"
|
||
)
|
||
|
||
type cmdNetworkForward struct {
|
||
global *cmdGlobal
|
||
flagTarget string
|
||
}
|
||
|
||
func (c *cmdNetworkForward) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("forward")
|
||
cmd.Short = i18n.G("Manage network forwards")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Manage network forwards"))
|
||
|
||
// List.
|
||
networkForwardListCmd := cmdNetworkForwardList{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardListCmd.command())
|
||
|
||
// Show.
|
||
networkForwardShowCmd := cmdNetworkForwardShow{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardShowCmd.command())
|
||
|
||
// Create.
|
||
networkForwardCreateCmd := cmdNetworkForwardCreate{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardCreateCmd.command())
|
||
|
||
// Get.
|
||
networkForwardGetCmd := cmdNetworkForwardGet{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardGetCmd.command())
|
||
|
||
// Set.
|
||
networkForwardSetCmd := cmdNetworkForwardSet{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardSetCmd.command())
|
||
|
||
// Unset.
|
||
networkForwardUnsetCmd := cmdNetworkForwardUnset{global: c.global, networkForward: c, networkForwardSet: &networkForwardSetCmd}
|
||
cmd.AddCommand(networkForwardUnsetCmd.command())
|
||
|
||
// Edit.
|
||
networkForwardEditCmd := cmdNetworkForwardEdit{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardEditCmd.command())
|
||
|
||
// Delete.
|
||
networkForwardDeleteCmd := cmdNetworkForwardDelete{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardDeleteCmd.command())
|
||
|
||
// Port.
|
||
networkForwardPortCmd := cmdNetworkForwardPort{global: c.global, networkForward: c}
|
||
cmd.AddCommand(networkForwardPortCmd.command())
|
||
|
||
// Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706
|
||
cmd.Args = cobra.NoArgs
|
||
cmd.Run = func(cmd *cobra.Command, _ []string) { _ = cmd.Usage() }
|
||
return cmd
|
||
}
|
||
|
||
// List.
|
||
type cmdNetworkForwardList struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
|
||
flagFormat string
|
||
flagColumns string
|
||
}
|
||
|
||
type networkForwardColumn struct {
|
||
Name string
|
||
Data func(api.NetworkForward) string
|
||
}
|
||
|
||
var cmdNetworkForwardListUsage = u.Usage{u.Network.Remote()}
|
||
|
||
func (c *cmdNetworkForwardList) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("list", cmdNetworkForwardListUsage...)
|
||
cmd.Aliases = []string{"ls"}
|
||
cmd.Short = i18n.G("List available network forwards")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||
`List available network forwards
|
||
|
||
Default column layout: ldDp
|
||
|
||
== Columns ==
|
||
The -c option takes a comma separated list of arguments that control
|
||
which network forward attributes to output when displaying
|
||
in table or csv format.
|
||
|
||
Column arguments are either pre-defined shorthand chars (see below),
|
||
or (extended) config keys.
|
||
|
||
Commas between consecutive shorthand chars are optional.
|
||
|
||
Pre-defined column shorthand chars:
|
||
l - Listen Address
|
||
d - Description
|
||
D - Default Target Address
|
||
p - Port
|
||
L - Location of the network zone (e.g. its cluster member)`,
|
||
))
|
||
|
||
cmd.RunE = c.run
|
||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultNetworkForwardColumns, "", i18n.G("Columns"))
|
||
|
||
cmd.PreRunE = func(cmd *cobra.Command, _ []string) error {
|
||
return cli.ValidateFlagFormatForListOutput(cmd.Flag("format").Value.String())
|
||
}
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
const defaultNetworkForwardColumns = "ldDp"
|
||
|
||
func (c *cmdNetworkForwardList) parseColumns(clustered bool) ([]networkForwardColumn, error) {
|
||
columnsShorthandMap := map[rune]networkForwardColumn{
|
||
'l': {i18n.G("LISTEN ADDRESS"), c.listenAddressColumnData},
|
||
'd': {i18n.G("DESCRIPTION"), c.descriptionColumnData},
|
||
'D': {i18n.G("DEFAULT TARGET ADDRESS"), c.defaultTargetAddressColumnData},
|
||
'p': {i18n.G("PORTS"), c.portsColumnData},
|
||
'L': {i18n.G("LOCATION"), c.locationColumnData},
|
||
}
|
||
|
||
columnList := strings.Split(c.flagColumns, ",")
|
||
columns := []networkForwardColumn{}
|
||
if c.flagColumns == defaultNetworkForwardColumns && clustered {
|
||
columnList = append(columnList, "L")
|
||
}
|
||
|
||
for _, columnEntry := range columnList {
|
||
if columnEntry == "" {
|
||
return nil, fmt.Errorf(i18n.G("Empty column entry (redundant, leading or trailing command) in '%s'"), c.flagColumns)
|
||
}
|
||
|
||
for _, columnRune := range columnEntry {
|
||
column, ok := columnsShorthandMap[columnRune]
|
||
if !ok {
|
||
return nil, fmt.Errorf(i18n.G("Unknown column shorthand char '%c' in '%s'"), columnRune, columnEntry)
|
||
}
|
||
|
||
columns = append(columns, column)
|
||
}
|
||
}
|
||
|
||
return columns, nil
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) listenAddressColumnData(forward api.NetworkForward) string {
|
||
return forward.ListenAddress
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) descriptionColumnData(forward api.NetworkForward) string {
|
||
return forward.Description
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) defaultTargetAddressColumnData(forward api.NetworkForward) string {
|
||
return forward.Config["target_address"]
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) portsColumnData(forward api.NetworkForward) string {
|
||
return fmt.Sprintf("%d", len(forward.Ports))
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) locationColumnData(forward api.NetworkForward) string {
|
||
return forward.Location
|
||
}
|
||
|
||
func (c *cmdNetworkForwardList) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardListUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
|
||
forwards, err := d.GetNetworkForwards(networkName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Parse column flags.
|
||
columns, err := c.parseColumns(d.IsClustered())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
data := make([][]string, 0, len(forwards))
|
||
for _, forward := range forwards {
|
||
line := []string{}
|
||
for _, column := range columns {
|
||
line = append(line, column.Data(forward))
|
||
}
|
||
|
||
data = append(data, line)
|
||
}
|
||
|
||
sort.Sort(cli.SortColumnsNaturally(data))
|
||
|
||
header := []string{}
|
||
for _, column := range columns {
|
||
header = append(header, column.Name)
|
||
}
|
||
|
||
return cli.RenderTable(os.Stdout, c.flagFormat, header, data, forwards)
|
||
}
|
||
|
||
// Show.
|
||
type cmdNetworkForwardShow struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
}
|
||
|
||
var cmdNetworkForwardShowUsage = u.Usage{u.Network.Remote(), u.ListenAddress}
|
||
|
||
func (c *cmdNetworkForwardShow) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("show", cmdNetworkForwardShowUsage...)
|
||
cmd.Short = i18n.G("Show network forward configurations")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Show network forward configurations"))
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardShow) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardShowUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// Show the network forward config.
|
||
forward, _, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
data, err := yaml.Dump(&forward, yaml.WithV2Defaults())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Printf("%s", data)
|
||
|
||
return nil
|
||
}
|
||
|
||
// Create.
|
||
type cmdNetworkForwardCreate struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
|
||
flagDescription string
|
||
}
|
||
|
||
var cmdNetworkForwardCreateUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.KV.List(0)}
|
||
|
||
func (c *cmdNetworkForwardCreate) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("create", cmdNetworkForwardCreateUsage...)
|
||
cmd.Aliases = []string{"add"}
|
||
cmd.Short = i18n.G("Create new network forwards")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Create new network forwards"))
|
||
cmd.Example = cli.FormatSection("", i18n.G(`incus network forward create n1 127.0.0.1
|
||
|
||
incus network forward create n1 127.0.0.1 < config.yaml
|
||
Create a new network forward for network n1 from config.yaml`))
|
||
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
cli.AddStringFlag(cmd.Flags(), &c.flagDescription, "description", "", "", i18n.G("Network forward description"))
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardCreate) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardCreateUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
keys, err := kvToMap(parsed[2])
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// If stdin isn't a terminal, read yaml from it.
|
||
var forwardPut api.NetworkForwardPut
|
||
if !termios.IsTerminal(getStdinFd()) {
|
||
loader, err := yaml.NewLoader(os.Stdin, yaml.WithKnownFields())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
err = loader.Load(&forwardPut)
|
||
if err != nil && !errors.Is(err, io.EOF) {
|
||
return err
|
||
}
|
||
}
|
||
|
||
if forwardPut.Config == nil {
|
||
forwardPut.Config = map[string]string{}
|
||
}
|
||
|
||
maps.Copy(forwardPut.Config, keys)
|
||
|
||
// Create the network forward.
|
||
forward := api.NetworkForwardsPost{
|
||
ListenAddress: listenAddress,
|
||
NetworkForwardPut: forwardPut,
|
||
}
|
||
|
||
if c.flagDescription != "" {
|
||
forward.Description = c.flagDescription
|
||
}
|
||
|
||
forward.Normalise()
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
err = d.CreateNetworkForward(networkName, forward)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if !c.global.flagQuiet {
|
||
fmt.Printf(i18n.G("Network forward %s created")+"\n", forward.ListenAddress)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Get.
|
||
type cmdNetworkForwardGet struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
|
||
flagIsProperty bool
|
||
}
|
||
|
||
var cmdNetworkForwardGetUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.Key}
|
||
|
||
func (c *cmdNetworkForwardGet) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("get", cmdNetworkForwardGetUsage...)
|
||
cmd.Short = i18n.G("Get values for network forward configuration keys")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Get values for network forward configuration keys"))
|
||
|
||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Get the key as a network forward property"))
|
||
cmd.RunE = c.run
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
if len(args) == 2 {
|
||
return c.global.cmpNetworkForwardConfigs(args[0], args[1])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardGet) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardGetUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
key := parsed[2].String
|
||
|
||
// Get the current config.
|
||
forward, _, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if c.flagIsProperty {
|
||
w := forward.Writable()
|
||
res, err := getFieldByJSONTag(&w, key)
|
||
if err != nil {
|
||
return fmt.Errorf(i18n.G("The property %q does not exist on the network forward %q: %v"), key, listenAddress, err)
|
||
}
|
||
|
||
fmt.Printf("%v\n", res)
|
||
} else {
|
||
for k, v := range forward.Config {
|
||
if k == key {
|
||
fmt.Printf("%s\n", v)
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Set.
|
||
type cmdNetworkForwardSet struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
|
||
flagIsProperty bool
|
||
}
|
||
|
||
var cmdNetworkForwardSetUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.LegacyKV.List(1)}
|
||
|
||
func (c *cmdNetworkForwardSet) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("set", cmdNetworkForwardSetUsage...)
|
||
cmd.Short = i18n.G("Set network forward keys")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||
`Set network forward keys
|
||
|
||
For backward compatibility, a single configuration key may still be set with:
|
||
incus network set [<remote>:]<network> <listen_address> <key> <value>`,
|
||
))
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Set the key as a network forward property"))
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
// set runs the post-parsing command logic.
|
||
func (c *cmdNetworkForwardSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
keys, err := kvToMap(parsed[2])
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// Get the current config.
|
||
forward, etag, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if forward.Config == nil {
|
||
forward.Config = map[string]string{}
|
||
}
|
||
|
||
writable := forward.Writable()
|
||
if c.flagIsProperty {
|
||
if cmd.Name() == "unset" {
|
||
for k := range keys {
|
||
err := unsetFieldByJSONTag(&writable, k)
|
||
if err != nil {
|
||
return fmt.Errorf(i18n.G("Error unsetting property: %v"), err)
|
||
}
|
||
}
|
||
} else {
|
||
err := unpackKVToWritable(&writable, keys)
|
||
if err != nil {
|
||
return fmt.Errorf(i18n.G("Error setting properties: %v"), err)
|
||
}
|
||
}
|
||
} else {
|
||
maps.Copy(writable.Config, keys)
|
||
}
|
||
|
||
writable.Normalise()
|
||
|
||
return d.UpdateNetworkForward(networkName, forward.ListenAddress, writable, etag)
|
||
}
|
||
|
||
func (c *cmdNetworkForwardSet) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardSetUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return c.set(cmd, parsed)
|
||
}
|
||
|
||
// Unset.
|
||
type cmdNetworkForwardUnset struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
networkForwardSet *cmdNetworkForwardSet
|
||
|
||
flagIsProperty bool
|
||
}
|
||
|
||
var cmdNetworkForwardUnsetUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.Key.List(1)}
|
||
|
||
func (c *cmdNetworkForwardUnset) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("unset", cmdNetworkForwardUnsetUsage...)
|
||
cmd.Short = i18n.G("Unset network forward configuration keys")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Unset network forward keys"))
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the keys as network forward properties"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
if len(args) == 2 {
|
||
return c.global.cmpNetworkForwardConfigs(args[0], args[1])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardUnset) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardUnsetUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
c.networkForwardSet.flagIsProperty = c.flagIsProperty
|
||
return unsetKey(c.networkForwardSet, cmd, parsed)
|
||
}
|
||
|
||
// Edit.
|
||
type cmdNetworkForwardEdit struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
}
|
||
|
||
var cmdNetworkForwardEditUsage = u.Usage{u.Network.Remote(), u.ListenAddress}
|
||
|
||
func (c *cmdNetworkForwardEdit) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("edit", cmdNetworkForwardEditUsage...)
|
||
cmd.Short = i18n.G("Edit network forward configurations as YAML")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Edit network forward configurations as YAML"))
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardEdit) helpTemplate() string {
|
||
return i18n.G(
|
||
`### This is a YAML representation of the network forward.
|
||
### Any line starting with a '# will be ignored.
|
||
###
|
||
### A network forward consists of a default target address and optional set of port forwards for a listen address.
|
||
###
|
||
### An example would look like:
|
||
### listen_address: 192.0.2.1
|
||
### config:
|
||
### target_address: 198.51.100.2
|
||
### description: test desc
|
||
### ports:
|
||
### - description: port forward
|
||
### protocol: tcp
|
||
### listen_port: 80,81,8080-8090
|
||
### target_address: 198.51.100.3
|
||
### target_port: 80,81,8080-8090
|
||
### location: server01
|
||
###
|
||
### Note that the listen_address and location cannot be changed.`,
|
||
)
|
||
}
|
||
|
||
func (c *cmdNetworkForwardEdit) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardEditUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// If stdin isn't a terminal, read text from it
|
||
if !termios.IsTerminal(getStdinFd()) {
|
||
loader, err := yaml.NewLoader(os.Stdin, yaml.WithKnownFields())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Allow output of `incus network forward show` command to be passed in here, but only take the
|
||
// contents of the NetworkForwardPut fields when updating. The other fields are silently discarded.
|
||
newData := api.NetworkForward{}
|
||
err = loader.Load(&newData)
|
||
if err != nil && !errors.Is(err, io.EOF) {
|
||
return err
|
||
}
|
||
|
||
newData.Normalise()
|
||
|
||
return d.UpdateNetworkForward(networkName, listenAddress, newData.NetworkForwardPut, "")
|
||
}
|
||
|
||
// Get the current config.
|
||
forward, etag, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
data, err := yaml.Dump(&forward, yaml.WithV2Defaults())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Spawn the editor.
|
||
content, err := cli.TextEditor("", []byte(c.helpTemplate()+"\n\n"+string(data)))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for {
|
||
// Parse the text received from the editor.
|
||
newData := api.NetworkForward{} // We show the full info, but only send the writable fields.
|
||
err = yaml.Load(content, &newData, yaml.WithKnownFields())
|
||
if err == nil {
|
||
newData.Normalise()
|
||
err = d.UpdateNetworkForward(networkName, listenAddress, newData.Writable(), etag)
|
||
}
|
||
|
||
// Respawn the editor.
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, i18n.G("Config parsing error: %s")+"\n", err)
|
||
fmt.Println(i18n.G("Press enter to open the editor again or ctrl+c to abort change"))
|
||
|
||
_, err := os.Stdin.Read(make([]byte, 1))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
content, err = cli.TextEditor("", content)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
continue
|
||
}
|
||
|
||
break
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Delete.
|
||
type cmdNetworkForwardDelete struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
}
|
||
|
||
var cmdNetworkForwardDeleteUsage = u.Usage{u.Network.Remote(), u.ListenAddress}
|
||
|
||
func (c *cmdNetworkForwardDelete) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("delete", cmdNetworkForwardDeleteUsage...)
|
||
cmd.Aliases = []string{"rm", "remove"}
|
||
cmd.Short = i18n.G("Delete network forwards")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Delete network forwards"))
|
||
cmd.RunE = c.run
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardDelete) run(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardDeleteUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// Delete the network forward.
|
||
err = d.DeleteNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if !c.global.flagQuiet {
|
||
fmt.Printf(i18n.G("Network forward %s deleted")+"\n", listenAddress)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// Add/Remove Port.
|
||
type cmdNetworkForwardPort struct {
|
||
global *cmdGlobal
|
||
networkForward *cmdNetworkForward
|
||
flagRemoveForce bool
|
||
flagDescription string
|
||
}
|
||
|
||
func (c *cmdNetworkForwardPort) command() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("port")
|
||
cmd.Short = i18n.G("Manage network forward ports")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Manage network forward ports"))
|
||
|
||
// Port Add.
|
||
cmd.AddCommand(c.commandAdd())
|
||
|
||
// Port Remove.
|
||
cmd.AddCommand(c.commandRemove())
|
||
|
||
return cmd
|
||
}
|
||
|
||
var cmdNetworkForwardPortAddUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.Protocol, u.ListenPort.List(1, ","), u.Target(u.Address), u.Target(u.Port).List(0, ",")}
|
||
|
||
func (c *cmdNetworkForwardPort) commandAdd() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("add", cmdNetworkForwardPortAddUsage...)
|
||
cmd.Aliases = []string{"create"}
|
||
cmd.Short = i18n.G("Add ports to a forward")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Add ports to a forward"))
|
||
cmd.RunE = c.runAdd
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
cli.AddStringFlag(cmd.Flags(), &c.flagDescription, "description", "", "", i18n.G("Port description"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
if len(args) == 2 {
|
||
return []string{"tcp", "udp"}, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardPort) runAdd(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardPortAddUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
protocol := parsed[2].String
|
||
// Only the list’s string representation is used.
|
||
listenPorts := parsed[3].String
|
||
targetAddress := parsed[4].String
|
||
// Only the list’s string representation is used.
|
||
targetPorts := parsed[5].String
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// Get the network forward.
|
||
forward, etag, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
forward.Ports = append(forward.Ports, api.NetworkForwardPort{
|
||
Protocol: protocol,
|
||
ListenPort: listenPorts,
|
||
TargetAddress: targetAddress,
|
||
TargetPort: targetPorts,
|
||
Description: c.flagDescription,
|
||
})
|
||
|
||
forward.Normalise()
|
||
|
||
return d.UpdateNetworkForward(networkName, forward.ListenAddress, forward.Writable(), etag)
|
||
}
|
||
|
||
var cmdNetworkForwardPortRemoveUsage = u.Usage{u.Network.Remote(), u.ListenAddress, u.Protocol.Optional(u.ListenPort.List(0, ","))}
|
||
|
||
func (c *cmdNetworkForwardPort) commandRemove() *cobra.Command {
|
||
cmd := &cobra.Command{}
|
||
cmd.Use = cli.U("remove", cmdNetworkForwardPortRemoveUsage...)
|
||
cmd.Aliases = []string{"delete", "rm"}
|
||
cmd.Short = i18n.G("Remove ports from a forward")
|
||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G("Remove ports from a forward"))
|
||
cli.AddBoolFlag(cmd.Flags(), &c.flagRemoveForce, "force|f", i18n.G("Remove all ports that match"))
|
||
cmd.RunE = c.runRemove
|
||
|
||
cli.AddStringFlag(cmd.Flags(), &c.networkForward.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||
|
||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||
if len(args) == 0 {
|
||
return c.global.cmpNetworks(toComplete)
|
||
}
|
||
|
||
if len(args) == 1 {
|
||
return c.global.cmpNetworkForwards(args[0])
|
||
}
|
||
|
||
if len(args) == 2 {
|
||
return []string{"tcp", "udp"}, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||
}
|
||
|
||
return cmd
|
||
}
|
||
|
||
func (c *cmdNetworkForwardPort) runRemove(cmd *cobra.Command, args []string) error {
|
||
parsed, err := c.global.Parse(cmdNetworkForwardPortRemoveUsage, cmd, args)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
d := parsed[0].RemoteServer
|
||
networkName := parsed[0].RemoteObject.String
|
||
listenAddress := parsed[1].String
|
||
hasProtocol := !parsed[2].Skipped
|
||
protocol := ""
|
||
hasListenPorts := false
|
||
listenPorts := ""
|
||
if hasProtocol {
|
||
protocol = parsed[2].List[0].String
|
||
hasListenPorts = !parsed[2].List[1].Skipped
|
||
// Only the list’s string representation is used.
|
||
listenPorts = parsed[2].List[1].String
|
||
}
|
||
|
||
// If a target was specified, create the forward on the given member.
|
||
if c.networkForward.flagTarget != "" {
|
||
d = d.UseTarget(c.networkForward.flagTarget)
|
||
}
|
||
|
||
// Get the network forward.
|
||
forward, etag, err := d.GetNetworkForward(networkName, listenAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
removed := false
|
||
newPorts := make([]api.NetworkForwardPort, 0, len(forward.Ports))
|
||
|
||
for _, port := range forward.Ports {
|
||
if hasProtocol && port.Protocol != protocol || hasListenPorts && port.ListenPort != listenPorts {
|
||
newPorts = append(newPorts, port)
|
||
} else {
|
||
if removed && !c.flagRemoveForce {
|
||
return errors.New(i18n.G("Multiple ports match. Use --force to remove them all"))
|
||
}
|
||
|
||
removed = true
|
||
}
|
||
}
|
||
|
||
if !removed {
|
||
return errors.New(i18n.G("No matching port(s) found"))
|
||
}
|
||
|
||
forward.Ports = newPorts
|
||
forward.Normalise()
|
||
|
||
return d.UpdateNetworkForward(networkName, forward.ListenAddress, forward.Writable(), etag)
|
||
}
|