github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/cmd/envcmd/environmentcommand.go (about)

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