github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"net/http"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/gnuflag"
    15  	"github.com/juju/loggo"
    16  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/modelmanager"
    20  	"github.com/juju/juju/core/model"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/juju/osenv"
    23  	"github.com/juju/juju/jujuclient"
    24  )
    25  
    26  var logger = loggo.GetLogger("juju.cmd.modelcmd")
    27  
    28  // ErrNoModelSpecified is returned by commands that operate on
    29  // an environment if there is no current model, no model
    30  // has been explicitly specified, and there is no default model.
    31  var ErrNoModelSpecified = errors.New(`No model in focus.
    32  
    33  Please use "juju models" to see models available to you.
    34  You can set current model by running "juju switch"
    35  or specify any other model on the command line using the "-m" option.
    36  `)
    37  
    38  // ModelCommand extends cmd.Command with a SetModelName method.
    39  type ModelCommand interface {
    40  	Command
    41  
    42  	// SetClientStore is called prior to the wrapped command's Init method
    43  	// with the default controller store. It may also be called to override the
    44  	// default controller store for testing.
    45  	SetClientStore(jujuclient.ClientStore)
    46  
    47  	// ClientStore returns the controller store that the command is
    48  	// associated with.
    49  	ClientStore() jujuclient.ClientStore
    50  
    51  	// SetModelName sets the model name for this command. Setting the model
    52  	// name will also set the related controller name. The model name can
    53  	// be qualified with a controller name (controller:model), or
    54  	// unqualified, in which case it will be assumed to be within the
    55  	// current controller.
    56  	//
    57  	// Passing an empty model name will choose the default
    58  	// model, or return an error if there isn't one.
    59  	//
    60  	// SetModelName is called prior to the wrapped command's Init method
    61  	// with the active model name. The model name is guaranteed
    62  	// to be non-empty at entry of Init.
    63  	SetModelName(modelName string, allowDefault bool) error
    64  
    65  	// ModelName returns the name of the model.
    66  	ModelName() (string, error)
    67  
    68  	// ModelType returns the type of the model.
    69  	ModelType() (model.ModelType, error)
    70  
    71  	// ModelGeneration sets the model generation for this command and updates
    72  	// the store.
    73  	SetModelGeneration(model.GenerationVersion) error
    74  
    75  	// ModelGeneration returns the generation of the model.
    76  	ModelGeneration() (model.GenerationVersion, error)
    77  
    78  	// ControllerName returns the name of the controller that contains
    79  	// the model returned by ModelName().
    80  	ControllerName() (string, error)
    81  
    82  	// maybeInitModel initializes the model name, resolving empty
    83  	// model or controller parts to the current model or controller if
    84  	// needed. It fails a model cannot be determined.
    85  	maybeInitModel() error
    86  }
    87  
    88  // ModelCommandBase is a convenience type for embedding in commands
    89  // that wish to implement ModelCommand.
    90  type ModelCommandBase struct {
    91  	CommandBase
    92  
    93  	// store is the client controller store that contains information
    94  	// about controllers, models, etc.
    95  	store jujuclient.ClientStore
    96  
    97  	// _modelName, _modelType, _modelGeneration and _controllerName hold the
    98  	// current model and controller names, model type and generation. They
    99  	// are only valid after maybeInitModel is called, and should in general
   100  	// not be accessed directly, but through ModelName and ControllerName
   101  	// respectively.
   102  	_modelName       string
   103  	_modelType       model.ModelType
   104  	_modelGeneration model.GenerationVersion
   105  	_controllerName  string
   106  
   107  	allowDefaultModel bool
   108  
   109  	// doneInitModel holds whether maybeInitModel has been called.
   110  	doneInitModel bool
   111  
   112  	// initModelError holds the result of the maybeInitModel call.
   113  	initModelError error
   114  }
   115  
   116  // SetClientStore implements the ModelCommand interface.
   117  func (c *ModelCommandBase) SetClientStore(store jujuclient.ClientStore) {
   118  	c.store = store
   119  }
   120  
   121  // ClientStore implements the ModelCommand interface.
   122  func (c *ModelCommandBase) ClientStore() jujuclient.ClientStore {
   123  	// c.store is set in maybeInitModel() below.
   124  	if c.store == nil && !c.runStarted {
   125  		panic("inappropriate method called before init finished")
   126  	}
   127  	return c.store
   128  }
   129  
   130  func (c *ModelCommandBase) maybeInitModel() error {
   131  	// maybeInitModel() might have been called previously before the actual command's
   132  	// Init() method was invoked. If allowDefaultModel = false, then we would have
   133  	// returned [ErrNoModelSpecified,ErrNoControllersDefined,ErrNoCurrentController]
   134  	// at that point and so need to try again.
   135  	// If any other error result was returned, we bail early here.
   136  	retriableError := func(original error) bool {
   137  		return errors.Cause(c.initModelError) != ErrNoModelSpecified &&
   138  			errors.Cause(c.initModelError) != ErrNoControllersDefined &&
   139  			errors.Cause(c.initModelError) != ErrNoCurrentController
   140  	}
   141  	if c.doneInitModel && retriableError(c.initModelError) {
   142  		return errors.Trace(c.initModelError)
   143  	}
   144  
   145  	// A previous call to maybeInitModel returned
   146  	// [ErrNoModelSpecified,ErrNoControllersDefined,ErrNoCurrentController],
   147  	// so we try again because the model should now have been set.
   148  
   149  	// Set up the client store if not already done.
   150  	if !c.doneInitModel {
   151  		store := c.store
   152  		if store == nil {
   153  			store = jujuclient.NewFileClientStore()
   154  		}
   155  		store = QualifyingClientStore{store}
   156  		c.SetClientStore(store)
   157  	}
   158  
   159  	c.doneInitModel = true
   160  	c.initModelError = c.initModel0()
   161  	return errors.Trace(c.initModelError)
   162  }
   163  
   164  func (c *ModelCommandBase) initModel0() error {
   165  	if c._modelName == "" && !c.allowDefaultModel {
   166  		return errors.Trace(ErrNoModelSpecified)
   167  	}
   168  	if c._modelName == "" {
   169  		c._modelName = os.Getenv(osenv.JujuModelEnvKey)
   170  	}
   171  	controllerName, modelName := SplitModelName(c._modelName)
   172  	if controllerName == "" {
   173  		currentController, err := c.store.CurrentController()
   174  		if err != nil {
   175  			return errors.Trace(translateControllerError(c.store, err))
   176  		}
   177  		controllerName = currentController
   178  	} else if _, err := c.store.ControllerByName(controllerName); err != nil {
   179  		return errors.Trace(err)
   180  	}
   181  	c._controllerName = controllerName
   182  	if modelName == "" {
   183  		currentModel, err := c.store.CurrentModel(controllerName)
   184  		if err != nil {
   185  			return errors.Trace(err)
   186  		}
   187  		modelName = currentModel
   188  	}
   189  	c._modelName = modelName
   190  	return nil
   191  }
   192  
   193  // SetModelName implements the ModelCommand interface.
   194  func (c *ModelCommandBase) SetModelName(modelName string, allowDefault bool) error {
   195  	c._modelName = modelName
   196  	c.allowDefaultModel = allowDefault
   197  
   198  	// After setting the model name, we may need to ensure we have access to the
   199  	// other model details if not already done.
   200  	if err := c.maybeInitModel(); err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	return nil
   204  }
   205  
   206  // ModelName implements the ModelCommand interface.
   207  func (c *ModelCommandBase) ModelName() (string, error) {
   208  	c.assertRunStarted()
   209  	if err := c.maybeInitModel(); err != nil {
   210  		return "", errors.Trace(err)
   211  	}
   212  	return c._modelName, nil
   213  }
   214  
   215  // ModelType implements the ModelCommand interface.
   216  func (c *ModelCommandBase) ModelType() (model.ModelType, error) {
   217  	if c._modelType != "" {
   218  		return c._modelType, nil
   219  	}
   220  	// If we need to look up the model type, we need to ensure we
   221  	// have access to the model details.
   222  	if err := c.maybeInitModel(); err != nil {
   223  		return "", errors.Trace(err)
   224  	}
   225  	details, err := c.store.ModelByName(c._controllerName, c._modelName)
   226  	if err != nil {
   227  		if !c.runStarted {
   228  			return "", errors.Trace(err)
   229  		}
   230  		details, err = c.modelDetails(c._controllerName, c._modelName)
   231  		if err != nil {
   232  			return "", errors.Trace(err)
   233  		}
   234  	}
   235  	c._modelType = details.ModelType
   236  	return c._modelType, nil
   237  }
   238  
   239  // SetModelGeneration implements the ModelCommand interface.
   240  func (c *ModelCommandBase) SetModelGeneration(generation model.GenerationVersion) error {
   241  	_, modelDetails, err := c.ModelDetails()
   242  	if err != nil {
   243  		return errors.Annotate(err, "getting model details")
   244  	}
   245  	modelDetails.ModelGeneration = generation
   246  	if err = c.store.UpdateModel(c._controllerName, c._modelName, *modelDetails); err != nil {
   247  		return err
   248  	}
   249  	c._modelGeneration = generation
   250  	return nil
   251  }
   252  
   253  // ModelGeneration implements the ModelCommand interface.
   254  func (c *ModelCommandBase) ModelGeneration() (model.GenerationVersion, error) {
   255  	if c._modelGeneration != "" {
   256  		return c._modelGeneration, nil
   257  	}
   258  	// If we need to look up the model generation, we need to ensure we
   259  	// have access to the model details.
   260  	if err := c.maybeInitModel(); err != nil {
   261  		return "", errors.Trace(err)
   262  	}
   263  	details, err := c.store.ModelByName(c._controllerName, c._modelName)
   264  	if err != nil {
   265  		if !c.runStarted {
   266  			return "", errors.Trace(err)
   267  		}
   268  		details, err = c.modelDetails(c._controllerName, c._modelName)
   269  		if err != nil {
   270  			return "", errors.Trace(err)
   271  		}
   272  	}
   273  	c._modelGeneration = details.ModelGeneration
   274  	return c._modelGeneration, nil
   275  }
   276  
   277  // ControllerName implements the ModelCommand interface.
   278  func (c *ModelCommandBase) ControllerName() (string, error) {
   279  	c.assertRunStarted()
   280  	if err := c.maybeInitModel(); err != nil {
   281  		return "", errors.Trace(err)
   282  	}
   283  	return c._controllerName, nil
   284  }
   285  
   286  func (c *ModelCommandBase) BakeryClient() (*httpbakery.Client, error) {
   287  	controllerName, err := c.ControllerName()
   288  	if err != nil {
   289  		return nil, errors.Trace(err)
   290  	}
   291  	return c.CommandBase.BakeryClient(c.ClientStore(), controllerName)
   292  }
   293  
   294  func (c *ModelCommandBase) CookieJar() (http.CookieJar, error) {
   295  	controllerName, err := c.ControllerName()
   296  	if err != nil {
   297  		return nil, errors.Trace(err)
   298  	}
   299  	return c.CommandBase.CookieJar(c.ClientStore(), controllerName)
   300  }
   301  
   302  func (c *ModelCommandBase) NewAPIClient() (*api.Client, error) {
   303  	root, err := c.NewAPIRoot()
   304  	if err != nil {
   305  		return nil, errors.Trace(err)
   306  	}
   307  	return root.Client(), nil
   308  }
   309  
   310  func (c *ModelCommandBase) ModelDetails() (string, *jujuclient.ModelDetails, error) {
   311  	modelName, err := c.ModelName()
   312  	if err != nil {
   313  		return "", nil, errors.Trace(err)
   314  	}
   315  	controllerName, err := c.ControllerName()
   316  	if err != nil {
   317  		return "", nil, errors.Trace(err)
   318  	}
   319  	details, err := c.modelDetails(controllerName, modelName)
   320  	return modelName, details, err
   321  }
   322  
   323  func (c *ModelCommandBase) modelDetails(controllerName, modelName string) (*jujuclient.ModelDetails, error) {
   324  	if modelName == "" {
   325  		return nil, errors.Trace(ErrNoModelSpecified)
   326  	}
   327  	details, err := c.store.ModelByName(controllerName, modelName)
   328  	if err != nil {
   329  		if !errors.IsNotFound(err) {
   330  			return nil, errors.Trace(err)
   331  		}
   332  		logger.Debugf("model %q not found, refreshing", modelName)
   333  		// The model isn't known locally, so query the models
   334  		// available in the controller, and cache them locally.
   335  		if err := c.RefreshModels(c.store, controllerName); err != nil {
   336  			return nil, errors.Annotate(err, "refreshing models")
   337  		}
   338  		details, err = c.store.ModelByName(controllerName, modelName)
   339  	}
   340  	return details, errors.Trace(err)
   341  }
   342  
   343  // NewAPIRoot returns a new connection to the API server for the environment
   344  // directed to the model specified on the command line.
   345  func (c *ModelCommandBase) NewAPIRoot() (api.Connection, error) {
   346  	// We need to call ModelDetails() here and not just ModelName() to force
   347  	// a refresh of the internal model details if those are not yet stored locally.
   348  	modelName, _, err := c.ModelDetails()
   349  	if err != nil {
   350  		return nil, errors.Trace(err)
   351  	}
   352  	return c.newAPIRoot(modelName)
   353  }
   354  
   355  // NewControllerAPIRoot returns a new connection to the API server for the environment
   356  // directed to the controller specified on the command line.
   357  // This is for the use of model-centered commands that still want
   358  // to talk to controller-only APIs.
   359  func (c *ModelCommandBase) NewControllerAPIRoot() (api.Connection, error) {
   360  	return c.newAPIRoot("")
   361  }
   362  
   363  // newAPIRoot is the internal implementation of NewAPIRoot and NewControllerAPIRoot;
   364  // if modelName is empty, it makes a controller-only connection.
   365  func (c *ModelCommandBase) newAPIRoot(modelName string) (api.Connection, error) {
   366  	controllerName, err := c.ControllerName()
   367  	if err != nil {
   368  		return nil, errors.Trace(err)
   369  	}
   370  	return c.CommandBase.NewAPIRoot(c.store, controllerName, modelName)
   371  }
   372  
   373  // ModelUUIDs returns the model UUIDs for the given model names.
   374  func (c *ModelCommandBase) ModelUUIDs(modelNames []string) ([]string, error) {
   375  	controllerName, err := c.ControllerName()
   376  	if err != nil {
   377  		return nil, errors.Trace(err)
   378  	}
   379  	return c.CommandBase.ModelUUIDs(c.ClientStore(), controllerName, modelNames)
   380  }
   381  
   382  // CurrentAccountDetails returns details of the account associated with
   383  // the current controller.
   384  func (c *ModelCommandBase) CurrentAccountDetails() (*jujuclient.AccountDetails, error) {
   385  	controllerName, err := c.ControllerName()
   386  	if err != nil {
   387  		return nil, errors.Trace(err)
   388  	}
   389  	return c.ClientStore().AccountDetails(controllerName)
   390  }
   391  
   392  // NewModelManagerAPIClient returns an API client for the
   393  // ModelManager on the current controller using the current credentials.
   394  func (c *ModelCommandBase) NewModelManagerAPIClient() (*modelmanager.Client, error) {
   395  	root, err := c.NewControllerAPIRoot()
   396  	if err != nil {
   397  		return nil, errors.Trace(err)
   398  	}
   399  	return modelmanager.NewClient(root), nil
   400  }
   401  
   402  // WrapOption specifies an option to the Wrap function.
   403  type WrapOption func(*modelCommandWrapper)
   404  
   405  // Options for the Wrap function.
   406  var (
   407  	// WrapSkipModelFlags specifies that the -m and --model flags
   408  	// should not be defined.
   409  	WrapSkipModelFlags WrapOption = wrapSkipModelFlags
   410  
   411  	// WrapSkipDefaultModel specifies that no default model should
   412  	// be used.
   413  	WrapSkipDefaultModel WrapOption = wrapSkipDefaultModel
   414  )
   415  
   416  func wrapSkipModelFlags(w *modelCommandWrapper) {
   417  	w.skipModelFlags = true
   418  }
   419  
   420  func wrapSkipDefaultModel(w *modelCommandWrapper) {
   421  	w.useDefaultModel = false
   422  }
   423  
   424  // Wrap wraps the specified ModelCommand, returning a ModelCommand
   425  // that proxies to each of the ModelCommand methods.
   426  // Any provided options are applied to the wrapped command
   427  // before it is returned.
   428  func Wrap(c ModelCommand, options ...WrapOption) ModelCommand {
   429  	wrapper := &modelCommandWrapper{
   430  		ModelCommand:    c,
   431  		skipModelFlags:  false,
   432  		useDefaultModel: true,
   433  	}
   434  	for _, option := range options {
   435  		option(wrapper)
   436  	}
   437  	// Define a new type so that we can embed the ModelCommand
   438  	// interface one level deeper than cmd.Command, so that
   439  	// we'll get the Command methods from WrapBase
   440  	// and all the ModelCommand methods not in cmd.Command
   441  	// from modelCommandWrapper.
   442  	type embed struct {
   443  		*modelCommandWrapper
   444  	}
   445  	return struct {
   446  		embed
   447  		cmd.Command
   448  	}{
   449  		Command: WrapBase(wrapper),
   450  		embed:   embed{wrapper},
   451  	}
   452  }
   453  
   454  type modelCommandWrapper struct {
   455  	ModelCommand
   456  
   457  	skipModelFlags  bool
   458  	useDefaultModel bool
   459  	modelName       string
   460  }
   461  
   462  func (w *modelCommandWrapper) inner() cmd.Command {
   463  	return w.ModelCommand
   464  }
   465  
   466  type modelSpecificCommand interface {
   467  	// IncompatibleModel returns an error if the command is being run against
   468  	// a model with which it is not compatible.
   469  	IncompatibleModel(err error) error
   470  }
   471  
   472  // IAASOnlyCommand is used as a marker and is embedded
   473  // by commands which should only run in IAAS models.
   474  type IAASOnlyCommand interface {
   475  	_iaasonly() // not implemented, marker only.
   476  }
   477  
   478  // CAASOnlyCommand is used as a marker and is embedded
   479  // by commands which should only run in CAAS models.
   480  type CAASOnlyCommand interface {
   481  	_caasonly() // not implemented, marker only.
   482  }
   483  
   484  // validateCommandForModelType returns an error if an IAAS-only command
   485  // is run on a CAAS model.
   486  func (w *modelCommandWrapper) validateCommandForModelType(runStarted bool) error {
   487  	_, iaasOnly := w.inner().(IAASOnlyCommand)
   488  	_, caasOnly := w.inner().(CAASOnlyCommand)
   489  	if !caasOnly && !iaasOnly {
   490  		return nil
   491  	}
   492  
   493  	modelType, err := w.ModelCommand.ModelType()
   494  	if err != nil {
   495  		err = errors.Cause(err)
   496  		// We need to error if Run() has been invoked the model is known and there was
   497  		// some other error. If the model is not yet known, we'll grab the details
   498  		// during the Run() API call later.
   499  		if runStarted || (err != ErrNoModelSpecified && !errors.IsNotFound(err)) {
   500  			return errors.Trace(err)
   501  		}
   502  		return nil
   503  	}
   504  	if modelType == model.CAAS && iaasOnly {
   505  		err = errors.Errorf("Juju command %q not supported on kubernetes models", w.Info().Name)
   506  	}
   507  	if modelType == model.IAAS && caasOnly {
   508  		err = errors.Errorf("Juju command %q not supported on non-container models", w.Info().Name)
   509  	}
   510  
   511  	if c, ok := w.inner().(modelSpecificCommand); ok {
   512  		return c.IncompatibleModel(err)
   513  	}
   514  	return err
   515  }
   516  
   517  func (w *modelCommandWrapper) Init(args []string) error {
   518  	if !w.skipModelFlags {
   519  		if err := w.ModelCommand.SetModelName(w.modelName, w.useDefaultModel); err != nil {
   520  			return errors.Trace(err)
   521  		}
   522  	}
   523  
   524  	// If we are able to get access to the model type before running the actual
   525  	// command's Init(), we can bail early if the command is not supported for the
   526  	// specific model type. Otherwise, if the command is one which doesn't allow a
   527  	// default model, we need to wait till Run() is invoked.
   528  	if err := w.validateCommandForModelType(false); err != nil {
   529  		return errors.Trace(err)
   530  	}
   531  
   532  	if err := w.ModelCommand.Init(args); err != nil {
   533  		return errors.Trace(err)
   534  	}
   535  	return nil
   536  }
   537  
   538  func (w *modelCommandWrapper) Run(ctx *cmd.Context) error {
   539  	w.setRunStarted()
   540  	store := w.ClientStore()
   541  	if store == nil {
   542  		store = jujuclient.NewFileClientStore()
   543  	}
   544  	store = QualifyingClientStore{store}
   545  	w.SetClientStore(store)
   546  
   547  	// Some commands are only supported on IAAS models.
   548  	if err := w.validateCommandForModelType(true); err != nil {
   549  		return errors.Trace(err)
   550  	}
   551  	return w.ModelCommand.Run(ctx)
   552  }
   553  
   554  func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   555  	if !w.skipModelFlags {
   556  		f.StringVar(&w.modelName, "m", "", "Model to operate in. Accepts [<controller name>:]<model name>")
   557  		f.StringVar(&w.modelName, "model", "", "")
   558  	}
   559  	w.ModelCommand.SetFlags(f)
   560  }
   561  
   562  type bootstrapContext struct {
   563  	*cmd.Context
   564  	verifyCredentials bool
   565  }
   566  
   567  // ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials
   568  func (ctx *bootstrapContext) ShouldVerifyCredentials() bool {
   569  	return ctx.verifyCredentials
   570  }
   571  
   572  // BootstrapContext returns a new BootstrapContext constructed from a command Context.
   573  func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext {
   574  	return &bootstrapContext{
   575  		Context:           cmdContext,
   576  		verifyCredentials: true,
   577  	}
   578  }
   579  
   580  // BootstrapContextNoVerify returns a new BootstrapContext constructed from a command Context
   581  // where the validation of credentials is false.
   582  func BootstrapContextNoVerify(cmdContext *cmd.Context) environs.BootstrapContext {
   583  	return &bootstrapContext{
   584  		Context:           cmdContext,
   585  		verifyCredentials: false,
   586  	}
   587  }
   588  
   589  // SplitModelName splits a model name into its controller
   590  // and model parts. If the model is unqualified, then the
   591  // returned controller string will be empty, and the returned
   592  // model string will be identical to the input.
   593  func SplitModelName(name string) (controller, model string) {
   594  	if i := strings.IndexRune(name, ':'); i >= 0 {
   595  		return name[:i], name[i+1:]
   596  	}
   597  	return "", name
   598  }
   599  
   600  // JoinModelName joins a controller and model name into a
   601  // qualified model name.
   602  func JoinModelName(controller, model string) string {
   603  	return fmt.Sprintf("%s:%s", controller, model)
   604  }