github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/subnet/subnet.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package subnet
     5  
     6  import (
     7  	"io"
     8  	"net"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/api"
    16  	"github.com/juju/juju/api/subnets"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  	"github.com/juju/juju/network"
    20  )
    21  
    22  // SubnetAPI defines the necessary API methods needed by the subnet
    23  // subcommands.
    24  type SubnetAPI interface {
    25  	io.Closer
    26  
    27  	// AddSubnet adds an existing subnet to Juju.
    28  	AddSubnet(cidr names.SubnetTag, id network.Id, spaceTag names.SpaceTag, zones []string) error
    29  
    30  	// ListSubnets returns information about subnets known to Juju,
    31  	// optionally filtered by space and/or zone (both can be empty).
    32  	ListSubnets(withSpace *names.SpaceTag, withZone string) ([]params.Subnet, error)
    33  
    34  	// AllZones returns all availability zones known to Juju.
    35  	AllZones() ([]string, error)
    36  
    37  	// AllSpaces returns all Juju network spaces.
    38  	AllSpaces() ([]names.Tag, error)
    39  
    40  	// CreateSubnet creates a new Juju subnet.
    41  	CreateSubnet(subnetCIDR names.SubnetTag, spaceTag names.SpaceTag, zones []string, isPublic bool) error
    42  
    43  	// RemoveSubnet marks an existing subnet as no longer used, which
    44  	// will cause it to get removed at some point after all its
    45  	// related entites are cleaned up. It will fail if the subnet is
    46  	// still in use by any machines.
    47  	RemoveSubnet(subnetCIDR names.SubnetTag) error
    48  }
    49  
    50  // mvpAPIShim forwards SubnetAPI methods to the real API facade for
    51  // implemented methods only. Tested with a feature test only.
    52  type mvpAPIShim struct {
    53  	SubnetAPI
    54  
    55  	apiState api.Connection
    56  	facade   *subnets.API
    57  }
    58  
    59  func (m *mvpAPIShim) Close() error {
    60  	return m.apiState.Close()
    61  }
    62  
    63  func (m *mvpAPIShim) AddSubnet(cidr names.SubnetTag, id network.Id, spaceTag names.SpaceTag, zones []string) error {
    64  	return m.facade.AddSubnet(cidr, id, spaceTag, zones)
    65  }
    66  
    67  func (m *mvpAPIShim) ListSubnets(withSpace *names.SpaceTag, withZone string) ([]params.Subnet, error) {
    68  	return m.facade.ListSubnets(withSpace, withZone)
    69  }
    70  
    71  var logger = loggo.GetLogger("juju.cmd.juju.subnet")
    72  
    73  // SubnetCommandBase is the base type embedded into all subnet
    74  // subcommands.
    75  type SubnetCommandBase struct {
    76  	modelcmd.ModelCommandBase
    77  	modelcmd.IAASOnlyCommand
    78  	api SubnetAPI
    79  }
    80  
    81  // NewAPI returns a SubnetAPI for the root api endpoint that the
    82  // environment command returns.
    83  func (c *SubnetCommandBase) NewAPI() (SubnetAPI, error) {
    84  	if c.api != nil {
    85  		// Already created.
    86  		return c.api, nil
    87  	}
    88  	root, err := c.NewAPIRoot()
    89  	if err != nil {
    90  		return nil, errors.Trace(err)
    91  	}
    92  
    93  	// This is tested with a feature test.
    94  	shim := &mvpAPIShim{
    95  		apiState: root,
    96  		facade:   subnets.NewAPI(root),
    97  	}
    98  	return shim, nil
    99  }
   100  
   101  type RunOnAPI func(api SubnetAPI, ctx *cmd.Context) error
   102  
   103  func (c *SubnetCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
   104  	api, err := c.NewAPI()
   105  	if err != nil {
   106  		return errors.Annotate(err, "cannot connect to the API server")
   107  	}
   108  	defer api.Close()
   109  	return toRun(api, ctx)
   110  }
   111  
   112  // Common errors shared between subcommands.
   113  var (
   114  	errNoCIDR     = errors.New("CIDR is required")
   115  	errNoCIDROrID = errors.New("either CIDR or provider ID is required")
   116  	errNoSpace    = errors.New("space name is required")
   117  	errNoZones    = errors.New("at least one zone is required")
   118  )
   119  
   120  // CheckNumArgs is a helper used to validate the number of arguments
   121  // passed to Init(). If the number of arguments is X, errors[X] (if
   122  // set) will be returned, otherwise no error occurs.
   123  func (s *SubnetCommandBase) CheckNumArgs(args []string, errors []error) error {
   124  	for num, err := range errors {
   125  		if len(args) == num {
   126  			return err
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  // ValidateCIDR parses given and returns an error if it's not valid.
   133  // If the CIDR is incorrectly specified (e.g. 10.10.10.0/16 instead of
   134  // 10.10.0.0/16) and strict is false, the correctly parsed CIDR in the
   135  // expected format is returned instead without an error. Otherwise,
   136  // when strict is true and given is incorrectly formatted, an error
   137  // will be returned.
   138  func (s *SubnetCommandBase) ValidateCIDR(given string, strict bool) (names.SubnetTag, error) {
   139  	_, ipNet, err := net.ParseCIDR(given)
   140  	if err != nil {
   141  		logger.Debugf("cannot parse CIDR %q: %v", given, err)
   142  		return names.SubnetTag{}, errors.Errorf("%q is not a valid CIDR", given)
   143  	}
   144  	if strict && given != ipNet.String() {
   145  		expected := ipNet.String()
   146  		return names.SubnetTag{}, errors.Errorf("%q is not correctly specified, expected %q", given, expected)
   147  	}
   148  	// Already validated, so shouldn't error here.
   149  	return names.NewSubnetTag(ipNet.String()), nil
   150  }
   151  
   152  // ValidateSpace parses given and returns an error if it's not a valid
   153  // space name, otherwise returns the parsed tag and no error.
   154  func (s *SubnetCommandBase) ValidateSpace(given string) (names.SpaceTag, error) {
   155  	if !names.IsValidSpace(given) {
   156  		return names.SpaceTag{}, errors.Errorf("%q is not a valid space name", given)
   157  	}
   158  	return names.NewSpaceTag(given), nil
   159  }