Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

o/snapstate: move auxinfo functions to snapstate backend with metadata helpers #15051

Merged
merged 14 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2018-2022 Canonical Ltd
* Copyright (C) 2018-2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -17,7 +17,7 @@
*
*/

package snapstate
package backend

import (
"encoding/json"
Expand All @@ -30,27 +30,27 @@ import (
"github.com/snapcore/snapd/snap"
)

// auxStoreInfo is information about a snap (*not* a snap revision), not
// AuxStoreInfo is information about a snap (*not* a snap revision), not
// needed in the state, that may be stored to augment the information
// returned for locally-installed snaps
type auxStoreInfo struct {
type AuxStoreInfo struct {
Media snap.MediaInfos `json:"media,omitempty"`
StoreURL string `json:"store-url,omitempty"`
// XXX this is now included in snap.SideInfo.EditedLinks but
// continue having this to support old snapd
Website string `json:"website,omitempty"`
}

func auxStoreInfoFilename(snapID string) string {
func AuxStoreInfoFilename(snapID string) string {
return filepath.Join(dirs.SnapAuxStoreInfoDir, snapID) + ".json"
}

// retrieveAuxStoreInfo loads the stored per-snap auxiliary store info into the given *snap.Info
func retrieveAuxStoreInfo(info *snap.Info) error {
// RetrieveAuxStoreInfo loads the stored per-snap auxiliary store info into the given *snap.Info
func RetrieveAuxStoreInfo(info *snap.Info) error {
if info.SnapID == "" {
return nil
}
f, err := os.Open(auxStoreInfoFilename(info.SnapID))
f, err := os.Open(AuxStoreInfoFilename(info.SnapID))
if err != nil {
if os.IsNotExist(err) {
return nil
Expand All @@ -59,7 +59,7 @@ func retrieveAuxStoreInfo(info *snap.Info) error {
}
defer f.Close()

var aux auxStoreInfo
var aux AuxStoreInfo
dec := json.NewDecoder(f)
if err := dec.Decode(&aux); err != nil {
return fmt.Errorf("cannot decode auxiliary store info for snap %q: %v", info.InstanceName(), err)
Expand All @@ -79,15 +79,15 @@ func retrieveAuxStoreInfo(info *snap.Info) error {
}

// keepAuxStoreInfo saves the given auxiliary store info to disk.
func keepAuxStoreInfo(snapID string, aux *auxStoreInfo) error {
func keepAuxStoreInfo(snapID string, aux AuxStoreInfo) error {
if snapID == "" {
return nil
}
if err := os.MkdirAll(dirs.SnapAuxStoreInfoDir, 0755); err != nil {
return fmt.Errorf("cannot create directory for auxiliary store info: %v", err)
}

af, err := osutil.NewAtomicFile(auxStoreInfoFilename(snapID), 0644, 0, osutil.NoChown, osutil.NoChown)
af, err := osutil.NewAtomicFile(AuxStoreInfoFilename(snapID), 0644, 0, osutil.NoChown, osutil.NoChown)
if err != nil {
return fmt.Errorf("cannot create file for auxiliary store info for snap %s: %v", snapID, err)
}
Expand All @@ -109,7 +109,7 @@ func discardAuxStoreInfo(snapID string) error {
if snapID == "" {
return nil
}
if err := os.Remove(auxStoreInfoFilename(snapID)); err != nil && !os.IsNotExist(err) {
if err := os.Remove(AuxStoreInfoFilename(snapID)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("cannot remove auxiliary store info file for snap %s: %v", snapID, err)
}
return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2018-2022 Canonical Ltd
* Copyright (C) 2018-2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -17,7 +17,7 @@
*
*/

package snapstate_test
package backend_test

import (
"path/filepath"
Expand All @@ -26,7 +26,7 @@ import (

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/snap"
)

Expand All @@ -40,30 +40,30 @@ func (s *auxInfoSuite) SetUpTest(c *check.C) {

func (s *auxInfoSuite) TestAuxStoreInfoFilename(c *check.C) {
// precondition check
filename := snapstate.AuxStoreInfoFilename("some-snap-id")
filename := backend.AuxStoreInfoFilename("some-snap-id")
c.Check(filename, check.Equals, filepath.Join(dirs.SnapAuxStoreInfoDir, "some-snap-id.json"))
}

func (s *auxInfoSuite) TestAuxStoreInfoRoundTrip(c *check.C) {
media := snap.MediaInfos{{Type: "1-2-3-testing"}}
info := &snap.Info{SuggestedName: "some-snap"}
info.SnapID = "some-id"
filename := snapstate.AuxStoreInfoFilename(info.SnapID)
filename := backend.AuxStoreInfoFilename(info.SnapID)
c.Assert(osutil.FileExists(filename), check.Equals, false)
c.Check(snapstate.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(backend.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(info.Media, check.HasLen, 0)
c.Check(info.Website(), check.Equals, "")
c.Check(info.StoreURL, check.Equals, "")

aux := &snapstate.AuxStoreInfo{
aux := backend.AuxStoreInfo{
Media: media,
Website: "http://example.com/some-snap",
StoreURL: "https://snapcraft.io/some-snap",
}
c.Assert(snapstate.KeepAuxStoreInfo(info.SnapID, aux), check.IsNil)
c.Assert(backend.KeepAuxStoreInfo(info.SnapID, aux), check.IsNil)
c.Check(osutil.FileExists(filename), check.Equals, true)

c.Assert(snapstate.RetrieveAuxStoreInfo(info), check.IsNil)
c.Assert(backend.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(info.Media, check.HasLen, 1)
c.Check(info.Media, check.DeepEquals, media)
c.Check(info.Website(), check.Equals, "http://example.com/some-snap")
Expand All @@ -75,7 +75,7 @@ func (s *auxInfoSuite) TestAuxStoreInfoRoundTrip(c *check.C) {
info.EditedLinks = map[string][]string{
"website": {"http://newer-website-com"},
}
c.Assert(snapstate.RetrieveAuxStoreInfo(info), check.IsNil)
c.Assert(backend.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(info.Media, check.HasLen, 1)
c.Check(info.Media, check.DeepEquals, media)
c.Check(info.Website(), check.Equals, "http://newer-website-com")
Expand All @@ -86,13 +86,13 @@ func (s *auxInfoSuite) TestAuxStoreInfoRoundTrip(c *check.C) {
info.LegacyWebsite = ""
info.StoreURL = ""

c.Assert(snapstate.DiscardAuxStoreInfo(info.SnapID), check.IsNil)
c.Assert(backend.DiscardAuxStoreInfo(info.SnapID), check.IsNil)
c.Assert(osutil.FileExists(filename), check.Equals, false)

c.Check(snapstate.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(backend.RetrieveAuxStoreInfo(info), check.IsNil)
c.Check(info.Media, check.HasLen, 0)
c.Check(info.Website(), check.Equals, "")
c.Check(info.StoreURL, check.Equals, "")

c.Check(snapstate.DiscardAuxStoreInfo(info.SnapID), check.IsNil)
c.Check(backend.DiscardAuxStoreInfo(info.SnapID), check.IsNil)
}
3 changes: 3 additions & 0 deletions overlord/snapstate/backend/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ var (
RemoveIfEmpty = removeIfEmpty
SnapDataDirs = snapDataDirs
SnapCommonDataDirs = snapCommonDataDirs

KeepAuxStoreInfo = keepAuxStoreInfo
DiscardAuxStoreInfo = discardAuxStoreInfo
)

func MockWrappersAddSnapdSnapServices(f func(s *snap.Info, opts *wrappers.AddSnapdSnapServicesOptions, inter wrappers.Interacter) (wrappers.SnapdRestart, error)) (restore func()) {
Expand Down
82 changes: 82 additions & 0 deletions overlord/snapstate/backend/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2025 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package backend

// InstallStoreMetadata saves revision-agnostic metadata to disk for the snap
// with the given snap ID. At the moment, this metadata includes auxiliary
// store information. Returns a closure to undo the function's actions,
// depending on whether it's a first install or if there are other instances.
func InstallStoreMetadata(snapID string, aux AuxStoreInfo, linkCtx LinkContext) (undo func(), err error) {
if snapID == "" {
return func() {}, nil
}
if err := keepAuxStoreInfo(snapID, aux); err != nil {
return nil, err
}

Check warning on line 32 in overlord/snapstate/backend/metadata.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/backend/metadata.go#L31-L32

Added lines #L31 - L32 were not covered by tests
// TODO: install other types of revision-agnostic metadata
return func() {
UninstallStoreMetadata(snapID, linkCtx)
}, nil
}

// UninstallStoreMetadata removes revision-agnostic metadata from disk for the
// snap with the given snap ID. At the moment, this metadata includes auxiliary
// store information. The given linkCtx governs what metadata is removed and
// what is preserved.
func UninstallStoreMetadata(snapID string, linkCtx LinkContext) error {
if linkCtx.HasOtherInstances || snapID == "" {
return nil
}
if linkCtx.FirstInstall {
// only discard aux store info if there are no other revision of the
// snap present, in case we want to roll-back the discard, and need the
// auxinfo on disk to re-populate an old snap.Info. This might occur if
// e.g. we unlinked the snap and now need to undoUnlinkSnap.
if err := discardAuxStoreInfo(snapID); err != nil {
return err
}

Check warning on line 54 in overlord/snapstate/backend/metadata.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/backend/metadata.go#L53-L54

Added lines #L53 - L54 were not covered by tests
}
// TODO: discard other types of revision-agnostic metadata
return nil
}

// DiscardStoreMetadata removes revision-agnostic metadata from disk for the
// snap with the given snap ID, and is intended to be called when the final
// revision of that snap is being discarded. At the moment, this only calls
// UninstallStoreMetadata. If hasOtherInstances is false, this function does
// nothing, as another instance of the same snap may still require this
// metadata.
func DiscardStoreMetadata(snapID string, hasOtherInstances bool) error {
if hasOtherInstances || snapID == "" {
return nil
}
linkCtx := LinkContext{
// since the final revision is being discarded, we're effectively
// removing the "first" install of the snap
FirstInstall: true,
HasOtherInstances: hasOtherInstances,
}
if err := UninstallStoreMetadata(snapID, linkCtx); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reuse here doesn't seem very valuable to me, better to just call discardAuxStoreInfo(snapID)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, good point, probably easier to unconditionally remove auxinfo (and icon in the future) than construct a special LinkContext to get UninstallStoreMetadata to do what we want.

return err
}

Check warning on line 78 in overlord/snapstate/backend/metadata.go

View check run for this annotation

Codecov / codecov/patch

overlord/snapstate/backend/metadata.go#L77-L78

Added lines #L77 - L78 were not covered by tests
// TODO: discard other types of revision-agnostic metadata which should be
// removed when the final revision of the snap is discarded
return nil
}
Loading
Loading