~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/charmstore/store.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
package charmstore // import "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
5
5
 
6
6
import (
7
 
        "archive/zip"
8
 
        "crypto/sha256"
9
7
        "encoding/json"
10
8
        "fmt"
11
9
        "io"
159
157
        // Close all cached stores. Any used by
160
158
        // outstanding requests will be closed when the
161
159
        // requests complete.
 
160
loop:
162
161
        for {
163
162
                select {
164
163
                case s := <-p.reqStoreC:
165
164
                        s.DB.Close()
166
165
                default:
167
 
                        return
 
166
                        break loop
168
167
                }
169
168
        }
170
 
 
171
 
        p.auditLogger.Close()
 
169
        if p.auditLogger != nil {
 
170
                p.auditLogger.Close()
 
171
        }
172
172
}
173
173
 
174
174
// RequestStore returns a store for a client request. It returns
358
358
                s.DB.Entities(),
359
359
                mgo.Index{Key: []string{"promulgated-url"}, Unique: true, Sparse: true},
360
360
        }, {
361
 
                s.DB.BaseEntities(),
362
 
                mgo.Index{Key: []string{"public"}},
363
 
        }, {
364
361
                s.DB.Logs(),
365
362
                mgo.Index{Key: []string{"urls"}},
366
363
        }, {
391
388
                s.DB.Entities(),
392
389
                mgo.Index{Key: []string{"bundlecharms"}},
393
390
        }, {
 
391
                s.DB.Entities(),
 
392
                mgo.Index{Key: []string{"name", "development", "-promulgated-revision", "-supportedseries"}},
 
393
        }, {
 
394
                s.DB.Entities(),
 
395
                mgo.Index{Key: []string{"name", "development", "user", "-revision", "-supportedseries"}},
 
396
        }, {
394
397
                s.DB.BaseEntities(),
395
398
                mgo.Index{Key: []string{"name"}},
 
399
        }, {
 
400
                // TODO this index should be created by the mgo gridfs code.
 
401
                s.DB.C("entitystore.files"),
 
402
                mgo.Index{Key: []string{"filename"}},
396
403
        }}
397
404
        for _, idx := range indexes {
398
405
                err := idx.c.EnsureIndex(idx.i)
403
410
        return nil
404
411
}
405
412
 
406
 
func (s *Store) putArchive(archive blobstore.ReadSeekCloser) (blobName, blobHash, blobHash256 string, size int64, err error) {
407
 
        hash := blobstore.NewHash()
408
 
        hash256 := sha256.New()
409
 
        size, err = io.Copy(io.MultiWriter(hash, hash256), archive)
410
 
        if err != nil {
411
 
                return "", "", "", 0, errgo.Notef(err, "cannot copy archive")
412
 
        }
413
 
        if _, err = archive.Seek(0, 0); err != nil {
414
 
                return "", "", "", 0, errgo.Notef(err, "cannot seek in archive")
415
 
        }
416
 
        blobHash = fmt.Sprintf("%x", hash.Sum(nil))
417
 
        blobName = bson.NewObjectId().Hex()
418
 
        if err = s.BlobStore.PutUnchallenged(archive, blobName, size, blobHash); err != nil {
419
 
                return "", "", "", 0, errgo.Notef(err, "cannot put archive into blob store")
420
 
        }
421
 
        return blobName, blobHash, fmt.Sprintf("%x", hash256.Sum(nil)), size, nil
422
 
}
423
 
 
424
 
// AddCharmWithArchive is like AddCharm but
425
 
// also adds the charm archive to the blob store.
426
 
// This method is provided principally so that
427
 
// tests can easily create content in the store.
428
 
//
429
 
// If purl is not nil then the charm will also be
430
 
// available at the promulgated url specified.
431
 
func (s *Store) AddCharmWithArchive(url *router.ResolvedURL, ch charm.Charm) error {
432
 
        blobName, blobHash, blobHash256, blobSize, err := s.uploadCharmOrBundle(ch)
433
 
        if err != nil {
434
 
                return errgo.Notef(err, "cannot upload charm")
435
 
        }
436
 
        return s.AddCharm(ch, AddParams{
437
 
                URL:         url,
438
 
                BlobName:    blobName,
439
 
                BlobHash:    blobHash,
440
 
                BlobHash256: blobHash256,
441
 
                BlobSize:    blobSize,
442
 
        })
443
 
}
444
 
 
445
 
// AddBundleWithArchive is like AddBundle but
446
 
// also adds the charm archive to the blob store.
447
 
// This method is provided principally so that
448
 
// tests can easily create content in the store.
449
 
//
450
 
// If purl is not nil then the bundle will also be
451
 
// available at the promulgated url specified.
452
 
func (s *Store) AddBundleWithArchive(url *router.ResolvedURL, b charm.Bundle) error {
453
 
        blobName, blobHash, blobHash256, size, err := s.uploadCharmOrBundle(b)
454
 
        if err != nil {
455
 
                return errgo.Notef(err, "cannot upload bundle")
456
 
        }
457
 
        return s.AddBundle(b, AddParams{
458
 
                URL:         url,
459
 
                BlobName:    blobName,
460
 
                BlobHash:    blobHash,
461
 
                BlobHash256: blobHash256,
462
 
                BlobSize:    size,
463
 
        })
464
 
}
465
 
 
466
 
func (s *Store) uploadCharmOrBundle(c interface{}) (blobName, blobHash, blobHash256 string, size int64, err error) {
467
 
        archive, err := getArchive(c)
468
 
        if err != nil {
469
 
                return "", "", "", 0, errgo.Notef(err, "cannot get archive")
470
 
        }
471
 
        defer archive.Close()
472
 
        return s.putArchive(archive)
473
 
}
474
 
 
475
413
// AddAudit adds the given entry to the audit log.
476
414
func (s *Store) AddAudit(entry audit.Entry) {
477
415
        s.addAuditAtTime(entry, time.Now())
488
426
        }
489
427
}
490
428
 
491
 
// AddParams holds parameters held in common between the
492
 
// Store.AddCharm and Store.AddBundle methods.
493
 
type AddParams struct {
494
 
        // URL holds the id to be associated with the stored entity.
495
 
        // If URL.PromulgatedRevision is not -1, the entity will
496
 
        // be promulgated.
497
 
        URL *router.ResolvedURL
498
 
 
499
 
        // BlobName holds the name of the entity's archive blob.
500
 
        BlobName string
501
 
 
502
 
        // BlobHash holds the hash of the entity's archive blob.
503
 
        BlobHash string
504
 
 
505
 
        // BlobHash256 holds the sha256 hash of the entity's archive blob.
506
 
        BlobHash256 string
507
 
 
508
 
        // BlobHash holds the size of the entity's archive blob.
509
 
        BlobSize int64
510
 
 
511
 
        // Contents holds references to files inside the
512
 
        // entity's archive blob.
513
 
        Contents map[mongodoc.FileId]mongodoc.ZipFile
514
 
}
515
 
 
516
 
// AddCharm adds a charm entities collection with the given parameters.
517
 
