github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/runner/jujuc/network-get.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuc
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/cmd/v3"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/gnuflag"
    12  
    13  	jujucmd "github.com/juju/juju/cmd"
    14  	"github.com/juju/juju/rpc/params"
    15  )
    16  
    17  // NetworkGetCommand implements the network-get command.
    18  type NetworkGetCommand struct {
    19  	cmd.CommandBase
    20  	ctx Context
    21  
    22  	RelationId      int
    23  	relationIdProxy gnuflag.Value
    24  
    25  	bindingName string
    26  
    27  	bindAddress    bool
    28  	ingressAddress bool
    29  	egressSubnets  bool
    30  	keys           []string
    31  
    32  	// deprecated
    33  	primaryAddress bool
    34  
    35  	out cmd.Output
    36  }
    37  
    38  func NewNetworkGetCommand(ctx Context) (_ cmd.Command, err error) {
    39  	cmd := &NetworkGetCommand{ctx: ctx}
    40  	cmd.relationIdProxy, err = NewRelationIdValue(ctx, &cmd.RelationId)
    41  	if err != nil {
    42  		return nil, errors.Trace(err)
    43  	}
    44  
    45  	return cmd, nil
    46  }
    47  
    48  // Info is part of the cmd.Command interface.
    49  func (c *NetworkGetCommand) Info() *cmd.Info {
    50  	args := "<binding-name> [--ingress-address] [--bind-address] [--egress-subnets]"
    51  	doc := `
    52  network-get returns the network config for a given binding name. By default
    53  it returns the list of interfaces and associated addresses in the space for
    54  the binding, as well as the ingress address for the binding. If defined, any
    55  egress subnets are also returned.
    56  If one of the following flags are specified, just that value is returned.
    57  If more than one flag is specified, a map of values is returned.
    58      --bind-address: the address the local unit should listen on to serve connections, as well
    59                      as the address that should be advertised to its peers.
    60      --ingress-address: the address the local unit should advertise as being used for incoming connections.
    61      --egress-subnets: subnets (in CIDR notation) from which traffic on this relation will originate.
    62  `
    63  	return jujucmd.Info(&cmd.Info{
    64  		Name:    "network-get",
    65  		Args:    args,
    66  		Purpose: "get network config",
    67  		Doc:     doc,
    68  	})
    69  }
    70  
    71  // SetFlags is part of the cmd.Command interface.
    72  func (c *NetworkGetCommand) SetFlags(f *gnuflag.FlagSet) {
    73  	c.out.AddFlags(f, "smart", cmd.DefaultFormatters.Formatters())
    74  	f.BoolVar(&c.primaryAddress, "primary-address", false, "(deprecated) get the primary address for the binding")
    75  	f.BoolVar(&c.bindAddress, "bind-address", false, "get the address for the binding on which the unit should listen")
    76  	f.BoolVar(&c.ingressAddress, "ingress-address", false, "get the ingress address for the binding")
    77  	f.BoolVar(&c.egressSubnets, "egress-subnets", false, "get the egress subnets for the binding")
    78  	f.Var(c.relationIdProxy, "r", "specify a relation by id")
    79  	f.Var(c.relationIdProxy, "relation", "")
    80  }
    81  
    82  const (
    83  	bindAddressKey    = "bind-address"
    84  	ingressAddressKey = "ingress-address"
    85  	egressSubnetsKey  = "egress-subnets"
    86  )
    87  
    88  // Init is part of the cmd.Command interface.
    89  func (c *NetworkGetCommand) Init(args []string) error {
    90  	if len(args) < 1 {
    91  		return errors.New("no arguments specified")
    92  	}
    93  	c.bindingName = args[0]
    94  	if c.bindingName == "" {
    95  		return fmt.Errorf("no binding name specified")
    96  	}
    97  	if c.bindAddress {
    98  		c.keys = append(c.keys, bindAddressKey)
    99  	}
   100  	if c.ingressAddress {
   101  		c.keys = append(c.keys, ingressAddressKey)
   102  	}
   103  	if c.egressSubnets {
   104  		c.keys = append(c.keys, egressSubnetsKey)
   105  	}
   106  
   107  	return cmd.CheckEmpty(args[1:])
   108  }
   109  
   110  func (c *NetworkGetCommand) Run(ctx *cmd.Context) error {
   111  	netInfo, err := c.ctx.NetworkInfo([]string{c.bindingName}, c.RelationId)
   112  	if err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  
   116  	ni, ok := netInfo[c.bindingName]
   117  	if !ok || len(ni.Info) == 0 {
   118  		return fmt.Errorf("no network config found for binding %q", c.bindingName)
   119  	}
   120  	if ni.Error != nil {
   121  		return errors.Trace(ni.Error)
   122  	}
   123  
   124  	// If no specific attributes were asked for, write everything we know.
   125  	if !c.primaryAddress && len(c.keys) == 0 {
   126  		return c.out.Write(ctx, resultToDisplay(ni))
   127  	}
   128  
   129  	// Backwards compatibility - we just want the primary address.
   130  	if c.primaryAddress {
   131  		if c.ingressAddress || c.egressSubnets || c.bindAddress {
   132  			return fmt.Errorf("--primary-address must be the only flag specified")
   133  		}
   134  		if len(ni.Info[0].Addresses) == 0 {
   135  			return fmt.Errorf("no addresses attached to space for binding %q", c.bindingName)
   136  		}
   137  		return c.out.Write(ctx, ni.Info[0].Addresses[0].Address)
   138  	}
   139  
   140  	// Write the specific articles requested.
   141  	keyValues := make(map[string]interface{})
   142  	if c.egressSubnets {
   143  		keyValues[egressSubnetsKey] = ni.EgressSubnets
   144  	}
   145  	if c.ingressAddress {
   146  		var ingressAddress string
   147  		if len(ni.IngressAddresses) == 0 {
   148  			if len(ni.Info[0].Addresses) == 0 {
   149  				return fmt.Errorf("no addresses attached to space for binding %q", c.bindingName)
   150  			}
   151  			ingressAddress = ni.Info[0].Addresses[0].Address
   152  		} else {
   153  			ingressAddress = ni.IngressAddresses[0]
   154  		}
   155  		keyValues[ingressAddressKey] = ingressAddress
   156  	}
   157  	if c.bindAddress {
   158  		keyValues[bindAddressKey] = ni.Info[0].Addresses[0].Address
   159  	}
   160  	if len(c.keys) == 1 {
   161  		return c.out.Write(ctx, keyValues[c.keys[0]])
   162  	}
   163  	return c.out.Write(ctx, keyValues)
   164  }
   165  
   166  // These display types are used for serialising to stdout.
   167  // We should never write raw params structs.
   168  
   169  // interfaceAddressDisplay mirrors params.InterfaceAddress.
   170  type interfaceAddressDisplay struct {
   171  	Hostname string `json:"hostname" yaml:"hostname"`
   172  	Address  string `json:"value" yaml:"value"`
   173  	CIDR     string `json:"cidr" yaml:"cidr"`
   174  
   175  	// This copy is used to preserve YAML serialisation that older agents
   176  	// may be expecting. Delete them for Juju 3/4.
   177  	AddressX string `json:"-" yaml:"address"`
   178  }
   179  
   180  // networkInfoDisplay mirrors params.NetworkInfo.
   181  type networkInfoDisplay struct {
   182  	MACAddress    string                    `json:"mac-address" yaml:"mac-address"`
   183  	InterfaceName string                    `json:"interface-name" yaml:"interface-name"`
   184  	Addresses     []interfaceAddressDisplay `json:"addresses" yaml:"addresses"`
   185  
   186  	// These copies are used to preserve YAML serialisation that older agents
   187  	// may be expecting. Delete them for Juju 3/4.
   188  	MACAddressX    string `json:"-" yaml:"macaddress"`
   189  	InterfaceNameX string `json:"-" yaml:"interfacename"`
   190  }
   191  
   192  // networkInfoResultDisplay mirrors params.NetworkInfoResult except for the
   193  // Error member. It is assumed that we check it for nil before conversion.
   194  type networkInfoResultDisplay struct {
   195  	Info             []networkInfoDisplay `json:"bind-addresses,omitempty" yaml:"bind-addresses,omitempty"`
   196  	EgressSubnets    []string             `json:"egress-subnets,omitempty" yaml:"egress-subnets,omitempty"`
   197  	IngressAddresses []string             `json:"ingress-addresses,omitempty" yaml:"ingress-addresses,omitempty"`
   198  }
   199  
   200  func resultToDisplay(result params.NetworkInfoResult) networkInfoResultDisplay {
   201  	display := networkInfoResultDisplay{
   202  		Info:             make([]networkInfoDisplay, len(result.Info)),
   203  		EgressSubnets:    make([]string, len(result.EgressSubnets)),
   204  		IngressAddresses: make([]string, len(result.IngressAddresses)),
   205  	}
   206  
   207  	copy(display.EgressSubnets, result.EgressSubnets)
   208  	copy(display.IngressAddresses, result.IngressAddresses)
   209  
   210  	for i, rInfo := range result.Info {
   211  		dInfo := networkInfoDisplay{
   212  			MACAddress:    rInfo.MACAddress,
   213  			InterfaceName: rInfo.InterfaceName,
   214  			Addresses:     make([]interfaceAddressDisplay, len(rInfo.Addresses)),
   215  
   216  			MACAddressX:    rInfo.MACAddress,
   217  			InterfaceNameX: rInfo.InterfaceName,
   218  		}
   219  
   220  		for j, addr := range rInfo.Addresses {
   221  			dInfo.Addresses[j] = interfaceAddressDisplay{
   222  				Hostname: addr.Hostname,
   223  				Address:  addr.Address,
   224  				CIDR:     addr.CIDR,
   225  
   226  				AddressX: addr.Address,
   227  			}
   228  		}
   229  
   230  		display.Info[i] = dInfo
   231  	}
   232  
   233  	return display
   234  }