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