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 }