github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/envcmd/environmentcommand.go (about)

     1  // Copyright 2013-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package envcmd
     5  
     6  import (
     7  	"io"
     8  	"os"
     9  	"strconv"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"launchpad.net/gnuflag"
    15  
    16  	"github.com/juju/juju/api"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/configstore"
    20  	"github.com/juju/juju/juju"
    21  	"github.com/juju/juju/juju/osenv"
    22  	"github.com/juju/juju/version"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.cmd.envcmd")
    26  
    27  // ErrNoEnvironmentSpecified is returned by commands that operate on
    28  // an environment if there is no current environment, no environment
    29  // has been explicitly specified, and there is no default environment.
    30  var ErrNoEnvironmentSpecified = errors.New("no environment specified")
    31  
    32  // GetDefaultEnvironment returns the name of the Juju default environment.
    33  // There is simple ordering for the default environment.  Firstly check the
    34  // JUJU_ENV environment variable.  If that is set, it gets used.  If it isn't
    35  // set, look in the $JUJU_HOME/current-environment file.  If neither are
    36  // available, read environments.yaml and use the default environment therein.
    37  // If no default is specified in the environments file, an empty string is returned.
    38  // Not having a default environment specified is not an error.
    39  func GetDefaultEnvironment() (string, error) {
    40  	if defaultEnv := os.Getenv(osenv.JujuEnvEnvKey); defaultEnv != "" {
    41  		return defaultEnv, nil
    42  	}
    43  	if currentEnv, err := ReadCurrentEnvironment(); err != nil {
    44  		return "", errors.Trace(err)
    45  	} else if currentEnv != "" {
    46  		return currentEnv, nil
    47  	}
    48  	if currentSystem, err := ReadCurrentSystem(); err != nil {
    49  		return "", errors.Trace(err)
    50  	} else if currentSystem != "" {
    51  		return "", errors.Errorf("not operating on an environment, using system %q", currentSystem)
    52  	}
    53  	envs, err := environs.ReadEnvirons("")
    54  	if environs.IsNoEnv(err) {
    55  		// That's fine, not an error here.
    56  		return "", nil
    57  	} else if err != nil {
    58  		return "", errors.Trace(err)
    59  	}
    60  	return envs.Default, nil
    61  }
    62  
    63  // EnvironCommand extends cmd.Command with a SetEnvName method.
    64  type EnvironCommand interface {
    65  	cmd.Command
    66  
    67  	// SetEnvName is called prior to the wrapped command's Init method
    68  	// with the active environment name. The environment name is guaranteed
    69  	// to be non-empty at entry of Init.
    70  	SetEnvName(envName string)
    71  }
    72  
    73  // EnvCommandBase is a convenience type for embedding in commands
    74  // that wish to implement EnvironCommand.
    75  type EnvCommandBase struct {
    76  	cmd.CommandBase
    77  	// EnvName will very soon be package visible only as we want to be able
    78  	// to specify an environment in multiple ways, and not always referencing
    79  	// a file on disk based on the EnvName or the environemnts.yaml file.
    80  	envName string
    81  
    82  	// compatVersion defines the minimum CLI version
    83  	// that this command should be compatible with.
    84  	compatVerson *int
    85  
    86  	envGetterClient EnvironmentGetter
    87  	envGetterErr    error
    88  }
    89  
    90  func (c *EnvCommandBase) SetEnvName(envName string) {
    91  	c.envName = envName
    92  }
    93  
    94  func (c *EnvCommandBase) NewAPIClient() (*api.Client, error) {
    95  	root, err := c.NewAPIRoot()
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	return root.Client(), nil
   100  }
   101  
   102  // NewEnvironmentGetter returns a new object which implements the
   103  // EnvironmentGetter interface.
   104  func (c *EnvCommandBase) NewEnvironmentGetter() (EnvironmentGetter, error) {
   105  	if c.envGetterErr != nil {
   106  		return nil, c.envGetterErr
   107  	}
   108  
   109  	if c.envGetterClient != nil {
   110  		return c.envGetterClient, nil
   111  	}
   112  
   113  	return c.NewAPIClient()
   114  }
   115  
   116  func (c *EnvCommandBase) NewAPIRoot() (api.Connection, error) {
   117  	// This is work in progress as we remove the EnvName from downstream code.
   118  	// We want to be able to specify the environment in a number of ways, one of
   119  	// which is the connection name on the client machine.
   120  	if c.envName == "" {
   121  		return nil, errors.Trace(ErrNoEnvironmentSpecified)
   122  	}
   123  	return juju.NewAPIFromName(c.envName)
   124  }
   125  
   126  // Config returns the configuration for the environment; obtaining bootstrap
   127  // information from the API if necessary.  If callers already have an active
   128  // client API connection, it will be used.  Otherwise, a new API connection will
   129  // be used if necessary.
   130  func (c *EnvCommandBase) Config(store configstore.Storage, client EnvironmentGetter) (*config.Config, error) {
   131  	if c.envName == "" {
   132  		return nil, errors.Trace(ErrNoEnvironmentSpecified)
   133  	}
   134  	cfg, _, err := environs.ConfigForName(c.envName, store)
   135  	if err == nil {
   136  		return cfg, nil
   137  	} else if !environs.IsEmptyConfig(err) {
   138  		return nil, errors.Trace(err)
   139  	}
   140  
   141  	if client == nil {
   142  		client, err = c.NewEnvironmentGetter()
   143  		if err != nil {
   144  			return nil, errors.Trace(err)
   145  		}
   146  		defer client.Close()
   147  	}
   148  
   149  	bootstrapCfg, err := client.EnvironmentGet()
   150  	if err != nil {
   151  		return nil, errors.Trace(err)
   152  	}
   153  	return config.New(config.NoDefaults, bootstrapCfg)
   154  }
   155  
   156  // ConnectionCredentials returns the credentials used to connect to the API for
   157  // the specified environment.
   158  func (c *EnvCommandBase) ConnectionCredentials() (configstore.APICredentials, error) {
   159  	// TODO: the user may soon be specified through the command line
   160  	// or through an environment setting, so return these when they are ready.
   161  	var emptyCreds configstore.APICredentials
   162  	if c.envName == "" {
   163  		return emptyCreds, errors.Trace(ErrNoEnvironmentSpecified)
   164  	}
   165  	info, err := ConnectionInfoForName(c.envName)
   166  	if err != nil {
   167  		return emptyCreds, errors.Trace(err)
   168  	}
   169  	return info.APICredentials(), nil
   170  }
   171  
   172  // ConnectionEndpoint returns the end point information used to
   173  // connect to the API for the specified environment.
   174  func (c *EnvCommandBase) ConnectionEndpoint(refresh bool) (configstore.APIEndpoint, error) {
   175  	// TODO: the endpoint information may soon be specified through the command line
   176  	// or through an environment setting, so return these when they are ready.
   177  	// NOTE: refresh when specified through command line should error.
   178  	var emptyEndpoint configstore.APIEndpoint
   179  	if c.envName == "" {
   180  		return emptyEndpoint, errors.Trace(ErrNoEnvironmentSpecified)
   181  	}
   182  	info, err := ConnectionInfoForName(c.envName)
   183  	if err != nil {
   184  		return emptyEndpoint, errors.Trace(err)
   185  	}
   186  	endpoint := info.APIEndpoint()
   187  	if !refresh && len(endpoint.Addresses) > 0 {
   188  		logger.Debugf("found cached addresses, not connecting to API server")
   189  		return endpoint, nil
   190  	}
   191  
   192  	// We need to connect to refresh our endpoint settings
   193  	// The side effect of connecting is that we update the store with new API information
   194  	refresher, err := endpointRefresher(c)
   195  	if err != nil {
   196  		return emptyEndpoint, err
   197  	}
   198  	refresher.Close()
   199  
   200  	info, err = ConnectionInfoForName(c.envName)
   201  	if err != nil {
   202  		return emptyEndpoint, err
   203  	}
   204  	return info.APIEndpoint(), nil
   205  }
   206  
   207  // ConnectionWriter defines the methods needed to write information about
   208  // a given connection.  This is a subset of the methods in the interface
   209  // defined in configstore.EnvironInfo.
   210  type ConnectionWriter interface {
   211  	Write() error
   212  	SetAPICredentials(configstore.APICredentials)
   213  	SetAPIEndpoint(configstore.APIEndpoint)
   214  	SetBootstrapConfig(map[string]interface{})
   215  	Location() string
   216  }
   217  
   218  var endpointRefresher = func(c *EnvCommandBase) (io.Closer, error) {
   219  	return c.NewAPIRoot()
   220  }
   221  
   222  var getConfigStore = func() (configstore.Storage, error) {
   223  	store, err := configstore.Default()
   224  	if err != nil {
   225  		return nil, errors.Trace(err)
   226  	}
   227  	return store, nil
   228  }
   229  
   230  // ConnectionInfoForName reads the environment information for the named
   231  // environment (envName) and returns it.
   232  func ConnectionInfoForName(envName string) (configstore.EnvironInfo, error) {
   233  	store, err := getConfigStore()
   234  	if err != nil {
   235  		return nil, errors.Trace(err)
   236  	}
   237  	info, err := store.ReadInfo(envName)
   238  	if err != nil {
   239  		return nil, errors.Trace(err)
   240  	}
   241  	return info, nil
   242  }
   243  
   244  // ConnectionWriter returns an instance that is able to be used
   245  // to record information about the connection.  When the connection
   246  // is determined through either command line parameters or environment
   247  // variables, an error is returned.
   248  func (c *EnvCommandBase) ConnectionWriter() (ConnectionWriter, error) {
   249  	// TODO: when accessing with just command line params or environment
   250  	// variables, this should error.
   251  	if c.envName == "" {
   252  		return nil, errors.Trace(ErrNoEnvironmentSpecified)
   253  	}
   254  	return ConnectionInfoForName(c.envName)
   255  }
   256  
   257  // CompatVersion returns the minimum CLI version
   258  // that this command should be compatible with.
   259  func (c *EnvCommandBase) CompatVersion() int {
   260  	if c.compatVerson != nil {
   261  		return *c.compatVerson
   262  	}
   263  	compatVerson := 1
   264  	val := os.Getenv(osenv.JujuCLIVersion)
   265  	if val != "" {
   266  		vers, err := strconv.Atoi(val)
   267  		if err != nil {
   268  			logger.Warningf("invalid %s value: %v", osenv.JujuCLIVersion, val)
   269  		} else {
   270  			compatVerson = vers
   271  		}
   272  	}
   273  	c.compatVerson = &compatVerson
   274  	return *c.compatVerson
   275  }
   276  
   277  // ConnectionName returns the name of the connection if there is one.
   278  // It is possible that the name of the connection is empty if the
   279  // connection information is supplied through command line arguments
   280  // or environment variables.
   281  func (c *EnvCommandBase) ConnectionName() string {
   282  	return c.envName
   283  }
   284  
   285  // Wrap wraps the specified EnvironCommand, returning a Command
   286  // that proxies to each of the EnvironCommand methods.
   287  func Wrap(c EnvironCommand) cmd.Command {
   288  	return &environCommandWrapper{EnvironCommand: c}
   289  }
   290  
   291  type environCommandWrapper struct {
   292  	EnvironCommand
   293  	envName string
   294  }
   295  
   296  func (w *environCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   297  	f.StringVar(&w.envName, "e", "", "juju environment to operate in")
   298  	f.StringVar(&w.envName, "environment", "", "")
   299  	w.EnvironCommand.SetFlags(f)
   300  }
   301  
   302  func (w *environCommandWrapper) Init(args []string) error {
   303  	if w.envName == "" {
   304  		// Look for the default.
   305  		defaultEnv, err := GetDefaultEnvironment()
   306  		if err != nil {
   307  			return err
   308  		}
   309  		w.envName = defaultEnv
   310  	}
   311  	w.SetEnvName(w.envName)
   312  	return w.EnvironCommand.Init(args)
   313  }
   314  
   315  type bootstrapContext struct {
   316  	*cmd.Context
   317  	verifyCredentials bool
   318  }
   319  
   320  // ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials
   321  func (ctx *bootstrapContext) ShouldVerifyCredentials() bool {
   322  	return ctx.verifyCredentials
   323  }
   324  
   325  // BootstrapContext returns a new BootstrapContext constructed from a command Context.
   326  func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext {
   327  	return &bootstrapContext{
   328  		Context:           cmdContext,
   329  		verifyCredentials: true,
   330  	}
   331  }
   332  
   333  // BootstrapContextNoVerify returns a new BootstrapContext constructed from a command Context
   334  // where the validation of credentials is false.
   335  func BootstrapContextNoVerify(cmdContext *cmd.Context) environs.BootstrapContext {
   336  	return &bootstrapContext{
   337  		Context:           cmdContext,
   338  		verifyCredentials: false,
   339  	}
   340  }
   341  
   342  type EnvironmentGetter interface {
   343  	EnvironmentGet() (map[string]interface{}, error)
   344  	Close() error
   345  }
   346  
   347  // GetEnvironmentVersion retrieves the environment's agent-version
   348  // value from an API client.
   349  func GetEnvironmentVersion(client EnvironmentGetter) (version.Number, error) {
   350  	noVersion := version.Number{}
   351  	attrs, err := client.EnvironmentGet()
   352  	if err != nil {
   353  		return noVersion, errors.Annotate(err, "unable to retrieve environment config")
   354  	}
   355  	vi, found := attrs["agent-version"]
   356  	if !found {
   357  		return noVersion, errors.New("version not found in environment config")
   358  	}
   359  	vs, ok := vi.(string)
   360  	if !ok {
   361  		return noVersion, errors.New("invalid environment version type in config")
   362  	}
   363  	v, err := version.Parse(vs)
   364  	if err != nil {
   365  		return noVersion, errors.Annotate(err, "unable to parse environment version")
   366  	}
   367  	return v, nil
   368  }