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  }