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 }