launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"github.com/loggo/loggo"
     9  	"net/url"
    10  	"os"
    11  	"strings"
    12  
    13  	errgo "launchpad.net/errgo/errors"
    14  	"launchpad.net/juju-core/charm"
    15  	coreCloudinit "launchpad.net/juju-core/cloudinit"
    16  	"launchpad.net/juju-core/cloudinit/sshinit"
    17  	"launchpad.net/juju-core/environs"
    18  	"launchpad.net/juju-core/environs/cloudinit"
    19  	"launchpad.net/juju-core/environs/config"
    20  	"launchpad.net/juju-core/errors"
    21  	"launchpad.net/juju-core/instance"
    22  	"launchpad.net/juju-core/juju"
    23  	"launchpad.net/juju-core/names"
    24  	"launchpad.net/juju-core/state"
    25  	"launchpad.net/juju-core/state/api"
    26  	"launchpad.net/juju-core/state/api/params"
    27  	"launchpad.net/juju-core/state/apiserver/common"
    28  	"launchpad.net/juju-core/state/statecmd"
    29  	"launchpad.net/juju-core/utils"
    30  )
    31  
    32  var logger = loggo.GetLogger("juju.state.apiserver.client")
    33  
    34  var mask = errgo.Mask
    35  
    36  type API struct {
    37  	state     *state.State
    38  	auth      common.Authorizer
    39  	resources *common.Resources
    40  	client    *Client
    41  	dataDir   string
    42  }
    43  
    44  // Client serves client-specific API methods.
    45  type Client struct {
    46  	api *API
    47  }
    48  
    49  // NewAPI creates a new instance of the Client API.
    50  func NewAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer, datadir string) *API {
    51  	r := &API{
    52  		state:     st,
    53  		auth:      authorizer,
    54  		resources: resources,
    55  		dataDir:   datadir,
    56  	}
    57  	r.client = &Client{
    58  		api: r,
    59  	}
    60  	return r
    61  }
    62  
    63  // Client returns an object that provides access
    64  // to methods accessible to non-agent clients.
    65  func (r *API) Client(id string) (*Client, error) {
    66  	if !r.auth.AuthClient() {
    67  		return nil, common.ErrPerm
    68  	}
    69  	if id != "" {
    70  		// Safeguard id for possible future use.
    71  		return nil, common.ErrBadId
    72  	}
    73  	return r.client, nil
    74  }
    75  
    76  func (c *Client) WatchAll() (params.AllWatcherId, error) {
    77  	w := c.api.state.Watch()
    78  	return params.AllWatcherId{
    79  		AllWatcherId: c.api.resources.Register(w),
    80  	}, nil
    81  }
    82  
    83  // ServiceSet implements the server side of Client.ServiceSet. Values set to an
    84  // empty string will be unset.
    85  //
    86  // (Deprecated) Use NewServiceSetForClientAPI instead, to preserve values set to
    87  // an empty string, and use ServiceUnset to unset values.
    88  func (c *Client) ServiceSet(p params.ServiceSet) error {
    89  	svc, err := c.api.state.Service(p.ServiceName)
    90  	if err != nil {
    91  		return mask(err, errors.IsNotFoundError)
    92  	}
    93  	return serviceSetSettingsStrings(svc, p.Options)
    94  }
    95  
    96  // NewServiceSetForClientAPI implements the server side of
    97  // Client.NewServiceSetForClientAPI. This is exactly like ServiceSet except that
    98  // it does not unset values that are set to an empty string.  ServiceUnset
    99  // should be used for that.
   100  //
   101  // TODO(Nate): rename this to ServiceSet (and remove the deprecated ServiceSet)
   102  // when the GUI handles the new behavior.
   103  func (c *Client) NewServiceSetForClientAPI(p params.ServiceSet) error {
   104  	svc, err := c.api.state.Service(p.ServiceName)
   105  	if err != nil {
   106  		return mask(err, errors.IsNotFoundError)
   107  	}
   108  	return newServiceSetSettingsStringsForClientAPI(svc, p.Options)
   109  }
   110  
   111  // ServiceUnset implements the server side of Client.ServiceUnset.
   112  func (c *Client) ServiceUnset(p params.ServiceUnset) error {
   113  	svc, err := c.api.state.Service(p.ServiceName)
   114  	if err != nil {
   115  		return mask(err, errors.IsNotFoundError)
   116  	}
   117  	settings := make(charm.Settings)
   118  	for _, option := range p.Options {
   119  		settings[option] = nil
   120  	}
   121  	return svc.UpdateConfigSettings(settings)
   122  }
   123  
   124  // ServiceSetYAML implements the server side of Client.ServerSetYAML.
   125  func (c *Client) ServiceSetYAML(p params.ServiceSetYAML) error {
   126  	svc, err := c.api.state.Service(p.ServiceName)
   127  	if err != nil {
   128  		return mask(err, errors.IsNotFoundError)
   129  	}
   130  	return serviceSetSettingsYAML(svc, p.Config)
   131  }
   132  
   133  // ServiceCharmRelations implements the server side of Client.ServiceCharmRelations.
   134  func (c *Client) ServiceCharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) {
   135  	var results params.ServiceCharmRelationsResults
   136  	service, err := c.api.state.Service(p.ServiceName)
   137  	if err != nil {
   138  		return results, mask(err, errors.IsNotFoundError)
   139  	}
   140  	endpoints, err := service.Endpoints()
   141  	if err != nil {
   142  		return results, mask(err)
   143  	}
   144  	results.CharmRelations = make([]string, len(endpoints))
   145  	for i, endpoint := range endpoints {
   146  		results.CharmRelations[i] = endpoint.Relation.Name
   147  	}
   148  	return results, nil
   149  }
   150  
   151  // Resolved implements the server side of Client.Resolved.
   152  func (c *Client) Resolved(p params.Resolved) error {
   153  	unit, err := c.api.state.Unit(p.UnitName)
   154  	if err != nil {
   155  		return mask(err, errors.IsNotFoundError)
   156  	}
   157  	return unit.Resolve(p.Retry)
   158  }
   159  
   160  // PublicAddress implements the server side of Client.PublicAddress.
   161  func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) {
   162  	switch {
   163  	case names.IsMachine(p.Target):
   164  		machine, err := c.api.state.Machine(p.Target)
   165  		if err != nil {
   166  			return results, mask(err, errors.IsNotFoundError)
   167  		}
   168  		addr := instance.SelectPublicAddress(machine.Addresses())
   169  		if addr == "" {
   170  			return results, errgo.Newf("machine %q has no public address", machine)
   171  		}
   172  		return params.PublicAddressResults{PublicAddress: addr}, nil
   173  
   174  	case names.IsUnit(p.Target):
   175  		unit, err := c.api.state.Unit(p.Target)
   176  		if err != nil {
   177  			return results, mask(err, errors.IsNotFoundError)
   178  		}
   179  		addr, ok := unit.PublicAddress()
   180  		if !ok {
   181  			return results, errgo.Newf("unit %q has no public address", unit)
   182  		}
   183  		return params.PublicAddressResults{PublicAddress: addr}, nil
   184  	}
   185  	return results, errgo.Newf("unknown unit or machine %q", p.Target)
   186  }
   187  
   188  // ServiceExpose changes the juju-managed firewall to expose any ports that
   189  // were also explicitly marked by units as open.
   190  func (c *Client) ServiceExpose(args params.ServiceExpose) error {
   191  	svc, err := c.api.state.Service(args.ServiceName)
   192  	if err != nil {
   193  		return mask(err, errors.IsNotFoundError)
   194  	}
   195  	return svc.SetExposed()
   196  }
   197  
   198  // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that
   199  // were also explicitly marked by units as open.
   200  func (c *Client) ServiceUnexpose(args params.ServiceUnexpose) error {
   201  	svc, err := c.api.state.Service(args.ServiceName)
   202  	if err != nil {
   203  		return mask(err, errors.IsNotFoundError)
   204  	}
   205  	return svc.ClearExposed()
   206  }
   207  
   208  var CharmStore charm.Repository = charm.Store
   209  
   210  // ServiceDeploy fetches the charm from the charm store and deploys it.
   211  // AddCharm or AddLocalCharm should be called to add the charm
   212  // before calling ServiceDeploy, although for backward compatibility
   213  // this is not necessary until 1.16 support is removed.
   214  func (c *Client) ServiceDeploy(args params.ServiceDeploy) error {
   215  	curl, err := charm.ParseURL(args.CharmUrl)
   216  	if err != nil {
   217  		return mask(err)
   218  	}
   219  	if curl.Revision < 0 {
   220  		return errgo.Newf("charm url must include revision")
   221  	}
   222  
   223  	// Try to find the charm URL in state first.
   224  	ch, err := c.api.state.Charm(curl)
   225  	if errors.IsNotFoundError(err) {
   226  		// Remove this whole if block when 1.16 compatibility is dropped.
   227  		if curl.Schema != "cs" {
   228  			return errgo.Newf(`charm url has unsupported schema %q`, curl.Schema)
   229  		}
   230  		err = c.AddCharm(params.CharmURL{args.CharmUrl})
   231  		if err != nil {
   232  			return mask(err)
   233  		}
   234  		ch, err = c.api.state.Charm(curl)
   235  		if err != nil {
   236  			return mask(err)
   237  		}
   238  	} else if err != nil {
   239  		return mask(err)
   240  	}
   241  
   242  	var settings charm.Settings
   243  	if len(args.ConfigYAML) > 0 {
   244  		settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName)
   245  	} else if len(args.Config) > 0 {
   246  		// Parse config in a compatile way (see function comment).
   247  		settings, err = parseSettingsCompatible(ch, args.Config)
   248  	}
   249  	if err != nil {
   250  		return mask(err)
   251  	}
   252  
   253  	_, err = juju.DeployService(c.api.state,
   254  		juju.DeployServiceParams{
   255  			ServiceName:    args.ServiceName,
   256  			Charm:          ch,
   257  			NumUnits:       args.NumUnits,
   258  			ConfigSettings: settings,
   259  			Constraints:    args.Constraints,
   260  			ToMachineSpec:  args.ToMachineSpec,
   261  		})
   262  	return mask(err)
   263  }
   264  
   265  // ServiceUpdate updates the service attributes, including charm URL,
   266  // minimum number of units, settings and constraints.
   267  // All parameters in params.ServiceUpdate except the service name are optional.
   268  func (c *Client) ServiceUpdate(args params.ServiceUpdate) error {
   269  	service, err := c.api.state.Service(args.ServiceName)
   270  	if err != nil {
   271  		return mask(err, errors.IsNotFoundError)
   272  	}
   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 mask(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 mask(err)
   284  		}
   285  	}
   286  	// Set up service's settings.
   287  	if args.SettingsYAML != "" {
   288  		if err = serviceSetSettingsYAML(service, args.SettingsYAML); err != nil {
   289  			return mask(err)
   290  		}
   291  	} else if len(args.SettingsStrings) > 0 {
   292  		if err = serviceSetSettingsStrings(service, args.SettingsStrings); err != nil {
   293  			return mask(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 mask(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 mask(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 errgo.Newf(`charm url has unsupported schema %q`, curl.Schema)
   327  	}
   328  	if curl.Revision < 0 {
   329  		return errgo.Newf("charm url must include revision")
   330  	}
   331  	err := c.AddCharm(params.CharmURL{curl.String()})
   332  	if err != nil {
   333  		return mask(err)
   334  	}
   335  	ch, err := c.api.state.Charm(curl)
   336  	if err != nil {
   337  		return mask(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 mask(err)
   348  	}
   349  	changes, err := ch.Config().ParseSettingsYAML([]byte(settings), service.Name())
   350  	if err != nil {
   351  		return mask(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 mask(err)
   362  	}
   363  	// Parse config in a compatible way (see function comment).
   364  	changes, err := parseSettingsCompatible(ch, settings)
   365  	if err != nil {
   366  		return mask(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 mask(err)
   380  	}
   381  
   382  	// Validate the settings.
   383  	changes, err := ch.Config().ParseSettingsStrings(settings)
   384  	if err != nil {
   385  		return mask(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 mask(err, errors.IsNotFoundError)
   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, mask(err, errors.IsNotFoundError)
   405  	}
   406  	if args.NumUnits < 1 {
   407  		return nil, errgo.Newf("must add at least one unit")
   408  	}
   409  	if args.NumUnits > 1 && args.ToMachineSpec != "" {
   410  		return nil, errgo.Newf("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{}, mask(err, errors.IsNotFoundError)
   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 = errgo.Newf("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 = errgo.Newf("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 mask(err, errors.IsNotFoundError)
   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{}, mask(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{}, mask(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 mask(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{}, mask(err, errors.IsNotFoundError)
   498  	}
   499  	rel, err := c.api.state.AddRelation(inEps...)
   500  	if err != nil {
   501  		return params.AddRelationResults{}, mask(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{}, mask(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 mask(err, errors.IsNotFoundError)
   519  	}
   520  	rel, err := c.api.state.EndpointsRelation(eps...)
   521  	if err != nil {
   522  		return mask(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, mask(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, errgo.Newf("parent machine specified without container type")
   569  	}
   570  
   571  	jobs, err := stateJobs(p.Jobs)
   572  	if err != nil {
   573  		return nil, mask(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, mask(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, mask(err, errors.IsNotFoundError)
   612  	}
   613  	mcfg.DisablePackageCommands = args.DisablePackageCommands
   614  	cloudcfg := coreCloudinit.New()
   615  	if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil {
   616  		return result, mask(err)
   617  	}
   618  
   619  	// ProvisioningScript is run on an existing machine;
   620  	// we explicitly disable apt_upgrade so as not to
   621  	// trample the machine's existing configuration.
   622  	cloudcfg.SetAptUpgrade(false)
   623  	result.Script, err = sshinit.ConfigureScript(cloudcfg)
   624  	return result, err
   625  }
   626  
   627  // DestroyMachines removes a given set of machines.
   628  func (c *Client) DestroyMachines(args params.DestroyMachines) error {
   629  	var errs []string
   630  	for _, id := range args.MachineNames {
   631  		machine, err := c.api.state.Machine(id)
   632  		switch {
   633  		case errors.IsNotFoundError(err):
   634  			err = errgo.Newf("machine %s does not exist", id)
   635  		case err != nil:
   636  		case args.Force:
   637  			err = machine.ForceDestroy()
   638  		case machine.Life() != state.Alive:
   639  			continue
   640  		default:
   641  			err = machine.Destroy()
   642  		}
   643  		if err != nil {
   644  			errs = append(errs, err.Error())
   645  		}
   646  	}
   647  	return destroyErr("machines", args.MachineNames, errs)
   648  }
   649  
   650  // CharmInfo returns information about the requested charm.
   651  func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) {
   652  	curl, err := charm.ParseURL(args.CharmURL)
   653  	if err != nil {
   654  		return api.CharmInfo{}, mask(err)
   655  	}
   656  	charm, err := c.api.state.Charm(curl)
   657  	if err != nil {
   658  		return api.CharmInfo{}, mask(err)
   659  	}
   660  	info := api.CharmInfo{
   661  		Revision: charm.Revision(),
   662  		URL:      curl.String(),
   663  		Config:   charm.Config(),
   664  		Meta:     charm.Meta(),
   665  	}
   666  	return info, nil
   667  }
   668  
   669  // EnvironmentInfo returns information about the current environment (default
   670  // series and type).
   671  func (c *Client) EnvironmentInfo() (api.EnvironmentInfo, error) {
   672  	state := c.api.state
   673  	conf, err := state.EnvironConfig()
   674  	if err != nil {
   675  		return api.EnvironmentInfo{}, mask(err)
   676  	}
   677  	env, err := state.Environment()
   678  	if err != nil {
   679  		return api.EnvironmentInfo{}, mask(err)
   680  	}
   681  
   682  	info := api.EnvironmentInfo{
   683  		DefaultSeries: conf.DefaultSeries(),
   684  		ProviderType:  conf.Type(),
   685  		Name:          conf.Name(),
   686  		UUID:          env.UUID(),
   687  	}
   688  	return info, nil
   689  }
   690  
   691  // GetAnnotations returns annotations about a given entity.
   692  func (c *Client) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) {
   693  	nothing := params.GetAnnotationsResults{}
   694  	entity, err := c.findEntity(args.Tag)
   695  	if err != nil {
   696  		return nothing, mask(err)
   697  	}
   698  	ann, err := entity.Annotations()
   699  	if err != nil {
   700  		return nothing, mask(err)
   701  	}
   702  	return params.GetAnnotationsResults{Annotations: ann}, nil
   703  }
   704  
   705  func (c *Client) findEntity(tag string) (state.Annotator, error) {
   706  	entity0, err := c.api.state.FindEntity(tag)
   707  	if err != nil {
   708  		return nil, mask(err, errgo.Any)
   709  	}
   710  	entity, ok := entity0.(state.Annotator)
   711  	if !ok {
   712  		return nil, common.NotSupportedError(tag, "annotations")
   713  	}
   714  	return entity, nil
   715  }
   716  
   717  // SetAnnotations stores annotations about a given entity.
   718  func (c *Client) SetAnnotations(args params.SetAnnotations) error {
   719  	entity, err := c.findEntity(args.Tag)
   720  	if err != nil {
   721  		return mask(err, errgo.Any)
   722  	}
   723  	return entity.SetAnnotations(args.Pairs)
   724  }
   725  
   726  // parseSettingsCompatible parses setting strings in a way that is
   727  // compatible with the behavior before this CL based on the issue
   728  // http://pad.lv/1194945. Until then setting an option to an empty
   729  // string caused it to reset to the default value. We now allow
   730  // empty strings as actual values, but we want to preserve the API
   731  // behavior.
   732  func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) {
   733  	setSettings := map[string]string{}
   734  	unsetSettings := charm.Settings{}
   735  	// Split settings into those which set and those which unset a value.
   736  	for name, value := range settings {
   737  		if value == "" {
   738  			unsetSettings[name] = nil
   739  			continue
   740  		}
   741  		setSettings[name] = value
   742  	}
   743  	// Validate the settings.
   744  	changes, err := ch.Config().ParseSettingsStrings(setSettings)
   745  	if err != nil {
   746  		// Validate the unsettings and merge them into the changes.
   747  		return nil, mask(err)
   748  	}
   749  
   750  	unsetSettings, err = ch.Config().ValidateSettings(unsetSettings)
   751  	if err != nil {
   752  		return nil, mask(err)
   753  	}
   754  	for name := range unsetSettings {
   755  		changes[name] = nil
   756  	}
   757  	return changes, nil
   758  }
   759  
   760  // EnvironmentGet implements the server-side part of the
   761  // get-environment CLI command.
   762  func (c *Client) EnvironmentGet() (params.EnvironmentGetResults, error) {
   763  	result := params.EnvironmentGetResults{}
   764  	// Get the existing environment config from the state.
   765  	config, err := c.api.state.EnvironConfig()
   766  	if err != nil {
   767  		return result, mask(err)
   768  	}
   769  	result.Config = config.AllAttrs()
   770  	return result, nil
   771  }
   772  
   773  // EnvironmentSet implements the server-side part of the
   774  // set-environment CLI command.
   775  func (c *Client) EnvironmentSet(args params.EnvironmentSet) error {
   776  	// TODO(dimitern,thumper): 2013-11-06 bug #1167616
   777  	// SetEnvironConfig should take both new and old configs.
   778  
   779  	// Get the existing environment config from the state.
   780  	oldConfig, err := c.api.state.EnvironConfig()
   781  	if err != nil {
   782  		return mask(err)
   783  	}
   784  	// Make sure we don't allow changing agent-version.
   785  	if v, found := args.Config["agent-version"]; found {
   786  		oldVersion, _ := oldConfig.AgentVersion()
   787  		if v != oldVersion.String() {
   788  			return errgo.Newf("agent-version cannot be changed")
   789  		}
   790  	}
   791  	// Apply the attributes specified for the command to the state config.
   792  	newConfig, err := oldConfig.Apply(args.Config)
   793  	if err != nil {
   794  		return mask(err)
   795  	}
   796  	env, err := environs.New(oldConfig)
   797  	if err != nil {
   798  		return mask(err)
   799  	}
   800  	// Now validate this new config against the existing config via the provider.
   801  	provider := env.Provider()
   802  	newProviderConfig, err := provider.Validate(newConfig, oldConfig)
   803  	if err != nil {
   804  		return mask(err)
   805  	}
   806  	// Now try to apply the new validated config.
   807  	return c.api.state.SetEnvironConfig(newProviderConfig, oldConfig)
   808  }
   809  
   810  // SetEnvironAgentVersion sets the environment agent version.
   811  func (c *Client) SetEnvironAgentVersion(args params.SetEnvironAgentVersion) error {
   812  	return c.api.state.SetEnvironAgentVersion(args.Version)
   813  }
   814  
   815  func destroyErr(desc string, ids, errs []string) error {
   816  	if len(errs) == 0 {
   817  		return nil
   818  	}
   819  	msg := "some %s were not destroyed"
   820  	if len(errs) == len(ids) {
   821  		msg = "no %s were destroyed"
   822  	}
   823  	msg = fmt.Sprintf(msg, desc)
   824  	return errgo.Newf("%s: %s", msg, strings.Join(errs, "; "))
   825  }
   826  
   827  // AddCharm adds the given charm URL (which must include revision) to
   828  // the environment, if it does not exist yet. Local charms are not
   829  // supported, only charm store URLs. See also AddLocalCharm().
   830  func (c *Client) AddCharm(args params.CharmURL) error {
   831  	charmURL, err := charm.ParseURL(args.URL)
   832  	if err != nil {
   833  		return mask(err)
   834  	}
   835  	if charmURL.Schema != "cs" {
   836  		return errgo.Newf("only charm store charm URLs are supported, with cs: schema")
   837  	}
   838  	if charmURL.Revision < 0 {
   839  		return errgo.Newf("charm URL must include revision")
   840  	}
   841  
   842  	// First, check if a pending or a real charm exists in state.
   843  	stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL)
   844  	if err == nil && stateCharm.IsUploaded() {
   845  		// Charm already in state (it was uploaded already).
   846  		return nil
   847  	} else if err != nil {
   848  		return mask(err)
   849  	}
   850  
   851  	// Get the charm and its information from the store.
   852  	envConfig, err := c.api.state.EnvironConfig()
   853  	if err != nil {
   854  		return mask(err)
   855  	}
   856  	store := config.AuthorizeCharmRepo(CharmStore, envConfig)
   857  	downloadedCharm, err := store.Get(charmURL)
   858  	if err != nil {
   859  		return errgo.Notef(err, "cannot download charm %q", charmURL.String())
   860  	}
   861  
   862  	// Open it and calculate the SHA256 hash.
   863  	downloadedBundle, ok := downloadedCharm.(*charm.Bundle)
   864  	if !ok {
   865  		return errgo.Newf("expected a charm archive, got %T", downloadedCharm)
   866  	}
   867  	archive, err := os.Open(downloadedBundle.Path)
   868  	if err != nil {
   869  		return errgo.NoteMask(err, "cannot read downloaded charm")
   870  	}
   871  	defer archive.Close()
   872  	bundleSHA256, size, err := utils.ReadSHA256(archive)
   873  	if err != nil {
   874  		return errgo.NoteMask(err, "cannot calculate SHA256 hash of charm")
   875  	}
   876  	if _, err := archive.Seek(0, 0); err != nil {
   877  		return errgo.NoteMask(err, "cannot rewind charm archive")
   878  	}
   879  
   880  	// Get the environment storage and upload the charm.
   881  	env, err := environs.New(envConfig)
   882  	if err != nil {
   883  		return errgo.NoteMask(err, "cannot access environment")
   884  	}
   885  	storage := env.Storage()
   886  	archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision)
   887  	if err != nil {
   888  		return errgo.NoteMask(err, "cannot generate charm archive name")
   889  	}
   890  	if err := storage.Put(archiveName, archive, size); err != nil {
   891  		return errgo.NoteMask(err, "cannot upload charm to provider storage")
   892  	}
   893  	storageURL, err := storage.URL(archiveName)
   894  	if err != nil {
   895  		return errgo.NoteMask(err, "cannot get storage URL for charm")
   896  	}
   897  	bundleURL, err := url.Parse(storageURL)
   898  	if err != nil {
   899  		return errgo.NoteMask(err, "cannot parse storage URL")
   900  	}
   901  
   902  	// Finally, update the charm data in state and mark it as no longer pending.
   903  	_, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256)
   904  	if err == state.ErrCharmRevisionAlreadyModified ||
   905  		state.IsCharmAlreadyUploadedError(err) {
   906  		// This is not an error, it just signifies somebody else
   907  		// managed to upload and update the charm in state before
   908  		// us. This means we have to delete what we just uploaded
   909  		// to storage.
   910  		if err := storage.Remove(archiveName); err != nil {
   911  			errgo.NoteMask(err, "cannot remove duplicated charm from storage")
   912  		}
   913  		return nil
   914  	}
   915  	return err
   916  }
   917  
   918  // CharmArchiveName returns a string that is suitable as a file name
   919  // in a storage URL. It is constructed from the charm name, revision
   920  // and a random UUID string.
   921  func CharmArchiveName(name string, revision int) (string, error) {
   922  	uuid, err := utils.NewUUID()
   923  	if err != nil {
   924  		return "", mask(err)
   925  	}
   926  	return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil
   927  }