github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "net" 9 10 "github.com/juju/cmd" 11 "github.com/juju/errors" 12 "github.com/juju/gnuflag" 13 14 "github.com/juju/juju/apiserver/params" 15 jujucmd "github.com/juju/juju/cmd" 16 ) 17 18 // NetworkGetCommand implements the network-get command. 19 type NetworkGetCommand struct { 20 cmd.CommandBase 21 ctx Context 22 23 RelationId int 24 relationIdProxy gnuflag.Value 25 26 bindingName string 27 28 bindAddress bool 29 ingressAddress bool 30 egressSubnets bool 31 keys []string 32 33 // deprecated 34 primaryAddress bool 35 36 out cmd.Output 37 } 38 39 type resolver func(host string) (addrs []string, err error) 40 41 var LookupHost resolver = net.LookupHost 42 43 func NewNetworkGetCommand(ctx Context) (_ cmd.Command, err error) { 44 cmd := &NetworkGetCommand{ctx: ctx} 45 cmd.relationIdProxy, err = NewRelationIdValue(ctx, &cmd.RelationId) 46 if err != nil { 47 return nil, errors.Trace(err) 48 } 49 50 return cmd, nil 51 } 52 53 // Info is part of the cmd.Command interface. 54 func (c *NetworkGetCommand) Info() *cmd.Info { 55 args := "<binding-name> [--ingress-address] [--bind-address] [--egress-subnets]" 56 doc := ` 57 network-get returns the network config for a given binding name. By default 58 it returns the list of interfaces and associated addresses in the space for 59 the binding, as well as the ingress address for the binding. If defined, any 60 egress subnets are also returned. 61 If one of the following flags are specified, just that value is returned. 62 If more than one flag is specified, a map of values is returned. 63 --bind-address: the address the local unit should listen on to serve connections, as well 64 as the address that should be advertised to its peers. 65 --ingress-address: the address the local unit should advertise as being used for incoming connections. 66 --egress_subnets: subnets (in CIDR notation) from which traffic on this relation will originate. 67 ` 68 return jujucmd.Info(&cmd.Info{ 69 Name: "network-get", 70 Args: args, 71 Purpose: "get network config", 72 Doc: doc, 73 }) 74 } 75 76 // SetFlags is part of the cmd.Command interface. 77 func (c *NetworkGetCommand) SetFlags(f *gnuflag.FlagSet) { 78 c.out.AddFlags(f, "smart", cmd.DefaultFormatters) 79 f.BoolVar(&c.primaryAddress, "primary-address", false, "(deprecated) get the primary address for the binding") 80 f.BoolVar(&c.bindAddress, "bind-address", false, "get the address for the binding on which the unit should listen") 81 f.BoolVar(&c.ingressAddress, "ingress-address", false, "get the ingress address for the binding") 82 f.BoolVar(&c.egressSubnets, "egress-subnets", false, "get the egress subnets for the binding") 83 f.Var(c.relationIdProxy, "r", "specify a relation by id") 84 f.Var(c.relationIdProxy, "relation", "") 85 } 86 87 const ( 88 bindAddressKey = "bind-address" 89 ingressAddressKey = "ingress-address" 90 egressSubnetsKey = "egress-subnets" 91 ) 92 93 // Init is part of the cmd.Command interface. 94 func (c *NetworkGetCommand) Init(args []string) error { 95 if len(args) < 1 { 96 return errors.New("no arguments specified") 97 } 98 c.bindingName = args[0] 99 if c.bindingName == "" { 100 return fmt.Errorf("no binding name specified") 101 } 102 if c.bindAddress { 103 c.keys = append(c.keys, bindAddressKey) 104 } 105 if c.ingressAddress { 106 c.keys = append(c.keys, ingressAddressKey) 107 } 108 if c.egressSubnets { 109 c.keys = append(c.keys, egressSubnetsKey) 110 } 111 112 return cmd.CheckEmpty(args[1:]) 113 } 114 115 func (c *NetworkGetCommand) Run(ctx *cmd.Context) error { 116 netInfo, err := c.ctx.NetworkInfo([]string{c.bindingName}, c.RelationId) 117 if err != nil { 118 return errors.Trace(err) 119 } 120 121 ni, ok := netInfo[c.bindingName] 122 if !ok || len(ni.Info) == 0 { 123 return fmt.Errorf("no network config found for binding %q", c.bindingName) 124 } 125 if ni.Error != nil { 126 return errors.Trace(ni.Error) 127 } 128 129 ni = resolveNetworkInfoAddresses(ni, LookupHost) 130 131 // If no specific attributes were asked for, write everything we know. 132 if !c.primaryAddress && len(c.keys) == 0 { 133 return c.out.Write(ctx, ni) 134 } 135 136 // Backwards compatibility - we just want the primary address. 137 if c.primaryAddress { 138 if c.ingressAddress || c.egressSubnets || c.bindAddress { 139 return fmt.Errorf("--primary-address must be the only flag specified") 140 } 141 if len(ni.Info[0].Addresses) == 0 { 142 return fmt.Errorf("no addresses attached to space for binding %q", c.bindingName) 143 } 144 return c.out.Write(ctx, ni.Info[0].Addresses[0].Address) 145 } 146 147 // Write the specific articles requested. 148 keyValues := make(map[string]interface{}) 149 if c.egressSubnets { 150 keyValues[egressSubnetsKey] = ni.EgressSubnets 151 } 152 if c.ingressAddress { 153 var ingressAddress string 154 if len(ni.IngressAddresses) == 0 { 155 if len(ni.Info[0].Addresses) == 0 { 156 return fmt.Errorf("no addresses attached to space for binding %q", c.bindingName) 157 } 158 ingressAddress = ni.Info[0].Addresses[0].Address 159 } else { 160 ingressAddress = ni.IngressAddresses[0] 161 } 162 keyValues[ingressAddressKey] = ingressAddress 163 } 164 if c.bindAddress { 165 keyValues[bindAddressKey] = ni.Info[0].Addresses[0].Address 166 } 167 if len(c.keys) == 1 { 168 return c.out.Write(ctx, keyValues[c.keys[0]]) 169 } 170 return c.out.Write(ctx, keyValues) 171 } 172 173 // TODO(externalreality) This addresses the immediate problem of 174 // https://bugs.launchpad.net/juju/+bug/1721368, but the hostname can populate 175 // both the egress subnet CIDR and the ingress addresses. These too should be 176 // resolved. In addition these values probably should not be stored as hostnames 177 // but rather the IP, that is, it might be better to do the resolution on input 178 // rather than output (network-get) as we do here. 179 func resolveNetworkInfoAddresses( 180 netInfoResult params.NetworkInfoResult, lookupHost resolver, 181 ) params.NetworkInfoResult { 182 // Maintain a cache of host-name -> address resolutions. 183 resolved := make(map[string]string) 184 addressForHost := func(hostName string) string { 185 resolvedAddr, ok := resolved[hostName] 186 if !ok { 187 resolvedAddr = resolveHostAddress(hostName, lookupHost) 188 resolved[hostName] = resolvedAddr 189 } 190 return resolvedAddr 191 } 192 193 // Resolve addresses in Info. 194 for i, info := range netInfoResult.Info { 195 for j, addr := range info.Addresses { 196 if ip := net.ParseIP(addr.Address); ip == nil { 197 resolvedAddr := addressForHost(addr.Address) 198 if resolvedAddr != "" { 199 addr.Hostname = addr.Address 200 addr.Address = resolvedAddr 201 netInfoResult.Info[i].Addresses[j] = addr 202 } 203 } 204 } 205 } 206 207 // Resolve addresses in IngressAddresses. 208 for i, addr := range netInfoResult.IngressAddresses { 209 if ip := net.ParseIP(addr); ip == nil { 210 resolvedAddr := addressForHost(addr) 211 if resolvedAddr != "" { 212 netInfoResult.IngressAddresses[i] = resolvedAddr 213 } 214 } 215 } 216 217 return netInfoResult 218 } 219 220 func resolveHostAddress(hostName string, lookupHost resolver) string { 221 resolved, err := lookupHost(hostName) 222 if err != nil { 223 logger.Warningf("The address %q is neither an IP address nor a resolvable hostname", hostName) 224 return "" 225 } 226 return resolved[0] 227 }