github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/controller/controller.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The controller package defines an API end point for functions dealing 5 // with controllers as a whole. 6 package controller 7 8 import ( 9 "encoding/json" 10 "sort" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/txn" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/macaroon.v2-unstable" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/api/migrationtarget" 20 "github.com/juju/juju/apiserver/common" 21 "github.com/juju/juju/apiserver/common/cloudspec" 22 "github.com/juju/juju/apiserver/facade" 23 "github.com/juju/juju/apiserver/params" 24 corecontroller "github.com/juju/juju/controller" 25 coremigration "github.com/juju/juju/core/migration" 26 "github.com/juju/juju/migration" 27 "github.com/juju/juju/permission" 28 "github.com/juju/juju/pubsub/controller" 29 "github.com/juju/juju/state" 30 ) 31 32 var logger = loggo.GetLogger("juju.apiserver.controller") 33 34 // ControllerAPI provides the Controller API. 35 type ControllerAPI struct { 36 *common.ControllerConfigAPI 37 *common.ModelStatusAPI 38 cloudspec.CloudSpecAPI 39 40 state *state.State 41 statePool *state.StatePool 42 authorizer facade.Authorizer 43 apiUser names.UserTag 44 resources facade.Resources 45 presence facade.Presence 46 hub facade.Hub 47 } 48 49 // ControllerAPIv6 provides the v6 Controller API. The only difference 50 // between this and v7 is that v6 doesn't have the IdentityProviderURL method. 51 type ControllerAPIv6 struct { 52 *ControllerAPI 53 } 54 55 // ControllerAPIv5 provides the v5 Controller API. The only difference 56 // between this and v6 is that v5 doesn't have the MongoVersion method. 57 type ControllerAPIv5 struct { 58 *ControllerAPIv6 59 } 60 61 // ControllerAPIv4 provides the v4 Controller API. The only difference 62 // between this and v5 is that v4 doesn't have the 63 // UpdateControllerConfig method. 64 type ControllerAPIv4 struct { 65 *ControllerAPIv5 66 } 67 68 // ControllerAPIv3 provides the v3 Controller API. 69 type ControllerAPIv3 struct { 70 *ControllerAPIv4 71 } 72 73 // NewControllerAPIv7 creates a new ControllerAPIv7. 74 func NewControllerAPIv7(ctx facade.Context) (*ControllerAPI, error) { 75 st := ctx.State() 76 authorizer := ctx.Auth() 77 pool := ctx.StatePool() 78 resources := ctx.Resources() 79 presence := ctx.Presence() 80 hub := ctx.Hub() 81 82 return NewControllerAPI( 83 st, 84 pool, 85 authorizer, 86 resources, 87 presence, 88 hub, 89 ) 90 } 91 92 // NewControllerAPIv6 creates a new ControllerAPIv6. 93 func NewControllerAPIv6(ctx facade.Context) (*ControllerAPIv6, error) { 94 v7, err := NewControllerAPIv7(ctx) 95 if err != nil { 96 return nil, errors.Trace(err) 97 } 98 return &ControllerAPIv6{v7}, nil 99 } 100 101 // NewControllerAPIv5 creates a new ControllerAPIv5. 102 func NewControllerAPIv5(ctx facade.Context) (*ControllerAPIv5, error) { 103 v6, err := NewControllerAPIv6(ctx) 104 if err != nil { 105 return nil, errors.Trace(err) 106 } 107 return &ControllerAPIv5{v6}, nil 108 } 109 110 // NewControllerAPIv4 creates a new ControllerAPIv4. 111 func NewControllerAPIv4(ctx facade.Context) (*ControllerAPIv4, error) { 112 v5, err := NewControllerAPIv5(ctx) 113 if err != nil { 114 return nil, errors.Trace(err) 115 } 116 return &ControllerAPIv4{v5}, nil 117 } 118 119 // NewControllerAPIv3 creates a new ControllerAPIv3. 120 func NewControllerAPIv3(ctx facade.Context) (*ControllerAPIv3, error) { 121 v4, err := NewControllerAPIv4(ctx) 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 return &ControllerAPIv3{v4}, nil 126 } 127 128 // NewControllerAPI creates a new api server endpoint for operations 129 // on a controller. 130 func NewControllerAPI( 131 st *state.State, 132 pool *state.StatePool, 133 authorizer facade.Authorizer, 134 resources facade.Resources, 135 presence facade.Presence, 136 hub facade.Hub, 137 ) (*ControllerAPI, error) { 138 if !authorizer.AuthClient() { 139 return nil, errors.Trace(common.ErrPerm) 140 } 141 142 // Since we know this is a user tag (because AuthClient is true), 143 // we just do the type assertion to the UserTag. 144 apiUser, _ := authorizer.GetAuthTag().(names.UserTag) 145 146 model, err := st.Model() 147 if err != nil { 148 return nil, errors.Trace(err) 149 } 150 return &ControllerAPI{ 151 ControllerConfigAPI: common.NewStateControllerConfig(st), 152 ModelStatusAPI: common.NewModelStatusAPI( 153 common.NewModelManagerBackend(model, pool), 154 authorizer, 155 apiUser, 156 ), 157 CloudSpecAPI: cloudspec.NewCloudSpec( 158 cloudspec.MakeCloudSpecGetter(pool), 159 common.AuthFuncForTag(model.ModelTag()), 160 ), 161 state: st, 162 statePool: pool, 163 authorizer: authorizer, 164 apiUser: apiUser, 165 resources: resources, 166 presence: presence, 167 hub: hub, 168 }, nil 169 } 170 171 func (c *ControllerAPI) checkHasAdmin() error { 172 isAdmin, err := c.authorizer.HasPermission(permission.SuperuserAccess, c.state.ControllerTag()) 173 if err != nil { 174 return errors.Trace(err) 175 } 176 if !isAdmin { 177 return common.ServerError(common.ErrPerm) 178 } 179 return nil 180 } 181 182 // IdentityProviderURL isn't on the v6 API. 183 func (c *ControllerAPIv6) IdentityProviderURL() {} 184 185 // IdentityProviderURL returns the URL of the configured external identity 186 // provider for this controller or an empty string if no external identity 187 // provider has been configured when the controller was bootstrapped. 188 // 189 // NOTE: the implementation intentionally does not check for SuperuserAccess 190 // as the URL is known even to users with login access. 191 func (c *ControllerAPI) IdentityProviderURL() (params.StringResult, error) { 192 var result params.StringResult 193 194 cfgRes, err := c.ControllerConfig() 195 if err != nil { 196 return result, errors.Trace(err) 197 } 198 199 if cfgRes.Config != nil { 200 result.Result = corecontroller.Config(cfgRes.Config).IdentityURL() 201 } 202 return result, nil 203 } 204 205 // ModelStatus is a legacy method call to ensure that we preserve 206 // backward compatibility. 207 // TODO (anastasiamac 2017-10-26) This should be made obsolete/removed. 208 func (c *ControllerAPIv3) ModelStatus(req params.Entities) (params.ModelStatusResults, error) { 209 results, err := c.ModelStatusAPI.ModelStatus(req) 210 if err != nil { 211 return params.ModelStatusResults{}, err 212 } 213 214 for _, r := range results.Results { 215 if r.Error != nil { 216 return params.ModelStatusResults{Results: make([]params.ModelStatus, len(req.Entities))}, errors.Trace(r.Error) 217 } 218 } 219 return results, nil 220 } 221 222 // MongoVersion isn't on the v5 API. 223 func (c *ControllerAPIv5) MongoVersion() {} 224 225 // MongoVersion allows the introspection of the mongo version per controller 226 func (c *ControllerAPI) MongoVersion() (params.StringResult, error) { 227 result := params.StringResult{} 228 if err := c.checkHasAdmin(); err != nil { 229 return result, errors.Trace(err) 230 } 231 version, err := c.state.MongoVersion() 232 if err != nil { 233 return result, errors.Trace(err) 234 } 235 result.Result = version 236 return result, nil 237 } 238 239 // AllModels allows controller administrators to get the list of all the 240 // models in the controller. 241 func (c *ControllerAPI) AllModels() (params.UserModelList, error) { 242 result := params.UserModelList{} 243 if err := c.checkHasAdmin(); err != nil { 244 return result, errors.Trace(err) 245 } 246 247 modelUUIDs, err := c.state.AllModelUUIDs() 248 if err != nil { 249 return result, errors.Trace(err) 250 } 251 for _, modelUUID := range modelUUIDs { 252 st, err := c.statePool.Get(modelUUID) 253 if err != nil { 254 // This model could have been removed. 255 if errors.IsNotFound(err) { 256 continue 257 } 258 return result, errors.Trace(err) 259 } 260 defer st.Release() 261 262 model, err := st.Model() 263 if err != nil { 264 return result, errors.Trace(err) 265 } 266 267 userModel := params.UserModel{ 268 Model: params.Model{ 269 Name: model.Name(), 270 UUID: model.UUID(), 271 Type: string(model.Type()), 272 OwnerTag: model.Owner().String(), 273 }, 274 } 275 276 lastConn, err := model.LastModelConnection(c.apiUser) 277 if err != nil { 278 if !state.IsNeverConnectedError(err) { 279 return result, errors.Trace(err) 280 } 281 } else { 282 userModel.LastConnection = &lastConn 283 } 284 285 result.UserModels = append(result.UserModels, userModel) 286 } 287 288 return result, nil 289 } 290 291 // ListBlockedModels returns a list of all models on the controller 292 // which have a block in place. The resulting slice is sorted by model 293 // name, then owner. Callers must be controller administrators to retrieve the 294 // list. 295 func (c *ControllerAPI) ListBlockedModels() (params.ModelBlockInfoList, error) { 296 results := params.ModelBlockInfoList{} 297 if err := c.checkHasAdmin(); err != nil { 298 return results, errors.Trace(err) 299 } 300 blocks, err := c.state.AllBlocksForController() 301 if err != nil { 302 return results, errors.Trace(err) 303 } 304 305 modelBlocks := make(map[string][]string) 306 for _, block := range blocks { 307 uuid := block.ModelUUID() 308 types, ok := modelBlocks[uuid] 309 if !ok { 310 types = []string{block.Type().String()} 311 } else { 312 types = append(types, block.Type().String()) 313 } 314 modelBlocks[uuid] = types 315 } 316 317 for uuid, blocks := range modelBlocks { 318 model, ph, err := c.statePool.GetModel(uuid) 319 if err != nil { 320 logger.Debugf("unable to retrieve model %s: %v", uuid, err) 321 continue 322 } 323 results.Models = append(results.Models, params.ModelBlockInfo{ 324 UUID: model.UUID(), 325 Name: model.Name(), 326 OwnerTag: model.Owner().String(), 327 Blocks: blocks, 328 }) 329 ph.Release() 330 } 331 332 // Sort the resulting sequence by model name, then owner. 333 sort.Sort(orderedBlockInfo(results.Models)) 334 return results, nil 335 } 336 337 // ModelConfig returns the model config for the controller 338 // model. For information on the current model, use 339 // client.ModelGet 340 func (c *ControllerAPI) ModelConfig() (params.ModelConfigResults, error) { 341 result := params.ModelConfigResults{} 342 if err := c.checkHasAdmin(); err != nil { 343 return result, errors.Trace(err) 344 } 345 346 controllerState := c.statePool.SystemState() 347 controllerModel, err := controllerState.Model() 348 if err != nil { 349 return result, errors.Trace(err) 350 } 351 cfg, err := controllerModel.Config() 352 if err != nil { 353 return result, errors.Trace(err) 354 } 355 356 result.Config = make(map[string]params.ConfigValue) 357 for name, val := range cfg.AllAttrs() { 358 result.Config[name] = params.ConfigValue{ 359 Value: val, 360 } 361 } 362 return result, nil 363 } 364 365 // HostedModelConfigs returns all the information that the client needs in 366 // order to connect directly with the host model's provider and destroy it 367 // directly. 368 func (c *ControllerAPI) HostedModelConfigs() (params.HostedModelConfigsResults, error) { 369 result := params.HostedModelConfigsResults{} 370 if err := c.checkHasAdmin(); err != nil { 371 return result, errors.Trace(err) 372 } 373 374 modelUUIDs, err := c.state.AllModelUUIDs() 375 if err != nil { 376 return result, errors.Trace(err) 377 } 378 379 for _, modelUUID := range modelUUIDs { 380 if modelUUID == c.state.ControllerModelUUID() { 381 continue 382 } 383 st, err := c.statePool.Get(modelUUID) 384 if err != nil { 385 // This model could have been removed. 386 if errors.IsNotFound(err) { 387 continue 388 } 389 return result, errors.Trace(err) 390 } 391 defer st.Release() 392 model, err := st.Model() 393 if err != nil { 394 return result, errors.Trace(err) 395 } 396 397 config := params.HostedModelConfig{ 398 Name: model.Name(), 399 OwnerTag: model.Owner().String(), 400 } 401 modelConf, err := model.Config() 402 if err != nil { 403 config.Error = common.ServerError(err) 404 } else { 405 config.Config = modelConf.AllAttrs() 406 } 407 cloudSpec := c.GetCloudSpec(model.ModelTag()) 408 if config.Error == nil { 409 config.CloudSpec = cloudSpec.Result 410 config.Error = cloudSpec.Error 411 } 412 result.Models = append(result.Models, config) 413 } 414 415 return result, nil 416 } 417 418 // RemoveBlocks removes all the blocks in the controller. 419 func (c *ControllerAPI) RemoveBlocks(args params.RemoveBlocksArgs) error { 420 if err := c.checkHasAdmin(); err != nil { 421 return errors.Trace(err) 422 } 423 424 if !args.All { 425 return errors.New("not supported") 426 } 427 return errors.Trace(c.state.RemoveAllBlocksForController()) 428 } 429 430 // WatchAllModels starts watching events for all models in the 431 // controller. The returned AllWatcherId should be used with Next on the 432 // AllModelWatcher endpoint to receive deltas. 433 func (c *ControllerAPI) WatchAllModels() (params.AllWatcherId, error) { 434 if err := c.checkHasAdmin(); err != nil { 435 return params.AllWatcherId{}, errors.Trace(err) 436 } 437 w := c.state.WatchAllModels(c.statePool) 438 return params.AllWatcherId{ 439 AllWatcherId: c.resources.Register(w), 440 }, nil 441 } 442 443 // GetControllerAccess returns the level of access the specified users 444 // have on the controller. 445 func (c *ControllerAPI) GetControllerAccess(req params.Entities) (params.UserAccessResults, error) { 446 results := params.UserAccessResults{} 447 isAdmin, err := c.authorizer.HasPermission(permission.SuperuserAccess, c.state.ControllerTag()) 448 if err != nil { 449 return results, errors.Trace(err) 450 } 451 452 users := req.Entities 453 results.Results = make([]params.UserAccessResult, len(users)) 454 for i, user := range users { 455 userTag, err := names.ParseUserTag(user.Tag) 456 if err != nil { 457 results.Results[i].Error = common.ServerError(err) 458 continue 459 } 460 if !isAdmin && !c.authorizer.AuthOwner(userTag) { 461 results.Results[i].Error = common.ServerError(common.ErrPerm) 462 continue 463 } 464 access, err := c.state.UserPermission(userTag, c.state.ControllerTag()) 465 if err != nil { 466 results.Results[i].Error = common.ServerError(err) 467 continue 468 } 469 results.Results[i].Result = ¶ms.UserAccess{ 470 Access: string(access), 471 UserTag: userTag.String()} 472 } 473 return results, nil 474 } 475 476 // InitiateMigration attempts to begin the migration of one or 477 // more models to other controllers. 478 func (c *ControllerAPI) InitiateMigration(reqArgs params.InitiateMigrationArgs) ( 479 params.InitiateMigrationResults, error, 480 ) { 481 out := params.InitiateMigrationResults{ 482 Results: make([]params.InitiateMigrationResult, len(reqArgs.Specs)), 483 } 484 if err := c.checkHasAdmin(); err != nil { 485 return out, errors.Trace(err) 486 } 487 488 for i, spec := range reqArgs.Specs { 489 result := &out.Results[i] 490 result.ModelTag = spec.ModelTag 491 id, err := c.initiateOneMigration(spec) 492 if err != nil { 493 result.Error = common.ServerError(err) 494 } else { 495 result.MigrationId = id 496 } 497 } 498 return out, nil 499 } 500 501 func (c *ControllerAPI) initiateOneMigration(spec params.MigrationSpec) (string, error) { 502 modelTag, err := names.ParseModelTag(spec.ModelTag) 503 if err != nil { 504 return "", errors.Annotate(err, "model tag") 505 } 506 507 // Ensure the model exists. 508 if modelExists, err := c.state.ModelExists(modelTag.Id()); err != nil { 509 return "", errors.Annotate(err, "reading model") 510 } else if !modelExists { 511 return "", errors.NotFoundf("model") 512 } 513 514 hostedState, err := c.statePool.Get(modelTag.Id()) 515 if err != nil { 516 return "", errors.Trace(err) 517 } 518 defer hostedState.Release() 519 520 // Construct target info. 521 specTarget := spec.TargetInfo 522 controllerTag, err := names.ParseControllerTag(specTarget.ControllerTag) 523 if err != nil { 524 return "", errors.Annotate(err, "controller tag") 525 } 526 authTag, err := names.ParseUserTag(specTarget.AuthTag) 527 if err != nil { 528 return "", errors.Annotate(err, "auth tag") 529 } 530 var macs []macaroon.Slice 531 if specTarget.Macaroons != "" { 532 if err := json.Unmarshal([]byte(specTarget.Macaroons), &macs); err != nil { 533 return "", errors.Annotate(err, "invalid macaroons") 534 } 535 } 536 targetInfo := coremigration.TargetInfo{ 537 ControllerTag: controllerTag, 538 Addrs: specTarget.Addrs, 539 CACert: specTarget.CACert, 540 AuthTag: authTag, 541 Password: specTarget.Password, 542 Macaroons: macs, 543 } 544 545 // Check if the migration is likely to succeed. 546 if err := runMigrationPrechecks(hostedState.State, c.statePool.SystemState(), &targetInfo, c.presence); err != nil { 547 return "", errors.Trace(err) 548 } 549 550 // Trigger the migration. 551 mig, err := hostedState.CreateMigration(state.MigrationSpec{ 552 InitiatedBy: c.apiUser, 553 TargetInfo: targetInfo, 554 }) 555 if err != nil { 556 return "", errors.Trace(err) 557 } 558 return mig.Id(), nil 559 } 560 561 // ModifyControllerAccess changes the model access granted to users. 562 func (c *ControllerAPI) ModifyControllerAccess(args params.ModifyControllerAccessRequest) (params.ErrorResults, error) { 563 result := params.ErrorResults{ 564 Results: make([]params.ErrorResult, len(args.Changes)), 565 } 566 if len(args.Changes) == 0 { 567 return result, nil 568 } 569 570 hasPermission, err := c.authorizer.HasPermission(permission.SuperuserAccess, c.state.ControllerTag()) 571 if err != nil { 572 return result, errors.Trace(err) 573 } 574 575 for i, arg := range args.Changes { 576 if !hasPermission { 577 result.Results[i].Error = common.ServerError(common.ErrPerm) 578 continue 579 } 580 581 controllerAccess := permission.Access(arg.Access) 582 if err := permission.ValidateControllerAccess(controllerAccess); err != nil { 583 // TODO(wallyworld) - remove in Juju 3.0 584 // Backwards compatibility requires us to accept add-model. 585 if controllerAccess != permission.AddModelAccess { 586 result.Results[i].Error = common.ServerError(err) 587 continue 588 } 589 } 590 591 targetUserTag, err := names.ParseUserTag(arg.UserTag) 592 if err != nil { 593 result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify controller access")) 594 continue 595 } 596 597 result.Results[i].Error = common.ServerError( 598 ChangeControllerAccess(c.state, c.apiUser, targetUserTag, arg.Action, controllerAccess)) 599 } 600 return result, nil 601 } 602 603 // ConfigSet changes the value of specified controller configuration 604 // settings. Only some settings can be changed after bootstrap. 605 // Settings that aren't specified in the params are left unchanged. 606 func (c *ControllerAPI) ConfigSet(args params.ControllerConfigSet) error { 607 if err := c.checkHasAdmin(); err != nil { 608 return errors.Trace(err) 609 } 610 if err := c.state.UpdateControllerConfig(args.Config, nil); err != nil { 611 return errors.Trace(err) 612 } 613 // TODO(thumper): add a version to controller config to allow for 614 // simultaneous updates and races in publishing, potentially across 615 // HA servers. 616 cfg, err := c.state.ControllerConfig() 617 if err != nil { 618 return errors.Trace(err) 619 } 620 if _, err := c.hub.Publish( 621 controller.ConfigChanged, 622 controller.ConfigChangedMessage{cfg}); err != nil { 623 return errors.Trace(err) 624 } 625 return nil 626 } 627 628 // Mask the ConfigSet method from the v4 API. The API reflection code 629 // in rpc/rpcreflect/type.go:newMethod skips 2-argument methods, so 630 // this removes the method as far as the RPC machinery is concerned. 631 632 // ConfigSet isn't on the v4 API. 633 func (c *ControllerAPIv4) ConfigSet(_, _ struct{}) {} 634 635 // runMigrationPrechecks runs prechecks on the migration and updates 636 // information in targetInfo as needed based on information 637 // retrieved from the target controller. 638 var runMigrationPrechecks = func(st, ctlrSt *state.State, targetInfo *coremigration.TargetInfo, presence facade.Presence) error { 639 // Check model and source controller. 640 backend, err := migration.PrecheckShim(st, ctlrSt) 641 if err != nil { 642 return errors.Annotate(err, "creating backend") 643 } 644 modelPresence := presence.ModelPresence(st.ModelUUID()) 645 controllerPresence := presence.ModelPresence(ctlrSt.ModelUUID()) 646 if err := migration.SourcePrecheck(backend, modelPresence, controllerPresence); err != nil { 647 return errors.Annotate(err, "source prechecks failed") 648 } 649 650 // Check target controller. 651 conn, err := api.Open(targetToAPIInfo(targetInfo), migration.ControllerDialOpts()) 652 if err != nil { 653 return errors.Annotate(err, "connect to target controller") 654 } 655 defer conn.Close() 656 modelInfo, err := makeModelInfo(st, ctlrSt) 657 if err != nil { 658 return errors.Trace(err) 659 } 660 client := migrationtarget.NewClient(conn) 661 if targetInfo.CACert == "" { 662 targetInfo.CACert, err = client.CACert() 663 if err != nil { 664 if !params.IsCodeNotImplemented(err) { 665 return errors.Annotatef(err, "cannot retrieve CA certificate") 666 } 667 // If the call's not implemented, it indicates an earlier version 668 // of the controller, which we can't migrate to. 669 return errors.New("controller API version is too old") 670 } 671 } 672 err = client.Prechecks(modelInfo) 673 return errors.Annotate(err, "target prechecks failed") 674 } 675 676 func makeModelInfo(st, ctlrSt *state.State) (coremigration.ModelInfo, error) { 677 var empty coremigration.ModelInfo 678 679 model, err := st.Model() 680 if err != nil { 681 return empty, errors.Trace(err) 682 } 683 684 // Retrieve agent version for the model. 685 conf, err := model.ModelConfig() 686 if err != nil { 687 return empty, errors.Trace(err) 688 } 689 agentVersion, _ := conf.AgentVersion() 690 691 // Retrieve agent version for the controller. 692 controllerModel, err := ctlrSt.Model() 693 if err != nil { 694 return empty, errors.Trace(err) 695 } 696 controllerConfig, err := controllerModel.Config() 697 if err != nil { 698 return empty, errors.Trace(err) 699 } 700 controllerVersion, _ := controllerConfig.AgentVersion() 701 702 return coremigration.ModelInfo{ 703 UUID: model.UUID(), 704 Name: model.Name(), 705 Owner: model.Owner(), 706 AgentVersion: agentVersion, 707 ControllerAgentVersion: controllerVersion, 708 }, nil 709 } 710 711 func targetToAPIInfo(ti *coremigration.TargetInfo) *api.Info { 712 return &api.Info{ 713 Addrs: ti.Addrs, 714 CACert: ti.CACert, 715 Tag: ti.AuthTag, 716 Password: ti.Password, 717 Macaroons: ti.Macaroons, 718 } 719 } 720 721 // grantControllerCloudAccess exists for backwards compatibility since older clients 722 // still set add-model on the controller rather than the controller cloud. 723 func grantControllerCloudAccess(accessor *state.State, targetUserTag names.UserTag, access permission.Access) error { 724 controllerInfo, err := accessor.ControllerInfo() 725 if err != nil { 726 return errors.Trace(err) 727 } 728 cloud := controllerInfo.CloudName 729 err = accessor.CreateCloudAccess(cloud, targetUserTag, access) 730 if errors.IsAlreadyExists(err) { 731 cloudAccess, err := accessor.GetCloudAccess(cloud, targetUserTag) 732 if errors.IsNotFound(err) { 733 // Conflicts with prior check, must be inconsistent state. 734 err = txn.ErrExcessiveContention 735 } 736 if err != nil { 737 return errors.Annotate(err, "could not look up cloud access for user") 738 } 739 740 // Only set access if greater access is being granted. 741 if cloudAccess.EqualOrGreaterCloudAccessThan(access) { 742 return errors.Errorf("user already has %q access or greater", access) 743 } 744 if _, err = accessor.SetUserAccess(targetUserTag, names.NewCloudTag(cloud), access); err != nil { 745 return errors.Annotate(err, "could not set cloud access for user") 746 } 747 return nil 748 749 } 750 if err != nil { 751 return errors.Trace(err) 752 } 753 return nil 754 } 755 756 func grantControllerAccess(accessor *state.State, targetUserTag, apiUser names.UserTag, access permission.Access) error { 757 // TODO(wallyworld) - remove in Juju 3.0 758 // Older clients still use the controller facade to manage add-model access. 759 if access == permission.AddModelAccess { 760 return grantControllerCloudAccess(accessor, targetUserTag, access) 761 } 762 763 _, err := accessor.AddControllerUser(state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access}) 764 if errors.IsAlreadyExists(err) { 765 controllerTag := accessor.ControllerTag() 766 controllerUser, err := accessor.UserAccess(targetUserTag, controllerTag) 767 if errors.IsNotFound(err) { 768 // Conflicts with prior check, must be inconsistent state. 769 err = txn.ErrExcessiveContention 770 } 771 if err != nil { 772 return errors.Annotate(err, "could not look up controller access for user") 773 } 774 775 // Only set access if greater access is being granted. 776 if controllerUser.Access.EqualOrGreaterControllerAccessThan(access) { 777 return errors.Errorf("user already has %q access or greater", access) 778 } 779 if _, err = accessor.SetUserAccess(controllerUser.UserTag, controllerUser.Object, access); err != nil { 780 return errors.Annotate(err, "could not set controller access for user") 781 } 782 return nil 783 784 } 785 if err != nil { 786 return errors.Trace(err) 787 } 788 return nil 789 } 790 791 func revokeControllerAccess(accessor *state.State, targetUserTag, apiUser names.UserTag, access permission.Access) error { 792 // TODO(wallyworld) - remove in Juju 3.0 793 // Older clients still use the controller facade to manage add-model access. 794 if access == permission.AddModelAccess { 795 controllerInfo, err := accessor.ControllerInfo() 796 if err != nil { 797 return errors.Trace(err) 798 } 799 return accessor.RemoveCloudAccess(controllerInfo.CloudName, targetUserTag) 800 } 801 802 controllerTag := accessor.ControllerTag() 803 switch access { 804 case permission.LoginAccess: 805 // Revoking login access removes all access. 806 err := accessor.RemoveUserAccess(targetUserTag, controllerTag) 807 return errors.Annotate(err, "could not revoke controller access") 808 case permission.SuperuserAccess: 809 // Revoking superuser sets login. 810 controllerUser, err := accessor.UserAccess(targetUserTag, controllerTag) 811 if err != nil { 812 return errors.Annotate(err, "could not look up controller access for user") 813 } 814 _, err = accessor.SetUserAccess(controllerUser.UserTag, controllerUser.Object, permission.LoginAccess) 815 return errors.Annotate(err, "could not set controller access to login") 816 817 default: 818 return errors.Errorf("don't know how to revoke %q access", access) 819 } 820 } 821 822 // ChangeControllerAccess performs the requested access grant or revoke action for the 823 // specified user on the controller. 824 func ChangeControllerAccess(accessor *state.State, apiUser, targetUserTag names.UserTag, action params.ControllerAction, access permission.Access) error { 825 switch action { 826 case params.GrantControllerAccess: 827 err := grantControllerAccess(accessor, targetUserTag, apiUser, access) 828 if err != nil { 829 return errors.Annotate(err, "could not grant controller access") 830 } 831 return nil 832 case params.RevokeControllerAccess: 833 return revokeControllerAccess(accessor, targetUserTag, apiUser, access) 834 default: 835 return errors.Errorf("unknown action %q", action) 836 } 837 } 838 839 type orderedBlockInfo []params.ModelBlockInfo 840 841 func (o orderedBlockInfo) Len() int { 842 return len(o) 843 } 844 845 func (o orderedBlockInfo) Less(i, j int) bool { 846 if o[i].Name < o[j].Name { 847 return true 848 } 849 if o[i].Name > o[j].Name { 850 return false 851 } 852 853 if o[i].OwnerTag < o[j].OwnerTag { 854 return true 855 } 856 if o[i].OwnerTag > o[j].OwnerTag { 857 return false 858 } 859 860 // Unreachable based on the rules of there not being duplicate 861 // models of the same name for the same owner, but return false 862 // instead of panicing. 863 return false 864 } 865 866 func (o orderedBlockInfo) Swap(i, j int) { 867 o[i], o[j] = o[j], o[i] 868 }