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  }