github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"net/url"
     8  	"regexp"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	"gopkg.in/juju/charm.v6-unstable"
    13  	"gopkg.in/macaroon.v1"
    14  	"gopkg.in/mgo.v2/bson"
    15  	"gopkg.in/mgo.v2/txn"
    16  
    17  	"github.com/juju/juju/mongo"
    18  )
    19  
    20  // MacaroonCache is a type that wraps State and implements charmstore.MacaroonCache.
    21  type MacaroonCache struct {
    22  	*State
    23  }
    24  
    25  // Set stores the macaroon on the charm.
    26  func (m MacaroonCache) Set(u *charm.URL, ms macaroon.Slice) error {
    27  	c, err := m.Charm(u)
    28  	if err != nil {
    29  		return errors.Trace(err)
    30  	}
    31  	return c.UpdateMacaroon(ms)
    32  }
    33  
    34  // Get retrieves the macaroon for the charm (if any).
    35  func (m MacaroonCache) Get(u *charm.URL) (macaroon.Slice, error) {
    36  	c, err := m.Charm(u)
    37  	if err != nil {
    38  		return nil, errors.Trace(err)
    39  	}
    40  	return c.Macaroon()
    41  }
    42  
    43  // charmDoc represents the internal state of a charm in MongoDB.
    44  type charmDoc struct {
    45  	DocID     string     `bson:"_id"`
    46  	URL       *charm.URL `bson:"url"` // DANGEROUS see below
    47  	ModelUUID string     `bson:"model-uuid"`
    48  
    49  	// TODO(fwereade) 2015-06-18 lp:1467964
    50  	// DANGEROUS: our schema can change any time the charm package changes,
    51  	// and we have no automated way to detect when that happens. We *must*
    52  	// not depend upon serializations we cannot control from inside this
    53  	// package. What's in a *charm.Meta? What will be tomorrow? What logic
    54  	// will we be writing on the assumption that all stored Metas have set
    55  	// some field? What fields might lose precision when they go into the
    56  	// database?
    57  	Meta    *charm.Meta    `bson:"meta"`
    58  	Config  *charm.Config  `bson:"config"`
    59  	Actions *charm.Actions `bson:"actions"`
    60  	Metrics *charm.Metrics `bson:"metrics"`
    61  
    62  	// DEPRECATED: BundleURL is deprecated, and exists here
    63  	// only for migration purposes. We should remove this
    64  	// when migrations are no longer necessary.
    65  	BundleURL *url.URL `bson:"bundleurl,omitempty"`
    66  
    67  	BundleSha256  string `bson:"bundlesha256"`
    68  	StoragePath   string `bson:"storagepath"`
    69  	PendingUpload bool   `bson:"pendingupload"`
    70  	Placeholder   bool   `bson:"placeholder"`
    71  	Macaroon      []byte `bson:"macaroon"`
    72  }
    73  
    74  // CharmInfo contains all the data necessary to store a charm's metadata.
    75  type CharmInfo struct {
    76  	Charm       charm.Charm
    77  	ID          *charm.URL
    78  	StoragePath string
    79  	SHA256      string
    80  	Macaroon    macaroon.Slice
    81  }
    82  
    83  // insertCharmOps returns the txn operations necessary to insert the supplied
    84  // charm data. If curl is nil, an error will be returned.
    85  func insertCharmOps(st *State, info CharmInfo) ([]txn.Op, error) {
    86  	if info.ID == nil {
    87  		return nil, errors.New("*charm.URL was nil")
    88  	}
    89  
    90  	doc := charmDoc{
    91  		DocID:        info.ID.String(),
    92  		URL:          info.ID,
    93  		ModelUUID:    st.ModelTag().Id(),
    94  		Meta:         info.Charm.Meta(),
    95  		Config:       safeConfig(info.Charm),
    96  		Metrics:      info.Charm.Metrics(),
    97  		Actions:      info.Charm.Actions(),
    98  		BundleSha256: info.SHA256,
    99  		StoragePath:  info.StoragePath,
   100  	}
   101  	if info.Macaroon != nil {
   102  		mac, err := info.Macaroon.MarshalBinary()
   103  		if err != nil {
   104  			return nil, errors.Annotate(err, "can't convert macaroon to binary for storage")
   105  		}
   106  		doc.Macaroon = mac
   107  	}
   108  	return insertAnyCharmOps(&doc)
   109  }
   110  
   111  // insertPlaceholderCharmOps returns the txn operations necessary to insert a
   112  // charm document referencing a store charm that is not yet directly accessible
   113  // within the model. If curl is nil, an error will be returned.
   114  func insertPlaceholderCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
   115  	if curl == nil {
   116  		return nil, errors.New("*charm.URL was nil")
   117  	}
   118  	return insertAnyCharmOps(&charmDoc{
   119  		DocID:       curl.String(),
   120  		URL:         curl,
   121  		ModelUUID:   st.ModelTag().Id(),
   122  		Placeholder: true,
   123  	})
   124  }
   125  
   126  // insertPendingCharmOps returns the txn operations necessary to insert a charm
   127  // document referencing a charm that has yet to be uploaded to the model.
   128  // If curl is nil, an error will be returned.
   129  func insertPendingCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
   130  	if curl == nil {
   131  		return nil, errors.New("*charm.URL was nil")
   132  	}
   133  	return insertAnyCharmOps(&charmDoc{
   134  		DocID:         curl.String(),
   135  		URL:           curl,
   136  		ModelUUID:     st.ModelTag().Id(),
   137  		PendingUpload: true,
   138  	})
   139  }
   140  
   141  // insertAnyCharmOps returns the txn operations necessary to insert the supplied
   142  // charm document.
   143  func insertAnyCharmOps(cdoc *charmDoc) ([]txn.Op, error) {
   144  	return []txn.Op{{
   145  		C:      charmsC,
   146  		Id:     cdoc.DocID,
   147  		Assert: txn.DocMissing,
   148  		Insert: cdoc,
   149  	}}, nil
   150  }
   151  
   152  // updateCharmOps returns the txn operations necessary to update the charm
   153  // document with the supplied data, so long as the supplied assert still holds
   154  // true.
   155  func updateCharmOps(
   156  	st *State, info CharmInfo, assert interface{},
   157  ) ([]txn.Op, error) {
   158  
   159  	data := bson.D{
   160  		{"meta", info.Charm.Meta()},
   161  		{"config", safeConfig(info.Charm)},
   162  		{"actions", info.Charm.Actions()},
   163  		{"metrics", info.Charm.Metrics()},
   164  		{"storagepath", info.StoragePath},
   165  		{"bundlesha256", info.SHA256},
   166  		{"pendingupload", false},
   167  		{"placeholder", false},
   168  	}
   169  
   170  	if len(info.Macaroon) > 0 {
   171  		mac, err := info.Macaroon.MarshalBinary()
   172  		if err != nil {
   173  			return nil, errors.Annotate(err, "can't convert macaroon to binary for storage")
   174  		}
   175  		data = append(data, bson.DocElem{"macaroon", mac})
   176  	}
   177  
   178  	updateFields := bson.D{{"$set", data}}
   179  	return []txn.Op{{
   180  		C:      charmsC,
   181  		Id:     info.ID.String(),
   182  		Assert: assert,
   183  		Update: updateFields,
   184  	}}, nil
   185  }
   186  
   187  // convertPlaceholderCharmOps returns the txn operations necessary to convert
   188  // the charm with the supplied docId from a placeholder to one marked for
   189  // pending upload.
   190  func convertPlaceholderCharmOps(docID string) ([]txn.Op, error) {
   191  	return []txn.Op{{
   192  		C:  charmsC,
   193  		Id: docID,
   194  		Assert: bson.D{
   195  			{"bundlesha256", ""},
   196  			{"pendingupload", false},
   197  			{"placeholder", true},
   198  		},
   199  		Update: bson.D{{"$set", bson.D{
   200  			{"pendingupload", true},
   201  			{"placeholder", false},
   202  		}}},
   203  	}}, nil
   204  
   205  }
   206  
   207  // deleteOldPlaceholderCharmsOps returns the txn ops required to delete all placeholder charm
   208  // records older than the specified charm URL.
   209  func deleteOldPlaceholderCharmsOps(st *State, charms mongo.Collection, curl *charm.URL) ([]txn.Op, error) {
   210  	// Get a regex with the charm URL and no revision.
   211  	noRevURL := curl.WithRevision(-1)
   212  	curlRegex := "^" + regexp.QuoteMeta(st.docID(noRevURL.String()))
   213  
   214  	var docs []charmDoc
   215  	query := bson.D{{"_id", bson.D{{"$regex", curlRegex}}}, {"placeholder", true}}
   216  	err := charms.Find(query).Select(bson.D{{"_id", 1}, {"url", 1}}).All(&docs)
   217  	if err != nil {
   218  		return nil, errors.Trace(err)
   219  	}
   220  	var ops []txn.Op
   221  	for _, doc := range docs {
   222  		if doc.URL.Revision >= curl.Revision {
   223  			continue
   224  		}
   225  		ops = append(ops, txn.Op{
   226  			C:      charmsC,
   227  			Id:     doc.DocID,
   228  			Assert: stillPlaceholder,
   229  			Remove: true,
   230  		})
   231  	}
   232  	return ops, nil
   233  }
   234  
   235  // safeConfig is a travesty which attempts to work around our continued failure
   236  // to properly insulate our database from code changes; it escapes mongo-
   237  // significant characters in config options. See lp:1467964.
   238  func safeConfig(ch charm.Charm) *charm.Config {
   239  	// Make sure we escape any "$" and "." in config option names
   240  	// first. See http://pad.lv/1308146.
   241  	cfg := ch.Config()
   242  	escapedConfig := charm.NewConfig()
   243  	for optionName, option := range cfg.Options {
   244  		escapedName := escapeReplacer.Replace(optionName)
   245  		escapedConfig.Options[escapedName] = option
   246  	}
   247  	return escapedConfig
   248  }
   249  
   250  // Charm represents the state of a charm in the model.
   251  type Charm struct {
   252  	st  *State
   253  	doc charmDoc
   254  }
   255  
   256  func newCharm(st *State, cdoc *charmDoc) *Charm {
   257  	// Because we probably just read the doc from state, make sure we
   258  	// unescape any config option names for "$" and ".". See
   259  	// http://pad.lv/1308146
   260  	if cdoc != nil && cdoc.Config != nil {
   261  		unescapedConfig := charm.NewConfig()
   262  		for optionName, option := range cdoc.Config.Options {
   263  			unescapedName := unescapeReplacer.Replace(optionName)
   264  			unescapedConfig.Options[unescapedName] = option
   265  		}
   266  		cdoc.Config = unescapedConfig
   267  	}
   268  	ch := Charm{st: st, doc: *cdoc}
   269  	return &ch
   270  }
   271  
   272  // Tag returns a tag identifying the charm.
   273  // Implementing state.GlobalEntity interface.
   274  func (c *Charm) Tag() names.Tag {
   275  	return names.NewCharmTag(c.URL().String())
   276  }
   277  
   278  // charmGlobalKey returns the global database key for the charm
   279  // with the given url.
   280  func charmGlobalKey(charmURL *charm.URL) string {
   281  	return "c#" + charmURL.String()
   282  }
   283  
   284  // GlobalKey returns the global database key for the charm.
   285  // Implementing state.GlobalEntity interface.
   286  func (c *Charm) globalKey() string {
   287  	return charmGlobalKey(c.doc.URL)
   288  }
   289  
   290  func (c *Charm) String() string {
   291  	return c.doc.URL.String()
   292  }
   293  
   294  // URL returns the URL that identifies the charm.
   295  func (c *Charm) URL() *charm.URL {
   296  	clone := *c.doc.URL
   297  	return &clone
   298  }
   299  
   300  // Revision returns the monotonically increasing charm
   301  // revision number.
   302  func (c *Charm) Revision() int {
   303  	return c.doc.URL.Revision
   304  }
   305  
   306  // Meta returns the metadata of the charm.
   307  func (c *Charm) Meta() *charm.Meta {
   308  	return c.doc.Meta
   309  }
   310  
   311  // Config returns the configuration of the charm.
   312  func (c *Charm) Config() *charm.Config {
   313  	return c.doc.Config
   314  }
   315  
   316  // Metrics returns the metrics declared for the charm.
   317  func (c *Charm) Metrics() *charm.Metrics {
   318  	return c.doc.Metrics
   319  }
   320  
   321  // Actions returns the actions definition of the charm.
   322  func (c *Charm) Actions() *charm.Actions {
   323  	return c.doc.Actions
   324  }
   325  
   326  // StoragePath returns the storage path of the charm bundle.
   327  func (c *Charm) StoragePath() string {
   328  	return c.doc.StoragePath
   329  }
   330  
   331  // BundleURL returns the url to the charm bundle in
   332  // the provider storage.
   333  //
   334  // DEPRECATED: this is only to be used for migrating
   335  // charm archives to model storage.
   336  func (c *Charm) BundleURL() *url.URL {
   337  	return c.doc.BundleURL
   338  }
   339  
   340  // BundleSha256 returns the SHA256 digest of the charm bundle bytes.
   341  func (c *Charm) BundleSha256() string {
   342  	return c.doc.BundleSha256
   343  }
   344  
   345  // IsUploaded returns whether the charm has been uploaded to the
   346  // model storage.
   347  func (c *Charm) IsUploaded() bool {
   348  	return !c.doc.PendingUpload
   349  }
   350  
   351  // IsPlaceholder returns whether the charm record is just a placeholder
   352  // rather than representing a deployed charm.
   353  func (c *Charm) IsPlaceholder() bool {
   354  	return c.doc.Placeholder
   355  }
   356  
   357  // Macaroon return the macaroon that can be used to request data about the charm
   358  // from the charmstore, or nil if the charm is not private.
   359  func (c *Charm) Macaroon() (macaroon.Slice, error) {
   360  	if len(c.doc.Macaroon) == 0 {
   361  		return nil, nil
   362  	}
   363  	var m macaroon.Slice
   364  	if err := m.UnmarshalBinary(c.doc.Macaroon); err != nil {
   365  		return nil, errors.Trace(err)
   366  	}
   367  
   368  	return m, nil
   369  }
   370  
   371  // UpdateMacaroon updates the stored macaroon for this charm.
   372  func (c *Charm) UpdateMacaroon(m macaroon.Slice) error {
   373  	info := CharmInfo{
   374  		Charm:       c,
   375  		ID:          c.URL(),
   376  		StoragePath: c.StoragePath(),
   377  		SHA256:      c.BundleSha256(),
   378  		Macaroon:    m,
   379  	}
   380  	ops, err := updateCharmOps(c.st, info, txn.DocExists)
   381  	if err != nil {
   382  		return errors.Trace(err)
   383  	}
   384  	if err := c.st.runTransaction(ops); err != nil {
   385  		return errors.Trace(err)
   386  	}
   387  	return nil
   388  }