github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/charm.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"regexp"
     8  
     9  	"github.com/juju/errors"
    10  	jujutxn "github.com/juju/txn"
    11  	"github.com/juju/version"
    12  	"gopkg.in/juju/charm.v6-unstable"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/macaroon.v1"
    15  	"gopkg.in/mgo.v2"
    16  	"gopkg.in/mgo.v2/bson"
    17  	"gopkg.in/mgo.v2/txn"
    18  
    19  	"github.com/juju/juju/mongo"
    20  	"github.com/juju/juju/state/storage"
    21  	jujuversion "github.com/juju/juju/version"
    22  )
    23  
    24  // MacaroonCache is a type that wraps State and implements charmstore.MacaroonCache.
    25  type MacaroonCache struct {
    26  	*State
    27  }
    28  
    29  // Set stores the macaroon on the charm.
    30  func (m MacaroonCache) Set(u *charm.URL, ms macaroon.Slice) error {
    31  	c, err := m.Charm(u)
    32  	if err != nil {
    33  		return errors.Trace(err)
    34  	}
    35  	return c.UpdateMacaroon(ms)
    36  }
    37  
    38  // Get retrieves the macaroon for the charm (if any).
    39  func (m MacaroonCache) Get(u *charm.URL) (macaroon.Slice, error) {
    40  	c, err := m.Charm(u)
    41  	if err != nil {
    42  		return nil, errors.Trace(err)
    43  	}
    44  	return c.Macaroon()
    45  }
    46  
    47  // charmDoc represents the internal state of a charm in MongoDB.
    48  type charmDoc struct {
    49  	DocID string     `bson:"_id"`
    50  	URL   *charm.URL `bson:"url"` // DANGEROUS see charm.* fields below
    51  
    52  	// Life manages charm lifetime in the usual way, but only local
    53  	// charms can actually be "destroyed"; store charms are
    54  	// immortal. When a local charm is removed, its document is left
    55  	// in place, with Life set to Dead, to ensure we don't
    56  	// accidentally reuse the charm URL, which must be unique within
    57  	// a model.
    58  	//
    59  	// Note that this aligns with the existing contract implied by
    60  	// Dead: that most clients should see it as not existing at all.
    61  	// Nothing strictly obliges us to clean up the doc.
    62  	Life Life `bson:"life"`
    63  
    64  	// These fields are flags; if any of them is set, the charm
    65  	// cannot actually be safely used for anything.
    66  	PendingUpload bool `bson:"pendingupload"`
    67  	Placeholder   bool `bson:"placeholder"`
    68  
    69  	// These fields control access to the charm archive.
    70  	BundleSha256 string `bson:"bundlesha256"`
    71  	StoragePath  string `bson:"storagepath"`
    72  	Macaroon     []byte `bson:"macaroon"`
    73  
    74  	// The remaining fields hold data sufficient to define a
    75  	// charm.Charm.
    76  
    77  	// TODO(fwereade) 2015-06-18 lp:1467964
    78  	// DANGEROUS: our schema can change any time the charm package changes,
    79  	// and we have no automated way to detect when that happens. We *must*
    80  	// not depend upon serializations we cannot control from inside this
    81  	// package. What's in a *charm.Meta? What will be tomorrow? What logic
    82  	// will we be writing on the assumption that all stored Metas have set
    83  	// some field? What fields might lose precision when they go into the
    84  	// database?
    85  	Meta    *charm.Meta    `bson:"meta"`
    86  	Config  *charm.Config  `bson:"config"`
    87  	Actions *charm.Actions `bson:"actions"`
    88  	Metrics *charm.Metrics `bson:"metrics"`
    89  }
    90  
    91  // CharmInfo contains all the data necessary to store a charm's metadata.
    92  type CharmInfo struct {
    93  	Charm       charm.Charm
    94  	ID          *charm.URL
    95  	StoragePath string
    96  	SHA256      string
    97  	Macaroon    macaroon.Slice
    98  }
    99  
   100  // insertCharmOps returns the txn operations necessary to insert the supplied
   101  // charm data. If curl is nil, an error will be returned.
   102  func insertCharmOps(st *State, info CharmInfo) ([]txn.Op, error) {
   103  	if info.ID == nil {
   104  		return nil, errors.New("*charm.URL was nil")
   105  	}
   106  
   107  	doc := charmDoc{
   108  		DocID:        info.ID.String(),
   109  		URL:          info.ID,
   110  		Meta:         info.Charm.Meta(),
   111  		Config:       safeConfig(info.Charm),
   112  		Metrics:      info.Charm.Metrics(),
   113  		Actions:      info.Charm.Actions(),
   114  		BundleSha256: info.SHA256,
   115  		StoragePath:  info.StoragePath,
   116  	}
   117  	if info.Macaroon != nil {
   118  		mac, err := info.Macaroon.MarshalBinary()
   119  		if err != nil {
   120  			return nil, errors.Annotate(err, "can't convert macaroon to binary for storage")
   121  		}
   122  		doc.Macaroon = mac
   123  	}
   124  	return insertAnyCharmOps(st, &doc)
   125  }
   126  
   127  // insertPlaceholderCharmOps returns the txn operations necessary to insert a
   128  // charm document referencing a store charm that is not yet directly accessible
   129  // within the model. If curl is nil, an error will be returned.
   130  func insertPlaceholderCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
   131  	if curl == nil {
   132  		return nil, errors.New("*charm.URL was nil")
   133  	}
   134  	return insertAnyCharmOps(st, &charmDoc{
   135  		DocID:       curl.String(),
   136  		URL:         curl,
   137  		Placeholder: true,
   138  	})
   139  }
   140  
   141  // insertPendingCharmOps returns the txn operations necessary to insert a charm
   142  // document referencing a charm that has yet to be uploaded to the model.
   143  // If curl is nil, an error will be returned.
   144  func insertPendingCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
   145  	if curl == nil {
   146  		return nil, errors.New("*charm.URL was nil")
   147  	}
   148  	return insertAnyCharmOps(st, &charmDoc{
   149  		DocID:         curl.String(),
   150  		URL:           curl,
   151  		PendingUpload: true,
   152  	})
   153  }
   154  
   155  // insertAnyCharmOps returns the txn operations necessary to insert the supplied
   156  // charm document.
   157  func insertAnyCharmOps(st modelBackend, cdoc *charmDoc) ([]txn.Op, error) {
   158  
   159  	charms, closer := st.getCollection(charmsC)
   160  	defer closer()
   161  
   162  	life, err := nsLife.read(charms, cdoc.DocID)
   163  	if errors.IsNotFound(err) {
   164  		// everything is as it should be
   165  	} else if err != nil {
   166  		return nil, errors.Trace(err)
   167  	} else if life == Dead {
   168  		return nil, errors.New("url already consumed")
   169  	} else {
   170  		return nil, errors.New("already exists")
   171  	}
   172  	charmOp := txn.Op{
   173  		C:      charmsC,
   174  		Id:     cdoc.DocID,
   175  		Assert: txn.DocMissing,
   176  		Insert: cdoc,
   177  	}
   178  
   179  	refcounts, closer := st.getCollection(refcountsC)
   180  	defer closer()
   181  
   182  	charmKey := charmGlobalKey(cdoc.URL)
   183  	refOp, required, err := nsRefcounts.LazyCreateOp(refcounts, charmKey)
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	} else if required {
   187  		return []txn.Op{refOp, charmOp}, nil
   188  	}
   189  	return []txn.Op{charmOp}, nil
   190  }
   191  
   192  // updateCharmOps returns the txn operations necessary to update the charm
   193  // document with the supplied data, so long as the supplied assert still holds
   194  // true.
   195  func updateCharmOps(
   196  	st *State, info CharmInfo, assert bson.D,
   197  ) ([]txn.Op, error) {
   198  
   199  	charms, closer := st.getCollection(charmsC)
   200  	defer closer()
   201  
   202  	charmKey := info.ID.String()
   203  	op, err := nsLife.aliveOp(charms, charmKey)
   204  	if err != nil {
   205  		return nil, errors.Annotate(err, "charm")
   206  	}
   207  	lifeAssert, ok := op.Assert.(bson.D)
   208  	if !ok {
   209  		return nil, errors.Errorf("expected bson.D, got %#v", op.Assert)
   210  	}
   211  	op.Assert = append(lifeAssert, assert...)
   212  
   213  	data := bson.D{
   214  		{"meta", info.Charm.Meta()},
   215  		{"config", safeConfig(info.Charm)},
   216  		{"actions", info.Charm.Actions()},
   217  		{"metrics", info.Charm.Metrics()},
   218  		{"storagepath", info.StoragePath},
   219  		{"bundlesha256", info.SHA256},
   220  		{"pendingupload", false},
   221  		{"placeholder", false},
   222  	}
   223  	if len(info.Macaroon) > 0 {
   224  		mac, err := info.Macaroon.MarshalBinary()
   225  		if err != nil {
   226  			return nil, errors.Annotate(err, "can't convert macaroon to binary for storage")
   227  		}
   228  		data = append(data, bson.DocElem{"macaroon", mac})
   229  	}
   230  
   231  	op.Update = bson.D{{"$set", data}}
   232  	return []txn.Op{op}, nil
   233  }
   234  
   235  // convertPlaceholderCharmOps returns the txn operations necessary to convert
   236  // the charm with the supplied docId from a placeholder to one marked for
   237  // pending upload.
   238  func convertPlaceholderCharmOps(docID string) ([]txn.Op, error) {
   239  	return []txn.Op{{
   240  		C:  charmsC,
   241  		Id: docID,
   242  		Assert: bson.D{
   243  			{"bundlesha256", ""},
   244  			{"pendingupload", false},
   245  			{"placeholder", true},
   246  		},
   247  		Update: bson.D{{"$set", bson.D{
   248  			{"pendingupload", true},
   249  			{"placeholder", false},
   250  		}}},
   251  	}}, nil
   252  
   253  }
   254  
   255  // deleteOldPlaceholderCharmsOps returns the txn ops required to delete all placeholder charm
   256  // records older than the specified charm URL.
   257  func deleteOldPlaceholderCharmsOps(st *State, charms mongo.Collection, curl *charm.URL) ([]txn.Op, error) {
   258  	// Get a regex with the charm URL and no revision.
   259  	noRevURL := curl.WithRevision(-1)
   260  	curlRegex := "^" + regexp.QuoteMeta(st.docID(noRevURL.String()))
   261  
   262  	var docs []charmDoc
   263  	query := bson.D{{"_id", bson.D{{"$regex", curlRegex}}}, {"placeholder", true}}
   264  	err := charms.Find(query).Select(bson.D{{"_id", 1}, {"url", 1}}).All(&docs)
   265  	if err != nil {
   266  		return nil, errors.Trace(err)
   267  	}
   268  
   269  	refcounts, closer := st.getCollection(refcountsC)
   270  	defer closer()
   271  
   272  	var ops []txn.Op
   273  	for _, doc := range docs {
   274  		if doc.URL.Revision >= curl.Revision {
   275  			continue
   276  		}
   277  		key := charmGlobalKey(doc.URL)
   278  		refOp, err := nsRefcounts.RemoveOp(refcounts, key, 0)
   279  		if err != nil {
   280  			return nil, errors.Trace(err)
   281  		}
   282  		ops = append(ops, refOp, txn.Op{
   283  			C:      charms.Name(),
   284  			Id:     doc.DocID,
   285  			Assert: stillPlaceholder,
   286  			Remove: true,
   287  		})
   288  	}
   289  	return ops, nil
   290  }
   291  
   292  // safeConfig is a travesty which attempts to work around our continued failure
   293  // to properly insulate our database from code changes; it escapes mongo-
   294  // significant characters in config options. See lp:1467964.
   295  func safeConfig(ch charm.Charm) *charm.Config {
   296  	// Make sure we escape any "$" and "." in config option names
   297  	// first. See http://pad.lv/1308146.
   298  	cfg := ch.Config()
   299  	escapedConfig := charm.NewConfig()
   300  	for optionName, option := range cfg.Options {
   301  		escapedName := escapeReplacer.Replace(optionName)
   302  		escapedConfig.Options[escapedName] = option
   303  	}
   304  	return escapedConfig
   305  }
   306  
   307  // Charm represents the state of a charm in the model.
   308  type Charm struct {
   309  	st  *State
   310  	doc charmDoc
   311  }
   312  
   313  func newCharm(st *State, cdoc *charmDoc) *Charm {
   314  	// Because we probably just read the doc from state, make sure we
   315  	// unescape any config option names for "$" and ".". See
   316  	// http://pad.lv/1308146
   317  	if cdoc != nil && cdoc.Config != nil {
   318  		unescapedConfig := charm.NewConfig()
   319  		for optionName, option := range cdoc.Config.Options {
   320  			unescapedName := unescapeReplacer.Replace(optionName)
   321  			unescapedConfig.Options[unescapedName] = option
   322  		}
   323  		cdoc.Config = unescapedConfig
   324  	}
   325  	ch := Charm{st: st, doc: *cdoc}
   326  	return &ch
   327  }
   328  
   329  // Tag returns a tag identifying the charm.
   330  // Implementing state.GlobalEntity interface.
   331  func (c *Charm) Tag() names.Tag {
   332  	return names.NewCharmTag(c.URL().String())
   333  }
   334  
   335  // Life returns the charm's life state.
   336  func (c *Charm) Life() Life {
   337  	return c.doc.Life
   338  }
   339  
   340  // Refresh loads fresh charm data from the database. In practice, the
   341  // only observable change should be to its Life value.
   342  func (c *Charm) Refresh() error {
   343  	ch, err := c.st.Charm(c.doc.URL)
   344  	if err != nil {
   345  		return errors.Trace(err)
   346  	}
   347  	c.doc = ch.doc
   348  	return nil
   349  }
   350  
   351  // Destroy sets the charm to Dying and prevents it from being used by
   352  // applications or units. It only works on local charms, and only when
   353  // the charm is not referenced by any application.
   354  func (c *Charm) Destroy() error {
   355  	buildTxn := func(_ int) ([]txn.Op, error) {
   356  		ops, err := charmDestroyOps(c.st, c.doc.URL)
   357  		switch errors.Cause(err) {
   358  		case nil:
   359  		case errNotAlive:
   360  			return nil, jujutxn.ErrNoOperations
   361  		default:
   362  			return nil, errors.Trace(err)
   363  		}
   364  		return ops, nil
   365  	}
   366  	if err := c.st.run(buildTxn); err != nil {
   367  		return errors.Trace(err)
   368  	}
   369  	c.doc.Life = Dying
   370  	return nil
   371  }
   372  
   373  // Remove will delete the charm's stored archive and render the charm
   374  // inaccessible to future clients. It will fail unless the charm is
   375  // already Dying (indicating that someone has called Destroy).
   376  func (c *Charm) Remove() error {
   377  	switch c.doc.Life {
   378  	case Alive:
   379  		return errors.New("still alive")
   380  	case Dead:
   381  		return nil
   382  	}
   383  
   384  	stor := storage.NewStorage(c.st.ModelUUID(), c.st.MongoSession())
   385  	err := stor.Remove(c.doc.StoragePath)
   386  	if errors.IsNotFound(err) {
   387  		// Not a problem, but we might still need to run the
   388  		// transaction further down to complete the process.
   389  	} else if err != nil {
   390  		return errors.Annotate(err, "deleting archive")
   391  	}
   392  
   393  	buildTxn := func(_ int) ([]txn.Op, error) {
   394  		ops, err := charmRemoveOps(c.st, c.doc.URL)
   395  		switch errors.Cause(err) {
   396  		case nil:
   397  		case errAlreadyDead:
   398  			return nil, jujutxn.ErrNoOperations
   399  		default:
   400  			return nil, errors.Trace(err)
   401  		}
   402  		return ops, nil
   403  	}
   404  	if err := c.st.run(buildTxn); err != nil {
   405  		return errors.Trace(err)
   406  	}
   407  	c.doc.Life = Dead
   408  	return nil
   409  }
   410  
   411  // charmGlobalKey returns the global database key for the charm
   412  // with the given url.
   413  func charmGlobalKey(charmURL *charm.URL) string {
   414  	return "c#" + charmURL.String()
   415  }
   416  
   417  // GlobalKey returns the global database key for the charm.
   418  // Implementing state.GlobalEntity interface.
   419  func (c *Charm) globalKey() string {
   420  	return charmGlobalKey(c.doc.URL)
   421  }
   422  
   423  func (c *Charm) String() string {
   424  	return c.doc.URL.String()
   425  }
   426  
   427  // URL returns the URL that identifies the charm.
   428  func (c *Charm) URL() *charm.URL {
   429  	clone := *c.doc.URL
   430  	return &clone
   431  }
   432  
   433  // Revision returns the monotonically increasing charm
   434  // revision number.
   435  func (c *Charm) Revision() int {
   436  	return c.doc.URL.Revision
   437  }
   438  
   439  // Meta returns the metadata of the charm.
   440  func (c *Charm) Meta() *charm.Meta {
   441  	return c.doc.Meta
   442  }
   443  
   444  // Config returns the configuration of the charm.
   445  func (c *Charm) Config() *charm.Config {
   446  	return c.doc.Config
   447  }
   448  
   449  // Metrics returns the metrics declared for the charm.
   450  func (c *Charm) Metrics() *charm.Metrics {
   451  	return c.doc.Metrics
   452  }
   453  
   454  // Actions returns the actions definition of the charm.
   455  func (c *Charm) Actions() *charm.Actions {
   456  	return c.doc.Actions
   457  }
   458  
   459  // StoragePath returns the storage path of the charm bundle.
   460  func (c *Charm) StoragePath() string {
   461  	return c.doc.StoragePath
   462  }
   463  
   464  // BundleSha256 returns the SHA256 digest of the charm bundle bytes.
   465  func (c *Charm) BundleSha256() string {
   466  	return c.doc.BundleSha256
   467  }
   468  
   469  // IsUploaded returns whether the charm has been uploaded to the
   470  // model storage.
   471  func (c *Charm) IsUploaded() bool {
   472  	return !c.doc.PendingUpload
   473  }
   474  
   475  // IsPlaceholder returns whether the charm record is just a placeholder
   476  // rather than representing a deployed charm.
   477  func (c *Charm) IsPlaceholder() bool {
   478  	return c.doc.Placeholder
   479  }
   480  
   481  // Macaroon return the macaroon that can be used to request data about the charm
   482  // from the charmstore, or nil if the charm is not private.
   483  func (c *Charm) Macaroon() (macaroon.Slice, error) {
   484  	if len(c.doc.Macaroon) == 0 {
   485  		return nil, nil
   486  	}
   487  	var m macaroon.Slice
   488  	if err := m.UnmarshalBinary(c.doc.Macaroon); err != nil {
   489  		return nil, errors.Trace(err)
   490  	}
   491  
   492  	return m, nil
   493  }
   494  
   495  // UpdateMacaroon updates the stored macaroon for this charm.
   496  func (c *Charm) UpdateMacaroon(m macaroon.Slice) error {
   497  	info := CharmInfo{
   498  		Charm:       c,
   499  		ID:          c.URL(),
   500  		StoragePath: c.StoragePath(),
   501  		SHA256:      c.BundleSha256(),
   502  		Macaroon:    m,
   503  	}
   504  	ops, err := updateCharmOps(c.st, info, nil)
   505  	if err != nil {
   506  		return errors.Trace(err)
   507  	}
   508  	if err := c.st.runTransaction(ops); err != nil {
   509  		return errors.Trace(err)
   510  	}
   511  	return nil
   512  }
   513  
   514  // AddCharm adds the ch charm with curl to the state.
   515  // On success the newly added charm state is returned.
   516  func (st *State) AddCharm(info CharmInfo) (stch *Charm, err error) {
   517  	charms, closer := st.getCollection(charmsC)
   518  	defer closer()
   519  
   520  	if err := validateCharmVersion(info.Charm); err != nil {
   521  		return nil, errors.Trace(err)
   522  	}
   523  
   524  	query := charms.FindId(info.ID.String()).Select(bson.D{{"placeholder", 1}})
   525  
   526  	buildTxn := func(attempt int) ([]txn.Op, error) {
   527  		var placeholderDoc struct {
   528  			Placeholder bool `bson:"placeholder"`
   529  		}
   530  		if err := query.One(&placeholderDoc); err == mgo.ErrNotFound {
   531  
   532  			return insertCharmOps(st, info)
   533  		} else if err != nil {
   534  			return nil, errors.Trace(err)
   535  		} else if placeholderDoc.Placeholder {
   536  			return updateCharmOps(st, info, stillPlaceholder)
   537  		}
   538  		return nil, errors.AlreadyExistsf("charm %q", info.ID)
   539  	}
   540  	if err = st.run(buildTxn); err == nil {
   541  		return st.Charm(info.ID)
   542  	}
   543  	return nil, errors.Trace(err)
   544  }
   545  
   546  type hasMeta interface {
   547  	Meta() *charm.Meta
   548  }
   549  
   550  func validateCharmVersion(ch hasMeta) error {
   551  	minver := ch.Meta().MinJujuVersion
   552  	if minver != version.Zero {
   553  		if minver.Compare(jujuversion.Current) > 0 {
   554  			return errors.Errorf("Charm's min version (%s) is higher than this juju environment's version (%s)", minver, jujuversion.Current)
   555  		}
   556  	}
   557  	return nil
   558  }
   559  
   560  // AllCharms returns all charms in state.
   561  func (st *State) AllCharms() ([]*Charm, error) {
   562  	charmsCollection, closer := st.getCollection(charmsC)
   563  	defer closer()
   564  	var cdoc charmDoc
   565  	var charms []*Charm
   566  	iter := charmsCollection.Find(nsLife.notDead()).Iter()
   567  	for iter.Next(&cdoc) {
   568  		ch := newCharm(st, &cdoc)
   569  		charms = append(charms, ch)
   570  	}
   571  	return charms, errors.Trace(iter.Close())
   572  }
   573  
   574  // Charm returns the charm with the given URL. Charms pending upload
   575  // to storage and placeholders are never returned.
   576  func (st *State) Charm(curl *charm.URL) (*Charm, error) {
   577  	charms, closer := st.getCollection(charmsC)
   578  	defer closer()
   579  
   580  	cdoc := &charmDoc{}
   581  	what := bson.D{
   582  		{"_id", curl.String()},
   583  		{"placeholder", bson.D{{"$ne", true}}},
   584  		{"pendingupload", bson.D{{"$ne", true}}},
   585  	}
   586  	what = append(what, nsLife.notDead()...)
   587  	err := charms.Find(what).One(&cdoc)
   588  	if err == mgo.ErrNotFound {
   589  		return nil, errors.NotFoundf("charm %q", curl)
   590  	}
   591  	if err != nil {
   592  		return nil, errors.Annotatef(err, "cannot get charm %q", curl)
   593  	}
   594  	if err := cdoc.Meta.Check(); err != nil {
   595  		return nil, errors.Annotatef(err, "malformed charm metadata found in state")
   596  	}
   597  	return newCharm(st, cdoc), nil
   598  }
   599  
   600  // LatestPlaceholderCharm returns the latest charm described by the
   601  // given URL but which is not yet deployed.
   602  func (st *State) LatestPlaceholderCharm(curl *charm.URL) (*Charm, error) {
   603  	charms, closer := st.getCollection(charmsC)
   604  	defer closer()
   605  
   606  	noRevURL := curl.WithRevision(-1)
   607  	curlRegex := "^" + regexp.QuoteMeta(st.docID(noRevURL.String()))
   608  	var docs []charmDoc
   609  	err := charms.Find(bson.D{{"_id", bson.D{{"$regex", curlRegex}}}, {"placeholder", true}}).All(&docs)
   610  	if err != nil {
   611  		return nil, errors.Annotatef(err, "cannot get charm %q", curl)
   612  	}
   613  	// Find the highest revision.
   614  	var latest charmDoc
   615  	for _, doc := range docs {
   616  		if latest.URL == nil || doc.URL.Revision > latest.URL.Revision {
   617  			latest = doc
   618  		}
   619  	}
   620  	if latest.URL == nil {
   621  		return nil, errors.NotFoundf("placeholder charm %q", noRevURL)
   622  	}
   623  	return newCharm(st, &latest), nil
   624  }
   625  
   626  // PrepareLocalCharmUpload must be called before a local charm is
   627  // uploaded to the provider storage in order to create a charm
   628  // document in state. It returns the chosen unique charm URL reserved
   629  // in state for the charm.
   630  //
   631  // The url's schema must be "local" and it must include a revision.
   632  func (st *State) PrepareLocalCharmUpload(curl *charm.URL) (chosenURL *charm.URL, err error) {
   633  	// Perform a few sanity checks first.
   634  	if curl.Schema != "local" {
   635  		return nil, errors.Errorf("expected charm URL with local schema, got %q", curl)
   636  	}
   637  	if curl.Revision < 0 {
   638  		return nil, errors.Errorf("expected charm URL with revision, got %q", curl)
   639  	}
   640  	// Get a regex with the charm URL and no revision.
   641  	noRevURL := curl.WithRevision(-1)
   642  	curlRegex := "^" + regexp.QuoteMeta(st.docID(noRevURL.String()))
   643  
   644  	charms, closer := st.getCollection(charmsC)
   645  	defer closer()
   646  
   647  	buildTxn := func(attempt int) ([]txn.Op, error) {
   648  		// Find the highest revision of that charm in state.
   649  		var docs []charmDoc
   650  		query := bson.D{{"_id", bson.D{{"$regex", curlRegex}}}}
   651  		err = charms.Find(query).Select(bson.D{{"_id", 1}, {"url", 1}}).All(&docs)
   652  		if err != nil {
   653  			return nil, errors.Trace(err)
   654  		}
   655  		// Find the highest revision.
   656  		maxRevision := -1
   657  		for _, doc := range docs {
   658  			if doc.URL.Revision > maxRevision {
   659  				maxRevision = doc.URL.Revision
   660  			}
   661  		}
   662  
   663  		// Respect the local charm's revision first.
   664  		chosenRevision := curl.Revision
   665  		if maxRevision >= chosenRevision {
   666  			// More recent revision exists in state, pick the next.
   667  			chosenRevision = maxRevision + 1
   668  		}
   669  		chosenURL = curl.WithRevision(chosenRevision)
   670  		return insertPendingCharmOps(st, chosenURL)
   671  	}
   672  	if err = st.run(buildTxn); err == nil {
   673  		return chosenURL, nil
   674  	}
   675  	return nil, errors.Trace(err)
   676  }
   677  
   678  // PrepareStoreCharmUpload must be called before a charm store charm
   679  // is uploaded to the provider storage in order to create a charm
   680  // document in state. If a charm with the same URL is already in
   681  // state, it will be returned as a *state.Charm (it can be still
   682  // pending or already uploaded). Otherwise, a new charm document is
   683  // added in state with just the given charm URL and
   684  // PendingUpload=true, which is then returned as a *state.Charm.
   685  //
   686  // The url's schema must be "cs" and it must include a revision.
   687  func (st *State) PrepareStoreCharmUpload(curl *charm.URL) (*Charm, error) {
   688  	// Perform a few sanity checks first.
   689  	if curl.Schema != "cs" {
   690  		return nil, errors.Errorf("expected charm URL with cs schema, got %q", curl)
   691  	}
   692  	if curl.Revision < 0 {
   693  		return nil, errors.Errorf("expected charm URL with revision, got %q", curl)
   694  	}
   695  
   696  	charms, closer := st.getCollection(charmsC)
   697  	defer closer()
   698  
   699  	var (
   700  		uploadedCharm charmDoc
   701  		err           error
   702  	)
   703  	buildTxn := func(attempt int) ([]txn.Op, error) {
   704  		// Find an uploaded or pending charm with the given exact curl.
   705  		err := charms.FindId(curl.String()).One(&uploadedCharm)
   706  		switch {
   707  		case err == mgo.ErrNotFound:
   708  			uploadedCharm = charmDoc{
   709  				DocID:         st.docID(curl.String()),
   710  				URL:           curl,
   711  				PendingUpload: true,
   712  			}
   713  			return insertAnyCharmOps(st, &uploadedCharm)
   714  		case err != nil:
   715  			return nil, errors.Trace(err)
   716  		case uploadedCharm.Placeholder:
   717  			// Update the fields of the document we're returning.
   718  			uploadedCharm.PendingUpload = true
   719  			uploadedCharm.Placeholder = false
   720  			return convertPlaceholderCharmOps(uploadedCharm.DocID)
   721  		default:
   722  			// The charm exists and it's either uploaded or still
   723  			// pending, but it's not a placeholder. In any case,
   724  			// there's nothing to do.
   725  			return nil, jujutxn.ErrNoOperations
   726  		}
   727  	}
   728  	if err = st.run(buildTxn); err == nil {
   729  		return newCharm(st, &uploadedCharm), nil
   730  	}
   731  	return nil, errors.Trace(err)
   732  }
   733  
   734  var (
   735  	stillPending     = bson.D{{"pendingupload", true}}
   736  	stillPlaceholder = bson.D{{"placeholder", true}}
   737  )
   738  
   739  // AddStoreCharmPlaceholder creates a charm document in state for the given charm URL which
   740  // must reference a charm from the store. The charm document is marked as a placeholder which
   741  // means that if the charm is to be deployed, it will need to first be uploaded to env storage.
   742  func (st *State) AddStoreCharmPlaceholder(curl *charm.URL) (err error) {
   743  	// Perform sanity checks first.
   744  	if curl.Schema != "cs" {
   745  		return errors.Errorf("expected charm URL with cs schema, got %q", curl)
   746  	}
   747  	if curl.Revision < 0 {
   748  		return errors.Errorf("expected charm URL with revision, got %q", curl)
   749  	}
   750  	charms, closer := st.getCollection(charmsC)
   751  	defer closer()
   752  
   753  	buildTxn := func(attempt int) ([]txn.Op, error) {
   754  		// See if the charm already exists in state and exit early if that's the case.
   755  		var doc charmDoc
   756  		err := charms.Find(bson.D{{"_id", curl.String()}}).Select(bson.D{{"_id", 1}}).One(&doc)
   757  		if err != nil && err != mgo.ErrNotFound {
   758  			return nil, errors.Trace(err)
   759  		}
   760  		if err == nil {
   761  			return nil, jujutxn.ErrNoOperations
   762  		}
   763  
   764  		// Delete all previous placeholders so we don't fill up the database with unused data.
   765  		deleteOps, err := deleteOldPlaceholderCharmsOps(st, charms, curl)
   766  		if err != nil {
   767  			return nil, errors.Trace(err)
   768  		}
   769  		insertOps, err := insertPlaceholderCharmOps(st, curl)
   770  		if err != nil {
   771  			return nil, errors.Trace(err)
   772  		}
   773  		ops := append(deleteOps, insertOps...)
   774  		return ops, nil
   775  	}
   776  	return errors.Trace(st.run(buildTxn))
   777  }
   778  
   779  // UpdateUploadedCharm marks the given charm URL as uploaded and
   780  // updates the rest of its data, returning it as *state.Charm.
   781  func (st *State) UpdateUploadedCharm(info CharmInfo) (*Charm, error) {
   782  	charms, closer := st.getCollection(charmsC)
   783  	defer closer()
   784  
   785  	doc := &charmDoc{}
   786  	err := charms.FindId(info.ID.String()).One(&doc)
   787  	if err == mgo.ErrNotFound {
   788  		return nil, errors.NotFoundf("charm %q", info.ID)
   789  	}
   790  	if err != nil {
   791  		return nil, errors.Trace(err)
   792  	}
   793  	if !doc.PendingUpload {
   794  		return nil, errors.Trace(&ErrCharmAlreadyUploaded{info.ID})
   795  	}
   796  
   797  	ops, err := updateCharmOps(st, info, stillPending)
   798  	if err != nil {
   799  		return nil, errors.Trace(err)
   800  	}
   801  	if err := st.runTransaction(ops); err != nil {
   802  		return nil, onAbort(err, ErrCharmRevisionAlreadyModified)
   803  	}
   804  	return st.Charm(info.ID)
   805  }