github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/api/client.go (about) 1 // Copyright 2013, 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api 5 6 import ( 7 "bytes" 8 "crypto/tls" 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/http" 14 "net/url" 15 "os" 16 "path" 17 "strings" 18 "time" 19 20 "github.com/juju/errors" 21 "github.com/juju/loggo" 22 "github.com/juju/names" 23 "github.com/juju/utils" 24 "golang.org/x/net/websocket" 25 "gopkg.in/juju/charm.v5" 26 "gopkg.in/macaroon.v1" 27 28 "github.com/juju/juju/api/base" 29 "github.com/juju/juju/apiserver/params" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/instance" 32 "github.com/juju/juju/network" 33 "github.com/juju/juju/state/multiwatcher" 34 "github.com/juju/juju/tools" 35 "github.com/juju/juju/version" 36 ) 37 38 // Client represents the client-accessible part of the state. 39 type Client struct { 40 base.ClientFacade 41 facade base.FacadeCaller 42 st *State 43 } 44 45 // NetworksSpecification holds the enabled and disabled networks for a 46 // service. 47 // TODO(dimitern): Drop this in a follow-up. 48 type NetworksSpecification struct { 49 Enabled []string 50 Disabled []string 51 } 52 53 // AgentStatus holds status info about a machine or unit agent. 54 type AgentStatus struct { 55 Status params.Status 56 Info string 57 Data map[string]interface{} 58 Since *time.Time 59 Kind params.HistoryKind 60 Version string 61 Life string 62 Err error 63 } 64 65 // MachineStatus holds status info about a machine. 66 type MachineStatus struct { 67 Agent AgentStatus 68 69 // The following fields mirror fields in AgentStatus (introduced 70 // in 1.19.x). The old fields below are being kept for 71 // compatibility with old clients. 72 // They can be removed once API versioning lands. 73 AgentState params.Status 74 AgentStateInfo string 75 AgentVersion string 76 Life string 77 Err error 78 79 DNSName string 80 InstanceId instance.Id 81 InstanceState string 82 Series string 83 Id string 84 Containers map[string]MachineStatus 85 Hardware string 86 Jobs []multiwatcher.MachineJob 87 HasVote bool 88 WantsVote bool 89 } 90 91 // ServiceStatus holds status info about a service. 92 type ServiceStatus struct { 93 Err error 94 Charm string 95 Exposed bool 96 Life string 97 Relations map[string][]string 98 Networks NetworksSpecification 99 CanUpgradeTo string 100 SubordinateTo []string 101 Units map[string]UnitStatus 102 MeterStatuses map[string]MeterStatus 103 Status AgentStatus 104 } 105 106 // UnitStatusHistory holds a slice of statuses. 107 type UnitStatusHistory struct { 108 Statuses []AgentStatus 109 } 110 111 // MeterStatus represents the meter status of a unit. 112 type MeterStatus struct { 113 Color string 114 Message string 115 } 116 117 // UnitStatus holds status info about a unit. 118 type UnitStatus struct { 119 // UnitAgent holds the status for a unit's agent. 120 UnitAgent AgentStatus 121 122 // Workload holds the status for a unit's workload 123 Workload AgentStatus 124 125 // Until Juju 2.0, we need to continue to return legacy agent state values 126 // as top level struct attributes when the "FullStatus" API is called. 127 AgentState params.Status 128 AgentStateInfo string 129 AgentVersion string 130 Life string 131 Err error 132 133 Machine string 134 OpenedPorts []string 135 PublicAddress string 136 Charm string 137 Subordinates map[string]UnitStatus 138 } 139 140 // RelationStatus holds status info about a relation. 141 type RelationStatus struct { 142 Id int 143 Key string 144 Interface string 145 Scope charm.RelationScope 146 Endpoints []EndpointStatus 147 } 148 149 // EndpointStatus holds status info about a single endpoint 150 type EndpointStatus struct { 151 ServiceName string 152 Name string 153 Role charm.RelationRole 154 Subordinate bool 155 } 156 157 func (epStatus *EndpointStatus) String() string { 158 return epStatus.ServiceName + ":" + epStatus.Name 159 } 160 161 // NetworkStatus holds status info about a network. 162 type NetworkStatus struct { 163 Err error 164 ProviderId network.Id 165 CIDR string 166 VLANTag int 167 } 168 169 // Status holds information about the status of a juju environment. 170 type Status struct { 171 EnvironmentName string 172 Machines map[string]MachineStatus 173 Services map[string]ServiceStatus 174 Networks map[string]NetworkStatus 175 Relations []RelationStatus 176 } 177 178 // Status returns the status of the juju environment. 179 func (c *Client) Status(patterns []string) (*params.FullStatus, error) { 180 var result params.FullStatus 181 p := params.StatusParams{Patterns: patterns} 182 if err := c.facade.FacadeCall("FullStatus", p, &result); err != nil { 183 return nil, err 184 } 185 return &result, nil 186 } 187 188 // UnitStatusHistory retrieves the last <size> results of <kind:combined|agent|workload> status 189 // for <unitName> unit 190 func (c *Client) UnitStatusHistory(kind params.HistoryKind, unitName string, size int) (*params.UnitStatusHistory, error) { 191 var results params.UnitStatusHistory 192 args := params.StatusHistory{ 193 Kind: kind, 194 Size: size, 195 Name: unitName, 196 } 197 err := c.facade.FacadeCall("UnitStatusHistory", args, &results) 198 if err != nil { 199 if params.IsCodeNotImplemented(err) { 200 return ¶ms.UnitStatusHistory{}, errors.NotImplementedf("UnitStatusHistory") 201 } 202 return ¶ms.UnitStatusHistory{}, errors.Trace(err) 203 } 204 return &results, nil 205 } 206 207 // LegacyStatus is a stub version of Status that 1.16 introduced. Should be 208 // removed along with structs when api versioning makes it safe to do so. 209 func (c *Client) LegacyStatus() (*params.LegacyStatus, error) { 210 var result params.LegacyStatus 211 if err := c.facade.FacadeCall("Status", nil, &result); err != nil { 212 return nil, err 213 } 214 return &result, nil 215 } 216 217 // ServiceSet sets configuration options on a service. 218 func (c *Client) ServiceSet(service string, options map[string]string) error { 219 p := params.ServiceSet{ 220 ServiceName: service, 221 Options: options, 222 } 223 // TODO(Nate): Put this back to ServiceSet when the GUI stops expecting 224 // ServiceSet to unset values set to an empty string. 225 return c.facade.FacadeCall("NewServiceSetForClientAPI", p, nil) 226 } 227 228 // ServiceUnset resets configuration options on a service. 229 func (c *Client) ServiceUnset(service string, options []string) error { 230 p := params.ServiceUnset{ 231 ServiceName: service, 232 Options: options, 233 } 234 return c.facade.FacadeCall("ServiceUnset", p, nil) 235 } 236 237 // Resolved clears errors on a unit. 238 func (c *Client) Resolved(unit string, retry bool) error { 239 p := params.Resolved{ 240 UnitName: unit, 241 Retry: retry, 242 } 243 return c.facade.FacadeCall("Resolved", p, nil) 244 } 245 246 // RetryProvisioning updates the provisioning status of a machine allowing the 247 // provisioner to retry. 248 func (c *Client) RetryProvisioning(machines ...names.MachineTag) ([]params.ErrorResult, error) { 249 p := params.Entities{} 250 p.Entities = make([]params.Entity, len(machines)) 251 for i, machine := range machines { 252 p.Entities[i] = params.Entity{Tag: machine.String()} 253 } 254 var results params.ErrorResults 255 err := c.facade.FacadeCall("RetryProvisioning", p, &results) 256 return results.Results, err 257 } 258 259 // PublicAddress returns the public address of the specified 260 // machine or unit. For a machine, target is an id not a tag. 261 func (c *Client) PublicAddress(target string) (string, error) { 262 var results params.PublicAddressResults 263 p := params.PublicAddress{Target: target} 264 err := c.facade.FacadeCall("PublicAddress", p, &results) 265 return results.PublicAddress, err 266 } 267 268 // PrivateAddress returns the private address of the specified 269 // machine or unit. 270 func (c *Client) PrivateAddress(target string) (string, error) { 271 var results params.PrivateAddressResults 272 p := params.PrivateAddress{Target: target} 273 err := c.facade.FacadeCall("PrivateAddress", p, &results) 274 return results.PrivateAddress, err 275 } 276 277 // ServiceSetYAML sets configuration options on a service 278 // given options in YAML format. 279 func (c *Client) ServiceSetYAML(service string, yaml string) error { 280 p := params.ServiceSetYAML{ 281 ServiceName: service, 282 Config: yaml, 283 } 284 return c.facade.FacadeCall("ServiceSetYAML", p, nil) 285 } 286 287 // ServiceGet returns the configuration for the named service. 288 func (c *Client) ServiceGet(service string) (*params.ServiceGetResults, error) { 289 var results params.ServiceGetResults 290 params := params.ServiceGet{ServiceName: service} 291 err := c.facade.FacadeCall("ServiceGet", params, &results) 292 return &results, err 293 } 294 295 // AddRelation adds a relation between the specified endpoints and returns the relation info. 296 func (c *Client) AddRelation(endpoints ...string) (*params.AddRelationResults, error) { 297 var addRelRes params.AddRelationResults 298 params := params.AddRelation{Endpoints: endpoints} 299 err := c.facade.FacadeCall("AddRelation", params, &addRelRes) 300 return &addRelRes, err 301 } 302 303 // DestroyRelation removes the relation between the specified endpoints. 304 func (c *Client) DestroyRelation(endpoints ...string) error { 305 params := params.DestroyRelation{Endpoints: endpoints} 306 return c.facade.FacadeCall("DestroyRelation", params, nil) 307 } 308 309 // ServiceCharmRelations returns the service's charms relation names. 310 func (c *Client) ServiceCharmRelations(service string) ([]string, error) { 311 var results params.ServiceCharmRelationsResults 312 params := params.ServiceCharmRelations{ServiceName: service} 313 err := c.facade.FacadeCall("ServiceCharmRelations", params, &results) 314 return results.CharmRelations, err 315 } 316 317 // AddMachines1dot18 adds new machines with the supplied parameters. 318 // 319 // TODO(axw) 2014-04-11 #XXX 320 // This exists for backwards compatibility; 321 // We cannot remove this code while clients > 1.20 need to talk to 1.18 322 // servers (which is something we need for an undetermined amount of time). 323 func (c *Client) AddMachines1dot18(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { 324 args := params.AddMachines{ 325 MachineParams: machineParams, 326 } 327 results := new(params.AddMachinesResults) 328 err := c.facade.FacadeCall("AddMachines", args, results) 329 return results.Machines, err 330 } 331 332 // AddMachines adds new machines with the supplied parameters. 333 func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { 334 args := params.AddMachines{ 335 MachineParams: machineParams, 336 } 337 results := new(params.AddMachinesResults) 338 err := c.facade.FacadeCall("AddMachinesV2", args, results) 339 return results.Machines, err 340 } 341 342 // ProvisioningScript returns a shell script that, when run, 343 // provisions a machine agent on the machine executing the script. 344 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (script string, err error) { 345 var result params.ProvisioningScriptResult 346 if err = c.facade.FacadeCall("ProvisioningScript", args, &result); err != nil { 347 return "", err 348 } 349 return result.Script, nil 350 } 351 352 // DestroyMachines removes a given set of machines. 353 func (c *Client) DestroyMachines(machines ...string) error { 354 params := params.DestroyMachines{MachineNames: machines} 355 return c.facade.FacadeCall("DestroyMachines", params, nil) 356 } 357 358 // ForceDestroyMachines removes a given set of machines and all associated units. 359 func (c *Client) ForceDestroyMachines(machines ...string) error { 360 params := params.DestroyMachines{Force: true, MachineNames: machines} 361 return c.facade.FacadeCall("DestroyMachines", params, nil) 362 } 363 364 // ServiceExpose changes the juju-managed firewall to expose any ports that 365 // were also explicitly marked by units as open. 366 func (c *Client) ServiceExpose(service string) error { 367 params := params.ServiceExpose{ServiceName: service} 368 return c.facade.FacadeCall("ServiceExpose", params, nil) 369 } 370 371 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 372 // were also explicitly marked by units as open. 373 func (c *Client) ServiceUnexpose(service string) error { 374 params := params.ServiceUnexpose{ServiceName: service} 375 return c.facade.FacadeCall("ServiceUnexpose", params, nil) 376 } 377 378 // ServiceDeployWithNetworks works exactly like ServiceDeploy, but 379 // allows the specification of requested networks that must be present 380 // on the machines where the service is deployed. Another way to specify 381 // networks to include/exclude is using constraints. 382 func (c *Client) ServiceDeployWithNetworks( 383 charmURL string, 384 serviceName string, 385 numUnits int, 386 configYAML string, 387 cons constraints.Value, 388 toMachineSpec string, 389 networks []string, 390 ) error { 391 params := params.ServiceDeploy{ 392 ServiceName: serviceName, 393 CharmUrl: charmURL, 394 NumUnits: numUnits, 395 ConfigYAML: configYAML, 396 Constraints: cons, 397 ToMachineSpec: toMachineSpec, 398 Networks: networks, 399 } 400 return c.facade.FacadeCall("ServiceDeployWithNetworks", params, nil) 401 } 402 403 // ServiceDeploy obtains the charm, either locally or from the charm store, 404 // and deploys it. 405 func (c *Client) ServiceDeploy(charmURL string, serviceName string, numUnits int, configYAML string, cons constraints.Value, toMachineSpec string) error { 406 params := params.ServiceDeploy{ 407 ServiceName: serviceName, 408 CharmUrl: charmURL, 409 NumUnits: numUnits, 410 ConfigYAML: configYAML, 411 Constraints: cons, 412 ToMachineSpec: toMachineSpec, 413 } 414 return c.facade.FacadeCall("ServiceDeploy", params, nil) 415 } 416 417 // ServiceUpdate updates the service attributes, including charm URL, 418 // minimum number of units, settings and constraints. 419 // TODO(frankban) deprecate redundant API calls that this supercedes. 420 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 421 return c.facade.FacadeCall("ServiceUpdate", args, nil) 422 } 423 424 // ServiceSetCharm sets the charm for a given service. 425 func (c *Client) ServiceSetCharm(serviceName string, charmUrl string, force bool) error { 426 args := params.ServiceSetCharm{ 427 ServiceName: serviceName, 428 CharmUrl: charmUrl, 429 Force: force, 430 } 431 return c.facade.FacadeCall("ServiceSetCharm", args, nil) 432 } 433 434 // ServiceGetCharmURL returns the charm URL the given service is 435 // running at present. 436 func (c *Client) ServiceGetCharmURL(serviceName string) (*charm.URL, error) { 437 result := new(params.StringResult) 438 args := params.ServiceGet{ServiceName: serviceName} 439 err := c.facade.FacadeCall("ServiceGetCharmURL", args, &result) 440 if err != nil { 441 return nil, err 442 } 443 return charm.ParseURL(result.Result) 444 } 445 446 // AddServiceUnits adds a given number of units to a service. 447 func (c *Client) AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error) { 448 args := params.AddServiceUnits{ 449 ServiceName: service, 450 NumUnits: numUnits, 451 ToMachineSpec: machineSpec, 452 } 453 results := new(params.AddServiceUnitsResults) 454 err := c.facade.FacadeCall("AddServiceUnits", args, results) 455 return results.Units, err 456 } 457 458 // AddServiceUnitsWithPlacement adds a given number of units to a service using the specified 459 // placement directives to assign units to machines. 460 func (c *Client) AddServiceUnitsWithPlacement(service string, numUnits int, placement []*instance.Placement) ([]string, error) { 461 args := params.AddServiceUnits{ 462 ServiceName: service, 463 NumUnits: numUnits, 464 Placement: placement, 465 } 466 results := new(params.AddServiceUnitsResults) 467 err := c.facade.FacadeCall("AddServiceUnitsWithPlacement", args, results) 468 return results.Units, err 469 } 470 471 // DestroyServiceUnits decreases the number of units dedicated to a service. 472 func (c *Client) DestroyServiceUnits(unitNames ...string) error { 473 params := params.DestroyServiceUnits{unitNames} 474 return c.facade.FacadeCall("DestroyServiceUnits", params, nil) 475 } 476 477 // ServiceDestroy destroys a given service. 478 func (c *Client) ServiceDestroy(service string) error { 479 params := params.ServiceDestroy{ 480 ServiceName: service, 481 } 482 return c.facade.FacadeCall("ServiceDestroy", params, nil) 483 } 484 485 // GetServiceConstraints returns the constraints for the given service. 486 func (c *Client) GetServiceConstraints(service string) (constraints.Value, error) { 487 results := new(params.GetConstraintsResults) 488 err := c.facade.FacadeCall("GetServiceConstraints", params.GetServiceConstraints{service}, results) 489 return results.Constraints, err 490 } 491 492 // GetEnvironmentConstraints returns the constraints for the environment. 493 func (c *Client) GetEnvironmentConstraints() (constraints.Value, error) { 494 results := new(params.GetConstraintsResults) 495 err := c.facade.FacadeCall("GetEnvironmentConstraints", nil, results) 496 return results.Constraints, err 497 } 498 499 // SetServiceConstraints specifies the constraints for the given service. 500 func (c *Client) SetServiceConstraints(service string, constraints constraints.Value) error { 501 params := params.SetConstraints{ 502 ServiceName: service, 503 Constraints: constraints, 504 } 505 return c.facade.FacadeCall("SetServiceConstraints", params, nil) 506 } 507 508 // SetEnvironmentConstraints specifies the constraints for the environment. 509 func (c *Client) SetEnvironmentConstraints(constraints constraints.Value) error { 510 params := params.SetConstraints{ 511 Constraints: constraints, 512 } 513 return c.facade.FacadeCall("SetEnvironmentConstraints", params, nil) 514 } 515 516 // CharmInfo holds information about a charm. 517 type CharmInfo struct { 518 Revision int 519 URL string 520 Config *charm.Config 521 Meta *charm.Meta 522 Actions *charm.Actions 523 } 524 525 // CharmInfo returns information about the requested charm. 526 func (c *Client) CharmInfo(charmURL string) (*CharmInfo, error) { 527 args := params.CharmInfo{CharmURL: charmURL} 528 info := new(CharmInfo) 529 if err := c.facade.FacadeCall("CharmInfo", args, info); err != nil { 530 return nil, err 531 } 532 return info, nil 533 } 534 535 // EnvironmentInfo holds information about the Juju environment. 536 type EnvironmentInfo struct { 537 DefaultSeries string 538 ProviderType string 539 Name string 540 UUID string 541 ServerUUID string 542 } 543 544 // EnvironmentInfo returns details about the Juju environment. 545 func (c *Client) EnvironmentInfo() (*EnvironmentInfo, error) { 546 info := new(EnvironmentInfo) 547 err := c.facade.FacadeCall("EnvironmentInfo", nil, info) 548 return info, err 549 } 550 551 // EnvironmentUUID returns the environment UUID from the client connection. 552 func (c *Client) EnvironmentUUID() string { 553 tag, err := c.st.EnvironTag() 554 if err != nil { 555 logger.Warningf("environ tag not an environ: %v", err) 556 return "" 557 } 558 return tag.Id() 559 } 560 561 // ShareEnvironment allows the given users access to the environment. 562 func (c *Client) ShareEnvironment(users ...names.UserTag) error { 563 var args params.ModifyEnvironUsers 564 for _, user := range users { 565 if &user != nil { 566 args.Changes = append(args.Changes, params.ModifyEnvironUser{ 567 UserTag: user.String(), 568 Action: params.AddEnvUser, 569 }) 570 } 571 } 572 573 var result params.ErrorResults 574 err := c.facade.FacadeCall("ShareEnvironment", args, &result) 575 if err != nil { 576 return errors.Trace(err) 577 } 578 579 for i, r := range result.Results { 580 if r.Error != nil && r.Error.Code == params.CodeAlreadyExists { 581 logger.Warningf("environment is already shared with %s", users[i].Username()) 582 result.Results[i].Error = nil 583 } 584 } 585 return result.Combine() 586 } 587 588 // EnvironmentUserInfo returns information on all users in the environment. 589 func (c *Client) EnvironmentUserInfo() ([]params.EnvUserInfo, error) { 590 var results params.EnvUserInfoResults 591 err := c.facade.FacadeCall("EnvUserInfo", nil, &results) 592 if err != nil { 593 return nil, errors.Trace(err) 594 } 595 596 info := []params.EnvUserInfo{} 597 for i, result := range results.Results { 598 if result.Result == nil { 599 return nil, errors.Errorf("unexpected nil result at position %d", i) 600 } 601 info = append(info, *result.Result) 602 } 603 return info, nil 604 } 605 606 // UnshareEnvironment removes access to the environment for the given users. 607 func (c *Client) UnshareEnvironment(users ...names.UserTag) error { 608 var args params.ModifyEnvironUsers 609 for _, user := range users { 610 if &user != nil { 611 args.Changes = append(args.Changes, params.ModifyEnvironUser{ 612 UserTag: user.String(), 613 Action: params.RemoveEnvUser, 614 }) 615 } 616 } 617 618 var result params.ErrorResults 619 err := c.facade.FacadeCall("ShareEnvironment", args, &result) 620 if err != nil { 621 return errors.Trace(err) 622 } 623 624 for i, r := range result.Results { 625 if r.Error != nil && r.Error.Code == params.CodeNotFound { 626 logger.Warningf("environment was not previously shared with user %s", users[i].Username()) 627 result.Results[i].Error = nil 628 } 629 } 630 return result.Combine() 631 } 632 633 // WatchAll holds the id of the newly-created AllWatcher/AllEnvWatcher. 634 type WatchAll struct { 635 AllWatcherId string 636 } 637 638 // WatchAll returns an AllWatcher, from which you can request the Next 639 // collection of Deltas. 640 func (c *Client) WatchAll() (*AllWatcher, error) { 641 info := new(WatchAll) 642 if err := c.facade.FacadeCall("WatchAll", nil, info); err != nil { 643 return nil, err 644 } 645 return NewAllWatcher(c.st, &info.AllWatcherId), nil 646 } 647 648 // GetAnnotations returns annotations that have been set on the given entity. 649 // This API is now deprecated - "Annotations" client should be used instead. 650 // TODO(anastasiamac) remove for Juju 2.x 651 func (c *Client) GetAnnotations(tag string) (map[string]string, error) { 652 args := params.GetAnnotations{tag} 653 ann := new(params.GetAnnotationsResults) 654 err := c.facade.FacadeCall("GetAnnotations", args, ann) 655 return ann.Annotations, err 656 } 657 658 // SetAnnotations sets the annotation pairs on the given entity. 659 // Currently annotations are supported on machines, services, 660 // units and the environment itself. 661 // This API is now deprecated - "Annotations" client should be used instead. 662 // TODO(anastasiamac) remove for Juju 2.x 663 func (c *Client) SetAnnotations(tag string, pairs map[string]string) error { 664 args := params.SetAnnotations{tag, pairs} 665 return c.facade.FacadeCall("SetAnnotations", args, nil) 666 } 667 668 // Close closes the Client's underlying State connection 669 // Client is unique among the api.State facades in closing its own State 670 // connection, but it is conventional to use a Client object without any access 671 // to its underlying state connection. 672 func (c *Client) Close() error { 673 return c.st.Close() 674 } 675 676 // EnvironmentGet returns all environment settings. 677 func (c *Client) EnvironmentGet() (map[string]interface{}, error) { 678 result := params.EnvironmentConfigResults{} 679 err := c.facade.FacadeCall("EnvironmentGet", nil, &result) 680 return result.Config, err 681 } 682 683 // EnvironmentSet sets the given key-value pairs in the environment. 684 func (c *Client) EnvironmentSet(config map[string]interface{}) error { 685 args := params.EnvironmentSet{Config: config} 686 return c.facade.FacadeCall("EnvironmentSet", args, nil) 687 } 688 689 // EnvironmentUnset sets the given key-value pairs in the environment. 690 func (c *Client) EnvironmentUnset(keys ...string) error { 691 args := params.EnvironmentUnset{Keys: keys} 692 return c.facade.FacadeCall("EnvironmentUnset", args, nil) 693 } 694 695 // SetEnvironAgentVersion sets the environment agent-version setting 696 // to the given value. 697 func (c *Client) SetEnvironAgentVersion(version version.Number) error { 698 args := params.SetEnvironAgentVersion{Version: version} 699 return c.facade.FacadeCall("SetEnvironAgentVersion", args, nil) 700 } 701 702 // AbortCurrentUpgrade aborts and archives the current upgrade 703 // synchronisation record, if any. 704 func (c *Client) AbortCurrentUpgrade() error { 705 return c.facade.FacadeCall("AbortCurrentUpgrade", nil, nil) 706 } 707 708 // FindTools returns a List containing all tools matching the specified parameters. 709 func (c *Client) FindTools(majorVersion, minorVersion int, series, arch string) (result params.FindToolsResult, err error) { 710 args := params.FindToolsParams{ 711 MajorVersion: majorVersion, 712 MinorVersion: minorVersion, 713 Arch: arch, 714 Series: series, 715 } 716 err = c.facade.FacadeCall("FindTools", args, &result) 717 return result, err 718 } 719 720 // RunOnAllMachines runs the command on all the machines with the specified 721 // timeout. 722 func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.RunResult, error) { 723 var results params.RunResults 724 args := params.RunParams{Commands: commands, Timeout: timeout} 725 err := c.facade.FacadeCall("RunOnAllMachines", args, &results) 726 return results.Results, err 727 } 728 729 // Run the Commands specified on the machines identified through the ids 730 // provided in the machines, services and units slices. 731 func (c *Client) Run(run params.RunParams) ([]params.RunResult, error) { 732 var results params.RunResults 733 err := c.facade.FacadeCall("Run", run, &results) 734 return results.Results, err 735 } 736 737 // DestroyEnvironment puts the environment into a "dying" state, 738 // and removes all non-manager machine instances. DestroyEnvironment 739 // will fail if there are any manually-provisioned non-manager machines 740 // in state. 741 func (c *Client) DestroyEnvironment() error { 742 return c.facade.FacadeCall("DestroyEnvironment", nil, nil) 743 } 744 745 // AddLocalCharm prepares the given charm with a local: schema in its 746 // URL, and uploads it via the API server, returning the assigned 747 // charm URL. If the API server does not support charm uploads, an 748 // error satisfying params.IsCodeNotImplemented() is returned. 749 func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) { 750 if curl.Schema != "local" { 751 return nil, errors.Errorf("expected charm URL with local: schema, got %q", curl.String()) 752 } 753 // Package the charm for uploading. 754 var archive *os.File 755 switch ch := ch.(type) { 756 case *charm.CharmDir: 757 var err error 758 if archive, err = ioutil.TempFile("", "charm"); err != nil { 759 return nil, errors.Annotate(err, "cannot create temp file") 760 } 761 defer os.Remove(archive.Name()) 762 defer archive.Close() 763 if err := ch.ArchiveTo(archive); err != nil { 764 return nil, errors.Annotate(err, "cannot repackage charm") 765 } 766 if _, err := archive.Seek(0, 0); err != nil { 767 return nil, errors.Annotate(err, "cannot rewind packaged charm") 768 } 769 case *charm.CharmArchive: 770 var err error 771 if archive, err = os.Open(ch.Path); err != nil { 772 return nil, errors.Annotate(err, "cannot read charm archive") 773 } 774 defer archive.Close() 775 default: 776 return nil, errors.Errorf("unknown charm type %T", ch) 777 } 778 779 endPoint, err := c.apiEndpoint("charms", "series="+curl.Series) 780 if err != nil { 781 return nil, errors.Trace(err) 782 } 783 784 // wrap archive in a noopCloser to prevent the underlying transport closing 785 // the request body. This is neccessary to prevent a data race on the underlying 786 // *os.File as the http transport _may_ issue Close once the body is sent, or it 787 // may not if there is an error. 788 noop := &noopCloser{archive} 789 req, err := http.NewRequest("POST", endPoint, noop) 790 if err != nil { 791 return nil, errors.Annotate(err, "cannot create upload request") 792 } 793 req.SetBasicAuth(c.st.tag, c.st.password) 794 req.Header.Set("Content-Type", "application/zip") 795 796 // Send the request. 797 798 // BUG(dimitern) 2013-12-17 bug #1261780 799 // Due to issues with go 1.1.2, fixed later, we cannot use a 800 // regular TLS client with the CACert here, because we get "x509: 801 // cannot validate certificate for 127.0.0.1 because it doesn't 802 // contain any IP SANs". Once we use a later go version, this 803 // should be changed to connect to the API server with a regular 804 // HTTP+TLS enabled client, using the CACert (possily cached, like 805 // the tag and password) passed in api.Open()'s info argument. 806 resp, err := utils.GetNonValidatingHTTPClient().Do(req) 807 if err != nil { 808 return nil, errors.Annotate(err, "cannot upload charm") 809 } 810 defer resp.Body.Close() 811 812 // Now parse the response & return. 813 body, err := ioutil.ReadAll(resp.Body) 814 if err != nil { 815 return nil, errors.Annotate(err, "cannot read charm upload response") 816 } 817 if resp.StatusCode != http.StatusOK { 818 return nil, errors.Errorf("charm upload failed: %v (%s)", resp.StatusCode, bytes.TrimSpace(body)) 819 } 820 821 var jsonResponse params.CharmsResponse 822 if err := json.Unmarshal(body, &jsonResponse); err != nil { 823 return nil, errors.Annotate(err, "cannot unmarshal upload response") 824 } 825 if jsonResponse.Error != "" { 826 return nil, errors.Errorf("error uploading charm: %v", jsonResponse.Error) 827 } 828 return charm.MustParseURL(jsonResponse.CharmURL), nil 829 } 830 831 // noopCloser implements io.ReadCloser, but does not close the underlying io.ReadCloser. 832 // This is necessary to ensure the ownership of io.ReadCloser implementations that are 833 // passed to the net/http Transport which may (under some circumstances), call Close on 834 // the body passed to a request. 835 type noopCloser struct { 836 io.ReadCloser 837 } 838 839 func (n *noopCloser) Close() error { 840 841 // do not propogate the Close method to the underlying ReadCloser. 842 return nil 843 } 844 845 func (c *Client) apiEndpoint(destination, query string) (string, error) { 846 root, err := c.apiRoot() 847 if err != nil { 848 return "", errors.Trace(err) 849 } 850 851 upURL := url.URL{ 852 Scheme: c.st.serverScheme, 853 Host: c.st.Addr(), 854 Path: path.Join(root, destination), 855 RawQuery: query, 856 } 857 return upURL.String(), nil 858 } 859 860 func (c *Client) apiRoot() (string, error) { 861 var apiRoot string 862 if _, err := c.st.ServerTag(); err == nil { 863 envTag, err := c.st.EnvironTag() 864 if err != nil { 865 return "", errors.Annotate(err, "cannot get API endpoint address") 866 } 867 868 apiRoot = fmt.Sprintf("/environment/%s/", envTag.Id()) 869 } else { 870 // If the server tag is not set, then the agent version is < 1.23. We 871 // use the old API endpoint for backwards compatibility. 872 apiRoot = "/" 873 } 874 return apiRoot, nil 875 } 876 877 // AddCharm adds the given charm URL (which must include revision) to 878 // the environment, if it does not exist yet. Local charms are not 879 // supported, only charm store URLs. See also AddLocalCharm() in the 880 // client-side API. 881 // 882 // If the AddCharm API call fails because of an authorization error 883 // when retrieving the charm from the charm store, an error 884 // satisfying params.IsCodeUnauthorized will be returned. 885 func (c *Client) AddCharm(curl *charm.URL) error { 886 args := params.CharmURL{ 887 URL: curl.String(), 888 } 889 return c.facade.FacadeCall("AddCharm", args, nil) 890 } 891 892 // AddCharmWithAuthorization is like AddCharm except it also provides 893 // the given charmstore macaroon for the juju server to use when 894 // obtaining the charm from the charm store. The macaroon is 895 // conventionally obtained from the /delegatable-macaroon endpoint in 896 // the charm store. 897 // 898 // If the AddCharmWithAuthorization API call fails because of an 899 // authorization error when retrieving the charm from the charm store, 900 // an error satisfying params.IsCodeUnauthorized will be returned. 901 func (c *Client) AddCharmWithAuthorization(curl *charm.URL, csMac *macaroon.Macaroon) error { 902 args := params.AddCharmWithAuthorization{ 903 URL: curl.String(), 904 CharmStoreMacaroon: csMac, 905 } 906 return c.facade.FacadeCall("AddCharmWithAuthorization", args, nil) 907 } 908 909 // ResolveCharm resolves the best available charm URLs with series, for charm 910 // locations without a series specified. 911 func (c *Client) ResolveCharm(ref *charm.Reference) (*charm.URL, error) { 912 args := params.ResolveCharms{References: []charm.Reference{*ref}} 913 result := new(params.ResolveCharmResults) 914 if err := c.facade.FacadeCall("ResolveCharms", args, result); err != nil { 915 return nil, err 916 } 917 if len(result.URLs) == 0 { 918 return nil, errors.New("unexpected empty response") 919 } 920 urlInfo := result.URLs[0] 921 if urlInfo.Error != "" { 922 return nil, errors.New(urlInfo.Error) 923 } 924 return urlInfo.URL, nil 925 } 926 927 // UploadTools uploads tools at the specified location to the API server over HTTPS. 928 func (c *Client) UploadTools(r io.Reader, vers version.Binary, additionalSeries ...string) (*tools.Tools, error) { 929 // Prepare the upload request. 930 query := fmt.Sprintf("binaryVersion=%s&series=%s", 931 vers, 932 strings.Join(additionalSeries, ","), 933 ) 934 935 endPoint, err := c.apiEndpoint("tools", query) 936 if err != nil { 937 return nil, errors.Trace(err) 938 } 939 940 req, err := http.NewRequest("POST", endPoint, r) 941 if err != nil { 942 return nil, errors.Annotate(err, "cannot create upload request") 943 } 944 req.SetBasicAuth(c.st.tag, c.st.password) 945 req.Header.Set("Content-Type", "application/x-tar-gz") 946 947 // Send the request. 948 949 // BUG(dimitern) 2013-12-17 bug #1261780 950 // Due to issues with go 1.1.2, fixed later, we cannot use a 951 // regular TLS client with the CACert here, because we get "x509: 952 // cannot validate certificate for 127.0.0.1 because it doesn't 953 // contain any IP SANs". Once we use a later go version, this 954 // should be changed to connect to the API server with a regular 955 // HTTP+TLS enabled client, using the CACert (possily cached, like 956 // the tag and password) passed in api.Open()'s info argument. 957 resp, err := utils.GetNonValidatingHTTPClient().Do(req) 958 if err != nil { 959 return nil, errors.Annotate(err, "cannot upload tools") 960 } 961 defer resp.Body.Close() 962 963 // Now parse the response & return. 964 body, err := ioutil.ReadAll(resp.Body) 965 if err != nil { 966 return nil, errors.Annotate(err, "cannot read tools upload response") 967 } 968 if resp.StatusCode != http.StatusOK { 969 message := fmt.Sprintf("%s", bytes.TrimSpace(body)) 970 if resp.StatusCode == http.StatusBadRequest && strings.Contains(message, params.CodeOperationBlocked) { 971 // Operation Blocked errors must contain correct error code and message. 972 return nil, ¶ms.Error{Code: params.CodeOperationBlocked, Message: message} 973 } 974 return nil, errors.Errorf("tools upload failed: %v (%s)", resp.StatusCode, message) 975 } 976 977 var jsonResponse params.ToolsResult 978 if err := json.Unmarshal(body, &jsonResponse); err != nil { 979 return nil, errors.Annotate(err, "cannot unmarshal upload response") 980 } 981 if err := jsonResponse.Error; err != nil { 982 return nil, errors.Annotate(err, "error uploading tools") 983 } 984 return jsonResponse.Tools, nil 985 } 986 987 // APIHostPorts returns a slice of network.HostPort for each API server. 988 func (c *Client) APIHostPorts() ([][]network.HostPort, error) { 989 var result params.APIHostPortsResult 990 if err := c.facade.FacadeCall("APIHostPorts", nil, &result); err != nil { 991 return nil, err 992 } 993 return result.NetworkHostsPorts(), nil 994 } 995 996 // EnsureAvailability ensures the availability of Juju state servers. 997 // DEPRECATED: remove when we stop supporting 1.20 and earlier servers. 998 // This API is now on the HighAvailability facade. 999 func (c *Client) EnsureAvailability(numStateServers int, cons constraints.Value, series string) (params.StateServersChanges, error) { 1000 var results params.StateServersChangeResults 1001 envTag, err := c.st.EnvironTag() 1002 if err != nil { 1003 return params.StateServersChanges{}, errors.Trace(err) 1004 } 1005 arg := params.StateServersSpecs{ 1006 Specs: []params.StateServersSpec{{ 1007 EnvironTag: envTag.String(), 1008 NumStateServers: numStateServers, 1009 Constraints: cons, 1010 Series: series, 1011 }}} 1012 err = c.facade.FacadeCall("EnsureAvailability", arg, &results) 1013 if err != nil { 1014 return params.StateServersChanges{}, err 1015 } 1016 if len(results.Results) != 1 { 1017 return params.StateServersChanges{}, errors.Errorf("expected 1 result, got %d", len(results.Results)) 1018 } 1019 result := results.Results[0] 1020 if result.Error != nil { 1021 return params.StateServersChanges{}, result.Error 1022 } 1023 return result.Result, nil 1024 } 1025 1026 // AgentVersion reports the version number of the api server. 1027 func (c *Client) AgentVersion() (version.Number, error) { 1028 var result params.AgentVersionResult 1029 if err := c.facade.FacadeCall("AgentVersion", nil, &result); err != nil { 1030 return version.Number{}, err 1031 } 1032 return result.Version, nil 1033 } 1034 1035 // websocketDialConfig is called instead of websocket.DialConfig so we can 1036 // override it in tests. 1037 var websocketDialConfig = func(config *websocket.Config) (io.ReadCloser, error) { 1038 return websocket.DialConfig(config) 1039 } 1040 1041 // DebugLogParams holds parameters for WatchDebugLog that control the 1042 // filtering of the log messages. If the structure is zero initialized, the 1043 // entire log file is sent back starting from the end, and until the user 1044 // closes the connection. 1045 type DebugLogParams struct { 1046 // IncludeEntity lists entity tags to include in the response. Tags may 1047 // finish with a '*' to match a prefix e.g.: unit-mysql-*, machine-2. If 1048 // none are set, then all lines are considered included. 1049 IncludeEntity []string 1050 // IncludeModule lists logging modules to include in the response. If none 1051 // are set all modules are considered included. If a module is specified, 1052 // all the submodules also match. 1053 IncludeModule []string 1054 // ExcludeEntity lists entity tags to exclude from the response. As with 1055 // IncludeEntity the values may finish with a '*'. 1056 ExcludeEntity []string 1057 // ExcludeModule lists logging modules to exclude from the resposne. If a 1058 // module is specified, all the submodules are also excluded. 1059 ExcludeModule []string 1060 // Limit defines the maximum number of lines to return. Once this many 1061 // have been sent, the socket is closed. If zero, all filtered lines are 1062 // sent down the connection until the client closes the connection. 1063 Limit uint 1064 // Backlog tells the server to try to go back this many lines before 1065 // starting filtering. If backlog is zero and replay is false, then there 1066 // may be an initial delay until the next matching log message is written. 1067 Backlog uint 1068 // Level specifies the minimum logging level to be sent back in the response. 1069 Level loggo.Level 1070 // Replay tells the server to start at the start of the log file rather 1071 // than the end. If replay is true, backlog is ignored. 1072 Replay bool 1073 } 1074 1075 // WatchDebugLog returns a ReadCloser that the caller can read the log 1076 // lines from. Only log lines that match the filtering specified in 1077 // the DebugLogParams are returned. It returns an error that satisfies 1078 // errors.IsNotImplemented when the API server does not support the 1079 // end-point. 1080 // 1081 // TODO(dimitern) We already have errors.IsNotImplemented - why do we 1082 // need to define a different error for this purpose here? 1083 func (c *Client) WatchDebugLog(args DebugLogParams) (io.ReadCloser, error) { 1084 // The websocket connection just hangs if the server doesn't have the log 1085 // end point. So do a version check, as version was added at the same time 1086 // as the remote end point. 1087 _, err := c.AgentVersion() 1088 if err != nil { 1089 return nil, errors.NotSupportedf("WatchDebugLog") 1090 } 1091 // Prepare URL. 1092 attrs := url.Values{} 1093 if args.Replay { 1094 attrs.Set("replay", fmt.Sprint(args.Replay)) 1095 } 1096 if args.Limit > 0 { 1097 attrs.Set("maxLines", fmt.Sprint(args.Limit)) 1098 } 1099 if args.Backlog > 0 { 1100 attrs.Set("backlog", fmt.Sprint(args.Backlog)) 1101 } 1102 if args.Level != loggo.UNSPECIFIED { 1103 attrs.Set("level", fmt.Sprint(args.Level)) 1104 } 1105 attrs["includeEntity"] = args.IncludeEntity 1106 attrs["includeModule"] = args.IncludeModule 1107 attrs["excludeEntity"] = args.ExcludeEntity 1108 attrs["excludeModule"] = args.ExcludeModule 1109 1110 path := "/log" 1111 if _, ok := c.st.ServerVersion(); ok { 1112 // If the server version is set, then we know the server is capable of 1113 // serving debug log at the environment path. We also fully expect 1114 // that the server has returned a valid environment tag. 1115 envTag, err := c.st.EnvironTag() 1116 if err != nil { 1117 return nil, errors.Annotate(err, "very unexpected") 1118 } 1119 path = fmt.Sprintf("/environment/%s/log", envTag.Id()) 1120 } 1121 1122 target := url.URL{ 1123 Scheme: "wss", 1124 Host: c.st.addr, 1125 Path: path, 1126 RawQuery: attrs.Encode(), 1127 } 1128 cfg, err := websocket.NewConfig(target.String(), "http://localhost/") 1129 cfg.Header = utils.BasicAuthHeader(c.st.tag, c.st.password) 1130 cfg.TlsConfig = &tls.Config{RootCAs: c.st.certPool, ServerName: "juju-apiserver"} 1131 connection, err := websocketDialConfig(cfg) 1132 if err != nil { 1133 return nil, err 1134 } 1135 // Read the initial error and translate to a real error. 1136 // Read up to the first new line character. We can't use bufio here as it 1137 // reads too much from the reader. 1138 line := make([]byte, 4096) 1139 n, err := connection.Read(line) 1140 if err != nil { 1141 return nil, errors.Annotate(err, "unable to read initial response") 1142 } 1143 line = line[0:n] 1144 1145 logger.Debugf("initial line: %q", line) 1146 var errResult params.ErrorResult 1147 err = json.Unmarshal(line, &errResult) 1148 if err != nil { 1149 return nil, errors.Annotate(err, "unable to unmarshal initial response") 1150 } 1151 if errResult.Error != nil { 1152 return nil, errResult.Error 1153 } 1154 return connection, nil 1155 }