github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/gnuflag"
    14  	"github.com/juju/loggo"
    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 in focus.
    28  
    29  Please use "juju models" to see models available to you.
    30  You can set current model by running "juju switch"
    31  or specify any other model on the command line using the "-m" flag.
    32  `)
    33  
    34  // GetCurrentModel returns the name of the current Juju model.
    35  //
    36  // If $JUJU_MODEL is set, use that. Otherwise, get the current
    37  // controller from controllers.yaml, and then identify the current
    38  // model for that controller in models.yaml. If there is no current
    39  // controller, then an empty string is returned. It is not an error
    40  // to have no current model.
    41  //
    42  // If there is a current controller, but no current model for that
    43  // controller, then GetCurrentModel will return the string
    44  // "<controller>:". If there is a current model as well, it will
    45  // return "<controller>:<model>". Only when $JUJU_MODEL is set,
    46  // will the result possibly be unqualified.
    47  func GetCurrentModel(store jujuclient.ClientStore) (string, error) {
    48  	if model := os.Getenv(osenv.JujuModelEnvKey); model != "" {
    49  		return model, nil
    50  	}
    51  
    52  	currentController, err := store.CurrentController()
    53  	if errors.IsNotFound(err) {
    54  		return "", nil
    55  	} else if err != nil {
    56  		return "", errors.Trace(err)
    57  	}
    58  
    59  	currentModel, err := store.CurrentModel(currentController)
    60  	if errors.IsNotFound(err) {
    61  		return currentController + ":", nil
    62  	} else if err != nil {
    63  		return "", errors.Trace(err)
    64  	}
    65  	return JoinModelName(currentController, currentModel), nil
    66  }
    67  
    68  // ModelCommand extends cmd.Command with a SetModelName method.
    69  type ModelCommand interface {
    70  	CommandBase
    71  
    72  	// SetClientStore is called prior to the wrapped command's Init method
    73  	// with the default controller store. It may also be called to override the
    74  	// default controller store for testing.
    75  	SetClientStore(jujuclient.ClientStore)
    76  
    77  	// ClientStore returns the controller store that the command is
    78  	// associated with.
    79  	ClientStore() jujuclient.ClientStore
    80  
    81  	// SetModelName sets the model name for this command. Setting the model
    82  	// name will also set the related controller name. The model name can
    83  	// be qualified with a controller name (controller:model), or
    84  	// unqualified, in which case it will be assumed to be within the
    85  	// current controller.
    86  	//
    87  	// SetModelName is called prior to the wrapped command's Init method
    88  	// with the active model name. The model name is guaranteed
    89  	// to be non-empty at entry of Init.
    90  	SetModelName(modelName string) error
    91  
    92  	// ModelName returns the name of the model.
    93  	ModelName() string
    94  
    95  	// ControllerName returns the name of the controller that contains
    96  	// the model returned by ModelName().
    97  	ControllerName() string
    98  
    99  	// SetAPIOpener allows the replacement of the default API opener,
   100  	// which ends up calling NewAPIRoot
   101  	SetAPIOpener(opener APIOpener)
   102  }
   103  
   104  // ModelCommandBase is a convenience type for embedding in commands
   105  // that wish to implement ModelCommand.
   106  type ModelCommandBase struct {
   107  	JujuCommandBase
   108  
   109  	// store is the client controller store that contains information
   110  	// about controllers, models, etc.
   111  	store jujuclient.ClientStore
   112  
   113  	modelName      string
   114  	controllerName string
   115  
   116  	// opener is the strategy used to open the API connection.
   117  	opener APIOpener
   118  }
   119  
   120  // SetClientStore implements the ModelCommand interface.
   121  func (c *ModelCommandBase) SetClientStore(store jujuclient.ClientStore) {
   122  	c.store = store
   123  }
   124  
   125  // ClientStore implements the ModelCommand interface.
   126  func (c *ModelCommandBase) ClientStore() jujuclient.ClientStore {
   127  	return c.store
   128  }
   129  
   130  // SetModelName implements the ModelCommand interface.
   131  func (c *ModelCommandBase) SetModelName(modelName string) error {
   132  	controllerName, modelName := SplitModelName(modelName)
   133  	if controllerName == "" {
   134  		currentController, err := c.store.CurrentController()
   135  		if errors.IsNotFound(err) {
   136  			return errors.Errorf("no current controller, and none specified")
   137  		} else if err != nil {
   138  			return errors.Trace(err)
   139  		}
   140  		controllerName = currentController
   141  	} else {
   142  		var err error
   143  		if _, err = c.store.ControllerByName(controllerName); err != nil {
   144  			return errors.Trace(err)
   145  		}
   146  	}
   147  	c.controllerName = controllerName
   148  	c.modelName = modelName
   149  	return nil
   150  }
   151  
   152  // ModelName implements the ModelCommand interface.
   153  func (c *ModelCommandBase) ModelName() string {
   154  	return c.modelName
   155  }
   156  
   157  // ControllerName implements the ModelCommand interface.
   158  func (c *ModelCommandBase) ControllerName() string {
   159  	return c.controllerName
   160  }
   161  
   162  // SetAPIOpener specifies the strategy used by the command to open
   163  // the API connection.
   164  func (c *ModelCommandBase) SetAPIOpener(opener APIOpener) {
   165  	c.opener = opener
   166  }
   167  
   168  func (c *ModelCommandBase) NewAPIClient() (*api.Client, error) {
   169  	root, err := c.NewAPIRoot()
   170  	if err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	return root.Client(), nil
   174  }
   175  
   176  // NewAPIRoot returns a new connection to the API server for the environment
   177  // directed to the model specified on the command line.
   178  func (c *ModelCommandBase) NewAPIRoot() (api.Connection, error) {
   179  	// This is work in progress as we remove the ModelName from downstream code.
   180  	// We want to be able to specify the environment in a number of ways, one of
   181  	// which is the connection name on the client machine.
   182  	if c.modelName == "" {
   183  		return nil, errors.Trace(ErrNoModelSpecified)
   184  	}
   185  	_, err := c.store.ModelByName(c.controllerName, c.modelName)
   186  	if err != nil {
   187  		if !errors.IsNotFound(err) {
   188  			return nil, errors.Trace(err)
   189  		}
   190  		// The model isn't known locally, so query the models
   191  		// available in the controller, and cache them locally.
   192  		if err := c.RefreshModels(c.store, c.controllerName); err != nil {
   193  			return nil, errors.Annotate(err, "refreshing models")
   194  		}
   195  	}
   196  	return c.newAPIRoot(c.modelName)
   197  }
   198  
   199  // NewControllerAPIRoot returns a new connection to the API server for the environment
   200  // directed to the controller specified on the command line.
   201  // This is for the use of model-centered commands that still want
   202  // to talk to controller-only APIs.
   203  func (c *ModelCommandBase) NewControllerAPIRoot() (api.Connection, error) {
   204  	return c.newAPIRoot("")
   205  }
   206  
   207  // newAPIRoot is the internal implementation of NewAPIRoot and NewControllerAPIRoot;
   208  // if modelName is empty, it makes a controller-only connection.
   209  func (c *ModelCommandBase) newAPIRoot(modelName string) (api.Connection, error) {
   210  	if c.controllerName == "" {
   211  		controllers, err := c.store.AllControllers()
   212  		if err != nil {
   213  			return nil, errors.Trace(err)
   214  		}
   215  		if len(controllers) == 0 {
   216  			return nil, errors.Trace(ErrNoControllersDefined)
   217  		}
   218  		return nil, errors.Trace(ErrNoCurrentController)
   219  	}
   220  	opener := c.opener
   221  	if opener == nil {
   222  		opener = OpenFunc(c.JujuCommandBase.NewAPIRoot)
   223  	}
   224  	return opener.Open(c.store, c.controllerName, modelName)
   225  }
   226  
   227  // ConnectionName returns the name of the connection if there is one.
   228  // It is possible that the name of the connection is empty if the
   229  // connection information is supplied through command line arguments
   230  // or environment variables.
   231  func (c *ModelCommandBase) ConnectionName() string {
   232  	return c.modelName
   233  }
   234  
   235  // WrapOption specifies an option to the Wrap function.
   236  type WrapOption func(*modelCommandWrapper)
   237  
   238  // Options for the Wrap function.
   239  var (
   240  	// WrapSkipModelFlags specifies that the -m and --model flags
   241  	// should not be defined.
   242  	WrapSkipModelFlags WrapOption = wrapSkipModelFlags
   243  
   244  	// WrapSkipDefaultModel specifies that no default model should
   245  	// be used.
   246  	WrapSkipDefaultModel WrapOption = wrapSkipDefaultModel
   247  )
   248  
   249  func wrapSkipModelFlags(w *modelCommandWrapper) {
   250  	w.skipModelFlags = true
   251  }
   252  
   253  func wrapSkipDefaultModel(w *modelCommandWrapper) {
   254  	w.useDefaultModel = false
   255  }
   256  
   257  // Wrap wraps the specified ModelCommand, returning a Command
   258  // that proxies to each of the ModelCommand methods.
   259  // Any provided options are applied to the wrapped command
   260  // before it is returned.
   261  func Wrap(c ModelCommand, options ...WrapOption) cmd.Command {
   262  	wrapper := &modelCommandWrapper{
   263  		ModelCommand:    c,
   264  		skipModelFlags:  false,
   265  		useDefaultModel: true,
   266  	}
   267  	for _, option := range options {
   268  		option(wrapper)
   269  	}
   270  	return WrapBase(wrapper)
   271  }
   272  
   273  type modelCommandWrapper struct {
   274  	ModelCommand
   275  
   276  	skipModelFlags  bool
   277  	useDefaultModel bool
   278  	modelName       string
   279  }
   280  
   281  func (w *modelCommandWrapper) Run(ctx *cmd.Context) error {
   282  	return w.ModelCommand.Run(ctx)
   283  }
   284  
   285  func (w *modelCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   286  	if !w.skipModelFlags {
   287  		f.StringVar(&w.modelName, "m", "", "Model to operate in. Accepts [<controller name>:]<model name>")
   288  		f.StringVar(&w.modelName, "model", "", "")
   289  	}
   290  	w.ModelCommand.SetFlags(f)
   291  }
   292  
   293  func (w *modelCommandWrapper) Init(args []string) error {
   294  	store := w.ClientStore()
   295  	if store == nil {
   296  		store = jujuclient.NewFileClientStore()
   297  	}
   298  	store = QualifyingClientStore{store}
   299  	w.SetClientStore(store)
   300  	if !w.skipModelFlags {
   301  		if w.modelName == "" && w.useDefaultModel {
   302  			// Look for the default.
   303  			defaultModel, err := GetCurrentModel(store)
   304  			if err != nil {
   305  				return err
   306  			}
   307  			w.modelName = defaultModel
   308  		}
   309  		if w.modelName == "" && !w.useDefaultModel {
   310  			return errors.Trace(ErrNoModelSpecified)
   311  		}
   312  	}
   313  	if w.modelName != "" {
   314  		if err := w.SetModelName(w.modelName); err != nil {
   315  			return translateControllerError(store, err)
   316  		}
   317  	}
   318  	return w.ModelCommand.Init(args)
   319  }
   320  
   321  type bootstrapContext struct {
   322  	*cmd.Context
   323  	verifyCredentials bool
   324  }
   325  
   326  // ShouldVerifyCredentials implements BootstrapContext.ShouldVerifyCredentials
   327  func (ctx *bootstrapContext) ShouldVerifyCredentials() bool {
   328  	return ctx.verifyCredentials
   329  }
   330  
   331  // BootstrapContext returns a new BootstrapContext constructed from a command Context.
   332  func BootstrapContext(cmdContext *cmd.Context) environs.BootstrapContext {
   333  	return &bootstrapContext{
   334  		Context:           cmdContext,
   335  		verifyCredentials: true,
   336  	}
   337  }
   338  
   339  // BootstrapContextNoVerify returns a new BootstrapContext constructed from a command Context
   340  // where the validation of credentials is false.
   341  func BootstrapContextNoVerify(cmdContext *cmd.Context) environs.BootstrapContext {
   342  	return &bootstrapContext{
   343  		Context:           cmdContext,
   344  		verifyCredentials: false,
   345  	}
   346  }
   347  
   348  // SplitModelName splits a model name into its controller
   349  // and model parts. If the model is unqualified, then the
   350  // returned controller string will be empty, and the returned
   351  // model string will be identical to the input.
   352  func SplitModelName(name string) (controller, model string) {
   353  	if i := strings.IndexRune(name, ':'); i >= 0 {
   354  		return name[:i], name[i+1:]
   355  	}
   356  	return "", name
   357  }
   358  
   359  // JoinModelName joins a controller and model name into a
   360  // qualified model name.
   361  func JoinModelName(controller, model string) string {
   362  	return fmt.Sprintf("%s:%s", controller, model)
   363  }