github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/state/apiserver/client/client.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/errgo/errgo"
    13  	"github.com/juju/loggo"
    14  
    15  	"launchpad.net/juju-core/charm"
    16  	coreCloudinit "launchpad.net/juju-core/cloudinit"
    17  	"launchpad.net/juju-core/cloudinit/sshinit"
    18  	"launchpad.net/juju-core/environs"
    19  	"launchpad.net/juju-core/environs/cloudinit"
    20  	"launchpad.net/juju-core/environs/config"
    21  	envtools "launchpad.net/juju-core/environs/tools"
    22  	"launchpad.net/juju-core/errors"
    23  	"launchpad.net/juju-core/instance"
    24  	"launchpad.net/juju-core/juju"
    25  	"launchpad.net/juju-core/names"
    26  	"launchpad.net/juju-core/state"
    27  	"launchpad.net/juju-core/state/api"
    28  	"launchpad.net/juju-core/state/api/params"
    29  	"launchpad.net/juju-core/state/apiserver/common"
    30  	"launchpad.net/juju-core/state/statecmd"
    31  	coretools "launchpad.net/juju-core/tools"
    32  	"launchpad.net/juju-core/utils"
    33  )
    34  
    35  var logger = loggo.GetLogger("juju.state.apiserver.client")
    36  
    37  type API struct {
    38  	state     *state.State
    39  	auth      common.Authorizer
    40  	resources *common.Resources
    41  	client    *Client
    42  	dataDir   string
    43  }
    44  
    45  // Client serves client-specific API methods.
    46  type Client struct {
    47  	api *API
    48  }
    49  
    50  // NewAPI creates a new instance of the Client API.
    51  func NewAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer, datadir string) *API {
    52  	r := &API{
    53  		state:     st,
    54  		auth:      authorizer,
    55  		resources: resources,
    56  		dataDir:   datadir,
    57  	}
    58  	r.client = &Client{
    59  		api: r,
    60  	}
    61  	return r
    62  }
    63  
    64  // Client returns an object that provides access
    65  // to methods accessible to non-agent clients.
    66  func (r *API) Client(id string) (*Client, error) {
    67  	if !r.auth.AuthClient() {
    68  		return nil, common.ErrPerm
    69  	}
    70  	if id != "" {
    71  		// Safeguard id for possible future use.
    72  		return nil, common.ErrBadId
    73  	}
    74  	return r.client, nil
    75  }
    76  
    77  func (c *Client) WatchAll() (params.AllWatcherId, error) {
    78  	w := c.api.state.Watch()
    79  	return params.AllWatcherId{
    80  		AllWatcherId: c.api.resources.Register(w),
    81  	}, nil
    82  }
    83  
    84  // ServiceSet implements the server side of Client.ServiceSet. Values set to an
    85  // empty string will be unset.
    86  //
    87  // (Deprecated) Use NewServiceSetForClientAPI instead, to preserve values set to
    88  // an empty string, and use ServiceUnset to unset values.
    89  func (c *Client) ServiceSet(p params.ServiceSet) error {
    90  	svc, err := c.api.state.Service(p.ServiceName)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	return serviceSetSettingsStrings(svc, p.Options)
    95  }
    96  
    97  // NewServiceSetForClientAPI implements the server side of
    98  // Client.NewServiceSetForClientAPI. This is exactly like ServiceSet except that
    99  // it does not unset values that are set to an empty string.  ServiceUnset
   100  // should be used for that.
   101  //
   102  // TODO(Nate): rename this to ServiceSet (and remove the deprecated ServiceSet)
   103  // when the GUI handles the new behavior.
   104  func (c *Client) NewServiceSetForClientAPI(p params.ServiceSet) error {
   105  	svc, err := c.api.state.Service(p.ServiceName)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	return newServiceSetSettingsStringsForClientAPI(svc, p.Options)
   110  }
   111  
   112  // ServiceUnset implements the server side of Client.ServiceUnset.
   113  func (c *Client) ServiceUnset(p params.ServiceUnset) error {
   114  	svc, err := c.api.state.Service(p.ServiceName)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	settings := make(charm.Settings)
   119  	for _, option := range p.Options {
   120  		settings[option] = nil
   121  	}
   122  	return svc.UpdateConfigSettings(settings)
   123  }
   124  
   125  // ServiceSetYAML implements the server side of Client.ServerSetYAML.
   126  func (c *Client) ServiceSetYAML(p params.ServiceSetYAML) error {
   127  	svc, err := c.api.state.Service(p.ServiceName)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	return serviceSetSettingsYAML(svc, p.Config)
   132  }
   133  
   134  // ServiceCharmRelations implements the server side of Client.ServiceCharmRelations.
   135  func (c *Client) ServiceCharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) {
   136  	var results params.ServiceCharmRelationsResults
   137  	service, err := c.api.state.Service(p.ServiceName)
   138  	if err != nil {
   139  		return results, err
   140  	}
   141  	endpoints, err := service.Endpoints()
   142  	if err != nil {
   143  		return results, err
   144  	}
   145  	results.CharmRelations = make([]string, len(endpoints))
   146  	for i, endpoint := range endpoints {
   147  		results.CharmRelations[i] = endpoint.Relation.Name
   148  	}
   149  	return results, nil
   150  }
   151  
   152  // Resolved implements the server side of Client.Resolved.
   153  func (c *Client) Resolved(p params.Resolved) error {
   154  	unit, err := c.api.state.Unit(p.UnitName)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	return unit.Resolve(p.Retry)
   159  }
   160  
   161  // PublicAddress implements the server side of Client.PublicAddress.
   162  func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) {
   163  	switch {
   164  	case names.IsMachine(p.Target):
   165  		machine, err := c.api.state.Machine(p.Target)
   166  		if err != nil {
   167  			return results, err
   168  		}
   169  		addr := instance.SelectPublicAddress(machine.Addresses())
   170  		if addr == "" {
   171  			return results, fmt.Errorf("machine %q has no public address", machine)
   172  		}
   173  		return params.PublicAddressResults{PublicAddress: addr}, nil
   174  
   175  	case names.IsUnit(p.Target):
   176  		unit, err := c.api.state.Unit(p.Target)
   177  		if err != nil {
   178  			return results, err
   179  		}
   180  		addr, ok := unit.PublicAddress()
   181  		if !ok {
   182  			return results, fmt.Errorf("unit %q has no public address", unit)
   183  		}
   184  		return params.PublicAddressResults{PublicAddress: addr}, nil
   185  	}
   186  	return results, fmt.Errorf("unknown unit or machine %q", p.Target)
   187  }
   188  
   189  // ServiceExpose changes the juju-managed firewall to expose any ports that
   190  // were also explicitly marked by units as open.
   191  func (c *Client) ServiceExpose(args params.ServiceExpose) error {
   192  	svc, err := c.api.state.Service(args.ServiceName)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	return svc.SetExposed()
   197  }
   198  
   199  // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that
   200  // were also explicitly marked by units as open.
   201  func (c *Client) ServiceUnexpose(args params.ServiceUnexpose) error {
   202  	svc, err := c.api.state.Service(args.ServiceName)
   203  	if err != nil {
   204  		return err
   205  	}
   206  	return svc.ClearExposed()
   207  }
   208  
   209  var CharmStore charm.Repository = charm.Store
   210  
   211  // ServiceDeploy fetches the charm from the charm store and deploys it.
   212  // AddCharm or AddLocalCharm should be called to add the charm
   213  // before calling ServiceDeploy, although for backward compatibility
   214  // this is not necessary until 1.16 support is removed.
   215  func (c *Client) ServiceDeploy(args params.ServiceDeploy) error {
   216  	curl, err := charm.ParseURL(args.CharmUrl)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	if curl.Revision < 0 {
   221  		return fmt.Errorf("charm url must include revision")
   222  	}
   223  
   224  	// Try to find the charm URL in state first.
   225  	ch, err := c.api.state.Charm(curl)
   226  	if errors.IsNotFoundError(err) {
   227  		// Remove this whole if block when 1.16 compatibility is dropped.
   228  		if curl.Schema != "cs" {
   229  			return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema)
   230  		}
   231  		err = c.AddCharm(params.CharmURL{args.CharmUrl})
   232  		if err != nil {
   233  			return err
   234  		}
   235  		ch, err = c.api.state.Charm(curl)
   236  		if err != nil {
   237  			return err
   238  		}
   239  	} else if err != nil {
   240  		return err
   241  	}
   242  
   243  	var settings charm.Settings
   244  	if len(args.ConfigYAML) > 0 {
   245  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName)
   246  	} else if len(args.Config) > 0 {
   247  		// Parse config in a compatile way (see function comment).
   248  		settings, err = parseSettingsCompatible(ch, args.Config)
   249  	}
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	_, err = juju.DeployService(c.api.state,
   255  		juju.DeployServiceParams{
   256  			ServiceName:    args.ServiceName,
   257  			Charm:          ch,
   258  			NumUnits:       args.NumUnits,
   259  			ConfigSettings: settings,
   260  			Constraints:    args.Constraints,
   261  			ToMachineSpec:  args.ToMachineSpec,
   262  		})
   263  	return err
   264  }
   265  
   266  // ServiceUpdate updates the service attributes, including charm URL,
   267  // minimum number of units, settings and constraints.
   268  // All parameters in params.ServiceUpdate except the service name are optional.
   269  func (c *Client) ServiceUpdate(args params.ServiceUpdate) error {
   270  	service, err := c.api.state.Service(args.ServiceName)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	// Set the charm for the given service.
   275  	if args.CharmUrl != "" {
   276  		if err = c.serviceSetCharm(service, args.CharmUrl, args.ForceCharmUrl); err != nil {
   277  			return err
   278  		}
   279  	}
   280  	// Set the minimum number of units for the given service.
   281  	if args.MinUnits != nil {
   282  		if err = service.SetMinUnits(*args.MinUnits); err != nil {
   283  			return err
   284  		}
   285  	}
   286  	// Set up service's settings.
   287  	if args.SettingsYAML != "" {
   288  		if err = serviceSetSettingsYAML(service, args.SettingsYAML); err != nil {
   289  			return err
   290  		}
   291  	} else if len(args.SettingsStrings) > 0 {
   292  		if err = serviceSetSettingsStrings(service, args.SettingsStrings); err != nil {
   293  			return err
   294  		}
   295  	}
   296  	// Update service's constraints.
   297  	if args.Constraints != nil {
   298  		return service.SetConstraints(*args.Constraints)
   299  	}
   300  	return nil
   301  }
   302  
   303  // serviceSetCharm sets the charm for the given service.
   304  func (c *Client) serviceSetCharm(service *state.Service, url string, force bool) error {
   305  	curl, err := charm.ParseURL(url)
   306  	if err != nil {
   307  		return err
   308  	}
   309  	sch, err := c.api.state.Charm(curl)
   310  	if errors.IsNotFoundError(err) {
   311  		// Charms should be added before trying to use them, with
   312  		// AddCharm or AddLocalCharm API calls. When they're not,
   313  		// we're reverting to 1.16 compatibility mode.
   314  		return c.serviceSetCharm1dot16(service, curl, force)
   315  	}
   316  	if err != nil {
   317  		return err
   318  	}
   319  	return service.SetCharm(sch, force)
   320  }
   321  
   322  // serviceSetCharm1dot16 sets the charm for the given service in 1.16
   323  // compatibility mode. Remove this when support for 1.16 is dropped.
   324  func (c *Client) serviceSetCharm1dot16(service *state.Service, curl *charm.URL, force bool) error {
   325  	if curl.Schema != "cs" {
   326  		return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema)
   327  	}
   328  	if curl.Revision < 0 {
   329  		return fmt.Errorf("charm url must include revision")
   330  	}
   331  	err := c.AddCharm(params.CharmURL{curl.String()})
   332  	if err != nil {
   333  		return err
   334  	}
   335  	ch, err := c.api.state.Charm(curl)
   336  	if err != nil {
   337  		return err
   338  	}
   339  	return service.SetCharm(ch, force)
   340  }
   341  
   342  // serviceSetSettingsYAML updates the settings for the given service,
   343  // taking the configuration from a YAML string.
   344  func serviceSetSettingsYAML(service *state.Service, settings string) error {
   345  	ch, _, err := service.Charm()
   346  	if err != nil {
   347  		return err
   348  	}
   349  	changes, err := ch.Config().ParseSettingsYAML([]byte(settings), service.Name())
   350  	if err != nil {
   351  		return err
   352  	}
   353  	return service.UpdateConfigSettings(changes)
   354  }
   355  
   356  // serviceSetSettingsStrings updates the settings for the given service,
   357  // taking the configuration from a map of strings.
   358  func serviceSetSettingsStrings(service *state.Service, settings map[string]string) error {
   359  	ch, _, err := service.Charm()
   360  	if err != nil {
   361  		return err
   362  	}
   363  	// Parse config in a compatible way (see function comment).
   364  	changes, err := parseSettingsCompatible(ch, settings)
   365  	if err != nil {
   366  		return err
   367  	}
   368  	return service.UpdateConfigSettings(changes)
   369  }
   370  
   371  // newServiceSetSettingsStringsForClientAPI updates the settings for the given
   372  // service, taking the configuration from a map of strings.
   373  //
   374  // TODO(Nate): replace serviceSetSettingsStrings with this onces the GUI no
   375  // longer expects to be able to unset values by sending an empty string.
   376  func newServiceSetSettingsStringsForClientAPI(service *state.Service, settings map[string]string) error {
   377  	ch, _, err := service.Charm()
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	// Validate the settings.
   383  	changes, err := ch.Config().ParseSettingsStrings(settings)
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	return service.UpdateConfigSettings(changes)
   389  }
   390  
   391  // ServiceSetCharm sets the charm for a given service.
   392  func (c *Client) ServiceSetCharm(args params.ServiceSetCharm) error {
   393  	service, err := c.api.state.Service(args.ServiceName)
   394  	if err != nil {
   395  		return err
   396  	}
   397  	return c.serviceSetCharm(service, args.CharmUrl, args.Force)
   398  }
   399  
   400  // addServiceUnits adds a given number of units to a service.
   401  func addServiceUnits(state *state.State, args params.AddServiceUnits) ([]*state.Unit, error) {
   402  	service, err := state.Service(args.ServiceName)
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	if args.NumUnits < 1 {
   407  		return nil, fmt.Errorf("must add at least one unit")
   408  	}
   409  	if args.NumUnits > 1 && args.ToMachineSpec != "" {
   410  		return nil, fmt.Errorf("cannot use NumUnits with ToMachineSpec")
   411  	}
   412  	return juju.AddUnits(state, service, args.NumUnits, args.ToMachineSpec)
   413  }
   414  
   415  // AddServiceUnits adds a given number of units to a service.
   416  func (c *Client) AddServiceUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) {
   417  	units, err := addServiceUnits(c.api.state, args)
   418  	if err != nil {
   419  		return params.AddServiceUnitsResults{}, err
   420  	}
   421  	unitNames := make([]string, len(units))
   422  	for i, unit := range units {
   423  		unitNames[i] = unit.String()
   424  	}
   425  	return params.AddServiceUnitsResults{Units: unitNames}, nil
   426  }
   427  
   428  // DestroyServiceUnits removes a given set of service units.
   429  func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error {
   430  	var errs []string
   431  	for _, name := range args.UnitNames {
   432  		unit, err := c.api.state.Unit(name)
   433  		switch {
   434  		case errors.IsNotFoundError(err):
   435  			err = fmt.Errorf("unit %q does not exist", name)
   436  		case err != nil:
   437  		case unit.Life() != state.Alive:
   438  			continue
   439  		case unit.IsPrincipal():
   440  			err = unit.Destroy()
   441  		default:
   442  			err = fmt.Errorf("unit %q is a subordinate", name)
   443  		}
   444  		if err != nil {
   445  			errs = append(errs, err.Error())
   446  		}
   447  	}
   448  	return destroyErr("units", args.UnitNames, errs)
   449  }
   450  
   451  // ServiceDestroy destroys a given service.
   452  func (c *Client) ServiceDestroy(args params.ServiceDestroy) error {
   453  	svc, err := c.api.state.Service(args.ServiceName)
   454  	if err != nil {
   455  		return err
   456  	}
   457  	return svc.Destroy()
   458  }
   459  
   460  // GetServiceConstraints returns the constraints for a given service.
   461  func (c *Client) GetServiceConstraints(args params.GetServiceConstraints) (params.GetConstraintsResults, error) {
   462  	svc, err := c.api.state.Service(args.ServiceName)
   463  	if err != nil {
   464  		return params.GetConstraintsResults{}, err
   465  	}
   466  	cons, err := svc.Constraints()
   467  	return params.GetConstraintsResults{cons}, err
   468  }
   469  
   470  // GetEnvironmentConstraints returns the constraints for the environment.
   471  func (c *Client) GetEnvironmentConstraints() (params.GetConstraintsResults, error) {
   472  	cons, err := c.api.state.EnvironConstraints()
   473  	if err != nil {
   474  		return params.GetConstraintsResults{}, err
   475  	}
   476  	return params.GetConstraintsResults{cons}, nil
   477  }
   478  
   479  // SetServiceConstraints sets the constraints for a given service.
   480  func (c *Client) SetServiceConstraints(args params.SetConstraints) error {
   481  	svc, err := c.api.state.Service(args.ServiceName)
   482  	if err != nil {
   483  		return err
   484  	}
   485  	return svc.SetConstraints(args.Constraints)
   486  }
   487  
   488  // SetEnvironmentConstraints sets the constraints for the environment.
   489  func (c *Client) SetEnvironmentConstraints(args params.SetConstraints) error {
   490  	return c.api.state.SetEnvironConstraints(args.Constraints)
   491  }
   492  
   493  // AddRelation adds a relation between the specified endpoints and returns the relation info.
   494  func (c *Client) AddRelation(args params.AddRelation) (params.AddRelationResults, error) {
   495  	inEps, err := c.api.state.InferEndpoints(args.Endpoints)
   496  	if err != nil {
   497  		return params.AddRelationResults{}, err
   498  	}
   499  	rel, err := c.api.state.AddRelation(inEps...)
   500  	if err != nil {
   501  		return params.AddRelationResults{}, err
   502  	}
   503  	outEps := make(map[string]charm.Relation)
   504  	for _, inEp := range inEps {
   505  		outEp, err := rel.Endpoint(inEp.ServiceName)
   506  		if err != nil {
   507  			return params.AddRelationResults{}, err
   508  		}
   509  		outEps[inEp.ServiceName] = outEp.Relation
   510  	}
   511  	return params.AddRelationResults{Endpoints: outEps}, nil
   512  }
   513  
   514  // DestroyRelation removes the relation between the specified endpoints.
   515  func (c *Client) DestroyRelation(args params.DestroyRelation) error {
   516  	eps, err := c.api.state.InferEndpoints(args.Endpoints)
   517  	if err != nil {
   518  		return err
   519  	}
   520  	rel, err := c.api.state.EndpointsRelation(eps...)
   521  	if err != nil {
   522  		return err
   523  	}
   524  	return rel.Destroy()
   525  }
   526  
   527  // AddMachines adds new machines with the supplied parameters.
   528  func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) {
   529  	results := params.AddMachinesResults{
   530  		Machines: make([]params.AddMachinesResult, len(args.MachineParams)),
   531  	}
   532  
   533  	var defaultSeries string
   534  	conf, err := c.api.state.EnvironConfig()
   535  	if err != nil {
   536  		return results, err
   537  	}
   538  	defaultSeries = conf.DefaultSeries()
   539  
   540  	for i, p := range args.MachineParams {
   541  		m, err := c.addOneMachine(p, defaultSeries)
   542  		results.Machines[i].Error = common.ServerError(err)
   543  		if err == nil {
   544  			results.Machines[i].Machine = m.Id()
   545  		}
   546  	}
   547  	return results, nil
   548  }
   549  
   550  // InjectMachines injects a machine into state with provisioned status.
   551  func (c *Client) InjectMachines(args params.AddMachines) (params.AddMachinesResults, error) {
   552  	return c.AddMachines(args)
   553  }
   554  
   555  func (c *Client) addOneMachine(p params.AddMachineParams, defaultSeries string) (*state.Machine, error) {
   556  	if p.Series == "" {
   557  		p.Series = defaultSeries
   558  	}
   559  	if p.ContainerType != "" {
   560  		// Guard against dubious client by making sure that
   561  		// the following attributes can only be set when we're
   562  		// not making a new container.
   563  		p.InstanceId = ""
   564  		p.Nonce = ""
   565  		p.HardwareCharacteristics = instance.HardwareCharacteristics{}
   566  		p.Addrs = nil
   567  	} else if p.ParentId != "" {
   568  		return nil, fmt.Errorf("parent machine specified without container type")
   569  	}
   570  
   571  	jobs, err := stateJobs(p.Jobs)
   572  	if err != nil {
   573  		return nil, err
   574  	}
   575  	template := state.MachineTemplate{
   576  		Series:      p.Series,
   577  		Constraints: p.Constraints,
   578  		InstanceId:  p.InstanceId,
   579  		Jobs:        jobs,
   580  		Nonce:       p.Nonce,
   581  		HardwareCharacteristics: p.HardwareCharacteristics,
   582  		Addresses:               p.Addrs,
   583  	}
   584  	if p.ContainerType == "" {
   585  		return c.api.state.AddOneMachine(template)
   586  	}
   587  	if p.ParentId != "" {
   588  		return c.api.state.AddMachineInsideMachine(template, p.ParentId, p.ContainerType)
   589  	}
   590  	return c.api.state.AddMachineInsideNewMachine(template, template, p.ContainerType)
   591  }
   592  
   593  func stateJobs(jobs []params.MachineJob) ([]state.MachineJob, error) {
   594  	newJobs := make([]state.MachineJob, len(jobs))
   595  	for i, job := range jobs {
   596  		newJob, err := state.MachineJobFromParams(job)
   597  		if err != nil {
   598  			return nil, err
   599  		}
   600  		newJobs[i] = newJob
   601  	}
   602  	return newJobs, nil
   603  }
   604  
   605  // ProvisioningScript returns a shell script that, when run,
   606  // provisions a machine agent on the machine executing the script.
   607  func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (params.ProvisioningScriptResult, error) {
   608  	var result params.ProvisioningScriptResult
   609  	mcfg, err := statecmd.MachineConfig(c.api.state, args.MachineId, args.Nonce, args.DataDir)
   610  	if err != nil {
   611  		return result, err
   612  	}
   613  	mcfg.DisablePackageCommands = args.DisablePackageCommands
   614  	cloudcfg := coreCloudinit.New()
   615  	if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil {
   616  		return result, err
   617  	}
   618  	// ProvisioningScript is run on an existing machine;
   619  	// we explicitly disable apt_upgrade so as not to
   620  	// trample the machine's existing configuration.
   621  	cloudcfg.SetAptUpgrade(false)
   622  	result.Script, err = sshinit.ConfigureScript(cloudcfg)
   623  	return result, err
   624  }
   625  
   626  // DestroyMachines removes a given set of machines.
   627  func (c *Client) DestroyMachines(args params.DestroyMachines) error {
   628  	var errs []string
   629  	for _, id := range args.MachineNames {
   630  		machine, err := c.api.state.Machine(id)
   631  		switch {
   632  		case errors.IsNotFoundError(err):
   633  			err = fmt.Errorf("machine %s does not exist", id)
   634  		case err != nil:
   635  		case args.Force:
   636  			err = machine.ForceDestroy()
   637  		case machine.Life() != state.Alive:
   638  			continue
   639  		default:
   640  			err = machine.Destroy()
   641  		}
   642  		if err != nil {
   643  			errs = append(errs, err.Error())
   644  		}
   645  	}
   646  	return destroyErr("machines", args.MachineNames, errs)
   647  }
   648  
   649  // CharmInfo returns information about the requested charm.
   650  func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) {
   651  	curl, err := charm.ParseURL(args.CharmURL)
   652  	if err != nil {
   653  		return api.CharmInfo{}, err
   654  	}
   655  	charm, err := c.api.state.Charm(curl)
   656  	if err != nil {
   657  		return api.CharmInfo{}, err
   658  	}
   659  	info := api.CharmInfo{
   660  		Revision: charm.Revision(),
   661  		URL:      curl.String(),
   662  		Config:   charm.Config(),
   663  		Meta:     charm.Meta(),
   664  	}
   665  	return info, nil
   666  }
   667  
   668  // EnvironmentInfo returns information about the current environment (default
   669  // series and type).
   670  func (c *Client) EnvironmentInfo() (api.EnvironmentInfo, error) {
   671  	state := c.api.state
   672  	conf, err := state.EnvironConfig()
   673  	if err != nil {
   674  		return api.EnvironmentInfo{}, err
   675  	}
   676  	env, err := state.Environment()
   677  	if err != nil {
   678  		return api.EnvironmentInfo{}, err
   679  	}
   680  
   681  	info := api.EnvironmentInfo{
   682  		DefaultSeries: conf.DefaultSeries(),
   683  		ProviderType:  conf.Type(),
   684  		Name:          conf.Name(),
   685  		UUID:          env.UUID(),
   686  	}
   687  	return info, nil
   688  }
   689  
   690  // GetAnnotations returns annotations about a given entity.
   691  func (c *Client) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
   692  	nothing := params.GetAnnotationsResults{}
   693  	entity, err := c.findEntity(args.Tag)
   694  	if err != nil {
   695  		return nothing, err
   696  	}
   697  	ann, err := entity.Annotations()
   698  	if err != nil {
   699  		return nothing, err
   700  	}
   701  	return params.GetAnnotationsResults{Annotations: ann}, nil
   702  }
   703  
   704  func (c *Client) findEntity(tag string) (state.Annotator, error) {
   705  	entity0, err := c.api.state.FindEntity(tag)
   706  	if err != nil {
   707  		return nil, err
   708  	}
   709  	entity, ok := entity0.(state.Annotator)
   710  	if !ok {
   711  		return nil, common.NotSupportedError(tag, "annotations")
   712  	}
   713  	return entity, nil
   714  }
   715  
   716  // SetAnnotations stores annotations about a given entity.
   717  func (c *Client) SetAnnotations(args params.SetAnnotations) error {
   718  	entity, err := c.findEntity(args.Tag)
   719  	if err != nil {
   720  		return err
   721  	}
   722  	return entity.SetAnnotations(args.Pairs)
   723  }
   724  
   725  // parseSettingsCompatible parses setting strings in a way that is
   726  // compatible with the behavior before this CL based on the issue
   727  // http://pad.lv/1194945. Until then setting an option to an empty
   728  // string caused it to reset to the default value. We now allow
   729  // empty strings as actual values, but we want to preserve the API
   730  // behavior.
   731  func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) {
   732  	setSettings := map[string]string{}
   733  	unsetSettings := charm.Settings{}
   734  	// Split settings into those which set and those which unset a value.
   735  	for name, value := range settings {
   736  		if value == "" {
   737  			unsetSettings[name] = nil
   738  			continue
   739  		}
   740  		setSettings[name] = value
   741  	}
   742  	// Validate the settings.
   743  	changes, err := ch.Config().ParseSettingsStrings(setSettings)
   744  	if err != nil {
   745  		return nil, err
   746  	}
   747  	// Validate the unsettings and merge them into the changes.
   748  	unsetSettings, err = ch.Config().ValidateSettings(unsetSettings)
   749  	if err != nil {
   750  		return nil, err
   751  	}
   752  	for name := range unsetSettings {
   753  		changes[name] = nil
   754  	}
   755  	return changes, nil
   756  }
   757  
   758  // EnvironmentGet implements the server-side part of the
   759  // get-environment CLI command.
   760  func (c *Client) EnvironmentGet() (params.EnvironmentGetResults, error) {
   761  	result := params.EnvironmentGetResults{}
   762  	// Get the existing environment config from the state.
   763  	config, err := c.api.state.EnvironConfig()
   764  	if err != nil {
   765  		return result, err
   766  	}
   767  	result.Config = config.AllAttrs()
   768  	return result, nil
   769  }
   770  
   771  // EnvironmentSet implements the server-side part of the
   772  // set-environment CLI command.
   773  func (c *Client) EnvironmentSet(args params.EnvironmentSet) error {
   774  	// TODO(dimitern,thumper): 2013-11-06 bug #1167616
   775  	// SetEnvironConfig should take both new and old configs.
   776  
   777  	// Get the existing environment config from the state.
   778  	oldConfig, err := c.api.state.EnvironConfig()
   779  	if err != nil {
   780  		return err
   781  	}
   782  	// Make sure we don't allow changing agent-version.
   783  	if v, found := args.Config["agent-version"]; found {
   784  		oldVersion, _ := oldConfig.AgentVersion()
   785  		if v != oldVersion.String() {
   786  			return fmt.Errorf("agent-version cannot be changed")
   787  		}
   788  	}
   789  	// Apply the attributes specified for the command to the state config.
   790  	newConfig, err := oldConfig.Apply(args.Config)
   791  	if err != nil {
   792  		return err
   793  	}
   794  	env, err := environs.New(oldConfig)
   795  	if err != nil {
   796  		return err
   797  	}
   798  	// Now validate this new config against the existing config via the provider.
   799  	provider := env.Provider()
   800  	newProviderConfig, err := provider.Validate(newConfig, oldConfig)
   801  	if err != nil {
   802  		return err
   803  	}
   804  	// Now try to apply the new validated config.
   805  	return c.api.state.SetEnvironConfig(newProviderConfig, oldConfig)
   806  }
   807  
   808  // SetEnvironAgentVersion sets the environment agent version.
   809  func (c *Client) SetEnvironAgentVersion(args params.SetEnvironAgentVersion) error {
   810  	return c.api.state.SetEnvironAgentVersion(args.Version)
   811  }
   812  
   813  // FindTools returns a List containing all tools matching the given parameters.
   814  func (c *Client) FindTools(args params.FindToolsParams) (params.FindToolsResults, error) {
   815  	result := params.FindToolsResults{}
   816  	// Get the existing environment config from the state.
   817  	envConfig, err := c.api.state.EnvironConfig()
   818  	if err != nil {
   819  		return result, err
   820  	}
   821  	env, err := environs.New(envConfig)
   822  	if err != nil {
   823  		return result, err
   824  	}
   825  	filter := coretools.Filter{
   826  		Arch:   args.Arch,
   827  		Series: args.Series,
   828  	}
   829  	result.List, err = envtools.FindTools(env, args.MajorVersion, args.MinorVersion, filter, envtools.DoNotAllowRetry)
   830  	result.Error = common.ServerError(err)
   831  	return result, nil
   832  }
   833  
   834  func destroyErr(desc string, ids, errs []string) error {
   835  	if len(errs) == 0 {
   836  		return nil
   837  	}
   838  	msg := "some %s were not destroyed"
   839  	if len(errs) == len(ids) {
   840  		msg = "no %s were destroyed"
   841  	}
   842  	msg = fmt.Sprintf(msg, desc)
   843  	return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; "))
   844  }
   845  
   846  // AddCharm adds the given charm URL (which must include revision) to
   847  // the environment, if it does not exist yet. Local charms are not
   848  // supported, only charm store URLs. See also AddLocalCharm().
   849  func (c *Client) AddCharm(args params.CharmURL) error {
   850  	charmURL, err := charm.ParseURL(args.URL)
   851  	if err != nil {
   852  		return err
   853  	}
   854  	if charmURL.Schema != "cs" {
   855  		return fmt.Errorf("only charm store charm URLs are supported, with cs: schema")
   856  	}
   857  	if charmURL.Revision < 0 {
   858  		return fmt.Errorf("charm URL must include revision")
   859  	}
   860  
   861  	// First, check if a pending or a real charm exists in state.
   862  	stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL)
   863  	if err == nil && stateCharm.IsUploaded() {
   864  		// Charm already in state (it was uploaded already).
   865  		return nil
   866  	} else if err != nil {
   867  		return err
   868  	}
   869  
   870  	// Get the charm and its information from the store.
   871  	envConfig, err := c.api.state.EnvironConfig()
   872  	if err != nil {
   873  		return err
   874  	}
   875  	store := config.SpecializeCharmRepo(CharmStore, envConfig)
   876  	downloadedCharm, err := store.Get(charmURL)
   877  	if err != nil {
   878  		return errgo.Annotatef(err, "cannot download charm %q", charmURL.String())
   879  	}
   880  
   881  	// Open it and calculate the SHA256 hash.
   882  	downloadedBundle, ok := downloadedCharm.(*charm.Bundle)
   883  	if !ok {
   884  		return errgo.New("expected a charm archive, got %T", downloadedCharm)
   885  	}
   886  	archive, err := os.Open(downloadedBundle.Path)
   887  	if err != nil {
   888  		return errgo.Annotate(err, "cannot read downloaded charm")
   889  	}
   890  	defer archive.Close()
   891  	bundleSHA256, size, err := utils.ReadSHA256(archive)
   892  	if err != nil {
   893  		return errgo.Annotate(err, "cannot calculate SHA256 hash of charm")
   894  	}
   895  	if _, err := archive.Seek(0, 0); err != nil {
   896  		return errgo.Annotate(err, "cannot rewind charm archive")
   897  	}
   898  
   899  	// Get the environment storage and upload the charm.
   900  	env, err := environs.New(envConfig)
   901  	if err != nil {
   902  		return errgo.Annotate(err, "cannot access environment")
   903  	}
   904  	storage := env.Storage()
   905  	archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision)
   906  	if err != nil {
   907  		return errgo.Annotate(err, "cannot generate charm archive name")
   908  	}
   909  	if err := storage.Put(archiveName, archive, size); err != nil {
   910  		return errgo.Annotate(err, "cannot upload charm to provider storage")
   911  	}
   912  	storageURL, err := storage.URL(archiveName)
   913  	if err != nil {
   914  		return errgo.Annotate(err, "cannot get storage URL for charm")
   915  	}
   916  	bundleURL, err := url.Parse(storageURL)
   917  	if err != nil {
   918  		return errgo.Annotate(err, "cannot parse storage URL")
   919  	}
   920  
   921  	// Finally, update the charm data in state and mark it as no longer pending.
   922  	_, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256)
   923  	if err == state.ErrCharmRevisionAlreadyModified ||
   924  		state.IsCharmAlreadyUploadedError(err) {
   925  		// This is not an error, it just signifies somebody else
   926  		// managed to upload and update the charm in state before
   927  		// us. This means we have to delete what we just uploaded
   928  		// to storage.
   929  		if err := storage.Remove(archiveName); err != nil {
   930  			errgo.Annotate(err, "cannot remove duplicated charm from storage")
   931  		}
   932  		return nil
   933  	}
   934  	return err
   935  }
   936  
   937  // CharmArchiveName returns a string that is suitable as a file name
   938  // in a storage URL. It is constructed from the charm name, revision
   939  // and a random UUID string.
   940  func CharmArchiveName(name string, revision int) (string, error) {
   941  	uuid, err := utils.NewUUID()
   942  	if err != nil {
   943  		return "", err
   944  	}
   945  	return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil
   946  }