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 Sep 11, 2024
1 parent 89166a0 commit c498797
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 17 deletions.
113 changes: 97 additions & 16 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 @@ -177,11 +195,12 @@ type essentialInfo struct {
Confinement string
Type string
Base string
/*Channels []string*/
}

var errInfo = errors.New("cannot get info")

func snapEssentialInfo(w http.ResponseWriter, fn, snapID string, bs asserts.Backstore) (*essentialInfo, error) {
func snapEssentialInfo(w http.ResponseWriter, fn, snapID string, bs asserts.Backstore, cs *ChannelRepository) (*essentialInfo, error) {
f, err := snapfile.Open(fn)
if err != nil {
http.Error(w, fmt.Sprintf("cannot read: %v: %v", fn, err), 400)
Expand Down Expand Up @@ -348,7 +367,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
http.Error(w, fmt.Sprintf("internal error collecting assertions: %v", err), 500)
return
}
snaps, err := s.collectSnaps()
snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
Expand All @@ -360,7 +379,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
return
}

essInfo, err := snapEssentialInfo(w, fn, "", bs)
essInfo, err := snapEssentialInfo(w, fn, "", bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
Expand All @@ -374,8 +393,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 All @@ -394,7 +413,7 @@ func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
w.Write(out)
}

func (s *Store) collectSnaps() (map[string]string, error) {
func (s *Store) collectSnaps(cs *ChannelRepository) (map[string]string, error) {
snapFns, err := filepath.Glob(filepath.Join(s.blobDir, "*.snap"))
if err != nil {
return nil, err
Expand All @@ -415,6 +434,19 @@ func (s *Store) collectSnaps() (map[string]string, error) {
return nil, err
}
snaps[info.SnapName()] = fn

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

logger.Debugf("found snap %q at %v", info.SnapName(), fn)
}

Expand Down Expand Up @@ -484,7 +516,7 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
return
}

snaps, err := s.collectSnaps()
snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
Expand All @@ -499,7 +531,7 @@ func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) {
}

if fn, ok := snaps[name]; ok {
essInfo, err := snapEssentialInfo(w, fn, pkg.SnapID, bs)
essInfo, err := snapEssentialInfo(w, fn, pkg.SnapID, bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
Expand All @@ -513,8 +545,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 @@ -575,8 +607,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 @@ -585,6 +618,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 @@ -653,7 +687,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
return
}

snaps, err := s.collectSnaps()
snaps, err := s.collectSnaps(s.channelRepository)
if err != nil {
http.Error(w, fmt.Sprintf("internal error collecting snaps: %v", err), 500)
return
Expand All @@ -667,6 +701,7 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
Action: "refresh",
SnapID: s.SnapID,
InstanceKey: s.InstanceKey,
Channel: s.TrackingChannel,
}
}
}
Expand All @@ -684,8 +719,17 @@ func (s *Store) snapActionEndpoint(w http.ResponseWriter, req *http.Request) {
return
}

if fn, ok := snaps[name]; ok {
essInfo, err := snapEssentialInfo(w, fn, snapID, bs)
var snapPath string
var foundSnap bool
if a.Channel != "" {
snapPath, foundSnap = snaps[fmt.Sprintf("%s|%s", name, a.Channel)]
}
if !foundSnap {
snapPath, foundSnap = snaps[name]
}

if foundSnap {
essInfo, err := snapEssentialInfo(w, snapPath, snapID, bs, s.channelRepository)
if essInfo == nil {
if err != errInfo {
panic(err)
Expand All @@ -712,7 +756,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(snapPath))
res.Snap.Download.Sha3_384 = hexify(essInfo.Digest)
res.Snap.Download.Size = essInfo.Size
replyData.Results = append(replyData.Results, res)
Expand Down Expand Up @@ -869,3 +913,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
}
}
2 changes: 1 addition & 1 deletion tests/lib/fakestore/store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ func (s *storeTestSuite) TestMakeTestSnap(c *C) {
func (s *storeTestSuite) TestCollectSnaps(c *C) {
s.makeTestSnap(c, "name: foo\nversion: 1")

snaps, err := s.store.collectSnaps()
snaps, err := s.store.collectSnaps(s.store.channelRepository)
c.Assert(err, IsNil)
c.Assert(snaps, DeepEquals, map[string]string{
"foo": filepath.Join(s.store.blobDir, "foo_1_all.snap"),
Expand Down

0 comments on commit c498797

Please sign in to comment.