github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/state/api/client.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api 5 6 import ( 7 "crypto/tls" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "os" 15 "strings" 16 "time" 17 18 "code.google.com/p/go.net/websocket" 19 "github.com/juju/charm" 20 "github.com/juju/errors" 21 "github.com/juju/loggo" 22 "github.com/juju/utils" 23 24 "github.com/juju/juju/constraints" 25 "github.com/juju/juju/instance" 26 "github.com/juju/juju/network" 27 "github.com/juju/juju/state/api/params" 28 "github.com/juju/juju/tools" 29 "github.com/juju/juju/version" 30 ) 31 32 // Client represents the client-accessible part of the state. 33 type Client struct { 34 st *State 35 } 36 37 // NetworksSpecification holds the enabled and disabled networks for a 38 // service. 39 type NetworksSpecification struct { 40 Enabled []string 41 Disabled []string 42 } 43 44 func (c *Client) call(method string, params, result interface{}) error { 45 return c.st.Call("Client", "", method, params, result) 46 } 47 48 // AgentStatus holds status info about a machine or unit agent. 49 type AgentStatus struct { 50 Status params.Status 51 Info string 52 Data params.StatusData 53 Version string 54 Life string 55 Err error 56 } 57 58 // MachineStatus holds status info about a machine. 59 type MachineStatus struct { 60 Agent AgentStatus 61 62 // The following fields mirror fields in AgentStatus (introduced 63 // in 1.19.x). The old fields below are being kept for 64 // compatibility with old clients. They can be removed once API 65 // versioning lands or for 1.21, whichever comes first. 66 AgentState params.Status 67 AgentStateInfo string 68 AgentVersion string 69 Life string 70 Err error 71 72 DNSName string 73 InstanceId instance.Id 74 InstanceState string 75 Series string 76 Id string 77 Containers map[string]MachineStatus 78 Hardware string 79 Jobs []params.MachineJob 80 HasVote bool 81 WantsVote bool 82 } 83 84 // ServiceStatus holds status info about a service. 85 type ServiceStatus struct { 86 Err error 87 Charm string 88 Exposed bool 89 Life string 90 Relations map[string][]string 91 Networks NetworksSpecification 92 CanUpgradeTo string 93 SubordinateTo []string 94 Units map[string]UnitStatus 95 } 96 97 // UnitStatus holds status info about a unit. 98 type UnitStatus struct { 99 Agent AgentStatus 100 101 // See the comment in MachineStatus regarding these fields. 102 AgentState params.Status 103 AgentStateInfo string 104 AgentVersion string 105 Life string 106 Err error 107 108 Machine string 109 OpenedPorts []string 110 PublicAddress string 111 Charm string 112 Subordinates map[string]UnitStatus 113 } 114 115 // RelationStatus holds status info about a relation. 116 type RelationStatus struct { 117 Id int 118 Key string 119 Interface string 120 Scope charm.RelationScope 121 Endpoints []EndpointStatus 122 } 123 124 // EndpointStatus holds status info about a single endpoint 125 type EndpointStatus struct { 126 ServiceName string 127 Name string 128 Role charm.RelationRole 129 Subordinate bool 130 } 131 132 func (epStatus *EndpointStatus) String() string { 133 return epStatus.ServiceName + ":" + epStatus.Name 134 } 135 136 // NetworkStatus holds status info about a network. 137 type NetworkStatus struct { 138 Err error 139 ProviderId network.Id 140 CIDR string 141 VLANTag int 142 } 143 144 // Status holds information about the status of a juju environment. 145 type Status struct { 146 EnvironmentName string 147 Machines map[string]MachineStatus 148 Services map[string]ServiceStatus 149 Networks map[string]NetworkStatus 150 Relations []RelationStatus 151 } 152 153 // Status returns the status of the juju environment. 154 func (c *Client) Status(patterns []string) (*Status, error) { 155 var result Status 156 p := params.StatusParams{Patterns: patterns} 157 if err := c.call("FullStatus", p, &result); err != nil { 158 return nil, err 159 } 160 return &result, nil 161 } 162 163 // LegacyMachineStatus holds just the instance-id of a machine. 164 type LegacyMachineStatus struct { 165 InstanceId string // Not type instance.Id just to match original api. 166 } 167 168 // LegacyStatus holds minimal information on the status of a juju environment. 169 type LegacyStatus struct { 170 Machines map[string]LegacyMachineStatus 171 } 172 173 // LegacyStatus is a stub version of Status that 1.16 introduced. Should be 174 // removed along with structs when api versioning makes it safe to do so. 175 func (c *Client) LegacyStatus() (*LegacyStatus, error) { 176 var result LegacyStatus 177 if err := c.call("Status", nil, &result); err != nil { 178 return nil, err 179 } 180 return &result, nil 181 } 182 183 // ServiceSet sets configuration options on a service. 184 func (c *Client) ServiceSet(service string, options map[string]string) error { 185 p := params.ServiceSet{ 186 ServiceName: service, 187 Options: options, 188 } 189 // TODO(Nate): Put this back to ServiceSet when the GUI stops expecting 190 // ServiceSet to unset values set to an empty string. 191 return c.call("NewServiceSetForClientAPI", p, nil) 192 } 193 194 // ServiceUnset resets configuration options on a service. 195 func (c *Client) ServiceUnset(service string, options []string) error { 196 p := params.ServiceUnset{ 197 ServiceName: service, 198 Options: options, 199 } 200 return c.call("ServiceUnset", p, nil) 201 } 202 203 // Resolved clears errors on a unit. 204 func (c *Client) Resolved(unit string, retry bool) error { 205 p := params.Resolved{ 206 UnitName: unit, 207 Retry: retry, 208 } 209 return c.call("Resolved", p, nil) 210 } 211 212 // RetryProvisioning updates the provisioning status of a machine allowing the 213 // provisioner to retry. 214 func (c *Client) RetryProvisioning(machines ...string) ([]params.ErrorResult, error) { 215 p := params.Entities{} 216 p.Entities = make([]params.Entity, len(machines)) 217 for i, machine := range machines { 218 p.Entities[i] = params.Entity{Tag: machine} 219 } 220 var results params.ErrorResults 221 err := c.st.Call("Client", "", "RetryProvisioning", p, &results) 222 return results.Results, err 223 } 224 225 // PublicAddress returns the public address of the specified 226 // machine or unit. 227 func (c *Client) PublicAddress(target string) (string, error) { 228 var results params.PublicAddressResults 229 p := params.PublicAddress{Target: target} 230 err := c.call("PublicAddress", p, &results) 231 return results.PublicAddress, err 232 } 233 234 // PrivateAddress returns the private address of the specified 235 // machine or unit. 236 func (c *Client) PrivateAddress(target string) (string, error) { 237 var results params.PrivateAddressResults 238 p := params.PrivateAddress{Target: target} 239 err := c.call("PrivateAddress", p, &results) 240 return results.PrivateAddress, err 241 } 242 243 // ServiceSetYAML sets configuration options on a service 244 // given options in YAML format. 245 func (c *Client) ServiceSetYAML(service string, yaml string) error { 246 p := params.ServiceSetYAML{ 247 ServiceName: service, 248 Config: yaml, 249 } 250 return c.call("ServiceSetYAML", p, nil) 251 } 252 253 // ServiceGet returns the configuration for the named service. 254 func (c *Client) ServiceGet(service string) (*params.ServiceGetResults, error) { 255 var results params.ServiceGetResults 256 params := params.ServiceGet{ServiceName: service} 257 err := c.call("ServiceGet", params, &results) 258 return &results, err 259 } 260 261 // AddRelation adds a relation between the specified endpoints and returns the relation info. 262 func (c *Client) AddRelation(endpoints ...string) (*params.AddRelationResults, error) { 263 var addRelRes params.AddRelationResults 264 params := params.AddRelation{Endpoints: endpoints} 265 err := c.call("AddRelation", params, &addRelRes) 266 return &addRelRes, err 267 } 268 269 // DestroyRelation removes the relation between the specified endpoints. 270 func (c *Client) DestroyRelation(endpoints ...string) error { 271 params := params.DestroyRelation{Endpoints: endpoints} 272 return c.call("DestroyRelation", params, nil) 273 } 274 275 // ServiceCharmRelations returns the service's charms relation names. 276 func (c *Client) ServiceCharmRelations(service string) ([]string, error) { 277 var results params.ServiceCharmRelationsResults 278 params := params.ServiceCharmRelations{ServiceName: service} 279 err := c.call("ServiceCharmRelations", params, &results) 280 return results.CharmRelations, err 281 } 282 283 // AddMachines1dot18 adds new machines with the supplied parameters. 284 // 285 // TODO(axw) 2014-04-11 #XXX 286 // This exists for backwards compatibility; remove in 1.21 (client only). 287 func (c *Client) AddMachines1dot18(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { 288 args := params.AddMachines{ 289 MachineParams: machineParams, 290 } 291 results := new(params.AddMachinesResults) 292 err := c.call("AddMachines", args, results) 293 return results.Machines, err 294 } 295 296 // AddMachines adds new machines with the supplied parameters. 297 func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { 298 args := params.AddMachines{ 299 MachineParams: machineParams, 300 } 301 results := new(params.AddMachinesResults) 302 err := c.call("AddMachinesV2", args, results) 303 return results.Machines, err 304 } 305 306 // ProvisioningScript returns a shell script that, when run, 307 // provisions a machine agent on the machine executing the script. 308 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (script string, err error) { 309 var result params.ProvisioningScriptResult 310 if err = c.call("ProvisioningScript", args, &result); err != nil { 311 return "", err 312 } 313 return result.Script, nil 314 } 315 316 // DestroyMachines removes a given set of machines. 317 func (c *Client) DestroyMachines(machines ...string) error { 318 params := params.DestroyMachines{MachineNames: machines} 319 return c.call("DestroyMachines", params, nil) 320 } 321 322 // ForceDestroyMachines removes a given set of machines and all associated units. 323 func (c *Client) ForceDestroyMachines(machines ...string) error { 324 params := params.DestroyMachines{Force: true, MachineNames: machines} 325 return c.call("DestroyMachines", params, nil) 326 } 327 328 // ServiceExpose changes the juju-managed firewall to expose any ports that 329 // were also explicitly marked by units as open. 330 func (c *Client) ServiceExpose(service string) error { 331 params := params.ServiceExpose{ServiceName: service} 332 return c.call("ServiceExpose", params, nil) 333 } 334 335 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 336 // were also explicitly marked by units as open. 337 func (c *Client) ServiceUnexpose(service string) error { 338 params := params.ServiceUnexpose{ServiceName: service} 339 return c.call("ServiceUnexpose", params, nil) 340 } 341 342 // ServiceDeployWithNetworks works exactly like ServiceDeploy, but 343 // allows the specification of requested networks that must be present 344 // on the machines where the service is deployed. Another way to specify 345 // networks to include/exclude is using constraints. 346 func (c *Client) ServiceDeployWithNetworks(charmURL string, serviceName string, numUnits int, configYAML string, cons constraints.Value, toMachineSpec string, networks []string) error { 347 params := params.ServiceDeploy{ 348 ServiceName: serviceName, 349 CharmUrl: charmURL, 350 NumUnits: numUnits, 351 ConfigYAML: configYAML, 352 Constraints: cons, 353 ToMachineSpec: toMachineSpec, 354 Networks: networks, 355 } 356 return c.st.Call("Client", "", "ServiceDeployWithNetworks", params, nil) 357 } 358 359 // ServiceDeploy obtains the charm, either locally or from the charm store, 360 // and deploys it. 361 func (c *Client) ServiceDeploy(charmURL string, serviceName string, numUnits int, configYAML string, cons constraints.Value, toMachineSpec string) error { 362 params := params.ServiceDeploy{ 363 ServiceName: serviceName, 364 CharmUrl: charmURL, 365 NumUnits: numUnits, 366 ConfigYAML: configYAML, 367 Constraints: cons, 368 ToMachineSpec: toMachineSpec, 369 } 370 return c.call("ServiceDeploy", params, nil) 371 } 372 373 // ServiceUpdate updates the service attributes, including charm URL, 374 // minimum number of units, settings and constraints. 375 // TODO(frankban) deprecate redundant API calls that this supercedes. 376 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 377 return c.call("ServiceUpdate", args, nil) 378 } 379 380 // ServiceSetCharm sets the charm for a given service. 381 func (c *Client) ServiceSetCharm(serviceName string, charmUrl string, force bool) error { 382 args := params.ServiceSetCharm{ 383 ServiceName: serviceName, 384 CharmUrl: charmUrl, 385 Force: force, 386 } 387 return c.call("ServiceSetCharm", args, nil) 388 } 389 390 // ServiceGetCharmURL returns the charm URL the given service is 391 // running at present. 392 func (c *Client) ServiceGetCharmURL(serviceName string) (*charm.URL, error) { 393 result := new(params.StringResult) 394 args := params.ServiceGet{ServiceName: serviceName} 395 err := c.call("ServiceGetCharmURL", args, &result) 396 if err != nil { 397 return nil, err 398 } 399 return charm.ParseURL(result.Result) 400 } 401 402 // AddServiceUnits adds a given number of units to a service. 403 func (c *Client) AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error) { 404 args := params.AddServiceUnits{ 405 ServiceName: service, 406 NumUnits: numUnits, 407 ToMachineSpec: machineSpec, 408 } 409 results := new(params.AddServiceUnitsResults) 410 err := c.call("AddServiceUnits", args, results) 411 return results.Units, err 412 } 413 414 // DestroyServiceUnits decreases the number of units dedicated to a service. 415 func (c *Client) DestroyServiceUnits(unitNames ...string) error { 416 params := params.DestroyServiceUnits{unitNames} 417 return c.call("DestroyServiceUnits", params, nil) 418 } 419 420 // ServiceDestroy destroys a given service. 421 func (c *Client) ServiceDestroy(service string) error { 422 params := params.ServiceDestroy{ 423 ServiceName: service, 424 } 425 return c.call("ServiceDestroy", params, nil) 426 } 427 428 // GetServiceConstraints returns the constraints for the given service. 429 func (c *Client) GetServiceConstraints(service string) (constraints.Value, error) { 430 results := new(params.GetConstraintsResults) 431 err := c.call("GetServiceConstraints", params.GetServiceConstraints{service}, results) 432 return results.Constraints, err 433 } 434 435 // GetEnvironmentConstraints returns the constraints for the environment. 436 func (c *Client) GetEnvironmentConstraints() (constraints.Value, error) { 437 results := new(params.GetConstraintsResults) 438 err := c.call("GetEnvironmentConstraints", nil, results) 439 return results.Constraints, err 440 } 441 442 // SetServiceConstraints specifies the constraints for the given service. 443 func (c *Client) SetServiceConstraints(service string, constraints constraints.Value) error { 444 params := params.SetConstraints{ 445 ServiceName: service, 446 Constraints: constraints, 447 } 448 return c.call("SetServiceConstraints", params, nil) 449 } 450 451 // SetEnvironmentConstraints specifies the constraints for the environment. 452 func (c *Client) SetEnvironmentConstraints(constraints constraints.Value) error { 453 params := params.SetConstraints{ 454 Constraints: constraints, 455 } 456 return c.call("SetEnvironmentConstraints", params, nil) 457 } 458 459 // CharmInfo holds information about a charm. 460 type CharmInfo struct { 461 Revision int 462 URL string 463 Config *charm.Config 464 Meta *charm.Meta 465 } 466 467 // CharmInfo returns information about the requested charm. 468 func (c *Client) CharmInfo(charmURL string) (*CharmInfo, error) { 469 args := params.CharmInfo{CharmURL: charmURL} 470 info := new(CharmInfo) 471 if err := c.call("CharmInfo", args, info); err != nil { 472 return nil, err 473 } 474 return info, nil 475 } 476 477 // EnvironmentInfo holds information about the Juju environment. 478 type EnvironmentInfo struct { 479 DefaultSeries string 480 ProviderType string 481 Name string 482 UUID string 483 } 484 485 // EnvironmentInfo returns details about the Juju environment. 486 func (c *Client) EnvironmentInfo() (*EnvironmentInfo, error) { 487 info := new(EnvironmentInfo) 488 err := c.call("EnvironmentInfo", nil, info) 489 return info, err 490 } 491 492 // WatchAll holds the id of the newly-created AllWatcher. 493 type WatchAll struct { 494 AllWatcherId string 495 } 496 497 // WatchAll returns an AllWatcher, from which you can request the Next 498 // collection of Deltas. 499 func (c *Client) WatchAll() (*AllWatcher, error) { 500 info := new(WatchAll) 501 if err := c.call("WatchAll", nil, info); err != nil { 502 return nil, err 503 } 504 return newAllWatcher(c, &info.AllWatcherId), nil 505 } 506 507 // GetAnnotations returns annotations that have been set on the given entity. 508 func (c *Client) GetAnnotations(tag string) (map[string]string, error) { 509 args := params.GetAnnotations{tag} 510 ann := new(params.GetAnnotationsResults) 511 err := c.call("GetAnnotations", args, ann) 512 return ann.Annotations, err 513 } 514 515 // SetAnnotations sets the annotation pairs on the given entity. 516 // Currently annotations are supported on machines, services, 517 // units and the environment itself. 518 func (c *Client) SetAnnotations(tag string, pairs map[string]string) error { 519 args := params.SetAnnotations{tag, pairs} 520 return c.call("SetAnnotations", args, nil) 521 } 522 523 // Close closes the Client's underlying State connection 524 // Client is unique among the api.State facades in closing its own State 525 // connection, but it is conventional to use a Client object without any access 526 // to its underlying state connection. 527 func (c *Client) Close() error { 528 return c.st.Close() 529 } 530 531 // EnvironmentGet returns all environment settings. 532 func (c *Client) EnvironmentGet() (map[string]interface{}, error) { 533 result := params.EnvironmentGetResults{} 534 err := c.call("EnvironmentGet", nil, &result) 535 return result.Config, err 536 } 537 538 // EnvironmentSet sets the given key-value pairs in the environment. 539 func (c *Client) EnvironmentSet(config map[string]interface{}) error { 540 args := params.EnvironmentSet{Config: config} 541 return c.call("EnvironmentSet", args, nil) 542 } 543 544 // EnvironmentUnset sets the given key-value pairs in the environment. 545 func (c *Client) EnvironmentUnset(keys ...string) error { 546 args := params.EnvironmentUnset{Keys: keys} 547 return c.call("EnvironmentUnset", args, nil) 548 } 549 550 // SetEnvironAgentVersion sets the environment agent-version setting 551 // to the given value. 552 func (c *Client) SetEnvironAgentVersion(version version.Number) error { 553 args := params.SetEnvironAgentVersion{Version: version} 554 return c.call("SetEnvironAgentVersion", args, nil) 555 } 556 557 // FindTools returns a List containing all tools matching the specified parameters. 558 func (c *Client) FindTools(majorVersion, minorVersion int, 559 series, arch string) (result params.FindToolsResults, err error) { 560 561 args := params.FindToolsParams{ 562 MajorVersion: majorVersion, 563 MinorVersion: minorVersion, 564 Arch: arch, 565 Series: series, 566 } 567 err = c.call("FindTools", args, &result) 568 return result, err 569 } 570 571 // RunOnAllMachines runs the command on all the machines with the specified 572 // timeout. 573 func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.RunResult, error) { 574 var results params.RunResults 575 args := params.RunParams{Commands: commands, Timeout: timeout} 576 err := c.call("RunOnAllMachines", args, &results) 577 return results.Results, err 578 } 579 580 // Run the Commands specified on the machines identified through the ids 581 // provided in the machines, services and units slices. 582 func (c *Client) Run(run params.RunParams) ([]params.RunResult, error) { 583 var results params.RunResults 584 err := c.call("Run", run, &results) 585 return results.Results, err 586 } 587 588 // DestroyEnvironment puts the environment into a "dying" state, 589 // and removes all non-manager machine instances. DestroyEnvironment 590 // will fail if there are any manually-provisioned non-manager machines 591 // in state. 592 func (c *Client) DestroyEnvironment() error { 593 return c.call("DestroyEnvironment", nil, nil) 594 } 595 596 // AddLocalCharm prepares the given charm with a local: schema in its 597 // URL, and uploads it via the API server, returning the assigned 598 // charm URL. If the API server does not support charm uploads, an 599 // error satisfying params.IsCodeNotImplemented() is returned. 600 func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) { 601 if curl.Schema != "local" { 602 return nil, fmt.Errorf("expected charm URL with local: schema, got %q", curl.String()) 603 } 604 // Package the charm for uploading. 605 var archive *os.File 606 switch ch := ch.(type) { 607 case *charm.Dir: 608 var err error 609 if archive, err = ioutil.TempFile("", "charm"); err != nil { 610 return nil, fmt.Errorf("cannot create temp file: %v", err) 611 } 612 defer os.Remove(archive.Name()) 613 defer archive.Close() 614 if err := ch.BundleTo(archive); err != nil { 615 return nil, fmt.Errorf("cannot repackage charm: %v", err) 616 } 617 if _, err := archive.Seek(0, 0); err != nil { 618 return nil, fmt.Errorf("cannot rewind packaged charm: %v", err) 619 } 620 case *charm.Bundle: 621 var err error 622 if archive, err = os.Open(ch.Path); err != nil { 623 return nil, fmt.Errorf("cannot read charm archive: %v", err) 624 } 625 defer archive.Close() 626 default: 627 return nil, fmt.Errorf("unknown charm type %T", ch) 628 } 629 630 // Prepare the upload request. 631 url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series) 632 req, err := http.NewRequest("POST", url, archive) 633 if err != nil { 634 return nil, fmt.Errorf("cannot create upload request: %v", err) 635 } 636 req.SetBasicAuth(c.st.tag, c.st.password) 637 req.Header.Set("Content-Type", "application/zip") 638 639 // Send the request. 640 641 // BUG(dimitern) 2013-12-17 bug #1261780 642 // Due to issues with go 1.1.2, fixed later, we cannot use a 643 // regular TLS client with the CACert here, because we get "x509: 644 // cannot validate certificate for 127.0.0.1 because it doesn't 645 // contain any IP SANs". Once we use a later go version, this 646 // should be changed to connect to the API server with a regular 647 // HTTP+TLS enabled client, using the CACert (possily cached, like 648 // the tag and password) passed in api.Open()'s info argument. 649 resp, err := utils.GetNonValidatingHTTPClient().Do(req) 650 if err != nil { 651 return nil, fmt.Errorf("cannot upload charm: %v", err) 652 } 653 if resp.StatusCode == http.StatusMethodNotAllowed { 654 // API server is 1.16 or older, so charm upload 655 // is not supported; notify the client. 656 return nil, ¶ms.Error{ 657 Message: "charm upload is not supported by the API server", 658 Code: params.CodeNotImplemented, 659 } 660 } 661 662 // Now parse the response & return. 663 body, err := ioutil.ReadAll(resp.Body) 664 if err != nil { 665 return nil, fmt.Errorf("cannot read charm upload response: %v", err) 666 } 667 defer resp.Body.Close() 668 var jsonResponse params.CharmsResponse 669 if err := json.Unmarshal(body, &jsonResponse); err != nil { 670 return nil, fmt.Errorf("cannot unmarshal upload response: %v", err) 671 } 672 if jsonResponse.Error != "" { 673 return nil, fmt.Errorf("error uploading charm: %v", jsonResponse.Error) 674 } 675 return charm.MustParseURL(jsonResponse.CharmURL), nil 676 } 677 678 // AddCharm adds the given charm URL (which must include revision) to 679 // the environment, if it does not exist yet. Local charms are not 680 // supported, only charm store URLs. See also AddLocalCharm() in the 681 // client-side API. 682 func (c *Client) AddCharm(curl *charm.URL) error { 683 args := params.CharmURL{URL: curl.String()} 684 return c.call("AddCharm", args, nil) 685 } 686 687 // ResolveCharm resolves the best available charm URLs with series, for charm 688 // locations without a series specified. 689 func (c *Client) ResolveCharm(ref charm.Reference) (*charm.URL, error) { 690 args := params.ResolveCharms{References: []charm.Reference{ref}} 691 result := new(params.ResolveCharmResults) 692 if err := c.st.Call("Client", "", "ResolveCharms", args, result); err != nil { 693 return nil, err 694 } 695 if len(result.URLs) == 0 { 696 return nil, fmt.Errorf("unexpected empty response") 697 } 698 urlInfo := result.URLs[0] 699 if urlInfo.Error != "" { 700 return nil, fmt.Errorf("%v", urlInfo.Error) 701 } 702 return urlInfo.URL, nil 703 } 704 705 func (c *Client) UploadTools( 706 toolsFilename string, vers version.Binary, fakeSeries ...string, 707 ) ( 708 tools *tools.Tools, err error, 709 ) { 710 toolsTarball, err := os.Open(toolsFilename) 711 if err != nil { 712 return nil, err 713 } 714 defer toolsTarball.Close() 715 716 // Prepare the upload request. 717 url := fmt.Sprintf("%s/tools?binaryVersion=%s&series=%s", c.st.serverRoot, vers, strings.Join(fakeSeries, ",")) 718 req, err := http.NewRequest("POST", url, toolsTarball) 719 if err != nil { 720 return nil, fmt.Errorf("cannot create upload request: %v", err) 721 } 722 req.SetBasicAuth(c.st.tag, c.st.password) 723 req.Header.Set("Content-Type", "application/x-tar-gz") 724 725 // Send the request. 726 727 // BUG(dimitern) 2013-12-17 bug #1261780 728 // Due to issues with go 1.1.2, fixed later, we cannot use a 729 // regular TLS client with the CACert here, because we get "x509: 730 // cannot validate certificate for 127.0.0.1 because it doesn't 731 // contain any IP SANs". Once we use a later go version, this 732 // should be changed to connect to the API server with a regular 733 // HTTP+TLS enabled client, using the CACert (possily cached, like 734 // the tag and password) passed in api.Open()'s info argument. 735 resp, err := utils.GetNonValidatingHTTPClient().Do(req) 736 if err != nil { 737 return nil, fmt.Errorf("cannot upload charm: %v", err) 738 } 739 if resp.StatusCode == http.StatusMethodNotAllowed { 740 // API server is older than 1.17.5, so tools upload 741 // is not supported; notify the client. 742 return nil, ¶ms.Error{ 743 Message: "tools upload is not supported by the API server", 744 Code: params.CodeNotImplemented, 745 } 746 } 747 748 // Now parse the response & return. 749 body, err := ioutil.ReadAll(resp.Body) 750 if err != nil { 751 return nil, fmt.Errorf("cannot read tools upload response: %v", err) 752 } 753 defer resp.Body.Close() 754 var jsonResponse params.ToolsResult 755 if err := json.Unmarshal(body, &jsonResponse); err != nil { 756 return nil, fmt.Errorf("cannot unmarshal upload response: %v", err) 757 } 758 if err := jsonResponse.Error; err != nil { 759 return nil, fmt.Errorf("error uploading tools: %v", err) 760 } 761 return jsonResponse.Tools, nil 762 } 763 764 // APIHostPorts returns a slice of network.HostPort for each API server. 765 func (c *Client) APIHostPorts() ([][]network.HostPort, error) { 766 var result params.APIHostPortsResult 767 if err := c.call("APIHostPorts", nil, &result); err != nil { 768 return nil, err 769 } 770 return result.Servers, nil 771 } 772 773 // EnsureAvailability ensures the availability of Juju state servers. 774 func (c *Client) EnsureAvailability(numStateServers int, cons constraints.Value, series string) error { 775 args := params.EnsureAvailability{ 776 NumStateServers: numStateServers, 777 Constraints: cons, 778 Series: series, 779 } 780 return c.call("EnsureAvailability", args, nil) 781 } 782 783 // AgentVersion reports the version number of the api server. 784 func (c *Client) AgentVersion() (version.Number, error) { 785 var result params.AgentVersionResult 786 if err := c.call("AgentVersion", nil, &result); err != nil { 787 return version.Number{}, err 788 } 789 return result.Version, nil 790 } 791 792 // websocketDialConfig is called instead of websocket.DialConfig so we can 793 // override it in tests. 794 var websocketDialConfig = func(config *websocket.Config) (io.ReadCloser, error) { 795 return websocket.DialConfig(config) 796 } 797 798 // DebugLogParams holds parameters for WatchDebugLog that control the 799 // filtering of the log messages. If the structure is zero initialized, the 800 // entire log file is sent back starting from the end, and until the user 801 // closes the connection. 802 type DebugLogParams struct { 803 // IncludeEntity lists entity tags to include in the response. Tags may 804 // finish with a '*' to match a prefix e.g.: unit-mysql-*, machine-2. If 805 // none are set, then all lines are considered included. 806 IncludeEntity []string 807 // IncludeModule lists logging modules to include in the response. If none 808 // are set all modules are considered included. If a module is specified, 809 // all the submodules also match. 810 IncludeModule []string 811 // ExcludeEntity lists entity tags to exclude from the response. As with 812 // IncludeEntity the values may finish with a '*'. 813 ExcludeEntity []string 814 // ExcludeModule lists logging modules to exclude from the resposne. If a 815 // module is specified, all the submodules are also excluded. 816 ExcludeModule []string 817 // Limit defines the maximum number of lines to return. Once this many 818 // have been sent, the socket is closed. If zero, all filtered lines are 819 // sent down the connection until the client closes the connection. 820 Limit uint 821 // Backlog tells the server to try to go back this many lines before 822 // starting filtering. If backlog is zero and replay is false, then there 823 // may be an initial delay until the next matching log message is written. 824 Backlog uint 825 // Level specifies the minimum logging level to be sent back in the response. 826 Level loggo.Level 827 // Replay tells the server to start at the start of the log file rather 828 // than the end. If replay is true, backlog is ignored. 829 Replay bool 830 } 831 832 // WatchDebugLog returns a ReadCloser that the caller can read the log 833 // lines from. Only log lines that match the filtering specified in 834 // the DebugLogParams are returned. It returns an error that satisfies 835 // errors.IsNotImplemented when the API server does not support the 836 // end-point. 837 // 838 // TODO(dimitern) We already have errors.IsNotImplemented - why do we 839 // need to define a different error for this purpose here? 840 func (c *Client) WatchDebugLog(args DebugLogParams) (io.ReadCloser, error) { 841 // The websocket connection just hangs if the server doesn't have the log 842 // end point. So do a version check, as version was added at the same time 843 // as the remote end point. 844 _, err := c.AgentVersion() 845 if err != nil { 846 return nil, errors.NotSupportedf("WatchDebugLog") 847 } 848 // Prepare URL. 849 attrs := url.Values{} 850 if args.Replay { 851 attrs.Set("replay", fmt.Sprint(args.Replay)) 852 } 853 if args.Limit > 0 { 854 attrs.Set("maxLines", fmt.Sprint(args.Limit)) 855 } 856 if args.Backlog > 0 { 857 attrs.Set("backlog", fmt.Sprint(args.Backlog)) 858 } 859 if args.Level != loggo.UNSPECIFIED { 860 attrs.Set("level", fmt.Sprint(args.Level)) 861 } 862 attrs["includeEntity"] = args.IncludeEntity 863 attrs["includeModule"] = args.IncludeModule 864 attrs["excludeEntity"] = args.ExcludeEntity 865 attrs["excludeModule"] = args.ExcludeModule 866 867 target := url.URL{ 868 Scheme: "wss", 869 Host: c.st.addr, 870 Path: "/log", 871 RawQuery: attrs.Encode(), 872 } 873 cfg, err := websocket.NewConfig(target.String(), "http://localhost/") 874 cfg.Header = utils.BasicAuthHeader(c.st.tag, c.st.password) 875 cfg.TlsConfig = &tls.Config{RootCAs: c.st.certPool, ServerName: "anything"} 876 connection, err := websocketDialConfig(cfg) 877 if err != nil { 878 return nil, err 879 } 880 // Read the initial error and translate to a real error. 881 // Read up to the first new line character. We can't use bufio here as it 882 // reads too much from the reader. 883 line := make([]byte, 4096) 884 n, err := connection.Read(line) 885 if err != nil { 886 return nil, fmt.Errorf("unable to read initial response: %v", err) 887 } 888 line = line[0:n] 889 890 logger.Debugf("initial line: %q", line) 891 var errResult params.ErrorResult 892 err = json.Unmarshal(line, &errResult) 893 if err != nil { 894 return nil, fmt.Errorf("unable to unmarshal initial response: %v", err) 895 } 896 if errResult.Error != nil { 897 return nil, errResult.Error 898 } 899 return connection, nil 900 }