Skip to content

Commit

Permalink
o/snapstate: download snap icon after downloading snap file
Browse files Browse the repository at this point in the history
Signed-off-by: Oliver Calder <[email protected]>
  • Loading branch information
olivercalder committed Feb 14, 2025
1 parent b8dc74f commit e7b453f
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 7 deletions.
10 changes: 9 additions & 1 deletion overlord/managers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ func (s *baseMgrsSuite) mockStore(c *C) *httptest.Server {
hit := strings.Replace(hitTemplate, "@URL@", baseURL.String()+"/api/v1/snaps/download/"+name+"/"+revno, -1)
hit = strings.Replace(hit, "@NAME@", name, -1)
hit = strings.Replace(hit, "@SNAPID@", fakeSnapID(name), -1)
hit = strings.Replace(hit, "@ICON@", baseURL.String()+"/icon", -1)
hit = strings.Replace(hit, "@ICON@", "http://example.com/icon.svg", -1)
hit = strings.Replace(hit, "@VERSION@", info.Version, -1)
hit = strings.Replace(hit, "@REVISION@", revno, -1)
hit = strings.Replace(hit, `@TYPE@`, string(info.Type()), -1)
Expand All @@ -1094,6 +1094,14 @@ func (s *baseMgrsSuite) mockStore(c *C) *httptest.Server {
s.storeObserver(r)
}

if r.URL.Path == "http://example.com/icon.svg" {
// the http server was hit while requesting the snap icon, so just
// write some stand-in data
iconContents := fmt.Sprintf("icon contents")
w.Write([]byte(iconContents))
return
}

// all URLS are /api/v1/snaps/... or /v2/snaps/ or /v2/assertions/... so
// check the url is sane and discard the common prefix
// to simplify indexing into the comps slice.
Expand Down
30 changes: 29 additions & 1 deletion overlord/snapstate/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,12 @@ type fakeDownload struct {
opts *store.DownloadOptions
}

type fakeIconDownload struct {
name string
target string
url string
}

type byName []store.CurrentSnap

func (bna byName) Len() int { return len(bna) }
Expand Down Expand Up @@ -183,6 +189,7 @@ type fakeStore struct {
mu sync.Mutex

downloads []fakeDownload
iconDownloads []fakeIconDownload
refreshRevnos map[string]snap.Revision
fakeBackend *fakeSnappyBackend
fakeCurrentProgress int
Expand All @@ -196,7 +203,8 @@ type fakeStore struct {
// it should return the resources that the snap should have.
snapResourcesFn func(*snap.Info) []store.SnapResourceResult

downloadCallback func()
downloadCallback func()
downloadIconCallback func(targetPath string)

namesToAssertedIDs map[string]string
idsToNames map[string]string
Expand Down Expand Up @@ -250,6 +258,12 @@ func (f *fakeStore) appendDownload(dl *fakeDownload) {
f.downloads = append(f.downloads, *dl)
}

func (f *fakeStore) appendIconDownload(dl *fakeIconDownload) {
f.mu.Lock()
defer f.mu.Unlock()
f.iconDownloads = append(f.iconDownloads, *dl)
}

type snapSpec struct {
Name string
Channel string
Expand Down Expand Up @@ -931,6 +945,20 @@ func (f *fakeStore) Download(ctx context.Context, name, targetFn string, snapInf
return nil
}

func (f *fakeStore) DownloadIcon(ctx context.Context, name string, targetPath string, downloadURL string) error {
if f.downloadIconCallback != nil {
f.downloadIconCallback(targetPath)
}
f.appendIconDownload(&fakeIconDownload{
name: name,
target: targetPath,
url: downloadURL,
})
f.fakeBackend.appendOp(&fakeOp{op: "storesvc-download-icon", name: name})

return nil
}

func (f *fakeStore) WriteCatalogs(ctx context.Context, _ io.Writer, _ store.SnapAdder) error {
if ctx == nil {
panic("context required")
Expand Down
48 changes: 43 additions & 5 deletions overlord/snapstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error {

meter := NewTaskProgressAdapterUnlocked(t)
targetFn := snapsup.BlobPath()
targetIconFn := backend.IconDownloadFilename(snapsup.SideInfo.SnapID)
iconURL := snapsup.Media.IconURL()

dlOpts := &store.DownloadOptions{
Scheduled: snapsup.IsAutoRefresh,
Expand Down Expand Up @@ -753,17 +755,53 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, tomb *tomb.Tomb) error {
if err != nil {
return err
}

// Re-compute icon download filepath and URL if the result info has the
// necessary information
if result.SnapID != "" {
targetIconFn = backend.IconDownloadFilename(result.SnapID)
}
if resultIconURL := result.Media.IconURL(); resultIconURL != "" {
iconURL = resultIconURL
}

Check warning on line 766 in overlord/snapstate/handlers.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/handlers.go#L765-L766

Added lines #L765 - L766 were not covered by tests

ctx := tomb.Context(nil) // XXX: should this be a real context?

timings.Run(perfTimings, "download", fmt.Sprintf("download snap %q", snapsup.SnapName()), func(timings.Measurer) {
err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, &result.DownloadInfo, meter, user, dlOpts)
err = theStore.Download(ctx, snapsup.SnapName(), targetFn, &result.DownloadInfo, meter, user, dlOpts)
})
snapsup.SideInfo = &result.SideInfo
if err != nil {
return err
}

Check warning on line 776 in overlord/snapstate/handlers.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/handlers.go#L775-L776

Added lines #L775 - L776 were not covered by tests
// Snap download succeeded, now try to download the snap icon
if iconURL == "" {
logger.Debugf("cannot download snap icon for %q: no icon URL", snapsup.SnapName())
} else {
timings.Run(perfTimings, "download-icon", fmt.Sprintf("download snap icon for %q", snapsup.SnapName()), func(timings.Measurer) {
if iconErr := theStore.DownloadIcon(ctx, snapsup.SnapName(), targetIconFn, iconURL); iconErr != nil {
logger.Debugf("cannot download snap icon for %q: %#v", snapsup.SnapName(), iconErr)
}

Check warning on line 784 in overlord/snapstate/handlers.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/handlers.go#L781-L784

Added lines #L781 - L784 were not covered by tests
})
}
} else {
ctx := tomb.Context(nil) // XXX: should this be a real context?
timings.Run(perfTimings, "download", fmt.Sprintf("download snap %q", snapsup.SnapName()), func(timings.Measurer) {
err = theStore.Download(tomb.Context(nil), snapsup.SnapName(), targetFn, snapsup.DownloadInfo, meter, user, dlOpts)
err = theStore.Download(ctx, snapsup.SnapName(), targetFn, snapsup.DownloadInfo, meter, user, dlOpts)
})
}
if err != nil {
return err
if err != nil {
return err
}
// Snap download succeeded, now try to download the snap icon
if iconURL == "" {
logger.Debugf("cannot download snap icon for %q: no icon URL", snapsup.SnapName())
} else {
timings.Run(perfTimings, "download-icon", fmt.Sprintf("download snap icon for %q", snapsup.SnapName()), func(timings.Measurer) {
if iconErr := theStore.DownloadIcon(ctx, snapsup.SnapName(), targetIconFn, iconURL); iconErr != nil {
logger.Debugf("cannot download snap icon for %q: %#v", snapsup.SnapName(), iconErr)
}

Check warning on line 802 in overlord/snapstate/handlers.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/handlers.go#L801-L802

Added lines #L801 - L802 were not covered by tests
})
}
}

snapsup.SnapPath = targetFn
Expand Down
22 changes: 22 additions & 0 deletions overlord/snapstate/snapstate_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,17 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
// we start without the auxiliary store info
c.Check(backend.AuxStoreInfoFilename("some-snap-id"), testutil.FileAbsent)

iconContents := []byte("I'm an svg")
// set up the downloadIcon callback to write the fake icon to the icons pool
var downloadIconCalls int
s.fakeStore.downloadIconCallback = func(targetPath string) {
downloadIconCalls++
c.Assert(downloadIconCalls, Equals, 1)

c.Assert(os.MkdirAll(filepath.Dir(targetPath), 0o755), IsNil)
c.Assert(os.WriteFile(targetPath, iconContents, 0o644), IsNil)
}

chg := s.state.NewChange("install", "install a snap")
opts := &snapstate.RevisionOptions{Channel: "channel-for-media"}
ts, err := snapstate.Install(context.Background(), s.state, "some-snap", opts, s.user.ID, snapstate.Flags{})
Expand All @@ -1337,6 +1348,11 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
name: "some-snap",
target: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"),
}})
c.Check(s.fakeStore.iconDownloads, DeepEquals, []fakeIconDownload{{
name: "some-snap",
target: backend.IconDownloadFilename("some-snap-id"),
url: "http://example.com/icon.png",
}})
c.Check(s.fakeStore.seenPrivacyKeys["privacy-key"], Equals, true, Commentf("salts seen: %v", s.fakeStore.seenPrivacyKeys))
expected := fakeOps{
{
Expand All @@ -1357,6 +1373,10 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
op: "storesvc-download",
name: "some-snap",
},
{
op: "storesvc-download-icon",
name: "some-snap",
},
{
op: "validate-snap:Doing",
name: "some-snap",
Expand Down Expand Up @@ -1514,6 +1534,8 @@ func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) {
},
})
c.Check(info.StoreURL, Equals, "https://snapcraft.io/example-snap")

c.Check(backend.IconInstallFilename("some-snap-id"), testutil.FileEquals, iconContents)
}

func (s *snapmgrTestSuite) testParallelInstanceInstallRunThrough(c *C, inputFlags, expectedFlags snapstate.Flags) {
Expand Down

0 comments on commit e7b453f

Please sign in to comment.