github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/system/useenvironment.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package system 5 6 import ( 7 "strings" 8 9 "github.com/juju/cmd" 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 "launchpad.net/gnuflag" 13 14 "github.com/juju/juju/api" 15 "github.com/juju/juju/api/base" 16 "github.com/juju/juju/cmd/envcmd" 17 "github.com/juju/juju/environs/configstore" 18 ) 19 20 // UseEnvironmentCommand returns the list of all the environments the 21 // current user can access on the current system. 22 type UseEnvironmentCommand struct { 23 envcmd.SysCommandBase 24 25 apiOpen api.OpenFunc 26 api UseEnvironmentAPI 27 userCreds *configstore.APICredentials 28 endpoint *configstore.APIEndpoint 29 30 LocalName string 31 Owner string 32 EnvName string 33 EnvUUID string 34 } 35 36 // UseEnvironmentAPI defines the methods on the environment manager API that 37 // the use environment command calls. 38 type UseEnvironmentAPI interface { 39 Close() error 40 ListEnvironments(user string) ([]base.UserEnvironment, error) 41 } 42 43 var useEnvDoc = ` 44 use-environment caches the necessary information about the specified 45 environment on the current machine. This allows you to switch between 46 environments. 47 48 By default, the local names for the environment are based on the name that the 49 owner of the environment gave it when they created it. If you are the owner 50 of the environment, then the local name is just the name of the environment. 51 If you are not the owner, the name is prefixed by the name of the owner and a 52 dash. 53 54 If there is just one environment called "test" in the current system that you 55 have access to, then you can just specify the name. 56 57 $ juju system use-environment test 58 59 If however there are multiple enviornments called "test" that are owned 60 61 $ juju system use-environment test 62 Multiple environments matched name "test": 63 cb4b94e8-29bb-44ae-820c-adac21194395, owned by bob@local 64 ae673c19-73ef-437f-8224-4842a1772bdf, owned by mary@local 65 Please specify either the environment UUID or the owner to disambiguate. 66 ERROR multiple environments matched 67 68 You can specify either the environment UUID like this: 69 70 $ juju system use-environment cb4b94e8-29bb-44ae-820c-adac21194395 71 72 Or, specify the owner: 73 74 $ juju system use-environment mary@local/test 75 76 Since '@local' is the default for users, this can be shortened to: 77 78 $ juju system use-environment mary/test 79 80 81 See Also: 82 juju help juju-systems 83 juju help system create-environment 84 juju help environment share 85 juju help environment unshare 86 juju help switch 87 juju help user add 88 ` 89 90 // Info implements Command.Info 91 func (c *UseEnvironmentCommand) Info() *cmd.Info { 92 return &cmd.Info{ 93 Name: "use-environment", 94 Purpose: "use an environment that you have access to on this machine", 95 Doc: useEnvDoc, 96 Aliases: []string{"use-env"}, 97 } 98 } 99 100 func (c *UseEnvironmentCommand) getAPI() (UseEnvironmentAPI, error) { 101 if c.api != nil { 102 return c.api, nil 103 } 104 return c.NewEnvironmentManagerAPIClient() 105 } 106 107 func (c *UseEnvironmentCommand) getConnectionCredentials() (configstore.APICredentials, error) { 108 if c.userCreds != nil { 109 return *c.userCreds, nil 110 } 111 return c.ConnectionCredentials() 112 } 113 114 func (c *UseEnvironmentCommand) getConnectionEndpoint() (configstore.APIEndpoint, error) { 115 if c.endpoint != nil { 116 return *c.endpoint, nil 117 } 118 return c.ConnectionEndpoint() 119 } 120 121 // SetFlags implements Command.SetFlags. 122 func (c *UseEnvironmentCommand) SetFlags(f *gnuflag.FlagSet) { 123 f.StringVar(&c.LocalName, "name", "", "the local name for this environment") 124 } 125 126 // SetFlags implements Command.Init. 127 func (c *UseEnvironmentCommand) Init(args []string) error { 128 if c.apiOpen == nil { 129 c.apiOpen = apiOpen 130 } 131 if len(args) == 0 || strings.TrimSpace(args[0]) == "" { 132 return errors.New("no environment supplied") 133 } 134 135 name, args := args[0], args[1:] 136 137 // First check to see if an owner has been specified. 138 bits := strings.SplitN(name, "/", 2) 139 switch len(bits) { 140 case 1: 141 // No user specified 142 c.EnvName = bits[0] 143 case 2: 144 owner := bits[0] 145 if names.IsValidUser(owner) { 146 c.Owner = owner 147 } else { 148 return errors.Errorf("%q is not a valid user", owner) 149 } 150 c.EnvName = bits[1] 151 } 152 153 // Environment names can generally be anything, but we take a good 154 // stab at trying to determine if the user has speicifed a UUID 155 // instead of a name. For now, we only accept a properly formatted UUID, 156 // which means one with dashes in the right place. 157 if names.IsValidEnvironment(c.EnvName) { 158 c.EnvUUID, c.EnvName = c.EnvName, "" 159 } 160 161 return cmd.CheckEmpty(args) 162 } 163 164 // Run implements Command.Run 165 func (c *UseEnvironmentCommand) Run(ctx *cmd.Context) error { 166 client, err := c.getAPI() 167 if err != nil { 168 return errors.Trace(err) 169 } 170 defer client.Close() 171 172 creds, err := c.getConnectionCredentials() 173 if err != nil { 174 return errors.Trace(err) 175 } 176 endpoint, err := c.getConnectionEndpoint() 177 if err != nil { 178 return errors.Trace(err) 179 } 180 181 username := names.NewUserTag(creds.User).Username() 182 183 env, err := c.findMatchingEnvironment(ctx, client, creds) 184 if err != nil { 185 return errors.Trace(err) 186 } 187 188 if c.LocalName == "" { 189 if env.Owner == username { 190 c.LocalName = env.Name 191 } else { 192 envOwner := names.NewUserTag(env.Owner) 193 c.LocalName = envOwner.Name() + "-" + env.Name 194 } 195 } 196 197 // Check with the store to see if we have an environment with that name. 198 store, err := configstore.Default() 199 if err != nil { 200 return errors.Trace(err) 201 } 202 203 existing, err := store.ReadInfo(c.LocalName) 204 if err == nil { 205 // We have an existing environment with the same name. If it is the 206 // same environment with the same user, then this is fine, and we just 207 // change the current environment. 208 endpoint := existing.APIEndpoint() 209 existingCreds := existing.APICredentials() 210 // Need to make sure we check the username of the credentials, 211 // not just matching tags. 212 existingUsername := names.NewUserTag(existingCreds.User).Username() 213 if endpoint.EnvironUUID == env.UUID && existingUsername == username { 214 ctx.Infof("You already have environment details for %q cached locally.", c.LocalName) 215 return envcmd.SetCurrentEnvironment(ctx, c.LocalName) 216 } 217 ctx.Infof("You have an existing environment called %q, use --name to specify a different local name.", c.LocalName) 218 return errors.New("existing environment") 219 } 220 221 info := store.CreateInfo(c.LocalName) 222 if err := c.updateCachedInfo(info, env.UUID, creds, endpoint); err != nil { 223 return errors.Annotatef(err, "failed to cache environment details") 224 } 225 226 return envcmd.SetCurrentEnvironment(ctx, c.LocalName) 227 } 228 229 func (c *UseEnvironmentCommand) updateCachedInfo(info configstore.EnvironInfo, envUUID string, creds configstore.APICredentials, endpoint configstore.APIEndpoint) error { 230 info.SetAPICredentials(creds) 231 // Specify the environment UUID. The server UUID will be the same as the 232 // endpoint that we have just connected to, as will be the CACert, addresses 233 // and hostnames. 234 endpoint.EnvironUUID = envUUID 235 info.SetAPIEndpoint(endpoint) 236 return errors.Trace(info.Write()) 237 } 238 239 func (c *UseEnvironmentCommand) findMatchingEnvironment(ctx *cmd.Context, client UseEnvironmentAPI, creds configstore.APICredentials) (base.UserEnvironment, error) { 240 241 var empty base.UserEnvironment 242 243 envs, err := client.ListEnvironments(creds.User) 244 if err != nil { 245 return empty, errors.Annotate(err, "cannot list environments") 246 } 247 248 var owner string 249 if c.Owner != "" { 250 // The username always contains the provider aspect of the user. 251 owner = names.NewUserTag(c.Owner).Username() 252 } 253 254 // If we have a UUID, we warn if the owner is different, but accept it. 255 // We also trust that the environment UUIDs are unique 256 if c.EnvUUID != "" { 257 for _, env := range envs { 258 if env.UUID == c.EnvUUID { 259 if owner != "" && env.Owner != owner { 260 ctx.Infof("Specified environment owned by %s, not %s", env.Owner, owner) 261 } 262 return env, nil 263 } 264 } 265 return empty, errors.NotFoundf("matching environment") 266 } 267 268 var matches []base.UserEnvironment 269 for _, env := range envs { 270 match := env.Name == c.EnvName 271 if match && owner != "" { 272 match = env.Owner == owner 273 } 274 if match { 275 matches = append(matches, env) 276 } 277 } 278 279 // If there is only one match, that's the one. 280 switch len(matches) { 281 case 0: 282 return empty, errors.NotFoundf("matching environment") 283 case 1: 284 return matches[0], nil 285 } 286 287 // We are going to return an error, but tell the user what the matches 288 // were so they can make an informed decision. We are also going to assume 289 // here that the resulting environment list has only one matching name for 290 // each user. There are tests creating environments that enforce this. 291 ctx.Infof("Multiple environments matched name %q:", c.EnvName) 292 for _, env := range matches { 293 ctx.Infof(" %s, owned by %s", env.UUID, env.Owner) 294 } 295 ctx.Infof("Please specify either the environment UUID or the owner to disambiguate.") 296 297 return empty, errors.New("multiple environments matched") 298 }