github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/application/application.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package application contains api calls for functionality 5 // related to deploying and managing applications and their 6 // related charms. 7 package application 8 9 import ( 10 "fmt" 11 "net" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/schema" 16 "gopkg.in/juju/charm.v6" 17 csparams "gopkg.in/juju/charmrepo.v3/csclient/params" 18 "gopkg.in/juju/environschema.v1" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/macaroon.v2-unstable" 21 goyaml "gopkg.in/yaml.v2" 22 23 "github.com/juju/juju/apiserver/common" 24 "github.com/juju/juju/apiserver/common/storagecommon" 25 "github.com/juju/juju/apiserver/facade" 26 "github.com/juju/juju/apiserver/params" 27 "github.com/juju/juju/caas" 28 k8s "github.com/juju/juju/caas/kubernetes/provider" 29 "github.com/juju/juju/core/application" 30 "github.com/juju/juju/core/constraints" 31 "github.com/juju/juju/core/crossmodel" 32 "github.com/juju/juju/core/instance" 33 "github.com/juju/juju/core/status" 34 "github.com/juju/juju/environs" 35 "github.com/juju/juju/network" 36 "github.com/juju/juju/permission" 37 "github.com/juju/juju/state" 38 "github.com/juju/juju/state/stateenvirons" 39 "github.com/juju/juju/storage/poolmanager" 40 ) 41 42 var logger = loggo.GetLogger("juju.apiserver.application") 43 44 // APIv4 provides the Application API facade for versions 1-4. 45 type APIv4 struct { 46 *APIv5 47 } 48 49 // APIv5 provides the Application API facade for version 5. 50 type APIv5 struct { 51 *APIv6 52 } 53 54 // APIv6 provides the Application API facade for version 6. 55 type APIv6 struct { 56 *APIv7 57 } 58 59 // APIv7 provides the Application API facade for version 7. 60 type APIv7 struct { 61 *APIv8 62 } 63 64 // APIv8 provides the Application API facade for version 8. 65 type APIv8 struct { 66 *APIv9 67 } 68 69 // APIv9 provides the Application API facade for version 9. 70 type APIv9 struct { 71 *APIBase 72 } 73 74 // APIBase implements the shared application interface and is the concrete 75 // implementation of the api end point. 76 // 77 // API provides the Application API facade for version 5. 78 type APIBase struct { 79 backend Backend 80 storageAccess storageInterface 81 82 authorizer facade.Authorizer 83 check BlockChecker 84 85 modelTag names.ModelTag 86 modelType state.ModelType 87 modelName string 88 89 resources facade.Resources 90 91 // TODO(axw) stateCharm only exists because I ran out 92 // of time unwinding all of the tendrils of state. We 93 // should pass a charm.Charm and charm.URL back into 94 // state wherever we pass in a state.Charm currently. 95 stateCharm func(Charm) *state.Charm 96 97 storagePoolManager poolmanager.PoolManager 98 caasBroker caas.Broker 99 deployApplicationFunc func(ApplicationDeployer, DeployApplicationParams) (Application, error) 100 } 101 102 // NewFacadeV4 provides the signature required for facade registration 103 // for versions 1-4. 104 func NewFacadeV4(ctx facade.Context) (*APIv4, error) { 105 api, err := NewFacadeV5(ctx) 106 if err != nil { 107 return nil, errors.Trace(err) 108 } 109 return &APIv4{api}, nil 110 } 111 112 // NewFacadeV5 provides the signature required for facade registration 113 // for version 5. 114 func NewFacadeV5(ctx facade.Context) (*APIv5, error) { 115 api, err := NewFacadeV6(ctx) 116 if err != nil { 117 return nil, errors.Trace(err) 118 } 119 return &APIv5{api}, nil 120 } 121 122 // NewFacadeV6 provides the signature required for facade registration 123 // for version 6. 124 func NewFacadeV6(ctx facade.Context) (*APIv6, error) { 125 api, err := NewFacadeV7(ctx) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 return &APIv6{api}, nil 130 } 131 132 // NewFacadeV7 provides the signature required for facade registration 133 // for version 7. 134 func NewFacadeV7(ctx facade.Context) (*APIv7, error) { 135 api, err := NewFacadeV8(ctx) 136 if err != nil { 137 return nil, errors.Trace(err) 138 } 139 return &APIv7{api}, nil 140 } 141 142 // NewFacadeV8 provides the signature required for facade registration 143 // for version 8. 144 func NewFacadeV8(ctx facade.Context) (*APIv8, error) { 145 api, err := NewFacadeV9(ctx) 146 if err != nil { 147 return nil, errors.Trace(err) 148 } 149 return &APIv8{api}, nil 150 } 151 152 func NewFacadeV9(ctx facade.Context) (*APIv9, error) { 153 api, err := newFacadeBase(ctx) 154 if err != nil { 155 return nil, errors.Trace(err) 156 } 157 return &APIv9{api}, nil 158 } 159 160 func newFacadeBase(ctx facade.Context) (*APIBase, error) { 161 model, err := ctx.State().Model() 162 if err != nil { 163 return nil, errors.Annotate(err, "getting model") 164 } 165 storageAccess, err := getStorageState(ctx.State()) 166 if err != nil { 167 return nil, errors.Annotate(err, "getting state") 168 } 169 blockChecker := common.NewBlockChecker(ctx.State()) 170 stateCharm := CharmToStateCharm 171 172 var storagePoolManager poolmanager.PoolManager 173 var caasBroker caas.Broker 174 if model.Type() == state.ModelTypeCAAS { 175 caasBroker, err = stateenvirons.GetNewCAASBrokerFunc(caas.New)(ctx.State()) 176 if err != nil { 177 return nil, errors.Annotate(err, "getting caas client") 178 } 179 storageProviderRegistry := stateenvirons.NewStorageProviderRegistry(caasBroker) 180 storagePoolManager = poolmanager.New(state.NewStateSettings(ctx.State()), storageProviderRegistry) 181 } 182 183 resources := ctx.Resources() 184 185 return NewAPIBase( 186 &stateShim{ctx.State()}, 187 storageAccess, 188 ctx.Auth(), 189 blockChecker, 190 model.ModelTag(), 191 model.Type(), 192 model.Name(), 193 stateCharm, 194 DeployApplication, 195 storagePoolManager, 196 resources, 197 caasBroker, 198 ) 199 } 200 201 // NewAPIBase returns a new application API facade. 202 func NewAPIBase( 203 backend Backend, 204 storageAccess storageInterface, 205 authorizer facade.Authorizer, 206 blockChecker BlockChecker, 207 modelTag names.ModelTag, 208 modelType state.ModelType, 209 modelName string, 210 stateCharm func(Charm) *state.Charm, 211 deployApplication func(ApplicationDeployer, DeployApplicationParams) (Application, error), 212 storagePoolManager poolmanager.PoolManager, 213 resources facade.Resources, 214 caasBroker caas.Broker, 215 ) (*APIBase, error) { 216 if !authorizer.AuthClient() { 217 return nil, common.ErrPerm 218 } 219 return &APIBase{ 220 backend: backend, 221 storageAccess: storageAccess, 222 authorizer: authorizer, 223 check: blockChecker, 224 modelTag: modelTag, 225 modelType: modelType, 226 modelName: modelName, 227 stateCharm: stateCharm, 228 deployApplicationFunc: deployApplication, 229 storagePoolManager: storagePoolManager, 230 resources: resources, 231 caasBroker: caasBroker, 232 }, nil 233 } 234 235 func (api *APIBase) checkPermission(tag names.Tag, perm permission.Access) error { 236 allowed, err := api.authorizer.HasPermission(perm, tag) 237 if err != nil { 238 return errors.Trace(err) 239 } 240 if !allowed { 241 return common.ErrPerm 242 } 243 return nil 244 } 245 246 func (api *APIBase) checkCanRead() error { 247 return api.checkPermission(api.modelTag, permission.ReadAccess) 248 } 249 250 func (api *APIBase) checkCanWrite() error { 251 return api.checkPermission(api.modelTag, permission.WriteAccess) 252 } 253 254 // SetMetricCredentials sets credentials on the application. 255 func (api *APIBase) SetMetricCredentials(args params.ApplicationMetricCredentials) (params.ErrorResults, error) { 256 if err := api.checkCanWrite(); err != nil { 257 return params.ErrorResults{}, errors.Trace(err) 258 } 259 result := params.ErrorResults{ 260 Results: make([]params.ErrorResult, len(args.Creds)), 261 } 262 if len(args.Creds) == 0 { 263 return result, nil 264 } 265 for i, a := range args.Creds { 266 application, err := api.backend.Application(a.ApplicationName) 267 if err != nil { 268 result.Results[i].Error = common.ServerError(err) 269 continue 270 } 271 err = application.SetMetricCredentials(a.MetricCredentials) 272 if err != nil { 273 result.Results[i].Error = common.ServerError(err) 274 } 275 } 276 return result, nil 277 } 278 279 // Deploy fetches the charms from the charm store and deploys them 280 // using the specified placement directives. 281 // V5 deploy did not support policy, so pass through an empty string. 282 func (api *APIv5) Deploy(args params.ApplicationsDeployV5) (params.ErrorResults, error) { 283 noDefinedPolicy := "" 284 var newArgs params.ApplicationsDeploy 285 for _, value := range args.Applications { 286 newArgs.Applications = append(newArgs.Applications, params.ApplicationDeploy{ 287 ApplicationName: value.ApplicationName, 288 Series: value.Series, 289 CharmURL: value.CharmURL, 290 Channel: value.Channel, 291 NumUnits: value.NumUnits, 292 Config: value.Config, 293 ConfigYAML: value.ConfigYAML, 294 Constraints: value.Constraints, 295 Placement: value.Placement, 296 Policy: noDefinedPolicy, 297 Storage: value.Storage, 298 AttachStorage: value.AttachStorage, 299 EndpointBindings: value.EndpointBindings, 300 Resources: value.Resources, 301 }) 302 } 303 return api.APIBase.Deploy(newArgs) 304 } 305 306 // Deploy fetches the charms from the charm store and deploys them 307 // using the specified placement directives. 308 // V6 deploy did not support devices, so pass through an empty map. 309 func (api *APIv6) Deploy(args params.ApplicationsDeployV6) (params.ErrorResults, error) { 310 var newArgs params.ApplicationsDeploy 311 for _, value := range args.Applications { 312 newArgs.Applications = append(newArgs.Applications, params.ApplicationDeploy{ 313 ApplicationName: value.ApplicationName, 314 Series: value.Series, 315 CharmURL: value.CharmURL, 316 Channel: value.Channel, 317 NumUnits: value.NumUnits, 318 Config: value.Config, 319 ConfigYAML: value.ConfigYAML, 320 Constraints: value.Constraints, 321 Placement: value.Placement, 322 Policy: value.Policy, 323 Devices: nil, // set Devices to nil because v6 and lower versions do not support it 324 Storage: value.Storage, 325 AttachStorage: value.AttachStorage, 326 EndpointBindings: value.EndpointBindings, 327 Resources: value.Resources, 328 }) 329 } 330 return api.APIBase.Deploy(newArgs) 331 } 332 333 // Deploy fetches the charms from the charm store and deploys them 334 // using the specified placement directives. 335 func (api *APIBase) Deploy(args params.ApplicationsDeploy) (params.ErrorResults, error) { 336 if err := api.checkCanWrite(); err != nil { 337 return params.ErrorResults{}, errors.Trace(err) 338 } 339 result := params.ErrorResults{ 340 Results: make([]params.ErrorResult, len(args.Applications)), 341 } 342 if err := api.check.ChangeAllowed(); err != nil { 343 return result, errors.Trace(err) 344 } 345 346 for i, arg := range args.Applications { 347 err := deployApplication(api.backend, api.modelType, api.modelName, api.stateCharm, arg, api.deployApplicationFunc, api.storagePoolManager, api.caasBroker) 348 result.Results[i].Error = common.ServerError(err) 349 350 if err != nil && len(arg.Resources) != 0 { 351 // Remove any pending resources - these would have been 352 // converted into real resources if the application had 353 // been created successfully, but will otherwise be 354 // leaked. lp:1705730 355 // TODO(babbageclunk): rework the deploy API so the 356 // resources are created transactionally to avoid needing 357 // to do this. 358 resources, err := api.backend.Resources() 359 if err != nil { 360 logger.Errorf("couldn't get backend.Resources") 361 continue 362 } 363 err = resources.RemovePendingAppResources(arg.ApplicationName, arg.Resources) 364 if err != nil { 365 logger.Errorf("couldn't remove pending resources for %q", arg.ApplicationName) 366 } 367 } 368 } 369 return result, nil 370 } 371 372 func applicationConfigSchema(modelType state.ModelType) (environschema.Fields, schema.Defaults, error) { 373 if modelType != state.ModelTypeCAAS { 374 return trustFields, trustDefaults, nil 375 } 376 // TODO(caas) - get the schema from the provider 377 defaults := caas.ConfigDefaults(k8s.ConfigDefaults()) 378 schema, err := caas.ConfigSchema(k8s.ConfigSchema()) 379 if err != nil { 380 return nil, nil, err 381 } 382 return AddTrustSchemaAndDefaults(schema, defaults) 383 } 384 385 func splitApplicationAndCharmConfig(modelType state.ModelType, inConfig map[string]string) ( 386 appCfg map[string]interface{}, 387 charmCfg map[string]string, 388 _ error, 389 ) { 390 391 providerSchema, _, err := applicationConfigSchema(modelType) 392 if err != nil { 393 return nil, nil, errors.Trace(err) 394 } 395 appConfigKeys := application.KnownConfigKeys(providerSchema) 396 397 appConfigAttrs := make(map[string]interface{}) 398 charmConfig := make(map[string]string) 399 for k, v := range inConfig { 400 if appConfigKeys.Contains(k) { 401 appConfigAttrs[k] = v 402 } else { 403 charmConfig[k] = v 404 } 405 } 406 return appConfigAttrs, charmConfig, nil 407 } 408 409 // splitApplicationAndCharmConfigFromYAML extracts app specific settings from a charm config YAML 410 // and returns those app settings plus a YAML with just the charm settings left behind. 411 func splitApplicationAndCharmConfigFromYAML(modelType state.ModelType, inYaml, appName string) ( 412 appCfg map[string]interface{}, 413 outYaml string, 414 _ error, 415 ) { 416 var allSettings map[string]map[string]interface{} 417 if err := goyaml.Unmarshal([]byte(inYaml), &allSettings); err != nil { 418 return nil, "", errors.Annotate(err, "cannot parse settings data") 419 } 420 settings, ok := allSettings[appName] 421 if !ok { 422 return nil, "", errors.Errorf("no settings found for %q", appName) 423 } 424 425 providerSchema, _, err := applicationConfigSchema(modelType) 426 if err != nil { 427 return nil, "", errors.Trace(err) 428 } 429 appConfigKeys := application.KnownConfigKeys(providerSchema) 430 431 appConfigAttrs := make(map[string]interface{}) 432 for k, v := range settings { 433 if appConfigKeys.Contains(k) { 434 appConfigAttrs[k] = v 435 delete(settings, k) 436 } 437 } 438 if len(settings) == 0 { 439 return appConfigAttrs, "", nil 440 } 441 442 allSettings[appName] = settings 443 charmConfig, err := goyaml.Marshal(allSettings) 444 if err != nil { 445 return nil, "", errors.Annotate(err, "cannot marshall charm settings") 446 } 447 return appConfigAttrs, string(charmConfig), nil 448 } 449 450 // deployApplication fetches the charm from the charm store and deploys it. 451 // The logic has been factored out into a common function which is called by 452 // both the legacy API on the client facade, as well as the new application facade. 453 func deployApplication( 454 backend Backend, 455 modelType state.ModelType, 456 modelName string, 457 stateCharm func(Charm) *state.Charm, 458 args params.ApplicationDeploy, 459 deployApplicationFunc func(ApplicationDeployer, DeployApplicationParams) (Application, error), 460 storagePoolManager poolmanager.PoolManager, 461 caasBroker caas.Broker, 462 ) error { 463 curl, err := charm.ParseURL(args.CharmURL) 464 if err != nil { 465 return errors.Trace(err) 466 } 467 if curl.Revision < 0 { 468 return errors.Errorf("charm url must include revision") 469 } 470 471 if modelType != state.ModelTypeIAAS { 472 if len(args.AttachStorage) > 0 { 473 return errors.Errorf( 474 "AttachStorage may not be specified for %s models", 475 modelType, 476 ) 477 } 478 if len(args.Placement) > 1 { 479 return errors.Errorf( 480 "only 1 placement directive is supported for %s models, got %d", 481 modelType, 482 len(args.Placement), 483 ) 484 } 485 486 // CAAS models will use an "operator-storage" storage pool if it exists. 487 sp, err := storagePoolManager.Get(caas.OperatorStoragePoolName) 488 if err != nil && !errors.IsNotFound(err) { 489 return errors.Trace(err) 490 } 491 if err == nil { 492 if sp.Provider() != k8s.K8s_ProviderType { 493 return errors.Errorf( 494 "the %q storage pool requires a provider type of %q, not %q", caas.OperatorStoragePoolName, k8s.K8s_ProviderType, sp.Provider()) 495 } 496 } else { 497 // No operator-storage pool so try and see if there's a cluster default. 498 if _, err := caasBroker.GetStorageClassName(caas.OperatorStorageClassLabels(args.ApplicationName, modelName)...); err != nil { 499 return errors.Annotatef( 500 err, 501 "deploying a Kubernetes application requires a suitable storage class.\n"+ 502 "None were found in the cluster. Either create a default storage class\n"+ 503 "or create a Juju storage-pool called 'operator-storage' to define how operator"+ 504 "storage should be allocated. See https://discourse.jujucharms.com/t/getting-started/152.", 505 ) 506 } 507 } 508 509 var defaultStorageClass *string 510 for storageName, cons := range args.Storage { 511 if cons.Pool == "" && defaultStorageClass == nil { 512 // In case no storage pool is specified for charm storage, pre-load any cluster default. 513 result, err := caasBroker.GetStorageClassName(caas.UnitStorageClassLabels(args.ApplicationName, modelName)...) 514 if err != nil && !errors.IsNotFound(err) { 515 return errors.Trace(err) 516 } 517 defaultStorageClass = &result 518 } 519 if cons.Pool == "" && *defaultStorageClass == "" { 520 return errors.Errorf("storage pool for %q must be specified since there's no cluster default storage class", storageName) 521 } 522 if cons.Pool != "" { 523 sp, err := storagePoolManager.Get(cons.Pool) 524 if err != nil { 525 return errors.Trace(err) 526 } 527 if sp.Provider() != k8s.K8s_ProviderType { 528 return errors.Errorf("invalid storage provider type %q for %q", sp.Provider(), storageName) 529 } 530 } 531 } 532 } 533 534 // This check is done early so that errors deeper in the call-stack do not 535 // leave an application deployment in an unrecoverable error state. 536 if err := checkMachinePlacement(backend, args); err != nil { 537 return errors.Trace(err) 538 } 539 540 // Try to find the charm URL in state first. 541 ch, err := backend.Charm(curl) 542 if err != nil { 543 return errors.Trace(err) 544 } 545 546 if err := checkMinVersion(ch); err != nil { 547 return errors.Trace(err) 548 } 549 550 // Split out the app config from the charm config for any config 551 // passed in as a map as opposed to YAML. 552 var appConfig map[string]interface{} 553 var charmConfig map[string]string 554 if len(args.Config) > 0 { 555 if appConfig, charmConfig, err = splitApplicationAndCharmConfig(modelType, args.Config); err != nil { 556 return errors.Trace(err) 557 } 558 } 559 560 // Split out the app config from the charm config for any config 561 // passed in as YAML. 562 var charmYamlConfig string 563 appSettings := make(map[string]interface{}) 564 if len(args.ConfigYAML) > 0 { 565 if appSettings, charmYamlConfig, err = splitApplicationAndCharmConfigFromYAML(modelType, args.ConfigYAML, args.ApplicationName); err != nil { 566 return errors.Trace(err) 567 } 568 } 569 570 // Overlay any app settings in YAML with those from config map. 571 for k, v := range appConfig { 572 appSettings[k] = v 573 } 574 575 var applicationConfig *application.Config 576 schema, defaults, err := applicationConfigSchema(modelType) 577 if err != nil { 578 return errors.Trace(err) 579 } 580 applicationConfig, err = application.NewConfig(appSettings, schema, defaults) 581 if err != nil { 582 return errors.Trace(err) 583 } 584 585 var settings = make(charm.Settings) 586 if len(charmYamlConfig) > 0 { 587 settings, err = ch.Config().ParseSettingsYAML([]byte(charmYamlConfig), args.ApplicationName) 588 if err != nil { 589 return errors.Trace(err) 590 } 591 } 592 // Overlay any settings in YAML with those from config map. 593 if len(charmConfig) > 0 { 594 // Parse config in a compatible way (see function comment). 595 overrideSettings, err := parseSettingsCompatible(ch.Config(), charmConfig) 596 if err != nil { 597 return errors.Trace(err) 598 } 599 for k, v := range overrideSettings { 600 settings[k] = v 601 } 602 } 603 604 // Parse storage tags in AttachStorage. 605 if len(args.AttachStorage) > 0 && args.NumUnits != 1 { 606 return errors.Errorf("AttachStorage is non-empty, but NumUnits is %d", args.NumUnits) 607 } 608 attachStorage := make([]names.StorageTag, len(args.AttachStorage)) 609 for i, tagString := range args.AttachStorage { 610 tag, err := names.ParseStorageTag(tagString) 611 if err != nil { 612 return errors.Trace(err) 613 } 614 attachStorage[i] = tag 615 } 616 617 _, err = deployApplicationFunc(backend, DeployApplicationParams{ 618 ApplicationName: args.ApplicationName, 619 Series: args.Series, 620 Charm: stateCharm(ch), 621 Channel: csparams.Channel(args.Channel), 622 NumUnits: args.NumUnits, 623 ApplicationConfig: applicationConfig, 624 CharmConfig: settings, 625 Constraints: args.Constraints, 626 Placement: args.Placement, 627 Storage: args.Storage, 628 Devices: args.Devices, 629 AttachStorage: attachStorage, 630 EndpointBindings: args.EndpointBindings, 631 Resources: args.Resources, 632 }) 633 return errors.Trace(err) 634 } 635 636 // checkMachinePlacement does a non-exhaustive validation of any supplied 637 // placement directives. 638 // If the placement scope is for a machine, ensure that the machine exists. 639 // If the placement is for a machine or a container on an existing machine, 640 // check that the machine is not locked for series upgrade. 641 func checkMachinePlacement(backend Backend, args params.ApplicationDeploy) error { 642 errTemplate := "cannot deploy %q to machine %s" 643 app := args.ApplicationName 644 645 for _, p := range args.Placement { 646 dir := p.Directive 647 648 toProvisionedMachine := p.Scope == instance.MachineScope 649 if !toProvisionedMachine && dir == "" { 650 continue 651 } 652 653 m, err := backend.Machine(dir) 654 if err != nil { 655 if errors.IsNotFound(err) && !toProvisionedMachine { 656 continue 657 } 658 return errors.Annotatef(err, errTemplate, app, dir) 659 } 660 661 locked, err := m.IsLockedForSeriesUpgrade() 662 if locked { 663 err = errors.New("machine is locked for series upgrade") 664 } 665 if err != nil { 666 return errors.Annotatef(err, errTemplate, app, dir) 667 } 668 669 locked, err = m.IsParentLockedForSeriesUpgrade() 670 if locked { 671 err = errors.New("parent machine is locked for series upgrade") 672 } 673 if err != nil { 674 return errors.Annotatef(err, errTemplate, app, dir) 675 } 676 } 677 678 return nil 679 } 680 681 // ApplicationSetSettingsStrings updates the settings for the given application, 682 // taking the configuration from a map of strings. 683 func ApplicationSetSettingsStrings(application Application, settings map[string]string) error { 684 ch, _, err := application.Charm() 685 if err != nil { 686 return errors.Trace(err) 687 } 688 // Parse config in a compatible way (see function comment). 689 changes, err := parseSettingsCompatible(ch.Config(), settings) 690 if err != nil { 691 return errors.Trace(err) 692 } 693 return application.UpdateCharmConfig(changes) 694 } 695 696 // parseSettingsCompatible parses setting strings in a way that is 697 // compatible with the behavior before this CL based on the issue 698 // http://pad.lv/1194945. Until then setting an option to an empty 699 // string caused it to reset to the default value. We now allow 700 // empty strings as actual values, but we want to preserve the API 701 // behavior. 702 func parseSettingsCompatible(charmConfig *charm.Config, settings map[string]string) (charm.Settings, error) { 703 setSettings := map[string]string{} 704 unsetSettings := charm.Settings{} 705 // Split settings into those which set and those which unset a value. 706 for name, value := range settings { 707 if value == "" { 708 unsetSettings[name] = nil 709 continue 710 } 711 setSettings[name] = value 712 } 713 // Validate the settings. 714 changes, err := charmConfig.ParseSettingsStrings(setSettings) 715 if err != nil { 716 return nil, errors.Trace(err) 717 } 718 // Validate the unsettings and merge them into the changes. 719 unsetSettings, err = charmConfig.ValidateSettings(unsetSettings) 720 if err != nil { 721 return nil, errors.Trace(err) 722 } 723 for name := range unsetSettings { 724 changes[name] = nil 725 } 726 return changes, nil 727 } 728 729 // Update updates the application attributes, including charm URL, 730 // minimum number of units, charm config and constraints. 731 // All parameters in params.ApplicationUpdate except the application name are optional. 732 func (api *APIBase) Update(args params.ApplicationUpdate) error { 733 if err := api.checkCanWrite(); err != nil { 734 return err 735 } 736 if !args.ForceCharmURL { 737 if err := api.check.ChangeAllowed(); err != nil { 738 return errors.Trace(err) 739 } 740 } 741 app, err := api.backend.Application(args.ApplicationName) 742 if err != nil { 743 return errors.Trace(err) 744 } 745 // Set the charm for the given application. 746 if args.CharmURL != "" { 747 // For now we do not support changing the channel through Update(). 748 // TODO(ericsnow) Support it? 749 channel := app.Channel() 750 if err = api.applicationSetCharm( 751 args.ApplicationName, 752 app, 753 args.CharmURL, 754 channel, 755 nil, // charm settings (strings map) 756 "", // charm settings (YAML) 757 args.ForceSeries, 758 args.ForceCharmURL, 759 args.Force, 760 nil, // resource IDs 761 nil, // storage constraints 762 ); err != nil { 763 return errors.Trace(err) 764 } 765 } 766 // Set the minimum number of units for the given application. 767 if args.MinUnits != nil { 768 if err = app.SetMinUnits(*args.MinUnits); err != nil { 769 return errors.Trace(err) 770 } 771 } 772 // Set up application's settings. 773 if args.SettingsYAML != "" { 774 if err = applicationSetCharmConfigYAML(args.ApplicationName, app, args.SettingsYAML); err != nil { 775 return errors.Annotate(err, "setting configuration from YAML") 776 } 777 } else if len(args.SettingsStrings) > 0 { 778 if err = ApplicationSetSettingsStrings(app, args.SettingsStrings); err != nil { 779 return errors.Trace(err) 780 } 781 } 782 // Update application's constraints. 783 if args.Constraints != nil { 784 return app.SetConstraints(*args.Constraints) 785 } 786 return nil 787 } 788 789 // UpdateApplicationSeries updates the application series. Series for 790 // subordinates updated too. 791 func (api *APIBase) UpdateApplicationSeries(args params.UpdateSeriesArgs) (params.ErrorResults, error) { 792 if err := api.checkCanWrite(); err != nil { 793 return params.ErrorResults{}, err 794 } 795 if err := api.check.ChangeAllowed(); err != nil { 796 return params.ErrorResults{}, errors.Trace(err) 797 } 798 results := params.ErrorResults{ 799 Results: make([]params.ErrorResult, len(args.Args)), 800 } 801 for i, arg := range args.Args { 802 err := api.updateOneApplicationSeries(arg) 803 results.Results[i].Error = common.ServerError(err) 804 } 805 return results, nil 806 } 807 808 func (api *APIBase) updateOneApplicationSeries(arg params.UpdateSeriesArg) error { 809 if arg.Series == "" { 810 return ¶ms.Error{ 811 Message: "series missing from args", 812 Code: params.CodeBadRequest, 813 } 814 } 815 applicationTag, err := names.ParseApplicationTag(arg.Entity.Tag) 816 if err != nil { 817 return errors.Trace(err) 818 } 819 app, err := api.backend.Application(applicationTag.Id()) 820 if err != nil { 821 return errors.Trace(err) 822 } 823 if !app.IsPrincipal() { 824 return ¶ms.Error{ 825 Message: fmt.Sprintf("%q is a subordinate application, update-series not supported", applicationTag.Id()), 826 Code: params.CodeNotSupported, 827 } 828 } 829 if arg.Series == app.Series() { 830 return nil // no-op 831 } 832 return app.UpdateApplicationSeries(arg.Series, arg.Force) 833 } 834 835 // SetCharm sets the charm for a given for the application. 836 func (api *APIBase) SetCharm(args params.ApplicationSetCharm) error { 837 if err := api.checkCanWrite(); err != nil { 838 return err 839 } 840 // when forced units in error, don't block 841 if !args.ForceUnits { 842 if err := api.check.ChangeAllowed(); err != nil { 843 return errors.Trace(err) 844 } 845 } 846 application, err := api.backend.Application(args.ApplicationName) 847 if err != nil { 848 return errors.Trace(err) 849 } 850 if err := application.SetCharmProfile(args.CharmURL); err != nil { 851 return errors.Annotatef(err, "unable to set charm profile") 852 } 853 854 channel := csparams.Channel(args.Channel) 855 return api.applicationSetCharm( 856 args.ApplicationName, 857 application, 858 args.CharmURL, 859 channel, 860 args.ConfigSettings, 861 args.ConfigSettingsYAML, 862 args.ForceSeries, 863 args.ForceUnits, 864 args.Force, 865 args.ResourceIDs, 866 args.StorageConstraints, 867 ) 868 } 869 870 // applicationSetCharm sets the charm for the given for the application. 871 func (api *APIBase) applicationSetCharm( 872 appName string, 873 application Application, 874 url string, 875 channel csparams.Channel, 876 configSettingsStrings map[string]string, 877 configSettingsYAML string, 878 forceSeries, 879 forceUnits, 880 force bool, 881 resourceIDs map[string]string, 882 storageConstraints map[string]params.StorageConstraints, 883 ) error { 884 curl, err := charm.ParseURL(url) 885 if err != nil { 886 return errors.Trace(err) 887 } 888 sch, err := api.backend.Charm(curl) 889 if err != nil { 890 return errors.Trace(err) 891 } 892 var settings charm.Settings 893 if configSettingsYAML != "" { 894 settings, err = sch.Config().ParseSettingsYAML([]byte(configSettingsYAML), appName) 895 } else if len(configSettingsStrings) > 0 { 896 settings, err = parseSettingsCompatible(sch.Config(), configSettingsStrings) 897 } 898 if err != nil { 899 return errors.Annotate(err, "parsing config settings") 900 } 901 var stateStorageConstraints map[string]state.StorageConstraints 902 if len(storageConstraints) > 0 { 903 stateStorageConstraints = make(map[string]state.StorageConstraints) 904 for name, cons := range storageConstraints { 905 stateCons := state.StorageConstraints{Pool: cons.Pool} 906 if cons.Size != nil { 907 stateCons.Size = *cons.Size 908 } 909 if cons.Count != nil { 910 stateCons.Count = *cons.Count 911 } 912 stateStorageConstraints[name] = stateCons 913 } 914 } 915 cfg := state.SetCharmConfig{ 916 Charm: api.stateCharm(sch), 917 Channel: channel, 918 ConfigSettings: settings, 919 ForceSeries: forceSeries, 920 ForceUnits: forceUnits, 921 Force: force, 922 ResourceIDs: resourceIDs, 923 StorageConstraints: stateStorageConstraints, 924 } 925 return application.SetCharm(cfg) 926 } 927 928 // charmConfigFromGetYaml will parse a yaml produced by juju get and generate 929 // charm.Settings from it that can then be sent to the application. 930 func charmConfigFromGetYaml(yamlContents map[string]interface{}) (charm.Settings, error) { 931 onlySettings := charm.Settings{} 932 settingsMap, ok := yamlContents["settings"].(map[interface{}]interface{}) 933 if !ok { 934 return nil, errors.New("unknown format for settings") 935 } 936 937 for setting := range settingsMap { 938 s, ok := settingsMap[setting].(map[interface{}]interface{}) 939 if !ok { 940 return nil, errors.Errorf("unknown format for settings section %v", setting) 941 } 942 // some keys might not have a value, we don't care about those. 943 v, ok := s["value"] 944 if !ok { 945 continue 946 } 947 stringSetting, ok := setting.(string) 948 if !ok { 949 return nil, errors.Errorf("unexpected setting key, expected string got %T", setting) 950 } 951 onlySettings[stringSetting] = v 952 } 953 return onlySettings, nil 954 } 955 956 // applicationSetCharmConfigYAML updates the charm config for the 957 // given application, taking the configuration from a YAML string. 958 func applicationSetCharmConfigYAML(appName string, application Application, settings string) error { 959 b := []byte(settings) 960 var all map[string]interface{} 961 if err := goyaml.Unmarshal(b, &all); err != nil { 962 return errors.Annotate(err, "parsing settings data") 963 } 964 // The file is already in the right format. 965 if _, ok := all[appName]; !ok { 966 changes, err := charmConfigFromGetYaml(all) 967 if err != nil { 968 return errors.Annotate(err, "processing YAML generated by get") 969 } 970 return errors.Annotate(application.UpdateCharmConfig(changes), "updating settings with application YAML") 971 } 972 973 ch, _, err := application.Charm() 974 if err != nil { 975 return errors.Annotate(err, "obtaining charm for this application") 976 } 977 978 changes, err := ch.Config().ParseSettingsYAML(b, appName) 979 if err != nil { 980 return errors.Annotate(err, "creating config from YAML") 981 } 982 return errors.Annotate(application.UpdateCharmConfig(changes), "updating settings") 983 } 984 985 // GetCharmURL returns the charm URL the given application is 986 // running at present. 987 func (api *APIBase) GetCharmURL(args params.ApplicationGet) (params.StringResult, error) { 988 if err := api.checkCanWrite(); err != nil { 989 return params.StringResult{}, errors.Trace(err) 990 } 991 application, err := api.backend.Application(args.ApplicationName) 992 if err != nil { 993 return params.StringResult{}, errors.Trace(err) 994 } 995 charmURL, _ := application.CharmURL() 996 return params.StringResult{Result: charmURL.String()}, nil 997 } 998 999 // Set implements the server side of Application.Set. 1000 // It does not unset values that are set to an empty string. 1001 // Unset should be used for that. 1002 func (api *APIBase) Set(p params.ApplicationSet) error { 1003 if err := api.checkCanWrite(); err != nil { 1004 return err 1005 } 1006 if err := api.check.ChangeAllowed(); err != nil { 1007 return errors.Trace(err) 1008 } 1009 app, err := api.backend.Application(p.ApplicationName) 1010 if err != nil { 1011 return err 1012 } 1013 ch, _, err := app.Charm() 1014 if err != nil { 1015 return err 1016 } 1017 // Validate the settings. 1018 changes, err := ch.Config().ParseSettingsStrings(p.Options) 1019 if err != nil { 1020 return err 1021 } 1022 1023 return app.UpdateCharmConfig(changes) 1024 1025 } 1026 1027 // Unset implements the server side of Client.Unset. 1028 func (api *APIBase) Unset(p params.ApplicationUnset) error { 1029 if err := api.checkCanWrite(); err != nil { 1030 return err 1031 } 1032 if err := api.check.ChangeAllowed(); err != nil { 1033 return errors.Trace(err) 1034 } 1035 app, err := api.backend.Application(p.ApplicationName) 1036 if err != nil { 1037 return err 1038 } 1039 settings := make(charm.Settings) 1040 for _, option := range p.Options { 1041 settings[option] = nil 1042 } 1043 return app.UpdateCharmConfig(settings) 1044 } 1045 1046 // CharmRelations implements the server side of Application.CharmRelations. 1047 func (api *APIBase) CharmRelations(p params.ApplicationCharmRelations) (params.ApplicationCharmRelationsResults, error) { 1048 var results params.ApplicationCharmRelationsResults 1049 if err := api.checkCanRead(); err != nil { 1050 return results, errors.Trace(err) 1051 } 1052 1053 application, err := api.backend.Application(p.ApplicationName) 1054 if err != nil { 1055 return results, errors.Trace(err) 1056 } 1057 endpoints, err := application.Endpoints() 1058 if err != nil { 1059 return results, errors.Trace(err) 1060 } 1061 results.CharmRelations = make([]string, len(endpoints)) 1062 for i, endpoint := range endpoints { 1063 results.CharmRelations[i] = endpoint.Relation.Name 1064 } 1065 return results, nil 1066 } 1067 1068 // Expose changes the juju-managed firewall to expose any ports that 1069 // were also explicitly marked by units as open. 1070 func (api *APIBase) Expose(args params.ApplicationExpose) error { 1071 if err := api.checkCanWrite(); err != nil { 1072 return errors.Trace(err) 1073 } 1074 if err := api.check.ChangeAllowed(); err != nil { 1075 return errors.Trace(err) 1076 } 1077 app, err := api.backend.Application(args.ApplicationName) 1078 if err != nil { 1079 return errors.Trace(err) 1080 } 1081 if api.modelType == state.ModelTypeCAAS { 1082 appConfig, err := app.ApplicationConfig() 1083 if err != nil { 1084 return errors.Trace(err) 1085 } 1086 if appConfig.GetString(caas.JujuExternalHostNameKey, "") == "" { 1087 return errors.Errorf( 1088 "cannot expose a CAAS application without a %q value set, run\n"+ 1089 "juju config %s %s=<value>", caas.JujuExternalHostNameKey, args.ApplicationName, caas.JujuExternalHostNameKey) 1090 } 1091 } 1092 return app.SetExposed() 1093 } 1094 1095 // Unexpose changes the juju-managed firewall to unexpose any ports that 1096 // were also explicitly marked by units as open. 1097 func (api *APIBase) Unexpose(args params.ApplicationUnexpose) error { 1098 if err := api.checkCanWrite(); err != nil { 1099 return err 1100 } 1101 if err := api.check.ChangeAllowed(); err != nil { 1102 return errors.Trace(err) 1103 } 1104 app, err := api.backend.Application(args.ApplicationName) 1105 if err != nil { 1106 return err 1107 } 1108 return app.ClearExposed() 1109 } 1110 1111 // AddUnits adds a given number of units to an application. 1112 func (api *APIv5) AddUnits(args params.AddApplicationUnitsV5) (params.AddApplicationUnitsResults, error) { 1113 noDefinedPolicy := "" 1114 return api.APIBase.AddUnits(params.AddApplicationUnits{ 1115 ApplicationName: args.ApplicationName, 1116 NumUnits: args.NumUnits, 1117 Placement: args.Placement, 1118 Policy: noDefinedPolicy, 1119 AttachStorage: args.AttachStorage, 1120 }) 1121 } 1122 1123 // AddUnits adds a given number of units to an application. 1124 func (api *APIBase) AddUnits(args params.AddApplicationUnits) (params.AddApplicationUnitsResults, error) { 1125 if api.modelType == state.ModelTypeCAAS { 1126 return params.AddApplicationUnitsResults{}, errors.NotSupportedf("adding units on a non-container model") 1127 } 1128 if err := api.checkCanWrite(); err != nil { 1129 return params.AddApplicationUnitsResults{}, errors.Trace(err) 1130 } 1131 if err := api.check.ChangeAllowed(); err != nil { 1132 return params.AddApplicationUnitsResults{}, errors.Trace(err) 1133 } 1134 units, err := addApplicationUnits(api.backend, api.modelType, args) 1135 if err != nil { 1136 return params.AddApplicationUnitsResults{}, errors.Trace(err) 1137 } 1138 unitNames := make([]string, len(units)) 1139 for i, unit := range units { 1140 unitNames[i] = unit.UnitTag().Id() 1141 } 1142 return params.AddApplicationUnitsResults{Units: unitNames}, nil 1143 } 1144 1145 // addApplicationUnits adds a given number of units to an application. 1146 func addApplicationUnits(backend Backend, modelType state.ModelType, args params.AddApplicationUnits) ([]Unit, error) { 1147 if args.NumUnits < 1 { 1148 return nil, errors.New("must add at least one unit") 1149 } 1150 1151 assignUnits := true 1152 if modelType != state.ModelTypeIAAS { 1153 // In a CAAS model, there are no machines for 1154 // units to be assigned to. 1155 assignUnits = false 1156 if len(args.AttachStorage) > 0 { 1157 return nil, errors.Errorf( 1158 "AttachStorage may not be specified for %s models", 1159 modelType, 1160 ) 1161 } 1162 if len(args.Placement) > 1 { 1163 return nil, errors.Errorf( 1164 "only 1 placement directive is supported for %s models, got %d", 1165 modelType, 1166 len(args.Placement), 1167 ) 1168 } 1169 } 1170 1171 // Parse storage tags in AttachStorage. 1172 if len(args.AttachStorage) > 0 && args.NumUnits != 1 { 1173 return nil, errors.Errorf("AttachStorage is non-empty, but NumUnits is %d", args.NumUnits) 1174 } 1175 attachStorage := make([]names.StorageTag, len(args.AttachStorage)) 1176 for i, tagString := range args.AttachStorage { 1177 tag, err := names.ParseStorageTag(tagString) 1178 if err != nil { 1179 return nil, errors.Trace(err) 1180 } 1181 attachStorage[i] = tag 1182 } 1183 application, err := backend.Application(args.ApplicationName) 1184 if err != nil { 1185 return nil, errors.Trace(err) 1186 } 1187 return addUnits( 1188 application, 1189 args.ApplicationName, 1190 args.NumUnits, 1191 args.Placement, 1192 attachStorage, 1193 assignUnits, 1194 ) 1195 } 1196 1197 // DestroyUnits removes a given set of application units. 1198 // 1199 // NOTE(axw) this exists only for backwards compatibility, 1200 // for API facade versions 1-3; clients should prefer its 1201 // successor, DestroyUnit, below. Until all consumers have 1202 // been updated, or we bump a major version, we can't drop 1203 // this. 1204 // 1205 // TODO(axw) 2017-03-16 #1673323 1206 // Drop this in Juju 3.0. 1207 func (api *APIBase) DestroyUnits(args params.DestroyApplicationUnits) error { 1208 var errs []error 1209 entities := params.DestroyUnitsParams{ 1210 Units: make([]params.DestroyUnitParams, 0, len(args.UnitNames)), 1211 } 1212 for _, unitName := range args.UnitNames { 1213 if !names.IsValidUnit(unitName) { 1214 errs = append(errs, errors.NotValidf("unit name %q", unitName)) 1215 continue 1216 } 1217 entities.Units = append(entities.Units, params.DestroyUnitParams{ 1218 UnitTag: names.NewUnitTag(unitName).String(), 1219 }) 1220 } 1221 results, err := api.DestroyUnit(entities) 1222 if err != nil { 1223 return errors.Trace(err) 1224 } 1225 for _, result := range results.Results { 1226 if result.Error != nil { 1227 errs = append(errs, result.Error) 1228 } 1229 } 1230 return common.DestroyErr("units", args.UnitNames, errs) 1231 } 1232 1233 // DestroyUnit removes a given set of application units. 1234 // 1235 // NOTE(axw) this provides backwards compatibility for facade version 4. 1236 func (api *APIv4) DestroyUnit(args params.Entities) (params.DestroyUnitResults, error) { 1237 v5args := params.DestroyUnitsParams{ 1238 Units: make([]params.DestroyUnitParams, len(args.Entities)), 1239 } 1240 for i, arg := range args.Entities { 1241 v5args.Units[i].UnitTag = arg.Tag 1242 } 1243 return api.APIBase.DestroyUnit(v5args) 1244 } 1245 1246 // DestroyUnit removes a given set of application units. 1247 func (api *APIBase) DestroyUnit(args params.DestroyUnitsParams) (params.DestroyUnitResults, error) { 1248 if api.modelType == state.ModelTypeCAAS { 1249 return params.DestroyUnitResults{}, errors.NotSupportedf("removing units on a non-container model") 1250 } 1251 if err := api.checkCanWrite(); err != nil { 1252 return params.DestroyUnitResults{}, errors.Trace(err) 1253 } 1254 if err := api.check.RemoveAllowed(); err != nil { 1255 return params.DestroyUnitResults{}, errors.Trace(err) 1256 } 1257 destroyUnit := func(arg params.DestroyUnitParams) (*params.DestroyUnitInfo, error) { 1258 unitTag, err := names.ParseUnitTag(arg.UnitTag) 1259 if err != nil { 1260 return nil, errors.Trace(err) 1261 } 1262 name := unitTag.Id() 1263 unit, err := api.backend.Unit(name) 1264 if errors.IsNotFound(err) { 1265 return nil, errors.Errorf("unit %q does not exist", name) 1266 } else if err != nil { 1267 return nil, errors.Trace(err) 1268 } 1269 if !unit.IsPrincipal() { 1270 return nil, errors.Errorf("unit %q is a subordinate", name) 1271 } 1272 var info params.DestroyUnitInfo 1273 storage, err := storagecommon.UnitStorage(api.storageAccess, unit.UnitTag()) 1274 if err != nil { 1275 return nil, errors.Trace(err) 1276 } 1277 1278 if arg.DestroyStorage { 1279 for _, s := range storage { 1280 info.DestroyedStorage = append( 1281 info.DestroyedStorage, 1282 params.Entity{s.StorageTag().String()}, 1283 ) 1284 } 1285 } else { 1286 info.DestroyedStorage, info.DetachedStorage, err = storagecommon.ClassifyDetachedStorage( 1287 api.storageAccess.VolumeAccess(), api.storageAccess.FilesystemAccess(), storage, 1288 ) 1289 if err != nil { 1290 return nil, errors.Trace(err) 1291 } 1292 } 1293 op := unit.DestroyOperation() 1294 op.DestroyStorage = arg.DestroyStorage 1295 if err := api.backend.ApplyOperation(op); err != nil { 1296 return nil, errors.Trace(err) 1297 } 1298 return &info, nil 1299 } 1300 results := make([]params.DestroyUnitResult, len(args.Units)) 1301 for i, entity := range args.Units { 1302 info, err := destroyUnit(entity) 1303 if err != nil { 1304 results[i].Error = common.ServerError(err) 1305 continue 1306 } 1307 results[i].Info = info 1308 } 1309 return params.DestroyUnitResults{results}, nil 1310 } 1311 1312 // Destroy destroys a given application, local or remote. 1313 // 1314 // NOTE(axw) this exists only for backwards compatibility, 1315 // for API facade versions 1-3; clients should prefer its 1316 // successor, DestroyApplication, below. Until all consumers 1317 // have been updated, or we bump a major version, we can't 1318 // drop this. 1319 // 1320 // TODO(axw) 2017-03-16 #1673323 1321 // Drop this in Juju 3.0. 1322 func (api *APIBase) Destroy(in params.ApplicationDestroy) error { 1323 if !names.IsValidApplication(in.ApplicationName) { 1324 return errors.NotValidf("application name %q", in.ApplicationName) 1325 } 1326 args := params.DestroyApplicationsParams{ 1327 Applications: []params.DestroyApplicationParams{{ 1328 ApplicationTag: names.NewApplicationTag(in.ApplicationName).String(), 1329 }}, 1330 } 1331 results, err := api.DestroyApplication(args) 1332 if err != nil { 1333 return errors.Trace(err) 1334 } 1335 if err := results.Results[0].Error; err != nil { 1336 return common.ServerError(err) 1337 } 1338 return nil 1339 } 1340 1341 // DestroyApplication removes a given set of applications. 1342 // 1343 // NOTE(axw) this provides backwards compatibility for facade version 4. 1344 func (api *APIv4) DestroyApplication(args params.Entities) (params.DestroyApplicationResults, error) { 1345 v5args := params.DestroyApplicationsParams{ 1346 Applications: make([]params.DestroyApplicationParams, len(args.Entities)), 1347 } 1348 for i, arg := range args.Entities { 1349 v5args.Applications[i].ApplicationTag = arg.Tag 1350 } 1351 return api.APIBase.DestroyApplication(v5args) 1352 } 1353 1354 // DestroyApplication removes a given set of applications. 1355 func (api *APIBase) DestroyApplication(args params.DestroyApplicationsParams) (params.DestroyApplicationResults, error) { 1356 if err := api.checkCanWrite(); err != nil { 1357 return params.DestroyApplicationResults{}, err 1358 } 1359 if err := api.check.RemoveAllowed(); err != nil { 1360 return params.DestroyApplicationResults{}, errors.Trace(err) 1361 } 1362 destroyApp := func(arg params.DestroyApplicationParams) (*params.DestroyApplicationInfo, error) { 1363 tag, err := names.ParseApplicationTag(arg.ApplicationTag) 1364 if err != nil { 1365 return nil, err 1366 } 1367 var info params.DestroyApplicationInfo 1368 app, err := api.backend.Application(tag.Id()) 1369 if err != nil { 1370 return nil, err 1371 } 1372 units, err := app.AllUnits() 1373 if err != nil { 1374 return nil, err 1375 } 1376 storageSeen := names.NewSet() 1377 for _, unit := range units { 1378 info.DestroyedUnits = append( 1379 info.DestroyedUnits, 1380 params.Entity{unit.UnitTag().String()}, 1381 ) 1382 storage, err := storagecommon.UnitStorage(api.storageAccess, unit.UnitTag()) 1383 if err != nil { 1384 return nil, err 1385 } 1386 1387 // Filter out storage we've already seen. Shared 1388 // storage may be attached to multiple units. 1389 var unseen []state.StorageInstance 1390 for _, stor := range storage { 1391 storageTag := stor.StorageTag() 1392 if storageSeen.Contains(storageTag) { 1393 continue 1394 } 1395 storageSeen.Add(storageTag) 1396 unseen = append(unseen, stor) 1397 } 1398 storage = unseen 1399 1400 if arg.DestroyStorage { 1401 for _, s := range storage { 1402 info.DestroyedStorage = append( 1403 info.DestroyedStorage, 1404 params.Entity{s.StorageTag().String()}, 1405 ) 1406 } 1407 } else { 1408 destroyed, detached, err := storagecommon.ClassifyDetachedStorage( 1409 api.storageAccess.VolumeAccess(), api.storageAccess.FilesystemAccess(), storage, 1410 ) 1411 if err != nil { 1412 return nil, err 1413 } 1414 info.DestroyedStorage = append(info.DestroyedStorage, destroyed...) 1415 info.DetachedStorage = append(info.DetachedStorage, detached...) 1416 } 1417 } 1418 op := app.DestroyOperation() 1419 op.DestroyStorage = arg.DestroyStorage 1420 if err := api.backend.ApplyOperation(op); err != nil { 1421 return nil, err 1422 } 1423 return &info, nil 1424 } 1425 results := make([]params.DestroyApplicationResult, len(args.Applications)) 1426 for i, arg := range args.Applications { 1427 info, err := destroyApp(arg) 1428 if err != nil { 1429 results[i].Error = common.ServerError(err) 1430 continue 1431 } 1432 results[i].Info = info 1433 } 1434 return params.DestroyApplicationResults{results}, nil 1435 } 1436 1437 // DestroyConsumedApplications removes a given set of consumed (remote) applications. 1438 func (api *APIBase) DestroyConsumedApplications(args params.DestroyConsumedApplicationsParams) (params.ErrorResults, error) { 1439 if err := api.checkCanWrite(); err != nil { 1440 return params.ErrorResults{}, err 1441 } 1442 if err := api.check.RemoveAllowed(); err != nil { 1443 return params.ErrorResults{}, errors.Trace(err) 1444 } 1445 results := make([]params.ErrorResult, len(args.Applications)) 1446 for i, arg := range args.Applications { 1447 appTag, err := names.ParseApplicationTag(arg.ApplicationTag) 1448 if err != nil { 1449 results[i].Error = common.ServerError(err) 1450 continue 1451 } 1452 app, err := api.backend.RemoteApplication(appTag.Id()) 1453 if err != nil { 1454 results[i].Error = common.ServerError(err) 1455 continue 1456 } 1457 err = app.Destroy() 1458 if err != nil { 1459 results[i].Error = common.ServerError(err) 1460 continue 1461 } 1462 } 1463 return params.ErrorResults{results}, nil 1464 } 1465 1466 // ScaleApplications isn't on the V7 API. 1467 func (u *APIv7) ScaleApplications(_, _ struct{}) {} 1468 1469 // ScaleApplications scales the specified application to the requested number of units. 1470 func (api *APIBase) ScaleApplications(args params.ScaleApplicationsParams) (params.ScaleApplicationResults, error) { 1471 if api.modelType != state.ModelTypeCAAS { 1472 return params.ScaleApplicationResults{}, errors.NotSupportedf("scaling applications on a non-container model") 1473 } 1474 if err := api.checkCanWrite(); err != nil { 1475 return params.ScaleApplicationResults{}, errors.Trace(err) 1476 } 1477 scaleApplication := func(arg params.ScaleApplicationParams) (*params.ScaleApplicationInfo, error) { 1478 if arg.Scale == 0 && arg.ScaleChange == 0 { 1479 return nil, errors.NotValidf("scale of 0") 1480 } else if arg.Scale < 0 && arg.ScaleChange == 0 { 1481 return nil, errors.NotValidf("scale < 0") 1482 } else if arg.Scale != 0 && arg.ScaleChange != 0 { 1483 return nil, errors.NotValidf("requesting both scale and scale-change") 1484 } 1485 1486 appTag, err := names.ParseApplicationTag(arg.ApplicationTag) 1487 if err != nil { 1488 return nil, errors.Trace(err) 1489 } 1490 name := appTag.Id() 1491 app, err := api.backend.Application(name) 1492 if errors.IsNotFound(err) { 1493 return nil, errors.Errorf("application %q does not exist", name) 1494 } else if err != nil { 1495 return nil, errors.Trace(err) 1496 } 1497 var info params.ScaleApplicationInfo 1498 if arg.ScaleChange != 0 { 1499 newScale, err := app.ChangeScale(arg.ScaleChange) 1500 if err != nil { 1501 return nil, errors.Trace(err) 1502 } 1503 info.Scale = newScale 1504 } else { 1505 if err := app.Scale(arg.Scale); err != nil { 1506 return nil, errors.Trace(err) 1507 } 1508 info.Scale = arg.Scale 1509 } 1510 return &info, nil 1511 } 1512 results := make([]params.ScaleApplicationResult, len(args.Applications)) 1513 for i, entity := range args.Applications { 1514 info, err := scaleApplication(entity) 1515 if err != nil { 1516 results[i].Error = common.ServerError(err) 1517 continue 1518 } 1519 results[i].Info = info 1520 } 1521 return params.ScaleApplicationResults{results}, nil 1522 } 1523 1524 // GetConstraints returns the constraints for a given application. 1525 func (api *APIBase) GetConstraints(args params.Entities) (params.ApplicationGetConstraintsResults, error) { 1526 if err := api.checkCanRead(); err != nil { 1527 return params.ApplicationGetConstraintsResults{}, errors.Trace(err) 1528 } 1529 results := params.ApplicationGetConstraintsResults{ 1530 Results: make([]params.ApplicationConstraint, len(args.Entities)), 1531 } 1532 for i, arg := range args.Entities { 1533 cons, err := api.getConstraints(arg.Tag) 1534 results.Results[i].Constraints = cons 1535 results.Results[i].Error = common.ServerError(err) 1536 } 1537 return results, nil 1538 } 1539 1540 func (api *APIBase) getConstraints(entity string) (constraints.Value, error) { 1541 tag, err := names.ParseTag(entity) 1542 if err != nil { 1543 return constraints.Value{}, err 1544 } 1545 switch kind := tag.Kind(); kind { 1546 case names.ApplicationTagKind: 1547 app, err := api.backend.Application(tag.Id()) 1548 if err != nil { 1549 return constraints.Value{}, err 1550 } 1551 return app.Constraints() 1552 default: 1553 return constraints.Value{}, errors.Errorf("unexpected tag type, expected application, got %s", kind) 1554 } 1555 } 1556 1557 // SetConstraints sets the constraints for a given application. 1558 func (api *APIBase) SetConstraints(args params.SetConstraints) error { 1559 if err := api.checkCanWrite(); err != nil { 1560 return err 1561 } 1562 if err := api.check.ChangeAllowed(); err != nil { 1563 return errors.Trace(err) 1564 } 1565 app, err := api.backend.Application(args.ApplicationName) 1566 if err != nil { 1567 return err 1568 } 1569 return app.SetConstraints(args.Constraints) 1570 } 1571 1572 // AddRelation adds a relation between the specified endpoints and returns the relation info. 1573 func (api *APIBase) AddRelation(args params.AddRelation) (_ params.AddRelationResults, err error) { 1574 var rel Relation 1575 defer func() { 1576 if err != nil && rel != nil { 1577 if err := rel.Destroy(); err != nil { 1578 logger.Errorf("cannot destroy aborted relation %q: %v", rel.Tag().Id(), err) 1579 } 1580 } 1581 }() 1582 1583 if err := api.check.ChangeAllowed(); err != nil { 1584 return params.AddRelationResults{}, errors.Trace(err) 1585 } 1586 if err := api.checkCanWrite(); err != nil { 1587 return params.AddRelationResults{}, errors.Trace(err) 1588 } 1589 1590 // Validate any CIDRs. 1591 for _, cidr := range args.ViaCIDRs { 1592 if _, _, err := net.ParseCIDR(cidr); err != nil { 1593 return params.AddRelationResults{}, errors.Trace(err) 1594 } 1595 if cidr == "0.0.0.0/0" { 1596 return params.AddRelationResults{}, errors.Errorf("CIDR %q not allowed", cidr) 1597 } 1598 } 1599 1600 inEps, err := api.backend.InferEndpoints(args.Endpoints...) 1601 if err != nil { 1602 return params.AddRelationResults{}, errors.Trace(err) 1603 } 1604 if rel, err = api.backend.AddRelation(inEps...); err != nil { 1605 return params.AddRelationResults{}, errors.Trace(err) 1606 } 1607 if _, err := api.backend.SaveEgressNetworks(rel.Tag().Id(), args.ViaCIDRs); err != nil { 1608 return params.AddRelationResults{}, errors.Trace(err) 1609 } 1610 1611 outEps := make(map[string]params.CharmRelation) 1612 for _, inEp := range inEps { 1613 outEp, err := rel.Endpoint(inEp.ApplicationName) 1614 if err != nil { 1615 return params.AddRelationResults{}, errors.Trace(err) 1616 } 1617 outEps[inEp.ApplicationName] = params.CharmRelation{ 1618 Name: outEp.Relation.Name, 1619 Role: string(outEp.Relation.Role), 1620 Interface: outEp.Relation.Interface, 1621 Optional: outEp.Relation.Optional, 1622 Limit: outEp.Relation.Limit, 1623 Scope: string(outEp.Relation.Scope), 1624 } 1625 } 1626 return params.AddRelationResults{Endpoints: outEps}, nil 1627 } 1628 1629 // DestroyRelation removes the relation between the 1630 // specified endpoints or an id. 1631 func (api *APIBase) DestroyRelation(args params.DestroyRelation) (err error) { 1632 if err := api.checkCanWrite(); err != nil { 1633 return err 1634 } 1635 if err := api.check.RemoveAllowed(); err != nil { 1636 return errors.Trace(err) 1637 } 1638 var ( 1639 rel Relation 1640 eps []state.Endpoint 1641 ) 1642 if len(args.Endpoints) > 0 { 1643 eps, err = api.backend.InferEndpoints(args.Endpoints...) 1644 if err != nil { 1645 return err 1646 } 1647 rel, err = api.backend.EndpointsRelation(eps...) 1648 } else { 1649 rel, err = api.backend.Relation(args.RelationId) 1650 } 1651 if err != nil { 1652 return err 1653 } 1654 return rel.Destroy() 1655 } 1656 1657 // SetRelationsSuspended sets the suspended status of the specified relations. 1658 func (api *APIBase) SetRelationsSuspended(args params.RelationSuspendedArgs) (params.ErrorResults, error) { 1659 var statusResults params.ErrorResults 1660 if err := api.checkCanWrite(); err != nil { 1661 return statusResults, errors.Trace(err) 1662 } 1663 if err := api.check.ChangeAllowed(); err != nil { 1664 return statusResults, errors.Trace(err) 1665 } 1666 1667 changeOne := func(arg params.RelationSuspendedArg) error { 1668 rel, err := api.backend.Relation(arg.RelationId) 1669 if err != nil { 1670 return errors.Trace(err) 1671 } 1672 if rel.Suspended() == arg.Suspended { 1673 return nil 1674 } 1675 _, err = api.backend.OfferConnectionForRelation(rel.Tag().Id()) 1676 if errors.IsNotFound(err) { 1677 return errors.Errorf("cannot set suspend status for %q which is not associated with an offer", rel.Tag().Id()) 1678 } 1679 message := arg.Message 1680 if !arg.Suspended { 1681 message = "" 1682 } 1683 err = rel.SetSuspended(arg.Suspended, message) 1684 if err != nil { 1685 return errors.Trace(err) 1686 } 1687 1688 statusValue := status.Joining 1689 if arg.Suspended { 1690 statusValue = status.Suspending 1691 } 1692 return rel.SetStatus(status.StatusInfo{ 1693 Status: statusValue, 1694 Message: arg.Message, 1695 }) 1696 } 1697 results := make([]params.ErrorResult, len(args.Args)) 1698 for i, arg := range args.Args { 1699 err := changeOne(arg) 1700 results[i].Error = common.ServerError(err) 1701 } 1702 statusResults.Results = results 1703 return statusResults, nil 1704 } 1705 1706 // Consume adds remote applications to the model without creating any 1707 // relations. 1708 func (api *APIBase) Consume(args params.ConsumeApplicationArgs) (params.ErrorResults, error) { 1709 var consumeResults params.ErrorResults 1710 if err := api.checkCanWrite(); err != nil { 1711 return consumeResults, errors.Trace(err) 1712 } 1713 if err := api.check.ChangeAllowed(); err != nil { 1714 return consumeResults, errors.Trace(err) 1715 } 1716 1717 results := make([]params.ErrorResult, len(args.Args)) 1718 for i, arg := range args.Args { 1719 err := api.consumeOne(arg) 1720 results[i].Error = common.ServerError(err) 1721 } 1722 consumeResults.Results = results 1723 return consumeResults, nil 1724 } 1725 1726 func (api *APIBase) consumeOne(arg params.ConsumeApplicationArg) error { 1727 sourceModelTag, err := names.ParseModelTag(arg.SourceModelTag) 1728 if err != nil { 1729 return errors.Trace(err) 1730 } 1731 1732 // Maybe save the details of the controller hosting the offer. 1733 if arg.ControllerInfo != nil { 1734 controllerTag, err := names.ParseControllerTag(arg.ControllerInfo.ControllerTag) 1735 if err != nil { 1736 return errors.Trace(err) 1737 } 1738 // Only save controller details if the offer comes from 1739 // a different controller. 1740 if controllerTag.Id() != api.backend.ControllerTag().Id() { 1741 if _, err = api.backend.SaveController(crossmodel.ControllerInfo{ 1742 ControllerTag: controllerTag, 1743 Alias: arg.ControllerInfo.Alias, 1744 Addrs: arg.ControllerInfo.Addrs, 1745 CACert: arg.ControllerInfo.CACert, 1746 }, sourceModelTag.Id()); err != nil { 1747 return errors.Trace(err) 1748 } 1749 } 1750 } 1751 1752 appName := arg.ApplicationAlias 1753 if appName == "" { 1754 appName = arg.OfferName 1755 } 1756 _, err = api.saveRemoteApplication(sourceModelTag, appName, arg.ApplicationOfferDetails, arg.Macaroon) 1757 return err 1758 } 1759 1760 // saveRemoteApplication saves the details of the specified remote application and its endpoints 1761 // to the state model so relations to the remote application can be created. 1762 func (api *APIBase) saveRemoteApplication( 1763 sourceModelTag names.ModelTag, 1764 applicationName string, 1765 offer params.ApplicationOfferDetails, 1766 mac *macaroon.Macaroon, 1767 ) (RemoteApplication, error) { 1768 remoteEps := make([]charm.Relation, len(offer.Endpoints)) 1769 for j, ep := range offer.Endpoints { 1770 remoteEps[j] = charm.Relation{ 1771 Name: ep.Name, 1772 Role: ep.Role, 1773 Interface: ep.Interface, 1774 } 1775 } 1776 1777 remoteSpaces := make([]*environs.ProviderSpaceInfo, len(offer.Spaces)) 1778 for i, space := range offer.Spaces { 1779 remoteSpaces[i] = providerSpaceInfoFromParams(space) 1780 } 1781 1782 // If the a remote application with the same name and endpoints from the same 1783 // source model already exists, we will use that one. 1784 remoteApp, err := api.maybeUpdateExistingApplicationEndpoints(applicationName, sourceModelTag, remoteEps) 1785 if err == nil { 1786 return remoteApp, nil 1787 } else if !errors.IsNotFound(err) { 1788 return nil, errors.Trace(err) 1789 } 1790 1791 return api.backend.AddRemoteApplication(state.AddRemoteApplicationParams{ 1792 Name: applicationName, 1793 OfferUUID: offer.OfferUUID, 1794 URL: offer.OfferURL, 1795 SourceModel: sourceModelTag, 1796 Endpoints: remoteEps, 1797 Spaces: remoteSpaces, 1798 Bindings: offer.Bindings, 1799 Macaroon: mac, 1800 }) 1801 } 1802 1803 // providerSpaceInfoFromParams converts a params.RemoteSpace to the 1804 // equivalent ProviderSpaceInfo. 1805 func providerSpaceInfoFromParams(space params.RemoteSpace) *environs.ProviderSpaceInfo { 1806 result := &environs.ProviderSpaceInfo{ 1807 CloudType: space.CloudType, 1808 ProviderAttributes: space.ProviderAttributes, 1809 SpaceInfo: network.SpaceInfo{ 1810 Name: space.Name, 1811 ProviderId: network.Id(space.ProviderId), 1812 }, 1813 } 1814 for _, subnet := range space.Subnets { 1815 resultSubnet := network.SubnetInfo{ 1816 CIDR: subnet.CIDR, 1817 ProviderId: network.Id(subnet.ProviderId), 1818 ProviderNetworkId: network.Id(subnet.ProviderNetworkId), 1819 SpaceProviderId: network.Id(subnet.ProviderSpaceId), 1820 VLANTag: subnet.VLANTag, 1821 AvailabilityZones: subnet.Zones, 1822 } 1823 result.Subnets = append(result.Subnets, resultSubnet) 1824 } 1825 return result 1826 } 1827 1828 // maybeUpdateExistingApplicationEndpoints looks for a remote application with the 1829 // specified name and source model tag and tries to update its endpoints with the 1830 // new ones specified. If the endpoints are compatible, the newly updated remote 1831 // application is returned. 1832 func (api *APIBase) maybeUpdateExistingApplicationEndpoints( 1833 applicationName string, sourceModelTag names.ModelTag, remoteEps []charm.Relation, 1834 ) (RemoteApplication, error) { 1835 existingRemoteApp, err := api.backend.RemoteApplication(applicationName) 1836 if err != nil { 1837 return nil, errors.Trace(err) 1838 } 1839 if err != nil && !errors.IsNotFound(err) { 1840 return nil, errors.Trace(err) 1841 } 1842 if existingRemoteApp.SourceModel().Id() != sourceModelTag.Id() { 1843 return nil, errors.AlreadyExistsf("remote application called %q from a different model", applicationName) 1844 } 1845 newEpsMap := make(map[charm.Relation]bool) 1846 for _, ep := range remoteEps { 1847 newEpsMap[ep] = true 1848 } 1849 existingEps, err := existingRemoteApp.Endpoints() 1850 if err != nil { 1851 return nil, errors.Trace(err) 1852 } 1853 maybeSameEndpoints := len(newEpsMap) == len(existingEps) 1854 existingEpsByName := make(map[string]charm.Relation) 1855 for _, ep := range existingEps { 1856 existingEpsByName[ep.Name] = ep.Relation 1857 delete(newEpsMap, ep.Relation) 1858 } 1859 sameEndpoints := maybeSameEndpoints && len(newEpsMap) == 0 1860 if sameEndpoints { 1861 return existingRemoteApp, nil 1862 } 1863 1864 // Gather the new endpoints. All new endpoints passed to AddEndpoints() 1865 // below must not have the same name as an existing endpoint. 1866 var newEps []charm.Relation 1867 for ep := range newEpsMap { 1868 // See if we are attempting to update endpoints with the same name but 1869 // different relation data. 1870 if existing, ok := existingEpsByName[ep.Name]; ok && existing != ep { 1871 return nil, errors.Errorf("conflicting endpoint %v", ep.Name) 1872 } 1873 newEps = append(newEps, ep) 1874 } 1875 1876 if len(newEps) > 0 { 1877 // Update the existing remote app to have the new, additional endpoints. 1878 if err := existingRemoteApp.AddEndpoints(newEps); err != nil { 1879 return nil, errors.Trace(err) 1880 } 1881 } 1882 return existingRemoteApp, nil 1883 } 1884 1885 // Mask the new methods from the V4 API. The API reflection code in 1886 // rpc/rpcreflect/type.go:newMethod skips 2-argument methods, so this 1887 // removes the method as far as the RPC machinery is concerned. 1888 1889 // UpdateApplicationSeries isn't on the V4 API. 1890 func (u *APIv4) UpdateApplicationSeries(_, _ struct{}) {} 1891 1892 // GetConfig isn't on the V4 API. 1893 func (u *APIv4) GetConfig(_, _ struct{}) {} 1894 1895 // GetConstraints returns the v4 implementation of GetConstraints. 1896 func (api *APIv4) GetConstraints(args params.GetApplicationConstraints) (params.GetConstraintsResults, error) { 1897 if err := api.checkCanRead(); err != nil { 1898 return params.GetConstraintsResults{}, errors.Trace(err) 1899 } 1900 app, err := api.backend.Application(args.ApplicationName) 1901 if err != nil { 1902 return params.GetConstraintsResults{}, errors.Trace(err) 1903 } 1904 cons, err := app.Constraints() 1905 return params.GetConstraintsResults{cons}, errors.Trace(err) 1906 } 1907 1908 // Mask the new methods from the v4 and v5 API. The API reflection code in 1909 // rpc/rpcreflect/type.go:newMethod skips 2-argument methods, so this 1910 // removes the method as far as the RPC machinery is concerned. 1911 // 1912 // Since the v4 builds on v5, we can just make the methods unavailable on v5 1913 // and they will also be unavailable on v4. 1914 1915 // CharmConfig isn't on the v5 API. 1916 func (u *APIv5) CharmConfig(_, _ struct{}) {} 1917 1918 // CharmConfig is a shim to GetConfig on APIv5. It returns only charm config. 1919 // Version 8 and below accept params.Entities, where later versions must accept 1920 // a model generation 1921 func (api *APIv8) CharmConfig(args params.Entities) (params.ApplicationGetConfigResults, error) { 1922 return api.GetConfig(args) 1923 } 1924 1925 // CharmConfig returns charm config for the input list of applications and 1926 // model generations. 1927 func (api *APIBase) CharmConfig(args params.ApplicationGetArgs) (params.ApplicationGetConfigResults, error) { 1928 if err := api.checkCanRead(); err != nil { 1929 return params.ApplicationGetConfigResults{}, err 1930 } 1931 results := params.ApplicationGetConfigResults{ 1932 Results: make([]params.ConfigResult, len(args.Args)), 1933 } 1934 for i, arg := range args.Args { 1935 config, err := api.getCharmConfig(arg.ApplicationName) 1936 results.Results[i].Config = config 1937 results.Results[i].Error = common.ServerError(err) 1938 } 1939 return results, nil 1940 } 1941 1942 // GetConfig returns the charm config for each of the input applications. 1943 func (api *APIBase) GetConfig(args params.Entities) (params.ApplicationGetConfigResults, error) { 1944 if err := api.checkCanRead(); err != nil { 1945 return params.ApplicationGetConfigResults{}, err 1946 } 1947 results := params.ApplicationGetConfigResults{ 1948 Results: make([]params.ConfigResult, len(args.Entities)), 1949 } 1950 for i, arg := range args.Entities { 1951 tag, err := names.ParseTag(arg.Tag) 1952 if err != nil { 1953 results.Results[i].Error = common.ServerError(err) 1954 continue 1955 } 1956 if tag.Kind() != names.ApplicationTagKind { 1957 results.Results[i].Error = common.ServerError( 1958 errors.Errorf("unexpected tag type, expected application, got %s", tag.Kind())) 1959 continue 1960 } 1961 1962 config, err := api.getCharmConfig(tag.Id()) 1963 results.Results[i].Config = config 1964 results.Results[i].Error = common.ServerError(err) 1965 } 1966 return results, nil 1967 } 1968 1969 func (api *APIBase) getCharmConfig(appName string) (map[string]interface{}, error) { 1970 app, err := api.backend.Application(appName) 1971 if err != nil { 1972 return nil, err 1973 } 1974 settings, err := app.CharmConfig() 1975 if err != nil { 1976 return nil, err 1977 } 1978 ch, _, err := app.Charm() 1979 if err != nil { 1980 return nil, err 1981 } 1982 return describe(settings, ch.Config()), nil 1983 } 1984 1985 // SetApplicationsConfig isn't on the v5 API. 1986 func (u *APIv5) SetApplicationsConfig(_, _ struct{}) {} 1987 1988 // SetApplicationsConfig implements the server side of Application.SetApplicationsConfig. 1989 // It does not unset values that are set to an empty string. 1990 // Unset should be used for that. 1991 func (api *APIBase) SetApplicationsConfig(args params.ApplicationConfigSetArgs) (params.ErrorResults, error) { 1992 var result params.ErrorResults 1993 if err := api.checkCanWrite(); err != nil { 1994 return result, errors.Trace(err) 1995 } 1996 if err := api.check.ChangeAllowed(); err != nil { 1997 return result, errors.Trace(err) 1998 } 1999 result.Results = make([]params.ErrorResult, len(args.Args)) 2000 for i, arg := range args.Args { 2001 err := api.setApplicationConfig(arg) 2002 result.Results[i].Error = common.ServerError(err) 2003 } 2004 return result, nil 2005 } 2006 2007 func (api *APIBase) setApplicationConfig(arg params.ApplicationConfigSet) error { 2008 app, err := api.backend.Application(arg.ApplicationName) 2009 if err != nil { 2010 return errors.Trace(err) 2011 } 2012 2013 appConfigAttrs, charmConfig, err := splitApplicationAndCharmConfig(api.modelType, arg.Config) 2014 if err != nil { 2015 return errors.Trace(err) 2016 } 2017 schema, defaults, err := applicationConfigSchema(api.modelType) 2018 if err != nil { 2019 return errors.Trace(err) 2020 } 2021 2022 if len(appConfigAttrs) > 0 { 2023 if err := app.UpdateApplicationConfig(appConfigAttrs, nil, schema, defaults); err != nil { 2024 return errors.Annotate(err, "updating application config values") 2025 } 2026 } 2027 if len(charmConfig) > 0 { 2028 ch, _, err := app.Charm() 2029 if err != nil { 2030 return err 2031 } 2032 // Validate the charm and application config. 2033 charmConfigChanges, err := ch.Config().ParseSettingsStrings(charmConfig) 2034 if err != nil { 2035 return err 2036 } 2037 if err := app.UpdateCharmConfig(charmConfigChanges); err != nil { 2038 return errors.Annotate(err, "updating application charm settings") 2039 } 2040 } 2041 return nil 2042 } 2043 2044 // UnsetApplicationsConfig isn't on the v5 API. 2045 func (u *APIv5) UnsetApplicationsConfig(_, _ struct{}) {} 2046 2047 // UnsetApplicationsConfig implements the server side of Application.UnsetApplicationsConfig. 2048 func (api *APIBase) UnsetApplicationsConfig(args params.ApplicationConfigUnsetArgs) (params.ErrorResults, error) { 2049 var result params.ErrorResults 2050 if err := api.checkCanWrite(); err != nil { 2051 return result, errors.Trace(err) 2052 } 2053 if err := api.check.ChangeAllowed(); err != nil { 2054 return result, errors.Trace(err) 2055 } 2056 result.Results = make([]params.ErrorResult, len(args.Args)) 2057 for i, arg := range args.Args { 2058 err := api.unsetApplicationConfig(arg) 2059 result.Results[i].Error = common.ServerError(err) 2060 } 2061 return result, nil 2062 } 2063 2064 func (api *APIBase) unsetApplicationConfig(arg params.ApplicationUnset) error { 2065 app, err := api.backend.Application(arg.ApplicationName) 2066 if err != nil { 2067 return errors.Trace(err) 2068 } 2069 2070 schema, defaults, err := applicationConfigSchema(api.modelType) 2071 if err != nil { 2072 return errors.Trace(err) 2073 } 2074 appConfigFields := application.KnownConfigKeys(schema) 2075 2076 var appConfigKeys []string 2077 charmSettings := make(charm.Settings) 2078 for _, name := range arg.Options { 2079 if appConfigFields.Contains(name) { 2080 appConfigKeys = append(appConfigKeys, name) 2081 } else { 2082 charmSettings[name] = nil 2083 } 2084 } 2085 2086 if len(appConfigKeys) > 0 { 2087 if err := app.UpdateApplicationConfig(nil, appConfigKeys, schema, defaults); err != nil { 2088 return errors.Annotate(err, "updating application config values") 2089 } 2090 } 2091 if len(charmSettings) > 0 { 2092 if err := app.UpdateCharmConfig(charmSettings); err != nil { 2093 return errors.Annotate(err, "updating application charm settings") 2094 } 2095 } 2096 return nil 2097 } 2098 2099 // ResolveUnitErrors isn't on the v5 API. 2100 func (u *APIv5) ResolveUnitErrors(_, _ struct{}) {} 2101 2102 // ResolveUnitErrors marks errors on the specified units as resolved. 2103 func (api *APIBase) ResolveUnitErrors(p params.UnitsResolved) (params.ErrorResults, error) { 2104 if p.All { 2105 unitsWithErrors, err := api.backend.UnitsInError() 2106 if err != nil { 2107 return params.ErrorResults{}, errors.Trace(err) 2108 } 2109 for _, u := range unitsWithErrors { 2110 if err := u.Resolve(p.Retry); err != nil { 2111 return params.ErrorResults{}, errors.Annotatef(err, "resolve error for unit %q", u.UnitTag().Id()) 2112 } 2113 } 2114 } 2115 2116 var result params.ErrorResults 2117 if err := api.checkCanWrite(); err != nil { 2118 return result, errors.Trace(err) 2119 } 2120 if err := api.check.ChangeAllowed(); err != nil { 2121 return result, errors.Trace(err) 2122 } 2123 2124 result.Results = make([]params.ErrorResult, len(p.Tags.Entities)) 2125 for i, entity := range p.Tags.Entities { 2126 tag, err := names.ParseUnitTag(entity.Tag) 2127 if err != nil { 2128 result.Results[i].Error = common.ServerError(err) 2129 continue 2130 } 2131 unit, err := api.backend.Unit(tag.Id()) 2132 if err != nil { 2133 result.Results[i].Error = common.ServerError(err) 2134 continue 2135 } 2136 err = unit.Resolve(p.Retry) 2137 result.Results[i].Error = common.ServerError(err) 2138 } 2139 return result, nil 2140 } 2141 2142 // ApplicationInfo isn't on the v8 API. 2143 func (u *APIv8) ApplicationInfo(_, _ struct{}) {} 2144 2145 // ApplicationsInfo returns applications information. 2146 func (api *APIBase) ApplicationsInfo(in params.Entities) (params.ApplicationInfoResults, error) { 2147 out := make([]params.ApplicationInfoResult, len(in.Entities)) 2148 for i, one := range in.Entities { 2149 tag, err := names.ParseApplicationTag(one.Tag) 2150 if err != nil { 2151 out[i].Error = common.ServerError(err) 2152 continue 2153 } 2154 app, err := api.backend.Application(tag.Name) 2155 if err != nil { 2156 out[i].Error = common.ServerError(err) 2157 continue 2158 } 2159 2160 details, err := api.getConfig(params.ApplicationGet{ApplicationName: tag.Name}, describe) 2161 if err != nil { 2162 out[i].Error = common.ServerError(err) 2163 continue 2164 } 2165 2166 bindings, err := app.EndpointBindings() 2167 if err != nil { 2168 out[i].Error = common.ServerError(err) 2169 continue 2170 } 2171 2172 out[i].Result = ¶ms.ApplicationInfo{ 2173 Tag: tag.String(), 2174 Charm: details.Charm, 2175 Series: details.Series, 2176 Channel: details.Channel, 2177 Constraints: details.Constraints, 2178 Principal: app.IsPrincipal(), 2179 Exposed: app.IsExposed(), 2180 Remote: app.IsRemote(), 2181 EndpointBindings: bindings, 2182 } 2183 } 2184 return params.ApplicationInfoResults{out}, nil 2185 }