// If p.URL cannot be used as a name for the charm then the returned
518
 
// error will have the cause params.ErrEntityIdNotAllowed. If the charm
519
 
// duplicates an existing charm then the returned error will have the
520
 
// cause params.ErrDuplicateUpload.
521
 
func (s *Store) AddCharm(c charm.Charm, p AddParams) (err error) {
522
 
        // Strictly speaking this test is redundant, because a ResolvedURL should
523
 
        // always be canonical, but check just in case anyway, as this is
524
 
        // final gateway before a potentially invalid url might be stored
525
 
        // in the database.
526
 
        id := p.URL.URL
527
 
        if id.Series == "bundle" || id.User == "" || id.Revision == -1 {
528
 
                return errgo.Newf("charm added with invalid id %v", &id)
529
 
        }
530
 
        logger.Infof("add charm url %s; prev %d; dev %v", &id, p.URL.PromulgatedRevision, p.URL.Development)
531
 
        entity := &mongodoc.Entity{
532
 
                URL:                     &id,
533
 
                PromulgatedURL:          p.URL.PromulgatedURL(),
534
 
                BlobHash:                p.BlobHash,
535
 
                BlobHash256:             p.BlobHash256,
536
 
                BlobName:                p.BlobName,
537
 
                Size:                    p.BlobSize,
538
 
                UploadTime:              time.Now(),
539
 
                CharmMeta:               c.Meta(),
540
 
                CharmConfig:             c.Config(),
541
 
                CharmActions:            c.Actions(),
542
 
                CharmProvidedInterfaces: interfacesForRelations(c.Meta().Provides),
543
 
                CharmRequiredInterfaces: interfacesForRelations(c.Meta().Requires),
544
 
                Contents:                p.Contents,
545
 
                SupportedSeries:         c.Meta().Series,
546
 
                Development:             p.URL.Development,
547
 
        }
548
 
        denormalizeEntity(entity)
549
 
 
550
 
        // Check that we're not going to create a charm that duplicates
551
 
        // the name of a bundle. This is racy, but it's the best we can
552
 
        // do. Also check that there isn't an existing multi-series charm
553
 
        // that would be replaced by this one.
554
 
        entities, err := s.FindEntities(entity.BaseURL)
555
 
        if err != nil {
556
 
                return errgo.Notef(err, "cannot check for existing entities")
557
 
        }
558
 
        for _, entity := range entities {
559
 
                if entity.URL.Series == "bundle" {
560
 
                        return errgo.WithCausef(err, params.ErrEntityIdNotAllowed, "charm name duplicates bundle name %v", entity.URL)
561
 
                }
562
 
                if id.Series != "" && entity.URL.Series == "" {
563
 
                        return errgo.WithCausef(err, params.ErrEntityIdNotAllowed, "charm name duplicates multi-series charm name %v", entity.URL)
564
 
                }
565
 
        }
566
 
        if err := s.insertEntity(entity); err != nil {
567
 
                return errgo.Mask(err, errgo.Is(params.ErrDuplicateUpload))
568
 
        }
569
 
        return nil
570
 
}
571
 
 
572
 
// denormalizeEntity sets all denormalized fields in e
573
 
// from their associated canonical fields.
574
 
//
575
 
// It is the responsibility of the caller to set e.SupportedSeries
576
 
// if the entity URL does not contain a series. If the entity
577
 
// URL *does* contain a series, e.SupportedSeries will
578
 
// be overwritten.
579
 
//
580
 
// This is exported for the purposes of tests that
581
 
// need to create directly into the database.
582
 
func denormalizeEntity(e *mongodoc.Entity) {
583
 
        e.BaseURL = baseURL(e.URL)
584
 
        e.Name = e.URL.Name
585
 
        e.User = e.URL.User
586
 
        e.Revision = e.URL.Revision
587
 
        e.Series = e.URL.Series
588
 
        if e.URL.Series != "" {
589
 
                if e.URL.Series == "bundle" {
590
 
                        e.SupportedSeries = nil
591
 
                } else {
592
 
                        e.SupportedSeries = []string{e.URL.Series}
593
 
                }
594
 
        }
595
 
        if e.PromulgatedURL == nil {
596
 
                e.PromulgatedRevision = -1
597
 
        } else {
598
 
                e.PromulgatedRevision = e.PromulgatedURL.Revision
599
 
        }
600
 
}
601
 
 
602
 
var everyonePerm = []string{params.Everyone}
603
 
 
604
 
func (s *Store) insertEntity(entity *mongodoc.Entity) (err error) {
605
 
        // Add the base entity to the database.
606
 
        perms := []string{entity.User}
607
 
        acls := mongodoc.ACL{
608
 
                Read:  perms,
609
 
                Write: perms,
610
 
        }
611
 
        baseEntity := &mongodoc.BaseEntity{
612
 
                URL:             entity.BaseURL,
613
 
                User:            entity.User,
614
 
                Name:            entity.Name,
615
 
                Public:          false,
616
 
                ACLs:            acls,
617
 
                DevelopmentACLs: acls,
618
 
                Promulgated:     entity.PromulgatedURL != nil,
619
 
        }
620
 
        err = s.DB.BaseEntities().Insert(baseEntity)
621
 
        if err != nil && !mgo.IsDup(err) {
622
 
                return errgo.Notef(err, "cannot insert base entity")
623
 
        }
624
 
 
625
 
        // Add the entity to the database.
626
 
        err = s.DB.Entities().Insert(entity)
627
 
        if mgo.IsDup(err) {
628
 
                return params.ErrDuplicateUpload
629
 
        }
630
 
        if err != nil {
631
 
                return errgo.Notef(err, "cannot insert entity")
632
 
        }
633
 
        // Ensure that if anything fails after this, that we delete
634
 
        // the entity, otherwise we will be left in an internally
635
 
        // inconsistent state.
636
 
        defer func() {
637
 
                if err != nil {
638
 
                        if err := s.DB.Entities().RemoveId(entity.URL); err != nil {
639
 
                                logger.Errorf("cannot remove entity after elastic search failure: %v", err)
640
 
                        }
641
 
                }
642
 
        }()
643
 
        // Add entity to ElasticSearch.
644
 
        if err := s.UpdateSearch(EntityResolvedURL(entity)); err != nil {
645
 
                return errgo.Notef(err, "cannot index %s to ElasticSearch", entity.URL)
646
 
        }
647
 
        return nil
648
 
}
649
 
 
650
 
// FindEntity finds the entity in the store with the given URL,
651
 
// which must be fully qualified. If any fields are specified,
652
 
// only those fields will be populated in the returned entities.
653
 
// If the given URL has no user then it is assumed to be a
654
 
// promulgated entity.
655
 
