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, &params.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  }