github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/apiserver/client/client.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client 5 6 import ( 7 "fmt" 8 "net/url" 9 "os" 10 "strings" 11 12 "github.com/errgo/errgo" 13 "github.com/juju/loggo" 14 15 "launchpad.net/juju-core/charm" 16 coreCloudinit "launchpad.net/juju-core/cloudinit" 17 "launchpad.net/juju-core/cloudinit/sshinit" 18 "launchpad.net/juju-core/environs" 19 "launchpad.net/juju-core/environs/cloudinit" 20 "launchpad.net/juju-core/environs/config" 21 envtools "launchpad.net/juju-core/environs/tools" 22 "launchpad.net/juju-core/errors" 23 "launchpad.net/juju-core/instance" 24 "launchpad.net/juju-core/juju" 25 "launchpad.net/juju-core/names" 26 "launchpad.net/juju-core/state" 27 "launchpad.net/juju-core/state/api" 28 "launchpad.net/juju-core/state/api/params" 29 "launchpad.net/juju-core/state/apiserver/common" 30 "launchpad.net/juju-core/state/statecmd" 31 coretools "launchpad.net/juju-core/tools" 32 "launchpad.net/juju-core/utils" 33 ) 34 35 var logger = loggo.GetLogger("juju.state.apiserver.client") 36 37 type API struct { 38 state *state.State 39 auth common.Authorizer 40 resources *common.Resources 41 client *Client 42 dataDir string 43 } 44 45 // Client serves client-specific API methods. 46 type Client struct { 47 api *API 48 } 49 50 // NewAPI creates a new instance of the Client API. 51 func NewAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer, datadir string) *API { 52 r := &API{ 53 state: st, 54 auth: authorizer, 55 resources: resources, 56 dataDir: datadir, 57 } 58 r.client = &Client{ 59 api: r, 60 } 61 return r 62 } 63 64 // Client returns an object that provides access 65 // to methods accessible to non-agent clients. 66 func (r *API) Client(id string) (*Client, error) { 67 if !r.auth.AuthClient() { 68 return nil, common.ErrPerm 69 } 70 if id != "" { 71 // Safeguard id for possible future use. 72 return nil, common.ErrBadId 73 } 74 return r.client, nil 75 } 76 77 func (c *Client) WatchAll() (params.AllWatcherId, error) { 78 w := c.api.state.Watch() 79 return params.AllWatcherId{ 80 AllWatcherId: c.api.resources.Register(w), 81 }, nil 82 } 83 84 // ServiceSet implements the server side of Client.ServiceSet. Values set to an 85 // empty string will be unset. 86 // 87 // (Deprecated) Use NewServiceSetForClientAPI instead, to preserve values set to 88 // an empty string, and use ServiceUnset to unset values. 89 func (c *Client) ServiceSet(p params.ServiceSet) error { 90 svc, err := c.api.state.Service(p.ServiceName) 91 if err != nil { 92 return err 93 } 94 return serviceSetSettingsStrings(svc, p.Options) 95 } 96 97 // NewServiceSetForClientAPI implements the server side of 98 // Client.NewServiceSetForClientAPI. This is exactly like ServiceSet except that 99 // it does not unset values that are set to an empty string. ServiceUnset 100 // should be used for that. 101 // 102 // TODO(Nate): rename this to ServiceSet (and remove the deprecated ServiceSet) 103 // when the GUI handles the new behavior. 104 func (c *Client) NewServiceSetForClientAPI(p params.ServiceSet) error { 105 svc, err := c.api.state.Service(p.ServiceName) 106 if err != nil { 107 return err 108 } 109 return newServiceSetSettingsStringsForClientAPI(svc, p.Options) 110 } 111 112 // ServiceUnset implements the server side of Client.ServiceUnset. 113 func (c *Client) ServiceUnset(p params.ServiceUnset) error { 114 svc, err := c.api.state.Service(p.ServiceName) 115 if err != nil { 116 return err 117 } 118 settings := make(charm.Settings) 119 for _, option := range p.Options { 120 settings[option] = nil 121 } 122 return svc.UpdateConfigSettings(settings) 123 } 124 125 // ServiceSetYAML implements the server side of Client.ServerSetYAML. 126 func (c *Client) ServiceSetYAML(p params.ServiceSetYAML) error { 127 svc, err := c.api.state.Service(p.ServiceName) 128 if err != nil { 129 return err 130 } 131 return serviceSetSettingsYAML(svc, p.Config) 132 } 133 134 // ServiceCharmRelations implements the server side of Client.ServiceCharmRelations. 135 func (c *Client) ServiceCharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) { 136 var results params.ServiceCharmRelationsResults 137 service, err := c.api.state.Service(p.ServiceName) 138 if err != nil { 139 return results, err 140 } 141 endpoints, err := service.Endpoints() 142 if err != nil { 143 return results, err 144 } 145 results.CharmRelations = make([]string, len(endpoints)) 146 for i, endpoint := range endpoints { 147 results.CharmRelations[i] = endpoint.Relation.Name 148 } 149 return results, nil 150 } 151 152 // Resolved implements the server side of Client.Resolved. 153 func (c *Client) Resolved(p params.Resolved) error { 154 unit, err := c.api.state.Unit(p.UnitName) 155 if err != nil { 156 return err 157 } 158 return unit.Resolve(p.Retry) 159 } 160 161 // PublicAddress implements the server side of Client.PublicAddress. 162 func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) { 163 switch { 164 case names.IsMachine(p.Target): 165 machine, err := c.api.state.Machine(p.Target) 166 if err != nil { 167 return results, err 168 } 169 addr := instance.SelectPublicAddress(machine.Addresses()) 170 if addr == "" { 171 return results, fmt.Errorf("machine %q has no public address", machine) 172 } 173 return params.PublicAddressResults{PublicAddress: addr}, nil 174 175 case names.IsUnit(p.Target): 176 unit, err := c.api.state.Unit(p.Target) 177 if err != nil { 178 return results, err 179 } 180 addr, ok := unit.PublicAddress() 181 if !ok { 182 return results, fmt.Errorf("unit %q has no public address", unit) 183 } 184 return params.PublicAddressResults{PublicAddress: addr}, nil 185 } 186 return results, fmt.Errorf("unknown unit or machine %q", p.Target) 187 } 188 189 // ServiceExpose changes the juju-managed firewall to expose any ports that 190 // were also explicitly marked by units as open. 191 func (c *Client) ServiceExpose(args params.ServiceExpose) error { 192 svc, err := c.api.state.Service(args.ServiceName) 193 if err != nil { 194 return err 195 } 196 return svc.SetExposed() 197 } 198 199 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 200 // were also explicitly marked by units as open. 201 func (c *Client) ServiceUnexpose(args params.ServiceUnexpose) error { 202 svc, err := c.api.state.Service(args.ServiceName) 203 if err != nil { 204 return err 205 } 206 return svc.ClearExposed() 207 } 208 209 var CharmStore charm.Repository = charm.Store 210 211 // ServiceDeploy fetches the charm from the charm store and deploys it. 212 // AddCharm or AddLocalCharm should be called to add the charm 213 // before calling ServiceDeploy, although for backward compatibility 214 // this is not necessary until 1.16 support is removed. 215 func (c *Client) ServiceDeploy(args params.ServiceDeploy) error { 216 curl, err := charm.ParseURL(args.CharmUrl) 217 if err != nil { 218 return err 219 } 220 if curl.Revision < 0 { 221 return fmt.Errorf("charm url must include revision") 222 } 223 224 // Try to find the charm URL in state first. 225 ch, err := c.api.state.Charm(curl) 226 if errors.IsNotFoundError(err) { 227 // Remove this whole if block when 1.16 compatibility is dropped. 228 if curl.Schema != "cs" { 229 return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) 230 } 231 err = c.AddCharm(params.CharmURL{args.CharmUrl}) 232 if err != nil { 233 return err 234 } 235 ch, err = c.api.state.Charm(curl) 236 if err != nil { 237 return err 238 } 239 } else if err != nil { 240 return err 241 } 242 243 var settings charm.Settings 244 if len(args.ConfigYAML) > 0 { 245 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 246 } else if len(args.Config) > 0 { 247 // Parse config in a compatile way (see function comment). 248 settings, err = parseSettingsCompatible(ch, args.Config) 249 } 250 if err != nil { 251 return err 252 } 253 254 _, err = juju.DeployService(c.api.state, 255 juju.DeployServiceParams{ 256 ServiceName: args.ServiceName, 257 Charm: ch, 258 NumUnits: args.NumUnits, 259 ConfigSettings: settings, 260 Constraints: args.Constraints, 261 ToMachineSpec: args.ToMachineSpec, 262 }) 263 return err 264 } 265 266 // ServiceUpdate updates the service attributes, including charm URL, 267 // minimum number of units, settings and constraints. 268 // All parameters in params.ServiceUpdate except the service name are optional. 269 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 270 service, err := c.api.state.Service(args.ServiceName) 271 if err != nil { 272 return err 273 } 274 // Set the charm for the given service. 275 if args.CharmUrl != "" { 276 if err = c.serviceSetCharm(service, args.CharmUrl, args.ForceCharmUrl); err != nil { 277 return err 278 } 279 } 280 // Set the minimum number of units for the given service. 281 if args.MinUnits != nil { 282 if err = service.SetMinUnits(*args.MinUnits); err != nil { 283 return err 284 } 285 } 286 // Set up service's settings. 287 if args.SettingsYAML != "" { 288 if err = serviceSetSettingsYAML(service, args.SettingsYAML); err != nil { 289 return err 290 } 291 } else if len(args.SettingsStrings) > 0 { 292 if err = serviceSetSettingsStrings(service, args.SettingsStrings); err != nil { 293 return err 294 } 295 } 296 // Update service's constraints. 297 if args.Constraints != nil { 298 return service.SetConstraints(*args.Constraints) 299 } 300 return nil 301 } 302 303 // serviceSetCharm sets the charm for the given service. 304 func (c *Client) serviceSetCharm(service *state.Service, url string, force bool) error { 305 curl, err := charm.ParseURL(url) 306 if err != nil { 307 return err 308 } 309 sch, err := c.api.state.Charm(curl) 310 if errors.IsNotFoundError(err) { 311 // Charms should be added before trying to use them, with 312 // AddCharm or AddLocalCharm API calls. When they're not, 313 // we're reverting to 1.16 compatibility mode. 314 return c.serviceSetCharm1dot16(service, curl, force) 315 } 316 if err != nil { 317 return err 318 } 319 return service.SetCharm(sch, force) 320 } 321 322 // serviceSetCharm1dot16 sets the charm for the given service in 1.16 323 // compatibility mode. Remove this when support for 1.16 is dropped. 324 func (c *Client) serviceSetCharm1dot16(service *state.Service, curl *charm.URL, force bool) error { 325 if curl.Schema != "cs" { 326 return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) 327 } 328 if curl.Revision < 0 { 329 return fmt.Errorf("charm url must include revision") 330 } 331 err := c.AddCharm(params.CharmURL{curl.String()}) 332 if err != nil { 333 return err 334 } 335 ch, err := c.api.state.Charm(curl) 336 if err != nil { 337 return err 338 } 339 return service.SetCharm(ch, force) 340 } 341 342 // serviceSetSettingsYAML updates the settings for the given service, 343 // taking the configuration from a YAML string. 344 func serviceSetSettingsYAML(service *state.Service, settings string) error { 345 ch, _, err := service.Charm() 346 if err != nil { 347 return err 348 } 349 changes, err := ch.Config().ParseSettingsYAML([]byte(settings), service.Name()) 350 if err != nil { 351 return err 352 } 353 return service.UpdateConfigSettings(changes) 354 } 355 356 // serviceSetSettingsStrings updates the settings for the given service, 357 // taking the configuration from a map of strings. 358 func serviceSetSettingsStrings(service *state.Service, settings map[string]string) error { 359 ch, _, err := service.Charm() 360 if err != nil { 361 return err 362 } 363 // Parse config in a compatible way (see function comment). 364 changes, err := parseSettingsCompatible(ch, settings) 365 if err != nil { 366 return err 367 } 368 return service.UpdateConfigSettings(changes) 369 } 370 371 // newServiceSetSettingsStringsForClientAPI updates the settings for the given 372 // service, taking the configuration from a map of strings. 373 // 374 // TODO(Nate): replace serviceSetSettingsStrings with this onces the GUI no 375 // longer expects to be able to unset values by sending an empty string. 376 func newServiceSetSettingsStringsForClientAPI(service *state.Service, settings map[string]string) error { 377 ch, _, err := service.Charm() 378 if err != nil { 379 return err 380 } 381 382 // Validate the settings. 383 changes, err := ch.Config().ParseSettingsStrings(settings) 384 if err != nil { 385 return err 386 } 387 388 return service.UpdateConfigSettings(changes) 389 } 390 391 // ServiceSetCharm sets the charm for a given service. 392 func (c *Client) ServiceSetCharm(args params.ServiceSetCharm) error { 393 service, err := c.api.state.Service(args.ServiceName) 394 if err != nil { 395 return err 396 } 397 return c.serviceSetCharm(service, args.CharmUrl, args.Force) 398 } 399 400 // addServiceUnits adds a given number of units to a service. 401 func addServiceUnits(state *state.State, args params.AddServiceUnits) ([]*state.Unit, error) { 402 service, err := state.Service(args.ServiceName) 403 if err != nil { 404 return nil, err 405 } 406 if args.NumUnits < 1 { 407 return nil, fmt.Errorf("must add at least one unit") 408 } 409 if args.NumUnits > 1 && args.ToMachineSpec != "" { 410 return nil, fmt.Errorf("cannot use NumUnits with ToMachineSpec") 411 } 412 return juju.AddUnits(state, service, args.NumUnits, args.ToMachineSpec) 413 } 414 415 // AddServiceUnits adds a given number of units to a service. 416 func (c *Client) AddServiceUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) { 417 units, err := addServiceUnits(c.api.state, args) 418 if err != nil { 419 return params.AddServiceUnitsResults{}, err 420 } 421 unitNames := make([]string, len(units)) 422 for i, unit := range units { 423 unitNames[i] = unit.String() 424 } 425 return params.AddServiceUnitsResults{Units: unitNames}, nil 426 } 427 428 // DestroyServiceUnits removes a given set of service units. 429 func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error { 430 var errs []string 431 for _, name := range args.UnitNames { 432 unit, err := c.api.state.Unit(name) 433 switch { 434 case errors.IsNotFoundError(err): 435 err = fmt.Errorf("unit %q does not exist", name) 436 case err != nil: 437 case unit.Life() != state.Alive: 438 continue 439 case unit.IsPrincipal(): 440 err = unit.Destroy() 441 default: 442 err = fmt.Errorf("unit %q is a subordinate", name) 443 } 444 if err != nil { 445 errs = append(errs, err.Error()) 446 } 447 } 448 return destroyErr("units", args.UnitNames, errs) 449 } 450 451 // ServiceDestroy destroys a given service. 452 func (c *Client) ServiceDestroy(args params.ServiceDestroy) error { 453 svc, err := c.api.state.Service(args.ServiceName) 454 if err != nil { 455 return err 456 } 457 return svc.Destroy() 458 } 459 460 // GetServiceConstraints returns the constraints for a given service. 461 func (c *Client) GetServiceConstraints(args params.GetServiceConstraints) (params.GetConstraintsResults, error) { 462 svc, err := c.api.state.Service(args.ServiceName) 463 if err != nil { 464 return params.GetConstraintsResults{}, err 465 } 466 cons, err := svc.Constraints() 467 return params.GetConstraintsResults{cons}, err 468 } 469 470 // GetEnvironmentConstraints returns the constraints for the environment. 471 func (c *Client) GetEnvironmentConstraints() (params.GetConstraintsResults, error) { 472 cons, err := c.api.state.EnvironConstraints() 473 if err != nil { 474 return params.GetConstraintsResults{}, err 475 } 476 return params.GetConstraintsResults{cons}, nil 477 } 478 479 // SetServiceConstraints sets the constraints for a given service. 480 func (c *Client) SetServiceConstraints(args params.SetConstraints) error { 481 svc, err := c.api.state.Service(args.ServiceName) 482 if err != nil { 483 return err 484 } 485 return svc.SetConstraints(args.Constraints) 486 } 487 488 // SetEnvironmentConstraints sets the constraints for the environment. 489 func (c *Client) SetEnvironmentConstraints(args params.SetConstraints) error { 490 return c.api.state.SetEnvironConstraints(args.Constraints) 491 } 492 493 // AddRelation adds a relation between the specified endpoints and returns the relation info. 494 func (c *Client) AddRelation(args params.AddRelation) (params.AddRelationResults, error) { 495 inEps, err := c.api.state.InferEndpoints(args.Endpoints) 496 if err != nil { 497 return params.AddRelationResults{}, err 498 } 499 rel, err := c.api.state.AddRelation(inEps...) 500 if err != nil { 501 return params.AddRelationResults{}, err 502 } 503 outEps := make(map[string]charm.Relation) 504 for _, inEp := range inEps { 505 outEp, err := rel.Endpoint(inEp.ServiceName) 506 if err != nil { 507 return params.AddRelationResults{}, err 508 } 509 outEps[inEp.ServiceName] = outEp.Relation 510 } 511 return params.AddRelationResults{Endpoints: outEps}, nil 512 } 513 514 // DestroyRelation removes the relation between the specified endpoints. 515 func (c *Client) DestroyRelation(args params.DestroyRelation) error { 516 eps, err := c.api.state.InferEndpoints(args.Endpoints) 517 if err != nil { 518 return err 519 } 520 rel, err := c.api.state.EndpointsRelation(eps...) 521 if err != nil { 522 return err 523 } 524 return rel.Destroy() 525 } 526 527 // AddMachines adds new machines with the supplied parameters. 528 func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) { 529 results := params.AddMachinesResults{ 530 Machines: make([]params.AddMachinesResult, len(args.MachineParams)), 531 } 532 533 var defaultSeries string 534 conf, err := c.api.state.EnvironConfig() 535 if err != nil { 536 return results, err 537 } 538 defaultSeries = conf.DefaultSeries() 539 540 for i, p := range args.MachineParams { 541 m, err := c.addOneMachine(p, defaultSeries) 542 results.Machines[i].Error = common.ServerError(err) 543 if err == nil { 544 results.Machines[i].Machine = m.Id() 545 } 546 } 547 return results, nil 548 } 549 550 // InjectMachines injects a machine into state with provisioned status. 551 func (c *Client) InjectMachines(args params.AddMachines) (params.AddMachinesResults, error) { 552 return c.AddMachines(args) 553 } 554 555 func (c *Client) addOneMachine(p params.AddMachineParams, defaultSeries string) (*state.Machine, error) { 556 if p.Series == "" { 557 p.Series = defaultSeries 558 } 559 if p.ContainerType != "" { 560 // Guard against dubious client by making sure that 561 // the following attributes can only be set when we're 562 // not making a new container. 563 p.InstanceId = "" 564 p.Nonce = "" 565 p.HardwareCharacteristics = instance.HardwareCharacteristics{} 566 p.Addrs = nil 567 } else if p.ParentId != "" { 568 return nil, fmt.Errorf("parent machine specified without container type") 569 } 570 571 jobs, err := stateJobs(p.Jobs) 572 if err != nil { 573 return nil, err 574 } 575 template := state.MachineTemplate{ 576 Series: p.Series, 577 Constraints: p.Constraints, 578 InstanceId: p.InstanceId, 579 Jobs: jobs, 580 Nonce: p.Nonce, 581 HardwareCharacteristics: p.HardwareCharacteristics, 582 Addresses: p.Addrs, 583 } 584 if p.ContainerType == "" { 585 return c.api.state.AddOneMachine(template) 586 } 587 if p.ParentId != "" { 588 return c.api.state.AddMachineInsideMachine(template, p.ParentId, p.ContainerType) 589 } 590 return c.api.state.AddMachineInsideNewMachine(template, template, p.ContainerType) 591 } 592 593 func stateJobs(jobs []params.MachineJob) ([]state.MachineJob, error) { 594 newJobs := make([]state.MachineJob, len(jobs)) 595 for i, job := range jobs { 596 newJob, err := state.MachineJobFromParams(job) 597 if err != nil { 598 return nil, err 599 } 600 newJobs[i] = newJob 601 } 602 return newJobs, nil 603 } 604 605 // ProvisioningScript returns a shell script that, when run, 606 // provisions a machine agent on the machine executing the script. 607 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (params.ProvisioningScriptResult, error) { 608 var result params.ProvisioningScriptResult 609 mcfg, err := statecmd.MachineConfig(c.api.state, args.MachineId, args.Nonce, args.DataDir) 610 if err != nil { 611 return result, err 612 } 613 mcfg.DisablePackageCommands = args.DisablePackageCommands 614 cloudcfg := coreCloudinit.New() 615 if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil { 616 return result, err 617 } 618 // ProvisioningScript is run on an existing machine; 619 // we explicitly disable apt_upgrade so as not to 620 // trample the machine's existing configuration. 621 cloudcfg.SetAptUpgrade(false) 622 result.Script, err = sshinit.ConfigureScript(cloudcfg) 623 return result, err 624 } 625 626 // DestroyMachines removes a given set of machines. 627 func (c *Client) DestroyMachines(args params.DestroyMachines) error { 628 var errs []string 629 for _, id := range args.MachineNames { 630 machine, err := c.api.state.Machine(id) 631 switch { 632 case errors.IsNotFoundError(err): 633 err = fmt.Errorf("machine %s does not exist", id) 634 case err != nil: 635 case args.Force: 636 err = machine.ForceDestroy() 637 case machine.Life() != state.Alive: 638 continue 639 default: 640 err = machine.Destroy() 641 } 642 if err != nil { 643 errs = append(errs, err.Error()) 644 } 645 } 646 return destroyErr("machines", args.MachineNames, errs) 647 } 648 649 // CharmInfo returns information about the requested charm. 650 func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) { 651 curl, err := charm.ParseURL(args.CharmURL) 652 if err != nil { 653 return api.CharmInfo{}, err 654 } 655 charm, err := c.api.state.Charm(curl) 656 if err != nil { 657 return api.CharmInfo{}, err 658 } 659 info := api.CharmInfo{ 660 Revision: charm.Revision(), 661 URL: curl.String(), 662 Config: charm.Config(), 663 Meta: charm.Meta(), 664 } 665 return info, nil 666 } 667 668 // EnvironmentInfo returns information about the current environment (default 669 // series and type). 670 func (c *Client) EnvironmentInfo() (api.EnvironmentInfo, error) { 671 state := c.api.state 672 conf, err := state.EnvironConfig() 673 if err != nil { 674 return api.EnvironmentInfo{}, err 675 } 676 env, err := state.Environment() 677 if err != nil { 678 return api.EnvironmentInfo{}, err 679 } 680 681 info := api.EnvironmentInfo{ 682 DefaultSeries: conf.DefaultSeries(), 683 ProviderType: conf.Type(), 684 Name: conf.Name(), 685 UUID: env.UUID(), 686 } 687 return info, nil 688 } 689 690 // GetAnnotations returns annotations about a given entity. 691 func (c *Client) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) { 692 nothing := params.GetAnnotationsResults{} 693 entity, err := c.findEntity(args.Tag) 694 if err != nil { 695 return nothing, err 696 } 697 ann, err := entity.Annotations() 698 if err != nil { 699 return nothing, err 700 } 701 return params.GetAnnotationsResults{Annotations: ann}, nil 702 } 703 704 func (c *Client) findEntity(tag string) (state.Annotator, error) { 705 entity0, err := c.api.state.FindEntity(tag) 706 if err != nil { 707 return nil, err 708 } 709 entity, ok := entity0.(state.Annotator) 710 if !ok { 711 return nil, common.NotSupportedError(tag, "annotations") 712 } 713 return entity, nil 714 } 715 716 // SetAnnotations stores annotations about a given entity. 717 func (c *Client) SetAnnotations(args params.SetAnnotations) error { 718 entity, err := c.findEntity(args.Tag) 719 if err != nil { 720 return err 721 } 722 return entity.SetAnnotations(args.Pairs) 723 } 724 725 // parseSettingsCompatible parses setting strings in a way that is 726 // compatible with the behavior before this CL based on the issue 727 // http://pad.lv/1194945. Until then setting an option to an empty 728 // string caused it to reset to the default value. We now allow 729 // empty strings as actual values, but we want to preserve the API 730 // behavior. 731 func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) { 732 setSettings := map[string]string{} 733 unsetSettings := charm.Settings{} 734 // Split settings into those which set and those which unset a value. 735 for name, value := range settings { 736 if value == "" { 737 unsetSettings[name] = nil 738 continue 739 } 740 setSettings[name] = value 741 } 742 // Validate the settings. 743 changes, err := ch.Config().ParseSettingsStrings(setSettings) 744 if err != nil { 745 return nil, err 746 } 747 // Validate the unsettings and merge them into the changes. 748 unsetSettings, err = ch.Config().ValidateSettings(unsetSettings) 749 if err != nil { 750 return nil, err 751 } 752 for name := range unsetSettings { 753 changes[name] = nil 754 } 755 return changes, nil 756 } 757 758 // EnvironmentGet implements the server-side part of the 759 // get-environment CLI command. 760 func (c *Client) EnvironmentGet() (params.EnvironmentGetResults, error) { 761 result := params.EnvironmentGetResults{} 762 // Get the existing environment config from the state. 763 config, err := c.api.state.EnvironConfig() 764 if err != nil { 765 return result, err 766 } 767 result.Config = config.AllAttrs() 768 return result, nil 769 } 770 771 // EnvironmentSet implements the server-side part of the 772 // set-environment CLI command. 773 func (c *Client) EnvironmentSet(args params.EnvironmentSet) error { 774 // TODO(dimitern,thumper): 2013-11-06 bug #1167616 775 // SetEnvironConfig should take both new and old configs. 776 777 // Get the existing environment config from the state. 778 oldConfig, err := c.api.state.EnvironConfig() 779 if err != nil { 780 return err 781 } 782 // Make sure we don't allow changing agent-version. 783 if v, found := args.Config["agent-version"]; found { 784 oldVersion, _ := oldConfig.AgentVersion() 785 if v != oldVersion.String() { 786 return fmt.Errorf("agent-version cannot be changed") 787 } 788 } 789 // Apply the attributes specified for the command to the state config. 790 newConfig, err := oldConfig.Apply(args.Config) 791 if err != nil { 792 return err 793 } 794 env, err := environs.New(oldConfig) 795 if err != nil { 796 return err 797 } 798 // Now validate this new config against the existing config via the provider. 799 provider := env.Provider() 800 newProviderConfig, err := provider.Validate(newConfig, oldConfig) 801 if err != nil { 802 return err 803 } 804 // Now try to apply the new validated config. 805 return c.api.state.SetEnvironConfig(newProviderConfig, oldConfig) 806 } 807 808 // SetEnvironAgentVersion sets the environment agent version. 809 func (c *Client) SetEnvironAgentVersion(args params.SetEnvironAgentVersion) error { 810 return c.api.state.SetEnvironAgentVersion(args.Version) 811 } 812 813 // FindTools returns a List containing all tools matching the given parameters. 814 func (c *Client) FindTools(args params.FindToolsParams) (params.FindToolsResults, error) { 815 result := params.FindToolsResults{} 816 // Get the existing environment config from the state. 817 envConfig, err := c.api.state.EnvironConfig() 818 if err != nil { 819 return result, err 820 } 821 env, err := environs.New(envConfig) 822 if err != nil { 823 return result, err 824 } 825 filter := coretools.Filter{ 826 Arch: args.Arch, 827 Series: args.Series, 828 } 829 result.List, err = envtools.FindTools(env, args.MajorVersion, args.MinorVersion, filter, envtools.DoNotAllowRetry) 830 result.Error = common.ServerError(err) 831 return result, nil 832 } 833 834 func destroyErr(desc string, ids, errs []string) error { 835 if len(errs) == 0 { 836 return nil 837 } 838 msg := "some %s were not destroyed" 839 if len(errs) == len(ids) { 840 msg = "no %s were destroyed" 841 } 842 msg = fmt.Sprintf(msg, desc) 843 return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) 844 } 845 846 // AddCharm adds the given charm URL (which must include revision) to 847 // the environment, if it does not exist yet. Local charms are not 848 // supported, only charm store URLs. See also AddLocalCharm(). 849 func (c *Client) AddCharm(args params.CharmURL) error { 850 charmURL, err := charm.ParseURL(args.URL) 851 if err != nil { 852 return err 853 } 854 if charmURL.Schema != "cs" { 855 return fmt.Errorf("only charm store charm URLs are supported, with cs: schema") 856 } 857 if charmURL.Revision < 0 { 858 return fmt.Errorf("charm URL must include revision") 859 } 860 861 // First, check if a pending or a real charm exists in state. 862 stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL) 863 if err == nil && stateCharm.IsUploaded() { 864 // Charm already in state (it was uploaded already). 865 return nil 866 } else if err != nil { 867 return err 868 } 869 870 // Get the charm and its information from the store. 871 envConfig, err := c.api.state.EnvironConfig() 872 if err != nil { 873 return err 874 } 875 store := config.SpecializeCharmRepo(CharmStore, envConfig) 876 downloadedCharm, err := store.Get(charmURL) 877 if err != nil { 878 return errgo.Annotatef(err, "cannot download charm %q", charmURL.String()) 879 } 880 881 // Open it and calculate the SHA256 hash. 882 downloadedBundle, ok := downloadedCharm.(*charm.Bundle) 883 if !ok { 884 return errgo.New("expected a charm archive, got %T", downloadedCharm) 885 } 886 archive, err := os.Open(downloadedBundle.Path) 887 if err != nil { 888 return errgo.Annotate(err, "cannot read downloaded charm") 889 } 890 defer archive.Close() 891 bundleSHA256, size, err := utils.ReadSHA256(archive) 892 if err != nil { 893 return errgo.Annotate(err, "cannot calculate SHA256 hash of charm") 894 } 895 if _, err := archive.Seek(0, 0); err != nil { 896 return errgo.Annotate(err, "cannot rewind charm archive") 897 } 898 899 // Get the environment storage and upload the charm. 900 env, err := environs.New(envConfig) 901 if err != nil { 902 return errgo.Annotate(err, "cannot access environment") 903 } 904 storage := env.Storage() 905 archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) 906 if err != nil { 907 return errgo.Annotate(err, "cannot generate charm archive name") 908 } 909 if err := storage.Put(archiveName, archive, size); err != nil { 910 return errgo.Annotate(err, "cannot upload charm to provider storage") 911 } 912 storageURL, err := storage.URL(archiveName) 913 if err != nil { 914 return errgo.Annotate(err, "cannot get storage URL for charm") 915 } 916 bundleURL, err := url.Parse(storageURL) 917 if err != nil { 918 return errgo.Annotate(err, "cannot parse storage URL") 919 } 920 921 // Finally, update the charm data in state and mark it as no longer pending. 922 _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256) 923 if err == state.ErrCharmRevisionAlreadyModified || 924 state.IsCharmAlreadyUploadedError(err) { 925 // This is not an error, it just signifies somebody else 926 // managed to upload and update the charm in state before 927 // us. This means we have to delete what we just uploaded 928 // to storage. 929 if err := storage.Remove(archiveName); err != nil { 930 errgo.Annotate(err, "cannot remove duplicated charm from storage") 931 } 932 return nil 933 } 934 return err 935 } 936 937 // CharmArchiveName returns a string that is suitable as a file name 938 // in a storage URL. It is constructed from the charm name, revision 939 // and a random UUID string. 940 func CharmArchiveName(name string, revision int) (string, error) { 941 uuid, err := utils.NewUUID() 942 if err != nil { 943 return "", err 944 } 945 return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil 946 }