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