launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "time" 13 14 "launchpad.net/errgo/errors" 15 "launchpad.net/juju-core/charm" 16 "launchpad.net/juju-core/constraints" 17 "launchpad.net/juju-core/instance" 18 "launchpad.net/juju-core/state/api/base" 19 "launchpad.net/juju-core/state/api/params" 20 "launchpad.net/juju-core/utils" 21 "launchpad.net/juju-core/version" 22 ) 23 24 // Client represents the client-accessible part of the state. 25 type Client struct { 26 st *State 27 } 28 29 // MachineStatus holds status info about a machine. 30 type MachineStatus struct { 31 Err error 32 AgentState params.Status 33 AgentStateInfo string 34 AgentVersion string 35 DNSName string 36 InstanceId instance.Id 37 InstanceState string 38 Life string 39 Series string 40 Id string 41 Containers map[string]MachineStatus 42 Hardware string 43 } 44 45 // ServiceStatus holds status info about a service. 46 type ServiceStatus struct { 47 Err error 48 Charm string 49 Exposed bool 50 Life string 51 Relations map[string][]string 52 CanUpgradeTo string 53 SubordinateTo []string 54 Units map[string]UnitStatus 55 } 56 57 // UnitStatus holds status info about a unit. 58 type UnitStatus struct { 59 Err error 60 AgentState params.Status 61 AgentStateInfo string 62 AgentVersion string 63 Life string 64 Machine string 65 OpenedPorts []string 66 PublicAddress string 67 Charm string 68 Subordinates map[string]UnitStatus 69 } 70 71 // Status holds information about the status of a juju environment. 72 type Status struct { 73 EnvironmentName string 74 Machines map[string]MachineStatus 75 Services map[string]ServiceStatus 76 } 77 78 // Status returns the status of the juju environment. 79 func (c *Client) Status(patterns []string) (*Status, error) { 80 var s Status 81 p := params.StatusParams{Patterns: patterns} 82 if err := c.st.Call("Client", "", "Status", p, &s); err != nil { 83 return nil, base.WrapError(err) 84 } 85 return &s, nil 86 } 87 88 // ServiceSet sets configuration options on a service. 89 func (c *Client) ServiceSet(service string, options map[string]string) error { 90 p := params.ServiceSet{ 91 ServiceName: service, 92 Options: options, 93 } 94 // TODO(Nate): Put this back to ServiceSet when the GUI stops expecting 95 // ServiceSet to unset values set to an empty string. 96 return c.st.Call("Client", "", "NewServiceSetForClientAPI", p, nil) 97 } 98 99 // ServiceUnset resets configuration options on a service. 100 func (c *Client) ServiceUnset(service string, options []string) error { 101 p := params.ServiceUnset{ 102 ServiceName: service, 103 Options: options, 104 } 105 return c.st.Call("Client", "", "ServiceUnset", p, nil) 106 } 107 108 // Resolved clears errors on a unit. 109 func (c *Client) Resolved(unit string, retry bool) error { 110 p := params.Resolved{ 111 UnitName: unit, 112 Retry: retry, 113 } 114 return c.st.Call("Client", "", "Resolved", p, nil) 115 } 116 117 // PublicAddress returns the public address of the specified 118 // machine or unit. 119 func (c *Client) PublicAddress(target string) (string, error) { 120 var results params.PublicAddressResults 121 p := params.PublicAddress{Target: target} 122 err := c.st.Call("Client", "", "PublicAddress", p, &results) 123 return results.PublicAddress, err 124 } 125 126 // ServiceSetYAML sets configuration options on a service 127 // given options in YAML format. 128 func (c *Client) ServiceSetYAML(service string, yaml string) error { 129 p := params.ServiceSetYAML{ 130 ServiceName: service, 131 Config: yaml, 132 } 133 return c.st.Call("Client", "", "ServiceSetYAML", p, nil) 134 } 135 136 // ServiceGet returns the configuration for the named service. 137 func (c *Client) ServiceGet(service string) (*params.ServiceGetResults, error) { 138 var results params.ServiceGetResults 139 params := params.ServiceGet{ServiceName: service} 140 err := c.st.Call("Client", "", "ServiceGet", params, &results) 141 return &results, err 142 } 143 144 // AddRelation adds a relation between the specified endpoints and returns the relation info. 145 func (c *Client) AddRelation(endpoints ...string) (*params.AddRelationResults, error) { 146 var addRelRes params.AddRelationResults 147 params := params.AddRelation{Endpoints: endpoints} 148 err := c.st.Call("Client", "", "AddRelation", params, &addRelRes) 149 return &addRelRes, err 150 } 151 152 // DestroyRelation removes the relation between the specified endpoints. 153 func (c *Client) DestroyRelation(endpoints ...string) error { 154 params := params.DestroyRelation{Endpoints: endpoints} 155 return c.st.Call("Client", "", "DestroyRelation", params, nil) 156 } 157 158 // ServiceCharmRelations returns the service's charms relation names. 159 func (c *Client) ServiceCharmRelations(service string) ([]string, error) { 160 var results params.ServiceCharmRelationsResults 161 params := params.ServiceCharmRelations{ServiceName: service} 162 err := c.st.Call("Client", "", "ServiceCharmRelations", params, &results) 163 return results.CharmRelations, err 164 } 165 166 // AddMachines adds new machines with the supplied parameters. 167 func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { 168 args := params.AddMachines{ 169 MachineParams: machineParams, 170 } 171 results := new(params.AddMachinesResults) 172 err := c.st.Call("Client", "", "AddMachines", args, results) 173 return results.Machines, err 174 } 175 176 // ProvisioningScript returns a shell script that, when run, 177 // provisions a machine agent on the machine executing the script. 178 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (script string, err error) { 179 var result params.ProvisioningScriptResult 180 if err = c.st.Call("Client", "", "ProvisioningScript", args, &result); err != nil { 181 return "", base.WrapError(err) 182 } 183 return result.Script, nil 184 } 185 186 // DestroyMachines removes a given set of machines. 187 func (c *Client) DestroyMachines(machines ...string) error { 188 params := params.DestroyMachines{MachineNames: machines} 189 return c.st.Call("Client", "", "DestroyMachines", params, nil) 190 } 191 192 // ForceDestroyMachines removes a given set of machines and all associated units. 193 func (c *Client) ForceDestroyMachines(machines ...string) error { 194 params := params.DestroyMachines{Force: true, MachineNames: machines} 195 return c.st.Call("Client", "", "DestroyMachines", params, nil) 196 } 197 198 // ServiceExpose changes the juju-managed firewall to expose any ports that 199 // were also explicitly marked by units as open. 200 func (c *Client) ServiceExpose(service string) error { 201 params := params.ServiceExpose{ServiceName: service} 202 return c.st.Call("Client", "", "ServiceExpose", params, nil) 203 } 204 205 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 206 // were also explicitly marked by units as open. 207 func (c *Client) ServiceUnexpose(service string) error { 208 params := params.ServiceUnexpose{ServiceName: service} 209 return c.st.Call("Client", "", "ServiceUnexpose", params, nil) 210 } 211 212 // ServiceDeploy obtains the charm, either locally or from the charm store, 213 // and deploys it. 214 func (c *Client) ServiceDeploy(charmUrl string, serviceName string, numUnits int, configYAML string, cons constraints.Value, toMachineSpec string) error { 215 params := params.ServiceDeploy{ 216 ServiceName: serviceName, 217 CharmUrl: charmUrl, 218 NumUnits: numUnits, 219 ConfigYAML: configYAML, 220 Constraints: cons, 221 ToMachineSpec: toMachineSpec, 222 } 223 return c.st.Call("Client", "", "ServiceDeploy", params, nil) 224 } 225 226 // ServiceUpdate updates the service attributes, including charm URL, 227 // minimum number of units, settings and constraints. 228 // TODO(frankban) deprecate redundant API calls that this supercedes. 229 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 230 return c.st.Call("Client", "", "ServiceUpdate", args, nil) 231 } 232 233 // ServiceSetCharm sets the charm for a given service. 234 func (c *Client) ServiceSetCharm(serviceName string, charmUrl string, force bool) error { 235 args := params.ServiceSetCharm{ 236 ServiceName: serviceName, 237 CharmUrl: charmUrl, 238 Force: force, 239 } 240 return c.st.Call("Client", "", "ServiceSetCharm", args, nil) 241 } 242 243 // ServiceGetCharmURL returns the charm URL the given service is 244 // running at present. 245 func (c *Client) ServiceGetCharmURL(serviceName string) (*charm.URL, error) { 246 result := new(params.StringResult) 247 args := params.ServiceGet{ServiceName: serviceName} 248 err := c.st.Call("Client", "", "ServiceGetCharmURL", args, &result) 249 if err != nil { 250 return nil, base.WrapError(err) 251 } 252 return charm.ParseURL(result.Result) 253 } 254 255 // AddServiceUnits adds a given number of units to a service. 256 func (c *Client) AddServiceUnits(service string, numUnits int, machineSpec string) ([]string, error) { 257 args := params.AddServiceUnits{ 258 ServiceName: service, 259 NumUnits: numUnits, 260 ToMachineSpec: machineSpec, 261 } 262 results := new(params.AddServiceUnitsResults) 263 err := c.st.Call("Client", "", "AddServiceUnits", args, results) 264 return results.Units, err 265 } 266 267 // DestroyServiceUnits decreases the number of units dedicated to a service. 268 func (c *Client) DestroyServiceUnits(unitNames ...string) error { 269 params := params.DestroyServiceUnits{unitNames} 270 return c.st.Call("Client", "", "DestroyServiceUnits", params, nil) 271 } 272 273 // ServiceDestroy destroys a given service. 274 func (c *Client) ServiceDestroy(service string) error { 275 params := params.ServiceDestroy{ 276 ServiceName: service, 277 } 278 return c.st.Call("Client", "", "ServiceDestroy", params, nil) 279 } 280 281 // GetServiceConstraints returns the constraints for the given service. 282 func (c *Client) GetServiceConstraints(service string) (constraints.Value, error) { 283 results := new(params.GetConstraintsResults) 284 err := c.st.Call("Client", "", "GetServiceConstraints", params.GetServiceConstraints{service}, results) 285 return results.Constraints, err 286 } 287 288 // GetEnvironmentConstraints returns the constraints for the environment. 289 func (c *Client) GetEnvironmentConstraints() (constraints.Value, error) { 290 results := new(params.GetConstraintsResults) 291 err := c.st.Call("Client", "", "GetEnvironmentConstraints", nil, results) 292 return results.Constraints, err 293 } 294 295 // SetServiceConstraints specifies the constraints for the given service. 296 func (c *Client) SetServiceConstraints(service string, constraints constraints.Value) error { 297 params := params.SetConstraints{ 298 ServiceName: service, 299 Constraints: constraints, 300 } 301 return c.st.Call("Client", "", "SetServiceConstraints", params, nil) 302 } 303 304 // SetEnvironmentConstraints specifies the constraints for the environment. 305 func (c *Client) SetEnvironmentConstraints(constraints constraints.Value) error { 306 params := params.SetConstraints{ 307 Constraints: constraints, 308 } 309 return c.st.Call("Client", "", "SetEnvironmentConstraints", params, nil) 310 } 311 312 // CharmInfo holds information about a charm. 313 type CharmInfo struct { 314 Revision int 315 URL string 316 Config *charm.Config 317 Meta *charm.Meta 318 } 319 320 // CharmInfo returns information about the requested charm. 321 func (c *Client) CharmInfo(charmURL string) (*CharmInfo, error) { 322 args := params.CharmInfo{CharmURL: charmURL} 323 info := new(CharmInfo) 324 if err := c.st.Call("Client", "", "CharmInfo", args, info); err != nil { 325 return nil, base.WrapError(err) 326 } 327 return info, nil 328 } 329 330 // EnvironmentInfo holds information about the Juju environment. 331 type EnvironmentInfo struct { 332 DefaultSeries string 333 ProviderType string 334 Name string 335 UUID string 336 } 337 338 // EnvironmentInfo returns details about the Juju environment. 339 func (c *Client) EnvironmentInfo() (*EnvironmentInfo, error) { 340 info := new(EnvironmentInfo) 341 err := c.st.Call("Client", "", "EnvironmentInfo", nil, info) 342 return info, err 343 } 344 345 // WatchAll holds the id of the newly-created AllWatcher. 346 type WatchAll struct { 347 AllWatcherId string 348 } 349 350 // WatchAll returns an AllWatcher, from which you can request the Next 351 // collection of Deltas. 352 func (c *Client) WatchAll() (*AllWatcher, error) { 353 info := new(WatchAll) 354 if err := c.st.Call("Client", "", "WatchAll", nil, info); err != nil { 355 return nil, base.WrapError(err) 356 } 357 return newAllWatcher(c, &info.AllWatcherId), nil 358 } 359 360 // GetAnnotations returns annotations that have been set on the given entity. 361 func (c *Client) GetAnnotations(tag string) (map[string]string, error) { 362 args := params.GetAnnotations{tag} 363 ann := new(params.GetAnnotationsResults) 364 err := c.st.Call("Client", "", "GetAnnotations", args, ann) 365 return ann.Annotations, err 366 } 367 368 // SetAnnotations sets the annotation pairs on the given entity. 369 // Currently annotations are supported on machines, services, 370 // units and the environment itself. 371 func (c *Client) SetAnnotations(tag string, pairs map[string]string) error { 372 args := params.SetAnnotations{tag, pairs} 373 return c.st.Call("Client", "", "SetAnnotations", args, nil) 374 } 375 376 // Close closes the Client's underlying State connection 377 // Client is unique among the api.State facades in closing its own State 378 // connection, but it is conventional to use a Client object without any access 379 // to its underlying state connection. 380 func (c *Client) Close() error { 381 return c.st.Close() 382 } 383 384 // EnvironmentGet returns all environment settings. 385 func (c *Client) EnvironmentGet() (map[string]interface{}, error) { 386 result := params.EnvironmentGetResults{} 387 err := c.st.Call("Client", "", "EnvironmentGet", nil, &result) 388 return result.Config, err 389 } 390 391 // EnvironmentSet sets the given key-value pairs in the environment. 392 func (c *Client) EnvironmentSet(config map[string]interface{}) error { 393 args := params.EnvironmentSet{Config: config} 394 return c.st.Call("Client", "", "EnvironmentSet", args, nil) 395 } 396 397 // SetEnvironAgentVersion sets the environment agent-version setting 398 // to the given value. 399 func (c *Client) SetEnvironAgentVersion(version version.Number) error { 400 args := params.SetEnvironAgentVersion{Version: version} 401 return c.st.Call("Client", "", "SetEnvironAgentVersion", args, nil) 402 } 403 404 // RunOnAllMachines runs the command on all the machines with the specified 405 // timeout. 406 func (c *Client) RunOnAllMachines(commands string, timeout time.Duration) ([]params.RunResult, error) { 407 var results params.RunResults 408 args := params.RunParams{Commands: commands, Timeout: timeout} 409 err := c.st.Call("Client", "", "RunOnAllMachines", args, &results) 410 return results.Results, err 411 } 412 413 // Run the Commands specified on the machines identified through the ids 414 // provided in the machines, services and units slices. 415 func (c *Client) Run(run params.RunParams) ([]params.RunResult, error) { 416 var results params.RunResults 417 err := c.st.Call("Client", "", "Run", run, &results) 418 return results.Results, err 419 } 420 421 // DestroyEnvironment puts the environment into a "dying" state, 422 // and removes all non-manager machine instances. DestroyEnvironment 423 // will fail if there are any manually-provisioned non-manager machines 424 // in state. 425 func (c *Client) DestroyEnvironment() error { 426 return c.st.Call("Client", "", "DestroyEnvironment", nil, nil) 427 } 428 429 // AddLocalCharm prepares the given charm with a local: schema in its 430 // URL, and uploads it via the API server, returning the assigned 431 // charm URL. If the API server does not support charm uploads, an 432 // error satisfying params.IsCodeNotImplemented() is returned. 433 func (c *Client) AddLocalCharm(curl *charm.URL, ch charm.Charm) (*charm.URL, error) { 434 if curl.Schema != "local" { 435 return nil, errors.Newf("expected charm URL with local: schema, got %q", curl.String()) 436 } 437 // Package the charm for uploading. 438 var archive *os.File 439 switch ch := ch.(type) { 440 case *charm.Dir: 441 var err error 442 if archive, err = ioutil.TempFile("", "charm"); err != nil { 443 return nil, errors.Notef(err, "cannot create temp file") 444 } 445 defer os.Remove(archive.Name()) 446 defer archive.Close() 447 if err := ch.BundleTo(archive); err != nil { 448 return nil, errors.Notef(err, "cannot repackage charm") 449 } 450 if _, err := archive.Seek(0, 0); err != nil { 451 return nil, errors.Notef(err, "cannot rewind packaged charm") 452 } 453 case *charm.Bundle: 454 var err error 455 if archive, err = os.Open(ch.Path); err != nil { 456 return nil, errors.Notef(err, "cannot read charm archive") 457 } 458 defer archive.Close() 459 default: 460 return nil, errors.Newf("unknown charm type %T", ch) 461 } 462 463 // Prepare the upload request. 464 url := fmt.Sprintf("%s/charms?series=%s", c.st.serverRoot, curl.Series) 465 req, err := http.NewRequest("POST", url, archive) 466 if err != nil { 467 return nil, errors.Notef(err, "cannot create upload request") 468 } 469 req.SetBasicAuth(c.st.tag, c.st.password) 470 req.Header.Set("Content-Type", "application/zip") 471 472 // Send the request. 473 474 // BUG(dimitern) 2013-12-17 bug #1261780 475 // Due to issues with go 1.1.2, fixed later, we cannot use a 476 // regular TLS client with the CACert here, because we get "x509: 477 // cannot validate certificate for 127.0.0.1 because it doesn't 478 // contain any IP SANs". Once we use a later go version, this 479 // should be changed to connect to the API server with a regular 480 // HTTP+TLS enabled client, using the CACert (possily cached, like 481 // the tag and password) passed in api.Open()'s info argument. 482 resp, err := utils.GetNonValidatingHTTPClient().Do(req) 483 if err != nil { 484 return nil, errors.Notef(err, "cannot upload charm") 485 } 486 if resp.StatusCode == http.StatusMethodNotAllowed { 487 // API server is 1.16 or older, so charm upload 488 // is not supported; notify the client. 489 return nil, ¶ms.Error{ 490 Message: "charm upload is not supported by the API server", 491 Code: params.CodeNotImplemented, 492 } 493 } 494 495 // Now parse the response & return. 496 body, err := ioutil.ReadAll(resp.Body) 497 if err != nil { 498 return nil, errors.Notef(err, "cannot read charm upload response") 499 } 500 defer resp.Body.Close() 501 var jsonResponse params.CharmsResponse 502 if err := json.Unmarshal(body, &jsonResponse); err != nil { 503 return nil, errors.Notef(err, "cannot unmarshal upload response") 504 } 505 if jsonResponse.Error != "" { 506 return nil, errors.Newf("error uploading charm: %v", jsonResponse.Error) 507 } 508 return charm.MustParseURL(jsonResponse.CharmURL), nil 509 } 510 511 // AddCharm adds the given charm URL (which must include revision) to 512 // the environment, if it does not exist yet. Local charms are not 513 // supported, only charm store URLs. See also AddLocalCharm() in the 514 // client-side API. 515 func (c *Client) AddCharm(curl *charm.URL) error { 516 args := params.CharmURL{URL: curl.String()} 517 err := c.st.Call("Client", "", "AddCharm", args, nil) 518 return base.WrapError(err) 519 }