github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/space/space.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package space
     5  
     6  import (
     7  	"io"
     8  	"net"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/utils/set"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api"
    17  	"github.com/juju/juju/api/spaces"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/modelcmd"
    20  )
    21  
    22  // SpaceAPI defines the necessary API methods needed by the space
    23  // subcommands.
    24  type SpaceAPI interface {
    25  	io.Closer
    26  
    27  	// ListSpaces returns all Juju network spaces and their subnets.
    28  	ListSpaces() ([]params.Space, error)
    29  
    30  	// AddSpace adds a new Juju network space, associating the
    31  	// specified subnets with it (optional; can be empty), setting the
    32  	// space and subnets access to public or private.
    33  	AddSpace(name string, subnetIds []string, public bool) error
    34  
    35  	// TODO(dimitern): All of the following api methods should take
    36  	// names.SpaceTag instead of name, the only exceptions are
    37  	// AddSpace, and RenameSpace as the named space doesn't exist
    38  	// yet.
    39  
    40  	// RemoveSpace removes an existing Juju network space, transferring
    41  	// any associated subnets to the default space.
    42  	RemoveSpace(name string) error
    43  
    44  	// UpdateSpace changes the associated subnets for an existing space with
    45  	// the given name. The list of subnets must contain at least one entry.
    46  	UpdateSpace(name string, subnetIds []string) error
    47  
    48  	// RenameSpace changes the name of the space.
    49  	RenameSpace(name, newName string) error
    50  }
    51  
    52  var logger = loggo.GetLogger("juju.cmd.juju.space")
    53  
    54  // SpaceCommandBase is the base type embedded into all space
    55  // subcommands.
    56  type SpaceCommandBase struct {
    57  	modelcmd.ModelCommandBase
    58  	api SpaceAPI
    59  }
    60  
    61  // ParseNameAndCIDRs verifies the input args and returns any errors,
    62  // like missing/invalid name or CIDRs (validated when given, but it's
    63  // an error for CIDRs to be empty if cidrsOptional is false).
    64  func ParseNameAndCIDRs(args []string, cidrsOptional bool) (
    65  	name string, CIDRs set.Strings, err error,
    66  ) {
    67  	defer errors.DeferredAnnotatef(&err, "invalid arguments specified")
    68  
    69  	if len(args) == 0 {
    70  		return "", nil, errors.New("space name is required")
    71  	}
    72  	name, err = CheckName(args[0])
    73  	if err != nil {
    74  		return name, nil, errors.Trace(err)
    75  	}
    76  
    77  	CIDRs, err = CheckCIDRs(args[1:], cidrsOptional)
    78  	return name, CIDRs, errors.Trace(err)
    79  }
    80  
    81  // CheckName checks whether name is a valid space name.
    82  func CheckName(name string) (string, error) {
    83  	// Validate given name.
    84  	if !names.IsValidSpace(name) {
    85  		return "", errors.Errorf("%q is not a valid space name", name)
    86  	}
    87  	return name, nil
    88  }
    89  
    90  // CheckCIDRs parses the list of strings as CIDRs, checking for
    91  // correct formatting, no duplication and no overlaps. Returns error
    92  // if no CIDRs are provided, unless cidrsOptional is true.
    93  func CheckCIDRs(args []string, cidrsOptional bool) (set.Strings, error) {
    94  	// Validate any given CIDRs.
    95  	CIDRs := set.NewStrings()
    96  	for _, arg := range args {
    97  		_, ipNet, err := net.ParseCIDR(arg)
    98  		if err != nil {
    99  			logger.Debugf("cannot parse %q: %v", arg, err)
   100  			return CIDRs, errors.Errorf("%q is not a valid CIDR", arg)
   101  		}
   102  		cidr := ipNet.String()
   103  		if CIDRs.Contains(cidr) {
   104  			if cidr == arg {
   105  				return CIDRs, errors.Errorf("duplicate subnet %q specified", cidr)
   106  			}
   107  			return CIDRs, errors.Errorf("subnet %q overlaps with %q", arg, cidr)
   108  		}
   109  		CIDRs.Add(cidr)
   110  	}
   111  
   112  	if CIDRs.IsEmpty() && !cidrsOptional {
   113  		return CIDRs, errors.New("CIDRs required but not provided")
   114  	}
   115  
   116  	return CIDRs, nil
   117  }
   118  
   119  // mvpAPIShim forwards SpaceAPI methods to the real API facade for
   120  // implemented methods only. Tested with a feature test only.
   121  type mvpAPIShim struct {
   122  	SpaceAPI
   123  
   124  	apiState api.Connection
   125  	facade   *spaces.API
   126  }
   127  
   128  func (m *mvpAPIShim) Close() error {
   129  	return m.apiState.Close()
   130  }
   131  
   132  func (m *mvpAPIShim) AddSpace(name string, subnetIds []string, public bool) error {
   133  	return m.facade.CreateSpace(name, subnetIds, public)
   134  }
   135  
   136  func (m *mvpAPIShim) ListSpaces() ([]params.Space, error) {
   137  	return m.facade.ListSpaces()
   138  }
   139  
   140  // NewAPI returns a SpaceAPI for the root api endpoint that the
   141  // environment command returns.
   142  func (c *SpaceCommandBase) NewAPI() (SpaceAPI, error) {
   143  	if c.api != nil {
   144  		// Already addd.
   145  		return c.api, nil
   146  	}
   147  	root, err := c.NewAPIRoot()
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  
   152  	// This is tested with a feature test.
   153  	shim := &mvpAPIShim{
   154  		apiState: root,
   155  		facade:   spaces.NewAPI(root),
   156  	}
   157  	return shim, nil
   158  }
   159  
   160  type RunOnAPI func(api SpaceAPI, ctx *cmd.Context) error
   161  
   162  func (c *SpaceCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
   163  	api, err := c.NewAPI()
   164  	if err != nil {
   165  		return errors.Annotate(err, "cannot connect to the API server")
   166  	}
   167  	defer api.Close()
   168  	return toRun(api, ctx)
   169  }