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