Skip to content

Commit

Permalink
tests/lib/fakestore: add support for tracking channels
Browse files Browse the repository at this point in the history
  • Loading branch information
valentindavid committed Dec 10, 2024
1 parent e6a6ea1 commit 49be6de
Showing 1 changed file with 99 additions and 14 deletions.
113 changes: 99 additions & 14 deletions tests/lib/fakestore/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package store

import (
"bufio"
"context"
"encoding/base64"
"encoding/json"
Expand Down Expand Up @@ -68,6 +69,8 @@ type Store struct {
fallback *store.Store

srv *http.Server

channelRepository *ChannelRepository
}

// NewStore creates a new store server serving snaps from the given top directory and assertions from topDir/asserts. If assertFallback is true missing assertions are looked up in the main online store.
Expand All @@ -90,13 +93,20 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
Addr: addr,
Handler: mux,
},
channelRepository: &ChannelRepository{
rootDir: filepath.Join(topDir, "channels"),
},
}

mux.HandleFunc("/", rootEndpoint)
mux.HandleFunc("/api/v1/snaps/search", store.searchEndpoint)
mux.HandleFunc("/api/v1/snaps/details/", store.detailsEndpoint)
mux.HandleFunc("/api/v1/snaps/metadata", store.bulkEndpoint)
mux.Handle("/download/", http.StripPrefix("/download/", http.FileServer(http.Dir(topDir))))

mux.HandleFunc("/api/v1/snaps/auth/nonces", store.nonceEndpoint)
mux.HandleFunc("/api/v1/snaps/auth/sessions", store.sessionEndpoint)

// v2
mux.HandleFunc("/v2/assertions/", store.assertionsEndpoint)
mux.HandleFunc("/v2/snaps/refresh", store.snapActionEndpoint)
Expand All @@ -111,6 +121,14 @@ func (s *Store) URL() string {
return s.url
}

func (s *Store) RealURL(req *http.Request) string {
if req.Host == "" {
return s.url
} else {
return fmt.Sprintf("http://%s", req.Host)
}
}

func (s *Store) SnapsDir() string {
return s.blobDir
}
Expand Down Expand Up @@ -179,7 +197,9 @@ type essentialInfo struct {
Base string
}

