github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/collections/set"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    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  	// ReloadSpaces fetches spaces and subnets from substrate
    52  	ReloadSpaces() error
    53  }
    54  
    55  var logger = loggo.GetLogger("juju.cmd.juju.space")
    56  
    57  // SpaceCommandBase is the base type embedded into all space
    58  // subcommands.
    59  type SpaceCommandBase struct {
    60  	modelcmd.ModelCommandBase
    61  	modelcmd.IAASOnlyCommand
    62  	api SpaceAPI
    63  }
    64  
    65  // ParseNameAndCIDRs verifies the input args and returns any errors,
    66  // like missing/invalid name or CIDRs (validated when given, but it's
    67  // an error for CIDRs to be empty if cidrsOptional is false).
    68  func ParseNameAndCIDRs(args []string, cidrsOptional bool) (
    69  	name string, CIDRs set.Strings, err error,
    70  ) {
    71  	defer errors.DeferredAnnotatef(&err, "invalid arguments specified")
    72  
    73  	if len(args) == 0 {
    74  		return "", nil, errors.New("space name is required")
    75  	}
    76  	name, err = CheckName(args[0])
    77  	if err != nil {
    78  		return name, nil, errors.Trace(err)
    79  	}
    80  
    81  	CIDRs, err = CheckCIDRs(args[1:], cidrsOptional)
    82  	return name, CIDRs, errors.Trace(err)
    83  }
    84  
    85  // CheckName checks whether name is a valid space name.
    86  func CheckName(name string) (string, error) {
    87  	// Validate given name.
    88  	if !names.IsValidSpace(name) {
    89  		return "", errors.Errorf("%q is not a valid space name", name)
    90  	}
    91  	return name, nil
    92  }
    93  
    94  // CheckCIDRs parses the list of strings as CIDRs, checking for
    95  // correct formatting, no duplication and no overlaps. Returns error
    96  // if no CIDRs are provided, unless cidrsOptional is true.
    97  func CheckCIDRs(args []string, cidrsOptional bool) (set.Strings, error) {
    98  	// Validate any given CIDRs.
    99  	CIDRs := set.NewStrings()
   100  	for _, arg := range args {
   101  		_, ipNet, err := net.ParseCIDR(arg)
   102  		if err != nil {
   103  			logger.Debugf("cannot parse %q: %v", arg, err)
   104  			return CIDRs, errors.Errorf("%q is not a valid CIDR", arg)
   105  		}
   106  		cidr := ipNet.String()
   107  		if CIDRs.Contains(cidr) {
   108  			if cidr == arg {
   109  				return CIDRs, errors.Errorf("duplicate subnet %q specified", cidr)
   110  			}
   111  			return CIDRs, errors.Errorf("subnet %q overlaps with %q", arg, cidr)
   112  		}
   113  		CIDRs.Add(cidr)
   114  	}
   115  
   116  	if CIDRs.IsEmpty() && !cidrsOptional {
   117  		return CIDRs, errors.New("CIDRs required but not provided")
   118  	}
   119  
   120  	return CIDRs, nil
   121  }
   122  
   123  // mvpAPIShim forwards SpaceAPI methods to the real API facade for
   124  // implemented methods only. Tested with a feature test only.
   125  type mvpAPIShim struct {
   126  	SpaceAPI
   127  
   128  	apiState api.Connection
   129  	facade   *spaces.API
   130  }
   131  
   132  func (m *mvpAPIShim) Close() error {
   133  	return m.apiState.Close()
   134  }
   135  
   136  func (m *mvpAPIShim) AddSpace(name string, subnetIds []string, public bool) error {
   137  	return m.facade.CreateSpace(name, subnetIds, public)
   138  }
   139  
   140  func (m *mvpAPIShim) ListSpaces() ([]params.Space, error) {
   141  	return m.facade.ListSpaces()
   142  }
   143  
   144  func (m *mvpAPIShim) ReloadSpaces() error {
   145  	return m.facade.ReloadSpaces()
   146  }
   147  
   148  // NewAPI returns a SpaceAPI for the root api endpoint that the
   149  // environment command returns.
   150  func (c *SpaceCommandBase) NewAPI() (SpaceAPI, error) {
   151  	if c.api != nil {
   152  		// Already addd.
   153  		return c.api, nil
   154  	}
   155  	root, err := c.NewAPIRoot()
   156  	if err != nil {
   157  		return nil, errors.Trace(err)
   158  	}
   159  
   160  	// This is tested with a feature test.
   161  	shim := &mvpAPIShim{
   162  		apiState: root,
   163  		facade:   spaces.NewAPI(root),
   164  	}
   165  	return shim, nil
   166  }
   167  
   168  type RunOnAPI func(api SpaceAPI, ctx *cmd.Context) error
   169  
   170  func (c *SpaceCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
   171  	api, err := c.NewAPI()
   172  	if err != nil {
   173  		return errors.Annotate(err, "cannot connect to the API server")
   174  	}
   175  	defer api.Close()
   176  	return toRun(api, ctx)
   177  }