github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/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/collections/set" 16 "github.com/juju/description" 17 "github.com/juju/errors" 18 "github.com/juju/loggo" 19 "github.com/juju/txn" 20 "github.com/juju/utils" 21 "github.com/juju/version" 22 "gopkg.in/juju/names.v2" 23 "gopkg.in/yaml.v2" 24 25 "github.com/juju/juju/apiserver/common" 26 "github.com/juju/juju/apiserver/facade" 27 "github.com/juju/juju/apiserver/params" 28 "github.com/juju/juju/caas" 29 jujucloud "github.com/juju/juju/cloud" 30 "github.com/juju/juju/controller/modelmanager" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/environs/context" 34 "github.com/juju/juju/permission" 35 "github.com/juju/juju/state" 36 "github.com/juju/juju/state/stateenvirons" 37 "github.com/juju/juju/tools" 38 jujuversion "github.com/juju/juju/version" 39 ) 40 41 var logger = loggo.GetLogger("juju.apiserver.modelmanager") 42 43 // ModelManagerV5 defines the methods on the version 5 facade for the 44 // modelmanager API endpoint. 45 type ModelManagerV5 interface { 46 CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) 47 DumpModels(args params.DumpModelRequest) params.StringResults 48 DumpModelsDB(args params.Entities) params.MapResults 49 ListModelSummaries(request params.ModelSummariesRequest) (params.ModelSummaryResults, error) 50 ListModels(user params.Entity) (params.UserModelList, error) 51 DestroyModels(args params.DestroyModelsParams) (params.ErrorResults, error) 52 ModelInfo(args params.Entities) (params.ModelInfoResults, error) 53 ModelStatus(req params.Entities) (params.ModelStatusResults, error) 54 ChangeModelCredential(args params.ChangeModelCredentialsParams) (params.ErrorResults, error) 55 } 56 57 // ModelManagerV4 defines the methods on the version 4 facade for the 58 // modelmanager API endpoint. 59 type ModelManagerV4 interface { 60 CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) 61 DumpModels(args params.DumpModelRequest) params.StringResults 62 DumpModelsDB(args params.Entities) params.MapResults 63 ListModelSummaries(request params.ModelSummariesRequest) (params.ModelSummaryResults, error) 64 ListModels(user params.Entity) (params.UserModelList, error) 65 DestroyModels(args params.DestroyModelsParams) (params.ErrorResults, error) 66 ModelInfo(args params.Entities) (params.ModelInfoResults, error) 67 ModelStatus(req params.Entities) (params.ModelStatusResults, error) 68 } 69 70 // ModelManagerV3 defines the methods on the version 3 facade for the 71 // modelmanager API endpoint. 72 type ModelManagerV3 interface { 73 CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) 74 DumpModels(args params.DumpModelRequest) params.StringResults 75 DumpModelsDB(args params.Entities) params.MapResults 76 ListModels(user params.Entity) (params.UserModelList, error) 77 DestroyModels(args params.Entities) (params.ErrorResults, error) 78 ModelInfo(args params.Entities) (params.ModelInfoResults, error) 79 ModelStatus(req params.Entities) (params.ModelStatusResults, error) 80 } 81 82 // ModelManagerV2 defines the methods on the version 2 facade for the 83 // modelmanager API endpoint. 84 type ModelManagerV2 interface { 85 CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) 86 DumpModels(args params.Entities) params.MapResults 87 DumpModelsDB(args params.Entities) params.MapResults 88 ListModels(user params.Entity) (params.UserModelList, error) 89 DestroyModels(args params.Entities) (params.ErrorResults, error) 90 ModelStatus(req params.Entities) (params.ModelStatusResults, error) 91 } 92 93 type newCaasBrokerFunc func(args environs.OpenParams) (caas.Broker, error) 94 95 // ModelManagerAPI implements the model manager interface and is 96 // the concrete implementation of the api end point. 97 type ModelManagerAPI struct { 98 *common.ModelStatusAPI 99 state common.ModelManagerBackend 100 ctlrState common.ModelManagerBackend 101 check *common.BlockChecker 102 authorizer facade.Authorizer 103 toolsFinder *common.ToolsFinder 104 apiUser names.UserTag 105 isAdmin bool 106 model common.Model 107 getBroker newCaasBrokerFunc 108 callContext context.ProviderCallContext 109 } 110 111 // ModelManagerAPIV4 provides a way to wrap the different calls between 112 // version 4 and version 5 of the model manager API 113 type ModelManagerAPIV4 struct { 114 *ModelManagerAPI 115 } 116 117 // ModelManagerAPIV3 provides a way to wrap the different calls between 118 // version 3 and version 4 of the model manager API 119 type ModelManagerAPIV3 struct { 120 *ModelManagerAPIV4 121 } 122 123 // ModelManagerAPIV2 provides a way to wrap the different calls between 124 // version 2 and version 3 of the model manager API 125 type ModelManagerAPIV2 struct { 126 *ModelManagerAPIV3 127 } 128 129 var ( 130 _ ModelManagerV5 = (*ModelManagerAPI)(nil) 131 _ ModelManagerV4 = (*ModelManagerAPIV4)(nil) 132 _ ModelManagerV3 = (*ModelManagerAPIV3)(nil) 133 _ ModelManagerV2 = (*ModelManagerAPIV2)(nil) 134 ) 135 136 // NewFacadeV5 is used for API registration. 137 func NewFacadeV5(ctx facade.Context) (*ModelManagerAPI, error) { 138 st := ctx.State() 139 pool := ctx.StatePool() 140 ctlrSt := pool.SystemState() 141 auth := ctx.Auth() 142 143 var err error 144 model, err := st.Model() 145 if err != nil { 146 return nil, errors.Trace(err) 147 } 148 149 configGetter := stateenvirons.EnvironConfigGetter{st, model} 150 151 ctrlModel, err := ctlrSt.Model() 152 if err != nil { 153 return nil, err 154 } 155 156 return NewModelManagerAPI( 157 common.NewModelManagerBackend(model, pool), 158 common.NewModelManagerBackend(ctrlModel, pool), 159 configGetter, 160 caas.New, 161 auth, 162 model, 163 state.CallContext(st), 164 ) 165 } 166 167 // NewFacadeV4 is used for API registration. 168 func NewFacadeV4(ctx facade.Context) (*ModelManagerAPIV4, error) { 169 v5, err := NewFacadeV5(ctx) 170 if err != nil { 171 return nil, err 172 } 173 return &ModelManagerAPIV4{v5}, nil 174 } 175 176 // NewFacadeV3 is used for API registration. 177 func NewFacadeV3(ctx facade.Context) (*ModelManagerAPIV3, error) { 178 v4, err := NewFacadeV4(ctx) 179 if err != nil { 180 return nil, err 181 } 182 return &ModelManagerAPIV3{v4}, nil 183 } 184 185 // NewFacade is used for API registration. 186 func NewFacadeV2(ctx facade.Context) (*ModelManagerAPIV2, error) { 187 v3, err := NewFacadeV3(ctx) 188 if err != nil { 189 return nil, err 190 } 191 return &ModelManagerAPIV2{v3}, nil 192 } 193 194 // NewModelManagerAPI creates a new api server endpoint for managing 195 // models. 196 func NewModelManagerAPI( 197 st common.ModelManagerBackend, 198 ctlrSt common.ModelManagerBackend, 199 configGetter environs.EnvironConfigGetter, 200 getBroker newCaasBrokerFunc, 201 authorizer facade.Authorizer, 202 m common.Model, 203 callCtx context.ProviderCallContext, 204 ) (*ModelManagerAPI, error) { 205 if !authorizer.AuthClient() { 206 return nil, common.ErrPerm 207 } 208 // Since we know this is a user tag (because AuthClient is true), 209 // we just do the type assertion to the UserTag. 210 apiUser, _ := authorizer.GetAuthTag().(names.UserTag) 211 // Pretty much all of the user manager methods have special casing for admin 212 // users, so look once when we start and remember if the user is an admin. 213 isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, st.ControllerTag()) 214 if err != nil { 215 return nil, errors.Trace(err) 216 } 217 urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st) 218 return &ModelManagerAPI{ 219 ModelStatusAPI: common.NewModelStatusAPI(st, authorizer, apiUser), 220 state: st, 221 ctlrState: ctlrSt, 222 getBroker: getBroker, 223 check: common.NewBlockChecker(st), 224 authorizer: authorizer, 225 toolsFinder: common.NewToolsFinder(configGetter, st, urlGetter), 226 apiUser: apiUser, 227 isAdmin: isAdmin, 228 model: m, 229 callContext: callCtx, 230 }, nil 231 } 232 233 // authCheck checks if the user is acting on their own behalf, or if they 234 // are an administrator acting on behalf of another user. 235 func (m *ModelManagerAPI) authCheck(user names.UserTag) error { 236 if m.isAdmin { 237 logger.Tracef("%q is a controller admin", m.apiUser.Id()) 238 return nil 239 } 240 241 // We can't just compare the UserTags themselves as the provider part 242 // may be unset, and gets replaced with 'local'. We must compare against 243 // the Canonical value of the user tag. 244 if m.apiUser == user { 245 return nil 246 } 247 return common.ErrPerm 248 } 249 250 func (m *ModelManagerAPI) hasWriteAccess(modelTag names.ModelTag) (bool, error) { 251 canWrite, err := m.authorizer.HasPermission(permission.WriteAccess, modelTag) 252 if errors.IsNotFound(err) { 253 return false, nil 254 } 255 return canWrite, err 256 } 257 258 // ConfigSource describes a type that is able to provide config. 259 // Abstracted primarily for testing. 260 type ConfigSource interface { 261 Config() (*config.Config, error) 262 } 263 264 func (m *ModelManagerAPI) newModelConfig( 265 cloudSpec environs.CloudSpec, 266 args params.ModelCreateArgs, 267 source ConfigSource, 268 ) (*config.Config, error) { 269 // For now, we just smash to the two maps together as we store 270 // the account values and the model config together in the 271 // *config.Config instance. 272 joint := make(map[string]interface{}) 273 for key, value := range args.Config { 274 joint[key] = value 275 } 276 if _, ok := joint["uuid"]; ok { 277 return nil, errors.New("uuid is generated, you cannot specify one") 278 } 279 if args.Name == "" { 280 return nil, errors.NewNotValid(nil, "Name must be specified") 281 } 282 if _, ok := joint[config.NameKey]; ok { 283 return nil, errors.New("name must not be specified in config") 284 } 285 joint[config.NameKey] = args.Name 286 287 baseConfig, err := source.Config() 288 if err != nil { 289 return nil, errors.Trace(err) 290 } 291 292 regionSpec := &environs.RegionSpec{Cloud: cloudSpec.Name, Region: cloudSpec.Region} 293 if joint, err = m.state.ComposeNewModelConfig(joint, regionSpec); err != nil { 294 return nil, errors.Trace(err) 295 } 296 297 creator := modelmanager.ModelConfigCreator{ 298 Provider: environs.Provider, 299 FindTools: func(n version.Number) (tools.List, error) { 300 result, err := m.toolsFinder.FindTools(params.FindToolsParams{ 301 Number: n, 302 }) 303 if err != nil { 304 return nil, errors.Trace(err) 305 } 306 return result.List, nil 307 }, 308 } 309 return creator.NewModelConfig(cloudSpec, baseConfig, joint) 310 } 311 312 func (m *ModelManagerAPI) newCAASModelConfig( 313 cloudSpec environs.CloudSpec, 314 args params.ModelCreateArgs, 315 ) (*config.Config, error) { 316 uuid, err := utils.NewUUID() 317 if err != nil { 318 return nil, errors.Trace(err) 319 } 320 if args.Name == "" { 321 return nil, errors.NewNotValid(nil, "Name must be specified") 322 } 323 324 attrs := map[string]interface{}{ 325 config.NameKey: args.Name, 326 config.TypeKey: cloudSpec.Type, 327 config.UUIDKey: uuid.String(), 328 config.AgentVersionKey: jujuversion.Current.String(), 329 } 330 331 cfg, err := config.New(config.UseDefaults, attrs) 332 if err != nil { 333 return nil, errors.Annotate(err, "creating config from values failed") 334 } 335 336 return cfg, nil 337 } 338 339 func (m *ModelManagerAPI) checkAddModelPermission(cloud string, userTag names.UserTag) (bool, error) { 340 perm, err := m.ctlrState.GetCloudAccess(cloud, userTag) 341 if err != nil && !errors.IsNotFound(err) { 342 return false, errors.Trace(err) 343 } 344 if !perm.EqualOrGreaterCloudAccessThan(permission.AddModelAccess) { 345 return false, nil 346 } 347 return true, nil 348 } 349 350 // CreateModel creates a new model using the account and 351 // model config specified in the args. 352 func (m *ModelManagerAPI) CreateModel(args params.ModelCreateArgs) (params.ModelInfo, error) { 353 result := params.ModelInfo{} 354 355 // Get the controller model first. We need it both for the state 356 // server owner and the ability to get the config. 357 controllerModel, err := m.ctlrState.Model() 358 if err != nil { 359 return result, errors.Trace(err) 360 } 361 362 var cloudTag names.CloudTag 363 cloudRegionName := args.CloudRegion 364 if args.CloudTag != "" { 365 var err error 366 cloudTag, err = names.ParseCloudTag(args.CloudTag) 367 if err != nil { 368 return result, errors.Trace(err) 369 } 370 } else { 371 cloudTag = names.NewCloudTag(controllerModel.Cloud()) 372 } 373 if cloudRegionName == "" && cloudTag.Id() == controllerModel.Cloud() { 374 cloudRegionName = controllerModel.CloudRegion() 375 } 376 377 isAdmin, err := m.authorizer.HasPermission(permission.SuperuserAccess, m.state.ControllerTag()) 378 if err != nil { 379 return result, errors.Trace(err) 380 } 381 if !isAdmin { 382 canAddModel, err := m.checkAddModelPermission(cloudTag.Id(), m.apiUser) 383 if err != nil { 384 return result, errors.Trace(err) 385 } 386 if !canAddModel { 387 return result, common.ErrPerm 388 } 389 } 390 391 ownerTag, err := names.ParseUserTag(args.OwnerTag) 392 if err != nil { 393 return result, errors.Trace(err) 394 } 395 396 // a special case of ErrPerm will happen if the user has add-model permission but is trying to 397 // create a model for another person, which is not yet supported. 398 if !m.isAdmin && ownerTag != m.apiUser { 399 return result, errors.Annotatef(common.ErrPerm, "%q permission does not permit creation of models for different owners", permission.AddModelAccess) 400 } 401 402 cloud, err := m.state.Cloud(cloudTag.Id()) 403 if err != nil { 404 if errors.IsNotFound(err) && args.CloudTag != "" { 405 // A cloud was specified, and it was not found. 406 // Annotate the error with the supported clouds. 407 clouds, err := m.state.Clouds() 408 if err != nil { 409 return result, errors.Trace(err) 410 } 411 cloudNames := make([]string, 0, len(clouds)) 412 for tag := range clouds { 413 cloudNames = append(cloudNames, tag.Id()) 414 } 415 sort.Strings(cloudNames) 416 return result, errors.NewNotFound(err, fmt.Sprintf( 417 "cloud %q not found, expected one of %q", 418 cloudTag.Id(), cloudNames, 419 )) 420 } 421 return result, errors.Annotate(err, "getting cloud definition") 422 } 423 424 var cloudCredentialTag names.CloudCredentialTag 425 if args.CloudCredentialTag != "" { 426 var err error 427 cloudCredentialTag, err = names.ParseCloudCredentialTag(args.CloudCredentialTag) 428 if err != nil { 429 return result, errors.Trace(err) 430 } 431 } else { 432 if ownerTag == controllerModel.Owner() { 433 cloudCredentialTag, _ = controllerModel.CloudCredential() 434 } else { 435 // TODO(axw) check if the user has one and only one 436 // cloud credential, and if so, use it? For now, we 437 // require the user to specify a credential unless 438 // the cloud does not require one. 439 var hasEmpty bool 440 for _, authType := range cloud.AuthTypes { 441 if authType != jujucloud.EmptyAuthType { 442 continue 443 } 444 hasEmpty = true 445 break 446 } 447 if !hasEmpty { 448 return result, errors.NewNotValid(nil, "no credential specified") 449 } 450 } 451 } 452 453 var credential *jujucloud.Credential 454 if cloudCredentialTag != (names.CloudCredentialTag{}) { 455 credentialValue, err := m.state.CloudCredential(cloudCredentialTag) 456 if err != nil { 457 return result, errors.Annotate(err, "getting credential") 458 } 459 cloudCredential := jujucloud.NewNamedCredential( 460 credentialValue.Name, 461 jujucloud.AuthType(credentialValue.AuthType), 462 credentialValue.Attributes, 463 credentialValue.Revoked, 464 ) 465 credential = &cloudCredential 466 } 467 468 cloudSpec, err := environs.MakeCloudSpec(cloud, cloudRegionName, credential) 469 if err != nil { 470 return result, errors.Trace(err) 471 } 472 473 var model common.Model 474 475 if jujucloud.CloudIsCAAS(cloud) { 476 model, err = m.newCAASModel( 477 cloudSpec, 478 args, 479 cloudTag, 480 cloudCredentialTag, 481 ownerTag) 482 } else { 483 model, err = m.newModel( 484 cloudSpec, 485 args, 486 controllerModel, 487 cloudTag, 488 cloudRegionName, 489 cloudCredentialTag, 490 ownerTag) 491 } 492 if err != nil { 493 return result, errors.Trace(err) 494 } 495 return m.getModelInfo(model.ModelTag()) 496 } 497 498 func (m *ModelManagerAPI) newCAASModel(cloudSpec environs.CloudSpec, 499 createArgs params.ModelCreateArgs, 500 cloudTag names.CloudTag, 501 cloudCredentialTag names.CloudCredentialTag, 502 ownerTag names.UserTag, 503 ) (common.Model, error) { 504 newConfig, err := m.newCAASModelConfig(cloudSpec, createArgs) 505 if err != nil { 506 return nil, errors.Annotate(err, "failed to create config") 507 } 508 509 broker, err := m.getBroker(environs.OpenParams{ 510 Cloud: cloudSpec, 511 Config: newConfig, 512 }) 513 if err != nil { 514 return nil, errors.Annotate(err, "failed to open kubernetes client") 515 } 516 517 // CAAS models exist in a namespace which must be unique. 518 namespaces, err := broker.Namespaces() 519 if err != nil { 520 return nil, errors.Annotate(err, "failed to list namespaces") 521 } 522 nsSet := set.NewStrings(namespaces...) 523 if nsSet.Contains(createArgs.Name) { 524 return nil, errors.NewAlreadyExists(nil, fmt.Sprintf("namespace called %q already exists, would clash with model name", createArgs.Name)) 525 } 526 527 storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(broker) 528 529 model, st, err := m.state.NewModel(state.ModelArgs{ 530 Type: state.ModelTypeCAAS, 531 CloudName: cloudTag.Id(), 532 CloudCredential: cloudCredentialTag, 533 Config: newConfig, 534 Owner: ownerTag, 535 StorageProviderRegistry: storageProviderRegistry, 536 }) 537 if err != nil { 538 return nil, errors.Annotate(err, "failed to create new model") 539 } 540 defer st.Close() 541 542 return model, nil 543 } 544 545 func (m *ModelManagerAPI) newModel( 546 cloudSpec environs.CloudSpec, 547 createArgs params.ModelCreateArgs, 548 controllerModel common.Model, 549 cloudTag names.CloudTag, 550 cloudRegionName string, 551 cloudCredentialTag names.CloudCredentialTag, 552 ownerTag names.UserTag, 553 ) (common.Model, error) { 554 newConfig, err := m.newModelConfig(cloudSpec, createArgs, controllerModel) 555 if err != nil { 556 return nil, errors.Annotate(err, "failed to create config") 557 } 558 559 // Create the Environ. 560 env, err := environs.New(environs.OpenParams{ 561 Cloud: cloudSpec, 562 Config: newConfig, 563 }) 564 if err != nil { 565 return nil, errors.Annotate(err, "failed to open environ") 566 } 567 568 controllerCfg, err := m.state.ControllerConfig() 569 if err != nil { 570 return nil, errors.Trace(err) 571 } 572 573 err = env.Create( 574 m.callContext, 575 environs.CreateParams{ 576 ControllerUUID: controllerCfg.ControllerUUID(), 577 }, 578 ) 579 if err != nil { 580 return nil, errors.Annotate(err, "failed to create environ") 581 } 582 storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(env) 583 584 // NOTE: check the agent-version of the config, and if it is > the current 585 // version, it is not supported, also check existing tools, and if we don't 586 // have tools for that version, also die. 587 model, st, err := m.state.NewModel(state.ModelArgs{ 588 Type: state.ModelTypeIAAS, 589 CloudName: cloudTag.Id(), 590 CloudRegion: cloudRegionName, 591 CloudCredential: cloudCredentialTag, 592 Config: newConfig, 593 Owner: ownerTag, 594 StorageProviderRegistry: storageProviderRegistry, 595 EnvironVersion: env.Provider().Version(), 596 }) 597 if err != nil { 598 return nil, errors.Annotate(err, "failed to create new model") 599 } 600 defer st.Close() 601 602 if err = model.AutoConfigureContainerNetworking(env); err != nil { 603 if errors.IsNotSupported(err) { 604 logger.Debugf("Not performing container networking autoconfiguration on a non-networking environment") 605 } else { 606 return nil, errors.Annotate(err, "Failed to perform container networking autoconfiguration") 607 } 608 } 609 if err = st.ReloadSpaces(env); err != nil { 610 if errors.IsNotSupported(err) { 611 logger.Debugf("Not performing spaces load on a non-networking environment") 612 } else { 613 return nil, errors.Annotate(err, "Failed to perform spaces discovery") 614 } 615 } 616 return model, nil 617 } 618 619 func (m *ModelManagerAPI) dumpModel(args params.Entity, simplified bool) ([]byte, error) { 620 modelTag, err := names.ParseModelTag(args.Tag) 621 if err != nil { 622 return nil, errors.Trace(err) 623 } 624 625 isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 626 if err != nil { 627 return nil, errors.Trace(err) 628 } 629 if !isModelAdmin && !m.isAdmin { 630 return nil, common.ErrPerm 631 } 632 633 st, release, err := m.state.GetBackend(modelTag.Id()) 634 if err != nil { 635 if errors.IsNotFound(err) { 636 return nil, errors.Trace(common.ErrBadId) 637 } 638 return nil, errors.Trace(err) 639 } 640 defer release() 641 642 var exportConfig state.ExportConfig 643 if simplified { 644 exportConfig.SkipActions = true 645 exportConfig.SkipAnnotations = true 646 exportConfig.SkipCloudImageMetadata = true 647 exportConfig.SkipCredentials = true 648 exportConfig.SkipIPAddresses = true 649 exportConfig.SkipSettings = true 650 exportConfig.SkipSSHHostKeys = true 651 exportConfig.SkipStatusHistory = true 652 exportConfig.SkipLinkLayerDevices = true 653 } 654 655 model, err := st.ExportPartial(exportConfig) 656 if err != nil { 657 return nil, errors.Trace(err) 658 } 659 bytes, err := description.Serialize(model) 660 if err != nil { 661 return nil, errors.Trace(err) 662 } 663 return bytes, nil 664 } 665 666 func (m *ModelManagerAPIV2) dumpModel(args params.Entity) (map[string]interface{}, error) { 667 bytes, err := m.ModelManagerAPI.dumpModel(args, false) 668 if err != nil { 669 return nil, errors.Trace(err) 670 } 671 // Now read it back into a map. 672 var asMap map[string]interface{} 673 err = yaml.Unmarshal(bytes, &asMap) 674 if err != nil { 675 return nil, errors.Trace(err) 676 } 677 // In order to serialize the map through JSON, we need to make sure 678 // that all the embedded maps are map[string]interface{}, not 679 // map[interface{}]interface{} which is what YAML gives by default. 680 out, err := utils.ConformYAML(asMap) 681 if err != nil { 682 return nil, errors.Trace(err) 683 } 684 return out.(map[string]interface{}), nil 685 } 686 687 func (m *ModelManagerAPI) dumpModelDB(args params.Entity) (map[string]interface{}, error) { 688 modelTag, err := names.ParseModelTag(args.Tag) 689 if err != nil { 690 return nil, errors.Trace(err) 691 } 692 693 isModelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 694 if err != nil { 695 return nil, errors.Trace(err) 696 } 697 if !isModelAdmin && !m.isAdmin { 698 return nil, common.ErrPerm 699 } 700 701 st := m.state 702 if st.ModelTag() != modelTag { 703 newSt, release, err := m.state.GetBackend(modelTag.Id()) 704 if errors.IsNotFound(err) { 705 return nil, errors.Trace(common.ErrBadId) 706 } else if err != nil { 707 return nil, errors.Trace(err) 708 } 709 defer release() 710 st = newSt 711 } 712 713 return st.DumpAll() 714 } 715 716 // DumpModels will export the models into the database agnostic 717 // representation. The user needs to either be a controller admin, or have 718 // admin privileges on the model itself. 719 func (m *ModelManagerAPI) DumpModels(args params.DumpModelRequest) params.StringResults { 720 results := params.StringResults{ 721 Results: make([]params.StringResult, len(args.Entities)), 722 } 723 for i, entity := range args.Entities { 724 bytes, err := m.dumpModel(entity, args.Simplified) 725 if err != nil { 726 results.Results[i].Error = common.ServerError(err) 727 continue 728 } 729 // We know here that the bytes are valid YAML. 730 results.Results[i].Result = string(bytes) 731 } 732 return results 733 } 734 735 // DumpModels will export the models into the database agnostic 736 // representation. The user needs to either be a controller admin, or have 737 // admin privileges on the model itself. 738 func (m *ModelManagerAPIV2) DumpModels(args params.Entities) params.MapResults { 739 results := params.MapResults{ 740 Results: make([]params.MapResult, len(args.Entities)), 741 } 742 for i, entity := range args.Entities { 743 dumped, err := m.dumpModel(entity) 744 if err != nil { 745 results.Results[i].Error = common.ServerError(err) 746 continue 747 } 748 results.Results[i].Result = dumped 749 } 750 return results 751 } 752 753 // DumpModelsDB will gather all documents from all model collections 754 // for the specified model. The map result contains a map of collection 755 // names to lists of documents represented as maps. 756 func (m *ModelManagerAPI) DumpModelsDB(args params.Entities) params.MapResults { 757 results := params.MapResults{ 758 Results: make([]params.MapResult, len(args.Entities)), 759 } 760 for i, entity := range args.Entities { 761 dumped, err := m.dumpModelDB(entity) 762 if err != nil { 763 results.Results[i].Error = common.ServerError(err) 764 continue 765 } 766 results.Results[i].Result = dumped 767 } 768 return results 769 } 770 771 // ListModelSummaries returns models that the specified user 772 // has access to in the current server. Controller admins (superuser) 773 // can list models for any user. Other users 774 // can only ask about their own models. 775 func (m *ModelManagerAPI) ListModelSummaries(req params.ModelSummariesRequest) (params.ModelSummaryResults, error) { 776 result := params.ModelSummaryResults{} 777 778 userTag, err := names.ParseUserTag(req.UserTag) 779 if err != nil { 780 return result, errors.Trace(err) 781 } 782 783 err = m.authCheck(userTag) 784 if err != nil { 785 return result, errors.Trace(err) 786 } 787 788 modelInfos, err := m.state.ModelSummariesForUser(userTag, req.All) 789 if err != nil { 790 return result, errors.Trace(err) 791 } 792 793 for _, mi := range modelInfos { 794 summary := ¶ms.ModelSummary{ 795 Name: mi.Name, 796 UUID: mi.UUID, 797 Type: string(mi.Type), 798 OwnerTag: names.NewUserTag(mi.Owner).String(), 799 ControllerUUID: mi.ControllerUUID, 800 IsController: mi.IsController, 801 Life: params.Life(mi.Life.String()), 802 803 CloudTag: mi.CloudTag, 804 CloudRegion: mi.CloudRegion, 805 806 CloudCredentialTag: mi.CloudCredentialTag, 807 808 SLA: ¶ms.ModelSLAInfo{ 809 Level: mi.SLALevel, 810 Owner: mi.Owner, 811 }, 812 813 DefaultSeries: mi.DefaultSeries, 814 ProviderType: mi.ProviderType, 815 AgentVersion: mi.AgentVersion, 816 817 Status: common.EntityStatusFromState(mi.Status), 818 Counts: []params.ModelEntityCount{}, 819 UserLastConnection: mi.UserLastConnection, 820 } 821 822 if mi.MachineCount > 0 { 823 summary.Counts = append(summary.Counts, params.ModelEntityCount{params.Machines, mi.MachineCount}) 824 } 825 826 if mi.CoreCount > 0 { 827 summary.Counts = append(summary.Counts, params.ModelEntityCount{params.Cores, mi.CoreCount}) 828 } 829 830 access, err := common.StateToParamsUserAccessPermission(mi.Access) 831 if err == nil { 832 summary.UserAccess = access 833 } 834 if mi.Migration != nil { 835 migration := mi.Migration 836 startTime := migration.StartTime() 837 endTime := new(time.Time) 838 *endTime = migration.EndTime() 839 var zero time.Time 840 if *endTime == zero { 841 endTime = nil 842 } 843 844 summary.Migration = ¶ms.ModelMigrationStatus{ 845 Status: migration.StatusMessage(), 846 Start: &startTime, 847 End: endTime, 848 } 849 } 850 851 result.Results = append(result.Results, params.ModelSummaryResult{Result: summary}) 852 } 853 return result, nil 854 } 855 856 // ListModels returns the models that the specified user 857 // has access to in the current server. Controller admins (superuser) 858 // can list models for any user. Other users 859 // can only ask about their own models. 860 func (m *ModelManagerAPI) ListModels(user params.Entity) (params.UserModelList, error) { 861 result := params.UserModelList{} 862 863 userTag, err := names.ParseUserTag(user.Tag) 864 if err != nil { 865 return result, errors.Trace(err) 866 } 867 868 err = m.authCheck(userTag) 869 if err != nil { 870 return result, errors.Trace(err) 871 } 872 873 modelInfos, err := m.state.ModelBasicInfoForUser(userTag) 874 if err != nil { 875 return result, errors.Trace(err) 876 } 877 878 for _, mi := range modelInfos { 879 var ownerTag names.UserTag 880 if names.IsValidUser(mi.Owner) { 881 ownerTag = names.NewUserTag(mi.Owner) 882 } else { 883 // no reason to fail the request here, as it wasn't the users fault 884 logger.Warningf("for model %v, got an invalid owner: %q", mi.UUID, mi.Owner) 885 } 886 result.UserModels = append(result.UserModels, params.UserModel{ 887 Model: params.Model{ 888 Name: mi.Name, 889 UUID: mi.UUID, 890 Type: string(mi.Type), 891 OwnerTag: ownerTag.String(), 892 }, 893 LastConnection: &mi.LastConnection, 894 }) 895 } 896 897 return result, nil 898 } 899 900 // DestroyModels will try to destroy the specified models. 901 // If there is a block on destruction, this method will return an error. 902 func (m *ModelManagerAPIV3) DestroyModels(args params.Entities) (params.ErrorResults, error) { 903 // v3 DestroyModels is implemented in terms of v4: 904 // storage is unconditionally destroyed, as was the 905 // old behaviour. 906 destroyStorage := true 907 v4Args := params.DestroyModelsParams{ 908 Models: make([]params.DestroyModelParams, len(args.Entities)), 909 } 910 for i, arg := range args.Entities { 911 v4Args.Models[i] = params.DestroyModelParams{ 912 ModelTag: arg.Tag, 913 DestroyStorage: &destroyStorage, 914 } 915 } 916 return m.ModelManagerAPI.DestroyModels(v4Args) 917 } 918 919 // DestroyModels will try to destroy the specified models. 920 // If there is a block on destruction, this method will return an error. 921 func (m *ModelManagerAPI) DestroyModels(args params.DestroyModelsParams) (params.ErrorResults, error) { 922 results := params.ErrorResults{ 923 Results: make([]params.ErrorResult, len(args.Models)), 924 } 925 926 destroyModel := func(modelUUID string, destroyStorage *bool) error { 927 st, releaseSt, err := m.state.GetBackend(modelUUID) 928 if err != nil { 929 return errors.Trace(err) 930 } 931 defer releaseSt() 932 933 model, err := st.Model() 934 if err != nil { 935 return errors.Trace(err) 936 } 937 if !m.isAdmin { 938 hasAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, model.ModelTag()) 939 if err != nil { 940 return errors.Trace(err) 941 } 942 if !hasAdmin { 943 return errors.Trace(common.ErrPerm) 944 } 945 } 946 947 return errors.Trace(common.DestroyModel(st, destroyStorage)) 948 } 949 950 for i, arg := range args.Models { 951 tag, err := names.ParseModelTag(arg.ModelTag) 952 if err != nil { 953 results.Results[i].Error = common.ServerError(err) 954 continue 955 } 956 if err := destroyModel(tag.Id(), arg.DestroyStorage); err != nil { 957 results.Results[i].Error = common.ServerError(err) 958 continue 959 } 960 } 961 return results, nil 962 } 963 964 // ModelInfo returns information about the specified models. 965 func (m *ModelManagerAPI) ModelInfo(args params.Entities) (params.ModelInfoResults, error) { 966 results := params.ModelInfoResults{ 967 Results: make([]params.ModelInfoResult, len(args.Entities)), 968 } 969 970 getModelInfo := func(arg params.Entity) (params.ModelInfo, error) { 971 tag, err := names.ParseModelTag(arg.Tag) 972 if err != nil { 973 return params.ModelInfo{}, errors.Trace(err) 974 } 975 return m.getModelInfo(tag) 976 } 977 978 for i, arg := range args.Entities { 979 modelInfo, err := getModelInfo(arg) 980 if err != nil { 981 results.Results[i].Error = common.ServerError(err) 982 continue 983 } 984 results.Results[i].Result = &modelInfo 985 } 986 return results, nil 987 } 988 989 func (m *ModelManagerAPI) getModelInfo(tag names.ModelTag) (params.ModelInfo, error) { 990 st, release, err := m.state.GetBackend(tag.Id()) 991 if errors.IsNotFound(err) { 992 return params.ModelInfo{}, errors.Trace(common.ErrPerm) 993 } else if err != nil { 994 return params.ModelInfo{}, errors.Trace(err) 995 } 996 defer release() 997 998 model, err := st.Model() 999 if errors.IsNotFound(err) { 1000 return params.ModelInfo{}, errors.Trace(common.ErrPerm) 1001 } else if err != nil { 1002 return params.ModelInfo{}, errors.Trace(err) 1003 } 1004 1005 info := params.ModelInfo{ 1006 Name: model.Name(), 1007 Type: string(model.Type()), 1008 UUID: model.UUID(), 1009 ControllerUUID: model.ControllerUUID(), 1010 IsController: st.IsController(), 1011 OwnerTag: model.Owner().String(), 1012 Life: params.Life(model.Life().String()), 1013 CloudTag: names.NewCloudTag(model.Cloud()).String(), 1014 CloudRegion: model.CloudRegion(), 1015 } 1016 1017 if cloudCredentialTag, ok := model.CloudCredential(); ok { 1018 info.CloudCredentialTag = cloudCredentialTag.String() 1019 } 1020 1021 // All users with access to the model can see the SLA information. 1022 info.SLA = ¶ms.ModelSLAInfo{ 1023 Level: model.SLALevel(), 1024 Owner: model.SLAOwner(), 1025 } 1026 1027 // If model is not alive - dying or dead - or if it is being imported, 1028 // there is no guarantee that the rest of the call will succeed. 1029 // For these models we can ignore NotFound errors coming from persistence layer. 1030 // However, for Alive models, these errors are genuine and cannot be ignored. 1031 ignoreNotFoundError := model.Life() != state.Alive || model.MigrationMode() == state.MigrationModeImporting 1032 1033 // If we received an an error and cannot ignore it, we should consider it fatal and surface it. 1034 // We should do the same if we can ignore NotFound errors but the given error is of some other type. 1035 shouldErr := func(thisErr error) bool { 1036 if thisErr == nil { 1037 return false 1038 } 1039 return !ignoreNotFoundError || !errors.IsNotFound(thisErr) 1040 } 1041 cfg, err := model.Config() 1042 if shouldErr(err) { 1043 return params.ModelInfo{}, errors.Trace(err) 1044 } 1045 if err == nil { 1046 info.ProviderType = cfg.Type() 1047 info.DefaultSeries = config.PreferredSeries(cfg) 1048 if agentVersion, exists := cfg.AgentVersion(); exists { 1049 info.AgentVersion = &agentVersion 1050 } 1051 } 1052 1053 status, err := model.Status() 1054 if shouldErr(err) { 1055 return params.ModelInfo{}, errors.Trace(err) 1056 } 1057 if err == nil { 1058 entityStatus := common.EntityStatusFromState(status) 1059 info.Status = entityStatus 1060 } 1061 1062 // If the user is a controller superuser, they are considered a model 1063 // admin. 1064 modelAdmin := m.isAdmin 1065 if !m.isAdmin { 1066 modelAdmin, err = m.authorizer.HasPermission(permission.AdminAccess, model.ModelTag()) 1067 if err != nil { 1068 modelAdmin = false 1069 } 1070 } 1071 1072 users, err := model.Users() 1073 if shouldErr(err) { 1074 return params.ModelInfo{}, errors.Trace(err) 1075 } 1076 if err == nil { 1077 for _, user := range users { 1078 if !modelAdmin && m.authCheck(user.UserTag) != nil { 1079 // The authenticated user is neither the a controller 1080 // superuser, a model administrator, nor the model user, so 1081 // has no business knowing about the model user. 1082 continue 1083 } 1084 1085 userInfo, err := common.ModelUserInfo(user, model) 1086 if err != nil { 1087 return params.ModelInfo{}, errors.Trace(err) 1088 } 1089 info.Users = append(info.Users, userInfo) 1090 } 1091 1092 if len(info.Users) == 0 { 1093 // No users, which means the authenticated user doesn't 1094 // have access to the model. 1095 return params.ModelInfo{}, errors.Trace(common.ErrPerm) 1096 } 1097 } 1098 1099 canSeeMachines := modelAdmin 1100 if !canSeeMachines { 1101 if canSeeMachines, err = m.hasWriteAccess(tag); err != nil { 1102 return params.ModelInfo{}, errors.Trace(err) 1103 } 1104 } 1105 if canSeeMachines { 1106 if info.Machines, err = common.ModelMachineInfo(st); shouldErr(err) { 1107 return params.ModelInfo{}, err 1108 } 1109 } 1110 1111 migration, err := st.LatestMigration() 1112 if err != nil && !errors.IsNotFound(err) { 1113 return params.ModelInfo{}, errors.Trace(err) 1114 } 1115 if err == nil { 1116 startTime := migration.StartTime() 1117 endTime := new(time.Time) 1118 *endTime = migration.EndTime() 1119 var zero time.Time 1120 if *endTime == zero { 1121 endTime = nil 1122 } 1123 info.Migration = ¶ms.ModelMigrationStatus{ 1124 Status: migration.StatusMessage(), 1125 Start: &startTime, 1126 End: endTime, 1127 } 1128 } 1129 return info, nil 1130 } 1131 1132 // ModifyModelAccess changes the model access granted to users. 1133 func (m *ModelManagerAPI) ModifyModelAccess(args params.ModifyModelAccessRequest) (result params.ErrorResults, _ error) { 1134 result = params.ErrorResults{ 1135 Results: make([]params.ErrorResult, len(args.Changes)), 1136 } 1137 1138 canModifyController, err := m.authorizer.HasPermission(permission.SuperuserAccess, m.state.ControllerTag()) 1139 if err != nil { 1140 return result, errors.Trace(err) 1141 } 1142 if len(args.Changes) == 0 { 1143 return result, nil 1144 } 1145 1146 for i, arg := range args.Changes { 1147 modelAccess := permission.Access(arg.Access) 1148 if err := permission.ValidateModelAccess(modelAccess); err != nil { 1149 err = errors.Annotate(err, "could not modify model access") 1150 result.Results[i].Error = common.ServerError(err) 1151 continue 1152 } 1153 1154 modelTag, err := names.ParseModelTag(arg.ModelTag) 1155 if err != nil { 1156 result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) 1157 continue 1158 } 1159 canModifyModel, err := m.authorizer.HasPermission(permission.AdminAccess, modelTag) 1160 if err != nil { 1161 return result, errors.Trace(err) 1162 } 1163 canModify := canModifyController || canModifyModel 1164 1165 if !canModify { 1166 result.Results[i].Error = common.ServerError(common.ErrPerm) 1167 continue 1168 } 1169 1170 targetUserTag, err := names.ParseUserTag(arg.UserTag) 1171 if err != nil { 1172 result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify model access")) 1173 continue 1174 } 1175 1176 result.Results[i].Error = common.ServerError( 1177 changeModelAccess(m.state, modelTag, m.apiUser, targetUserTag, arg.Action, modelAccess, m.isAdmin)) 1178 } 1179 return result, nil 1180 } 1181 1182 func userAuthorizedToChangeAccess(st common.ModelManagerBackend, userIsAdmin bool, userTag names.UserTag) error { 1183 if userIsAdmin { 1184 // Just confirm that the model that has been given is a valid model. 1185 _, err := st.Model() 1186 if err != nil { 1187 return errors.Trace(err) 1188 } 1189 return nil 1190 } 1191 1192 // Get the current user's ModelUser for the Model to see if the user has 1193 // permission to grant or revoke permissions on the model. 1194 currentUser, err := st.UserAccess(userTag, st.ModelTag()) 1195 if err != nil { 1196 if errors.IsNotFound(err) { 1197 // No, this user doesn't have permission. 1198 return common.ErrPerm 1199 } 1200 return errors.Annotate(err, "could not retrieve user") 1201 } 1202 if currentUser.Access != permission.AdminAccess { 1203 return common.ErrPerm 1204 } 1205 return nil 1206 } 1207 1208 // changeModelAccess performs the requested access grant or revoke action for the 1209 // specified user on the specified model. 1210 func changeModelAccess(accessor common.ModelManagerBackend, modelTag names.ModelTag, apiUser, targetUserTag names.UserTag, action params.ModelAction, access permission.Access, userIsAdmin bool) error { 1211 st, release, err := accessor.GetBackend(modelTag.Id()) 1212 if err != nil { 1213 return errors.Annotate(err, "could not lookup model") 1214 } 1215 defer release() 1216 1217 if err := userAuthorizedToChangeAccess(st, userIsAdmin, apiUser); err != nil { 1218 return errors.Trace(err) 1219 } 1220 1221 model, err := st.Model() 1222 if err != nil { 1223 return errors.Trace(err) 1224 } 1225 1226 switch action { 1227 case params.GrantModelAccess: 1228 _, err = model.AddUser(state.UserAccessSpec{User: targetUserTag, CreatedBy: apiUser, Access: access}) 1229 if errors.IsAlreadyExists(err) { 1230 modelUser, err := st.UserAccess(targetUserTag, modelTag) 1231 if errors.IsNotFound(err) { 1232 // Conflicts with prior check, must be inconsistent state. 1233 err = txn.ErrExcessiveContention 1234 } 1235 if err != nil { 1236 return errors.Annotate(err, "could not look up model access for user") 1237 } 1238 1239 // Only set access if greater access is being granted. 1240 if modelUser.Access.EqualOrGreaterModelAccessThan(access) { 1241 return errors.Errorf("user already has %q access or greater", access) 1242 } 1243 if _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, access); err != nil { 1244 return errors.Annotate(err, "could not set model access for user") 1245 } 1246 return nil 1247 } 1248 return errors.Annotate(err, "could not grant model access") 1249 1250 case params.RevokeModelAccess: 1251 switch access { 1252 case permission.ReadAccess: 1253 // Revoking read access removes all access. 1254 err := st.RemoveUserAccess(targetUserTag, modelTag) 1255 return errors.Annotate(err, "could not revoke model access") 1256 case permission.WriteAccess: 1257 // Revoking write access sets read-only. 1258 modelUser, err := st.UserAccess(targetUserTag, modelTag) 1259 if err != nil { 1260 return errors.Annotate(err, "could not look up model access for user") 1261 } 1262 _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.ReadAccess) 1263 return errors.Annotate(err, "could not set model access to read-only") 1264 case permission.AdminAccess: 1265 // Revoking admin access sets read-write. 1266 modelUser, err := st.UserAccess(targetUserTag, modelTag) 1267 if err != nil { 1268 return errors.Annotate(err, "could not look up model access for user") 1269 } 1270 _, err = st.SetUserAccess(modelUser.UserTag, modelUser.Object, permission.WriteAccess) 1271 return errors.Annotate(err, "could not set model access to read-write") 1272 1273 default: 1274 return errors.Errorf("don't know how to revoke %q access", access) 1275 } 1276 1277 default: 1278 return errors.Errorf("unknown action %q", action) 1279 } 1280 } 1281 1282 // ModelDefaults returns the default config values used when creating a new model. 1283 func (m *ModelManagerAPI) ModelDefaults() (params.ModelDefaultsResult, error) { 1284 result := params.ModelDefaultsResult{} 1285 if !m.isAdmin { 1286 return result, common.ErrPerm 1287 } 1288 1289 values, err := m.model.ModelConfigDefaultValues() 1290 if err != nil { 1291 return result, errors.Trace(err) 1292 } 1293 result.Config = make(map[string]params.ModelDefaults) 1294 for attr, val := range values { 1295 settings := params.ModelDefaults{ 1296 Controller: val.Controller, 1297 Default: val.Default, 1298 } 1299 for _, v := range val.Regions { 1300 settings.Regions = append( 1301 settings.Regions, params.RegionDefaults{ 1302 RegionName: v.Name, 1303 Value: v.Value}) 1304 } 1305 result.Config[attr] = settings 1306 } 1307 return result, nil 1308 } 1309 1310 // SetModelDefaults writes new values for the specified default model settings. 1311 func (m *ModelManagerAPI) SetModelDefaults(args params.SetModelDefaults) (params.ErrorResults, error) { 1312 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Config))} 1313 if err := m.check.ChangeAllowed(); err != nil { 1314 return results, errors.Trace(err) 1315 } 1316 for i, arg := range args.Config { 1317 results.Results[i].Error = common.ServerError( 1318 m.setModelDefaults(arg), 1319 ) 1320 } 1321 return results, nil 1322 } 1323 1324 func (m *ModelManagerAPI) setModelDefaults(args params.ModelDefaultValues) error { 1325 if !m.isAdmin { 1326 return common.ErrPerm 1327 } 1328 1329 if err := m.check.ChangeAllowed(); err != nil { 1330 return errors.Trace(err) 1331 } 1332 // Make sure we don't allow changing agent-version. 1333 if _, found := args.Config["agent-version"]; found { 1334 return errors.New("agent-version cannot have a default value") 1335 } 1336 1337 var rspec *environs.RegionSpec 1338 if args.CloudRegion != "" { 1339 spec, err := m.makeRegionSpec(args.CloudTag, args.CloudRegion) 1340 if err != nil { 1341 return errors.Trace(err) 1342 } 1343 rspec = spec 1344 } 1345 return m.state.UpdateModelConfigDefaultValues(args.Config, nil, rspec) 1346 } 1347 1348 // UnsetModelDefaults removes the specified default model settings. 1349 func (m *ModelManagerAPI) UnsetModelDefaults(args params.UnsetModelDefaults) (params.ErrorResults, error) { 1350 results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Keys))} 1351 if !m.isAdmin { 1352 return results, common.ErrPerm 1353 } 1354 1355 if err := m.check.ChangeAllowed(); err != nil { 1356 return results, errors.Trace(err) 1357 } 1358 1359 for i, arg := range args.Keys { 1360 var rspec *environs.RegionSpec 1361 if arg.CloudRegion != "" { 1362 spec, err := m.makeRegionSpec(arg.CloudTag, arg.CloudRegion) 1363 if err != nil { 1364 results.Results[i].Error = common.ServerError( 1365 errors.Trace(err)) 1366 continue 1367 } 1368 rspec = spec 1369 } 1370 results.Results[i].Error = common.ServerError( 1371 m.state.UpdateModelConfigDefaultValues(nil, arg.Keys, rspec), 1372 ) 1373 } 1374 return results, nil 1375 } 1376 1377 // makeRegionSpec is a helper method for methods that call 1378 // state.UpdateModelConfigDefaultValues. 1379 func (m *ModelManagerAPI) makeRegionSpec(cloudTag, r string) (*environs.RegionSpec, error) { 1380 cTag, err := names.ParseCloudTag(cloudTag) 1381 if err != nil { 1382 return nil, errors.Trace(err) 1383 } 1384 rspec, err := environs.NewRegionSpec(cTag.Id(), r) 1385 if err != nil { 1386 return nil, errors.Trace(err) 1387 } 1388 return rspec, nil 1389 } 1390 1391 // ModelStatus is a legacy method call to ensure that we preserve 1392 // backward compatibility. 1393 // TODO (anastasiamac 2017-10-26) This should be made obsolete/removed. 1394 func (s *ModelManagerAPIV2) ModelStatus(req params.Entities) (params.ModelStatusResults, error) { 1395 return s.ModelManagerAPI.oldModelStatus(req) 1396 } 1397 1398 // ModelStatus is a legacy method call to ensure that we preserve 1399 // backward compatibility. 1400 // TODO (anastasiamac 2017-10-26) This should be made obsolete/removed. 1401 func (s *ModelManagerAPIV3) ModelStatus(req params.Entities) (params.ModelStatusResults, error) { 1402 return s.ModelManagerAPI.oldModelStatus(req) 1403 } 1404 1405 // ModelStatus is a legacy method call to ensure that we preserve 1406 // backward compatibility. 1407 // TODO (anastasiamac 2017-10-26) This should be made obsolete/removed. 1408 func (s *ModelManagerAPI) oldModelStatus(req params.Entities) (params.ModelStatusResults, error) { 1409 results, err := s.ModelStatusAPI.ModelStatus(req) 1410 if err != nil { 1411 return params.ModelStatusResults{}, err 1412 } 1413 for _, r := range results.Results { 1414 if r.Error != nil { 1415 return params.ModelStatusResults{Results: make([]params.ModelStatus, len(req.Entities))}, errors.Trace(r.Error) 1416 } 1417 } 1418 return results, nil 1419 } 1420 1421 // ChangeModelCredentials changes cloud credential reference for models. 1422 // These new cloud credentials must already exist on the controller. 1423 func (m *ModelManagerAPI) ChangeModelCredential(args params.ChangeModelCredentialsParams) (params.ErrorResults, error) { 1424 if err := m.check.ChangeAllowed(); err != nil { 1425 return params.ErrorResults{}, errors.Trace(err) 1426 } 1427 1428 controllerAdmin, err := m.authorizer.HasPermission(permission.SuperuserAccess, m.state.ControllerTag()) 1429 if err != nil { 1430 return params.ErrorResults{}, errors.Trace(err) 1431 } 1432 // Only controller or model admin can change cloud credential on a model. 1433 checkModelAccess := func(tag names.ModelTag) error { 1434 if controllerAdmin { 1435 return nil 1436 } 1437 modelAdmin, err := m.authorizer.HasPermission(permission.AdminAccess, tag) 1438 if err != nil { 1439 return errors.Trace(err) 1440 } 1441 if modelAdmin { 1442 return nil 1443 } 1444 return common.ErrPerm 1445 } 1446 1447 replaceModelCredential := func(arg params.ChangeModelCredentialParams) error { 1448 modelTag, err := names.ParseModelTag(arg.ModelTag) 1449 if err != nil { 1450 return errors.Trace(err) 1451 } 1452 if err := checkModelAccess(modelTag); err != nil { 1453 return errors.Trace(err) 1454 } 1455 credentialTag, err := names.ParseCloudCredentialTag(arg.CloudCredentialTag) 1456 if err != nil { 1457 return errors.Trace(err) 1458 } 1459 model, releaser, err := m.state.GetModel(modelTag.Id()) 1460 if err != nil { 1461 return errors.Trace(err) 1462 } 1463 defer releaser() 1464 1465 updated, err := model.SetCloudCredential(credentialTag) 1466 if err != nil { 1467 return errors.Trace(err) 1468 } 1469 if !updated { 1470 return errors.Errorf("model %v already uses credential %v", modelTag.Id(), credentialTag.Id()) 1471 } 1472 return nil 1473 } 1474 1475 results := make([]params.ErrorResult, len(args.Models)) 1476 for i, arg := range args.Models { 1477 if err := replaceModelCredential(arg); err != nil { 1478 results[i].Error = common.ServerError(err) 1479 } 1480 } 1481 return params.ErrorResults{results}, nil 1482 } 1483 1484 // Mask out new methods from the old API versions. The API reflection 1485 // code in rpc/rpcreflect/type.go:newMethod skips 2-argument methods, 1486 // so this removes the method as far as the RPC machinery is concerned. 1487 // 1488 // ChangeModelCredential did not exist prior to v5. 1489 func (*ModelManagerAPIV4) ChangeModelCredential(_, _ struct{}) {}