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 }