github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/modelcmd/modelcommand.go (about)

     1  // Copyright 2013-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcmd
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strings"
    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/juju/osenv"
    19  	"github.com/juju/juju/jujuclient"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.cmd.modelcmd")
    23  
    24  // ErrNoModelSpecified is returned by commands that operate on
    25  // an environment if there is no current model, no model
    26  // has been explicitly specified, and there is no default model.
    27  var ErrNoModelSpecified = errors.New(`no model specified
    28  
    29  There is no current model specified for the current controller,
    30  and none specified on the command line. Please use "juju switch"
    31  to set the current model, or specify a model on the command line
    32  using the "-m" flag.
    33  `)
    34  
    35  // GetCurrentModel returns the name of the current Juju model.
    36  //
    37  // If $JUJU_MODEL is set, use that. Otherwise, get the current
    38  // controller by reading $XDG_DATA_HOME/juju/current-controller,
    39  // and then identifying the current model for that controller
    40  // in models.yaml. If there is no current controller, or no
    41  // current model for that controller, then an empty string is
    42  // returned. It is not an error to have no default model.
    43  func GetCurrentModel(store jujuclient.ClientStore) (string, error) {
    44  	if model := os.Getenv(osenv.JujuModelEnvKey); model != "" {
    45  		return model, nil
    46  	}
    47  
    48  	currentController, err := ReadCurrentController()
    49  	if err != nil {
    50  		return "", errors.Trace(err)
    51  	}
    52  	if currentController == "" {
    53  		return "", nil
    54  	}
    55  
    56  	currentAccount, err := store.CurrentAccount(currentController)
    57  	if errors.IsNotFound(err) {
    58  		return "", nil
    59  	} else if err != nil {
    60  		return "", errors.Trace(err)
    61  	}
    62  
    63  	currentModel, err := store.CurrentModel(currentController, currentAccount)
    64  	if errors.IsNotFound(err) {
    65  		return "", nil
    66  	} else if err != nil {
    67  		return "", errors.Trace(err)
    68  	}
    69  	return currentModel, nil
    70  }
    71  
    72  // ModelCommand extends cmd.Command with a SetModelName method.
    73  type ModelCommand interface {
    74  	CommandBase
    75  
    76  	// SetClientStore is called prior to the wrapped command's Init method
    77  	// with the default controller store. It may also be called to override the
    78  	// default controller store for testing.
    79  	SetClientStore(jujuclient.ClientStore)
    80  
    81  	// ClientStore returns the controller store that the command is
    82  	// associated with.
    83  	ClientStore() jujuclient.ClientStore
    84  
    85  	// SetModelName sets the model name for this command. Setting the model
    86  	// name will also set the related controller name. The model name can
    87  	// be qualified with a controller name (controller:model), or
    88  	// unqualified, in which case it will be assumed to be within the
    89  	// current controller.
    90  	//
    91  	// SetModelName is called prior to the wrapped command's Init method
    92  	// with the active model name. The model name is guaranteed
    93  	// to be non-empty at entry of Init.
    94  	SetModelName(modelName string) error
    95  
    96  	// ModelName returns the name of the model.
    97  	ModelName() string
    98  
    99  	// ControllerName returns the name of the controller that contains
   100  	// the model returned by ModelName().
   101  	ControllerName() string
   102  
   103  	// SetAPIOpener allows the replacement of the default API opener,
   104  	// which ends up calling NewAPIRoot
   105  	SetAPIOpener(opener APIOpener)
   106  }
   107  
   108  // ModelCommandBase is a convenience type for embedding in commands
   109  // that wish to implement ModelCommand.
   110  type ModelCommandBase struct {
   111  	JujuCommandBase
   112  
   113  	// store is the client controller store that contains information
   114  	// about controllers, models, etc.
   115  	store jujuclient.ClientStore
   116  
   117  	modelName      string
   118  	accountName    string
   119  	controllerName string
   120  
   121  	// opener is the strategy used to open the API connection.
   122  	opener APIOpener
   123  }
   124  
   125  // SetClientStore implements the ModelCommand interface.
   126  func (c *ModelCommandBase) SetClientStore(store jujuclient.ClientStore) {
   127  	c.store = store
   128  }
   129  
   130  // ClientStore implements the ModelCommand interface.
   131  func (c *ModelCommandBase) ClientStore() jujuclient.ClientStore {
   132  	return c.store
   133  }
   134  
   135  // SetModelName implements the ModelCommand interface.
   136  func (c *ModelCommandBase) SetModelName(modelName string) error {
   137  	controllerName, modelName := SplitModelName(modelName)
   138  	if controllerName == "" {
   139  		currentController, err := ReadCurrentController()
   140  		if err != nil {
   141  			return errors.Trace(err)
   142  		}
   143  		if currentController == "" {
   144  			return errors.Errorf("no current controller, and none specified")
   145  		}
   146  		controllerName = currentController
   147  	} else {
   148  		var err error
   149  		controllerName, err = ResolveControllerName(c.store, controllerName)
   150  		if err != nil {
   151  			return errors.Trace(err)
   152  		}
   153  	}
   154  	accountName, err := c.store.CurrentAccount(controllerName)
   155  	if err != nil {
   156  		return errors.Trace(err)
   157  	}
   158  	c.controllerName = controllerName
   159  	c.accountName = accountName
   160  	c.modelName = modelName
   161  	return nil
   162  }
   163  
   164  // ModelName implements the ModelCommand interface.
   165  func (c *ModelCommandBase) ModelName() string {
   166  	return c.modelName
   167  }
   168  
   169  // AccountName implements the ModelCommand interface.
   170  func (c *ModelCommandBase) AccountName() string {
   171  	return c.accountName
   172  }
   173  
   174  // ControllerName implements the ModelCommand interface.
   175  func (c *ModelCommandBase) ControllerName() string {
   176  	return c.controllerName
   177  }
   178  
   179  // SetAPIOpener specifies the strategy used by the command to open
   180  // the API connection.
   181  func (c *ModelCommandBase) SetAPIOpener(opener APIOpener) {
   182  	c.opener = opener
   183  }
   184  
   185  func (c *ModelCommandBase) NewAPIClient() (*api.Client, error) {
   186  	root, err := c.NewAPIRoot()
   187  	if err != nil {
   188  		return nil, errors.Trace(err)
   189  	}
   190  	return root.Client(), nil
   191  }
   192  
   193  // NewAPIRoot returns a new connection to the API server for the environment.
   194  func (c *ModelCommandBase) NewAPIRoot() (api.Connection, error) {
   195  	// This is work in progress as we remove the ModelName from downstream code.
   196  	// We want to be able to specify the environment in a number of ways, one of
   197  	// which is the connection name on the client machine.
   198  	if c.controllerName == "" {
   199  		return nil, errors.Trace(ErrNoControllerSpecified)
   200  	}
   201  	if c.modelName == "" {
   202  		return nil, errors.Trace(ErrNoModelSpecified)
   203  	}
   204  	opener := c.opener
   205  	if opener == nil {
   206  		opener = OpenFunc(c.JujuCommandBase.NewAPIRoot)
   207  	}
   208  	_, err := c.store.ModelByName(c.controllerName, c.accountName, c.modelName)
   209  	if err != nil {
   210  		if !errors.IsNotFound(err) {
   211  			return nil, errors.Trace(err)
   212  		}
   213  		// The model isn't known locally, so query the models
   214  		// available in the controller, and cache them locally.
   215  		if err := c.RefreshModels(c.store, c.controllerName, c.accountName); err != nil {
   216  			return nil, errors.Annotate(err, "refreshing models")
   217  		}
   218  	}
   219  	return opener.Open(c.store, c.controllerName, c.accountName, c.modelName)
   220  }
   221  
   222  // ConnectionName returns the name of the connection if there is one.
   223  // It is possible that the name of the connection is empty if the
   224  // connection information is supplied through command line arguments
   225  // or environment variables.
   226  func (c *ModelCommandBase) ConnectionName() string {
   227  	return c.modelName
   228  }
   229  
   230  // WrapControllerOption sets various parameters of the
   231  // ModelCommand wrapper.
   232  type WrapEnvOption func(*modelCommandWrapper)
   233  
   234  // ModelSkipFlags instructs the wrapper to skip --m and
   235  // --model flag definition.
   236  func ModelSkipFlags(w *modelCommandWrapper) {
   237  	w.skipFlags = true
   238  }
   239  
   240  // ModelSkipDefault instructs the wrapper not to
   241  // use the default model.
   242  func ModelSkipDefault(w *modelCommandWrapper) {
   243  	w.useDefaultModel = false
   244  }
   245  
   246  // Wrap wraps the specified ModelCommand, returning a Command
   247  // that proxies to each of the ModelCommand methods.
   248  // Any provided options are applied to the wrapped command
   249  // before it is returned.
   250  func Wrap(c ModelCommand, options ...WrapEnvOption) cmd.Command {
   251  	wrapper := &modelCommandWrapper{
   252  		ModelCommand:    c,
   253  		skipFlags:       false,
   254  		useDefaultModel: true,
   255  		allowEmptyEnv:   false,
   256  	}
   257  	for _, option := range options {
   258  		option(wrapper)
   259  	}
   260  	return WrapBase(wrapper)
   261  }
   262  
   263  type modelCommandWrapper struct {
   264  	ModelCommand
   265  
   266  	skipFlags       bool
   267  	useDefaultModel bool
   268  	allowEmptyEnv   bool
   269  	modelName       string
   270  }
   271  
   272  func (w *modelCommandWrapper) Run(ctx *cmd.Context) error {
   273  	return w.ModelCommand.Run(ctx)
   274  }
   275  
   276  func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   277  	if !w.skipFlags {
   278  		f.StringVar(&w.modelName, "m", "", "Model to operate in")
   279  		f.StringVar(&w.modelName, "model", "", "")
   280  	}
   281  	w.ModelCommand.SetFlags(f)
   282  }
   283  
   284  func (w *modelCommandWrapper) Init(args []string) error {
   285  	store := w.ClientStore()
   286  	if store == nil {
   287  		store = jujuclient.NewFileClientStore()
   288  		w.SetClientStore(store)
   289  	}
   290  	if !w.skipFlags {
   291  		if w.modelName == "" && w.useDefaultModel {
   292  			// Look for the default.
   293  			defaultModel, err := GetCurrentModel(store)
   294  			if err != nil {
   295  				return err
   296  			}
   297  			w.modelName = defaultModel
   298  		}
   299  		if w.modelName == "" && !w.useDefaultModel {
   300  			if w.allowEmptyEnv {
   301  				return w.ModelCommand.Init(args)
   302  			} else {
   303  				return errors.Trace(ErrNoModelSpecified)
   304  			}
   305  		}
   306  	}
   307  	if w.modelName != "" {
   308  		if err := w.SetModelName(w.modelName); err != nil {
   309  			return errors.Annotate(err, "setting model name")
   310  		}
   311  	}
   312  	return w.ModelCommand.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 ModelGetter interface {
   343  	ModelGet() (map[string]interface{}, error)
   344  	Close() error
   345  }
   346  
   347  // SplitModelName splits a model name into its controller
   348  // and model parts. If the model is unqualified, then the
   349  // returned controller string will be empty, and the returned
   350  // model string will be identical to the input.
   351  func SplitModelName(name string) (controller, model string) {
   352  	if i := strings.IndexRune(name, ':'); i >= 0 {
   353  		return name[:i], name[i+1:]
   354  	}
   355  	return "", name
   356  }
   357  
   358  // JoinModelName joins a controller and model name into a
   359  // qualified model name.
   360  func JoinModelName(controller, model string) string {
   361  	return fmt.Sprintf("%s:%s", controller, model)
   362  }