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 }