mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
The idea is to gain more control over how snapstate functions to plan snap operations track prerequisites. So that later we can have an implementation of snapstate.PrereqTracker that supports properly self-contained sets of snaps and can deal with alternative providers for content more in the same way as we do during normal operations. This implementation could then be used first for seeding and then also for remodel, instead of the current code that requires the default-providers to be installed and otherwise errors. The snapstate functions that are getting a PrereqTracker argument added are the one we use in those contexts. * snap: introduce SimplePrereqTracker * many: introduce snapstate.PrereqTracker the idea is for some snapstate operation functions to take a dedicated implementation to be able to properly deal with prereq checking for self-contained set of snaps like for first boot/seeding * many: pass PrereqTracker to Update|Install*WithDeviceContext * many: pass PrereqTracker to InstallPath * o/devicetate: next steps TODO for seeding to support alternative providers * o/devicestate: next steps TODO for remodel to support alternative providers * snap: for clarity check for some-snap too in TestSimplePrereqTracker * o/snapstate: rename prereqTrack => addPrereq the arguments/types should make it clear enough what it is about but also added an internal doc comment
2104 lines
66 KiB
Go
2104 lines
66 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2014-2022 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 snap_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
. "gopkg.in/check.v1"
|
|
"gopkg.in/yaml.v2"
|
|
|
|
"github.com/snapcore/snapd/dirs"
|
|
"github.com/snapcore/snapd/osutil"
|
|
"github.com/snapcore/snapd/snap"
|
|
"github.com/snapcore/snapd/snap/snapfile"
|
|
"github.com/snapcore/snapd/snap/snaptest"
|
|
"github.com/snapcore/snapd/snap/squashfs"
|
|
"github.com/snapcore/snapd/testutil"
|
|
)
|
|
|
|
type infoSuite struct {
|
|
testutil.BaseTest
|
|
}
|
|
|
|
type infoSimpleSuite struct{}
|
|
|
|
var _ = Suite(&infoSuite{})
|
|
var _ = Suite(&infoSimpleSuite{})
|
|
|
|
func (s *infoSimpleSuite) SetUpTest(c *C) {
|
|
dirs.SetRootDir(c.MkDir())
|
|
}
|
|
|
|
func (s *infoSimpleSuite) TearDownTest(c *C) {
|
|
dirs.SetRootDir("")
|
|
}
|
|
|
|
func (s *infoSimpleSuite) TestReadInfoPanicsIfSanitizeUnset(c *C) {
|
|
defer snap.MockSanitizePlugsSlots(nil)()
|
|
|
|
si := &snap.SideInfo{Revision: snap.R(1)}
|
|
snaptest.MockSnap(c, sampleYaml, si)
|
|
c.Assert(func() { snap.ReadInfo("sample", si) }, Panics, `SanitizePlugsSlots function not set`)
|
|
}
|
|
|
|
func (s *infoSuite) SetUpTest(c *C) {
|
|
s.BaseTest.SetUpTest(c)
|
|
dirs.SetRootDir(c.MkDir())
|
|
hookType := snap.NewHookType(regexp.MustCompile(".*"))
|
|
s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
|
|
s.BaseTest.AddCleanup(snap.MockSupportedHookTypes([]*snap.HookType{hookType}))
|
|
}
|
|
|
|
func (s *infoSuite) TearDownTest(c *C) {
|
|
s.BaseTest.TearDownTest(c)
|
|
dirs.SetRootDir("")
|
|
}
|
|
|
|
func (s *infoSuite) TestSideInfoOverrides(c *C) {
|
|
info := &snap.Info{
|
|
SuggestedName: "name",
|
|
OriginalSummary: "summary",
|
|
OriginalDescription: "desc",
|
|
}
|
|
|
|
info.SideInfo = snap.SideInfo{
|
|
RealName: "newname",
|
|
EditedSummary: "fixed summary",
|
|
EditedDescription: "fixed desc",
|
|
Revision: snap.R(1),
|
|
SnapID: "snapidsnapidsnapidsnapidsnapidsn",
|
|
}
|
|
|
|
c.Check(info.InstanceName(), Equals, "newname")
|
|
c.Check(info.Summary(), Equals, "fixed summary")
|
|
c.Check(info.Description(), Equals, "fixed desc")
|
|
c.Check(info.Revision, Equals, snap.R(1))
|
|
c.Check(info.SnapID, Equals, "snapidsnapidsnapidsnapidsnapidsn")
|
|
c.Check(info.ID(), Equals, "snapidsnapidsnapidsnapidsnapidsn")
|
|
}
|
|
|
|
func (s *infoSuite) TestContactFromEdited(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: nil,
|
|
}
|
|
|
|
info.SideInfo = snap.SideInfo{
|
|
LegacyEditedContact: "mailto:econtact@example.com",
|
|
}
|
|
|
|
c.Check(info.Contact(), Equals, "mailto:econtact@example.com")
|
|
}
|
|
|
|
func (s *infoSuite) TestNoContact(c *C) {
|
|
info := &snap.Info{}
|
|
|
|
c.Check(info.Contact(), Equals, "")
|
|
}
|
|
|
|
func (s *infoSuite) TestContactFromLinks(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: map[string][]string{
|
|
"contact": {"ocontact1@example.com", "ocontact2@example.com"},
|
|
},
|
|
}
|
|
|
|
c.Check(info.Contact(), Equals, "mailto:ocontact1@example.com")
|
|
}
|
|
|
|
func (s *infoSuite) TestContactFromLinksMailtoAlready(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: map[string][]string{
|
|
"contact": {"mailto:ocontact1@example.com", "ocontact2@example.com"},
|
|
},
|
|
}
|
|
|
|
c.Check(info.Contact(), Equals, "mailto:ocontact1@example.com")
|
|
}
|
|
|
|
func (s *infoSuite) TestContactFromLinksNotEmail(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: map[string][]string{
|
|
"contact": {"https://ocontact1", "ocontact2"},
|
|
},
|
|
}
|
|
|
|
c.Check(info.Contact(), Equals, "https://ocontact1")
|
|
}
|
|
|
|
func (s *infoSuite) TestLinks(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: map[string][]string{
|
|
"contact": {"ocontact@example.com"},
|
|
"website": {"http://owebsite"},
|
|
},
|
|
}
|
|
|
|
info.SideInfo = snap.SideInfo{
|
|
EditedLinks: map[string][]string{
|
|
"contact": {"mailto:econtact@example.com"},
|
|
"website": {"http://ewebsite"},
|
|
},
|
|
}
|
|
|
|
c.Check(info.Links(), DeepEquals, map[string][]string{
|
|
"contact": {"mailto:econtact@example.com"},
|
|
"website": {"http://ewebsite"},
|
|
})
|
|
|
|
info.EditedLinks = nil
|
|
c.Check(info.Links(), DeepEquals, map[string][]string{
|
|
"contact": {"mailto:ocontact@example.com"},
|
|
"website": {"http://owebsite"},
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestNormalizeEditedLinks(c *C) {
|
|
info := &snap.Info{
|
|
SideInfo: snap.SideInfo{
|
|
EditedLinks: map[string][]string{
|
|
"contact": {"ocontact1@example.com", "ocontact2@example.com", "mailto:ocontact2@example.com", "ocontact"},
|
|
"website": {":", "http://owebsite1", "https://owebsite2", ""},
|
|
"": {"ocontact2@example.com"},
|
|
"?": {"ocontact3@example.com"},
|
|
"abc": {},
|
|
},
|
|
},
|
|
}
|
|
|
|
c.Check(snap.ValidateLinks(info.EditedLinks), NotNil)
|
|
c.Check(snap.ValidateLinks(info.Links()), IsNil)
|
|
c.Check(info.Links(), DeepEquals, map[string][]string{
|
|
"contact": {"mailto:ocontact1@example.com", "mailto:ocontact2@example.com"},
|
|
"website": {"http://owebsite1", "https://owebsite2"},
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestNormalizeOriginalLinks(c *C) {
|
|
info := &snap.Info{
|
|
SideInfo: snap.SideInfo{
|
|
LegacyEditedContact: "ocontact1@example.com",
|
|
},
|
|
LegacyWebsite: "http://owebsite1",
|
|
OriginalLinks: map[string][]string{
|
|
"contact": {"ocontact2@example.com", "mailto:ocontact2@example.com", "ocontact"},
|
|
"website": {":", "https://owebsite2", ""},
|
|
"": {"ocontact2@example.com"},
|
|
"?": {"ocontact3@example.com"},
|
|
"abc": {},
|
|
},
|
|
}
|
|
|
|
c.Check(snap.ValidateLinks(info.OriginalLinks), NotNil)
|
|
c.Check(snap.ValidateLinks(info.Links()), IsNil)
|
|
c.Check(info.Links(), DeepEquals, map[string][]string{
|
|
"contact": {"mailto:ocontact1@example.com", "mailto:ocontact2@example.com"},
|
|
"website": {"http://owebsite1", "https://owebsite2"},
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestWebsiteFromLegacy(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: nil,
|
|
LegacyWebsite: "http://website",
|
|
}
|
|
|
|
c.Check(info.Website(), Equals, "http://website")
|
|
}
|
|
|
|
func (s *infoSuite) TestNoWebsite(c *C) {
|
|
info := &snap.Info{}
|
|
|
|
c.Check(info.Website(), Equals, "")
|
|
}
|
|
|
|
func (s *infoSuite) TestWebsiteFromLinks(c *C) {
|
|
info := &snap.Info{
|
|
OriginalLinks: map[string][]string{
|
|
"website": {"http://website1", "http://website2"},
|
|
},
|
|
}
|
|
|
|
c.Check(info.Website(), Equals, "http://website1")
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoSecurityTag(c *C) {
|
|
appInfo := &snap.AppInfo{Snap: &snap.Info{SuggestedName: "http"}, Name: "GET"}
|
|
c.Check(appInfo.SecurityTag(), Equals, "snap.http.GET")
|
|
}
|
|
|
|
func (s *infoSuite) TestPlugSlotSecurityTags(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: name
|
|
apps:
|
|
app1:
|
|
app2:
|
|
hooks:
|
|
hook1:
|
|
plugs:
|
|
plug:
|
|
slots:
|
|
slot:
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(info.Plugs["plug"].SecurityTags(), DeepEquals, []string{
|
|
"snap.name.app1", "snap.name.app2", "snap.name.hook.hook1"})
|
|
c.Assert(info.Slots["slot"].SecurityTags(), DeepEquals, []string{
|
|
"snap.name.app1", "snap.name.app2", "snap.name.hook.hook1"})
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoWrapperPath(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: foo
|
|
apps:
|
|
foo:
|
|
bar:
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Apps["bar"].WrapperPath(), Equals, filepath.Join(dirs.SnapBinariesDir, "foo.bar"))
|
|
c.Check(info.Apps["foo"].WrapperPath(), Equals, filepath.Join(dirs.SnapBinariesDir, "foo"))
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoCompleterPath(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: foo
|
|
apps:
|
|
foo:
|
|
bar:
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Apps["bar"].CompleterPath(), Equals, filepath.Join(dirs.CompletersDir, "foo.bar"))
|
|
c.Check(info.Apps["foo"].CompleterPath(), Equals, filepath.Join(dirs.CompletersDir, "foo"))
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoLauncherCommand(c *C) {
|
|
dirs.SetRootDir("")
|
|
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: foo
|
|
apps:
|
|
foo:
|
|
command: foo-bin
|
|
bar:
|
|
command: bar-bin -x
|
|
baz:
|
|
command: bar-bin -x
|
|
timer: 10:00-12:00,,mon,12:00~14:00
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
info.Revision = snap.R(42)
|
|
c.Check(info.Apps["bar"].LauncherCommand(), Equals, "/usr/bin/snap run foo.bar")
|
|
c.Check(info.Apps["bar"].LauncherStopCommand(), Equals, "/usr/bin/snap run --command=stop foo.bar")
|
|
c.Check(info.Apps["bar"].LauncherReloadCommand(), Equals, "/usr/bin/snap run --command=reload foo.bar")
|
|
c.Check(info.Apps["bar"].LauncherPostStopCommand(), Equals, "/usr/bin/snap run --command=post-stop foo.bar")
|
|
c.Check(info.Apps["foo"].LauncherCommand(), Equals, "/usr/bin/snap run foo")
|
|
c.Check(info.Apps["baz"].LauncherCommand(), Equals, `/usr/bin/snap run --timer="10:00-12:00,,mon,12:00~14:00" foo.baz`)
|
|
|
|
// snap with instance key
|
|
info.InstanceKey = "instance"
|
|
c.Check(info.Apps["bar"].LauncherCommand(), Equals, "/usr/bin/snap run foo_instance.bar")
|
|
c.Check(info.Apps["bar"].LauncherStopCommand(), Equals, "/usr/bin/snap run --command=stop foo_instance.bar")
|
|
c.Check(info.Apps["bar"].LauncherReloadCommand(), Equals, "/usr/bin/snap run --command=reload foo_instance.bar")
|
|
c.Check(info.Apps["bar"].LauncherPostStopCommand(), Equals, "/usr/bin/snap run --command=post-stop foo_instance.bar")
|
|
c.Check(info.Apps["foo"].LauncherCommand(), Equals, "/usr/bin/snap run foo_instance")
|
|
c.Check(info.Apps["baz"].LauncherCommand(), Equals, `/usr/bin/snap run --timer="10:00-12:00,,mon,12:00~14:00" foo_instance.baz`)
|
|
}
|
|
|
|
const sampleYaml = `
|
|
name: sample
|
|
version: 1
|
|
apps:
|
|
app:
|
|
command: foo
|
|
app2:
|
|
command: bar
|
|
sample:
|
|
command: foobar
|
|
command-chain: [chain]
|
|
hooks:
|
|
configure:
|
|
command-chain: [hookchain]
|
|
`
|
|
|
|
func (s *infoSuite) TestReadInfo(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
|
|
snapInfo1 := snaptest.MockSnap(c, sampleYaml, si)
|
|
|
|
snapInfo2, err := snap.ReadInfo("sample", si)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(snapInfo2.InstanceName(), Equals, "sample")
|
|
c.Check(snapInfo2.Revision, Equals, snap.R(42))
|
|
c.Check(snapInfo2.Summary(), Equals, "esummary")
|
|
|
|
c.Check(snapInfo2.Apps["app"].Command, Equals, "foo")
|
|
c.Check(snapInfo2.Apps["sample"].CommandChain, DeepEquals, []string{"chain"})
|
|
c.Check(snapInfo2.Hooks["configure"].CommandChain, DeepEquals, []string{"hookchain"})
|
|
|
|
c.Check(snapInfo2, DeepEquals, snapInfo1)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoWithInstance(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "instance summary"}
|
|
|
|
snapInfo1 := snaptest.MockSnapInstance(c, "sample_instance", sampleYaml, si)
|
|
|
|
snapInfo2, err := snap.ReadInfo("sample_instance", si)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(snapInfo2.InstanceName(), Equals, "sample_instance")
|
|
c.Check(snapInfo2.SnapName(), Equals, "sample")
|
|
c.Check(snapInfo2.Revision, Equals, snap.R(42))
|
|
c.Check(snapInfo2.Summary(), Equals, "instance summary")
|
|
|
|
c.Check(snapInfo2.Apps["app"].Command, Equals, "foo")
|
|
c.Check(snapInfo2.Apps["sample"].CommandChain, DeepEquals, []string{"chain"})
|
|
c.Check(snapInfo2.Hooks["configure"].CommandChain, DeepEquals, []string{"hookchain"})
|
|
|
|
c.Check(snapInfo2, DeepEquals, snapInfo1)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadCurrentInfo(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42)}
|
|
|
|
snapInfo1 := snaptest.MockSnapCurrent(c, sampleYaml, si)
|
|
|
|
snapInfo2, err := snap.ReadCurrentInfo("sample")
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(snapInfo2.InstanceName(), Equals, "sample")
|
|
c.Check(snapInfo2.Revision, Equals, snap.R(42))
|
|
c.Check(snapInfo2, DeepEquals, snapInfo1)
|
|
|
|
snapInfo3, err := snap.ReadCurrentInfo("not-sample")
|
|
c.Check(snapInfo3, IsNil)
|
|
c.Assert(err, ErrorMatches, `cannot find current revision for snap not-sample:.*`)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadCurrentInfoWithInstance(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42)}
|
|
|
|
snapInfo1 := snaptest.MockSnapInstanceCurrent(c, "sample_instance", sampleYaml, si)
|
|
|
|
snapInfo2, err := snap.ReadCurrentInfo("sample_instance")
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(snapInfo2.InstanceName(), Equals, "sample_instance")
|
|
c.Check(snapInfo2.SnapName(), Equals, "sample")
|
|
c.Check(snapInfo2.Revision, Equals, snap.R(42))
|
|
c.Check(snapInfo2, DeepEquals, snapInfo1)
|
|
|
|
snapInfo3, err := snap.ReadCurrentInfo("sample_other")
|
|
c.Check(snapInfo3, IsNil)
|
|
c.Assert(err, ErrorMatches, `cannot find current revision for snap sample_other:.*`)
|
|
}
|
|
|
|
func (s *infoSuite) TestInstallDate(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(1)}
|
|
info := snaptest.MockSnap(c, sampleYaml, si)
|
|
// not current -> Zero
|
|
c.Check(info.InstallDate(), IsNil)
|
|
c.Check(snap.InstallDate(info.InstanceName()).IsZero(), Equals, true)
|
|
|
|
mountdir := info.MountDir()
|
|
dir, rev := filepath.Split(mountdir)
|
|
c.Assert(os.MkdirAll(dir, 0755), IsNil)
|
|
cur := filepath.Join(dir, "current")
|
|
c.Assert(os.Symlink(rev, cur), IsNil)
|
|
st, err := os.Lstat(cur)
|
|
c.Assert(err, IsNil)
|
|
instTime := st.ModTime()
|
|
// validity
|
|
c.Check(instTime.IsZero(), Equals, false)
|
|
|
|
c.Check(info.InstallDate().Equal(instTime), Equals, true)
|
|
c.Check(snap.InstallDate(info.InstanceName()).Equal(instTime), Equals, true)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoNotFound(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
info, err := snap.ReadInfo("sample", si)
|
|
c.Check(info, IsNil)
|
|
c.Check(err, ErrorMatches, `cannot find installed snap "sample" at revision 42: missing file .*sample/42/meta/snap.yaml`)
|
|
bse, ok := err.(snap.BrokenSnapError)
|
|
c.Assert(ok, Equals, true)
|
|
c.Check(bse.Broken(), Equals, bse.Error())
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoUnreadable(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
c.Assert(os.MkdirAll(filepath.Join(snap.MinimalPlaceInfo("sample", si.Revision).MountDir(), "meta", "snap.yaml"), 0755), IsNil)
|
|
|
|
info, err := snap.ReadInfo("sample", si)
|
|
c.Check(info, IsNil)
|
|
// TODO: maybe improve this error message
|
|
c.Check(err, ErrorMatches, ".* is a directory")
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoUnparsable(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
p := filepath.Join(snap.MinimalPlaceInfo("sample", si.Revision).MountDir(), "meta", "snap.yaml")
|
|
c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil)
|
|
c.Assert(os.WriteFile(p, []byte(`- :`), 0644), IsNil)
|
|
|
|
info, err := snap.ReadInfo("sample", si)
|
|
c.Check(info, IsNil)
|
|
// TODO: maybe improve this error message
|
|
c.Check(err, ErrorMatches, `cannot use installed snap "sample" at revision 42: cannot parse snap.yaml: yaml: .*`)
|
|
bse, ok := err.(snap.BrokenSnapError)
|
|
c.Assert(ok, Equals, true)
|
|
c.Check(bse.Broken(), Equals, bse.Error())
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoUnfindable(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
p := filepath.Join(snap.MinimalPlaceInfo("sample", si.Revision).MountDir(), "meta", "snap.yaml")
|
|
c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil)
|
|
c.Assert(os.WriteFile(p, []byte(``), 0644), IsNil)
|
|
|
|
info, err := snap.ReadInfo("sample", si)
|
|
c.Check(err, ErrorMatches, `cannot find installed snap "sample" at revision 42: missing file .*var/lib/snapd/snaps/sample_42.snap`)
|
|
c.Check(info, IsNil)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoDanglingSymlink(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"}
|
|
mpi := snap.MinimalPlaceInfo("sample", si.Revision)
|
|
p := filepath.Join(mpi.MountDir(), "meta", "snap.yaml")
|
|
c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil)
|
|
c.Assert(os.WriteFile(p, []byte(`name: test`), 0644), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Dir(mpi.MountFile()), 0755), IsNil)
|
|
c.Assert(os.Symlink("/dangling", mpi.MountFile()), IsNil)
|
|
|
|
info, err := snap.ReadInfo("sample", si)
|
|
c.Check(err, IsNil)
|
|
c.Check(info.SnapName(), Equals, "test")
|
|
c.Check(info.Revision, Equals, snap.R(42))
|
|
c.Check(info.Summary(), Equals, "esummary")
|
|
c.Check(info.Size, Equals, int64(0))
|
|
}
|
|
|
|
// makeTestSnap here can also be used to produce broken snaps (differently from snaptest.MakeTestSnapWithFiles)!
|
|
func makeTestSnap(c *C, snapYaml string) string {
|
|
var m struct {
|
|
Type string `yaml:"type"`
|
|
}
|
|
yaml.Unmarshal([]byte(snapYaml), &m) // yes, ignore the error
|
|
|
|
tmp := c.MkDir()
|
|
snapSource := filepath.Join(tmp, "snapsrc")
|
|
|
|
err := os.MkdirAll(filepath.Join(snapSource, "meta"), 0755)
|
|
c.Assert(err, IsNil)
|
|
|
|
// our regular snap.yaml
|
|
err = os.WriteFile(filepath.Join(snapSource, "meta", "snap.yaml"), []byte(snapYaml), 0644)
|
|
c.Assert(err, IsNil)
|
|
|
|
dest := filepath.Join(tmp, "foo.snap")
|
|
snap := squashfs.New(dest)
|
|
err = snap.Build(snapSource, &squashfs.BuildOpts{SnapType: m.Type})
|
|
c.Assert(err, IsNil)
|
|
|
|
return dest
|
|
}
|
|
|
|
// produce descrs for empty hooks suitable for snaptest.PopulateDir
|
|
func emptyHooks(hookNames ...string) (emptyHooks [][]string) {
|
|
for _, hookName := range hookNames {
|
|
emptyHooks = append(emptyHooks, []string{filepath.Join("meta", "hooks", hookName), ""})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFile(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
epoch: 1*
|
|
confinement: devmode`
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, nil)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
info, err := snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.InstanceName(), Equals, "foo")
|
|
c.Check(info.Version, Equals, "1.0")
|
|
c.Check(info.Type(), Equals, snap.TypeApp)
|
|
c.Check(info.Revision, Equals, snap.R(0))
|
|
c.Check(info.Epoch.String(), Equals, "1*")
|
|
c.Check(info.Confinement, Equals, snap.DevModeConfinement)
|
|
c.Check(info.NeedsDevMode(), Equals, true)
|
|
c.Check(info.NeedsClassic(), Equals, false)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromClassicSnapFile(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
confinement: classic`
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, nil)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
info, err := snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.InstanceName(), Equals, "foo")
|
|
c.Check(info.Version, Equals, "1.0")
|
|
c.Check(info.Type(), Equals, snap.TypeApp)
|
|
c.Check(info.Revision, Equals, snap.R(0))
|
|
c.Check(info.Confinement, Equals, snap.ClassicConfinement)
|
|
c.Check(info.NeedsDevMode(), Equals, false)
|
|
c.Check(info.NeedsClassic(), Equals, true)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileMissingEpoch(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app`
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, nil)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
info, err := snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.InstanceName(), Equals, "foo")
|
|
c.Check(info.Version, Equals, "1.0")
|
|
c.Check(info.Type(), Equals, snap.TypeApp)
|
|
c.Check(info.Revision, Equals, snap.R(0))
|
|
c.Check(info.Epoch.String(), Equals, "0") // Defaults to 0
|
|
c.Check(info.Confinement, Equals, snap.StrictConfinement)
|
|
c.Check(info.NeedsDevMode(), Equals, false)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileWithSideInfo(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app`
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, nil)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
info, err := snap.ReadInfoFromSnapFile(snapf, &snap.SideInfo{
|
|
RealName: "baz",
|
|
Revision: snap.R(42),
|
|
})
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.InstanceName(), Equals, "baz")
|
|
c.Check(info.Version, Equals, "1.0")
|
|
c.Check(info.Type(), Equals, snap.TypeApp)
|
|
c.Check(info.Revision, Equals, snap.R(42))
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileValidates(c *C) {
|
|
yaml := `name: foo.bar
|
|
version: 1.0
|
|
type: app`
|
|
snapPath := makeTestSnap(c, yaml)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, `invalid snap name.*`)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidType(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: foo`
|
|
snapPath := makeTestSnap(c, yaml)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, ".*invalid snap type.*")
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidConfinement(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
confinement: foo`
|
|
snapPath := makeTestSnap(c, yaml)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, ".*invalid confinement type.*")
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileChatchesInvalidSnapshot(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app`
|
|
contents := [][]string{
|
|
{"meta/snapshots.yaml", "Oops! This is not really valid yaml :-("},
|
|
}
|
|
sideInfo := &snap.SideInfo{}
|
|
snapInfo := snaptest.MockSnapWithFiles(c, yaml, sideInfo, contents)
|
|
|
|
snapf, err := snapfile.Open(snapInfo.MountDir())
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, "cannot read snapshot manifest: yaml: unmarshal errors:\n.*")
|
|
}
|
|
|
|
func (s *infoSuite) TestAppEnvSimple(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
environment:
|
|
global-k: global-v
|
|
apps:
|
|
foo:
|
|
environment:
|
|
app-k: app-v
|
|
`
|
|
info, err := snap.InfoFromSnapYaml([]byte(yaml))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Apps["foo"].EnvChain(), DeepEquals, []osutil.ExpandableEnv{
|
|
osutil.NewExpandableEnv("global-k", "global-v"),
|
|
osutil.NewExpandableEnv("app-k", "app-v"),
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestAppEnvOverrideGlobal(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
environment:
|
|
global-k: global-v
|
|
global-and-local: global-v
|
|
apps:
|
|
foo:
|
|
environment:
|
|
app-k: app-v
|
|
global-and-local: local-v
|
|
`
|
|
info, err := snap.InfoFromSnapYaml([]byte(yaml))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Apps["foo"].EnvChain(), DeepEquals, []osutil.ExpandableEnv{
|
|
osutil.NewExpandableEnv("global-k", "global-v", "global-and-local", "global-v"),
|
|
osutil.NewExpandableEnv("app-k", "app-v", "global-and-local", "local-v"),
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestHookEnvSimple(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
environment:
|
|
global-k: global-v
|
|
hooks:
|
|
foo:
|
|
environment:
|
|
app-k: app-v
|
|
`
|
|
info, err := snap.InfoFromSnapYaml([]byte(yaml))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Hooks["foo"].EnvChain(), DeepEquals, []osutil.ExpandableEnv{
|
|
osutil.NewExpandableEnv("global-k", "global-v"),
|
|
osutil.NewExpandableEnv("app-k", "app-v"),
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestHookEnvOverrideGlobal(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
type: app
|
|
environment:
|
|
global-k: global-v
|
|
global-and-local: global-v
|
|
hooks:
|
|
foo:
|
|
environment:
|
|
app-k: app-v
|
|
global-and-local: local-v
|
|
`
|
|
info, err := snap.InfoFromSnapYaml([]byte(yaml))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(info.Hooks["foo"].EnvChain(), DeepEquals, []osutil.ExpandableEnv{
|
|
osutil.NewExpandableEnv("global-k", "global-v", "global-and-local", "global-v"),
|
|
osutil.NewExpandableEnv("app-k", "app-v", "global-and-local", "local-v"),
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestSplitSnapApp(c *C) {
|
|
for _, t := range []struct {
|
|
in string
|
|
out []string
|
|
}{
|
|
// normal cases
|
|
{"foo.bar", []string{"foo", "bar"}},
|
|
{"foo.bar.baz", []string{"foo", "bar.baz"}},
|
|
// special case, snapName == appName
|
|
{"foo", []string{"foo", "foo"}},
|
|
// snap instance names
|
|
{"foo_instance.bar", []string{"foo_instance", "bar"}},
|
|
{"foo_instance.bar.baz", []string{"foo_instance", "bar.baz"}},
|
|
{"foo_instance", []string{"foo_instance", "foo"}},
|
|
} {
|
|
snap, app := snap.SplitSnapApp(t.in)
|
|
c.Check([]string{snap, app}, DeepEquals, t.out)
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestJoinSnapApp(c *C) {
|
|
for _, t := range []struct {
|
|
in []string
|
|
out string
|
|
}{
|
|
// normal cases
|
|
{[]string{"foo", "bar"}, "foo.bar"},
|
|
{[]string{"foo", "bar-baz"}, "foo.bar-baz"},
|
|
// special case, snapName == appName
|
|
{[]string{"foo", "foo"}, "foo"},
|
|
// snap instance names
|
|
{[]string{"foo_instance", "bar"}, "foo_instance.bar"},
|
|
{[]string{"foo_instance", "bar-baz"}, "foo_instance.bar-baz"},
|
|
{[]string{"foo_instance", "foo"}, "foo_instance"},
|
|
} {
|
|
snapApp := snap.JoinSnapApp(t.in[0], t.in[1])
|
|
c.Check(snapApp, Equals, t.out)
|
|
}
|
|
}
|
|
|
|
func ExampleSplitSnapApp() {
|
|
fmt.Println(snap.SplitSnapApp("hello-world.env"))
|
|
// Output: hello-world env
|
|
}
|
|
|
|
func ExampleSplitSnapApp_short() {
|
|
fmt.Println(snap.SplitSnapApp("hello-world"))
|
|
// Output: hello-world hello-world
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidHook(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
hooks:
|
|
123abc:`
|
|
snapPath := makeTestSnap(c, yaml)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, ".*invalid hook name.*")
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidImplicitHook(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
|
|
contents := [][]string{
|
|
{"meta/hooks/123abc", ""},
|
|
}
|
|
sideInfo := &snap.SideInfo{}
|
|
snapInfo := snaptest.MockSnapWithFiles(c, yaml, sideInfo, contents)
|
|
snapf, err := snapfile.Open(snapInfo.MountDir())
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, ".*invalid hook name.*")
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoFromSnapFileCatchesImplicitHookDefaultConfigureOnly(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
|
|
contents := [][]string{
|
|
{"meta/hooks/default-configure", ""},
|
|
}
|
|
sideInfo := &snap.SideInfo{}
|
|
snapInfo := snaptest.MockSnapWithFiles(c, yaml, sideInfo, contents)
|
|
snapf, err := snapfile.Open(snapInfo.MountDir())
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, ErrorMatches, "cannot specify \"default-configure\" hook without \"configure\" hook")
|
|
}
|
|
|
|
func (s *infoSuite) checkInstalledSnapAndSnapFile(c *C, instanceName, yaml string, contents string, hooks []string, checker func(c *C, info *snap.Info)) {
|
|
// First check installed snap
|
|
sideInfo := &snap.SideInfo{Revision: snap.R(42)}
|
|
info0 := snaptest.MockSnapInstance(c, instanceName, yaml, sideInfo)
|
|
snaptest.PopulateDir(info0.MountDir(), emptyHooks(hooks...))
|
|
info, err := snap.ReadInfo(info0.InstanceName(), sideInfo)
|
|
c.Check(err, IsNil)
|
|
checker(c, info)
|
|
|
|
// Now check snap file
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, yaml, emptyHooks(hooks...))
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
info, err = snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Check(err, IsNil)
|
|
checker(c, info)
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoNoHooks(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", nil, func(c *C, info *snap.Info) {
|
|
// Verify that no hooks were loaded for this snap
|
|
c.Check(info.Hooks, HasLen, 0)
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoSingleImplicitHook(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"test-hook"}, func(c *C, info *snap.Info) {
|
|
// Verify that the `test-hook` hook has now been loaded, and that it has
|
|
// no associated plugs.
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
verifyImplicitHook(c, info, "test-hook", nil)
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoMultipleImplicitHooks(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"foo", "bar"}, func(c *C, info *snap.Info) {
|
|
// Verify that both hooks have now been loaded, and that neither have any
|
|
// associated plugs.
|
|
c.Check(info.Hooks, HasLen, 2)
|
|
verifyImplicitHook(c, info, "foo", nil)
|
|
verifyImplicitHook(c, info, "bar", nil)
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoInvalidImplicitHook(c *C) {
|
|
hookType := snap.NewHookType(regexp.MustCompile("foo"))
|
|
s.BaseTest.AddCleanup(snap.MockSupportedHookTypes([]*snap.HookType{hookType}))
|
|
|
|
yaml := `name: foo
|
|
version: 1.0`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"foo", "bar"}, func(c *C, info *snap.Info) {
|
|
// Verify that only foo has been loaded, not bar
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
verifyImplicitHook(c, info, "foo", nil)
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoImplicitAndExplicitHooks(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
hooks:
|
|
explicit:
|
|
plugs: [test-plug]
|
|
slots: [test-slot]`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"explicit", "implicit"}, func(c *C, info *snap.Info) {
|
|
// Verify that the `implicit` hook has now been loaded, and that it has
|
|
// no associated plugs. Also verify that the `explicit` hook is still
|
|
// valid.
|
|
c.Check(info.Hooks, HasLen, 2)
|
|
verifyImplicitHook(c, info, "implicit", nil)
|
|
verifyExplicitHook(c, info, "explicit", []string{"test-plug"}, []string{"test-slot"})
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoExplicitHooks(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
slots:
|
|
test-slot:
|
|
hooks:
|
|
explicit:
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"explicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
verifyExplicitHook(c, info, "explicit", []string{"test-plug"}, []string{"test-slot"})
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoImplicitHookPlugWhenImplicitlyBoundToApp(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
apps:
|
|
app:
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"implicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
verifyImplicitHook(c, info, "implicit", []string{"test-plug"})
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoImplicitHookPlugWhenExplicitlyBoundToApp(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
apps:
|
|
app:
|
|
plugs: [test-plug]
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo", yaml, "SNAP", []string{"implicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
verifyImplicitHook(c, info, "implicit", nil)
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestParallelInstanceReadInfoImplicitAndExplicitHooks(c *C) {
|
|
yaml := `name: foo
|
|
version: 1.0
|
|
hooks:
|
|
explicit:
|
|
plugs: [test-plug]
|
|
slots: [test-slot]`
|
|
s.checkInstalledSnapAndSnapFile(c, "foo_instance", yaml, "SNAP", []string{"explicit", "implicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 2)
|
|
verifyImplicitHook(c, info, "implicit", nil)
|
|
verifyExplicitHook(c, info, "explicit", []string{"test-plug"}, []string{"test-slot"})
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestReadInfoImplicitHookWithTopLevelPlugSlots(c *C) {
|
|
yaml1 := `name: snap-1
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
slots:
|
|
test-slot:
|
|
hooks:
|
|
explicit:
|
|
plugs: [test-plug,other-plug]
|
|
slots: [test-slot,other-slot]
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "snap-1", yaml1, "SNAP", []string{"implicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 2)
|
|
implicitHook := info.Hooks["implicit"]
|
|
c.Assert(implicitHook, NotNil)
|
|
c.Assert(implicitHook.Explicit, Equals, false)
|
|
c.Assert(implicitHook.Plugs, HasLen, 0)
|
|
c.Assert(implicitHook.Slots, HasLen, 0)
|
|
|
|
c.Check(info.Plugs, HasLen, 2)
|
|
c.Check(info.Slots, HasLen, 2)
|
|
|
|
plug := info.Plugs["test-plug"]
|
|
c.Assert(plug, NotNil)
|
|
// implicit hook has not gained test-plug because it was already
|
|
// associated with an app or a hook (here with the hook called
|
|
// "explicit"). This is consistent with the hook called "implicit"
|
|
// having been defined in the YAML but devoid of any interface
|
|
// assignments.
|
|
c.Assert(implicitHook.Plugs["test-plug"], IsNil)
|
|
|
|
slot := info.Slots["test-slot"]
|
|
c.Assert(slot, NotNil)
|
|
c.Assert(implicitHook.Slots["test-slot"], IsNil)
|
|
|
|
explicitHook := info.Hooks["explicit"]
|
|
c.Assert(explicitHook, NotNil)
|
|
c.Assert(explicitHook.Explicit, Equals, true)
|
|
c.Assert(explicitHook.Plugs, HasLen, 2)
|
|
c.Assert(explicitHook.Slots, HasLen, 2)
|
|
|
|
plug = info.Plugs["test-plug"]
|
|
c.Assert(plug, NotNil)
|
|
c.Assert(explicitHook.Plugs["test-plug"], DeepEquals, plug)
|
|
|
|
slot = info.Slots["test-slot"]
|
|
c.Assert(slot, NotNil)
|
|
c.Assert(explicitHook.Slots["test-slot"], DeepEquals, slot)
|
|
})
|
|
|
|
yaml2 := `name: snap-2
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
slots:
|
|
test-slot:
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "snap-2", yaml2, "SNAP", []string{"implicit"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 1)
|
|
implicitHook := info.Hooks["implicit"]
|
|
c.Assert(implicitHook, NotNil)
|
|
c.Assert(implicitHook.Explicit, Equals, false)
|
|
c.Assert(implicitHook.Plugs, HasLen, 1)
|
|
c.Assert(implicitHook.Slots, HasLen, 1)
|
|
|
|
c.Check(info.Plugs, HasLen, 1)
|
|
c.Check(info.Slots, HasLen, 1)
|
|
|
|
plug := info.Plugs["test-plug"]
|
|
c.Assert(plug, NotNil)
|
|
c.Assert(implicitHook.Plugs["test-plug"], DeepEquals, plug)
|
|
|
|
slot := info.Slots["test-slot"]
|
|
c.Assert(slot, NotNil)
|
|
c.Assert(implicitHook.Slots["test-slot"], DeepEquals, slot)
|
|
})
|
|
|
|
yaml3 := `name: snap-3
|
|
version: 1.0
|
|
plugs:
|
|
test-plug:
|
|
slots:
|
|
test-slot:
|
|
`
|
|
s.checkInstalledSnapAndSnapFile(c, "snap-3", yaml3, "SNAP", []string{"implicit-1", "implicit-2"}, func(c *C, info *snap.Info) {
|
|
c.Check(info.Hooks, HasLen, 2)
|
|
implicit1Hook := info.Hooks["implicit-1"]
|
|
c.Assert(implicit1Hook, NotNil)
|
|
c.Assert(implicit1Hook.Explicit, Equals, false)
|
|
c.Assert(implicit1Hook.Plugs, HasLen, 1)
|
|
c.Assert(implicit1Hook.Slots, HasLen, 1)
|
|
|
|
implicit2Hook := info.Hooks["implicit-2"]
|
|
c.Assert(implicit2Hook, NotNil)
|
|
c.Assert(implicit2Hook.Explicit, Equals, false)
|
|
c.Assert(implicit2Hook.Plugs, HasLen, 1)
|
|
c.Assert(implicit2Hook.Slots, HasLen, 1)
|
|
|
|
c.Check(info.Plugs, HasLen, 1)
|
|
c.Check(info.Slots, HasLen, 1)
|
|
|
|
plug := info.Plugs["test-plug"]
|
|
c.Assert(plug, NotNil)
|
|
c.Assert(implicit1Hook.Plugs["test-plug"], DeepEquals, plug)
|
|
c.Assert(implicit2Hook.Plugs["test-plug"], DeepEquals, plug)
|
|
|
|
slot := info.Slots["test-slot"]
|
|
c.Assert(slot, NotNil)
|
|
c.Assert(implicit1Hook.Slots["test-slot"], DeepEquals, slot)
|
|
c.Assert(implicit2Hook.Slots["test-slot"], DeepEquals, slot)
|
|
})
|
|
|
|
}
|
|
|
|
func verifyImplicitHook(c *C, info *snap.Info, hookName string, plugNames []string) {
|
|
hook := info.Hooks[hookName]
|
|
c.Assert(hook, NotNil, Commentf("Expected hooks to contain %q", hookName))
|
|
c.Check(hook.Name, Equals, hookName)
|
|
|
|
if len(plugNames) == 0 {
|
|
c.Check(hook.Plugs, IsNil)
|
|
}
|
|
|
|
for _, plugName := range plugNames {
|
|
// Verify that the HookInfo and PlugInfo point to each other
|
|
plug := hook.Plugs[plugName]
|
|
c.Assert(plug, NotNil, Commentf("Expected hook plugs to contain %q", plugName))
|
|
c.Check(plug.Name, Equals, plugName)
|
|
c.Check(plug.Hooks, HasLen, 1)
|
|
hook = plug.Hooks[hookName]
|
|
c.Assert(hook, NotNil, Commentf("Expected plug to be associated with hook %q", hookName))
|
|
c.Check(hook.Name, Equals, hookName)
|
|
|
|
// Verify also that the hook plug made it into info.Plugs
|
|
c.Check(info.Plugs[plugName], DeepEquals, plug)
|
|
}
|
|
}
|
|
|
|
func verifyExplicitHook(c *C, info *snap.Info, hookName string, plugNames []string, slotNames []string) {
|
|
hook := info.Hooks[hookName]
|
|
c.Assert(hook, NotNil, Commentf("Expected hooks to contain %q", hookName))
|
|
c.Check(hook.Name, Equals, hookName)
|
|
c.Check(hook.Plugs, HasLen, len(plugNames))
|
|
c.Check(hook.Slots, HasLen, len(slotNames))
|
|
|
|
for _, plugName := range plugNames {
|
|
// Verify that the HookInfo and PlugInfo point to each other
|
|
plug := hook.Plugs[plugName]
|
|
c.Assert(plug, NotNil, Commentf("Expected hook plugs to contain %q", plugName))
|
|
c.Check(plug.Name, Equals, plugName)
|
|
c.Check(plug.Hooks, HasLen, 1)
|
|
hook = plug.Hooks[hookName]
|
|
c.Assert(hook, NotNil, Commentf("Expected plug to be associated with hook %q", hookName))
|
|
c.Check(hook.Name, Equals, hookName)
|
|
|
|
// Verify also that the hook plug made it into info.Plugs
|
|
c.Check(info.Plugs[plugName], DeepEquals, plug)
|
|
}
|
|
|
|
for _, slotName := range slotNames {
|
|
// Verify that the HookInfo and SlotInfo point to each other
|
|
slot := hook.Slots[slotName]
|
|
c.Assert(slot, NotNil, Commentf("Expected hook slots to contain %q", slotName))
|
|
c.Check(slot.Name, Equals, slotName)
|
|
c.Check(slot.Hooks, HasLen, 1)
|
|
hook = slot.Hooks[hookName]
|
|
c.Assert(hook, NotNil, Commentf("Expected slot to be associated with hook %q", hookName))
|
|
c.Check(hook.Name, Equals, hookName)
|
|
|
|
// Verify also that the hook plug made it into info.Slots
|
|
c.Check(info.Slots[slotName], DeepEquals, slot)
|
|
}
|
|
|
|
}
|
|
|
|
func (s *infoSuite) TestPlaceInfoRevision(c *C) {
|
|
info := snap.MinimalPlaceInfo("name", snap.R("1"))
|
|
c.Check(info.SnapRevision(), Equals, snap.R("1"))
|
|
}
|
|
|
|
func (s *infoSuite) TestMinimalInfoDirAndFileMethods(c *C) {
|
|
dirs.SetRootDir("")
|
|
info := snap.MinimalPlaceInfo("name", snap.R("1"))
|
|
s.testDirAndFileMethods(c, info)
|
|
}
|
|
|
|
func (s *infoSuite) TestDirAndFileMethods(c *C) {
|
|
dirs.SetRootDir("")
|
|
info := &snap.Info{SuggestedName: "name"}
|
|
info.SideInfo = snap.SideInfo{Revision: snap.R(1)}
|
|
s.testDirAndFileMethods(c, info)
|
|
}
|
|
|
|
func (s *infoSuite) testDirAndFileMethods(c *C, info snap.PlaceInfo) {
|
|
c.Check(info.MountDir(), Equals, fmt.Sprintf("%s/name/1", dirs.SnapMountDir))
|
|
c.Check(info.MountFile(), Equals, "/var/lib/snapd/snaps/name_1.snap")
|
|
c.Check(info.HooksDir(), Equals, fmt.Sprintf("%s/name/1/meta/hooks", dirs.SnapMountDir))
|
|
c.Check(info.DataDir(), Equals, "/var/snap/name/1")
|
|
c.Check(info.UserDataDir("/home/bob", nil), Equals, "/home/bob/snap/name/1")
|
|
c.Check(info.UserCommonDataDir("/home/bob", nil), Equals, "/home/bob/snap/name/common")
|
|
c.Check(info.CommonDataDir(), Equals, "/var/snap/name/common")
|
|
c.Check(info.CommonDataSaveDir(), Equals, "/var/lib/snapd/save/snap/name")
|
|
c.Check(info.UserXdgRuntimeDir(12345), Equals, "/run/user/12345/snap.name")
|
|
// XXX: Those are actually a globs, not directories
|
|
c.Check(info.DataHomeDir(nil), Equals, "/home/*/snap/name/1")
|
|
c.Check(info.CommonDataHomeDir(nil), Equals, "/home/*/snap/name/common")
|
|
c.Check(info.XdgRuntimeDirs(), Equals, "/run/user/*/snap.name")
|
|
c.Check(info.BinaryNameGlobs(), DeepEquals, []string{"name", "name.*"})
|
|
}
|
|
|
|
func (s *infoSuite) TestMinimalInfoDirAndFileMethodsParallelInstall(c *C) {
|
|
dirs.SetRootDir("")
|
|
info := snap.MinimalPlaceInfo("name_instance", snap.R("1"))
|
|
s.testInstanceDirAndFileMethods(c, info)
|
|
}
|
|
|
|
func (s *infoSuite) TestDirAndFileMethodsParallelInstall(c *C) {
|
|
dirs.SetRootDir("")
|
|
info := &snap.Info{SuggestedName: "name", InstanceKey: "instance"}
|
|
info.SideInfo = snap.SideInfo{Revision: snap.R(1)}
|
|
s.testInstanceDirAndFileMethods(c, info)
|
|
}
|
|
|
|
func (s *infoSuite) testInstanceDirAndFileMethods(c *C, info snap.PlaceInfo) {
|
|
c.Check(info.MountDir(), Equals, fmt.Sprintf("%s/name_instance/1", dirs.SnapMountDir))
|
|
c.Check(info.MountFile(), Equals, "/var/lib/snapd/snaps/name_instance_1.snap")
|
|
c.Check(info.HooksDir(), Equals, fmt.Sprintf("%s/name_instance/1/meta/hooks", dirs.SnapMountDir))
|
|
c.Check(info.DataDir(), Equals, "/var/snap/name_instance/1")
|
|
c.Check(info.UserDataDir("/home/bob", nil), Equals, "/home/bob/snap/name_instance/1")
|
|
c.Check(info.UserCommonDataDir("/home/bob", nil), Equals, "/home/bob/snap/name_instance/common")
|
|
c.Check(info.CommonDataDir(), Equals, "/var/snap/name_instance/common")
|
|
c.Check(info.CommonDataSaveDir(), Equals, "/var/lib/snapd/save/snap/name_instance")
|
|
c.Check(info.UserXdgRuntimeDir(12345), Equals, "/run/user/12345/snap.name_instance")
|
|
// XXX: Those are actually a globs, not directories
|
|
c.Check(info.DataHomeDir(nil), Equals, "/home/*/snap/name_instance/1")
|
|
c.Check(info.CommonDataHomeDir(nil), Equals, "/home/*/snap/name_instance/common")
|
|
c.Check(info.XdgRuntimeDirs(), Equals, "/run/user/*/snap.name_instance")
|
|
c.Check(info.BinaryNameGlobs(), DeepEquals, []string{"name_instance", "name_instance.*"})
|
|
}
|
|
|
|
func BenchmarkTestParsePlaceInfoFromSnapFileName(b *testing.B) {
|
|
for n := 0; n < b.N; n++ {
|
|
for _, sn := range []string{
|
|
"core_21.snap",
|
|
"kernel_41.snap",
|
|
"some-long-kernel-name-kernel_82.snap",
|
|
"what-is-this-core_111.snap",
|
|
} {
|
|
snap.ParsePlaceInfoFromSnapFileName(sn)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestParsePlaceInfoFromSnapFileName(c *C) {
|
|
tt := []struct {
|
|
sn string
|
|
name string
|
|
rev string
|
|
expectErr string
|
|
}{
|
|
{sn: "", expectErr: "empty snap file name"},
|
|
{sn: "name", expectErr: `snap file name "name" has invalid format \(missing '_'\)`},
|
|
{sn: "name_", expectErr: `cannot parse revision in snap file name "name_": invalid snap revision: ""`},
|
|
{sn: "name__", expectErr: "too many '_' in snap file name"},
|
|
{sn: "_name.snap", expectErr: `snap file name \"_name.snap\" has invalid format \(no snap name before '_'\)`},
|
|
{sn: "name_key.snap", expectErr: `cannot parse revision in snap file name "name_key.snap": invalid snap revision: "key"`},
|
|
{sn: "name.snap", expectErr: `snap file name "name.snap" has invalid format \(missing '_'\)`},
|
|
{sn: "name_12.snap", name: "name", rev: "12"},
|
|
{sn: "name_key_12.snap", expectErr: "too many '_' in snap file name"},
|
|
}
|
|
for _, t := range tt {
|
|
p, err := snap.ParsePlaceInfoFromSnapFileName(t.sn)
|
|
if t.expectErr != "" {
|
|
c.Check(err, ErrorMatches, t.expectErr)
|
|
} else {
|
|
c.Check(p.SnapName(), Equals, t.name)
|
|
c.Check(p.SnapRevision(), Equals, snap.R(t.rev))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestAppDesktopFile(c *C) {
|
|
snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{})
|
|
snapInfo, err := snap.ReadInfo("sample", &snap.SideInfo{})
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Check(snapInfo.InstanceName(), Equals, "sample")
|
|
c.Check(snapInfo.Apps["app"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample_app.desktop`)
|
|
c.Check(snapInfo.Apps["sample"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample_sample.desktop`)
|
|
c.Check(snapInfo.DesktopPrefix(), Equals, "sample")
|
|
|
|
// snap with instance key
|
|
snapInfo.InstanceKey = "instance"
|
|
c.Check(snapInfo.InstanceName(), Equals, "sample_instance")
|
|
c.Check(snapInfo.Apps["app"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample\+instance_app.desktop`)
|
|
c.Check(snapInfo.Apps["sample"].DesktopFile(), Matches, `.*/var/lib/snapd/desktop/applications/sample\+instance_sample.desktop`)
|
|
c.Check(snapInfo.DesktopPrefix(), Equals, "sample+instance")
|
|
}
|
|
|
|
const coreSnapYaml = `name: core
|
|
version: 0
|
|
type: os
|
|
plugs:
|
|
network-bind:
|
|
core-support:
|
|
`
|
|
|
|
// reading snap via ReadInfoFromSnapFile renames clashing core plugs
|
|
func (s *infoSuite) TestReadInfoFromSnapFileRenamesCorePlus(c *C) {
|
|
snapPath := snaptest.MakeTestSnapWithFiles(c, coreSnapYaml, nil)
|
|
|
|
snapf, err := snapfile.Open(snapPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
info, err := snap.ReadInfoFromSnapFile(snapf, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.Plugs["network-bind"], IsNil)
|
|
c.Check(info.Plugs["core-support"], IsNil)
|
|
c.Check(info.Plugs["network-bind-plug"], NotNil)
|
|
c.Check(info.Plugs["core-support-plug"], NotNil)
|
|
}
|
|
|
|
// reading snap via ReadInfo renames clashing core plugs
|
|
func (s *infoSuite) TestReadInfoRenamesCorePlugs(c *C) {
|
|
si := &snap.SideInfo{Revision: snap.R(42), RealName: "core"}
|
|
snaptest.MockSnap(c, coreSnapYaml, si)
|
|
info, err := snap.ReadInfo("core", si)
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.Plugs["network-bind"], IsNil)
|
|
c.Check(info.Plugs["core-support"], IsNil)
|
|
c.Check(info.Plugs["network-bind-plug"], NotNil)
|
|
c.Check(info.Plugs["core-support-plug"], NotNil)
|
|
}
|
|
|
|
// reading snap via InfoFromSnapYaml renames clashing core plugs
|
|
func (s *infoSuite) TestInfoFromSnapYamlRenamesCorePlugs(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(coreSnapYaml))
|
|
c.Assert(err, IsNil)
|
|
c.Check(info.Plugs["network-bind"], IsNil)
|
|
c.Check(info.Plugs["core-support"], IsNil)
|
|
c.Check(info.Plugs["network-bind-plug"], NotNil)
|
|
c.Check(info.Plugs["core-support-plug"], NotNil)
|
|
}
|
|
|
|
func (s *infoSuite) TestInfoServices(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: pans
|
|
apps:
|
|
svc1:
|
|
daemon: potato
|
|
svc2:
|
|
daemon: no
|
|
app1:
|
|
app2:
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
svcNames := []string{}
|
|
svcs := info.Services()
|
|
for i := range svcs {
|
|
svcNames = append(svcNames, svcs[i].ServiceName())
|
|
}
|
|
sort.Strings(svcNames)
|
|
c.Check(svcNames, DeepEquals, []string{
|
|
"snap.pans.svc1.service",
|
|
"snap.pans.svc2.service",
|
|
})
|
|
|
|
// snap with instance
|
|
info.InstanceKey = "instance"
|
|
svcNames = []string{}
|
|
for i := range info.Services() {
|
|
svcNames = append(svcNames, svcs[i].ServiceName())
|
|
}
|
|
sort.Strings(svcNames)
|
|
c.Check(svcNames, DeepEquals, []string{
|
|
"snap.pans_instance.svc1.service",
|
|
"snap.pans_instance.svc2.service",
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoIsService(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: pans
|
|
apps:
|
|
svc1:
|
|
daemon: potato
|
|
svc2:
|
|
daemon: no
|
|
svc3:
|
|
daemon: simple
|
|
daemon-scope: user
|
|
app1:
|
|
app2:
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
|
|
svc := info.Apps["svc1"]
|
|
c.Check(svc.IsService(), Equals, true)
|
|
c.Check(svc.DaemonScope, Equals, snap.SystemDaemon)
|
|
c.Check(svc.ServiceName(), Equals, "snap.pans.svc1.service")
|
|
c.Check(svc.ServiceFile(), Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans.svc1.service")
|
|
|
|
c.Check(info.Apps["svc2"].IsService(), Equals, true)
|
|
userSvc := info.Apps["svc3"]
|
|
c.Check(userSvc.IsService(), Equals, true)
|
|
c.Check(userSvc.DaemonScope, Equals, snap.UserDaemon)
|
|
c.Check(userSvc.ServiceName(), Equals, "snap.pans.svc3.service")
|
|
c.Check(userSvc.ServiceFile(), Equals, dirs.GlobalRootDir+"/etc/systemd/user/snap.pans.svc3.service")
|
|
c.Check(info.Apps["app1"].IsService(), Equals, false)
|
|
c.Check(info.Apps["app1"].IsService(), Equals, false)
|
|
|
|
// snap with instance key
|
|
info.InstanceKey = "instance"
|
|
c.Check(svc.ServiceName(), Equals, "snap.pans_instance.svc1.service")
|
|
c.Check(svc.ServiceFile(), Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans_instance.svc1.service")
|
|
c.Check(userSvc.ServiceName(), Equals, "snap.pans_instance.svc3.service")
|
|
c.Check(userSvc.ServiceFile(), Equals, dirs.GlobalRootDir+"/etc/systemd/user/snap.pans_instance.svc3.service")
|
|
}
|
|
|
|
func (s *infoSuite) TestAppInfoStringer(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: asnap
|
|
apps:
|
|
one:
|
|
daemon: simple
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmt.Sprintf("%q", info.Apps["one"].String()), Equals, `"asnap.one"`)
|
|
}
|
|
|
|
func (s *infoSuite) TestSocketFile(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: pans
|
|
apps:
|
|
app1:
|
|
daemon: true
|
|
sockets:
|
|
sock1:
|
|
listen-stream: /tmp/sock1.socket
|
|
`))
|
|
|
|
c.Assert(err, IsNil)
|
|
|
|
app := info.Apps["app1"]
|
|
socket := app.Sockets["sock1"]
|
|
c.Check(socket.File(), Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans.app1.sock1.socket")
|
|
|
|
// snap with instance key
|
|
info.InstanceKey = "instance"
|
|
c.Check(socket.File(), Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans_instance.app1.sock1.socket")
|
|
}
|
|
|
|
func (s *infoSuite) TestTimerFile(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: pans
|
|
apps:
|
|
app1:
|
|
daemon: true
|
|
timer: mon,10:00-12:00
|
|
`))
|
|
|
|
c.Assert(err, IsNil)
|
|
|
|
app := info.Apps["app1"]
|
|
timerFile := app.Timer.File()
|
|
c.Check(timerFile, Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans.app1.timer")
|
|
c.Check(strings.TrimSuffix(app.ServiceFile(), ".service")+".timer", Equals, timerFile)
|
|
|
|
// snap with instance key
|
|
info.InstanceKey = "instance"
|
|
c.Check(app.Timer.File(), Equals, dirs.GlobalRootDir+"/etc/systemd/system/snap.pans_instance.app1.timer")
|
|
}
|
|
|
|
func (s *infoSuite) TestLayoutParsing(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: layout-demo
|
|
layout:
|
|
/usr:
|
|
bind: $SNAP/usr
|
|
/mytmp:
|
|
type: tmpfs
|
|
mode: 1777
|
|
/mylink:
|
|
symlink: /link/target
|
|
`))
|
|
c.Assert(err, IsNil)
|
|
|
|
layout := info.Layout
|
|
c.Assert(layout, NotNil)
|
|
c.Check(layout["/usr"], DeepEquals, &snap.Layout{
|
|
Snap: info,
|
|
Path: "/usr",
|
|
User: "root",
|
|
Group: "root",
|
|
Mode: 0755,
|
|
Bind: "$SNAP/usr",
|
|
})
|
|
c.Check(layout["/mytmp"], DeepEquals, &snap.Layout{
|
|
Snap: info,
|
|
Path: "/mytmp",
|
|
Type: "tmpfs",
|
|
User: "root",
|
|
Group: "root",
|
|
Mode: 01777,
|
|
})
|
|
c.Check(layout["/mylink"], DeepEquals, &snap.Layout{
|
|
Snap: info,
|
|
Path: "/mylink",
|
|
User: "root",
|
|
Group: "root",
|
|
Mode: 0755,
|
|
Symlink: "/link/target",
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestPlugInfoString(c *C) {
|
|
plug := &snap.PlugInfo{Snap: &snap.Info{SuggestedName: "snap"}, Name: "plug"}
|
|
c.Assert(plug.String(), Equals, "snap:plug")
|
|
}
|
|
|
|
func (s *infoSuite) TestSlotInfoString(c *C) {
|
|
slot := &snap.SlotInfo{Snap: &snap.Info{SuggestedName: "snap"}, Name: "slot"}
|
|
c.Assert(slot.String(), Equals, "snap:slot")
|
|
}
|
|
|
|
func (s *infoSuite) TestPlugInfoAttr(c *C) {
|
|
var val string
|
|
var intVal int
|
|
|
|
plug := &snap.PlugInfo{Snap: &snap.Info{SuggestedName: "snap"}, Name: "plug", Interface: "interface", Attrs: map[string]interface{}{"key": "value", "number": int(123)}}
|
|
c.Assert(plug.Attr("key", &val), IsNil)
|
|
c.Check(val, Equals, "value")
|
|
|
|
c.Assert(plug.Attr("number", &intVal), IsNil)
|
|
c.Check(intVal, Equals, 123)
|
|
|
|
c.Check(plug.Attr("key", &intVal), ErrorMatches, `snap "snap" has interface "interface" with invalid value type string for "key" attribute: \*int`)
|
|
c.Check(plug.Attr("unknown", &val), ErrorMatches, `snap "snap" does not have attribute "unknown" for interface "interface"`)
|
|
c.Check(plug.Attr("key", intVal), ErrorMatches, `internal error: cannot get "key" attribute of interface "interface" with non-pointer value`)
|
|
}
|
|
|
|
func (s *infoSuite) TestSlotInfoAttr(c *C) {
|
|
var val string
|
|
var intVal int
|
|
|
|
slot := &snap.SlotInfo{Snap: &snap.Info{SuggestedName: "snap"}, Name: "plug", Interface: "interface", Attrs: map[string]interface{}{"key": "value", "number": int(123)}}
|
|
|
|
c.Assert(slot.Attr("key", &val), IsNil)
|
|
c.Check(val, Equals, "value")
|
|
|
|
c.Assert(slot.Attr("number", &intVal), IsNil)
|
|
c.Check(intVal, Equals, 123)
|
|
|
|
c.Check(slot.Attr("key", &intVal), ErrorMatches, `snap "snap" has interface "interface" with invalid value type string for "key" attribute: \*int`)
|
|
c.Check(slot.Attr("unknown", &val), ErrorMatches, `snap "snap" does not have attribute "unknown" for interface "interface"`)
|
|
c.Check(slot.Attr("key", intVal), ErrorMatches, `internal error: cannot get "key" attribute of interface "interface" with non-pointer value`)
|
|
}
|
|
|
|
func (s *infoSuite) TestDottedPathSlot(c *C) {
|
|
attrs := map[string]interface{}{
|
|
"nested": map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
}
|
|
|
|
slot := &snap.SlotInfo{Attrs: attrs}
|
|
c.Assert(slot, NotNil)
|
|
|
|
v, ok := slot.Lookup("nested.foo")
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(v, Equals, "bar")
|
|
|
|
v, ok = slot.Lookup("nested")
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(v, DeepEquals, map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
|
|
_, ok = slot.Lookup("x")
|
|
c.Assert(ok, Equals, false)
|
|
|
|
_, ok = slot.Lookup("..")
|
|
c.Assert(ok, Equals, false)
|
|
|
|
_, ok = slot.Lookup("nested.foo.x")
|
|
c.Assert(ok, Equals, false)
|
|
|
|
_, ok = slot.Lookup("nested.x")
|
|
c.Assert(ok, Equals, false)
|
|
}
|
|
|
|
func (s *infoSuite) TestDottedPathPlug(c *C) {
|
|
attrs := map[string]interface{}{
|
|
"nested": map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
}
|
|
|
|
plug := &snap.PlugInfo{Attrs: attrs}
|
|
c.Assert(plug, NotNil)
|
|
|
|
v, ok := plug.Lookup("nested")
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(v, DeepEquals, map[string]interface{}{
|
|
"foo": "bar",
|
|
})
|
|
|
|
v, ok = plug.Lookup("nested.foo")
|
|
c.Assert(ok, Equals, true)
|
|
c.Assert(v, Equals, "bar")
|
|
|
|
_, ok = plug.Lookup("x")
|
|
c.Assert(ok, Equals, false)
|
|
|
|
_, ok = plug.Lookup("..")
|
|
c.Assert(ok, Equals, false)
|
|
|
|
_, ok = plug.Lookup("nested.foo.x")
|
|
c.Assert(ok, Equals, false)
|
|
}
|
|
|
|
func (s *infoSuite) TestDefaultContentProviders(c *C) {
|
|
info, err := snap.InfoFromSnapYaml([]byte(yamlNeedDf))
|
|
c.Assert(err, IsNil)
|
|
|
|
plugs := make([]*snap.PlugInfo, 0, len(info.Plugs))
|
|
for _, plug := range info.Plugs {
|
|
plugs = append(plugs, plug)
|
|
}
|
|
|
|
dps := snap.DefaultContentProviders(plugs)
|
|
c.Check(dps, DeepEquals, map[string][]string{"gtk-common-themes": {"gtk-3-themes", "icon-themes"}})
|
|
}
|
|
|
|
func (s *infoSuite) TestExpandSnapVariables(c *C) {
|
|
dirs.SetRootDir("")
|
|
info, err := snap.InfoFromSnapYaml([]byte(`name: foo`))
|
|
c.Assert(err, IsNil)
|
|
info.Revision = snap.R(42)
|
|
c.Assert(info.ExpandSnapVariables("$SNAP/stuff"), Equals, "/snap/foo/42/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$SNAP_DATA/stuff"), Equals, "/var/snap/foo/42/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$SNAP_COMMON/stuff"), Equals, "/var/snap/foo/common/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$GARBAGE/rocks"), Equals, "/rocks")
|
|
|
|
info.InstanceKey = "instance"
|
|
// Despite setting the instance key the variables expand to the same
|
|
// value as before. This is because they are used from inside the mount
|
|
// namespace of the instantiated snap where the mount backend will
|
|
// ensure that the regular (non-instance) paths contain
|
|
// instance-specific code and data.
|
|
c.Assert(info.ExpandSnapVariables("$SNAP/stuff"), Equals, "/snap/foo/42/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$SNAP_DATA/stuff"), Equals, "/var/snap/foo/42/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$SNAP_COMMON/stuff"), Equals, "/var/snap/foo/common/stuff")
|
|
c.Assert(info.ExpandSnapVariables("$GARBAGE/rocks"), Equals, "/rocks")
|
|
}
|
|
|
|
func (s *infoSuite) TestStopModeTypeKillMode(c *C) {
|
|
for _, t := range []struct {
|
|
stopMode string
|
|
killall bool
|
|
}{
|
|
{"", true},
|
|
{"sigterm", false},
|
|
{"sigterm-all", true},
|
|
{"sighup", false},
|
|
{"sighup-all", true},
|
|
{"sigusr1", false},
|
|
{"sigusr1-all", true},
|
|
{"sigusr2", false},
|
|
{"sigusr2-all", true},
|
|
{"sigint", false},
|
|
{"sigint-all", true},
|
|
} {
|
|
c.Check(snap.StopModeType(t.stopMode).KillAll(), Equals, t.killall, Commentf("wrong KillAll for %v", t.stopMode))
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestStopModeTypeKillSignal(c *C) {
|
|
for _, t := range []struct {
|
|
stopMode string
|
|
killSig string
|
|
}{
|
|
{"", ""},
|
|
{"sigterm", "SIGTERM"},
|
|
{"sigterm-all", "SIGTERM"},
|
|
{"sighup", "SIGHUP"},
|
|
{"sighup-all", "SIGHUP"},
|
|
{"sigusr1", "SIGUSR1"},
|
|
{"sigusr1-all", "SIGUSR1"},
|
|
{"sigusr2", "SIGUSR2"},
|
|
{"sigusr2-all", "SIGUSR2"},
|
|
} {
|
|
c.Check(snap.StopModeType(t.stopMode).KillSignal(), Equals, t.killSig)
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestSplitInstanceName(c *C) {
|
|
snapName, instanceKey := snap.SplitInstanceName("foo_bar")
|
|
c.Check(snapName, Equals, "foo")
|
|
c.Check(instanceKey, Equals, "bar")
|
|
|
|
snapName, instanceKey = snap.SplitInstanceName("foo")
|
|
c.Check(snapName, Equals, "foo")
|
|
c.Check(instanceKey, Equals, "")
|
|
|
|
// all following instance names are invalid
|
|
|
|
snapName, instanceKey = snap.SplitInstanceName("_bar")
|
|
c.Check(snapName, Equals, "")
|
|
c.Check(instanceKey, Equals, "bar")
|
|
|
|
snapName, instanceKey = snap.SplitInstanceName("foo___bar_bar")
|
|
c.Check(snapName, Equals, "foo")
|
|
c.Check(instanceKey, Equals, "__bar_bar")
|
|
|
|
snapName, instanceKey = snap.SplitInstanceName("")
|
|
c.Check(snapName, Equals, "")
|
|
c.Check(instanceKey, Equals, "")
|
|
}
|
|
|
|
func (s *infoSuite) TestInstanceSnapName(c *C) {
|
|
c.Check(snap.InstanceSnap("foo_bar"), Equals, "foo")
|
|
c.Check(snap.InstanceSnap("foo"), Equals, "foo")
|
|
|
|
c.Check(snap.InstanceName("foo", "bar"), Equals, "foo_bar")
|
|
c.Check(snap.InstanceName("foo", ""), Equals, "foo")
|
|
}
|
|
|
|
func (s *infoSuite) TestInstanceNameInSnapInfo(c *C) {
|
|
info := &snap.Info{
|
|
SuggestedName: "snap-name",
|
|
InstanceKey: "foo",
|
|
}
|
|
|
|
c.Check(info.InstanceName(), Equals, "snap-name_foo")
|
|
c.Check(info.SnapName(), Equals, "snap-name")
|
|
|
|
info.InstanceKey = ""
|
|
c.Check(info.InstanceName(), Equals, "snap-name")
|
|
c.Check(info.SnapName(), Equals, "snap-name")
|
|
}
|
|
|
|
func (s *infoSuite) TestIsActive(c *C) {
|
|
info1 := snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{Revision: snap.R(1)})
|
|
info2 := snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{Revision: snap.R(2)})
|
|
// no current -> not active
|
|
c.Check(info1.IsActive(), Equals, false)
|
|
c.Check(info2.IsActive(), Equals, false)
|
|
|
|
mountdir := info1.MountDir()
|
|
dir, rev := filepath.Split(mountdir)
|
|
c.Assert(os.MkdirAll(dir, 0755), IsNil)
|
|
cur := filepath.Join(dir, "current")
|
|
c.Assert(os.Symlink(rev, cur), IsNil)
|
|
|
|
// is current -> is active
|
|
c.Check(info1.IsActive(), Equals, true)
|
|
c.Check(info2.IsActive(), Equals, false)
|
|
}
|
|
|
|
func (s *infoSuite) TestInfoTypeSnapdBackwardCompatibility(c *C) {
|
|
const snapdYaml = `
|
|
name: snapd
|
|
type: app
|
|
version: 1
|
|
`
|
|
snapInfo := snaptest.MockSnap(c, snapdYaml, &snap.SideInfo{Revision: snap.R(1), SnapID: "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"})
|
|
c.Check(snapInfo.Type(), Equals, snap.TypeSnapd)
|
|
}
|
|
|
|
func (s *infoSuite) TestDirAndFileHelpers(c *C) {
|
|
dirs.SetRootDir("")
|
|
|
|
c.Check(snap.MountDir("name", snap.R(1)), Equals, fmt.Sprintf("%s/name/1", dirs.SnapMountDir))
|
|
c.Check(snap.MountFile("name", snap.R(1)), Equals, "/var/lib/snapd/snaps/name_1.snap")
|
|
c.Check(snap.HooksDir("name", snap.R(1)), Equals, fmt.Sprintf("%s/name/1/meta/hooks", dirs.SnapMountDir))
|
|
c.Check(snap.DataDir("name", snap.R(1)), Equals, "/var/snap/name/1")
|
|
c.Check(snap.CommonDataDir("name"), Equals, "/var/snap/name/common")
|
|
c.Check(snap.CommonDataSaveDir("name"), Equals, "/var/lib/snapd/save/snap/name")
|
|
c.Check(snap.UserDataDir("/home/bob", "name", snap.R(1), nil), Equals, "/home/bob/snap/name/1")
|
|
c.Check(snap.UserCommonDataDir("/home/bob", "name", nil), Equals, "/home/bob/snap/name/common")
|
|
c.Check(snap.UserXdgRuntimeDir(12345, "name"), Equals, "/run/user/12345/snap.name")
|
|
c.Check(snap.UserSnapDir("/home/bob", "name", nil), Equals, "/home/bob/snap/name")
|
|
|
|
c.Check(snap.MountDir("name_instance", snap.R(1)), Equals, fmt.Sprintf("%s/name_instance/1", dirs.SnapMountDir))
|
|
c.Check(snap.MountFile("name_instance", snap.R(1)), Equals, "/var/lib/snapd/snaps/name_instance_1.snap")
|
|
c.Check(snap.HooksDir("name_instance", snap.R(1)), Equals, fmt.Sprintf("%s/name_instance/1/meta/hooks", dirs.SnapMountDir))
|
|
c.Check(snap.DataDir("name_instance", snap.R(1)), Equals, "/var/snap/name_instance/1")
|
|
c.Check(snap.CommonDataDir("name_instance"), Equals, "/var/snap/name_instance/common")
|
|
c.Check(snap.CommonDataSaveDir("name_instance"), Equals, "/var/lib/snapd/save/snap/name_instance")
|
|
c.Check(snap.UserDataDir("/home/bob", "name_instance", snap.R(1), nil), Equals, "/home/bob/snap/name_instance/1")
|
|
c.Check(snap.UserCommonDataDir("/home/bob", "name_instance", nil), Equals, "/home/bob/snap/name_instance/common")
|
|
c.Check(snap.UserXdgRuntimeDir(12345, "name_instance"), Equals, "/run/user/12345/snap.name_instance")
|
|
c.Check(snap.UserSnapDir("/home/bob", "name_instance", nil), Equals, "/home/bob/snap/name_instance")
|
|
}
|
|
|
|
func (s *infoSuite) TestSortByType(c *C) {
|
|
infos := []*snap.Info{
|
|
{SuggestedName: "app1", SnapType: "app"},
|
|
{SuggestedName: "os1", SnapType: "os"},
|
|
{SuggestedName: "base1", SnapType: "base"},
|
|
{SuggestedName: "gadget1", SnapType: "gadget"},
|
|
{SuggestedName: "kernel1", SnapType: "kernel"},
|
|
{SuggestedName: "app2", SnapType: "app"},
|
|
{SuggestedName: "os2", SnapType: "os"},
|
|
{SuggestedName: "snapd", SnapType: "snapd"},
|
|
{SuggestedName: "base2", SnapType: "base"},
|
|
{SuggestedName: "gadget2", SnapType: "gadget"},
|
|
{SuggestedName: "kernel2", SnapType: "kernel"},
|
|
}
|
|
sort.Stable(snap.ByType(infos))
|
|
|
|
c.Check(infos, DeepEquals, []*snap.Info{
|
|
{SuggestedName: "snapd", SnapType: "snapd"},
|
|
{SuggestedName: "os1", SnapType: "os"},
|
|
{SuggestedName: "os2", SnapType: "os"},
|
|
{SuggestedName: "kernel1", SnapType: "kernel"},
|
|
{SuggestedName: "kernel2", SnapType: "kernel"},
|
|
{SuggestedName: "base1", SnapType: "base"},
|
|
{SuggestedName: "base2", SnapType: "base"},
|
|
{SuggestedName: "gadget1", SnapType: "gadget"},
|
|
{SuggestedName: "gadget2", SnapType: "gadget"},
|
|
{SuggestedName: "app1", SnapType: "app"},
|
|
{SuggestedName: "app2", SnapType: "app"},
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestSortByTypeAgain(c *C) {
|
|
core := &snap.Info{SnapType: snap.TypeOS}
|
|
base := &snap.Info{SnapType: snap.TypeBase}
|
|
app := &snap.Info{SnapType: snap.TypeApp}
|
|
snapd := &snap.Info{}
|
|
snapd.SideInfo = snap.SideInfo{RealName: "snapd"}
|
|
|
|
byType := func(snaps ...*snap.Info) []*snap.Info {
|
|
sort.Stable(snap.ByType(snaps))
|
|
return snaps
|
|
}
|
|
|
|
c.Check(byType(base, core), DeepEquals, []*snap.Info{core, base})
|
|
c.Check(byType(app, core), DeepEquals, []*snap.Info{core, app})
|
|
c.Check(byType(app, base), DeepEquals, []*snap.Info{base, app})
|
|
c.Check(byType(app, base, core), DeepEquals, []*snap.Info{core, base, app})
|
|
c.Check(byType(app, core, base), DeepEquals, []*snap.Info{core, base, app})
|
|
|
|
c.Check(byType(app, core, base, snapd), DeepEquals, []*snap.Info{snapd, core, base, app})
|
|
c.Check(byType(app, snapd, core, base), DeepEquals, []*snap.Info{snapd, core, base, app})
|
|
}
|
|
|
|
func (s *infoSuite) TestMedia(c *C) {
|
|
c.Check(snap.MediaInfos{}.IconURL(), Equals, "")
|
|
|
|
media := snap.MediaInfos{
|
|
{
|
|
Type: "screenshot",
|
|
URL: "https://example.com/shot1.svg",
|
|
}, {
|
|
Type: "icon",
|
|
URL: "https://example.com/icon.png",
|
|
}, {
|
|
Type: "screenshot",
|
|
URL: "https://example.com/shot2.svg",
|
|
Width: 42,
|
|
Height: 17,
|
|
},
|
|
}
|
|
|
|
c.Check(media.IconURL(), Equals, "https://example.com/icon.png")
|
|
}
|
|
|
|
func (s *infoSuite) TestSortApps(c *C) {
|
|
tcs := []struct {
|
|
err string
|
|
apps []*snap.AppInfo
|
|
sorted []string
|
|
}{{
|
|
apps: []*snap.AppInfo{
|
|
{Name: "bar", Before: []string{"baz"}},
|
|
{Name: "foo"},
|
|
},
|
|
sorted: []string{"bar", "foo"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "bar", Before: []string{"foo"}},
|
|
{Name: "foo", Before: []string{"baz"}},
|
|
},
|
|
sorted: []string{"bar", "foo"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "bar", Before: []string{"foo"}},
|
|
},
|
|
sorted: []string{"bar"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "bar", After: []string{"foo"}},
|
|
},
|
|
sorted: []string{"bar"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "bar", Before: []string{"baz"}},
|
|
{Name: "baz", After: []string{"bar", "foo"}},
|
|
{Name: "foo"},
|
|
},
|
|
sorted: []string{"bar", "foo", "baz"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "foo", After: []string{"bar", "zed"}},
|
|
{Name: "bar", Before: []string{"foo"}},
|
|
{Name: "baz", After: []string{"foo"}},
|
|
{Name: "zed"},
|
|
},
|
|
sorted: []string{"bar", "zed", "foo", "baz"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "foo", After: []string{"baz"}},
|
|
{Name: "bar", Before: []string{"baz"}},
|
|
{Name: "baz"},
|
|
{Name: "zed", After: []string{"foo", "bar", "baz"}},
|
|
},
|
|
sorted: []string{"bar", "baz", "foo", "zed"},
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "foo", Before: []string{"bar"}, After: []string{"zed"}},
|
|
{Name: "bar", Before: []string{"baz"}},
|
|
{Name: "baz", Before: []string{"zed"}},
|
|
{Name: "zed"},
|
|
},
|
|
err: `applications are part of a before/after cycle: ((foo|bar|baz|zed)(, )?){4}`,
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "foo", Before: []string{"bar"}},
|
|
{Name: "bar", Before: []string{"foo"}},
|
|
{Name: "baz", Before: []string{"foo"}, After: []string{"bar"}},
|
|
},
|
|
err: `applications are part of a before/after cycle: ((foo|bar|baz)(, )?){3}`,
|
|
}, {
|
|
apps: []*snap.AppInfo{
|
|
{Name: "baz", After: []string{"bar"}},
|
|
{Name: "foo"},
|
|
{Name: "bar", After: []string{"foo"}},
|
|
},
|
|
sorted: []string{"foo", "bar", "baz"},
|
|
}}
|
|
for _, tc := range tcs {
|
|
sorted, err := snap.SortServices(tc.apps)
|
|
if tc.err != "" {
|
|
c.Assert(err, ErrorMatches, tc.err)
|
|
} else {
|
|
c.Assert(err, IsNil)
|
|
c.Assert(sorted, HasLen, len(tc.sorted))
|
|
sortedNames := make([]string, len(sorted))
|
|
for i, app := range sorted {
|
|
sortedNames[i] = app.Name
|
|
}
|
|
c.Assert(sortedNames, DeepEquals, tc.sorted)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestSortAppInfoBySnapApp(c *C) {
|
|
snap1 := &snap.Info{SuggestedName: "snapa"}
|
|
snap2 := &snap.Info{SuggestedName: "snapb"}
|
|
infos := []*snap.AppInfo{
|
|
{Snap: snap1, Name: "b"},
|
|
{Snap: snap2, Name: "b"},
|
|
{Snap: snap1, Name: "a"},
|
|
{Snap: snap2, Name: "a"},
|
|
}
|
|
sort.Stable(snap.AppInfoBySnapApp(infos))
|
|
|
|
c.Check(infos, DeepEquals, []*snap.AppInfo{
|
|
{Snap: snap1, Name: "a"},
|
|
{Snap: snap1, Name: "b"},
|
|
{Snap: snap2, Name: "a"},
|
|
{Snap: snap2, Name: "b"},
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestHelpersWithHiddenSnapFolder(c *C) {
|
|
dirs.SetRootDir("")
|
|
opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true}
|
|
|
|
c.Check(snap.UserDataDir("/home/bob", "name", snap.R(1), opts), Equals, "/home/bob/.snap/data/name/1")
|
|
c.Check(snap.UserCommonDataDir("/home/bob", "name", opts), Equals, "/home/bob/.snap/data/name/common")
|
|
c.Check(snap.UserSnapDir("/home/bob", "name", opts), Equals, "/home/bob/.snap/data/name")
|
|
c.Check(snap.SnapDir("/home/bob", opts), Equals, "/home/bob/.snap/data")
|
|
|
|
c.Check(snap.UserDataDir("/home/bob", "name_instance", snap.R(1), opts), Equals, "/home/bob/.snap/data/name_instance/1")
|
|
c.Check(snap.UserCommonDataDir("/home/bob", "name_instance", opts), Equals, "/home/bob/.snap/data/name_instance/common")
|
|
c.Check(snap.UserSnapDir("/home/bob", "name_instance", opts), Equals, "/home/bob/.snap/data/name_instance")
|
|
}
|
|
|
|
func (s *infoSuite) TestGetAttributeUnhappy(c *C) {
|
|
attrs := map[string]interface{}{}
|
|
var stringVal string
|
|
err := snap.GetAttribute("snap0", "iface0", attrs, "non-existent", &stringVal)
|
|
c.Check(stringVal, Equals, "")
|
|
c.Check(err, ErrorMatches, `snap "snap0" does not have attribute "non-existent" for interface "iface0"`)
|
|
c.Check(errors.Is(err, snap.AttributeNotFoundError{}), Equals, true)
|
|
}
|
|
|
|
func (s *infoSuite) TestGetAttributeHappy(c *C) {
|
|
attrs := map[string]interface{}{
|
|
"attr0": "a string",
|
|
"attr1": 12,
|
|
}
|
|
var intVal int
|
|
err := snap.GetAttribute("snap0", "iface0", attrs, "attr1", &intVal)
|
|
c.Check(err, IsNil)
|
|
c.Check(intVal, Equals, 12)
|
|
}
|
|
|
|
func (s *infoSuite) TestSnapdAssertionMaxFormatsFromSnapFileFromSnapd(c *C) {
|
|
tests := []struct {
|
|
info string
|
|
snapDecl int
|
|
sysUser int
|
|
}{
|
|
{info: `VERSION=2.58
|
|
SNAPD_ASSERTS_FORMATS='{"snap-declaration":5,"system-user":2}'`, snapDecl: 5, sysUser: 2},
|
|
{info: `VERSION=2.56
|
|
SNAPD_ASSERTS_FORMATS='{"snap-declaration":5,"system-user":1}'`, snapDecl: 5, sysUser: 1},
|
|
{info: `VERSION=2.55`, snapDecl: 5, sysUser: 1},
|
|
{info: `VERSION=2.54`, snapDecl: 5, sysUser: 1},
|
|
{info: `VERSION=2.47`, snapDecl: 4, sysUser: 1},
|
|
{info: `VERSION=2.46`, snapDecl: 4, sysUser: 1},
|
|
{info: `VERSION=2.45`, snapDecl: 4},
|
|
{info: `VERSION=2.44`, snapDecl: 4},
|
|
{info: `VERSION=2.36`, snapDecl: 3},
|
|
// old
|
|
{info: `VERSION=2.23`, snapDecl: 2},
|
|
// ancient
|
|
{info: `VERSION=2.17`, snapDecl: 1},
|
|
{info: `VERSION=2.16`},
|
|
}
|
|
for _, t := range tests {
|
|
snapdPath := snaptest.MakeTestSnapWithFiles(c, `name: snapd
|
|
type: snapd
|
|
version: 1.0`, [][]string{{
|
|
"/usr/lib/snapd/info", t.info}})
|
|
snapf, err := snapfile.Open(snapdPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
maxFormats, ver, err := snap.SnapdAssertionMaxFormatsFromSnapFile(snapf)
|
|
c.Assert(err, IsNil)
|
|
expectedMaxFormats := map[string]int{}
|
|
if t.sysUser > 0 {
|
|
expectedMaxFormats["system-user"] = t.sysUser
|
|
}
|
|
if t.snapDecl > 0 {
|
|
expectedMaxFormats["snap-declaration"] = t.snapDecl
|
|
}
|
|
c.Check(maxFormats, DeepEquals, expectedMaxFormats)
|
|
c.Check(strings.HasPrefix(t.info, fmt.Sprintf("VERSION=%s", ver)), Equals, true)
|
|
}
|
|
}
|
|
|
|
func (s *infoSuite) TestSnapdAssertionMaxFormatsFromSnapFileFromCore(c *C) {
|
|
corePath := snaptest.MakeTestSnapWithFiles(c, `name: core
|
|
type: os
|
|
version: 1.0`, [][]string{{
|
|
"/usr/lib/snapd/info", `VERSION=2.47`}})
|
|
snapf, err := snapfile.Open(corePath)
|
|
c.Assert(err, IsNil)
|
|
|
|
maxFormats, ver, err := snap.SnapdAssertionMaxFormatsFromSnapFile(snapf)
|
|
c.Assert(err, IsNil)
|
|
c.Check(ver, Equals, "2.47")
|
|
c.Check(maxFormats, DeepEquals, map[string]int{
|
|
"snap-declaration": 4,
|
|
"system-user": 1,
|
|
})
|
|
}
|
|
|
|
func (s *infoSuite) TestSnapdAssertionMaxFormatsFromSnapFileFromKernel(c *C) {
|
|
krnlPath := snaptest.MakeTestSnapWithFiles(c, `name: krnl
|
|
type: kernel
|
|
version: 1.0`, [][]string{{
|
|
"/snapd-info", `VERSION=2.56
|
|
SNAPD_ASSERTS_FORMATS='{"snap-declaration":5,"system-user":1}'`}})
|
|
snapf, err := snapfile.Open(krnlPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
maxFormats, ver, err := snap.SnapdAssertionMaxFormatsFromSnapFile(snapf)
|
|
c.Assert(err, IsNil)
|
|
c.Check(ver, Equals, "2.56")
|
|
c.Check(maxFormats, DeepEquals, map[string]int{
|
|
"snap-declaration": 5,
|
|
"system-user": 1,
|
|
})
|
|
|
|
// no snadd-info
|
|
krnlPath = snaptest.MakeTestSnapWithFiles(c, `name: krnl
|
|
type: kernel
|
|
version: 1.0`, nil)
|
|
snapf, err = snapfile.Open(krnlPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
maxFormats, ver, err = snap.SnapdAssertionMaxFormatsFromSnapFile(snapf)
|
|
c.Assert(err, IsNil)
|
|
c.Check(ver, Equals, "")
|
|
c.Check(maxFormats, IsNil)
|
|
}
|
|
|
|
func (s *infoSuite) TestSnapdAssertionMaxFormatsFromSnapFileFromOther(c *C) {
|
|
appPath := snaptest.MakeTestSnapWithFiles(c, `name: app
|
|
version: 1.0`, nil)
|
|
snapf, err := snapfile.Open(appPath)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, _, err = snap.SnapdAssertionMaxFormatsFromSnapFile(snapf)
|
|
c.Check(err, ErrorMatches, `cannot extract assertion max formats information, snaps of type app do not carry snapd`)
|
|
}
|