func (s *Store) FindEntity(url *router.ResolvedURL, fields ...string) (*mongodoc.Entity, error) {
656
 
        entities, err := s.FindEntities(url.UserOwnedURL(), fields...)
657
 
        if err != nil {
 
429
// FindEntity finds the entity in the store with the given URL, which
 
430
// must be fully qualified. If the given URL has no user then it is
 
431
// assumed to be a promulgated entity. If fields is not nil, only its
 
432
// fields will be populated in the returned entities.
 
433
func (s *Store) FindEntity(url *router.ResolvedURL, fields map[string]int) (*mongodoc.Entity, error) {
 
434
        q := s.DB.Entities().Find(bson.D{{"_id", &url.URL}})
 
435
        if fields != nil {
 
436
                q = q.Select(fields)
 
437
        }
 
438
        var entity mongodoc.Entity
 
439
        err := q.One(&entity)
 
440
        if err != nil {
 
441
                if err == mgo.ErrNotFound {
 
442
                        return nil, errgo.WithCausef(nil, params.ErrNotFound, "entity not found")
 
443
                }
658
444
                return nil, errgo.Mask(err)
659
445
        }
660
 
        if len(entities) == 0 {
661
 
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "entity not found")
662
 
        }
663
 
        // The URL is guaranteed to be fully qualified so we'll always
664
 
        // get exactly one result.
665
 
        return entities[0], nil
 
446
        return &entity, nil
666
447
}
667
448
 
668
449
// FindEntities finds all entities in the store matching the given URL.
669
 
// If any fields are specified, only those fields will be
670
 
// populated in the returned entities. If the given URL has no user then
671
 
// only promulgated entities will be queried. If the given URL channel does
672
 
// not represent an entity under development then only published entities
673
 
// will be queried.
674
 
func (s *Store) FindEntities(url *charm.URL, fields ...string) ([]*mongodoc.Entity, error) {
675
 
        query := selectFields(s.EntitiesQuery(url), fields)
 
450
// If the given URL has no user then only promulgated entities will be
 
451
// queried. If the given URL channel does not represent an entity under
 
452
// development then only published entities will be queried. If fields
 
453
// is not nil, only its fields will be populated in the returned
 
454
// entities.
 
455
func (s *Store) FindEntities(url *charm.URL, fields map[string]int) ([]*mongodoc.Entity, error) {
 
456
        query := s.EntitiesQuery(url)
 
457
        if fields != nil {
 
458
                query = query.Select(fields)
 
459
        }
676
460
        var docs []*mongodoc.Entity
677
461
        err := query.All(&docs)
678
462
        if err != nil {
682
466
}
683
467
 
684
468
// FindBestEntity finds the entity that provides the preferred match to
685
 
// the given URL. If any fields are specified, only those fields will be
686
 
// populated in the returned entities. If the given URL has no user then
687
 
// only promulgated entities will be queried.
688
 
func (s *Store) FindBestEntity(url *charm.URL, fields ...string) (*mongodoc.Entity, error) {
689
 
        if len(fields) > 0 {
 
469
// the given URL, on the given channel. If the given URL has no user
 
470
// then only promulgated entities will be queried. If fields is not nil,
 
471
// only those fields will be populated in the returned entities.
 
472
//
 
473
// If the URL contains a revision then it is assumed to be fully formed
 
474
// and refer to a single entity; the channel is ignored.
 
475
//
 
476
// If the URL does not contain a revision then the channel is searched
 
477
// for the best match, here NoChannel will be treated as
 
478
// params.StableChannel.
 
479
func (s *Store) FindBestEntity(url *charm.URL, channel params.Channel, fields map[string]int) (*mongodoc.Entity, error) {
 
480
        if fields != nil {
690
481
                // Make sure we have all the fields we need to make a decision.
691
 
                fields = append(fields, "_id", "promulgated-url", "promulgated-revision", "series", "revision")
692
 
        }
693
 
        entities, err := s.FindEntities(url, fields...)
 
482
                // TODO this would be more efficient if we used bitmasks for field selection.
 
483
                nfields := map[string]int{
 
484
                        "_id":                  1,
 
485
                        "promulgated-url":      1,
 
486
                        "promulgated-revision": 1,
 
487
                        "series":               1,
 
488
                        "revision":             1,
 
489
                        "development":          1,
 
490
                        "stable":               1,
 
491
                }
 
492
                for f := range fields {
 
493
                        nfields[f] = 1
 
494
                }
 
495
                fields = nfields
 
496
        }
 
497
        if url.Revision != -1 {
 
498
                // If the URL contains a revision, then it refers to a single entity.
 
499
                entity, err := s.findSingleEntity(url, fields)
 
500
                if errgo.Cause(err) == params.ErrNotFound {
 
501
                        return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", url)
 
502
                } else if err != nil {
 
503
                        return nil, errgo.Mask(err)
 
504
                }
 
505
                // If a channel was specified make sure the entity is in that channel.
 
506
                // This is crucial because if we don't do this, then the user could choose
 
507
                // to use any chosen set of ACLs against any entity.
 
508
                switch channel {
 
509
                case params.StableChannel:
 
510
                        if !entity.Stable {
 
511
                                return nil, errgo.WithCausef(nil, params.ErrNotFound, "%s not found in stable channel", url)
 
512
                        }
 
513
                case params.DevelopmentChannel:
 
514
                        if !entity.Development {
 
515
                                return nil, errgo.WithCausef(nil, params.ErrNotFound, "%s not found in development channel", url)
 
516
                        }
 
517
                }
 
518
                return entity, nil
 
519
        }
 
520
 
 
521
        switch channel {
 
522
        case params.UnpublishedChannel:
 
523
                return s.findUnpublishedEntity(url, fields)
 
524
        case params.NoChannel:
 
525
                channel = params.StableChannel
 
526
                fallthrough
 
527
        default:
 
528
                return s.findEntityInChannel(url, channel, fields)
 
529
        }
 
530
}
 
531
 
 
532
// findSingleEntity returns the entity referred to by URL. It is expected
 
533
// that the URL refers to only one entity and is fully formed. The url may
 
534
// refer to either a user-owned or promulgated charm name.
 
535
func (s *Store) findSingleEntity(url *charm.URL, fields map[string]int) (*mongodoc.Entity, error) {
 
536
        query := s.EntitiesQuery(url)
 
537
        if fields != nil {
 
538
                query = query.Select(fields)
 
539
        }
 
540
        var entity mongodoc.Entity
 
541
        err := query.One(&entity)
 
542
        if err == nil {
 
543
                return &entity, nil
 
544
        }
 
545
        if err == mgo.ErrNotFound {
 
546
                return nil, errgo.WithCausef(err, params.ErrNotFound, "no matching charm or bundle for %s", url)
 
547
        }
 
548
        return nil, errgo.Notef(err, "cannot find entities matching %s", url)
 
549
}
 
550
 
 
551
// findEntityInChannel attempts to find an entity on the given channel. The
 
552
// base entity for URL is retrieved and the series with the best match to
 
553
// URL.Series is used as the resolved entity.
 
554
func (s *Store) findEntityInChannel(url *charm.URL, ch params.Channel, fields map[string]int) (*mongodoc.Entity, error) {
 
555
        baseEntity, err := s.FindBaseEntity(url, map[string]int{
 
556
                "_id":             1,
 
557
                "channelentities": 1,
 
558
        })
 
559
        if errgo.Cause(err) == params.ErrNotFound {
 
560
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", url)
 
561
        } else if err != nil {
 
562
                return nil, errgo.Mask(err)
 
563
        }
 
564
        var entityURL *charm.URL
 
565
        if url.Series == "" {
 
566
                for _, u := range baseEntity.ChannelEntities[ch] {
 
567
                        if entityURL == nil || seriesScore[u.Series] > seriesScore[entityURL.Series] {
 
568
                                entityURL = u
 
569
                        }
 
570
                }
 
571
        } else {
 
572
                entityURL = baseEntity.ChannelEntities[ch][url.Series]
 
573
        }
 
574
        if entityURL == nil {
 
575
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", url)
 
576
        }
 
577
        return s.findSingleEntity(entityURL, fields)
 
578
}
 
579
 
 
580
// findUnpublishedEntity attempts to find an entity on the unpublished
 
581
// channel. This searches all entities in the store for the best match to
 
582
// the URL.
 
583
func (s *Store) findUnpublishedEntity(url *charm.URL, fields map[string]int) (*mongodoc.Entity, error) {
 
584
        entities, err := s.FindEntities(url, fields)
694
585
        if err != nil {
695
586
                return nil, errgo.Mask(err)
696
587
        }
697
588
        if len(entities) == 0 {
698
 
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "entity not found")
 
589
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "no matching charm or bundle for %s", url)
699
590
        }
700
591
        best := entities[0]
701
592
        for _, e := range entities {
749
640
        entities := s.DB.Entities()
750
641
        query := make(bson.D, 1, 5)
751
642
        query[0] = bson.DocElem{"name", url.Name}
752
 
        if url.Channel != charm.DevelopmentChannel {
753
 
                query = append(query, bson.DocElem{"development", false})
754
 
        }
755
643
        if url.User == "" {
756
644
                if url.Revision > -1 {
757
645
                        query = append(query, bson.DocElem{"promulgated-revision", url.Revision})
781
669
 
782
670
// FindBaseEntity finds the base entity in the store using the given URL,
783
671
// which can either represent a fully qualified entity or a base id.
784
 
// If any fields are specified, only those fields will be populated in the
 
672
// If fields is not nil, only those fields will be populated in the
785
673
// returned base entity.
786
 
func (s *Store) FindBaseEntity(url *charm.URL, fields ...string) (*mongodoc.BaseEntity, error) {
 
674
func (s *Store) FindBaseEntity(url *charm.URL, fields map[string]int) (*mongodoc.BaseEntity, error) {
787
675
        var query *mgo.Query
788
676
        if url.User == "" {
789
677
                query = s.DB.BaseEntities().Find(bson.D{{"name", url.Name}, {"promulgated", 1}})
790
678
        } else {
791
 
                query = s.DB.BaseEntities().FindId(baseURL(url))
792
 
        }
793
 
        query = selectFields(query, fields)
 
679
                query = s.DB.BaseEntities().FindId(mongodoc.BaseURL(url))
 
680
        }
 
681
        if fields != nil {
 
682
                query = query.Select(fields)
 
683
        }
794
684
        var baseEntity mongodoc.BaseEntity
795
685
        if err := query.One(&baseEntity); err != nil {
796
686
                if err == mgo.ErrNotFound {
801
691
        return &baseEntity, nil
802
692
}
803
693
 
804
 
func selectFields(query *mgo.Query, fields []string) *mgo.Query {
805
 
        if len(fields) > 0 {
806
 
                sel := make(bson.D, len(fields))
807
 
                for i, field := range fields {
808
 
                        sel[i] = bson.DocElem{field, 1}
809
 
                }
810
 
                query = query.Select(sel)
811
 
        }
812
 
        return query
 
694
// FieldSelector returns a field selector that will select
 
695
// the given fields, or all fields if none are specified.
 
696
func FieldSelector(fields ...string) map[string]int {
 
697
        if len(fields) == 0 {
 
698
                return nil
 
699
        }
 
700
        sel := make(map[string]int, len(fields))
 
701
        for _, field := range fields {
 
702
                sel[field] = 1
 
703
        }
 
704
        return sel
813
705
}
814
706
 
815
 
// UpdateEntity applies the provided update to the entity described by url.
816
 
func (s *Store) UpdateEntity(url *router.ResolvedURL, update interface{}) error {
 
707
// UpdateEntity applies the provided update to the entity described by
 
708
// url. If there are no entries in update then no update is performed,
 
709
// and no error is returned.
 
710
func (s *Store) UpdateEntity(url *router.ResolvedURL, update bson.D) error {
 
711
        if len(update) == 0 {
 
712
                return nil
 
713
        }
817
714
        if err := s.DB.Entities().Update(bson.D{{"_id", &url.URL}}, update); err != nil {
818
715
                if err == mgo.ErrNotFound {
819
716
                        return errgo.WithCausef(err, params.ErrNotFound, "cannot update %q", url)
823
720
        return nil
824
721
}
825
722
 
826
 
// UpdateBaseEntity applies the provided update to the base entity of url.
827
 
func (s *Store) UpdateBaseEntity(url *router.ResolvedURL, update interface{}) error {
828
 
        if err := s.DB.BaseEntities().Update(bson.D{{"_id", baseURL(&url.URL)}}, update); err != nil {
 
723
// UpdateBaseEntity applies the provided update to the base entity of
 
724
// url. If there are no entries in update then no update is performed,
 
725
// and no error is returned.
 
726
func (s *Store) UpdateBaseEntity(url *router.ResolvedURL, update bson.D) error {
 
727
        if len(update) == 0 {
 
728
                return nil
 
729
        }
 
730
        if err := s.DB.BaseEntities().Update(bson.D{{"_id", mongodoc.BaseURL(&url.URL)}}, update); err != nil {
829
731
                if err == mgo.ErrNotFound {
830
732
                        return errgo.WithCausef(err, params.ErrNotFound, "cannot update base entity for %q", url)
831
733
                }
834
736
        return nil
835
737
}
836
738
 
837
 
// SetDevelopment sets whether the entity corresponding to the given URL will
838
 
// be only available in its development version (in essence, not published).
839
 
func (s *Store) SetDevelopment(url *router.ResolvedURL, development bool) error {
840
 
        if err := s.UpdateEntity(url, bson.D{{
841
 
                "$set", bson.D{{"development", development}},
842
 
        }}); err != nil {
843
 
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
844
 
        }
845
 
        if !development {
846
 
                // If the entity is published, update the search index.
847
 
                rurl := *url
848
 
                rurl.Development = development
849
 
                if err := s.UpdateSearch(&rurl); err != nil {
850
 
                        return errgo.Notef(err, "cannot update search entities for %q", rurl)
851
 
                }
 
739
// Publish assigns channels to the entity corresponding to the given URL.
 
740
// An error is returned if no channels are provided. For the time being,
 
741
// the only supported channels are "development" and "stable".
 
742
func (s *Store) Publish(url *router.ResolvedURL, channels ...params.Channel) error {
 
743
        var updateSearch bool
 
744
        // Validate channels.
 
745
        actual := make([]params.Channel, 0, len(channels))
 
746
        for _, c := range channels {
 
747
                switch c {
 
748
                case params.StableChannel:
 
749
                        updateSearch = true
 
750
                        fallthrough
 
751
                case params.DevelopmentChannel:
 
752
                        actual = append(actual, c)
 
753
                }
 
754
        }
 
755
        numChannels := len(actual)
 
756
        if numChannels == 0 {
 
757
                return errgo.Newf("cannot update %q: no channels provided", url)
 
758
        }
 
759
 
 
760
        // Update the entity.
 
761
        update := make(bson.D, numChannels)
 
762
        for i, c := range actual {
 
763
                update[i] = bson.DocElem{string(c), true}
 
764
        }
 
765
        if err := s.UpdateEntity(url, bson.D{{"$set", update}}); err != nil {
 
766
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
 
767
        }
 
768
 
 
769
        // Update the base entity.
 
770
        entity, err := s.FindEntity(url, FieldSelector("series", "supportedseries"))
 
771
        if err != nil {
 
772
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
 
773
        }
 
774
        series := entity.SupportedSeries
 
775
        numSeries := len(series)
 
776
        if numSeries == 0 {
 
777
                series = []string{entity.Series}
 
778
                numSeries = 1
 
779
        }
 
780
        update = make(bson.D, 0, numChannels*numSeries)
 
781
        for _, c := range actual {
 
782
                for _, s := range series {
 
783
                        update = append(update, bson.DocElem{fmt.Sprintf("channelentities.%s.%s", c, s), entity.URL})
 
784
                }
 
785
        }
 
786
        if err := s.UpdateBaseEntity(url, bson.D{{"$set", update}}); err != nil {
 
787
                return errgo.Mask(err)
 
788
        }
 
789
 
 
790
        if !updateSearch {
 
791
                return nil
 
792
        }
 
793
 
 
794
        // Add entity to ElasticSearch.
 
795
        if err := s.UpdateSearch(url); err != nil {
 
796
                return errgo.Notef(err, "cannot index %s to ElasticSearch", url)
852
797
        }
853
798
        return nil
854
799
}
873
818
// chances this will happen are slim.
874
819
func (s *Store) SetPromulgated(url *router.ResolvedURL, promulgate bool) error {
875
820
        baseEntities := s.DB.BaseEntities()
876
 
        base := baseURL(&url.URL)
 
821
        base := mongodoc.BaseURL(&url.URL)
877
822
        if !promulgate {
878
823
                err := baseEntities.UpdateId(
879
824
                        base,
1000
945
        return nil
1001
946
}
1002
947
 
1003
 
func interfacesForRelations(rels map[string]charm.Relation) []string {
1004
 
        // Eliminate duplicates by storing interface names into a map.
1005
 
        interfaces := make(map[string]bool)
1006
 
        for _, rel := range rels {
1007
 
                interfaces[rel.Interface] = true
1008
 
        }
1009
 
        result := make([]string, 0, len(interfaces))
1010
 
        for iface := range interfaces {
1011
 
                result = append(result, iface)
1012
 
        }
1013
 
        return result
1014
 
}
1015
 
 
1016
 
func baseURL(url *charm.URL) *charm.URL {
1017
 
        newURL := *url
1018
 
        newURL.Revision = -1
1019
 
        newURL.Series = ""
1020
 
        newURL.Channel = ""
1021
 
        return &newURL
1022
 
}
1023
 
 
1024
 
var errNotImplemented = errgo.Newf("not implemented")
1025
 
 
1026
 
// AddBundle adds a bundle to the entities collection with the given
1027
 
// parameters. If p.URL cannot be used as a name for the bundle then the
1028
 
// returned error will have the cause params.ErrEntityIdNotAllowed. If
1029
 
// the bundle duplicates an existing bundle then the returned error will
1030
 
// have the cause params.ErrDuplicateUpload.
1031
 
func (s *Store) AddBundle(b charm.Bundle, p AddParams) error {
1032
 
        // Strictly speaking this test is redundant, because a ResolvedURL should
1033
 
        // always be canonical, but check just in case anyway, as this is
1034
 
        // final gateway before a potentially invalid url might be stored
1035
 
        // in the database.
1036
 
        if p.URL.URL.Series != "bundle" || p.URL.URL.User == "" || p.URL.URL.Revision == -1 || p.URL.URL.Series == "" {
1037
 
                return errgo.Newf("bundle added with invalid id %v", p.URL)
1038
 
        }
1039
 
        bundleData := b.Data()
1040
 
        urls, err := bundleCharms(bundleData)
1041
 
        if err != nil {
1042
 
                return errgo.Mask(err)
1043
 
        }
1044
 
        entity := &mongodoc.Entity{
1045
 
                URL:                &p.URL.URL,
1046
 
                BlobHash:           p.BlobHash,
1047
 
                BlobHash256:        p.BlobHash256,
1048
 
                BlobName:           p.BlobName,
1049
 
                Size:               p.BlobSize,
1050
 
                UploadTime:         time.Now(),
1051
 
                BundleData:         bundleData,
1052
 
                BundleUnitCount:    newInt(bundleUnitCount(bundleData)),
1053
 
                BundleMachineCount: newInt(bundleMachineCount(bundleData)),
1054
 
                BundleReadMe:       b.ReadMe(),
1055
 
                BundleCharms:       urls,
1056
 
                Contents:           p.Contents,
1057
 
                PromulgatedURL:     p.URL.PromulgatedURL(),
1058
 
                Development:        p.URL.Development,
1059
 
        }
1060
 
        denormalizeEntity(entity)
1061
 
 
1062
 
        // Check that we're not going to create a bundle that duplicates
1063
 
        // the name of a charm. This is racy, but it's the best we can do.
1064
 
        entities, err := s.FindEntities(entity.BaseURL)
1065
 
        if err != nil {
1066
 
                return errgo.Notef(err, "cannot check for existing entities")
1067
 
        }
1068
 
        for _, entity := range entities {
1069
 
                if entity.URL.Series != "bundle" {
1070
 
                        return errgo.WithCausef(err, params.ErrEntityIdNotAllowed, "bundle name duplicates charm name %s", entity.URL)
1071
 
                }
1072
 
        }
1073
 
        if err := s.insertEntity(entity); err != nil {
1074
 
                return errgo.Mask(err, errgo.Is(params.ErrDuplicateUpload))
1075
 
        }
1076
 
        return nil
1077
 
}
1078
 
 
1079
 
// OpenBlob opens a blob given its entity id; it returns the blob's
1080
 
// data source, its size and its hash. It returns a params.ErrNotFound
1081
 
// error if the entity does not exist.
1082
 
func (s *Store) OpenBlob(id *router.ResolvedURL) (r blobstore.ReadSeekCloser, size int64, hash string, err error) {
1083
 
        blobName, hash, err := s.BlobNameAndHash(id)
1084
 
        if err != nil {
1085
 
                return nil, 0, "", errgo.Mask(err, errgo.Is(params.ErrNotFound))
1086
 
        }
1087
 
        r, size, err = s.BlobStore.Open(blobName)
1088
 
        if err != nil {
1089
 
                return nil, 0, "", errgo.Notef(err, "cannot open archive data for %s", id)
1090
 
        }
1091
 
        return r, size, hash, nil
1092
 
}
1093
 
 
1094
 
// BlobNameAndHash returns the name that is used to store the blob
1095
 
// for the entity with the given id and its hash. It returns a params.ErrNotFound
1096
 
// error if the entity does not exist.
1097
 
func (s *Store) BlobNameAndHash(id *router.ResolvedURL) (name, hash string, err error) {
1098
 
        entity, err := s.FindEntity(id, "blobname", "blobhash")
1099
 
        if err != nil {
1100
 
                if errgo.Cause(err) == params.ErrNotFound {
1101
 
                        return "", "", errgo.WithCausef(nil, params.ErrNotFound, "entity not found")
1102
 
                }
1103
 
                return "", "", errgo.Notef(err, "cannot get %s", id)
1104
 
        }
1105
 
        return entity.BlobName, entity.BlobHash, nil
1106
 
}
1107
 
 
1108
 
// OpenCachedBlobFile opens a file from the given entity's archive blob.
1109
 
// The file is identified by the provided fileId. If the file has not
1110
 
// previously been opened on this entity, the isFile function will be
1111
 
// used to determine which file in the zip file to use. The result will
1112
 
// be cached for the next time.
1113
 
//
1114
 
// When retrieving the entity, at least the BlobName and
1115
 
// Contents fields must be populated.
1116
 
func (s *Store) OpenCachedBlobFile(
1117
 
        entity *mongodoc.Entity,
1118
 
        fileId mongodoc.FileId,
1119
 
        isFile func(f *zip.File) bool,
1120
 
) (_ io.ReadCloser, err error) {
1121
 
        if entity.BlobName == "" {
1122
 
                // We'd like to check that the Contents field was populated
1123
 
                // here but we can't because it doesn't necessarily
1124
 
                // exist in the entity.
1125
 
                return nil, errgo.New("provided entity does not have required fields")
1126
 
        }
1127
 
        zipf, ok := entity.Contents[fileId]
1128
 
        if ok && !zipf.IsValid() {
1129
 
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "")
1130
 
        }
1131
 
        blob, size, err := s.BlobStore.Open(entity.BlobName)
1132
 
        if err != nil {
1133
 
                return nil, errgo.Notef(err, "cannot open archive blob")
1134
 
        }
1135
 
        defer func() {
1136
 
                // When there's an error, we want to close
1137
 
                // the blob, otherwise we need to keep the blob
1138
 
                // open because it's used by the returned Reader.
1139
 
                if err != nil {
1140
 
                        blob.Close()
1141
 
                }
1142
 
        }()
1143
 
        if !ok {
1144
 
                // We haven't already searched the archive for the icon,
1145
 
                // so find its archive now.
1146
 
                zipf, err = s.findZipFile(blob, size, isFile)
1147
 
                if err != nil && errgo.Cause(err) != params.ErrNotFound {
1148
 
                        return nil, errgo.Mask(err)
1149
 
                }
1150
 
        }
1151
 
        // We update the content entry regardless of whether we've
1152
 
        // found a file, so that the next time that serveIcon is called
1153
 
        // it can know that we've already looked.
1154
 
        err = s.DB.Entities().UpdateId(
1155
 
                entity.URL,
1156
 
                bson.D{{"$set",
1157
 
                        bson.D{{"contents." + string(fileId), zipf}},
1158
 
                }},
1159
 
        )
1160
 
        if err != nil {
1161
 
                return nil, errgo.Notef(err, "cannot update %q", entity.URL)
1162
 
        }
1163
 
        if !zipf.IsValid() {
1164
 
                // We searched for the file and didn't find it.
1165
 
                return nil, errgo.WithCausef(nil, params.ErrNotFound, "")
1166
 
        }
1167
 
 
1168
 
        // We know where the icon is stored. Now serve it up.
1169
 
        r, err := ZipFileReader(blob, zipf)
1170
 
        if err != nil {
1171
 
                return nil, errgo.Notef(err, "cannot make zip file reader")
1172
 
        }
1173
 
        // We return a ReadCloser that reads from the newly created
1174
 
        // zip file reader, but when closed, will close the originally
1175
 
        // opened blob.
1176
 
        return struct {
1177
 
                io.Reader
1178
 
                io.Closer
1179
 
        }{r, blob}, nil
1180
 
}
1181
 
 
1182
 
func (s *Store) findZipFile(blob io.ReadSeeker, size int64, isFile func(f *zip.File) bool) (mongodoc.ZipFile, error) {
1183
 
        zipReader, err := zip.NewReader(&readerAtSeeker{blob}, size)
1184
 
        if err != nil {
1185
 
                return mongodoc.ZipFile{}, errgo.Notef(err, "cannot read archive data")
1186
 
        }
1187
 
        for _, f := range zipReader.File {
1188
 
                if isFile(f) {
1189
 
                        return NewZipFile(f)
1190
 
                }
1191
 
        }
1192
 
        return mongodoc.ZipFile{}, params.ErrNotFound
1193
 
}
1194
 
 
1195
 
// SetPerms sets the permissions for the base entity with
1196
 
// the given id for "which" operations ("read" or "write")
1197
 
// to the given ACL. This is mostly provided for testing.
 
948
// SetPerms sets the ACL specified by which for the base entity with the
 
949
// given id. The which parameter is in the form "[channel].operation",
 
950
// where channel, if specified, is one of "development" or "stable" and
 
951
// operation is one of "read" or "write". If which does not specify a
 
952
// channel then the unpublished ACL is updated. This is only provided for
 
953
// testing.
1198
954
func (s *Store) SetPerms(id *charm.URL, which string, acl ...string) error {
1199
 
        field := "acls"
1200
 
        if id.Channel == charm.DevelopmentChannel {
1201
 
                field = "developmentacls"
1202
 
        }
1203
 
        return s.DB.BaseEntities().UpdateId(baseURL(id), bson.D{{"$set",
1204
 
                bson.D{{field + "." + which, acl}},
 
955
        return s.DB.BaseEntities().UpdateId(mongodoc.BaseURL(id), bson.D{{"$set",
 
956
                bson.D{{"channelacls." + which, acl}},
1205
957
        }})
1206
958
}
1207
959
 
1208
 
func newInt(x int) *int {
1209
 
        return &x
1210
 
}
1211
 
 
1212
 
// bundleUnitCount returns the number of units created by the bundle.
1213
 
func bundleUnitCount(b *charm.BundleData) int {
1214
 
        count := 0
1215
 
        for _, service := range b.Services {
1216
 
                count += service.NumUnits
1217
 
        }
1218
 
        return count
1219
 
}
1220
 
 
1221
 
// bundleMachineCount returns the number of machines
1222
 
// that will be created or used by the bundle.
1223
 
func bundleMachineCount(b *charm.BundleData) int {
1224
 
        count := len(b.Machines)
1225
 
        for _, service := range b.Services {
1226
 
                // The default placement is "new".
1227
 
                placement := &charm.UnitPlacement{
1228
 
                        Machine: "new",
1229
 
                }
1230
 
                // Check for "new" placements, which means a new machine
1231
 
                // must be added.
1232
 
                for _, location := range service.To {
1233
 
                        var err error
1234
 
                        placement, err = charm.ParsePlacement(location)
1235
 
                        if err != nil {
1236
 
                                // Ignore invalid placements - a bundle should always
1237
 
                                // be verified before adding to the charm store so this
1238
 
                                // should never happen in practice.
1239
 
                                continue
1240
 
                        }
1241
 
                        if placement.Machine == "new" {
1242
 
                                count++
1243
 
                        }
1244
 
                }
1245
 
                // If there are less elements in To than NumUnits, the last placement
1246
 
                // element is replicated. For this reason, if the last element is
1247
 
                // "new", we need to add more machines.
1248
 
                if placement != nil && placement.Machine == "new" {
1249
 
                        count += service.NumUnits - len(service.To)
1250
 
                }
1251
 
        }
1252
 
        return count
1253
 
}
1254
 
 
1255
 
// bundleCharms returns all the charm URLs used by a bundle,
1256
 
// without duplicates.
1257
 
func bundleCharms(data *charm.BundleData) ([]*charm.URL, error) {
1258
 
        // Use a map to de-duplicate the URL list: a bundle can include services
1259
 
        // deployed by the same charm.
1260
 
        urlMap := make(map[string]*charm.URL)
1261
 
        for _, service := range data.Services {
1262
 
                url, err := charm.ParseURL(service.Charm)
1263
 
                if err != nil {
1264
 
                        return nil, errgo.Mask(err)
1265
 
                }
1266
 
                urlMap[url.String()] = url
1267
 
                // Also add the corresponding base URL.
1268
 
                base := baseURL(url)
1269
 
                urlMap[base.String()] = base
1270
 
        }
1271
 
        urls := make([]*charm.URL, 0, len(urlMap))
1272
 
        for _, url := range urlMap {
1273
 
                urls = append(urls, url)
1274
 
        }
1275
 
        return urls, nil
1276
 
}
1277
 
 
1278
960
// MatchingInterfacesQuery returns a mongo query
1279
961
// that will find any charms that require any interfaces
1280
962
// in the required slice or provide any interfaces in the
1281
963
// provided slice.
1282
 
//
1283
 
// Development charms are never matched.
1284
 
// TODO do we actually want to match dev charms here?
1285
964
func (s *Store) MatchingInterfacesQuery(required, provided []string) *mgo.Query {
1286
965
        return s.DB.Entities().Find(bson.D{{
1287
 
                "development", false,
1288
 
        }, {
1289
966
                "$or", []bson.D{{{
1290
967
                        "charmrequiredinterfaces", bson.D{{
1291
968
                                "$elemMatch", bson.D{{
1320
997
                        urlMap[urlStr] = true
1321
998
                        allUrls = append(allUrls, url)
1322
999
                }
1323
 
                base := baseURL(url)
 
1000
                base := mongodoc.BaseURL(url)
1324
1001
                urlStr = base.String()
1325
1002
                if ok, _ := urlMap[urlStr]; !ok {
1326
1003
                        urlMap[urlStr] = true
1342
1019
        return nil
1343
1020
}
1344
1021
 
 
1022
func (s *Store) DeleteEntity(id *router.ResolvedURL) error {
 
1023
        entity, err := s.FindEntity(id, FieldSelector("blobname", "blobhash", "prev5blobhash"))
 
1024
        if err != nil {
 
1025
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
 
1026
        }
 
1027
        // Remove the entity.
 
1028
        if err := s.DB.Entities().RemoveId(&id.URL); err != nil {
 
1029
                if err == mgo.ErrNotFound {
 
1030
                        // Someone else got there first.
 
1031
                        err = params.ErrNotFound
 
1032
                }
 
1033
                return errgo.Mask(err, errgo.Is(params.ErrNotFound))
 
1034
        }
 
1035
        // Remove the reference to the archive from the blob store.
 
1036
        if err := s.BlobStore.Remove(entity.BlobName); err != nil {
 
1037
                return errgo.Notef(err, "cannot remove blob %s", entity.BlobName)
 
1038
        }
 
1039
        if entity.BlobHash != entity.PreV5BlobHash {
 
1040
                name := preV5CompatibilityBlobName(entity.BlobName)
 
1041
                if err := s.BlobStore.Remove(name); err != nil {
 
1042
                        return errgo.Notef(err, "cannot remove compatibility blob %s", name)
 
1043
                }
 
1044
        }
 
1045
 
 
1046
        return nil
 
1047
}
 
1048
 
1345
1049
// StoreDatabase wraps an mgo.DB ands adds a few convenience methods.
1346
1050
type StoreDatabase struct {
1347
1051
        *mgo.Database
1419
1123
        return cs
1420
1124
}
1421
1125
 
 
1126
// readerAtSeeker adapts an io.ReadSeeker to an io.ReaderAt.
1422
1127
type readerAtSeeker struct {
1423
 
        r io.ReadSeeker
 
1128
        r   io.ReadSeeker
 
1129
        off int64
1424
1130
}
1425
1131
 
1426
 
func (r *readerAtSeeker) ReadAt(buf []byte, p int64) (int, error) {
1427
 
        if _, err := r.r.Seek(p, 0); err != nil {
1428
 
                return 0, errgo.Notef(err, "cannot seek")
 
1132
// ReadAt implemnts SizeReaderAt.ReadAt.
 
1133
func (r *readerAtSeeker) ReadAt(buf []byte, off int64) (n int, err error) {
 
1134
        if off != r.off {
 
1135
                _, err = r.r.Seek(off, 0)
 
1136
                if err != nil {
 
1137
                        return 0, err
 
1138
                }
 
1139
                r.off = off
1429
1140
        }
1430
 
        return r.r.Read(buf)
 
1141
        n, err = io.ReadFull(r.r, buf)
 
1142
        r.off += int64(n)
 
1143
        return n, err
1431
1144
}
1432
1145
 
1433
1146
// ReaderAtSeeker adapts r so that it can be used as
1434
 
// a ReaderAt. Note that, unlike some implementations
1435
 
// of ReaderAt, it is not OK to use concurrently.
 
1147
// a ReaderAt. Note that, contrary to the io.ReaderAt
 
1148
// contract, it is not OK to use concurrently.
1436
1149
func ReaderAtSeeker(r io.ReadSeeker) io.ReaderAt {
1437
 
        return &readerAtSeeker{r}
 
1150
        return &readerAtSeeker{r, 0}
1438
1151
}
1439
1152
 
1440
1153
// Search searches the store for the given SearchParams.
1509
1222
        "series": "series",
1510
1223
}
1511
1224
 
1512
 
// createSort creates a sort query parameters for mongo out of a Sort parameter.
 
1225
// createMongoSort creates a sort query parameters for mongo out of a Sort parameter.
1513
1226
func createMongoSort(sp SearchParams) (bson.D, error) {
 
1227
        if len(sp.sort) == 0 {
 
1228
                return bson.D{{
 
1229
                        "_id", 1,
 
1230
                }}, nil
 
1231
        }
1514
1232
        sort := make(bson.D, len(sp.sort))
1515
 
 
1516
1233
        for i, s := range sp.sort {
1517
1234
                field := sortMongoFields[s.Field]
1518
1235
                if field == "" {
1527
1244
        return sort, nil
1528
1245
}
1529
1246
 
1530
 
// List lists the store for the given ListParams.
1531
 
// It returns a ListResult containing the results of the list.
1532
 
func (store *Store) List(sp SearchParams) (ListResult, error) {
 
1247
// ListQuery holds a list query from which an iterator
 
1248
// can be created.
 
1249
type ListQuery struct {
 
1250
        store   *Store
 
1251
        filters map[string]interface{}
 
1252
        sort    bson.D
 
1253
}
 
1254
 
 
1255
// ListQuery lists entities in the store that conform to the
 
1256
// given search paramerters. It returns a ListQuery
 
1257
// that can be used to iterate through the list.
 
1258
func (store *Store) ListQuery(sp SearchParams) (*ListQuery, error) {
1533
1259
        filters, sort, err := prepareList(sp)
1534
1260
        if err != nil {
1535
 
                return ListResult{}, errgo.Mask(err)
1536
 
        }
1537
 
        q := []bson.M{{"$match": filters}}
1538
 
        q = append(q, bson.M{"$sort": bson.D{{"revision", 1}}})
1539
 
 
1540
 
        d := bson.M{
1541
 
                "_id": bson.M{
1542
 
                        "$concat": []interface{}{
1543
 
                                "$baseurl",
1544
 
                                "$series",
1545
 
                                bson.M{
1546
 
                                        "$cond": []string{"$development", "true", "false"},
1547
 
                                },
1548
 
                        },
1549
 
                },
1550
 
                "promulgated-url": bson.M{"$last": "$promulgated-url"},
1551
 
                "development":     bson.M{"$last": "$development"},
1552
 
                "name":            bson.M{"$last": "$name"},
1553
 
                "user":            bson.M{"$last": "$user"},
1554
 
                "series":          bson.M{"$last": "$series"},
1555
 
                "url":             bson.M{"$last": "$_id"},
1556
 
        }
1557
 
        group := bson.M{"$group": d}
1558
 
        q = append(q, group)
1559
 
        project := bson.M{
1560
 
                "$project": bson.M{
1561
 
                        "_id":             "$url",
1562
 
                        "development":     "$development",
1563
 
                        "name":            "$name",
1564
 
                        "user":            "$user",
1565
 
                        "series":          "$series",
1566
 
                        "promulgated-url": "$promulgated-url",
1567
 
                },
1568
 
        }
1569
 
        q = append(q, project)
1570
 
        if len(sort) == 0 {
1571
 
                q = append(q, bson.M{
1572
 
                        "$sort": bson.D{{"_id", 1}},
1573
 
                })
1574
 
        } else {
1575
 
                q = append(q, bson.M{"$sort": sort})
1576
 
        }
1577
 
 
1578
 
        pipe := store.DB.Entities().Pipe(q)
1579
 
        r := ListResult{
1580
 
                Results: make([]*router.ResolvedURL, 0),
1581
 
        }
1582
 
        var entity mongodoc.Entity
1583
 
        iter := pipe.Iter()
1584
 
        for iter.Next(&entity) {
1585
 
                r.Results = append(r.Results, EntityResolvedURL(&entity))
1586
 
        }
1587
 
        if err := iter.Close(); err != nil {
1588
 
                return ListResult{}, errgo.Mask(err)
1589
 
        }
1590
 
        return r, nil
 
1261
                return nil, errgo.Mask(err)
 
1262
        }
 
1263
        return &ListQuery{
 
1264
                store:   store,
 
1265
                filters: filters,
 
1266
                sort:    sort,
 
1267
        }, nil
 
1268
}
 
1269
 
 
1270
func (lq *ListQuery) Iter(fields map[string]int) *mgo.Iter {
 
1271
        qfields := FieldSelector(
 
1272
                "promulgated-url",
 
1273
                "development",
 
1274
                "name",
 
1275
                "user",
 
1276
                "series",
 
1277
        )
 
1278
        for f := range fields {
 
1279
                qfields[f] = 1
 
1280
        }
 
1281
        // _id and url have special treatment.
 
1282
        delete(qfields, "_id")
 
1283
        delete(qfields, "url")
 
1284
 
 
1285
        group := make(bson.D, 0, 2+len(qfields))
 
1286
        group = append(group, bson.DocElem{"_id", bson.D{{
 
1287
                "$concat", []interface{}{
 
1288
                        "$baseurl",
 
1289
                        "$series",
 
1290
                        bson.D{{
 
1291
                                "$cond", []string{"$development", "true", "false"},
 
1292
                        }},
 
1293
                },
 
1294
        }}})
 
1295
        group = append(group, bson.DocElem{"url", bson.D{{"$last", "$_id"}}})
 
1296
        for field := range qfields {
 
1297
                group = append(group, bson.DocElem{field, bson.D{{"$last", "$" + field}}})
 
1298
        }
 
1299
 
 
1300
        project := make(bson.D, 0, len(qfields)+1)
 
1301
        project = append(project, bson.DocElem{"_id", "$url"})
 
1302
        for f := range qfields {
 
1303
                project = append(project, bson.DocElem{f, "$" + f})
 
1304
        }
 
1305
 
 
1306
        q := []bson.D{
 
1307
                {{"$match", lq.filters}},
 
1308
                {{"$sort", bson.D{{"revision", 1}}}},
 
1309
                {{"$group", group}},
 
1310
                {{"$project", project}},
 
1311
                {{"$sort", lq.sort}},
 
1312
        }
 
1313
        return lq.store.DB.Entities().Pipe(q).Iter()
1591
1314
}
1592
1315
 
1593
1316
// SynchroniseElasticsearch creates new indexes in elasticsearch
1602
1325
        return nil
1603
1326
}
1604
1327
 
1605
 
// EntityResolvedURL returns the ResolvedURL for the entity.
1606
 
// It requires PromulgatedURL and Development fields to have been
1607
 
// filled out in the entity.
 
1328
// EntityResolvedURL returns the ResolvedURL for the entity. It requires
 
1329
// that the PromulgatedURL field has been filled out in the entity.
1608
1330
func EntityResolvedURL(e *mongodoc.Entity) *router.ResolvedURL {
1609
1331
        rurl := &router.ResolvedURL{
1610
1332
                URL:                 *e.URL,
1611
1333
                PromulgatedRevision: -1,
1612
 
                Development:         e.Development,
1613
1334
        }
1614
1335
        if e.PromulgatedURL != nil {
1615
1336
                rurl.PromulgatedRevision = e.PromulgatedURL.Revision