github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/modelcmd/controller.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcmd
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"launchpad.net/gnuflag"
    10  
    11  	"github.com/juju/juju/api"
    12  	"github.com/juju/juju/api/controller"
    13  	"github.com/juju/juju/api/modelmanager"
    14  	"github.com/juju/juju/api/usermanager"
    15  	"github.com/juju/juju/jujuclient"
    16  )
    17  
    18  var (
    19  	// ErrNoControllerSpecified is returned by commands that operate on
    20  	// a controller if there is no current controller, no controller has been
    21  	// explicitly specified, and there is no default controller.
    22  	ErrNoControllerSpecified = errors.New(`no controller specified
    23  
    24  There is no current controller. Please use "juju switch" to
    25  set the current controller/model, or create a new controller
    26  using "juju bootstrap".
    27  `)
    28  
    29  	// ErrNoAccountSpecified is returned by commands that operate on a
    30  	// controller if there is no current account associated with the
    31  	// controller.
    32  	ErrNoAccountSpecified = errors.New("no account specified")
    33  )
    34  
    35  // ControllerCommand is intended to be a base for all commands
    36  // that need to operate on controllers as opposed to models.
    37  type ControllerCommand interface {
    38  	CommandBase
    39  
    40  	// SetClientStore is called prior to the wrapped command's Init method
    41  	// with the default controller store. It may also be called to override the
    42  	// default controller store for testing.
    43  	SetClientStore(jujuclient.ClientStore)
    44  
    45  	// ClientStore returns the controller store that the command is
    46  	// associated with.
    47  	ClientStore() jujuclient.ClientStore
    48  
    49  	// SetControllerName is called prior to the wrapped command's Init method with
    50  	// the active controller name. The controller name is guaranteed to be non-empty
    51  	// at entry of Init. It records the current model name in the
    52  	// ControllerCommandBase.
    53  	SetControllerName(controllerName string) error
    54  
    55  	// ControllerName returns the name of the controller or model used to
    56  	// determine that API end point.
    57  	ControllerName() string
    58  
    59  	// SetAPIOpener allows the replacement of the default API opener,
    60  	// which ends up calling NewAPIRoot
    61  	SetAPIOpener(opener APIOpener)
    62  }
    63  
    64  // ControllerCommandBase is a convenience type for embedding in commands
    65  // that wish to implement ControllerCommand.
    66  type ControllerCommandBase struct {
    67  	JujuCommandBase
    68  
    69  	store          jujuclient.ClientStore
    70  	controllerName string
    71  	accountName    string
    72  
    73  	// opener is the strategy used to open the API connection.
    74  	opener APIOpener
    75  }
    76  
    77  // SetClientStore implements the ControllerCommand interface.
    78  func (c *ControllerCommandBase) SetClientStore(store jujuclient.ClientStore) {
    79  	c.store = store
    80  }
    81  
    82  // ClientStore implements the ControllerCommand interface.
    83  func (c *ControllerCommandBase) ClientStore() jujuclient.ClientStore {
    84  	return c.store
    85  }
    86  
    87  // SetControllerName implements the ControllerCommand interface.
    88  func (c *ControllerCommandBase) SetControllerName(controllerName string) error {
    89  	actualControllerName, err := ResolveControllerName(c.ClientStore(), controllerName)
    90  	if err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  	controllerName = actualControllerName
    94  
    95  	accountName, err := c.store.CurrentAccount(controllerName)
    96  	if err != nil && !errors.IsNotFound(err) {
    97  		return errors.Trace(err)
    98  	}
    99  	c.controllerName = controllerName
   100  	c.accountName = accountName
   101  	return nil
   102  }
   103  
   104  // ControllerName implements the ControllerCommand interface.
   105  func (c *ControllerCommandBase) ControllerName() string {
   106  	return c.controllerName
   107  }
   108  
   109  // AccountName implements the ControllerCommand interface.
   110  func (c *ControllerCommandBase) AccountName() string {
   111  	return c.accountName
   112  }
   113  
   114  // SetAPIOpener specifies the strategy used by the command to open
   115  // the API connection.
   116  func (c *ControllerCommandBase) SetAPIOpener(opener APIOpener) {
   117  	c.opener = opener
   118  }
   119  
   120  // NewModelManagerAPIClient returns an API client for the
   121  // ModelManager on the current controller using the current credentials.
   122  func (c *ControllerCommandBase) NewModelManagerAPIClient() (*modelmanager.Client, error) {
   123  	root, err := c.NewAPIRoot()
   124  	if err != nil {
   125  		return nil, errors.Trace(err)
   126  	}
   127  	return modelmanager.NewClient(root), nil
   128  }
   129  
   130  // NewControllerAPIClient returns an API client for the Controller on
   131  // the current controller using the current credentials.
   132  func (c *ControllerCommandBase) NewControllerAPIClient() (*controller.Client, error) {
   133  	root, err := c.NewAPIRoot()
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	return controller.NewClient(root), nil
   138  }
   139  
   140  // NewUserManagerAPIClient returns an API client for the UserManager on the
   141  // current controller using the current credentials.
   142  func (c *ControllerCommandBase) NewUserManagerAPIClient() (*usermanager.Client, error) {
   143  	root, err := c.NewAPIRoot()
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	return usermanager.NewClient(root), nil
   148  }
   149  
   150  // NewAPIRoot returns a restricted API for the current controller using the current
   151  // credentials.  Only the UserManager and ModelManager may be accessed
   152  // through this API connection.
   153  func (c *ControllerCommandBase) NewAPIRoot() (api.Connection, error) {
   154  	if c.controllerName == "" {
   155  		return nil, errors.Trace(ErrNoControllerSpecified)
   156  	}
   157  	if c.accountName == "" {
   158  		return nil, errors.Trace(ErrNoAccountSpecified)
   159  	}
   160  	opener := c.opener
   161  	if opener == nil {
   162  		opener = OpenFunc(c.JujuCommandBase.NewAPIRoot)
   163  	}
   164  	return opener.Open(c.store, c.controllerName, c.accountName, "")
   165  }
   166  
   167  // ModelUUIDs returns the model UUIDs for the given model names.
   168  func (c *ControllerCommandBase) ModelUUIDs(modelNames []string) ([]string, error) {
   169  	var result []string
   170  	store := c.ClientStore()
   171  	controllerName := c.ControllerName()
   172  	accountName := c.AccountName()
   173  	for _, modelName := range modelNames {
   174  		model, err := store.ModelByName(controllerName, accountName, modelName)
   175  		if errors.IsNotFound(err) {
   176  			// The model isn't known locally, so query the models available in the controller.
   177  			logger.Infof("model %q not cached locally, refreshing models from controller", modelName)
   178  			if err := c.RefreshModels(store, controllerName, accountName); err != nil {
   179  				return nil, errors.Annotatef(err, "refreshing model %q", modelName)
   180  			}
   181  			model, err = store.ModelByName(controllerName, accountName, modelName)
   182  		}
   183  		if err != nil {
   184  			return nil, errors.Annotatef(err, "model %q not found", modelName)
   185  		}
   186  		result = append(result, model.ModelUUID)
   187  	}
   188  	return result, nil
   189  }
   190  
   191  // WrapControllerOption sets various parameters of the
   192  // ControllerCommand wrapper.
   193  type WrapControllerOption func(*sysCommandWrapper)
   194  
   195  // ControllerSkipFlags instructs the wrapper to skip -c
   196  // and --controller flag definition.
   197  func ControllerSkipFlags(w *sysCommandWrapper) {
   198  	w.setFlags = false
   199  }
   200  
   201  // ControllerSkipDefault instructs the wrapper not to
   202  // use the default controller name.
   203  func ControllerSkipDefault(w *sysCommandWrapper) {
   204  	w.useDefaultControllerName = false
   205  }
   206  
   207  // ControllerAPIOpener instructs the underlying controller command to use a
   208  // different APIOpener strategy.
   209  func ControllerAPIOpener(opener APIOpener) WrapControllerOption {
   210  	return func(w *sysCommandWrapper) {
   211  		w.ControllerCommand.SetAPIOpener(opener)
   212  	}
   213  }
   214  
   215  // WrapController wraps the specified ControllerCommand, returning a Command
   216  // that proxies to each of the ControllerCommand methods.
   217  func WrapController(c ControllerCommand, options ...WrapControllerOption) cmd.Command {
   218  	wrapper := &sysCommandWrapper{
   219  		ControllerCommand:        c,
   220  		setFlags:                 true,
   221  		useDefaultControllerName: true,
   222  	}
   223  	for _, option := range options {
   224  		option(wrapper)
   225  	}
   226  	return WrapBase(wrapper)
   227  }
   228  
   229  type sysCommandWrapper struct {
   230  	ControllerCommand
   231  	setFlags                 bool
   232  	useDefaultControllerName bool
   233  	controllerName           string
   234  }
   235  
   236  // SetFlags implements Command.SetFlags, then calls the wrapped command's SetFlags.
   237  func (w *sysCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   238  	if w.setFlags {
   239  		f.StringVar(&w.controllerName, "c", "", "Controller to operate in")
   240  		f.StringVar(&w.controllerName, "controller", "", "")
   241  	}
   242  	w.ControllerCommand.SetFlags(f)
   243  }
   244  
   245  func (w *sysCommandWrapper) getDefaultControllerName() (string, error) {
   246  	if currentController, err := ReadCurrentController(); err != nil {
   247  		return "", errors.Trace(err)
   248  	} else if currentController != "" {
   249  		return currentController, nil
   250  	}
   251  	return "", errors.Trace(ErrNoControllerSpecified)
   252  }
   253  
   254  // Init implements Command.Init, then calls the wrapped command's Init.
   255  func (w *sysCommandWrapper) Init(args []string) error {
   256  	store := w.ClientStore()
   257  	if store == nil {
   258  		store = jujuclient.NewFileClientStore()
   259  		w.SetClientStore(store)
   260  	}
   261  	if w.setFlags {
   262  		if w.controllerName == "" && w.useDefaultControllerName {
   263  			name, err := w.getDefaultControllerName()
   264  			if err != nil {
   265  				return errors.Trace(err)
   266  			}
   267  			w.controllerName = name
   268  		}
   269  		if w.controllerName == "" && !w.useDefaultControllerName {
   270  			return ErrNoControllerSpecified
   271  		}
   272  	}
   273  	if w.controllerName != "" {
   274  		if err := w.SetControllerName(w.controllerName); err != nil {
   275  			return errors.Trace(err)
   276  		}
   277  	}
   278  	return w.ControllerCommand.Init(args)
   279  }
   280  
   281  // ResolveControllerName returns the canonical name of a controller given
   282  // an unambiguous identifier for that controller.
   283  // Locally created controllers (i.e. those whose names begin with "local.")
   284  // may be identified with or without the "local." prefix if there exists no
   285  // other controller in the store with the same unprefixed name.
   286  func ResolveControllerName(store jujuclient.ControllerStore, controllerName string) (string, error) {
   287  	_, err := store.ControllerByName(controllerName)
   288  	if err == nil {
   289  		return controllerName, nil
   290  	}
   291  	if !errors.IsNotFound(err) {
   292  		return "", err
   293  	}
   294  	var secondErr error
   295  	localName := "local." + controllerName
   296  	_, secondErr = store.ControllerByName(localName)
   297  	// If fallback name not found, return the original error.
   298  	if errors.IsNotFound(secondErr) {
   299  		return "", err
   300  	}
   301  	return localName, secondErr
   302  }