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 }