github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/upgradecharm.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gnuflag"
    13  	"gopkg.in/juju/charm.v6"
    14  	charmresource "gopkg.in/juju/charm.v6/resource"
    15  	"gopkg.in/juju/charmrepo.v3"
    16  	csclientparams "gopkg.in/juju/charmrepo.v3/csclient/params"
    17  	"gopkg.in/juju/names.v2"
    18  	"gopkg.in/juju/worker.v1/catacomb"
    19  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    20  	"gopkg.in/macaroon.v2-unstable"
    21  
    22  	"github.com/juju/juju/api"
    23  	"github.com/juju/juju/api/application"
    24  	"github.com/juju/juju/api/base"
    25  	"github.com/juju/juju/api/charms"
    26  	"github.com/juju/juju/api/controller"
    27  	"github.com/juju/juju/api/modelconfig"
    28  	"github.com/juju/juju/apiserver/params"
    29  	"github.com/juju/juju/charmstore"
    30  	jujucmd "github.com/juju/juju/cmd"
    31  	"github.com/juju/juju/cmd/juju/block"
    32  	"github.com/juju/juju/cmd/juju/common"
    33  	"github.com/juju/juju/cmd/modelcmd"
    34  	"github.com/juju/juju/core/model"
    35  	"github.com/juju/juju/environs/config"
    36  	"github.com/juju/juju/resource"
    37  	"github.com/juju/juju/resource/resourceadapters"
    38  	"github.com/juju/juju/storage"
    39  )
    40  
    41  // NewUpgradeCharmCommand returns a command which upgrades application's charm.
    42  func NewUpgradeCharmCommand() cmd.Command {
    43  	cmd := &upgradeCharmCommand{
    44  		DeployResources: resourceadapters.DeployResources,
    45  		ResolveCharm:    resolveCharm,
    46  		NewCharmAdder:   newCharmAdder,
    47  		NewCharmClient: func(conn base.APICallCloser) CharmClient {
    48  			return charms.NewClient(conn)
    49  		},
    50  		NewCharmUpgradeClient: func(conn base.APICallCloser) CharmAPIClient {
    51  			return application.NewClient(conn)
    52  		},
    53  		NewModelConfigGetter: func(conn base.APICallCloser) ModelConfigGetter {
    54  			return modelconfig.NewClient(conn)
    55  		},
    56  		NewResourceLister: func(conn base.APICallCloser) (ResourceLister, error) {
    57  			resclient, err := resourceadapters.NewAPIClient(conn)
    58  			if err != nil {
    59  				return nil, err
    60  			}
    61  			return resclient, nil
    62  		},
    63  		CharmStoreURLGetter: getCharmStoreAPIURL,
    64  	}
    65  	return modelcmd.Wrap(cmd)
    66  }
    67  
    68  // CharmAPIClient defines a subset of the application facade that deals with
    69  // charm related upgrades.
    70  type CharmAPIClient interface {
    71  	CharmUpgradeClient
    72  }
    73  
    74  // CharmUpgradeClient defines a subset of the application facade, as required
    75  // by the upgrade-charm command.
    76  type CharmUpgradeClient interface {
    77  	GetCharmURL(model.GenerationVersion, string) (*charm.URL, error)
    78  	Get(model.GenerationVersion, string) (*params.ApplicationGetResults, error)
    79  	SetCharm(model.GenerationVersion, application.SetCharmConfig) error
    80  }
    81  
    82  // CharmClient defines a subset of the charms facade, as required
    83  // by the upgrade-charm command.
    84  type CharmClient interface {
    85  	CharmInfo(string) (*charms.CharmInfo, error)
    86  }
    87  
    88  // ResourceLister defines a subset of the resources facade, as required
    89  // by the upgrade-charm command.
    90  type ResourceLister interface {
    91  	ListResources([]string) ([]resource.ApplicationResources, error)
    92  }
    93  
    94  // NewCharmAdderFunc is the type of a function used to construct
    95  // a new CharmAdder.
    96  type NewCharmAdderFunc func(
    97  	api.Connection,
    98  	*httpbakery.Client,
    99  	string, // Charmstore API URL
   100  	csclientparams.Channel,
   101  ) CharmAdder
   102  
   103  // UpgradeCharm is responsible for upgrading an application's charm.
   104  type upgradeCharmCommand struct {
   105  	modelcmd.ModelCommandBase
   106  
   107  	DeployResources       resourceadapters.DeployResourcesFunc
   108  	ResolveCharm          ResolveCharmFunc
   109  	NewCharmAdder         NewCharmAdderFunc
   110  	NewCharmClient        func(base.APICallCloser) CharmClient
   111  	NewCharmUpgradeClient func(base.APICallCloser) CharmAPIClient
   112  	NewModelConfigGetter  func(base.APICallCloser) ModelConfigGetter
   113  	NewResourceLister     func(base.APICallCloser) (ResourceLister, error)
   114  	CharmStoreURLGetter   func(base.APICallCloser) (string, error)
   115  
   116  	ApplicationName string
   117  	// Force should be ubiquitous and we should eventually deprecate both
   118  	// ForceUnits and ForceSeries; instead just using "force"
   119  	Force       bool
   120  	ForceUnits  bool
   121  	ForceSeries bool
   122  	SwitchURL   string
   123  	CharmPath   string
   124  	Revision    int // defaults to -1 (latest)
   125  
   126  	// Resources is a map of resource name to filename to be uploaded on upgrade.
   127  	Resources map[string]string
   128  
   129  	// Channel holds the charmstore channel to use when obtaining
   130  	// the charm to be upgraded to.
   131  	Channel csclientparams.Channel
   132  
   133  	// Config is a config file variable, pointing at a YAML file containing
   134  	// the application config to update.
   135  	Config cmd.FileVar
   136  
   137  	// Storage is a map of storage constraints, keyed on the storage name
   138  	// defined in charm storage metadata, to add or update during upgrade.
   139  	Storage map[string]storage.Constraints
   140  
   141  	catacomb catacomb.Catacomb
   142  	plan     catacomb.Plan
   143  }
   144  
   145  const upgradeCharmDoc = `
   146  When no options are set, the application's charm will be upgraded to the latest
   147  revision available in the repository from which it was originally deployed. An
   148  explicit revision can be chosen with the --revision option.
   149  
   150  A path will need to be supplied to allow an updated copy of the charm
   151  to be located.
   152  
   153  Deploying from a path is intended to suit the workflow of a charm author working
   154  on a single client machine; use of this deployment method from multiple clients
   155  is not supported and may lead to confusing behaviour. Each local charm gets
   156  uploaded with the revision specified in the charm, if possible, otherwise it
   157  gets a unique revision (highest in state + 1).
   158  
   159  When deploying from a path, the --path option is used to specify the location from
   160  which to load the updated charm. Note that the directory containing the charm must
   161  match what was originally used to deploy the charm as a superficial check that the
   162  updated charm is compatible.
   163  
   164  Resources may be uploaded at upgrade time by specifying the --resource option.
   165  Following the resource option should be name=filepath pair.  This option may be
   166  repeated more than once to upload more than one resource.
   167  
   168    juju upgrade-charm foo --resource bar=/some/file.tgz --resource baz=./docs/cfg.xml
   169  
   170  Where bar and baz are resources named in the metadata for the foo charm.
   171  
   172  Storage constraints may be added or updated at upgrade time by specifying
   173  the --storage option, with the same format as specified in "juju deploy".
   174  If new required storage is added by the new charm revision, then you must
   175  specify constraints or the defaults will be applied.
   176  
   177    juju upgrade-charm foo --storage cache=ssd,10G
   178  
   179  Charm settings may be added or updated at upgrade time by specifying the
   180  --config option, pointing to a YAML-encoded application config file.
   181  
   182    juju upgrade-charm foo --config config.yaml
   183  
   184  If the new version of a charm does not explicitly support the application's series, the
   185  upgrade is disallowed unless the --force-series option is used. This option should be
   186  used with caution since using a charm on a machine running an unsupported series may
   187  cause unexpected behavior.
   188  
   189  The --switch option allows you to replace the charm with an entirely different one.
   190  The new charm's URL and revision are inferred as they would be when running a
   191  deploy command.
   192  
   193  Please note that --switch is dangerous, because juju only has limited
   194  information with which to determine compatibility; the operation will succeed,
   195  regardless of potential havoc, so long as the following conditions hold:
   196  
   197  - The new charm must declare all relations that the application is currently
   198  participating in.
   199  - All config settings shared by the old and new charms must
   200  have the same types.
   201  
   202  The new charm may add new relations and configuration settings.
   203  
   204  --switch and --path are mutually exclusive.
   205  
   206  --path and --revision are mutually exclusive. The revision of the updated charm
   207  is determined by the contents of the charm at the specified path.
   208  
   209  --switch and --revision are mutually exclusive. To specify a given revision
   210  number with --switch, give it in the charm URL, for instance "cs:wordpress-5"
   211  would specify revision number 5 of the wordpress charm.
   212  
   213  Use of the --force-units option is not generally recommended; units upgraded while in an
   214  error state will not have upgrade-charm hooks executed, and may cause unexpected
   215  behavior.
   216  
   217  --force option for LXD Profiles is not generally recommended when upgrading an 
   218  application; overriding profiles on the container may cause unexpected 
   219  behavior. 
   220  `
   221  
   222  func (c *upgradeCharmCommand) Info() *cmd.Info {
   223  	return jujucmd.Info(&cmd.Info{
   224  		Name:    "upgrade-charm",
   225  		Args:    "<application>",
   226  		Purpose: "Upgrade an application's charm.",
   227  		Doc:     upgradeCharmDoc,
   228  	})
   229  }
   230  
   231  func (c *upgradeCharmCommand) SetFlags(f *gnuflag.FlagSet) {
   232  	c.ModelCommandBase.SetFlags(f)
   233  	f.BoolVar(&c.Force, "force", false, "Allow a charm to be upgraded which bypasses LXD profile allow list")
   234  	f.BoolVar(&c.ForceUnits, "force-units", false, "Upgrade all units immediately, even if in error state")
   235  	f.StringVar((*string)(&c.Channel), "channel", "", "Channel to use when getting the charm or bundle from the charm store")
   236  	f.BoolVar(&c.ForceSeries, "force-series", false, "Upgrade even if series of deployed applications are not supported by the new charm")
   237  	f.StringVar(&c.SwitchURL, "switch", "", "Crossgrade to a different charm")
   238  	f.StringVar(&c.CharmPath, "path", "", "Upgrade to a charm located at path")
   239  	f.IntVar(&c.Revision, "revision", -1, "Explicit revision of current charm")
   240  	f.Var(stringMap{&c.Resources}, "resource", "Resource to be uploaded to the controller")
   241  	f.Var(storageFlag{&c.Storage, nil}, "storage", "Charm storage constraints")
   242  	f.Var(&c.Config, "config", "Path to yaml-formatted application config")
   243  }
   244  
   245  func (c *upgradeCharmCommand) Init(args []string) error {
   246  	switch len(args) {
   247  	case 1:
   248  		if !names.IsValidApplication(args[0]) {
   249  			return errors.Errorf("invalid application name %q", args[0])
   250  		}
   251  		c.ApplicationName = args[0]
   252  	case 0:
   253  		return errors.Errorf("no application specified")
   254  	default:
   255  		return cmd.CheckEmpty(args[1:])
   256  	}
   257  	if c.SwitchURL != "" && c.Revision != -1 {
   258  		return errors.Errorf("--switch and --revision are mutually exclusive")
   259  	}
   260  	if c.CharmPath != "" && c.Revision != -1 {
   261  		return errors.Errorf("--path and --revision are mutually exclusive")
   262  	}
   263  	if c.SwitchURL != "" && c.CharmPath != "" {
   264  		return errors.Errorf("--switch and --path are mutually exclusive")
   265  	}
   266  	return nil
   267  }
   268  
   269  // Run connects to the specified environment and starts the charm
   270  // upgrade process.
   271  func (c *upgradeCharmCommand) Run(ctx *cmd.Context) error {
   272  	apiRoot, err := c.NewAPIRoot()
   273  	if err != nil {
   274  		return errors.Trace(err)
   275  	}
   276  	defer func() { _ = apiRoot.Close() }()
   277  
   278  	// If the user has specified config or storage constraints,
   279  	// make sure the server has facade version 2 at a minimum.
   280  	if c.Config.Path != "" || len(c.Storage) > 0 {
   281  		action := "updating config"
   282  		if c.Config.Path == "" {
   283  			action = "updating storage constraints"
   284  		}
   285  		if apiRoot.BestFacadeVersion("Application") < 2 {
   286  			suffix := "this server"
   287  			if version, ok := apiRoot.ServerVersion(); ok {
   288  				suffix = fmt.Sprintf("server version %s", version)
   289  			}
   290  			return errors.New(action + " at upgrade-charm time is not supported by " + suffix)
   291  		}
   292  	}
   293  
   294  	generation, err := c.ModelGeneration()
   295  	if err != nil {
   296  		return errors.Trace(err)
   297  	}
   298  	charmUpgradeClient := c.NewCharmUpgradeClient(apiRoot)
   299  	oldURL, err := charmUpgradeClient.GetCharmURL(generation, c.ApplicationName)
   300  	if err != nil {
   301  		return errors.Trace(err)
   302  	}
   303  
   304  	newRef := c.SwitchURL
   305  	if newRef == "" {
   306  		newRef = c.CharmPath
   307  	}
   308  	if c.SwitchURL == "" && c.CharmPath == "" {
   309  		// If the charm we are upgrading is local, then we must
   310  		// specify a path or switch url to upgrade with.
   311  		if oldURL.Schema == "local" {
   312  			return errors.New("upgrading a local charm requires either --path or --switch")
   313  		}
   314  		// No new URL specified, but revision might have been.
   315  		newRef = oldURL.WithRevision(c.Revision).String()
   316  	}
   317  
   318  	// First, ensure the charm is added to the model.
   319  	modelConfigGetter := c.NewModelConfigGetter(apiRoot)
   320  	modelConfig, err := getModelConfig(modelConfigGetter)
   321  	if err != nil {
   322  		return errors.Trace(err)
   323  	}
   324  	bakeryClient, err := c.BakeryClient()
   325  	if err != nil {
   326  		return errors.Trace(err)
   327  	}
   328  	conAPIRoot, err := c.NewControllerAPIRoot()
   329  	if err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  	csURL, err := c.CharmStoreURLGetter(conAPIRoot)
   333  	if err != nil {
   334  		return errors.Trace(err)
   335  	}
   336  
   337  	applicationInfo, err := charmUpgradeClient.Get(generation, c.ApplicationName)
   338  	if err != nil {
   339  		return errors.Trace(err)
   340  	}
   341  
   342  	if c.Channel == "" {
   343  		c.Channel = csclientparams.Channel(applicationInfo.Channel)
   344  	}
   345  	charmAdder := c.NewCharmAdder(apiRoot, bakeryClient, csURL, c.Channel)
   346  	charmRepo := c.getCharmStore(bakeryClient, csURL, modelConfig)
   347  
   348  	deployedSeries := applicationInfo.Series
   349  
   350  	chID, csMac, err := c.addCharm(charmAdder, charmRepo, modelConfig, oldURL, newRef, deployedSeries, c.Force)
   351  	if err != nil {
   352  		if termErr, ok := errors.Cause(err).(*common.TermsRequiredError); ok {
   353  			return errors.Trace(termErr.UserErr())
   354  		}
   355  		return block.ProcessBlockedError(err, block.BlockChange)
   356  	}
   357  	ctx.Infof("Added charm %q to the model.", chID.URL)
   358  
   359  	// Next, upgrade resources.
   360  	charmsClient := c.NewCharmClient(apiRoot)
   361  	resourceLister, err := c.NewResourceLister(apiRoot)
   362  	if err != nil {
   363  		return errors.Trace(err)
   364  	}
   365  	ids, err := c.upgradeResources(apiRoot, charmsClient, resourceLister, chID, csMac)
   366  	if err != nil {
   367  		return errors.Trace(err)
   368  	}
   369  
   370  	// Finally, upgrade the application.
   371  	var configYAML []byte
   372  	if c.Config.Path != "" {
   373  		configYAML, err = c.Config.Read(ctx)
   374  		if err != nil {
   375  			return errors.Trace(err)
   376  		}
   377  	}
   378  	cfg := application.SetCharmConfig{
   379  		ApplicationName:    c.ApplicationName,
   380  		CharmID:            chID,
   381  		ConfigSettingsYAML: string(configYAML),
   382  		Force:              c.Force,
   383  		ForceSeries:        c.ForceSeries,
   384  		ForceUnits:         c.ForceUnits,
   385  		ResourceIDs:        ids,
   386  		StorageConstraints: c.Storage,
   387  	}
   388  	return block.ProcessBlockedError(charmUpgradeClient.SetCharm(generation, cfg), block.BlockChange)
   389  }
   390  
   391  // upgradeResources pushes metadata up to the server for each resource defined
   392  // in the new charm's metadata and returns a map of resource names to pending
   393  // IDs to include in the upgrage-charm call.
   394  //
   395  // TODO(axw) apiRoot is passed in here because DeployResources requires it,
   396  // DeployResources should accept a resource-specific client instead.
   397  func (c *upgradeCharmCommand) upgradeResources(
   398  	apiRoot base.APICallCloser,
   399  	charmsClient CharmClient,
   400  	resourceLister ResourceLister,
   401  	chID charmstore.CharmID,
   402  	csMac *macaroon.Macaroon,
   403  ) (map[string]string, error) {
   404  	filtered, err := getUpgradeResources(
   405  		charmsClient,
   406  		resourceLister,
   407  		c.ApplicationName,
   408  		chID.URL,
   409  		c.Resources,
   410  	)
   411  	if err != nil {
   412  		return nil, errors.Trace(err)
   413  	}
   414  	if len(filtered) == 0 {
   415  		return nil, nil
   416  	}
   417  
   418  	// Note: the validity of user-supplied resources to be uploaded will be
   419  	// checked further down the stack.
   420  	ids, err := c.DeployResources(
   421  		c.ApplicationName,
   422  		chID,
   423  		csMac,
   424  		c.Resources,
   425  		filtered,
   426  		apiRoot,
   427  	)
   428  	return ids, errors.Trace(err)
   429  }
   430  
   431  func getUpgradeResources(
   432  	charmsClient CharmClient,
   433  	resourceLister ResourceLister,
   434  	applicationID string,
   435  	charmURL *charm.URL,
   436  	cliResources map[string]string,
   437  ) (map[string]charmresource.Meta, error) {
   438  	meta, err := getMetaResources(charmURL, charmsClient)
   439  	if err != nil {
   440  		return nil, errors.Trace(err)
   441  	}
   442  	if len(meta) == 0 {
   443  		return nil, nil
   444  	}
   445  
   446  	current, err := getResources(applicationID, resourceLister)
   447  	if err != nil {
   448  		return nil, errors.Trace(err)
   449  	}
   450  	filtered := filterResources(meta, current, cliResources)
   451  	return filtered, nil
   452  }
   453  
   454  func getMetaResources(charmURL *charm.URL, client CharmClient) (map[string]charmresource.Meta, error) {
   455  	charmInfo, err := client.CharmInfo(charmURL.String())
   456  	if err != nil {
   457  		return nil, errors.Trace(err)
   458  	}
   459  	return charmInfo.Meta.Resources, nil
   460  }
   461  
   462  func getResources(applicationID string, resourceLister ResourceLister) (map[string]resource.Resource, error) {
   463  	svcs, err := resourceLister.ListResources([]string{applicationID})
   464  	if err != nil {
   465  		return nil, errors.Trace(err)
   466  	}
   467  	return resource.AsMap(svcs[0].Resources), nil
   468  }
   469  
   470  func filterResources(
   471  	meta map[string]charmresource.Meta,
   472  	current map[string]resource.Resource,
   473  	uploads map[string]string,
   474  ) map[string]charmresource.Meta {
   475  	filtered := make(map[string]charmresource.Meta)
   476  	for name, res := range meta {
   477  		if shouldUpgradeResource(res, uploads, current) {
   478  			filtered[name] = res
   479  		}
   480  	}
   481  	return filtered
   482  }
   483  
   484  // shouldUpgradeResource reports whether we should upload the metadata for the given
   485  // resource.  This is always true for resources we're adding with the --resource
   486  // flag. For resources we're not adding with --resource, we only upload metadata
   487  // for charmstore resources.  Previously uploaded resources stay pinned to the
   488  // data the user uploaded.
   489  func shouldUpgradeResource(res charmresource.Meta, uploads map[string]string, current map[string]resource.Resource) bool {
   490  	// Always upload metadata for resources the user is uploading during
   491  	// upgrade-charm.
   492  	if _, ok := uploads[res.Name]; ok {
   493  		return true
   494  	}
   495  	cur, ok := current[res.Name]
   496  	if !ok {
   497  		// If there's no information on the server, there should be.
   498  		return true
   499  	}
   500  	// Never override existing resources a user has already uploaded.
   501  	if cur.Origin == charmresource.OriginUpload {
   502  		return false
   503  	}
   504  	return true
   505  }
   506  
   507  func newCharmAdder(
   508  	api api.Connection,
   509  	bakeryClient *httpbakery.Client,
   510  	csURL string,
   511  	channel csclientparams.Channel,
   512  ) CharmAdder {
   513  	csClient := newCharmStoreClient(bakeryClient, csURL).WithChannel(channel)
   514  
   515  	// TODO(katco): This anonymous adapter should go away in favor of
   516  	// a comprehensive API passed into the upgrade-charm command.
   517  	charmstoreAdapter := &struct {
   518  		*charmstoreClient
   519  		*apiClient
   520  	}{
   521  		charmstoreClient: &charmstoreClient{Client: csClient},
   522  		apiClient:        &apiClient{Client: api.Client()},
   523  	}
   524  	return charmstoreAdapter
   525  }
   526  
   527  func (c *upgradeCharmCommand) getCharmStore(
   528  	bakeryClient *httpbakery.Client,
   529  	csURL string,
   530  	modelConfig *config.Config,
   531  ) *charmrepo.CharmStore {
   532  	csClient := newCharmStoreClient(bakeryClient, csURL).WithChannel(c.Channel)
   533  	return config.SpecializeCharmRepo(
   534  		charmrepo.NewCharmStoreFromClient(csClient),
   535  		modelConfig,
   536  	).(*charmrepo.CharmStore)
   537  }
   538  
   539  // getCharmStoreAPIURL consults the controller config for the charmstore api url to use.
   540  var getCharmStoreAPIURL = func(conAPIRoot base.APICallCloser) (string, error) {
   541  	controllerAPI := controller.NewClient(conAPIRoot)
   542  	controllerCfg, err := controllerAPI.ControllerConfig()
   543  	if err != nil {
   544  		return "", errors.Trace(err)
   545  	}
   546  	return controllerCfg.CharmStoreURL(), nil
   547  }
   548  
   549  // addCharm interprets the new charmRef and adds the specified charm if
   550  // the new charm is different to what's already deployed as specified by
   551  // oldURL.
   552  func (c *upgradeCharmCommand) addCharm(
   553  	charmAdder CharmAdder,
   554  	charmRepo *charmrepo.CharmStore,
   555  	config *config.Config,
   556  	oldURL *charm.URL,
   557  	charmRef string,
   558  	deployedSeries string,
   559  	force bool,
   560  ) (charmstore.CharmID, *macaroon.Macaroon, error) {
   561  	var id charmstore.CharmID
   562  	// Charm may have been supplied via a path reference. If so, build a
   563  	// local charm URL from the deployed series.
   564  	ch, newURL, err := charmrepo.NewCharmAtPathForceSeries(charmRef, deployedSeries, c.ForceSeries)
   565  	if err == nil {
   566  		newName := ch.Meta().Name
   567  		if newName != oldURL.Name {
   568  			return id, nil, errors.Errorf("cannot upgrade %q to %q", oldURL.Name, newName)
   569  		}
   570  		addedURL, err := charmAdder.AddLocalCharm(newURL, ch, force)
   571  		id.URL = addedURL
   572  		return id, nil, err
   573  	}
   574  	if _, ok := err.(*charmrepo.NotFoundError); ok {
   575  		return id, nil, errors.Errorf("no charm found at %q", charmRef)
   576  	}
   577  	// If we get a "not exists" or invalid path error then we attempt to interpret
   578  	// the supplied charm reference as a URL below, otherwise we return the error.
   579  	if err != os.ErrNotExist && !charmrepo.IsInvalidPathError(err) {
   580  		return id, nil, err
   581  	}
   582  
   583  	refURL, err := charm.ParseURL(charmRef)
   584  	if err != nil {
   585  		return id, nil, errors.Trace(err)
   586  	}
   587  
   588  	// Charm has been supplied as a URL so we resolve and deploy using the store.
   589  	newURL, channel, supportedSeries, err := c.ResolveCharm(charmRepo.ResolveWithChannel, refURL)
   590  	if err != nil {
   591  		return id, nil, errors.Trace(err)
   592  	}
   593  	id.Channel = channel
   594  	_, seriesSupportedErr := charm.SeriesForCharm(deployedSeries, supportedSeries)
   595  	if !c.ForceSeries && deployedSeries != "" && newURL.Series == "" && seriesSupportedErr != nil {
   596  		series := []string{"no series"}
   597  		if len(supportedSeries) > 0 {
   598  			series = supportedSeries
   599  		}
   600  		return id, nil, errors.Errorf(
   601  			"cannot upgrade from single series %q charm to a charm supporting %q. Use --force-series to override.",
   602  			deployedSeries, series,
   603  		)
   604  	}
   605  	// If no explicit revision was set with either SwitchURL
   606  	// or Revision flags, discover the latest.
   607  	if *newURL == *oldURL {
   608  		if refURL.Revision != -1 {
   609  			return id, nil, errors.Errorf("already running specified charm %q", newURL)
   610  		}
   611  		// No point in trying to upgrade a charm store charm when
   612  		// we just determined that's the latest revision
   613  		// available.
   614  		return id, nil, errors.Errorf("already running latest charm %q", newURL)
   615  	}
   616  
   617  	curl, csMac, err := addCharmFromURL(charmAdder, newURL, channel, force)
   618  	if err != nil {
   619  		return id, nil, errors.Trace(err)
   620  	}
   621  	id.URL = curl
   622  	return id, csMac, nil
   623  }