github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/modelmanager/modelmanager.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package modelmanager defines an API end point for functions dealing with 5 // models. Creating, listing and sharing models. This facade is available at 6 // the root of the controller API, and as such, there is no implicit Model 7 // assocated. 8 package modelmanager 9 10 import ( 11 "fmt" 12 "sort" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/txn" 18 "github.com/juju/utils" 19 "github.com/juju/version" 20 "gopkg.in/juju/names.v2" 21 "gopkg.in/yaml.v1" 22 23 "github.com/juju/juju/apiserver/common" 24 "github.com/juju/juju/apiserver/facade" 25 "github.com/juju/juju/apiserver/params" 26 jujucloud "github.com/juju/juju/cloud" 27 "github.com/juju/juju/controller/modelmanager" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/config" 30 "github.com/juju/juju/migration" 31 "github.com/juju/juju/permission" 32 "github.com/juju/juju/state" 33 "github.com/juju/juju/state/stateenvirons" 34 "github.com/juju/juju/tools" 35 ) 36 37 var logger = loggo.GetLogger("juju.apiserver.modelmanager") 38 39 func init() { 40 common.RegisterStandardFacade("ModelManager", 2, newFacade) 41 } 42 43 // ModelManager defines the methods on the modelmanager API endpoint. 44 type ModelManager interface { 45 CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) 46 DumpModels(args params.Entities) params.MapResults 47 DumpModelsDB(args params.Entities) params.MapResults 48 ListModels(user params.Entity) (params.UserModelList, error) 49 DestroyModels(args params.Entities) (params.ErrorResults, error) 50 } 51 52 // ModelManagerAPI implements the model manager interface and is 53 // the concrete implementation of the api end point. 54 type ModelManagerAPI struct { 55 state common.ModelManagerBackend 56 check *common.BlockChecker 57 authorizer facade.Authorizer 58 toolsFinder *common.ToolsFinder 59 apiUser names.UserTag 60 isAdmin bool 61 } 62 63 var _ ModelManager = (*ModelManagerAPI)(nil) 64 65 func newFacade(st *state.State, _ facade.Resources, auth facade.Authorizer) (*ModelManagerAPI, error) { 66 configGetter := stateenvirons.EnvironConfigGetter{st} 67 return NewModelManagerAPI(common.NewModelManagerBackend(st), configGetter, auth) 68 } 69 70 // NewModelManagerAPI creates a new api server endpoint for managing 71 // models. 72 func NewModelManagerAPI( 73 st common.ModelManagerBackend, 74 configGetter environs.EnvironConfigGetter, 75 authorizer facade.Authorizer, 76 ) (*ModelManagerAPI, error) { 77 if !authorizer.AuthClient() { 78 return nil, common.ErrPerm 79 } 80 // Since we know this is a user tag (because AuthClient is true), 81 // we just do the type assertion to the UserTag. 82 apiUser, _ := authorizer.GetAuthTag().(names.UserTag) 83 // Pretty much all of the user manager methods have special casing for admin 84 // users, so look once when we start and remember if the user is an admin. 85 isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, st.ControllerTag()) 86 if err != nil { 87 return nil, errors.Trace(err) 88 } 89 urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st) 90 return &ModelManagerAPI{ 91 state: st, 92 check: common.NewBlockChecker(st), 93 authorizer: authorizer, 94 toolsFinder: common.NewToolsFinder(configGetter, st, urlGetter), 95 apiUser: apiUser, 96 isAdmin: isAdmin, 97 }, nil 98 } 99 100 // authCheck checks if the user is acting on their own behalf, or if they 101 // are an administrator acting on behalf of another user. 102 func (m *ModelManagerAPI) authCheck(user names.UserTag) error { 103 if m.isAdmin { 104 logger.Tracef("%q is a controller admin", m.apiUser.Canonical()) 105 return nil 106 } 107 108 // We can't just compare the UserTags themselves as the provider part 109 // may be unset, and gets replaced with 'local'. We must compare against 110 // the Canonical value of the user tag. 111 if m.apiUser.Canonical() == user.Canonical() { 112 return nil 113 } 114 return common.ErrPerm 115 } 116 117 func (m *ModelManagerAPI) hasWriteAccess(modelTag names.ModelTag) (bool, error) { 118 canWrite, err := m.authorizer.HasPermission(permission.WriteAccess, modelTag) 119 if errors.IsNotFound(err) { 120 return false, nil 121 } 122 return canWrite, err 123 } 124 125 // ConfigSource describes a type that is able to provide config. 126 // Abstracted primarily for testing. 127 type ConfigSource interface { 128 Config() (*config.Config, error) 129 } 130 131 func (m *ModelManagerAPI) newModelConfig( 132 cloudSpec environs.CloudSpec, 133 args params.ModelCreateArgs, 134 source ConfigSource, 135 ) (*config.Config, error) { 136 // For now, we just smash to the two maps together as we store 137 // the account values and the model config together in the 138 // *config.Config instance. 139 joint := make(map[string]interface{}) 140 for key, value := range args.Config { 141 joint[key] = value 142 } 143 if _, ok := joint["uuid"]; ok { 144 return nil, errors.New("uuid is generated, you cannot specify one") 145 } 146 if args.Name == "" { 147 return nil, errors.NewNotValid(nil, "Name must be specified") 148 } 149 if _, ok := joint[config.NameKey]; ok { 150 return nil, errors.New("name must not be specified in config") 151 } 152 joint[config.NameKey] = args.Name 153 154 baseConfig, err := source.Config() 155 if err != nil { 156 return nil, errors.Trace(err) 157 } 158 159 regionSpec := &environs.RegionSpec{Cloud: cloudSpec.Name, Region: cloudSpec.Region} 160 if joint, err = m.state.ComposeNewModelConfig(joint, regionSpec); err != nil { 161 return nil, errors.Trace(err) 162 } 163 164 creator := modelmanager.ModelConfigCreator{ 165 Provider: environs.Provider, 166 FindTools: func(n version.Number) (tools.List, error) { 167 result, err := m.toolsFinder.FindTools(params.FindToolsParams{ 168 Number: n, 169 }) 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 return result.List, nil 174 }, 175 } 176 return creator.NewModelConfig(cloudSpec, baseConfig, joint) 177 } 178 179 // CreateModel creates a new model using the account and 180 // model config specified in the args. 181 func (m *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) { 182 result := params.ModelInfo{} 183 canAddModel, err := m.authorizer.HasPermission(permission.AddModelAccess, m.state.ControllerTag()) 184 if err != nil { 185 return result, errors.Trace(err) 186 } 187 if !canAddModel { 188 return result, common.ErrPerm 189 } 190 191 // Get the controller model first. We need it both for the state 192 // server owner and the ability to get the config. 193 controllerModel, err := m.state.ControllerModel() 194 if err != nil { 195 return result, errors.Trace(err) 196 } 197 198 ownerTag, err := names.ParseUserTag(args.OwnerTag) 199 if err != nil { 200 return result, errors.Trace(err) 201 } 202 203 var cloudTag names.CloudTag 204 cloudRegionName := args.CloudRegion 205 if args.CloudTag != "" { 206 var err error 207 cloudTag, err = names.ParseCloudTag(args.CloudTag) 208 if err != nil { 209 return result, errors.Trace(err) 210 } 211 } else { 212 cloudTag = names.NewCloudTag(controllerModel.Cloud()) 213 } 214 if cloudRegionName == "" && cloudTag.Id() == controllerModel.Cloud() { 215 cloudRegionName = controllerModel.CloudRegion() 216 } 217 218 cloud, err := m.state.Cloud(cloudTag.Id()) 219 if err != nil { 220 if errors.IsNotFound(err) && args.CloudTag != "" { 221 // A cloud was specified, and it was not found. 222 // Annotate the error with the supported clouds. 223 clouds, err := m.state.Clouds() 224 if err != nil { 225 return result, errors.Trace(err) 226 } 227 cloudNames := make([]string, 0, len(clouds)) 228 for tag := range clouds { 229 cloudNames = append(cloudNames, tag.Id()) 230 } 231 sort.Strings(cloudNames) 232 return result, errors.NewNotFound(err, fmt.Sprintf( 233 "cloud %q not found, expected one of %q", 234 cloudTag.Id(), cloudNames, 235 )) 236 } 237 return result, errors.Annotate(err, "getting cloud definition") 238 } 239 240 var cloudCredentialTag names.CloudCredentialTag 241 if args.CloudCredentialTag != "" { 242 var err error 243 cloudCredentialTag, err = names.ParseCloudCredentialTag(args.CloudCredentialTag) 244 if err != nil { 245 return result, errors.Trace(err) 246 } 247 } else { 248 if ownerTag.Canonical() == controllerModel.Owner().Canonical() { 249 cloudCredentialTag, _ = controllerModel.CloudCredential() 250 } else { 251 // TODO(axw) check if the user has one and only one 252 // cloud credential, and if so, use it? For now, we 253 // require the user to specify a credential unless 254 // the cloud does not require one. 255 var hasEmpty bool 256 for _, authType := range cloud.AuthTypes { 257 if authType != jujucloud.EmptyAuthType { 258 continue 259 } 260 hasEmpty = true 261 break 262 } 263 if !hasEmpty { 264 return result, errors.NewNotValid(nil, "no credential specified") 265 } 266 } 267 } 268 269 var credential *jujucloud.Credential 270 if cloudCredentialTag != (names.CloudCredentialTag{}) { 271 credentialValue, err := m.state.CloudCredential(cloudCredentialTag) 272 if err != nil { 273 return result, errors.Annotate(err, "getting credential") 274 } 275 credential = &credentialValue 276 } 277 278 cloudSpec, err := environs.MakeCloudSpec(cloud, cloudTag.Id(), cloudRegionName, credential) 279 if err != nil { 280 return result, errors.Trace(err) 281 } 282 283 controllerCfg, err := m.state.ControllerConfig() 284 if err != nil { 285 return result, errors.Trace(err) 286 } 287 288 newConfig, err := m.newModelConfig(cloudSpec, args, controllerModel) 289 if err != nil { 290 return result, errors.Annotate(err, "failed to create config") 291 } 292 293 // Create the Environ. 294 env, err := environs.New(environs.OpenParams{ 295 Cloud: cloudSpec, 296 Config: newConfig, 297 }) 298 if err != nil { 299 return result, errors.Annotate(err, "failed to open environ") 300 } 301 if err := env.Create(environs.CreateParams{ 302 ControllerUUID: controllerCfg.ControllerUUID(), 303 }); err != nil { 304 return result, errors.Annotate(err, "failed to create environ") 305 } 306 storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env) 307 308 // NOTE: check the agent-version of the config, and if it is > the current 309 // version, it is not supported, also check existing tools, and if we don't 310 // have tools for that version, also die. 311 model, st, err := m.state.NewModel(state.ModelArgs{ 312 CloudName: cloudTag.Id(), 313 CloudRegion: cloudRegionName, 314 CloudCredential: cloudCredentialTag, 315 Config: newConfig, 316 Owner: ownerTag, 317 StorageProviderRegistry: storageProviderRegistry, 318 }) 319 if err != nil { 320 return result, errors.Annotate(err, "failed to create new model") 321 } 322 defer st.Close() 323 324 return m.getModelInfo(model.ModelTag()) 325 } 326 327 func (m *ModelManagerAPI) dumpModel(args params.Entity) (map[string]interface{}, error) { 328 modelTag, err := names.ParseModelTag(args.Tag) 329 if err != nil { 330 return nil, errors.Trace(err) 331 } 332 333 isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 334 if err != nil { 335 return nil, errors.Trace(err) 336 } 337 if !isModelAdmin && !m.isAdmin { 338 return nil, common.ErrPerm 339 } 340 341 st := m.state 342 if st.ModelTag() != modelTag { 343 st, err = m.state.ForModel(modelTag) 344 if err != nil { 345 if errors.IsNotFound(err) { 346 return nil, errors.Trace(common.ErrBadId) 347 } 348 return nil, errors.Trace(err) 349 } 350 defer st.Close() 351 } 352 353 bytes, err := migration.ExportModel(st) 354 if err != nil { 355 return nil, errors.Trace(err) 356 } 357 // Now read it back into a map. 358 var asMap map[string]interface{} 359 err = yaml.Unmarshal(bytes, &asMap) 360 if err != nil { 361 return nil, errors.Trace(err) 362 } 363 // In order to serialize the map through JSON, we need to make sure 364 // that all the embedded maps are map[string]interface{}, not 365 // map[interface{}]interface{} which is what YAML gives by default. 366 out, err := utils.ConformYAML(asMap) 367 if err != nil { 368 return nil, errors.Trace(err) 369 } 370 return out.(map[string]interface{}), nil 371 } 372 373 func (m *ModelManagerAPI) dumpModelDB(args params.Entity) (map[string]interface{}, error) { 374 modelTag, err := names.ParseModelTag(args.Tag) 375 if err != nil { 376 return nil, errors.Trace(err) 377 } 378 379 isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 380 if err != nil { 381 return nil, errors.Trace(err) 382 } 383 if !isModelAdmin && !m.isAdmin { 384 return nil, common.ErrPerm 385 } 386 387 st := m.state 388 if st.ModelTag() != modelTag { 389 st, err = m.state.ForModel(modelTag) 390 if err != nil { 391 if errors.IsNotFound(err) { 392 return nil, errors.Trace(common.ErrBadId) 393 } 394 return nil, errors.Trace(err) 395 } 396 defer st.Close() 397 } 398 399 return st.DumpAll() 400 } 401 402 // DumpModels will export the models into the database agnostic 403 // representation. The user needs to either be a controller admin, or have 404 // admin privileges on the model itself. 405 func (m *ModelManagerAPI) DumpModels(args params.Entities) params.MapResults { 406 results := params.MapResults{ 407 Results: make([]params.MapResult, len(args.Entities)), 408 } 409 for i, entity := range args.Entities { 410 dumped, err := m.dumpModel(entity) 411 if err != nil { 412 results.Results[i].Error = common.ServerError(err) 413 continue 414 } 415 results.Results[i].Result = dumped 416 } 417 return results 418 } 419 420 // DumpModelsDB will gather all documents from all model collections 421 // for the specified model. The map result contains a map of collection 422 // names to lists of documents represented as maps. 423 func (m *ModelManagerAPI) DumpModelsDB(args params.Entities) params.MapResults { 424 results := params.MapResults{ 425 Results: make([]params.MapResult, len(args.Entities)), 426 } 427 for i, entity := range args.Entities { 428 dumped, err := m.dumpModelDB(entity) 429 if err != nil { 430 results.Results[i].Error = common.ServerError(err) 431 continue 432 } 433 results.Results[i].Result = dumped 434 } 435 return results 436 } 437 438 // ListModels returns the models that the specified user 439 // has access to in the current server. Only that controller owner 440 // can list models for any user (at this stage). Other users 441 // can only ask about their own models. 442 func (m *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) { 443 result := params.UserModelList{} 444 445 userTag, err := names.ParseUserTag(user.Tag) 446 if err != nil { 447 return result, errors.Trace(err) 448 } 449 450 err = m.authCheck(userTag) 451 if err != nil { 452 return result, errors.Trace(err) 453 } 454 455 models, err := m.state.ModelsForUser(userTag) 456 if err != nil { 457 return result, errors.Trace(err) 458 } 459 460 for _, model := range models { 461 var lastConn *time.Time 462 userLastConn, err := model.LastConnection() 463 if err != nil { 464 if !state.IsNeverConnectedError(err) { 465 return result, errors.Trace(err) 466 } 467 } else { 468 lastConn = &userLastConn 469 } 470 result.UserModels = append(result.UserModels, params.UserModel{ 471 Model: params.Model{ 472 Name: model.Name(), 473 UUID: model.UUID(), 474 OwnerTag: model.Owner().String(), 475 }, 476 LastConnection: lastConn, 477 }) 478 } 479 480 return result, nil 481 } 482 483 // DestroyModels will try to destroy the specified models. 484 // If there is a block on destruction, this method will return an error. 485 func (m *ModelManagerAPI) DestroyModels(args params.Entities) (params.ErrorResults, error) { 486 results := params.ErrorResults{ 487 Results: make([]params.ErrorResult, len(args.Entities)), 488 } 489 490 destroyModel := func(tag names.ModelTag) error { 491 model, err := m.state.GetModel(tag) 492 if err != nil { 493 return errors.Trace(err) 494 } 495 if err := m.authCheck(model.Owner()); err != nil { 496 return errors.Trace(err) 497 } 498 return errors.Trace(common.DestroyModel(m.state, model.ModelTag())) 499 } 500 501 for i, arg := range args.Entities { 502 tag, err := names.ParseModelTag(arg.Tag) 503 if err != nil { 504 results.Results[i].Error = common.ServerError(err) 505 continue 506 } 507 if err := destroyModel(tag); err != nil { 508 results.Results[i].Error = common.ServerError(err) 509 continue 510 } 511 } 512 return results, nil 513 } 514 515 // ModelInfo returns information about the specified models. 516 func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) { 517 results := params.ModelInfoResults{ 518 Results: make([]params.ModelInfoResult, len(args.Entities)), 519 } 520 521 getModelInfo := func(arg params.Entity) (params.ModelInfo, error) { 522 tag, err := names.ParseModelTag(arg.Tag) 523 if err != nil { 524 return params.ModelInfo{}, errors.Trace(err) 525 } 526 return m.getModelInfo(tag) 527 } 528 529 for i, arg := range args.Entities { 530 modelInfo, err := getModelInfo(arg) 531 if err != nil { 532 results.Results[i].Error = common.ServerError(err) 533 continue 534 } 535 results.Results[i].Result = &modelInfo 536 } 537 return results, nil 538 } 539 540 func (m *ModelManagerAPI) getModelInfo(tag names.ModelTag) (params.ModelInfo, error) { 541 st, err := m.state.ForModel(tag) 542 if errors.IsNotFound(err) { 543 return params.ModelInfo{}, common.ErrPerm 544 } else if err != nil { 545 return params.ModelInfo{}, errors.Trace(err) 546 } 547 defer st.Close() 548 549 model, err := st.Model() 550 if errors.IsNotFound(err) { 551 return params.ModelInfo{}, common.ErrPerm 552 } else if err != nil { 553 return params.ModelInfo{}, errors.Trace(err) 554 } 555 556 cfg, err := model.Config() 557 if err != nil { 558 return params.ModelInfo{}, errors.Trace(err) 559 } 560 controllerCfg, err := st.ControllerConfig() 561 if err != nil { 562 return params.ModelInfo{}, errors.Trace(err) 563 } 564 users, err := model.Users() 565 if err != nil { 566 return params.ModelInfo{}, errors.Trace(err) 567 } 568 status, err := model.Status() 569 if err != nil { 570 return params.ModelInfo{}, errors.Trace(err) 571 } 572 573 owner := model.Owner() 574 info := params.ModelInfo{ 575 Name: cfg.Name(), 576 UUID: cfg.UUID(), 577 ControllerUUID: controllerCfg.ControllerUUID(), 578 OwnerTag: owner.String(), 579 Life: params.Life(model.Life().String()), 580 Status: common.EntityStatusFromState(status), 581 ProviderType: cfg.Type(), 582 DefaultSeries: config.PreferredSeries(cfg), 583 CloudTag: names.NewCloudTag(model.Cloud()).String(), 584 CloudRegion: model.CloudRegion(), 585 } 586 587 if cloudCredentialTag, ok := model.CloudCredential(); ok { 588 info.CloudCredentialTag = cloudCredentialTag.String() 589 } 590 591 authorizedOwner := m.authCheck(owner) == nil 592 for _, user := range users { 593 if !authorizedOwner && m.authCheck(user.UserTag) != nil { 594 // The authenticated user is neither the owner 595 // nor administrator, nor the model user, so 596 // has no business knowing about the model user. 597 continue 598 } 599 600 userInfo, err := common.ModelUserInfo(user, st) 601 if err != nil { 602 return params.ModelInfo{}, errors.Trace(err) 603 } 604 info.Users = append(info.Users, userInfo) 605 } 606 607 if len(info.Users) == 0 { 608 // No users, which means the authenticated user doesn't 609 // have access to the model. 610 return params.ModelInfo{}, common.ErrPerm 611 } 612 613 canSeeMachines := authorizedOwner 614 if !canSeeMachines { 615 if canSeeMachines, err = m.hasWriteAccess(tag); err != nil { 616 return params.ModelInfo{}, errors.Trace(err) 617 } 618 } 619 if canSeeMachines { 620 if info.Machines, err = common.ModelMachineInfo(st); err != nil { 621 return params.ModelInfo{}, err 622 } 623 } 624 return info, nil 625 } 626 627 // ModifyModelAccess changes the model access granted to users. 628 func (m *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, _ error) { 629 result = params.ErrorResults{ 630 Results: make([]params.ErrorResult, len(args.Changes)), 631 } 632 633 canModifyController, err := m.authorizer.HasPermission(permission.SuperuserAccess, m.state.ControllerTag()) 634 if err != nil { 635 return result, errors.Trace(err) 636 } 637 if len(args.Changes) == 0 { 638 return result, nil 639 } 640 641 for i, arg := range args.Changes { 642 modelAccess := permission.Access(arg.Access) 643 if err := permission.ValidateModelAccess(modelAccess); err != nil { 644 err = errors.Annotate(err, "could not modify model access") 645 result.Results[i].Error = common.ServerError(err) 646 continue 647 } 648 649 modelTag, err := names.ParseModelTag(arg.ModelTag) 650 if err != nil { 651 result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) 652 continue 653 } 654 canModifyModel, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 655 if err != nil { 656 return result, errors.Trace(err) 657 } 658 canModify := canModifyController || canModifyModel 659 660 if !canModify { 661 result.Results[i].Error = common.ServerError(common.ErrPerm) 662 continue 663 } 664 665 targetUserTag, err := names.ParseUserTag(arg.UserTag) 666 if err != nil { 667 result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) 668 continue 669 } 670 671 result.Results[i].Error = common.ServerError( 672 changeModelAccess(m.state, modelTag, m.apiUser, targetUserTag, arg.Action, modelAccess, m.isAdmin)) 673 } 674 return result, nil 675 } 676 677 func userAuthorizedToChangeAccess(st common.ModelManagerBackend, userIsAdmin bool, userTag names.UserTag) error { 678 if userIsAdmin { 679 // Just confirm that the model that has been given is a valid model. 680 _, err := st.Model() 681 if err != nil { 682 return errors.Trace(err) 683 } 684 return nil 685 } 686 687 // Get the current user's ModelUser for the Model to see if the user has 688 // permission to grant or revoke permissions on the model. 689 currentUser, err := st.UserAccess(userTag, st.ModelTag()) 690 if err != nil { 691 if errors.IsNotFound(err) { 692 // No, this user doesn't have permission. 693 return common.ErrPerm 694 } 695 return errors.Annotate(err, "could not retrieve user") 696 } 697 if currentUser.Access != permission.AdminAccess { 698 return common.ErrPerm 699 } 700 return nil 701 } 702 703 // changeModelAccess performs the requested access grant or revoke action for the 704 // specified user on the specified model. 705 func changeModelAccess(accessor common.ModelManagerBackend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.Access, userIsAdmin bool) error { 706 st, err := accessor.ForModel(modelTag) 707 if err != nil { 708 return errors.Annotate(err, "could not lookup model") 709 } 710 defer st.Close() 711 712 if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil { 713 return errors.Trace(err) 714 } 715 716 switch action { 717 case params.GrantModelAccess: 718 _, err = st.AddModelUser(modelTag.Id(), state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access}) 719 if errors.IsAlreadyExists(err) { 720 modelUser, err := st.UserAccess(targetUserTag, modelTag) 721 if errors.IsNotFound(err) { 722 // Conflicts with prior check, must be inconsistent state. 723 err = txn.ErrExcessiveContention 724 } 725 if err != nil { 726 return errors.Annotate(err, "could not look up model access for user") 727 } 728 729 // Only set access if greater access is being granted. 730 if modelUser.Access.EqualOrGreaterModelAccessThan(access) { 731 return errors.Errorf("user already has %q access or greater", access) 732 } 733 if _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, access); err != nil { 734 return errors.Annotate(err, "could not set model access for user") 735 } 736 return nil 737 } 738 return errors.Annotate(err, "could not grant model access") 739 740 case params.RevokeModelAccess: 741 switch access { 742 case permission.ReadAccess: 743 // Revoking read access removes all access. 744 err := st.RemoveUserAccess(targetUserTag, modelTag) 745 return errors.Annotate(err, "could not revoke model access") 746 case permission.WriteAccess: 747 // Revoking write access sets read-only. 748 modelUser, err := st.UserAccess(targetUserTag, modelTag) 749 if err != nil { 750 return errors.Annotate(err, "could not look up model access for user") 751 } 752 _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.ReadAccess) 753 return errors.Annotate(err, "could not set model access to read-only") 754 case permission.AdminAccess: 755 // Revoking admin access sets read-write. 756 modelUser, err := st.UserAccess(targetUserTag, modelTag) 757 if err != nil { 758 return errors.Annotate(err, "could not look up model access for user") 759 } 760 _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.WriteAccess) 761 return errors.Annotate(err, "could not set model access to read-write") 762 763 default: 764 return errors.Errorf("don't know how to revoke %q access", access) 765 } 766 767 default: 768 return errors.Errorf("unknown action %q", action) 769 } 770 } 771 772 // ModelDefaults returns the default config values used when creating a new model. 773 func (m *ModelManagerAPI) ModelDefaults() (params.ModelDefaultsResult, error) { 774 result := params.ModelDefaultsResult{} 775 if !m.isAdmin { 776 return result, common.ErrPerm 777 } 778 779 values, err := m.state.ModelConfigDefaultValues() 780 if err != nil { 781 return result, errors.Trace(err) 782 } 783 result.Config = make(map[string]params.ModelDefaults) 784 for attr, val := range values { 785 settings := params.ModelDefaults{ 786 Controller: val.Controller, 787 Default: val.Default, 788 } 789 for _, v := range val.Regions { 790 settings.Regions = append( 791 settings.Regions, params.RegionDefaults{ 792 RegionName: v.Name, 793 Value: v.Value}) 794 } 795 result.Config[attr] = settings 796 } 797 return result, nil 798 } 799 800 // SetModelDefaults writes new values for the specified default model settings. 801 func (m *ModelManagerAPI) SetModelDefaults(args params.SetModelDefaults) (params.ErrorResults, error) { 802 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Config))} 803 if err := m.check.ChangeAllowed(); err != nil { 804 return results, errors.Trace(err) 805 } 806 for i, arg := range args.Config { 807 results.Results[i].Error = common.ServerError( 808 m.setModelDefaults(arg), 809 ) 810 } 811 return results, nil 812 } 813 814 func (m *ModelManagerAPI) setModelDefaults(args params.ModelDefaultValues) error { 815 if !m.isAdmin { 816 return common.ErrPerm 817 } 818 819 if err := m.check.ChangeAllowed(); err != nil { 820 return errors.Trace(err) 821 } 822 // Make sure we don't allow changing agent-version. 823 if _, found := args.Config["agent-version"]; found { 824 return errors.New("agent-version cannot have a default value") 825 } 826 827 var rspec *environs.RegionSpec 828 if args.CloudRegion != "" { 829 spec, err := m.makeRegionSpec(args.CloudTag, args.CloudRegion) 830 if err != nil { 831 return errors.Trace(err) 832 } 833 rspec = spec 834 } 835 return m.state.UpdateModelConfigDefaultValues(args.Config, nil, rspec) 836 } 837 838 // UnsetModelDefaults removes the specified default model settings. 839 func (m *ModelManagerAPI) UnsetModelDefaults(args params.UnsetModelDefaults) (params.ErrorResults, error) { 840 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Keys))} 841 if !m.isAdmin { 842 return results, common.ErrPerm 843 } 844 845 if err := m.check.ChangeAllowed(); err != nil { 846 return results, errors.Trace(err) 847 } 848 849 for i, arg := range args.Keys { 850 var rspec *environs.RegionSpec 851 if arg.CloudRegion != "" { 852 spec, err := m.makeRegionSpec(arg.CloudTag, arg.CloudRegion) 853 if err != nil { 854 results.Results[i].Error = common.ServerError( 855 errors.Trace(err)) 856 continue 857 } 858 rspec = spec 859 } 860 results.Results[i].Error = common.ServerError( 861 m.state.UpdateModelConfigDefaultValues(nil, arg.Keys, rspec), 862 ) 863 } 864 return results, nil 865 } 866 867 // makeRegionSpec is a helper method for methods that call 868 // state.UpdateModelConfigDefaultValues. 869 func (m *ModelManagerAPI) makeRegionSpec(cloudTag, r string) (*environs.RegionSpec, error) { 870 cTag, err := names.ParseCloudTag(cloudTag) 871 if err != nil { 872 return nil, errors.Trace(err) 873 } 874 rspec, err := environs.NewRegionSpec(cTag.Id(), r) 875 if err != nil { 876 return nil, errors.Trace(err) 877 } 878 return rspec, nil 879 }