github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/subnet/create.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  	"strings"
     8  
     9  	"launchpad.net/gnuflag"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils/set"
    15  
    16  	"github.com/juju/juju/cmd/modelcmd"
    17  )
    18  
    19  // NewCreateCommand returns a command to create a new subnet.
    20  func NewCreateCommand() cmd.Command {
    21  	return modelcmd.Wrap(&createCommand{})
    22  }
    23  
    24  // createCommand calls the API to create a new subnet.
    25  type createCommand struct {
    26  	SubnetCommandBase
    27  
    28  	CIDR      names.SubnetTag
    29  	Space     names.SpaceTag
    30  	Zones     set.Strings
    31  	IsPublic  bool
    32  	IsPrivate bool
    33  
    34  	flagSet *gnuflag.FlagSet
    35  }
    36  
    37  const createCommandDoc = `
    38  Creates a new subnet with a given CIDR, associated with an existing Juju
    39  network space, and attached to one or more availablility zones. Desired
    40  access for the subnet can be specified using the mutually exclusive flags
    41  --private and --public.
    42  
    43  When --private is specified (or no flags are given, as this is the default),
    44  the created subnet will not allow access from outside the model and
    45  the available address range is only cloud-local.
    46  
    47  When --public is specified, the created subnet will support "shadow addresses".
    48  This means all machines inside the subnet will have cloud-local addresses
    49  configured, but there will also be a shadow address configured for each
    50  machine, so that the machines can be accessed from outside the model (similarly
    51  to the automatic public IP addresses supported with AWS VPCs).
    52  
    53  This command is only supported on clouds which support creating new subnets
    54  dynamically (i.e. Software Defined Networking or SDN). If you want to make
    55  an existing subnet available for Juju to use, rather than creating a new
    56  one, use the "juju subnet add" command.
    57  
    58  Some clouds allow a subnet to span multiple zones, but others do not. It is
    59  an error to try creating a subnet spanning more than one zone if it is not
    60  supported.
    61  `
    62  
    63  // Info is defined on the cmd.Command interface.
    64  func (c *createCommand) Info() *cmd.Info {
    65  	return &cmd.Info{
    66  		Name:    "create-subnet",
    67  		Args:    "<CIDR> <space> <zone1> [<zone2> <zone3> ...] [--public|--private]",
    68  		Purpose: "create a new subnet",
    69  		Doc:     strings.TrimSpace(createCommandDoc),
    70  	}
    71  }
    72  
    73  // SetFlags is defined on the cmd.Command interface.
    74  func (c *createCommand) SetFlags(f *gnuflag.FlagSet) {
    75  	c.SubnetCommandBase.SetFlags(f)
    76  	f.BoolVar(&c.IsPublic, "public", false, "enable public access with shadow addresses")
    77  	f.BoolVar(&c.IsPrivate, "private", true, "disable public access with shadow addresses")
    78  
    79  	// Because SetFlags is called before Parse, we cannot
    80  	// use f.Visit() here to check both flags were not
    81  	// specified at once. So we store the flag set and
    82  	// defer the check to Init().
    83  	c.flagSet = f
    84  }
    85  
    86  // Init is defined on the cmd.Command interface. It checks the
    87  // arguments for sanity and sets up the command to run.
    88  func (c *createCommand) Init(args []string) error {
    89  	// Ensure we have at least 3 arguments.
    90  	// TODO:(mfoord) we need to support VLANTag as an additional optional
    91  	// argument.
    92  	err := c.CheckNumArgs(args, []error{errNoCIDR, errNoSpace, errNoZones})
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	// Validate given CIDR.
    98  	c.CIDR, err = c.ValidateCIDR(args[0], true)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Validate the space name.
   104  	c.Space, err = c.ValidateSpace(args[1])
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	// Validate any given zones.
   110  	c.Zones = set.NewStrings()
   111  	for _, zone := range args[2:] {
   112  		if c.Zones.Contains(zone) {
   113  			return errors.Errorf("duplicate zone %q specified", zone)
   114  		}
   115  		c.Zones.Add(zone)
   116  	}
   117  
   118  	// Ensure --public and --private are not both specified.
   119  	// TODO(dimitern): This is a really awkward way to handle
   120  	// mutually exclusive bool flags and needs to be factored
   121  	// out in a helper if another command needs to do it.
   122  	var publicSet, privateSet bool
   123  	c.flagSet.Visit(func(flag *gnuflag.Flag) {
   124  		switch flag.Name {
   125  		case "public":
   126  			publicSet = true
   127  		case "private":
   128  			privateSet = true
   129  		}
   130  	})
   131  	switch {
   132  	case publicSet && privateSet:
   133  		return errors.Errorf("cannot specify both --public and --private")
   134  	case publicSet:
   135  		c.IsPrivate = false
   136  	case privateSet:
   137  		c.IsPublic = false
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  // Run implements Command.Run.
   144  func (c *createCommand) Run(ctx *cmd.Context) error {
   145  	return c.RunWithAPI(ctx, func(api SubnetAPI, ctx *cmd.Context) error {
   146  		if !c.Zones.IsEmpty() {
   147  			// Fetch all zones to validate the given zones.
   148  			zones, err := api.AllZones()
   149  			if err != nil {
   150  				return errors.Annotate(err, "cannot fetch availability zones")
   151  			}
   152  
   153  			// Find which of the given CIDRs match existing ones.
   154  			validZones := set.NewStrings()
   155  			for _, zone := range zones {
   156  				validZones.Add(zone)
   157  			}
   158  			diff := c.Zones.Difference(validZones)
   159  
   160  			if !diff.IsEmpty() {
   161  				// Some given zones are missing.
   162  				zones := strings.Join(diff.SortedValues(), ", ")
   163  				return errors.Errorf("unknown zones specified: %s", zones)
   164  			}
   165  		}
   166  
   167  		// Create the new subnet.
   168  		err := api.CreateSubnet(c.CIDR, c.Space, c.Zones.SortedValues(), c.IsPublic)
   169  		if err != nil {
   170  			return errors.Annotatef(err, "cannot create subnet %q", c.CIDR.Id())
   171  		}
   172  
   173  		zones := strings.Join(c.Zones.SortedValues(), ", ")
   174  		accessType := "private"
   175  		if c.IsPublic {
   176  			accessType = "public"
   177  		}
   178  		ctx.Infof(
   179  			"created a %s subnet %q in space %q with zones %s",
   180  			accessType, c.CIDR.Id(), c.Space.Id(), zones,
   181  		)
   182  		return nil
   183  	})
   184  }