func snapEssentialInfo(fn, snapID string, bs asserts.Backstore) (*essentialInfo, error) {
var errInfo = errors.New("cannot get info")

func snapEssentialInfo(fn, snapID string, bs asserts.Backstore, cs *ChannelRepository) (*essentialInfo, error) {
f, err := snapfile.Open(fn)
if err != nil {
return nil, fmt.Errorf("cannot read: %v: %v", fn, err)
Expand Down Expand Up @@ -356,7 +376,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {

fn := set.getLatest()

essInfo, err := snapEssentialInfo(fn, "", bs)
essInfo, err := snapEssentialInfo(fn, "", bs, s.channelRepository)
if err != nil {
http.Error(w, err.Error(), 400)
return
Expand All @@ -368,8 +388,8 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
PackageName: essInfo.Name,
Developer: essInfo.DevelName,
DeveloperID: essInfo.DeveloperID,
AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
DownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
DownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
Version: essInfo.Version,
Revision: essInfo.Revision,
DownloadDigest: hexify(essInfo.Digest),
Expand Down Expand Up @@ -436,7 +456,7 @@ func (s *Store) collectSnaps(bs asserts.Backstore) (map[string]*revisionSet, err
// we only care about the revision here, so we can get away without
// setting the id
const snapID = ""
info, err := snapEssentialInfo(fn, snapID, bs)
info, err := snapEssentialInfo(fn, snapID, bs, s.channelRepository)
if err != nil {
return nil, err
}
Expand All @@ -447,6 +467,18 @@ func (s *Store) collectSnaps(bs asserts.Backstore) (map[string]*revisionSet, err

snaps[info.Name].add(snap.R(info.Revision), fn)

digest, _, err := asserts.SnapFileSHA3_384(fn)
if err != nil {
return nil, err
}
channels, err := s.channelRepository.findSnapChannels(digest)
if err != nil {
return nil, err
}
for _, channel := range channels {
snaps[fmt.Sprintf("%s|%s", info.Name, channel)].add(snap.R(info.Revision), fn)
}

logger.Debugf("found snap %q (revision %d) at %v", info.Name, info.Revision, fn)
}

Expand Down Expand Up @@ -537,7 +569,7 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {

fn := set.getLatest()

essInfo, err := snapEssentialInfo(fn, pkg.SnapID, bs)
essInfo, err := snapEssentialInfo(fn, pkg.SnapID, bs, s.channelRepository)
if err != nil {
http.Error(w, err.Error(), 400)
return
Expand All @@ -549,8 +581,8 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
PackageName: essInfo.Name,
Developer: essInfo.DevelName,
DeveloperID: essInfo.DeveloperID,
DownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)),
DownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn)),
Version: essInfo.Version,
Revision: essInfo.Revision,
DownloadDigest: hexify(essInfo.Digest),
Expand Down Expand Up @@ -610,8 +642,9 @@ func (s *Store) collectAssertions() (asserts.Backstore, error) {
}

type currentSnap struct {
SnapID string `json:"snap-id"`
InstanceKey string `json:"instance-key"`
SnapID string `json:"snap-id"`
InstanceKey string `json:"instance-key"`
TrackingChannel string `json:"tracking-channel"`
}

type snapAction struct {
Expand All @@ -620,6 +653,7 @@ type snapAction struct {
SnapID string `json:"snap-id"`
Name string `json:"name"`
Revision int `json:"revision,omitempty"`
Channel string `json:"channel,omitempty"`
}

type snapActionRequest struct {
Expand Down Expand Up @@ -702,6 +736,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
Action: "refresh",
SnapID: s.SnapID,
InstanceKey: s.InstanceKey,
Channel: s.TrackingChannel,
}
}
}
Expand All @@ -723,8 +758,21 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
return
}

set, ok := snaps[name]
if !ok {
var set *revisionSet
var foundSnap bool
if a.Channel != "" {
set, foundSnap = snaps[fmt.Sprintf("%s|%s", name, a.Channel)]
}
if !foundSnap {
// FIXME: It is possible that many tests do
// not use channels correctly. So we have to
// fallback to searching for snaps by just
// name, without channel. Maybe we should
// remove that, and fix all the tests instead.
set, foundSnap = snaps[name]
}

if !foundSnap {
continue
}

Expand All @@ -735,7 +783,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
continue
}

essInfo, err := snapEssentialInfo(fn, snapID, bs)
essInfo, err := snapEssentialInfo(fn, snapID, bs, s.channelRepository)
if err != nil {
http.Error(w, err.Error(), 400)
return
Expand All @@ -760,7 +808,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
logger.Debugf("requested snap %q revision %d", essInfo.Name, a.Revision)
res.Snap.Publisher.ID = essInfo.DeveloperID
res.Snap.Publisher.Username = essInfo.DevelName
res.Snap.Download.URL = fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn))
res.Snap.Download.URL = fmt.Sprintf("%s/download/%s", s.RealURL(req), filepath.Base(fn))
res.Snap.Download.Sha3_384 = hexify(essInfo.Digest)
res.Snap.Download.Size = essInfo.Size
replyData.Results = append(replyData.Results, res)
Expand Down Expand Up @@ -920,3 +968,40 @@ func findSnapRevision(snapDigest string, bs asserts.Backstore) (*asserts.SnapRev

return snapRev, devAcct, nil
}

func (s *Store) nonceEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"nonce": "blah"}`))
return
}

func (s *Store) sessionEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(`{"macaroon": "blahblah"}`))
return
}

type ChannelRepository struct {
rootDir string
}

func (cr *ChannelRepository) findSnapChannels(snapDigest string) ([]string, error) {
dataPath := filepath.Join(cr.rootDir, snapDigest)
fd, err := os.Open(dataPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
} else {
return nil, err
}
} else {
sc := bufio.NewScanner(fd)
var lines []string
for sc.Scan() {
lines = append(lines, sc.Text())
}
return lines, nil
}
}

0 comments on commit 49be6de

Please sign in to comment.