launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "github.com/loggo/loggo" 9 "net/url" 10 "os" 11 "strings" 12 13 errgo "launchpad.net/errgo/errors" 14 "launchpad.net/juju-core/charm" 15 coreCloudinit "launchpad.net/juju-core/cloudinit" 16 "launchpad.net/juju-core/cloudinit/sshinit" 17 "launchpad.net/juju-core/environs" 18 "launchpad.net/juju-core/environs/cloudinit" 19 "launchpad.net/juju-core/environs/config" 20 "launchpad.net/juju-core/errors" 21 "launchpad.net/juju-core/instance" 22 "launchpad.net/juju-core/juju" 23 "launchpad.net/juju-core/names" 24 "launchpad.net/juju-core/state" 25 "launchpad.net/juju-core/state/api" 26 "launchpad.net/juju-core/state/api/params" 27 "launchpad.net/juju-core/state/apiserver/common" 28 "launchpad.net/juju-core/state/statecmd" 29 "launchpad.net/juju-core/utils" 30 ) 31 32 var logger = loggo.GetLogger("juju.state.apiserver.client") 33 34 var mask = errgo.Mask 35 36 type API struct { 37 state *state.State 38 auth common.Authorizer 39 resources *common.Resources 40 client *Client 41 dataDir string 42 } 43 44 // Client serves client-specific API methods. 45 type Client struct { 46 api *API 47 } 48 49 // NewAPI creates a new instance of the Client API. 50 func NewAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer, datadir string) *API { 51 r := &API{ 52 state: st, 53 auth: authorizer, 54 resources: resources, 55 dataDir: datadir, 56 } 57 r.client = &Client{ 58 api: r, 59 } 60 return r 61 } 62 63 // Client returns an object that provides access 64 // to methods accessible to non-agent clients. 65 func (r *API) Client(id string) (*Client, error) { 66 if !r.auth.AuthClient() { 67 return nil, common.ErrPerm 68 } 69 if id != "" { 70 // Safeguard id for possible future use. 71 return nil, common.ErrBadId 72 } 73 return r.client, nil 74 } 75 76 func (c *Client) WatchAll() (params.AllWatcherId, error) { 77 w := c.api.state.Watch() 78 return params.AllWatcherId{ 79 AllWatcherId: c.api.resources.Register(w), 80 }, nil 81 } 82 83 // ServiceSet implements the server side of Client.ServiceSet. Values set to an 84 // empty string will be unset. 85 // 86 // (Deprecated) Use NewServiceSetForClientAPI instead, to preserve values set to 87 // an empty string, and use ServiceUnset to unset values. 88 func (c *Client) ServiceSet(p params.ServiceSet) error { 89 svc, err := c.api.state.Service(p.ServiceName) 90 if err != nil { 91 return mask(err, errors.IsNotFoundError) 92 } 93 return serviceSetSettingsStrings(svc, p.Options) 94 } 95 96 // NewServiceSetForClientAPI implements the server side of 97 // Client.NewServiceSetForClientAPI. This is exactly like ServiceSet except that 98 // it does not unset values that are set to an empty string. ServiceUnset 99 // should be used for that. 100 // 101 // TODO(Nate): rename this to ServiceSet (and remove the deprecated ServiceSet) 102 // when the GUI handles the new behavior. 103 func (c *Client) NewServiceSetForClientAPI(p params.ServiceSet) error { 104 svc, err := c.api.state.Service(p.ServiceName) 105 if err != nil { 106 return mask(err, errors.IsNotFoundError) 107 } 108 return newServiceSetSettingsStringsForClientAPI(svc, p.Options) 109 } 110 111 // ServiceUnset implements the server side of Client.ServiceUnset. 112 func (c *Client) ServiceUnset(p params.ServiceUnset) error { 113 svc, err := c.api.state.Service(p.ServiceName) 114 if err != nil { 115 return mask(err, errors.IsNotFoundError) 116 } 117 settings := make(charm.Settings) 118 for _, option := range p.Options { 119 settings[option] = nil 120 } 121 return svc.UpdateConfigSettings(settings) 122 } 123 124 // ServiceSetYAML implements the server side of Client.ServerSetYAML. 125 func (c *Client) ServiceSetYAML(p params.ServiceSetYAML) error { 126 svc, err := c.api.state.Service(p.ServiceName) 127 if err != nil { 128 return mask(err, errors.IsNotFoundError) 129 } 130 return serviceSetSettingsYAML(svc, p.Config) 131 } 132 133 // ServiceCharmRelations implements the server side of Client.ServiceCharmRelations. 134 func (c *Client) ServiceCharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) { 135 var results params.ServiceCharmRelationsResults 136 service, err := c.api.state.Service(p.ServiceName) 137 if err != nil { 138 return results, mask(err, errors.IsNotFoundError) 139 } 140 endpoints, err := service.Endpoints() 141 if err != nil { 142 return results, mask(err) 143 } 144 results.CharmRelations = make([]string, len(endpoints)) 145 for i, endpoint := range endpoints { 146 results.CharmRelations[i] = endpoint.Relation.Name 147 } 148 return results, nil 149 } 150 151 // Resolved implements the server side of Client.Resolved. 152 func (c *Client) Resolved(p params.Resolved) error { 153 unit, err := c.api.state.Unit(p.UnitName) 154 if err != nil { 155 return mask(err, errors.IsNotFoundError) 156 } 157 return unit.Resolve(p.Retry) 158 } 159 160 // PublicAddress implements the server side of Client.PublicAddress. 161 func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) { 162 switch { 163 case names.IsMachine(p.Target): 164 machine, err := c.api.state.Machine(p.Target) 165 if err != nil { 166 return results, mask(err, errors.IsNotFoundError) 167 } 168 addr := instance.SelectPublicAddress(machine.Addresses()) 169 if addr == "" { 170 return results, errgo.Newf("machine %q has no public address", machine) 171 } 172 return params.PublicAddressResults{PublicAddress: addr}, nil 173 174 case names.IsUnit(p.Target): 175 unit, err := c.api.state.Unit(p.Target) 176 if err != nil { 177 return results, mask(err, errors.IsNotFoundError) 178 } 179 addr, ok := unit.PublicAddress() 180 if !ok { 181 return results, errgo.Newf("unit %q has no public address", unit) 182 } 183 return params.PublicAddressResults{PublicAddress: addr}, nil 184 } 185 return results, errgo.Newf("unknown unit or machine %q", p.Target) 186 } 187 188 // ServiceExpose changes the juju-managed firewall to expose any ports that 189 // were also explicitly marked by units as open. 190 func (c *Client) ServiceExpose(args params.ServiceExpose) error { 191 svc, err := c.api.state.Service(args.ServiceName) 192 if err != nil { 193 return mask(err, errors.IsNotFoundError) 194 } 195 return svc.SetExposed() 196 } 197 198 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 199 // were also explicitly marked by units as open. 200 func (c *Client) ServiceUnexpose(args params.ServiceUnexpose) error { 201 svc, err := c.api.state.Service(args.ServiceName) 202 if err != nil { 203 return mask(err, errors.IsNotFoundError) 204 } 205 return svc.ClearExposed() 206 } 207 208 var CharmStore charm.Repository = charm.Store 209 210 // ServiceDeploy fetches the charm from the charm store and deploys it. 211 // AddCharm or AddLocalCharm should be called to add the charm 212 // before calling ServiceDeploy, although for backward compatibility 213 // this is not necessary until 1.16 support is removed. 214 func (c *Client) ServiceDeploy(args params.ServiceDeploy) error { 215 curl, err := charm.ParseURL(args.CharmUrl) 216 if err != nil { 217 return mask(err) 218 } 219 if curl.Revision < 0 { 220 return errgo.Newf("charm url must include revision") 221 } 222 223 // Try to find the charm URL in state first. 224 ch, err := c.api.state.Charm(curl) 225 if errors.IsNotFoundError(err) { 226 // Remove this whole if block when 1.16 compatibility is dropped. 227 if curl.Schema != "cs" { 228 return errgo.Newf(`charm url has unsupported schema %q`, curl.Schema) 229 } 230 err = c.AddCharm(params.CharmURL{args.CharmUrl}) 231 if err != nil { 232 return mask(err) 233 } 234 ch, err = c.api.state.Charm(curl) 235 if err != nil { 236 return mask(err) 237 } 238 } else if err != nil { 239 return mask(err) 240 } 241 242 var settings charm.Settings 243 if len(args.ConfigYAML) > 0 { 244 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 245 } else if len(args.Config) > 0 { 246 // Parse config in a compatile way (see function comment). 247 settings, err = parseSettingsCompatible(ch, args.Config) 248 } 249 if err != nil { 250 return mask(err) 251 } 252 253 _, err = juju.DeployService(c.api.state, 254 juju.DeployServiceParams{ 255 ServiceName: args.ServiceName, 256 Charm: ch, 257 NumUnits: args.NumUnits, 258 ConfigSettings: settings, 259 Constraints: args.Constraints, 260 ToMachineSpec: args.ToMachineSpec, 261 }) 262 return mask(err) 263 } 264 265 // ServiceUpdate updates the service attributes, including charm URL, 266 // minimum number of units, settings and constraints. 267 // All parameters in params.ServiceUpdate except the service name are optional. 268 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 269 service, err := c.api.state.Service(args.ServiceName) 270 if err != nil { 271 return mask(err, errors.IsNotFoundError) 272 } 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 mask(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 mask(err) 284 } 285 } 286 // Set up service's settings. 287 if args.SettingsYAML != "" { 288 if err = serviceSetSettingsYAML(service, args.SettingsYAML); err != nil { 289 return mask(err) 290 } 291 } else if len(args.SettingsStrings) > 0 { 292 if err = serviceSetSettingsStrings(service, args.SettingsStrings); err != nil { 293 return mask(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 mask(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 mask(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 errgo.Newf(`charm url has unsupported schema %q`, curl.Schema) 327 } 328 if curl.Revision < 0 { 329 return errgo.Newf("charm url must include revision") 330 } 331 err := c.AddCharm(params.CharmURL{curl.String()}) 332 if err != nil { 333 return mask(err) 334 } 335 ch, err := c.api.state.Charm(curl) 336 if err != nil { 337 return mask(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 mask(err) 348 } 349 changes, err := ch.Config().ParseSettingsYAML([]byte(settings), service.Name()) 350 if err != nil { 351 return mask(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 mask(err) 362 } 363 // Parse config in a compatible way (see function comment). 364 changes, err := parseSettingsCompatible(ch, settings) 365 if err != nil { 366 return mask(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 mask(err) 380 } 381 382 // Validate the settings. 383 changes, err := ch.Config().ParseSettingsStrings(settings) 384 if err != nil { 385 return mask(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 mask(err, errors.IsNotFoundError) 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, mask(err, errors.IsNotFoundError) 405 } 406 if args.NumUnits < 1 { 407 return nil, errgo.Newf("must add at least one unit") 408 } 409 if args.NumUnits > 1 && args.ToMachineSpec != "" { 410 return nil, errgo.Newf("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{}, mask(err, errors.IsNotFoundError) 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 = errgo.Newf("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 = errgo.Newf("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 mask(err, errors.IsNotFoundError) 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{}, mask(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{}, mask(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 mask(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{}, mask(err, errors.IsNotFoundError) 498 } 499 rel, err := c.api.state.AddRelation(inEps...) 500 if err != nil { 501 return params.AddRelationResults{}, mask(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{}, mask(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 mask(err, errors.IsNotFoundError) 519 } 520 rel, err := c.api.state.EndpointsRelation(eps...) 521 if err != nil { 522 return mask(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, mask(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, errgo.Newf("parent machine specified without container type") 569 } 570 571 jobs, err := stateJobs(p.Jobs) 572 if err != nil { 573 return nil, mask(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, mask(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, mask(err, errors.IsNotFoundError) 612 } 613 mcfg.DisablePackageCommands = args.DisablePackageCommands 614 cloudcfg := coreCloudinit.New() 615 if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil { 616 return result, mask(err) 617 } 618 619 // ProvisioningScript is run on an existing machine; 620 // we explicitly disable apt_upgrade so as not to 621 // trample the machine's existing configuration. 622 cloudcfg.SetAptUpgrade(false) 623 result.Script, err = sshinit.ConfigureScript(cloudcfg) 624 return result, err 625 } 626 627 // DestroyMachines removes a given set of machines. 628 func (c *Client) DestroyMachines(args params.DestroyMachines) error { 629 var errs []string 630 for _, id := range args.MachineNames { 631 machine, err := c.api.state.Machine(id) 632 switch { 633 case errors.IsNotFoundError(err): 634 err = errgo.Newf("machine %s does not exist", id) 635 case err != nil: 636 case args.Force: 637 err = machine.ForceDestroy() 638 case machine.Life() != state.Alive: 639 continue 640 default: 641 err = machine.Destroy() 642 } 643 if err != nil { 644 errs = append(errs, err.Error()) 645 } 646 } 647 return destroyErr("machines", args.MachineNames, errs) 648 } 649 650 // CharmInfo returns information about the requested charm. 651 func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) { 652 curl, err := charm.ParseURL(args.CharmURL) 653 if err != nil { 654 return api.CharmInfo{}, mask(err) 655 } 656 charm, err := c.api.state.Charm(curl) 657 if err != nil { 658 return api.CharmInfo{}, mask(err) 659 } 660 info := api.CharmInfo{ 661 Revision: charm.Revision(), 662 URL: curl.String(), 663 Config: charm.Config(), 664 Meta: charm.Meta(), 665 } 666 return info, nil 667 } 668 669 // EnvironmentInfo returns information about the current environment (default 670 // series and type). 671 func (c *Client) EnvironmentInfo() (api.EnvironmentInfo, error) { 672 state := c.api.state 673 conf, err := state.EnvironConfig() 674 if err != nil { 675 return api.EnvironmentInfo{}, mask(err) 676 } 677 env, err := state.Environment() 678 if err != nil { 679 return api.EnvironmentInfo{}, mask(err) 680 } 681 682 info := api.EnvironmentInfo{ 683 DefaultSeries: conf.DefaultSeries(), 684 ProviderType: conf.Type(), 685 Name: conf.Name(), 686 UUID: env.UUID(), 687 } 688 return info, nil 689 } 690 691 // GetAnnotations returns annotations about a given entity. 692 func (c *Client) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) { 693 nothing := params.GetAnnotationsResults{} 694 entity, err := c.findEntity(args.Tag) 695 if err != nil { 696 return nothing, mask(err) 697 } 698 ann, err := entity.Annotations() 699 if err != nil { 700 return nothing, mask(err) 701 } 702 return params.GetAnnotationsResults{Annotations: ann}, nil 703 } 704 705 func (c *Client) findEntity(tag string) (state.Annotator, error) { 706 entity0, err := c.api.state.FindEntity(tag) 707 if err != nil { 708 return nil, mask(err, errgo.Any) 709 } 710 entity, ok := entity0.(state.Annotator) 711 if !ok { 712 return nil, common.NotSupportedError(tag, "annotations") 713 } 714 return entity, nil 715 } 716 717 // SetAnnotations stores annotations about a given entity. 718 func (c *Client) SetAnnotations(args params.SetAnnotations) error { 719 entity, err := c.findEntity(args.Tag) 720 if err != nil { 721 return mask(err, errgo.Any) 722 } 723 return entity.SetAnnotations(args.Pairs) 724 } 725 726 // parseSettingsCompatible parses setting strings in a way that is 727 // compatible with the behavior before this CL based on the issue 728 // http://pad.lv/1194945. Until then setting an option to an empty 729 // string caused it to reset to the default value. We now allow 730 // empty strings as actual values, but we want to preserve the API 731 // behavior. 732 func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) { 733 setSettings := map[string]string{} 734 unsetSettings := charm.Settings{} 735 // Split settings into those which set and those which unset a value. 736 for name, value := range settings { 737 if value == "" { 738 unsetSettings[name] = nil 739 continue 740 } 741 setSettings[name] = value 742 } 743 // Validate the settings. 744 changes, err := ch.Config().ParseSettingsStrings(setSettings) 745 if err != nil { 746 // Validate the unsettings and merge them into the changes. 747 return nil, mask(err) 748 } 749 750 unsetSettings, err = ch.Config().ValidateSettings(unsetSettings) 751 if err != nil { 752 return nil, mask(err) 753 } 754 for name := range unsetSettings { 755 changes[name] = nil 756 } 757 return changes, nil 758 } 759 760 // EnvironmentGet implements the server-side part of the 761 // get-environment CLI command. 762 func (c *Client) EnvironmentGet() (params.EnvironmentGetResults, error) { 763 result := params.EnvironmentGetResults{} 764 // Get the existing environment config from the state. 765 config, err := c.api.state.EnvironConfig() 766 if err != nil { 767 return result, mask(err) 768 } 769 result.Config = config.AllAttrs() 770 return result, nil 771 } 772 773 // EnvironmentSet implements the server-side part of the 774 // set-environment CLI command. 775 func (c *Client) EnvironmentSet(args params.EnvironmentSet) error { 776 // TODO(dimitern,thumper): 2013-11-06 bug #1167616 777 // SetEnvironConfig should take both new and old configs. 778 779 // Get the existing environment config from the state. 780 oldConfig, err := c.api.state.EnvironConfig() 781 if err != nil { 782 return mask(err) 783 } 784 // Make sure we don't allow changing agent-version. 785 if v, found := args.Config["agent-version"]; found { 786 oldVersion, _ := oldConfig.AgentVersion() 787 if v != oldVersion.String() { 788 return errgo.Newf("agent-version cannot be changed") 789 } 790 } 791 // Apply the attributes specified for the command to the state config. 792 newConfig, err := oldConfig.Apply(args.Config) 793 if err != nil { 794 return mask(err) 795 } 796 env, err := environs.New(oldConfig) 797 if err != nil { 798 return mask(err) 799 } 800 // Now validate this new config against the existing config via the provider. 801 provider := env.Provider() 802 newProviderConfig, err := provider.Validate(newConfig, oldConfig) 803 if err != nil { 804 return mask(err) 805 } 806 // Now try to apply the new validated config. 807 return c.api.state.SetEnvironConfig(newProviderConfig, oldConfig) 808 } 809 810 // SetEnvironAgentVersion sets the environment agent version. 811 func (c *Client) SetEnvironAgentVersion(args params.SetEnvironAgentVersion) error { 812 return c.api.state.SetEnvironAgentVersion(args.Version) 813 } 814 815 func destroyErr(desc string, ids, errs []string) error { 816 if len(errs) == 0 { 817 return nil 818 } 819 msg := "some %s were not destroyed" 820 if len(errs) == len(ids) { 821 msg = "no %s were destroyed" 822 } 823 msg = fmt.Sprintf(msg, desc) 824 return errgo.Newf("%s: %s", msg, strings.Join(errs, "; ")) 825 } 826 827 // AddCharm adds the given charm URL (which must include revision) to 828 // the environment, if it does not exist yet. Local charms are not 829 // supported, only charm store URLs. See also AddLocalCharm(). 830 func (c *Client) AddCharm(args params.CharmURL) error { 831 charmURL, err := charm.ParseURL(args.URL) 832 if err != nil { 833 return mask(err) 834 } 835 if charmURL.Schema != "cs" { 836 return errgo.Newf("only charm store charm URLs are supported, with cs: schema") 837 } 838 if charmURL.Revision < 0 { 839 return errgo.Newf("charm URL must include revision") 840 } 841 842 // First, check if a pending or a real charm exists in state. 843 stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL) 844 if err == nil && stateCharm.IsUploaded() { 845 // Charm already in state (it was uploaded already). 846 return nil 847 } else if err != nil { 848 return mask(err) 849 } 850 851 // Get the charm and its information from the store. 852 envConfig, err := c.api.state.EnvironConfig() 853 if err != nil { 854 return mask(err) 855 } 856 store := config.AuthorizeCharmRepo(CharmStore, envConfig) 857 downloadedCharm, err := store.Get(charmURL) 858 if err != nil { 859 return errgo.Notef(err, "cannot download charm %q", charmURL.String()) 860 } 861 862 // Open it and calculate the SHA256 hash. 863 downloadedBundle, ok := downloadedCharm.(*charm.Bundle) 864 if !ok { 865 return errgo.Newf("expected a charm archive, got %T", downloadedCharm) 866 } 867 archive, err := os.Open(downloadedBundle.Path) 868 if err != nil { 869 return errgo.NoteMask(err, "cannot read downloaded charm") 870 } 871 defer archive.Close() 872 bundleSHA256, size, err := utils.ReadSHA256(archive) 873 if err != nil { 874 return errgo.NoteMask(err, "cannot calculate SHA256 hash of charm") 875 } 876 if _, err := archive.Seek(0, 0); err != nil { 877 return errgo.NoteMask(err, "cannot rewind charm archive") 878 } 879 880 // Get the environment storage and upload the charm. 881 env, err := environs.New(envConfig) 882 if err != nil { 883 return errgo.NoteMask(err, "cannot access environment") 884 } 885 storage := env.Storage() 886 archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) 887 if err != nil { 888 return errgo.NoteMask(err, "cannot generate charm archive name") 889 } 890 if err := storage.Put(archiveName, archive, size); err != nil { 891 return errgo.NoteMask(err, "cannot upload charm to provider storage") 892 } 893 storageURL, err := storage.URL(archiveName) 894 if err != nil { 895 return errgo.NoteMask(err, "cannot get storage URL for charm") 896 } 897 bundleURL, err := url.Parse(storageURL) 898 if err != nil { 899 return errgo.NoteMask(err, "cannot parse storage URL") 900 } 901 902 // Finally, update the charm data in state and mark it as no longer pending. 903 _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256) 904 if err == state.ErrCharmRevisionAlreadyModified || 905 state.IsCharmAlreadyUploadedError(err) { 906 // This is not an error, it just signifies somebody else 907 // managed to upload and update the charm in state before 908 // us. This means we have to delete what we just uploaded 909 // to storage. 910 if err := storage.Remove(archiveName); err != nil { 911 errgo.NoteMask(err, "cannot remove duplicated charm from storage") 912 } 913 return nil 914 } 915 return err 916 } 917 918 // CharmArchiveName returns a string that is suitable as a file name 919 // in a storage URL. It is constructed from the charm name, revision 920 // and a random UUID string. 921 func CharmArchiveName(name string, revision int) (string, error) { 922 uuid, err := utils.NewUUID() 923 if err != nil { 924 return "", mask(err) 925 } 926 return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil 927 }