github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names"
    15  	"github.com/juju/utils/featureflag"
    16  	"github.com/juju/utils/set"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/spaces"
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/cmd/envcmd"
    22  	"github.com/juju/juju/feature"
    23  )
    24  
    25  // SpaceAPI defines the necessary API methods needed by the space
    26  // subcommands.
    27  type SpaceAPI interface {
    28  	io.Closer
    29  
    30  	// ListSpaces returns all Juju network spaces and their subnets.
    31  	ListSpaces() ([]params.Space, error)
    32  
    33  	// CreateSpace creates a new Juju network space, associating the
    34  	// specified subnets with it (optional; can be empty), setting the
    35  	// space and subnets access to public or private.
    36  	CreateSpace(name string, subnetIds []string, public bool) error
    37  
    38  	// TODO(dimitern): All of the following api methods should take
    39  	// names.SpaceTag instead of name, the only exceptions are
    40  	// CreateSpace, and RenameSpace as the named space doesn't exist
    41  	// yet.
    42  
    43  	// RemoveSpace removes an existing Juju network space, transferring
    44  	// any associated subnets to the default space.
    45  	RemoveSpace(name string) error
    46  
    47  	// UpdateSpace changes the associated subnets for an existing space with
    48  	// the given name. The list of subnets must contain at least one entry.
    49  	UpdateSpace(name string, subnetIds []string) error
    50  
    51  	// RenameSpace changes the name of the space.
    52  	RenameSpace(name, newName string) error
    53  }
    54  
    55  var logger = loggo.GetLogger("juju.cmd.juju.space")
    56  
    57  const commandDoc = `
    58  "juju space" provides commands to manage Juju network spaces.
    59  
    60  A space is a security subdivision of a network.
    61  
    62  In practice, a space is a collection of related subnets that have no
    63  firewalls between each other, and that have the same ingress and
    64  egress policies. Common examples in company networks are “the dmz” or
    65  “the pci compliant space”. The name of the space suggests that it is a
    66  logical network area which has some specific security characteristics
    67  - hence the “common ingress and egress policy” definition.
    68  
    69  All of the addresses in all the subnets in a given space are assumed
    70  to be equally able to connect to one another, and all of them are
    71  assumed to go through the same firewalls (or through the same firewall
    72  rules) for connections into or out of the space. For allocation
    73  purposes, then, putting a service on any address in a space is equally
    74  secure - all the addresses in the space have the same firewall rules
    75  applied to them.
    76  
    77  Users create spaces to describe relevant areas of their network (i.e.
    78  DMZ, internal, etc.).
    79  
    80  Spaces can be specified via constraints when deploying a service
    81  and/or at add-relation time. Since all subnets in a space are
    82  considered equal, placement of services in a space means placement on
    83  any of the subnets in that space. A machine bound to a space could be
    84  on any one of the subnets, and routable to any other machine in the
    85  space because any subnet in the space can access any other in the same
    86  space.
    87  
    88  Initially, there is one space (named "default") which always exists
    89  and "contains" all subnets not associated with another space. However,
    90  since the spaces are defined on the cloud substrate (e.g. using tags
    91  in EC2), there could be pre-existing spaces that get discovered after
    92  bootstrapping a new environment using shared credentials (multiple
    93  users or roles, same substrate). `
    94  
    95  // NewSuperCommand creates the "space" supercommand and registers the
    96  // subcommands that it supports.
    97  func NewSuperCommand() cmd.Command {
    98  	spaceCmd := cmd.NewSuperCommand(cmd.SuperCommandParams{
    99  		Name:        "space",
   100  		Doc:         strings.TrimSpace(commandDoc),
   101  		UsagePrefix: "juju",
   102  		Purpose:     "manage network spaces",
   103  	})
   104  	spaceCmd.Register(envcmd.Wrap(&CreateCommand{}))
   105  	spaceCmd.Register(envcmd.Wrap(&ListCommand{}))
   106  	if featureflag.Enabled(feature.PostNetCLIMVP) {
   107  		// The following commands are not part of the MVP.
   108  		spaceCmd.Register(envcmd.Wrap(&RemoveCommand{}))
   109  		spaceCmd.Register(envcmd.Wrap(&UpdateCommand{}))
   110  		spaceCmd.Register(envcmd.Wrap(&RenameCommand{}))
   111  	}
   112  
   113  	return spaceCmd
   114  }
   115  
   116  // SpaceCommandBase is the base type embedded into all space
   117  // subcommands.
   118  type SpaceCommandBase struct {
   119  	envcmd.EnvCommandBase
   120  	api SpaceAPI
   121  }
   122  
   123  // ParseNameAndCIDRs verifies the input args and returns any errors,
   124  // like missing/invalid name or CIDRs (validated when given, but it's
   125  // an error for CIDRs to be empty if cidrsOptional is false).
   126  func ParseNameAndCIDRs(args []string, cidrsOptional bool) (
   127  	name string, CIDRs set.Strings, err error,
   128  ) {
   129  	defer errors.DeferredAnnotatef(&err, "invalid arguments specified")
   130  
   131  	if len(args) == 0 {
   132  		return "", nil, errors.New("space name is required")
   133  	}
   134  	name, err = CheckName(args[0])
   135  	if err != nil {
   136  		return name, nil, errors.Trace(err)
   137  	}
   138  
   139  	CIDRs, err = CheckCIDRs(args[1:], cidrsOptional)
   140  	return name, CIDRs, err
   141  }
   142  
   143  // CheckName checks whether name is a valid space name.
   144  func CheckName(name string) (string, error) {
   145  	// Validate given name.
   146  	if !names.IsValidSpace(name) {
   147  		return "", errors.Errorf("%q is not a valid space name", name)
   148  	}
   149  	return name, nil
   150  }
   151  
   152  // CheckCIDRs parses the list of strings as CIDRs, checking for
   153  // correct formatting, no duplication and no overlaps. Returns error
   154  // if no CIDRs are provided, unless cidrsOptional is true.
   155  func CheckCIDRs(args []string, cidrsOptional bool) (set.Strings, error) {
   156  	// Validate any given CIDRs.
   157  	CIDRs := set.NewStrings()
   158  	for _, arg := range args {
   159  		_, ipNet, err := net.ParseCIDR(arg)
   160  		if err != nil {
   161  			logger.Debugf("cannot parse %q: %v", arg, err)
   162  			return CIDRs, errors.Errorf("%q is not a valid CIDR", arg)
   163  		}
   164  		cidr := ipNet.String()
   165  		if CIDRs.Contains(cidr) {
   166  			if cidr == arg {
   167  				return CIDRs, errors.Errorf("duplicate subnet %q specified", cidr)
   168  			}
   169  			return CIDRs, errors.Errorf("subnet %q overlaps with %q", arg, cidr)
   170  		}
   171  		CIDRs.Add(cidr)
   172  	}
   173  
   174  	if CIDRs.IsEmpty() && !cidrsOptional {
   175  		return CIDRs, errors.New("CIDRs required but not provided")
   176  	}
   177  
   178  	return CIDRs, nil
   179  }
   180  
   181  // mvpAPIShim forwards SpaceAPI methods to the real API facade for
   182  // implemented methods only. Tested with a feature test only.
   183  type mvpAPIShim struct {
   184  	SpaceAPI
   185  
   186  	apiState api.Connection
   187  	facade   *spaces.API
   188  }
   189  
   190  func (m *mvpAPIShim) Close() error {
   191  	return m.apiState.Close()
   192  }
   193  
   194  func (m *mvpAPIShim) CreateSpace(name string, subnetIds []string, public bool) error {
   195  	return m.facade.CreateSpace(name, subnetIds, public)
   196  }
   197  
   198  func (m *mvpAPIShim) ListSpaces() ([]params.Space, error) {
   199  	return m.facade.ListSpaces()
   200  }
   201  
   202  // NewAPI returns a SpaceAPI for the root api endpoint that the
   203  // environment command returns.
   204  func (c *SpaceCommandBase) NewAPI() (SpaceAPI, error) {
   205  	if c.api != nil {
   206  		// Already created.
   207  		return c.api, nil
   208  	}
   209  	root, err := c.NewAPIRoot()
   210  	if err != nil {
   211  		return nil, errors.Trace(err)
   212  	}
   213  
   214  	// This is tested with a feature test.
   215  	shim := &mvpAPIShim{
   216  		apiState: root,
   217  		facade:   spaces.NewAPI(root),
   218  	}
   219  	return shim, nil
   220  }
   221  
   222  type RunOnAPI func(api SpaceAPI, ctx *cmd.Context) error
   223  
   224  func (c *SpaceCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
   225  	api, err := c.NewAPI()
   226  	if err != nil {
   227  		return errors.Annotate(err, "cannot connect to the API server")
   228  	}
   229  	defer api.Close()
   230  	return toRun(api, ctx)
   231  }