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 }