github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/modelcmd/base.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  	"net/http"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    12  	"launchpad.net/gnuflag"
    13  
    14  	"github.com/juju/juju/api"
    15  	"github.com/juju/juju/api/base"
    16  	"github.com/juju/juju/api/modelmanager"
    17  	"github.com/juju/juju/cloud"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/juju"
    21  	"github.com/juju/juju/jujuclient"
    22  )
    23  
    24  var errNoNameSpecified = errors.New("no name specified")
    25  
    26  // CommandBase extends cmd.Command with a closeContext method.
    27  // It is implicitly implemented by any type that embeds JujuCommandBase.
    28  type CommandBase interface {
    29  	cmd.Command
    30  
    31  	// closeContext closes the command's API context.
    32  	closeContext()
    33  	setCmdContext(*cmd.Context)
    34  }
    35  
    36  // ModelAPI provides access to the model client facade methods.
    37  type ModelAPI interface {
    38  	ListModels(user string) ([]base.UserModel, error)
    39  	Close() error
    40  }
    41  
    42  // JujuCommandBase is a convenience type for embedding that need
    43  // an API connection.
    44  type JujuCommandBase struct {
    45  	cmd.CommandBase
    46  	cmdContext *cmd.Context
    47  	apiContext *APIContext
    48  	modelApi   ModelAPI
    49  }
    50  
    51  // closeContext closes the command's API context
    52  // if it has actually been created.
    53  func (c *JujuCommandBase) closeContext() {
    54  	if c.apiContext != nil {
    55  		if err := c.apiContext.Close(); err != nil {
    56  			logger.Errorf("%v", err)
    57  		}
    58  	}
    59  }
    60  
    61  // SetModelApi sets the api used to access model information.
    62  func (c *JujuCommandBase) SetModelApi(api ModelAPI) {
    63  	c.modelApi = api
    64  }
    65  
    66  func (c *JujuCommandBase) modelAPI(store jujuclient.ClientStore, controllerName, accountName string) (ModelAPI, error) {
    67  	if c.modelApi != nil {
    68  		return c.modelApi, nil
    69  	}
    70  	conn, err := c.NewAPIRoot(store, controllerName, accountName, "")
    71  	if err != nil {
    72  		return nil, errors.Trace(err)
    73  	}
    74  	c.modelApi = modelmanager.NewClient(conn)
    75  	return c.modelApi, nil
    76  }
    77  
    78  // NewAPIRoot returns a new connection to the API server for the given
    79  // model or controller.
    80  func (c *JujuCommandBase) NewAPIRoot(
    81  	store jujuclient.ClientStore,
    82  	controllerName, accountName, modelName string,
    83  ) (api.Connection, error) {
    84  	params, err := c.NewAPIConnectionParams(
    85  		store, controllerName, accountName, modelName,
    86  	)
    87  	if err != nil {
    88  		return nil, errors.Trace(err)
    89  	}
    90  	return juju.NewAPIConnection(params)
    91  }
    92  
    93  // NewAPIConnectionParams returns a juju.NewAPIConnectionParams with the
    94  // given arguments such that a call to juju.NewAPIConnection with the
    95  // result behaves the same as a call to JujuCommandBase.NewAPIRoot with
    96  // the same arguments.
    97  func (c *JujuCommandBase) NewAPIConnectionParams(
    98  	store jujuclient.ClientStore,
    99  	controllerName, accountName, modelName string,
   100  ) (juju.NewAPIConnectionParams, error) {
   101  	if err := c.initAPIContext(); err != nil {
   102  		return juju.NewAPIConnectionParams{}, errors.Trace(err)
   103  	}
   104  	return newAPIConnectionParams(store, controllerName, accountName, modelName, c.apiContext.BakeryClient)
   105  }
   106  
   107  // HTTPClient returns an http.Client that contains the loaded
   108  // persistent cookie jar. Note that this client is not good for
   109  // connecting to the Juju API itself because it does not
   110  // have the correct TLS setup - use api.Connection.HTTPClient
   111  // for that.
   112  func (c *JujuCommandBase) HTTPClient() (*http.Client, error) {
   113  	if err := c.initAPIContext(); err != nil {
   114  		return nil, errors.Trace(err)
   115  	}
   116  	return c.apiContext.BakeryClient.Client, nil
   117  }
   118  
   119  // BakeryClient returns a macaroon bakery client that
   120  // uses the same HTTP client returned by HTTPClient.
   121  func (c *JujuCommandBase) BakeryClient() (*httpbakery.Client, error) {
   122  	if err := c.initAPIContext(); err != nil {
   123  		return nil, errors.Trace(err)
   124  	}
   125  	return c.apiContext.BakeryClient, nil
   126  }
   127  
   128  // APIOpen establishes a connection to the API server using the
   129  // the given api.Info and api.DialOpts.
   130  func (c *JujuCommandBase) APIOpen(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   131  	if err := c.initAPIContext(); err != nil {
   132  		return nil, errors.Trace(err)
   133  	}
   134  	return c.apiOpen(info, opts)
   135  }
   136  
   137  // RefreshModels refreshes the local models cache for the current user
   138  // on the specified controller.
   139  func (c *JujuCommandBase) RefreshModels(store jujuclient.ClientStore, controllerName, accountName string) error {
   140  	accountDetails, err := store.AccountByName(controllerName, accountName)
   141  	if err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  
   145  	modelManager, err := c.modelAPI(store, controllerName, accountName)
   146  	if err != nil {
   147  		return errors.Trace(err)
   148  	}
   149  	defer modelManager.Close()
   150  
   151  	models, err := modelManager.ListModels(accountDetails.User)
   152  	if err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  	for _, model := range models {
   156  		modelDetails := jujuclient.ModelDetails{model.UUID}
   157  		if err := store.UpdateModel(controllerName, accountName, model.Name, modelDetails); err != nil {
   158  			return errors.Trace(err)
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // initAPIContext lazily initializes c.apiContext. Doing this lazily means that
   165  // we avoid unnecessarily loading and saving the cookies
   166  // when a command does not actually make an API connection.
   167  func (c *JujuCommandBase) initAPIContext() error {
   168  	if c.apiContext != nil {
   169  		return nil
   170  	}
   171  	apiContext, err := NewAPIContext(c.cmdContext)
   172  	if err != nil {
   173  		return errors.Trace(err)
   174  	}
   175  	c.apiContext = apiContext
   176  	return nil
   177  }
   178  
   179  func (c *JujuCommandBase) setCmdContext(ctx *cmd.Context) {
   180  	c.cmdContext = ctx
   181  }
   182  
   183  // apiOpen establishes a connection to the API server using the
   184  // the give api.Info and api.DialOpts.
   185  func (c *JujuCommandBase) apiOpen(info *api.Info, opts api.DialOpts) (api.Connection, error) {
   186  	return api.Open(info, opts)
   187  }
   188  
   189  // WrapBase wraps the specified CommandBase, returning a Command
   190  // that proxies to each of the CommandBase methods.
   191  func WrapBase(c CommandBase) cmd.Command {
   192  	return &baseCommandWrapper{
   193  		CommandBase: c,
   194  	}
   195  }
   196  
   197  type baseCommandWrapper struct {
   198  	CommandBase
   199  }
   200  
   201  // Run implements Command.Run.
   202  func (w *baseCommandWrapper) Run(ctx *cmd.Context) error {
   203  	defer w.closeContext()
   204  	w.setCmdContext(ctx)
   205  	return w.CommandBase.Run(ctx)
   206  }
   207  
   208  // SetFlags implements Command.SetFlags.
   209  func (w *baseCommandWrapper) SetFlags(f *gnuflag.FlagSet) {
   210  	w.CommandBase.SetFlags(f)
   211  }
   212  
   213  // Init implements Command.Init.
   214  func (w *baseCommandWrapper) Init(args []string) error {
   215  	return w.CommandBase.Init(args)
   216  }
   217  
   218  func newAPIConnectionParams(
   219  	store jujuclient.ClientStore,
   220  	controllerName,
   221  	accountName,
   222  	modelName string,
   223  	bakery *httpbakery.Client,
   224  ) (juju.NewAPIConnectionParams, error) {
   225  	if controllerName == "" {
   226  		return juju.NewAPIConnectionParams{}, errors.Trace(errNoNameSpecified)
   227  	}
   228  	var accountDetails *jujuclient.AccountDetails
   229  	if accountName != "" {
   230  		var err error
   231  		accountDetails, err = store.AccountByName(controllerName, accountName)
   232  		if err != nil {
   233  			return juju.NewAPIConnectionParams{}, errors.Trace(err)
   234  		}
   235  	}
   236  	var modelUUID string
   237  	if modelName != "" {
   238  		modelDetails, err := store.ModelByName(controllerName, accountName, modelName)
   239  		if err != nil {
   240  			return juju.NewAPIConnectionParams{}, errors.Trace(err)
   241  		}
   242  		modelUUID = modelDetails.ModelUUID
   243  	}
   244  	dialOpts := api.DefaultDialOpts()
   245  	dialOpts.BakeryClient = bakery
   246  	return juju.NewAPIConnectionParams{
   247  		Store:           store,
   248  		ControllerName:  controllerName,
   249  		BootstrapConfig: NewGetBootstrapConfigFunc(store),
   250  		AccountDetails:  accountDetails,
   251  		ModelUUID:       modelUUID,
   252  		DialOpts:        dialOpts,
   253  	}, nil
   254  }
   255  
   256  // NewGetBootstrapConfigFunc returns a function that, given a controller name,
   257  // returns the bootstrap config for that controller in the given client store.
   258  func NewGetBootstrapConfigFunc(store jujuclient.ClientStore) func(string) (*config.Config, error) {
   259  	return bootstrapConfigGetter{store}.getBootstrapConfig
   260  }
   261  
   262  type bootstrapConfigGetter struct {
   263  	jujuclient.ClientStore
   264  }
   265  
   266  func (g bootstrapConfigGetter) getBootstrapConfig(controllerName string) (*config.Config, error) {
   267  	controllerName, err := ResolveControllerName(g.ClientStore, controllerName)
   268  	if err != nil {
   269  		return nil, errors.Annotate(err, "resolving controller name")
   270  	}
   271  	bootstrapConfig, err := g.BootstrapConfigForController(controllerName)
   272  	if err != nil {
   273  		return nil, errors.Annotate(err, "getting bootstrap config")
   274  	}
   275  	cloudType, ok := bootstrapConfig.Config["type"].(string)
   276  	if !ok {
   277  		return nil, errors.NotFoundf("cloud type in bootstrap config")
   278  	}
   279  
   280  	var credential *cloud.Credential
   281  	if bootstrapConfig.Credential != "" {
   282  		credential, _, _, err = GetCredentials(
   283  			g.ClientStore,
   284  			bootstrapConfig.CloudRegion,
   285  			bootstrapConfig.Credential,
   286  			bootstrapConfig.Cloud,
   287  			cloudType,
   288  		)
   289  		if err != nil {
   290  			return nil, errors.Trace(err)
   291  		}
   292  	} else {
   293  		// The credential was auto-detected; run auto-detection again.
   294  		cloudCredential, err := DetectCredential(
   295  			bootstrapConfig.Cloud, cloudType,
   296  		)
   297  		if err != nil {
   298  			return nil, errors.Trace(err)
   299  		}
   300  		// DetectCredential ensures that there is only one credential
   301  		// to choose from. It's still in a map, though, hence for..range.
   302  		for _, one := range cloudCredential.AuthCredentials {
   303  			credential = &one
   304  		}
   305  	}
   306  
   307  	// Add attributes from the controller details.
   308  	controllerDetails, err := g.ControllerByName(controllerName)
   309  	if err != nil {
   310  		return nil, errors.Trace(err)
   311  	}
   312  	bootstrapConfig.Config[config.CACertKey] = controllerDetails.CACert
   313  	bootstrapConfig.Config[config.UUIDKey] = controllerDetails.ControllerUUID
   314  	bootstrapConfig.Config[config.ControllerUUIDKey] = controllerDetails.ControllerUUID
   315  
   316  	cfg, err := config.New(config.UseDefaults, bootstrapConfig.Config)
   317  	if err != nil {
   318  		return nil, errors.Trace(err)
   319  	}
   320  	provider, err := environs.Provider(cfg.Type())
   321  	if err != nil {
   322  		return nil, errors.Trace(err)
   323  	}
   324  	return provider.BootstrapConfig(environs.BootstrapConfigParams{
   325  		cfg, *credential,
   326  		bootstrapConfig.CloudRegion,
   327  		bootstrapConfig.CloudEndpoint,
   328  		bootstrapConfig.CloudStorageEndpoint,
   329  	})
   330  }