github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/mgo.v2/bson"
    14  	"gopkg.in/mgo.v2/txn"
    15  
    16  	"github.com/juju/juju/mongo"
    17  )
    18  
    19  // charmDoc represents the internal state of a charm in MongoDB.
    20  type charmDoc struct {
    21  	DocID   string     `bson:"_id"`
    22  	URL     *charm.URL `bson:"url"` // DANGEROUS see below
    23  	EnvUUID string     `bson:"env-uuid"`
    24  
    25  	// TODO(fwereade) 2015-06-18 lp:1467964
    26  	// DANGEROUS: our schema can change any time the charm package changes,
    27  	// and we have no automated way to detect when that happens. We *must*
    28  	// not depend upon serializations we cannot control from inside this
    29  	// package. What's in a *charm.Meta? What will be tomorrow? What logic
    30  	// will we be writing on the assumption that all stored Metas have set
    31  	// some field? What fields might lose precision when they go into the
    32  	// database?
    33  	Meta    *charm.Meta    `bson:"meta"`
    34  	Config  *charm.Config  `bson:"config"`
    35  	Actions *charm.Actions `bson:"actions"`
    36  	Metrics *charm.Metrics `bson:"metrics"`
    37  
    38  	// DEPRECATED: BundleURL is deprecated, and exists here
    39  	// only for migration purposes. We should remove this
    40  	// when migrations are no longer necessary.
    41  	BundleURL *url.URL `bson:"bundleurl,omitempty"`
    42  
    43  	BundleSha256  string `bson:"bundlesha256"`
    44  	StoragePath   string `bson:"storagepath"`
    45  	PendingUpload bool   `bson:"pendingupload"`
    46  	Placeholder   bool   `bson:"placeholder"`
    47  }
    48  
    49  // insertCharmOps returns the txn operations necessary to insert the supplied
    50  // charm data.
    51  func insertCharmOps(
    52  	st *State, ch charm.Charm, curl *charm.URL, storagePath, bundleSha256 string,
    53  ) ([]txn.Op, error) {
    54  	return insertAnyCharmOps(&charmDoc{
    55  		DocID:        curl.String(),
    56  		URL:          curl,
    57  		EnvUUID:      st.EnvironTag().Id(),
    58  		Meta:         ch.Meta(),
    59  		Config:       safeConfig(ch),
    60  		Metrics:      ch.Metrics(),
    61  		Actions:      ch.Actions(),
    62  		BundleSha256: bundleSha256,
    63  		StoragePath:  storagePath,
    64  	})
    65  }
    66  
    67  // insertPlaceholderCharmOps returns the txn operations necessary to insert a
    68  // charm document referencing a store charm that is not yet directly accessible
    69  // within the environment.
    70  func insertPlaceholderCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
    71  	return insertAnyCharmOps(&charmDoc{
    72  		DocID:       curl.String(),
    73  		URL:         curl,
    74  		EnvUUID:     st.EnvironTag().Id(),
    75  		Placeholder: true,
    76  	})
    77  }
    78  
    79  // insertPendingCharmOps returns the txn operations necessary to insert a charm
    80  // document referencing a charm that has yet to be uploaded to the environment.
    81  func insertPendingCharmOps(st *State, curl *charm.URL) ([]txn.Op, error) {
    82  	return insertAnyCharmOps(&charmDoc{
    83  		DocID:         curl.String(),
    84  		URL:           curl,
    85  		EnvUUID:       st.EnvironTag().Id(),
    86  		PendingUpload: true,
    87  	})
    88  }
    89  
    90  // insertAnyCharmOps returns the txn operations necessary to insert the supplied
    91  // charm document.
    92  func insertAnyCharmOps(cdoc *charmDoc) ([]txn.Op, error) {
    93  	return []txn.Op{{
    94  		C:      charmsC,
    95  		Id:     cdoc.DocID,
    96  		Assert: txn.DocMissing,
    97  		Insert: cdoc,
    98  	}}, nil
    99  }
   100  
   101  // updateCharmOps returns the txn operations necessary to update the charm
   102  // document with the supplied data, so long as the supplied assert still holds
   103  // true.
   104  func updateCharmOps(
   105  	st *State, ch charm.Charm, curl *charm.URL, storagePath, bundleSha256 string, assert bson.D,
   106  ) ([]txn.Op, error) {
   107  
   108  	updateFields := bson.D{{"$set", bson.D{
   109  		{"meta", ch.Meta()},
   110  		{"config", safeConfig(ch)},
   111  		{"actions", ch.Actions()},
   112  		{"metrics", ch.Metrics()},
   113  		{"storagepath", storagePath},
   114  		{"bundlesha256", bundleSha256},
   115  		{"pendingupload", false},
   116  		{"placeholder", false},
   117  	}}}
   118  	return []txn.Op{{
   119  		C:      charmsC,
   120  		Id:     curl.String(),
   121  		Assert: assert,
   122  		Update: updateFields,
   123  	}}, nil
   124  }
   125  
   126  // convertPlaceholderCharmOps returns the txn operations necessary to convert
   127  // the charm with the supplied docId from a placeholder to one marked for
   128  // pending upload.
   129  func convertPlaceholderCharmOps(docID string) ([]txn.Op, error) {
   130  	return []txn.Op{{
   131  		C:  charmsC,
   132  		Id: docID,
   133  		Assert: bson.D{
   134  			{"bundlesha256", ""},
   135  			{"pendingupload", false},
   136  			{"placeholder", true},
   137  		},
   138  		Update: bson.D{{"$set", bson.D{
   139  			{"pendingupload", true},
   140  			{"placeholder", false},
   141  		}}},
   142  	}}, nil
   143  
   144  }
   145  
   146  // deleteOldPlaceholderCharmsOps returns the txn ops required to delete all placeholder charm
   147  // records older than the specified charm URL.
   148  func deleteOldPlaceholderCharmsOps(st *State, charms mongo.Collection, curl *charm.URL) ([]txn.Op, error) {
   149  	// Get a regex with the charm URL and no revision.
   150  	noRevURL := curl.WithRevision(-1)
   151  	curlRegex := "^" + regexp.QuoteMeta(st.docID(noRevURL.String()))
   152  
   153  	var docs []charmDoc
   154  	query := bson.D{{"_id", bson.D{{"$regex", curlRegex}}}, {"placeholder", true}}
   155  	err := charms.Find(query).Select(bson.D{{"_id", 1}, {"url", 1}}).All(&docs)
   156  	if err != nil {
   157  		return nil, errors.Trace(err)
   158  	}
   159  	var ops []txn.Op
   160  	for _, doc := range docs {
   161  		if doc.URL.Revision >= curl.Revision {
   162  			continue
   163  		}
   164  		ops = append(ops, txn.Op{
   165  			C:      charmsC,
   166  			Id:     doc.DocID,
   167  			Assert: stillPlaceholder,
   168  			Remove: true,
   169  		})
   170  	}
   171  	return ops, nil
   172  }
   173  
   174  // safeConfig is a travesty which attempts to work around our continued failure
   175  // to properly insulate our database from code changes; it escapes mongo-
   176  // significant characters in config options. See lp:1467964.
   177  func safeConfig(ch charm.Charm) *charm.Config {
   178  	// Make sure we escape any "$" and "." in config option names
   179  	// first. See http://pad.lv/1308146.
   180  	cfg := ch.Config()
   181  	escapedConfig := charm.NewConfig()
   182  	for optionName, option := range cfg.Options {
   183  		escapedName := escapeReplacer.Replace(optionName)
   184  		escapedConfig.Options[escapedName] = option
   185  	}
   186  	return escapedConfig
   187  }
   188  
   189  // Charm represents the state of a charm in the environment.
   190  type Charm struct {
   191  	st  *State
   192  	doc charmDoc
   193  }
   194  
   195  func newCharm(st *State, cdoc *charmDoc) *Charm {
   196  	// Because we probably just read the doc from state, make sure we
   197  	// unescape any config option names for "$" and ".". See
   198  	// http://pad.lv/1308146
   199  	if cdoc != nil && cdoc.Config != nil {
   200  		unescapedConfig := charm.NewConfig()
   201  		for optionName, option := range cdoc.Config.Options {
   202  			unescapedName := unescapeReplacer.Replace(optionName)
   203  			unescapedConfig.Options[unescapedName] = option
   204  		}
   205  		cdoc.Config = unescapedConfig
   206  	}
   207  	return &Charm{st: st, doc: *cdoc}
   208  }
   209  
   210  // Tag returns a tag identifying the charm.
   211  // Implementing state.GlobalEntity interface.
   212  func (c *Charm) Tag() names.Tag {
   213  	return names.NewCharmTag(c.URL().String())
   214  }
   215  
   216  // charmGlobalKey returns the global database key for the charm
   217  // with the given url.
   218  func charmGlobalKey(charmURL *charm.URL) string {
   219  	return "c#" + charmURL.String()
   220  }
   221  
   222  // GlobalKey returns the global database key for the charm.
   223  // Implementing state.GlobalEntity interface.
   224  func (c *Charm) globalKey() string {
   225  	return charmGlobalKey(c.doc.URL)
   226  }
   227  
   228  func (c *Charm) String() string {
   229  	return c.doc.URL.String()
   230  }
   231  
   232  // URL returns the URL that identifies the charm.
   233  func (c *Charm) URL() *charm.URL {
   234  	clone := *c.doc.URL
   235  	return &clone
   236  }
   237  
   238  // Revision returns the monotonically increasing charm
   239  // revision number.
   240  func (c *Charm) Revision() int {
   241  	return c.doc.URL.Revision
   242  }
   243  
   244  // Meta returns the metadata of the charm.
   245  func (c *Charm) Meta() *charm.Meta {
   246  	return c.doc.Meta
   247  }
   248  
   249  // Config returns the configuration of the charm.
   250  func (c *Charm) Config() *charm.Config {
   251  	return c.doc.Config
   252  }
   253  
   254  // Metrics returns the metrics declared for the charm.
   255  func (c *Charm) Metrics() *charm.Metrics {
   256  	return c.doc.Metrics
   257  }
   258  
   259  // Actions returns the actions definition of the charm.
   260  func (c *Charm) Actions() *charm.Actions {
   261  	return c.doc.Actions
   262  }
   263  
   264  // StoragePath returns the storage path of the charm bundle.
   265  func (c *Charm) StoragePath() string {
   266  	return c.doc.StoragePath
   267  }
   268  
   269  // BundleURL returns the url to the charm bundle in
   270  // the provider storage.
   271  //
   272  // DEPRECATED: this is only to be used for migrating
   273  // charm archives to environment storage.
   274  func (c *Charm) BundleURL() *url.URL {
   275  	return c.doc.BundleURL
   276  }
   277  
   278  // BundleSha256 returns the SHA256 digest of the charm bundle bytes.
   279  func (c *Charm) BundleSha256() string {
   280  	return c.doc.BundleSha256
   281  }
   282  
   283  // IsUploaded returns whether the charm has been uploaded to the
   284  // environment storage.
   285  func (c *Charm) IsUploaded() bool {
   286  	return !c.doc.PendingUpload
   287  }
   288  
   289  // IsPlaceholder returns whether the charm record is just a placeholder
   290  // rather than representing a deployed charm.
   291  func (c *Charm) IsPlaceholder() bool {
   292  	return c.doc.Placeholder
   293  }