summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2016-09-19 17:47:45 +0200
committerMichael Vogt <mvo@ubuntu.com>2016-09-19 17:47:45 +0200
commit3c05581f715089518873c12939d84bf92e97d8e3 (patch)
tree0571917fc8c82efb9158e64a019fbccaa1f7bc16
parent0acd25e7826936e84b6809bb454004c6919f5148 (diff)
parent86d128fd04c1b40a805f88d24c8bda39ff28848c (diff)
Merge remote-tracking branch 'upstream/master' into bugfix/https-proxy
-rw-r--r--HACKING.md164
-rw-r--r--README.md159
-rw-r--r--asserts/account_key.go47
-rw-r--r--asserts/account_key_test.go24
-rw-r--r--asserts/asserts.go16
-rw-r--r--asserts/asserts_test.go35
-rw-r--r--asserts/fetcher.go25
-rw-r--r--asserts/snap_asserts.go11
-rw-r--r--asserts/snap_asserts_test.go29
-rw-r--r--asserts/snapasserts/snapasserts.go21
-rw-r--r--cmd/snap/cmd_download.go2
-rw-r--r--daemon/api.go12
-rw-r--r--daemon/api_test.go60
-rw-r--r--daemon/snap.go2
-rw-r--r--debian/changelog133
-rw-r--r--docs/hooks.md31
-rw-r--r--docs/interfaces.md2
-rw-r--r--image/helpers.go15
-rw-r--r--image/image.go32
-rw-r--r--image/image_test.go73
-rw-r--r--interfaces/apparmor/template.go1
-rw-r--r--interfaces/builtin/libvirt.go3
-rw-r--r--interfaces/builtin/libvirt_test.go4
-rw-r--r--interfaces/builtin/pulseaudio.go2
-rw-r--r--interfaces/repo.go34
-rw-r--r--interfaces/repo_test.go82
-rw-r--r--overlord/assertstate/assertmgr.go122
-rw-r--r--overlord/assertstate/assertmgr_test.go120
-rw-r--r--overlord/assertstate/export_test.go2
-rw-r--r--overlord/hookstate/hookmgr.go5
-rw-r--r--overlord/patch/patch4.go12
-rw-r--r--overlord/patch/patch4_test.go119
-rw-r--r--overlord/snapstate/backend/mountunit.go7
-rw-r--r--tests/lib/fakestore/refresh/refresh.go10
-rw-r--r--tests/lib/store.sh2
-rw-r--r--tests/main/prepare-image-grub/task.yaml3
-rw-r--r--tests/main/refresh-devmode/task.yaml2
-rw-r--r--tests/main/refresh/task.yaml2
-rw-r--r--tests/main/revert-devmode/task.yaml2
-rw-r--r--tests/main/revert/task.yaml2
40 files changed, 1094 insertions, 335 deletions
diff --git a/HACKING.md b/HACKING.md
new file mode 100644
index 0000000000..7947f12e8b
--- /dev/null
+++ b/HACKING.md
@@ -0,0 +1,164 @@
+# Hacking on snapd
+
+Hacking on snapd is fun and straightfoward. The code is extensively
+unit tested and we use the [spread](https://github.com/snapcore/spread)
+integration test framework for the integration/system level tests.
+
+## Development
+
+### Setting up a GOPATH
+
+When working with the source of Go programs, you should define a path within
+your home directory (or other workspace) which will be your `GOPATH`. `GOPATH`
+is similar to Java's `CLASSPATH` or Python's `~/.local`. `GOPATH` is documented
+[online](http://golang.org/pkg/go/build/) and inside the go tool itself
+
+ go help gopath
+
+Various conventions exist for naming the location of your `GOPATH`, but it
+should exist, and be writable by you. For example
+
+ export GOPATH=${HOME}/work
+ mkdir $GOPATH
+
+will define and create `$HOME/work` as your local `GOPATH`. The `go` tool
+itself will create three subdirectories inside your `GOPATH` when required;
+`src`, `pkg` and `bin`, which hold the source of Go programs, compiled packages
+and compiled binaries, respectively.
+
+Setting `GOPATH` correctly is critical when developing Go programs. Set and
+export it as part of your login script.
+
+Add `$GOPATH/bin` to your `PATH`, so you can run the go programs you install:
+
+ PATH="$PATH:$GOPATH/bin"
+
+### Getting the snapd sources
+
+The easiest way to get the source for `snapd` is to use the `go get` command.
+
+ go get -d -v github.com/snapcore/snapd/...
+
+This command will checkout the source of `snapd` and inspect it for any unmet
+Go package dependencies, downloading those as well. `go get` will also build
+and install `snapd` and its dependencies. To also build and install `snapd`
+itself into `$GOPATH/bin`, omit the `-d` flag. More details on the `go get`
+flags are available using
+
+ go help get
+
+At this point you will have the git local repository of the `snapd` source at
+`$GOPATH/github.com/snapcore/snapd`. The source for any
+dependent packages will also be available inside `$GOPATH`.
+
+### Dependencies handling
+
+To generate dependencies.tsv you need `godeps`, so
+
+ go get launchpad.net/godeps
+
+To obtain the correct dependencies for the project, run (from the
+projects root directory):
+
+ godeps -t -u dependencies.tsv
+
+You can use the script `get-deps.sh` to run the two previous steps.
+
+If the dependencies need updating, run (from the projects root directory):
+
+ godeps -t ./... > dependencies.tsv
+
+### Building
+
+To build, once the sources are available and `GOPATH` is set, you can just run
+
+ go build -o /tmp/snap github.com/snapcore/snapd/cmd/snap
+
+to get the `snap` binary in /tmp (or without -o to get it in the current
+working directory). Alternatively:
+
+ go install github.com/snapcore/snapd/...
+
+to have it available in `$GOPATH/bin`
+
+### Contributing
+
+Contributions are always welcome! Please make sure that you sign the
+Canonical contributor licence agreement at
+http://www.ubuntu.com/legal/contributors
+
+Snapd can be found on Github, so in order to fork the source and contribute,
+go to https://github.com/snapcore/snapd. Check out [Github's help
+pages](https://help.github.com/) to find out how to set up your local branch,
+commit changes and create pull requests.
+
+We value good tests, so when you fix a bug or add a new feature we highly
+encourage you to create a test in `$source_test.go`. See also the section
+about Testing.
+
+### Testing
+
+To run the various tests that we have to ensure a high quality source just run:
+
+ ./run-checks
+
+This will check if the source format is consistent, that it builds, all tests
+work as expected and that "go vet" has nothing to complain.
+
+You can run individual test for a sub-package by changing into that directory and:
+
+ go test -check.f $testname
+
+If a test hangs, you can enable verbose mode:
+
+ go test -v -check.vv
+
+(or -check.v for less verbose output).
+
+There is more to read about the testing framework on the [website](https://labix.org/gocheck)
+
+### Running the spread tests
+
+To run the spread tests locally you need the latest version of spread
+from https://github.com/snapcore/spread. It can be installed via:
+
+ $ sudo apt install qemu-kvm autopkgtest
+ $ sudo snap install --devmode spread
+
+Then setup the environment via:
+
+ $ mkdir -p .spread/qemu
+ $ cd .spread/qemu
+ $ adt-buildvm-ubuntu-cloud
+ $ mv adt-xenial-amd64-cloud.img ubuntu-16.04.img
+
+And you can run the tests via:
+
+ $ spread -v qemu:
+
+For quick reuse you can use:
+
+ $ spread -keep qemu:
+
+It will print how to reuse the systems. Make sure to use
+`export REUSE_PROJECT=1` in your environment too.
+
+
+### Testing snapd
+
+To test the `snapd` REST API daemon on a snappy system you need to
+transfer it to the snappy system and then run:
+
+ sudo systemctl stop snapd.service snapd.socket
+ sudo /lib/systemd/systemd-activate -E SNAPD_DEBUG=1 -E SNAP_REEXEC=0 -E SNAPD_DEBUG_HTTP3 -l /run/snapd.socket -l /run/snapd-snap.socket ./snapd
+ or with systemd version >= 230
+ sudo systemd-socket-activate -E SNAPD_DEBUG=1 -E SNAP_REEXEC=0 -E SNAPD_DEBUG_HTTP3 -l /run/snapd.socket -l /run/snapd-snap.socket ./snapd
+
+This will stop the installed snapd and activate the new one. Once it's
+printed out something like `Listening on /run/snapd.socket as 3.` you
+should then
+
+ sudo chmod 0666 /run/snapd*.socket
+
+so the socket has the right permissions (otherwise you need `sudo` to
+connect).
diff --git a/README.md b/README.md
index 50a14c905f..3b3a1ab119 100644
--- a/README.md
+++ b/README.md
@@ -1,162 +1,15 @@
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url]
# snapd
-The snapd and snap tools enable systems to work with .snap files.
+The snapd and snap tools enable systems to work with .snap files. See
+[snapcraft.io](http://snapcraft.io) for a high level overview about
+snap files and the snapd application.
## Development
-### Setting up a GOPATH
-
-When working with the source of Go programs, you should define a path within
-your home directory (or other workspace) which will be your `GOPATH`. `GOPATH`
-is similar to Java's `CLASSPATH` or Python's `~/.local`. `GOPATH` is documented
-[http://golang.org/pkg/go/build/](online) and inside the go tool itself
-
- go help gopath
-
-Various conventions exist for naming the location of your `GOPATH`, but it
-should exist, and be writable by you. For example
-
- export GOPATH=${HOME}/work
- mkdir $GOPATH
-
-will define and create `$HOME/work` as your local `GOPATH`. The `go` tool
-itself will create three subdirectories inside your `GOPATH` when required;
-`src`, `pkg` and `bin`, which hold the source of Go programs, compiled packages
-and compiled binaries, respectively.
-
-Setting `GOPATH` correctly is critical when developing Go programs. Set and
-export it as part of your login script.
-
-Add `$GOPATH/bin` to your `PATH`, so you can run the go programs you install:
-
- PATH="$PATH:$GOPATH/bin"
-
-### Getting the snappy sources
-
-The easiest way to get the source for `snappy` is to use the `go get` command.
-
- go get -d -v github.com/snapcore/snapd/...
-
-This command will checkout the source of `snappy` and inspect it for any unmet
-Go package dependencies, downloading those as well. `go get` will also build
-and install `snappy` and its dependencies. To checkout without installing, use
-the `-d` flag. More details on the `go get` flags are available using
-
- go help get
-
-At this point you will have the git local repository of the `snappy` source at
-`$GOPATH/github.com/snapcore/snapd`. The source for any
-dependent packages will also be available inside `$GOPATH`.
-
-### Dependencies handling
-
-To generate dependencies.tsv you need `godeps`, so
-
- go get launchpad.net/godeps
-
-To obtain the correct dependencies for the project, run:
-
- godeps -t -u dependencies.tsv
-
-You can use the script `get-deps.sh` to run the two previous steps.
-
-If the dependencies need updating
-
- godeps -t ./... > dependencies.tsv
-
-### Building
-
-To build, once the sources are available and `GOPATH` is set, you can just run
-
- go build -o /tmp/snap github.com/snapcore/snapd/cmd/snap
-
-to get the `snap` binary in /tmp (or without -o to get it in the current
-working directory). Alternatively:
-
- go install github.com/snapcore/snapd/...
-
-to have it available in `$GOPATH/bin`
-
-### Contributing
-
-Contributions are always welcome! Please make sure that you sign the
-Canonical contributor licence agreement at
-http://www.ubuntu.com/legal/contributors
-
-Snappy can be found on Github, so in order to fork the source and contribute,
-go to https://github.com/snapcore/snapd. Check out [Github's help
-pages](https://help.github.com/) to find out how to set up your local branch,
-commit changes and create pull requests.
-
-We value good tests, so when you fix a bug or add a new feature we highly
-encourage you to create a test in $source_testing.go. See also the section
-about Testing.
-
-### Testing
-
-To run the various tests that we have to ensure a high quality source just run:
-
- ./run-checks
-
-This will check if the source format is consistent, that it build, all tests
-work as expected and that "go vet" and "golint" have nothing to complain.
-
-You can run individual test for a sub-package by changing into that directory and:
-
- go test -check.f $testname
-
-If a test hangs, you can enable verbose mode:
-
- go test -v -check.vv
-
-(or -check.v for less verbose output).
-
-There is more to read about the testing framework on the [website](https://labix.org/gocheck)
-
-### Running the spread tests
-
-To run the spread tests locally you need the latest version of spread
-from https://github.com/snapcore/spread. It can be installed via:
-
- $ sudo apt install qemu-kvm autopkgtest
- $ sudo snap install spread
-
-Then setup the environment via:
-
- $ mkdir -p .spread/qemu
- $ cd .spread/qemu
- $ adt-buildvm-ubuntu-cloud
- $ mv adt-xenial-amd64-cloud.img ubuntu-16.04.img
-
-And you can run the tests via:
-
- $ spread -v qemu:
-
-For quick reuse you can use:
-
- $ spread -keep qemu:
-
-It will print how to reuse the systems. Make sure to use
-`export REUSE_PROJECT=1` in your environment too.
-
-
-### Testing snapd on a snappy system
-
-To test the `snapd` REST API daemon on a snappy system you need to
-transfer it to the snappy system and then run:
-
- sudo systemctl stop snapd.service snapd.socket
- sudo /lib/systemd/systemd-activate -E SNAPD_DEBUG=1 -l /run/snapd.socket -l /run/snapd-snap.socket ./snapd
-
-This will stop the installed snapd and activate the new one. Once it's
-printed out something like `Listening on /run/snapd.socket as 3.` you
-should then
-
- sudo chmod 0666 /run/snapd.socket
-
-so the socket has the right permissions (otherwise you need `sudo` to
-connect).
+To get started with development off the snapd code itself, please check
+out [HACKING.md](https://github.com/snapcore/snapd/blob/master/HACKING.md)
+for in-depth details.
## Reporting bugs
diff --git a/asserts/account_key.go b/asserts/account_key.go
index 8987331a23..3e01b4ad12 100644
--- a/asserts/account_key.go
+++ b/asserts/account_key.go
@@ -108,26 +108,27 @@ func (ak *AccountKey) checkConsistency(db RODatabase, acck *AccountKey) error {
if err != nil {
return err
}
-
- // Check that we don't end up with multiple keys with
- // different IDs but the same account-id and name.
- // Note that this is a non-transactional check-then-add, so
- // is not a hard guarantee. Backstores that can implement a
- // unique constraint should do so.
- assertions, err := db.FindMany(AccountKeyType, map[string]string{
- "account-id": ak.AccountID(),
- "name": ak.Name(),
- })
- if err != nil && err != ErrNotFound {
- return err
- }
- for _, assertion := range assertions {
- existingAccKey := assertion.(*AccountKey)
- if ak.PublicKeyID() != existingAccKey.PublicKeyID() {
- return fmt.Errorf("account-key assertion for %q with ID %q has the same name %q as existing ID %q", ak.AccountID(), ak.PublicKeyID(), ak.Name(), existingAccKey.PublicKeyID())
+ // XXX: Make this unconditional once account-key assertions are required to have a name.
+ if ak.Name() != "" {
+ // Check that we don't end up with multiple keys with
+ // different IDs but the same account-id and name.
+ // Note that this is a non-transactional check-then-add, so
+ // is not a hard guarantee. Backstores that can implement a
+ // unique constraint should do so.
+ assertions, err := db.FindMany(AccountKeyType, map[string]string{
+ "account-id": ak.AccountID(),
+ "name": ak.Name(),
+ })
+ if err != nil && err != ErrNotFound {
+ return err
+ }
+ for _, assertion := range assertions {
+ existingAccKey := assertion.(*AccountKey)
+ if ak.PublicKeyID() != existingAccKey.PublicKeyID() {
+ return fmt.Errorf("account-key assertion for %q with ID %q has the same name %q as existing ID %q", ak.AccountID(), ak.PublicKeyID(), ak.Name(), existingAccKey.PublicKeyID())
+ }
}
}
-
return nil
}
@@ -147,9 +148,13 @@ func assembleAccountKey(assert assertionBase) (Assertion, error) {
return nil, err
}
- _, err = checkStringMatches(assert.headers, "name", validAccountKeyName)
- if err != nil {
- return nil, err
+ // XXX: We should require name to be present after backfilling existing assertions.
+ _, ok := assert.headers["name"]
+ if ok {
+ _, err = checkStringMatches(assert.headers, "name", validAccountKeyName)
+ if err != nil {
+ return nil, err
+ }
}
since, err := checkRFC3339Date(assert.headers, "since")
diff --git a/asserts/account_key_test.go b/asserts/account_key_test.go
index 366bffcc52..37887ce183 100644
--- a/asserts/account_key_test.go
+++ b/asserts/account_key_test.go
@@ -85,6 +85,27 @@ func (aks *accountKeySuite) TestDecodeOK(c *C) {
c.Check(accKey.Since(), Equals, aks.since)
}
+func (aks *accountKeySuite) TestDecodeNoName(c *C) {
+ // XXX: remove this test once name is mandatory
+ encoded := "type: account-key\n" +
+ "authority-id: canonical\n" +
+ "account-id: acc-id1\n" +
+ "public-key-sha3-384: " + aks.keyID + "\n" +
+ aks.sinceLine +
+ fmt.Sprintf("body-length: %v", len(aks.pubKeyBody)) + "\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + "\n\n" +
+ aks.pubKeyBody + "\n\n" +
+ "AXNpZw=="
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.AccountKeyType)
+ accKey := a.(*asserts.AccountKey)
+ c.Check(accKey.AccountID(), Equals, "acc-id1")
+ c.Check(accKey.Name(), Equals, "")
+ c.Check(accKey.PublicKeyID(), Equals, aks.keyID)
+ c.Check(accKey.Since(), Equals, aks.since)
+}
+
func (aks *accountKeySuite) TestUntil(c *C) {
untilSinceLine := "until: " + aks.since.Format(time.RFC3339) + "\n"
@@ -143,7 +164,8 @@ func (aks *accountKeySuite) TestDecodeInvalidHeaders(c *C) {
invalidHeaderTests := []struct{ original, invalid, expectedErr string }{
{"account-id: acc-id1\n", "", `"account-id" header is mandatory`},
{"account-id: acc-id1\n", "account-id: \n", `"account-id" header should not be empty`},
- {"name: default\n", "", `"name" header is mandatory`},
+ // XXX: enable this once name is mandatory
+ // {"name: default\n", "", `"name" header is mandatory`},
{"name: default\n", "name: \n", `"name" header should not be empty`},
{"name: default\n", "name: a b\n", `"name" header contains invalid characters: "a b"`},
{"name: default\n", "name: -default\n", `"name" header contains invalid characters: "-default"`},
diff --git a/asserts/asserts.go b/asserts/asserts.go
index ddbee0bfb6..a1f9734533 100644
--- a/asserts/asserts.go
+++ b/asserts/asserts.go
@@ -99,7 +99,21 @@ type Ref struct {
}
func (ref *Ref) String() string {
- return fmt.Sprintf("%s %v", ref.Type.Name, ref.PrimaryKey)
+ pkStr := "-"
+ n := len(ref.Type.PrimaryKey)
+ if n != len(ref.PrimaryKey) {
+ pkStr = "???"
+ } else if n > 0 {
+ pkStr = ref.PrimaryKey[n-1]
+ if n > 1 {
+ sfx := []string{pkStr + ";"}
+ for i, k := range ref.Type.PrimaryKey[:n-1] {
+ sfx = append(sfx, fmt.Sprintf("%s:%s", k, ref.PrimaryKey[i]))
+ }
+ pkStr = strings.Join(sfx, " ")
+ }
+ }
+ return fmt.Sprintf("%s (%s)", ref.Type.Name, pkStr)
}
// Unique returns a unique string representing the reference that can be used as a key in maps.
diff --git a/asserts/asserts_test.go b/asserts/asserts_test.go
index f6d3acea33..619a613dd3 100644
--- a/asserts/asserts_test.go
+++ b/asserts/asserts_test.go
@@ -50,6 +50,41 @@ func (as *assertsSuite) TestRef(c *C) {
c.Check(ref.Unique(), Equals, "test-only-2/abc/xyz")
}
+func (as *assertsSuite) TestRefString(c *C) {
+ ref := &asserts.Ref{
+ Type: asserts.AccountType,
+ PrimaryKey: []string{"canonical"},
+ }
+
+ c.Check(ref.String(), Equals, "account (canonical)")
+
+ ref = &asserts.Ref{
+ Type: asserts.SnapDeclarationType,
+ PrimaryKey: []string{"18", "SNAPID"},
+ }
+
+ c.Check(ref.String(), Equals, "snap-declaration (SNAPID; series:18)")
+
+ ref = &asserts.Ref{
+ Type: asserts.ModelType,
+ PrimaryKey: []string{"18", "BRAND", "baz-3000"},
+ }
+
+ c.Check(ref.String(), Equals, "model (baz-3000; series:18 brand-id:BRAND)")
+
+ // broken primary key
+ ref = &asserts.Ref{
+ Type: asserts.ModelType,
+ PrimaryKey: []string{"18"},
+ }
+ c.Check(ref.String(), Equals, "model (???)")
+
+ ref = &asserts.Ref{
+ Type: asserts.TestOnlyNoAuthorityType,
+ }
+ c.Check(ref.String(), Equals, "test-only-no-authority (-)")
+}
+
func (as *assertsSuite) TestRefResolveError(c *C) {
ref := &asserts.Ref{
Type: asserts.TestOnly2Type,
diff --git a/asserts/fetcher.go b/asserts/fetcher.go
index a35a0d083c..cc3c93af77 100644
--- a/asserts/fetcher.go
+++ b/asserts/fetcher.go
@@ -31,8 +31,17 @@ const (
fetchSaved
)
-// Fetcher helps fetching assertions and their prerequisites.
-type Fetcher struct {
+// A Fetcher helps fetching assertions and their prerequisites.
+type Fetcher interface {
+ // Fetch retrieves the assertion indicated by ref then its prerequisites
+ // recursively, along the way saving prerequisites before dependent assertions.
+ Fetch(*Ref) error
+ // Save retrieves the prerequisites of the assertion recursively,
+ // along the way saving them, and finally saves the assertion.
+ Save(Assertion) error
+}
+
+type fetcher struct {
db RODatabase
retrieve func(*Ref) (Assertion, error)
save func(Assertion) error
@@ -41,8 +50,8 @@ type Fetcher struct {
}
// NewFetcher creates a Fetcher which will use trustedDB to determine trusted assertions, will fetch assertions following prerequisites using retrieve, and then will pass them to save, saving prerequisites before dependent assertions.
-func NewFetcher(trustedDB RODatabase, retrieve func(*Ref) (Assertion, error), save func(Assertion) error) *Fetcher {
- return &Fetcher{
+func NewFetcher(trustedDB RODatabase, retrieve func(*Ref) (Assertion, error), save func(Assertion) error) Fetcher {
+ return &fetcher{
db: trustedDB,
retrieve: retrieve,
save: save,
@@ -50,7 +59,7 @@ func NewFetcher(trustedDB RODatabase, retrieve func(*Ref) (Assertion, error), sa
}
}
-func (f *Fetcher) chase(ref *Ref, a Assertion) error {
+func (f *fetcher) chase(ref *Ref, a Assertion) error {
// check if ref points to a trusted assertion, in which case
// there is nothing to do
_, err := ref.Resolve(f.db.FindTrusted)
@@ -92,12 +101,12 @@ func (f *Fetcher) chase(ref *Ref, a Assertion) error {
// Fetch retrieves the assertion indicated by ref then its prerequisites
// recursively, along the way saving prerequisites before dependent assertions.
-func (f *Fetcher) Fetch(ref *Ref) error {
+func (f *fetcher) Fetch(ref *Ref) error {
return f.chase(ref, nil)
}
// fetchAccountKey behaves like Fetch for the account-key with the given key id.
-func (f *Fetcher) fetchAccountKey(keyID string) error {
+func (f *fetcher) fetchAccountKey(keyID string) error {
keyRef := &Ref{
Type: AccountKeyType,
PrimaryKey: []string{keyID},
@@ -107,6 +116,6 @@ func (f *Fetcher) fetchAccountKey(keyID string) error {
// Save retrieves the prerequisites of the assertion recursively,
// along the way saving them, and finally saves the assertion.
-func (f *Fetcher) Save(a Assertion) error {
+func (f *fetcher) Save(a Assertion) error {
return f.chase(a.Ref(), a)
}
diff --git a/asserts/snap_asserts.go b/asserts/snap_asserts.go
index f35342144b..083a422488 100644
--- a/asserts/snap_asserts.go
+++ b/asserts/snap_asserts.go
@@ -65,8 +65,8 @@ func (snapdcl *SnapDeclaration) Timestamp() time.Time {
}
// RefreshControl returns the ids of snaps whose updates are controlled by this declaration.
-func (mod *SnapDeclaration) RefreshControl() []string {
- return mod.refreshControl
+func (snapdcl *SnapDeclaration) RefreshControl() []string {
+ return snapdcl.refreshControl
}
// Implement further consistency checks.
@@ -383,7 +383,7 @@ func (validation *Validation) checkConsistency(db RODatabase, acck *AccountKey)
if err != nil {
return err
}
- _, err = db.Find(SnapDeclarationType, map[string]string{
+ a, err := db.Find(SnapDeclarationType, map[string]string{
"series": validation.Series(),
"snap-id": validation.SnapID(),
})
@@ -394,6 +394,11 @@ func (validation *Validation) checkConsistency(db RODatabase, acck *AccountKey)
return err
}
+ gatingDecl := a.(*SnapDeclaration)
+ if gatingDecl.PublisherID() != validation.AuthorityID() {
+ return fmt.Errorf("validation assertion by snap %q (id %q) not signed by its publisher", gatingDecl.SnapName(), validation.SnapID())
+ }
+
return nil
}
diff --git a/asserts/snap_asserts_test.go b/asserts/snap_asserts_test.go
index 48d9eeb065..7011836622 100644
--- a/asserts/snap_asserts_test.go
+++ b/asserts/snap_asserts_test.go
@@ -172,7 +172,6 @@ func (sds *snapDeclSuite) TestSnapDeclarationCheck(c *C) {
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": "dev-id1",
- "gates": "",
"timestamp": time.Now().Format(time.RFC3339),
}
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
@@ -192,7 +191,6 @@ func (sds *snapDeclSuite) TestSnapDeclarationCheckUntrustedAuthority(c *C) {
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": "dev-id1",
- "gates": "",
"timestamp": time.Now().Format(time.RFC3339),
}
snapDecl, err := otherDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
@@ -210,7 +208,6 @@ func (sds *snapDeclSuite) TestSnapDeclarationCheckMissingPublisherAccount(c *C)
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": "dev-id1",
- "gates": "",
"timestamp": time.Now().Format(time.RFC3339),
}
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
@@ -251,7 +248,6 @@ func (sds *snapDeclSuite) TestPrerequisites(c *C) {
"snap-id: snap-id-1\n" +
"snap-name: first\n" +
"publisher-id: dev-id1\n" +
- "gates: snap-id-3,snap-id-4\n" +
sds.tsLine +
"body-length: 0\n" +
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
@@ -365,7 +361,9 @@ func makeStoreAndCheckDB(c *C) (storeDB *assertstest.SigningDB, checkDB *asserts
func setup3rdPartySigning(c *C, username string, storeDB *assertstest.SigningDB, checkDB *asserts.Database) (signingDB *assertstest.SigningDB) {
privKey := testPrivKey2
- acct := assertstest.NewAccount(storeDB, username, nil, "")
+ acct := assertstest.NewAccount(storeDB, username, map[string]interface{}{
+ "account-id": username,
+ }, "")
accKey := assertstest.NewAccountKey(storeDB, acct, nil, privKey.PublicKey(), "")
err := checkDB.Add(acct)
@@ -381,7 +379,7 @@ func (sbs *snapBuildSuite) TestSnapBuildCheck(c *C) {
devDB := setup3rdPartySigning(c, "devel1", storeDB, db)
headers := map[string]interface{}{
- "authority-id": devDB.AuthorityID,
+ "authority-id": "devel1",
"snap-sha3-384": blobSHA3_384,
"snap-id": "snap-id-1",
"grade": "devel",
@@ -517,7 +515,6 @@ func prereqSnapDecl(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
"snap-id": "snap-id-1",
"snap-name": "foo",
"publisher-id": "dev-id1",
- "gates": "",
"timestamp": time.Now().Format(time.RFC3339),
}, nil, "")
c.Assert(err, IsNil)
@@ -716,7 +713,6 @@ func prereqSnapDecl2(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
"snap-id": "snap-id-2",
"snap-name": "bar",
"publisher-id": "dev-id1",
- "gates": "",
"timestamp": time.Now().Format(time.RFC3339),
}, nil, "")
c.Assert(err, IsNil)
@@ -726,6 +722,21 @@ func prereqSnapDecl2(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
func (vs *validationSuite) TestValidationCheck(c *C) {
storeDB, db := makeStoreAndCheckDB(c)
+ devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
+
+ prereqSnapDecl(c, storeDB, db)
+ prereqSnapDecl2(c, storeDB, db)
+
+ headers := vs.makeHeaders(nil)
+ validation, err := devDB.Sign(asserts.ValidationType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = db.Check(validation)
+ c.Assert(err, IsNil)
+}
+
+func (vs *validationSuite) TestValidationCheckWrongAuthority(c *C) {
+ storeDB, db := makeStoreAndCheckDB(c)
prereqDevAccount(c, storeDB, db)
prereqSnapDecl(c, storeDB, db)
@@ -736,7 +747,7 @@ func (vs *validationSuite) TestValidationCheck(c *C) {
c.Assert(err, IsNil)
err = db.Check(validation)
- c.Assert(err, IsNil)
+ c.Assert(err, ErrorMatches, `validation assertion by snap "foo" \(id "snap-id-1"\) not signed by its publisher`)
}
func (vs *validationSuite) TestRevocation(c *C) {
diff --git a/asserts/snapasserts/snapasserts.go b/asserts/snapasserts/snapasserts.go
index 9c6067b90f..eadd6f6a94 100644
--- a/asserts/snapasserts/snapasserts.go
+++ b/asserts/snapasserts/snapasserts.go
@@ -126,3 +126,24 @@ func DeriveSideInfo(snapPath string, db asserts.RODatabase) (*snap.SideInfo, err
Developer: devAcct.Username(),
}, nil
}
+
+// FetchSnapAssertions fetches the assertions matching the snap file digest using the given fetcher.
+func FetchSnapAssertions(f asserts.Fetcher, snapSHA3_384 string) error {
+ // for now starting from the snap-revision will get us all other relevant assertions
+ ref := &asserts.Ref{
+ Type: asserts.SnapRevisionType,
+ PrimaryKey: []string{snapSHA3_384},
+ }
+
+ return f.Fetch(ref)
+}
+
+// FetchSnapDeclaration fetches the snap declaration and its prerequisites for the given snap id using the given fetcher.
+func FetchSnapDeclaration(f asserts.Fetcher, snapID string) error {
+ ref := &asserts.Ref{
+ Type: asserts.SnapDeclarationType,
+ PrimaryKey: []string{release.Series, snapID},
+ }
+
+ return f.Fetch(ref)
+}
diff --git a/cmd/snap/cmd_download.go b/cmd/snap/cmd_download.go
index 07e5743f12..132e53c8cd 100644
--- a/cmd/snap/cmd_download.go
+++ b/cmd/snap/cmd_download.go
@@ -80,7 +80,7 @@ func fetchSnapAssertions(sto *store.Store, snapPath string, snapInfo *snap.Info,
}
f := image.StoreAssertionFetcher(sto, dlOpts, db, save)
- return image.FetchSnapAssertions(snapPath, snapInfo, f, db)
+ return image.FetchAndCheckSnapAssertions(snapPath, snapInfo, f, db)
}
func (x *cmdDownload) Execute(args []string) error {
diff --git a/daemon/api.go b/daemon/api.go
index ac4c187ae7..e04d4ac32f 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -660,6 +660,8 @@ var (
snapstateTryPath = snapstate.TryPath
snapstateUpdate = snapstate.Update
snapstateUpdateMany = snapstate.UpdateMany
+
+ assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
)
func ensureStateSoonImpl(st *state.State) {
@@ -731,6 +733,11 @@ func modeFlags(devMode, jailMode bool) (snapstate.Flags, error) {
}
func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated []string, tasksets []*state.TaskSet, err error) {
+ // we need refreshed snap-declarations to enforce refresh-control as best as we can, this also ensures that snap-declarations and their prerequisite assertions are updated regularly
+ if err := assertstateRefreshSnapDeclarations(st, inst.userID); err != nil {
+ return "", nil, nil, err
+ }
+
updated, tasksets, err = snapstateUpdateMany(st, inst.Snaps, inst.userID)
if err != nil {
return "", nil, nil, err
@@ -785,6 +792,11 @@ func snapUpdate(inst *snapInstruction, st *state.State) (string, []*state.TaskSe
return "", nil, err
}
+ // we need refreshed snap-declarations to enforce refresh-control as best as we can
+ if err = assertstateRefreshSnapDeclarations(st, inst.userID); err != nil {
+ return "", nil, err
+ }
+
ts, err := snapstateUpdate(st, inst.Snaps[0], inst.Channel, inst.Revision, inst.userID, flags)
if err != nil {
return "", nil, err
diff --git a/daemon/api_test.go b/daemon/api_test.go
index 0853d47969..adae8a4e7b 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -39,7 +39,6 @@ import (
"gopkg.in/check.v1"
"gopkg.in/macaroon.v1"
- tomb "gopkg.in/tomb.v2"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
@@ -49,7 +48,6 @@ import (
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
- "github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/ifacestate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
@@ -173,6 +171,7 @@ func (s *apiSuite) TearDownTest(c *check.C) {
snapstateInstall = snapstate.Install
snapstateGet = snapstate.Get
snapstateInstallPath = snapstate.InstallPath
+ assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
unsafeReadSnapInfo = unsafeReadSnapInfoImpl
ensureStateSoon = ensureStateSoonImpl
dirs.SetRootDir("")
@@ -397,6 +396,7 @@ func (s *apiSuite) TestListIncludesAll(c *check.C) {
"snapstateGet",
"snapstateUpdateMany",
"snapstateRefreshCandidates",
+ "assertstateRefreshSnapDeclarations",
"unsafeReadSnapInfo",
"osutilAddUser",
"storeUserInfo",
@@ -1766,19 +1766,9 @@ func (s *apiSuite) TestSetConf(c *check.C) {
d := s.daemon(c)
s.mockSnap(c, configYaml)
- oldHookRunner := hookstate.HookRunner
- defer func() { hookstate.HookRunner = oldHookRunner }()
-
// Mock the hook runner
- var hookSnap string
- var hookRevision snap.Revision
- var hookName string
- hookstate.HookRunner = func(snap string, revision snap.Revision, hook, _ string, _ *tomb.Tomb) ([]byte, error) {
- hookSnap = snap
- hookRevision = revision
- hookName = hook
- return nil, nil
- }
+ hookRunner := testutil.MockCommand(c, "snap", "")
+ defer hookRunner.Restore()
d.overlord.Loop()
defer d.overlord.Stop()
@@ -1815,9 +1805,9 @@ func (s *apiSuite) TestSetConf(c *check.C) {
c.Assert(err, check.IsNil)
// Check that the apply-config hook was run correctly
- c.Check(hookSnap, check.Equals, "config-snap")
- c.Check(hookRevision, check.Equals, snap.R(0))
- c.Check(hookName, check.Equals, "apply-config")
+ c.Check(hookRunner.Calls(), check.DeepEquals, [][]string{[]string{
+ "snap", "run", "--hook", "apply-config", "-r", "unset", "config-snap",
+ }})
}
func (s *apiSuite) TestAppIconGet(c *check.C) {
@@ -1897,6 +1887,12 @@ func (s *apiSuite) TestAppIconGetNoApp(c *check.C) {
c.Check(rec.Code, check.Equals, 404)
}
+func (s *apiSuite) TestNotInstalledSnapIcon(c *check.C) {
+ info := &snap.Info{SuggestedName: "notInstalledSnap", IconURL: "icon.svg"}
+ iconfile := snapIcon(info)
+ c.Check(iconfile, testutil.Contains, "icon.svg")
+}
+
func (s *apiSuite) TestInstallOnNonDevModeDistro(c *check.C) {
s.testInstall(c, &release.OS{ID: "ubuntu"}, snapstate.Flags(0), snap.R(0))
}
@@ -1969,6 +1965,7 @@ func (s *apiSuite) TestRefresh(c *check.C) {
calledFlags := snapstate.Flags(42)
calledUserID := 0
installQueue := []string{}
+ assertstateCalledUserID := 0
snapstateGet = func(s *state.State, name string, snapst *snapstate.SnapState) error {
// we have ubuntu-core
@@ -1982,6 +1979,10 @@ func (s *apiSuite) TestRefresh(c *check.C) {
t := s.NewTask("fake-refresh-snap", "Doing a fake install")
return state.NewTaskSet(t), nil
}
+ assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ assertstateCalledUserID = userID
+ return nil
+ }
d := s.daemon(c)
inst := &snapInstruction{
@@ -1996,6 +1997,7 @@ func (s *apiSuite) TestRefresh(c *check.C) {
summary, _, err := inst.dispatch()(inst, st)
c.Check(err, check.IsNil)
+ c.Check(assertstateCalledUserID, check.Equals, 17)
c.Check(calledFlags, check.Equals, snapstate.Flags(0))
c.Check(calledUserID, check.Equals, 17)
c.Check(err, check.IsNil)
@@ -2020,6 +2022,9 @@ func (s *apiSuite) TestRefreshDevMode(c *check.C) {
t := s.NewTask("fake-refresh-snap", "Doing a fake install")
return state.NewTaskSet(t), nil
}
+ assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ return nil
+ }
d := s.daemon(c)
inst := &snapInstruction{
@@ -2073,6 +2078,12 @@ func (s *apiSuite) TestPostSnapsOp(c *check.C) {
}
func (s *apiSuite) TestRefreshAll(c *check.C) {
+ refreshSnapDecls := false
+ assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ refreshSnapDecls = true
+ return assertstate.RefreshSnapDeclarations(s, userID)
+ }
+
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 0)
t := s.NewTask("fake-refresh-all", "Refreshing everything")
@@ -2087,9 +2098,16 @@ func (s *apiSuite) TestRefreshAll(c *check.C) {
st.Unlock()
c.Assert(err, check.IsNil)
c.Check(summary, check.Equals, "Refresh all snaps in the system")
+ c.Check(refreshSnapDecls, check.Equals, true)
}
func (s *apiSuite) TestRefreshMany(c *check.C) {
+ refreshSnapDecls := false
+ assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ refreshSnapDecls = true
+ return nil
+ }
+
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 2)
t := s.NewTask("fake-refresh-2", "Refreshing two")
@@ -2105,9 +2123,16 @@ func (s *apiSuite) TestRefreshMany(c *check.C) {
c.Assert(err, check.IsNil)
c.Check(summary, check.Equals, `Refresh snaps "foo", "bar"`)
c.Check(updates, check.DeepEquals, inst.Snaps)
+ c.Check(refreshSnapDecls, check.Equals, true)
}
func (s *apiSuite) TestRefreshMany1(c *check.C) {
+ refreshSnapDecls := false
+ assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ refreshSnapDecls = true
+ return nil
+ }
+
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 1)
t := s.NewTask("fake-refresh-1", "Refreshing one")
@@ -2123,6 +2148,7 @@ func (s *apiSuite) TestRefreshMany1(c *check.C) {
c.Assert(err, check.IsNil)
c.Check(summary, check.Equals, `Refresh snap "foo"`)
c.Check(updates, check.DeepEquals, inst.Snaps)
+ c.Check(refreshSnapDecls, check.Equals, true)
}
func (s *apiSuite) TestInstallMissingUbuntuCore(c *check.C) {
diff --git a/daemon/snap.go b/daemon/snap.go
index 15ac67c85f..20f1a3ede9 100644
--- a/daemon/snap.go
+++ b/daemon/snap.go
@@ -38,7 +38,7 @@ func snapIcon(info *snap.Info) string {
// XXX: copy of snap.Snap.Icon which will go away
found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*"))
if len(found) == 0 {
- return ""
+ return info.IconURL
}
return found[0]
diff --git a/debian/changelog b/debian/changelog
index 63136049d3..2401e99208 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,136 @@
+snapd (2.15.2) xenial; urgency=medium
+
+ * New upstream release, LP: #1623579
+ - asserts: define a bit less terse Ref.String
+ - interfaces: disable auto-connect in libvirt interface
+ - asserts: check that validation assertions are signed by the
+ publisher of the gating snap
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 19 Sep 2016 10:42:29 +0200
+
+snapd (2.15.1) xenial; urgency=medium
+
+ * New upstream release, LP: #1623579
+ - image: ensure local snaps are put last in seed.yaml
+ - asserts: revert change that made the account-key's name mandatory.
+ - many: refresh all snap decls
+ - interfaces/apparmor: allow reading /etc/environment
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 19 Sep 2016 09:19:44 +0200
+
+snapd (2.15) xenial; urgency=medium
+
+ * New upstream release, LP: #1623579
+ - tests: disable prepare-image-grub test in autopkgtest
+ - interfaces: allow special casing for auto-connect until we have
+ assertions
+ - docs: add a little documentation on hooks.
+ - hookstate,daemon: don't mock HookRunner, mock command.
+ - tests: add http_proxy to /etc/environment in the autopkgtest
+ environment
+ - backends: first bits of kernel-module security backend
+ - tests: ensure openssh-server is installed in autopkgtest
+ - tests: make ubuntu-core tests more robust
+ - many: mostly work to support ABA upgrades
+ - cmd/snap: do runtime linting of descriptions
+ - spread.yaml: don't assume LANG is set
+ - snap: fix SNAP* environment merging in `snap run`
+ - CONTRIBUTING.md: remove integration-tests, include spread
+ - store: don't discard error body from request device session call
+ - docs: add create-user documentation
+ - cmd/snap: match UX document for message when buying without login
+ - firstboot: do not overwrite any existing netplan config
+ - tests: add debug output to ubuntu-core-update-rollback-
+ stresstest:
+ - tests/lib/prepare.sh: test that classic does not setting bootvars
+ - snap: run all tests with gpg2
+ - asserts: basic support for validation assertion and refresh-
+ control
+ - interfaces: miscellaneous policy updates for default, browser-
+ support and camera
+ - snap: (re)add --force-dangerous compat option
+ - tests: ensure SUDO_{USER,GID} is unset in the spread tests
+ - many: clean out left over references to integration tests
+ - overlord/auth,store: fix raciness in updating device/user in state
+ through authcontext and other issuesbonus fixes:
+ - tests: fix spread tests on yakkety
+ - store: refactor auth/refresh tests
+ - asserts: use gpg --fixed-list-mode to be compatible with both gpg1
+ and gpg2
+ - cmd/snap: i18n option descriptions
+ - asserts: required account key name header
+ - tests: add yakkety test host
+ - packaging: make sure debhelper-generated snippet is invoked on
+ postrm
+ - snap,store: capture newest digest from the store, make it
+ DownloadInfo only
+ - tests: add upower-observe spread test
+ - Merge github.com:snapcore/snapd
+ - tests: fixes to actually run the spread tests inside autopkgtest
+ - cmd/snap: make "snap find" error nicer.
+ - tests: get the gadget name from snap list
+ - cmd/snap: tweak help of 'snap download'
+ - cmd/snap,image: teach snap download to download also assertions
+ - interfaces/builtin: tweak opengl interface
+ - interfaces: serial-port use udevUsbDeviceSnippet
+ - store: ensure the payment methods method handles auth failure
+ - overlord/snapstate: support revert flags
+ - many: add snap configuration to REST API
+ - tests: use ubuntu-image for the ubuntu-core-16 image creation
+ - cmd/snap: serialise empty keys list as [] rather than null
+ - cmd/snap,client: add snap set and snap get commands
+ - asserts: update trusted account-key asserts with names
+ - overlord/snapstate: misc fixes/tweaks/cleanups
+ - image: have prepare-image set devmode correctly
+ - overlord/boot: have firstboot support assertion files with
+ multiple assertions
+ - daemon: bail from enable and disable if revision given, and from
+ multi-op if unsupported optons given
+ - osutil: call sync after cp if
+ requested.overlord/snapstate/backend: switch to use osutil instead
+ of another buggy call to cp
+ - cmd/snap: generate account-key-request "since" header in UTC
+ - many: use symlinks instead of wrappers
+ - tests: remove silly [Service] entry from snapd.socket.d/local.conf
+ - store: switch device session to use device-session-request
+ assertion
+ - snap: ensure that plug and slot names are unique
+ - cmd/snap: fix test suite (no Exit(0) on tests!)
+ - interfaces: add interface for hidraw devices
+ - tests: use the real model assertion when creating the core test
+ image
+ - interfaces/builtin: add udisks2 and removable-media interfaces
+ - interface: network_manager: enable resolvconf
+ - interfaces/builtin: usb serial-port support via udev
+ - interfaces/udev: support noneSecurityTag keyed snippets
+ - snap: switch to the new agreed regexp for snap names
+ - tests: adjust test setup after ubuntu user removal
+ - many: start services only after the snap is fully ready (link-snap
+ was run)
+ - asserts: don't have Add/Check panic in the face of unsupported no-
+ authority assertions
+ - asserts: initial support to generate/sign snap-build assertions
+ - asserts: support checking account-key-request assertions
+ - overlord: introduce AuthContext.DeviceSessionRequest with support
+ in devicestate
+ - overlord/state: fix for reloaded task/change crashing on Set if
+ checkpointed w. no custom data yet
+ - snapd.refresh.service: require snap.socket and /snap/*/current.
+ - many: spell --force-dangerous as just --dangerous, devmode should
+ imply it
+ - overlord/devicestate: try to fetch/refresh the signing key of
+ serial (also in case is not there yet)
+ - image,overlord/boot,snap: metadata from asserts for image snaps
+ - many: automatically restart all-snap devices after os/kernel
+ updates
+ - interfaces: modem-manager: ignore camera
+ - firstboot: only configure en* and eth* interfaces by default
+ - interfaces: fix interface handling on no-app snaps
+ - snap: set user variables even if HOME is unset (like with systemd
+ services)
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 16 Sep 2016 07:46:22 +0200
+
snapd (2.14.2~16.04) xenial; urgency=medium
* New upstream release: LP: #1618095
diff --git a/docs/hooks.md b/docs/hooks.md
new file mode 100644
index 0000000000..b4e879f1ac
--- /dev/null
+++ b/docs/hooks.md
@@ -0,0 +1,31 @@
+# Hooks
+
+There are a number of situations where snapd needs to notify a snap that
+something has happened. For example, when a snap is upgraded, it may need to run
+some sort of migration on the previous version's data in order to make it
+consumable by the new version. Or when an interface is connected or
+disconnected, the snap might need to obtain attributes specific to that
+connection. These types of situations are handled by hooks.
+
+A hook is defined as an executable contained within the `meta/hooks/` directory
+inside the snap. The file name of the executable is the name of the hook (e.g.
+the upgrade hook executable would be `meta/hooks/upgrade`).
+
+As long as the file name of the executable corresponds to a supported hook name,
+that's all one needs to do in order to utilize a hook within their snap. Note
+that hooks, like apps, are executed within a confined environment. By default
+hooks will run with no plugs; if a hook needs more privileges one can use the
+top-level attribute `hooks` in `snap.yaml` to request plugs, like so:
+
+ hooks: # Top-level YAML attribute, parallel to `apps`
+ upgrade: # Hook name, corresponds to executable name
+ plugs: [network] # Or any other plugs required by this hook
+
+Note that hooks will be called with no parameters. If they need more information
+from snapd they can utilize the `snapctl` command.
+
+
+## Supported Hooks
+
+**Note:** The development of specific hooks is ongoing. None are currently
+supported.
diff --git a/docs/interfaces.md b/docs/interfaces.md
index 9987209806..12d7868fce 100644
--- a/docs/interfaces.md
+++ b/docs/interfaces.md
@@ -313,6 +313,8 @@ Can access the libvirt control socket, which gives privileged access to control
libvirtd on the host. This is commonly used to create and manage QEMU/KVM
instances on the host.
+* Auto-Connect: no
+
### locale-control
Can manage locales directly separate from ``config core``.
diff --git a/image/helpers.go b/image/helpers.go
index 29d0b23472..2adc646371 100644
--- a/image/helpers.go
+++ b/image/helpers.go
@@ -85,8 +85,8 @@ func DownloadSnap(sto Store, name string, revision snap.Revision, opts *Download
return targetPath, snap, nil
}
-// StoreAssertionFetcher creates an asserts.Fetcher for assertions against the given store using dlOpts for authorization, the fetcher will save assertions in the given database and after that also call save for each of them.
-func StoreAssertionFetcher(sto Store, dlOpts *DownloadOptions, db *asserts.Database, save func(asserts.Assertion) error) *asserts.Fetcher {
+// StoreAssertionFetcher creates an asserts.Fetcher for assertions against the given store using dlOpts for authorization, the fetcher will add assertions in the given database and after that also call save for each of them.
+func StoreAssertionFetcher(sto Store, dlOpts *DownloadOptions, db *asserts.Database, save func(asserts.Assertion) error) asserts.Fetcher {
retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
return sto.Assertion(ref.Type, ref.PrimaryKey, dlOpts.User)
}
@@ -104,17 +104,14 @@ func StoreAssertionFetcher(sto Store, dlOpts *DownloadOptions, db *asserts.Datab
return asserts.NewFetcher(db, retrieve, save2)
}
-// FetchSnapAssertions fetches and cross checks the snap assertions matching the given snap file using the provided asserts.Fetcher and assertion database.
-func FetchSnapAssertions(snapPath string, info *snap.Info, f *asserts.Fetcher, db asserts.RODatabase) error {
+// FetchAndCheckSnapAssertions fetches and cross checks the snap assertions matching the given snap file using the provided asserts.Fetcher and assertion database.
+func FetchAndCheckSnapAssertions(snapPath string, info *snap.Info, f asserts.Fetcher, db asserts.RODatabase) error {
sha3_384, size, err := asserts.SnapFileSHA3_384(snapPath)
if err != nil {
return err
}
- ref := &asserts.Ref{
- Type: asserts.SnapRevisionType,
- PrimaryKey: []string{sha3_384},
- }
- if err := f.Fetch(ref); err != nil {
+
+ if err := snapasserts.FetchSnapAssertions(f, sha3_384); err != nil {
return fmt.Errorf("cannot fetch snap signatures/assertions: %v", err)
}
diff --git a/image/image.go b/image/image.go
index c19ecd9721..dd0c5e947a 100644
--- a/image/image.go
+++ b/image/image.go
@@ -66,6 +66,13 @@ func (li *localInfos) Name(pathOrName string) string {
return pathOrName
}
+func (li *localInfos) PreferLocal(name string) string {
+ if path := li.Path(name); path != "" {
+ return path
+ }
+ return name
+}
+
func (li *localInfos) Path(name string) string {
return li.nameToPath[name]
}
@@ -185,7 +192,7 @@ func acquireSnap(sto Store, name string, dlOpts *DownloadOptions, local *localIn
}
type addingFetcher struct {
- *asserts.Fetcher
+ asserts.Fetcher
addedRefs []*asserts.Ref
}
@@ -248,22 +255,23 @@ func bootstrapToRootDir(sto Store, model *asserts.Model, opts *Options, local *l
DevMode: false, // XXX: should this be true?
}
- snaps := []string{}
- // opts.Snaps need to be considered first to support local overrides
- // of snaps mentioned in the model assertion whose fetching
- // from the store will be then skipped
- snaps = append(snaps, opts.Snaps...)
- snaps = append(snaps, model.Gadget())
- snaps = append(snaps, defaultCore)
- snaps = append(snaps, model.Kernel())
- snaps = append(snaps, model.RequiredSnaps()...)
-
for _, d := range []string{snapSeedDir, assertSeedDir} {
if err := os.MkdirAll(d, 0755); err != nil {
return err
}
}
+ snaps := []string{}
+ // core,kernel,gadget first
+ snaps = append(snaps, local.PreferLocal(defaultCore))
+ snaps = append(snaps, local.PreferLocal(model.Kernel()))
+ snaps = append(snaps, local.PreferLocal(model.Gadget()))
+ // then required and the user requested stuff
+ for _, snapName := range model.RequiredSnaps() {
+ snaps = append(snaps, local.PreferLocal(snapName))
+ }
+ snaps = append(snaps, opts.Snaps...)
+
seen := make(map[string]bool)
downloadedSnapsInfo := map[string]*snap.Info{}
var seedYaml snap.Seed
@@ -291,7 +299,7 @@ func bootstrapToRootDir(sto Store, model *asserts.Model, opts *Options, local *l
// TODO: support somehow including available assertions
// also for local snaps
if info.SnapID != "" {
- err = FetchSnapAssertions(fn, info, f.Fetcher, db)
+ err = FetchAndCheckSnapAssertions(fn, info, f, db)
if err != nil {
return err
}
diff --git a/image/image_test.go b/image/image_test.go
index 19300c7e6f..cefd4a6d9f 100644
--- a/image/image_test.go
+++ b/image/image_test.go
@@ -91,14 +91,15 @@ func (s *imageSuite) SetUpTest(c *C) {
s.storeSigning.Add(brandAccKey)
model, err := s.brandSigning.Sign(asserts.ModelType, map[string]interface{}{
- "series": "16",
- "authority-id": "my-brand",
- "brand-id": "my-brand",
- "model": "my-model",
- "architecture": "amd64",
- "gadget": "pc",
- "kernel": "pc-kernel",
- "timestamp": time.Now().Format(time.RFC3339),
+ "series": "16",
+ "authority-id": "my-brand",
+ "brand-id": "my-brand",
+ "model": "my-model",
+ "architecture": "amd64",
+ "gadget": "pc",
+ "kernel": "pc-kernel",
+ "required-snaps": []interface{}{"required-snap1"},
+ "timestamp": time.Now().Format(time.RFC3339),
}, nil, "")
c.Assert(err, IsNil)
s.model = model.(*asserts.Model)
@@ -169,6 +170,7 @@ name: ubuntu-core
version: 16.04
type: os
`
+
const devmodeSnap = `
name: devmode-snap
version: 1.0
@@ -176,6 +178,11 @@ type: app
confinement: devmode
`
+const requiredSnap1 = `
+name: required-snap1
+version: 1.0
+`
+
func (s *imageSuite) TestMissingModelAssertions(c *C) {
_, err := image.DecodeModelAssertion(&image.Options{})
c.Assert(err, ErrorMatches, "cannot read model assertion: open : no such file or directory")
@@ -322,6 +329,10 @@ func (s *imageSuite) setupSnaps(c *C, gadgetUnpackDir string) {
s.downloadedSnaps["ubuntu-core"] = snaptest.MakeTestSnapWithFiles(c, packageCore, nil)
s.storeSnapInfo["ubuntu-core"] = infoFromSnapYaml(c, packageCore, snap.R(3))
s.addSystemSnapAssertions(c, "ubuntu-core")
+
+ s.downloadedSnaps["required-snap1"] = snaptest.MakeTestSnapWithFiles(c, requiredSnap1, nil)
+ s.storeSnapInfo["required-snap1"] = infoFromSnapYaml(c, requiredSnap1, snap.R(3))
+ s.addSystemSnapAssertions(c, "required-snap1")
}
func (s *imageSuite) TestBootstrapToRootDir(c *C) {
@@ -355,10 +366,10 @@ func (s *imageSuite) TestBootstrapToRootDir(c *C) {
seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
+ c.Check(seed.Snaps, HasLen, 4)
// check the files are in place
- for i, name := range []string{"pc", "ubuntu-core", "pc-kernel"} {
+ for i, name := range []string{"ubuntu-core", "pc-kernel", "pc"} {
info := s.storeSnapInfo[name]
fn := filepath.Base(info.MountFile())
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
@@ -425,7 +436,10 @@ func (s *imageSuite) TestBootstrapToRootDirLocalCore(c *C) {
defer c2.Restore()
opts := &image.Options{
- Snaps: []string{s.downloadedSnaps["ubuntu-core"]},
+ Snaps: []string{
+ s.downloadedSnaps["ubuntu-core"],
+ s.downloadedSnaps["required-snap1"],
+ },
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
}
@@ -439,21 +453,31 @@ func (s *imageSuite) TestBootstrapToRootDirLocalCore(c *C) {
seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
+ c.Check(seed.Snaps, HasLen, 4)
// check the files are in place
- for i, name := range []string{"ubuntu-core_x1.snap", "pc", "pc-kernel"} {
+ for i, name := range []string{"ubuntu-core_x1.snap", "pc-kernel", "pc", "required-snap1_x1.snap"} {
unasserted := false
info := s.storeSnapInfo[name]
if info == nil {
- // ubuntu-core
- info = &snap.Info{
- SideInfo: snap.SideInfo{
- RealName: "ubuntu-core",
- Revision: snap.R("x1"),
- },
+ switch name {
+ case "ubuntu-core_x1.snap":
+ info = &snap.Info{
+ SideInfo: snap.SideInfo{
+ RealName: "ubuntu-core",
+ Revision: snap.R("x1"),
+ },
+ }
+ unasserted = true
+ case "required-snap1_x1.snap":
+ info = &snap.Info{
+ SideInfo: snap.SideInfo{
+ RealName: "required-snap1",
+ Revision: snap.R("x1"),
+ },
+ }
+ unasserted = true
}
- unasserted = true
}
fn := filepath.Base(info.MountFile())
@@ -470,7 +494,7 @@ func (s *imageSuite) TestBootstrapToRootDirLocalCore(c *C) {
l, err := ioutil.ReadDir(filepath.Join(rootdir, "var/lib/snapd/seed/snaps"))
c.Assert(err, IsNil)
- c.Check(l, HasLen, 3)
+ c.Check(l, HasLen, 4)
storeAccountKey := s.storeSigning.StoreAccountKey("")
brandPubKey, err := s.brandSigning.PublicKey("")
@@ -548,7 +572,7 @@ func (s *imageSuite) TestBootstrapToRootDirDevmodeSnap(c *C) {
seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 4)
+ c.Check(seed.Snaps, HasLen, 5)
// check devmode-snap
info := &snap.Info{
@@ -560,7 +584,10 @@ func (s *imageSuite) TestBootstrapToRootDirDevmodeSnap(c *C) {
fn := filepath.Base(info.MountFile())
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+
+ // ensure local snaps are put last in seed.yaml
+ last := len(seed.Snaps) - 1
+ c.Check(seed.Snaps[last], DeepEquals, &snap.SeedSnap{
Name: "devmode-snap",
File: fn,
DevMode: true,
diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go
index e6e2debf08..774fef348a 100644
--- a/interfaces/apparmor/template.go
+++ b/interfaces/apparmor/template.go
@@ -86,6 +86,7 @@ var defaultTemplate = []byte(`
/etc/libnl-3/{classid,pktloc} r, # apps that use libnl
/var/lib/extrausers/{passwd,group} r,
/etc/profile r,
+ /etc/environment r,
/usr/share/terminfo/** r,
/etc/inputrc r,
# Common utilities for shell scripts
diff --git a/interfaces/builtin/libvirt.go b/interfaces/builtin/libvirt.go
index 7cc335e37c..4218bd8cb9 100644
--- a/interfaces/builtin/libvirt.go
+++ b/interfaces/builtin/libvirt.go
@@ -43,6 +43,7 @@ func NewLibvirtInterface() interfaces.Interface {
connectedPlugAppArmor: libvirtConnectedPlugAppArmor,
connectedPlugSecComp: libvirtConnectedPlugSecComp,
reservedForOS: true,
- autoConnect: true,
+ // cannot auto-connect, it grants too much power
+ autoConnect: false,
}
}
diff --git a/interfaces/builtin/libvirt_test.go b/interfaces/builtin/libvirt_test.go
index 7e08b3a139..2ceafebf98 100644
--- a/interfaces/builtin/libvirt_test.go
+++ b/interfaces/builtin/libvirt_test.go
@@ -121,3 +121,7 @@ func (s *LibvirtInterfaceSuite) TestUnexpectedSecuritySystems(c *C) {
snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, interfaces.SecurityMount)
c.Assert(err, IsNil)
}
+
+func (s *LibvirtInterfaceSuite) TestAutoConnect(c *C) {
+ c.Check(s.iface.AutoConnect(), Equals, false)
+}
diff --git a/interfaces/builtin/pulseaudio.go b/interfaces/builtin/pulseaudio.go
index 261de434cf..78ac45c29e 100644
--- a/interfaces/builtin/pulseaudio.go
+++ b/interfaces/builtin/pulseaudio.go
@@ -27,7 +27,7 @@ import (
)
const pulseaudioConnectedPlugAppArmor = `
-/{run,dev}/shm/pulse-shm-* rwk,
+/{run,dev}/shm/pulse-shm-* rwkm,
owner /{,var/}run/pulse/ r,
owner /{,var/}run/pulse/native rwk,
diff --git a/interfaces/repo.go b/interfaces/repo.go
index fcad4e3caf..6b83fab821 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -716,6 +716,17 @@ func (r *Repository) DisconnectSnap(snapName string) ([]string, error) {
return result, nil
}
+// isLivePatchSnap checks special Name/Developer combinations to see
+// if this particular snap's connections should be automatically connected even
+// if the interfaces are not autoconnect and the snap is not an OS snap.
+// FIXME: remove once we have assertions that provide this feature
+func isLivePatchSnap(snap *snap.Info) bool {
+ if snap.Name() == "canonical-livepatch" && snap.DeveloperID == "canonical" {
+ return true
+ }
+ return false
+}
+
// AutoConnectCandidates finds and returns viable auto-connection candidates
// for a given plug.
func (r *Repository) AutoConnectCandidates(plugSnapName, plugName string) []*Slot {
@@ -726,13 +737,11 @@ func (r *Repository) AutoConnectCandidates(plugSnapName, plugName string) []*Slo
if plug == nil {
return nil
}
- if r.ifaces[plug.Interface].AutoConnect() == false {
- return nil
- }
+
var candidates []*Slot
for _, slotsForSnap := range r.slots {
for _, slot := range slotsForSnap {
- if isAutoConnectCandidate(plug, slot) {
+ if r.isAutoConnectCandidate(plug, slot) {
candidates = append(candidates, slot)
}
}
@@ -742,7 +751,20 @@ func (r *Repository) AutoConnectCandidates(plugSnapName, plugName string) []*Slo
// isAutoConnectCandidate returns true if the plug is a candidate to
// automatically connect to the given slot.
-func isAutoConnectCandidate(plug *Plug, slot *Slot) bool {
+func (r *Repository) isAutoConnectCandidate(plug *Plug, slot *Slot) bool {
+ if slot.Interface != plug.Interface {
+ return false
+ }
+
+ // FIXME: remove once we have assertions that provide this feature
+ if isLivePatchSnap(plug.Snap) {
+ return true
+ }
+
+ if !r.ifaces[plug.Interface].AutoConnect() {
+ return false
+ }
+
// content sharing auto connect candidates
if slot.Interface == "content" {
if slot.Attrs["content"] == plug.Attrs["content"] && slot.Snap.Developer == plug.Snap.Developer {
@@ -754,7 +776,7 @@ func isAutoConnectCandidate(plug *Plug, slot *Slot) bool {
}
// OS snap auto connect candidates
- if slot.Snap.Type == snap.TypeOS && slot.Interface == plug.Interface {
+ if slot.Snap.Type == snap.TypeOS {
return true
}
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index db369e6b9c..4cf90de0fa 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -1239,3 +1239,85 @@ func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingDeveloper(c *
candidateSlots := repo.AutoConnectCandidates("content-plug-snap", "import-content")
c.Check(candidateSlots, HasLen, 0)
}
+
+func makeLivepatchConnectionTestSnaps(c *C, name, developer string) (*Repository, *snap.Info, *snap.Info) {
+ repo := NewRepository()
+ err := repo.AddInterface(&TestInterface{InterfaceName: "restricted", AutoConnectFlag: false})
+ c.Assert(err, IsNil)
+
+ err = repo.AddInterface(&TestInterface{InterfaceName: "non-restricted", AutoConnectFlag: true})
+ c.Assert(err, IsNil)
+
+ plugSnap, err := snap.InfoFromSnapYaml([]byte(fmt.Sprintf(`
+name: %s
+plugs:
+ restricted:
+ interface: restricted
+ non-restricted:
+ interface: non-restricted
+`, name)))
+ c.Assert(err, IsNil)
+ slotSnap, err := snap.InfoFromSnapYaml([]byte(`
+name: ubuntu-core
+type: os
+slots:
+ restricted:
+ interface: restricted
+ non-restricted:
+ interface: non-restricted
+`))
+ c.Assert(err, IsNil)
+
+ plugSnap.DeveloperID = developer
+ slotSnap.DeveloperID = "canonical"
+ slotSnap.Type = snap.TypeOS
+
+ err = repo.AddSnap(plugSnap)
+ c.Assert(err, IsNil)
+ err = repo.AddSnap(slotSnap)
+ c.Assert(err, IsNil)
+
+ return repo, plugSnap, slotSnap
+}
+
+// test auto-connecting livepatch interfaces for special snaps
+func (s *RepositorySuite) TestAutoConnectLivepatchInterfaces(c *C) {
+ repo, _, _ := makeLivepatchConnectionTestSnaps(c, "canonical-livepatch", "canonical")
+ candidateSlots := repo.AutoConnectCandidates("canonical-livepatch", "restricted")
+ c.Check(candidateSlots, HasLen, 1)
+ c.Check(candidateSlots[0].Snap.Name(), Equals, "ubuntu-core")
+ c.Check(candidateSlots[0].Snap.DeveloperID, Equals, "canonical")
+ c.Check(candidateSlots[0].Name, Equals, "restricted")
+}
+
+// test auto-connecting unrestricted (auto-connect) interfaces for special snaps
+func (s *RepositorySuite) TestAutoConnectNonRestrictedInterfaces(c *C) {
+ repo, _, _ := makeLivepatchConnectionTestSnaps(c, "canonical-livepatch", "canonical")
+ candidateSlots := repo.AutoConnectCandidates("canonical-livepatch", "non-restricted")
+ c.Check(candidateSlots, HasLen, 1)
+ c.Check(candidateSlots[0].Snap.Name(), Equals, "ubuntu-core")
+ c.Check(candidateSlots[0].Snap.DeveloperID, Equals, "canonical")
+ c.Check(candidateSlots[0].Name, Equals, "non-restricted")
+}
+
+// test auto-connecting unrestricted (auto-connect) interfaces for non-special snaps
+func (s *RepositorySuite) TestAutoConnectNonRestrictedInterfacesNonSpecialSnap2(c *C) {
+ repo, _, _ := makeLivepatchConnectionTestSnaps(c, "canonical-livepatch", "someone-else")
+ candidateSlots := repo.AutoConnectCandidates("canonical-livepatch", "non-restricted")
+ c.Check(candidateSlots, HasLen, 1)
+ c.Check(candidateSlots[0].Snap.Name(), Equals, "ubuntu-core")
+ c.Check(candidateSlots[0].Snap.DeveloperID, Equals, "canonical")
+ c.Check(candidateSlots[0].Name, Equals, "non-restricted")
+}
+
+func (s *RepositorySuite) TestAutoConnectLivepatchWrongDeveloper(c *C) {
+ repo, _, _ := makeLivepatchConnectionTestSnaps(c, "canonical-livepatch", "somebody")
+ candidateSlots := repo.AutoConnectCandidates("canonical-livepatch", "restricted")
+ c.Check(candidateSlots, HasLen, 0)
+}
+
+func (s *RepositorySuite) TestAutoConnectLivepatchWrongName(c *C) {
+ repo, _, _ := makeLivepatchConnectionTestSnaps(c, "something", "canonical")
+ candidateSlots := repo.AutoConnectCandidates("canonical-livepatch", "restricted")
+ c.Check(candidateSlots, HasLen, 0)
+}
diff --git a/overlord/assertstate/assertmgr.go b/overlord/assertstate/assertmgr.go
index f291cd40f4..410bf5baf8 100644
--- a/overlord/assertstate/assertmgr.go
+++ b/overlord/assertstate/assertmgr.go
@@ -24,6 +24,7 @@ package assertstate
import (
"fmt"
+ "strings"
"gopkg.in/tomb.v2"
@@ -112,30 +113,18 @@ func userFromUserID(st *state.State, userID int) (*auth.UserState, error) {
return auth.User(st, userID)
}
-type assertionNotFoundError struct {
- ref *asserts.Ref
+type fetcher struct {
+ db *asserts.Database
+ asserts.Fetcher
+ fetched []asserts.Assertion
}
-func (e *assertionNotFoundError) Error() string {
- return fmt.Sprintf("%s %v not found", e.ref.Type.Name, e.ref.PrimaryKey)
-}
-
-// fetch fetches or updates the referenced assertion and all its prerequisites from the store and adds them to the system assertion database. It does not fail if required assertions were already present.
-func fetch(s *state.State, ref *asserts.Ref, userID int) error {
- // TODO: once we have a bulk assertion retrieval endpoint this approach will change
-
- user, err := userFromUserID(s, userID)
- if err != nil {
- return err
- }
-
+// newFetches creates a fetcher used to retrieve assertions from the store and later commit them to the system database in one go.
+func newFetcher(s *state.State, user *auth.UserState) *fetcher {
db := cachedDB(s)
sto := snapstate.Store(s)
- s.Unlock()
- defer s.Lock()
-
- got := []asserts.Assertion{}
+ f := &fetcher{db: db}
retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
// TODO: ignore errors if already in db?
@@ -143,21 +132,32 @@ func fetch(s *state.State, ref *asserts.Ref, userID int) error {
}
save := func(a asserts.Assertion) error {
- got = append(got, a)
+ f.fetched = append(f.fetched, a)
return nil
}
- f := asserts.NewFetcher(db, retrieve, save)
+ f.Fetcher = asserts.NewFetcher(db, retrieve, save)
- if err := f.Fetch(ref); err != nil {
- return err
- }
+ return f
+}
- s.Lock()
- defer s.Unlock()
+type commitError struct {
+ errs []error
+}
- for _, a := range got {
- err := db.Add(a)
+func (e *commitError) Error() string {
+ l := []string{""}
+ for _, e := range e.errs {
+ l = append(l, e.Error())
+ }
+ return fmt.Sprintf("cannot add some assertions to the system database:%s", strings.Join(l, "\n - "))
+}
+
+// commit does a best effort of adding all the fetched assertions to the system database.
+func (f *fetcher) commit() error {
+ var errs []error
+ for _, a := range f.fetched {
+ err := f.db.Add(a)
if revErr, ok := err.(*asserts.RevisionError); ok {
if revErr.Current >= a.Revision() {
// be idempotent
@@ -165,17 +165,39 @@ func fetch(s *state.State, ref *asserts.Ref, userID int) error {
continue
}
}
- // TODO: trigger w. caller a global sanity check if a is revoked
- // (but try to save as much possible still),
- // or err is a check error
if err != nil {
- return err
+ errs = append(errs, err)
}
}
-
+ if len(errs) != 0 {
+ return &commitError{errs: errs}
+ }
return nil
}
+func doFetch(s *state.State, userID int, fetching func(asserts.Fetcher) error) error {
+ // TODO: once we have a bulk assertion retrieval endpoint this approach will change
+
+ user, err := userFromUserID(s, userID)
+ if err != nil {
+ return err
+ }
+
+ f := newFetcher(s, user)
+
+ s.Unlock()
+ err = fetching(f)
+ s.Lock()
+ if err != nil {
+ return err
+ }
+
+ // TODO: trigger w. caller a global sanity check if a is revoked
+ // (but try to save as much possible still),
+ // or err is a check error
+ return f.commit()
+}
+
// doValidateSnap fetches the relevant assertions for the snap being installed and cross checks them with the snap.
func doValidateSnap(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
@@ -191,13 +213,9 @@ func doValidateSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}
- // for now starting from the snap-revision will get us all other relevant assertions
- ref := &asserts.Ref{
- Type: asserts.SnapRevisionType,
- PrimaryKey: []string{sha3_384},
- }
-
- err = fetch(t.State(), ref, ss.UserID)
+ err = doFetch(t.State(), ss.UserID, func(f asserts.Fetcher) error {
+ return snapasserts.FetchSnapAssertions(f, sha3_384)
+ })
if notFound, ok := err.(*store.AssertionNotFoundError); ok {
if notFound.Ref.Type == asserts.SnapRevisionType {
return fmt.Errorf("cannot verify snap %q, no matching signatures found", ss.Name())
@@ -221,3 +239,27 @@ func doValidateSnap(t *state.Task, _ *tomb.Tomb) error {
// TODO: set DeveloperID from assertions
return nil
}
+
+// RefreshSnapDeclarations refetches all the current snap declarations and their prerequisites.
+func RefreshSnapDeclarations(s *state.State, userID int) error {
+ snapStates, err := snapstate.All(s)
+ if err != nil {
+ return nil
+ }
+ fetching := func(f asserts.Fetcher) error {
+ for _, snapState := range snapStates {
+ info, err := snapState.CurrentInfo()
+ if err != nil {
+ return err
+ }
+ if info.SnapID == "" {
+ continue
+ }
+ if err := snapasserts.FetchSnapDeclaration(f, info.SnapID); err != nil {
+ return fmt.Errorf("cannot refresh snap-declaration for %q: %v", info.Name(), err)
+ }
+ }
+ return nil
+ }
+ return doFetch(s, userID, fetching)
+}
diff --git a/overlord/assertstate/assertmgr_test.go b/overlord/assertstate/assertmgr_test.go
index 6b7b73911d..3aece5336d 100644
--- a/overlord/assertstate/assertmgr_test.go
+++ b/overlord/assertstate/assertmgr_test.go
@@ -212,7 +212,7 @@ func (s *assertMgrSuite) prereqSnapAssertions(c *C, revisions ...int) {
}
}
-func (s *assertMgrSuite) TestFetch(c *C) {
+func (s *assertMgrSuite) TestDoFetch(c *C) {
s.prereqSnapAssertions(c, 10)
s.state.Lock()
@@ -223,7 +223,9 @@ func (s *assertMgrSuite) TestFetch(c *C) {
PrimaryKey: []string{makeDigest(10)},
}
- err := assertstate.Fetch(s.state, ref, 0)
+ err := assertstate.DoFetch(s.state, 0, func(f asserts.Fetcher) error {
+ return f.Fetch(ref)
+ })
c.Assert(err, IsNil)
snapRev, err := ref.Resolve(assertstate.DB(s.state).Find)
@@ -241,8 +243,11 @@ func (s *assertMgrSuite) TestFetchIdempotent(c *C) {
Type: asserts.SnapRevisionType,
PrimaryKey: []string{makeDigest(10)},
}
+ fetching := func(f asserts.Fetcher) error {
+ return f.Fetch(ref)
+ }
- err := assertstate.Fetch(s.state, ref, 0)
+ err := assertstate.DoFetch(s.state, 0, fetching)
c.Assert(err, IsNil)
ref = &asserts.Ref{
@@ -250,7 +255,7 @@ func (s *assertMgrSuite) TestFetchIdempotent(c *C) {
PrimaryKey: []string{makeDigest(11)},
}
- err = assertstate.Fetch(s.state, ref, 0)
+ err = assertstate.DoFetch(s.state, 0, fetching)
c.Assert(err, IsNil)
snapRev, err := ref.Resolve(assertstate.DB(s.state).Find)
@@ -369,3 +374,110 @@ func (s *assertMgrSuite) TestValidateSnapCrossCheckFail(c *C) {
c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot install snap "f" that is undergoing a rename to "foo".*`)
}
+
+func (s *assertMgrSuite) TestRefreshSnapDeclarations(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ err := s.storeSigning.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "series": "16",
+ "snap-id": "foo-id",
+ "snap-name": "foo",
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ snapDeclFoo, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = s.storeSigning.Add(snapDeclFoo)
+ c.Assert(err, IsNil)
+
+ headers = map[string]interface{}{
+ "series": "16",
+ "snap-id": "bar-id",
+ "snap-name": "bar",
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ snapDeclBar, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = s.storeSigning.Add(snapDeclBar)
+ c.Assert(err, IsNil)
+
+ snapstate.Set(s.state, "foo", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "foo", SnapID: "foo-id", Revision: snap.R(7)},
+ },
+ Current: snap.R(7),
+ })
+ snapstate.Set(s.state, "bar", &snapstate.SnapState{
+ Active: false,
+ Sequence: []*snap.SideInfo{
+ {RealName: "bar", SnapID: "bar-id", Revision: snap.R(3)},
+ },
+ Current: snap.R(3),
+ })
+ snapstate.Set(s.state, "local", &snapstate.SnapState{
+ Active: false,
+ Sequence: []*snap.SideInfo{
+ {RealName: "local", Revision: snap.R(-1)},
+ },
+ Current: snap.R(-1),
+ })
+
+ // previous state
+ err = assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, s.dev1Acct)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclBar)
+ c.Assert(err, IsNil)
+
+ // one changed assertion
+ headers = map[string]interface{}{
+ "series": "16",
+ "snap-id": "foo-id",
+ "snap-name": "fo-o",
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ "revision": "1",
+ }
+ snapDeclFoo1, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = s.storeSigning.Add(snapDeclFoo1)
+ c.Assert(err, IsNil)
+
+ err = assertstate.RefreshSnapDeclarations(s.state, 0)
+ c.Assert(err, IsNil)
+
+ a, err := assertstate.DB(s.state).Find(asserts.SnapDeclarationType, map[string]string{
+ "series": "16",
+ "snap-id": "foo-id",
+ })
+ c.Assert(err, IsNil)
+ c.Check(a.(*asserts.SnapDeclaration).SnapName(), Equals, "fo-o")
+
+ // another one
+ // one changed assertion
+ headers = s.dev1Acct.Headers()
+ headers["display-name"] = "Dev 1 edited display-name"
+ headers["revision"] = "1"
+ dev1Acct1, err := s.storeSigning.Sign(asserts.AccountType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = s.storeSigning.Add(dev1Acct1)
+ c.Assert(err, IsNil)
+
+ err = assertstate.RefreshSnapDeclarations(s.state, 0)
+ c.Assert(err, IsNil)
+
+ a, err = assertstate.DB(s.state).Find(asserts.AccountType, map[string]string{
+ "account-id": s.dev1Acct.AccountID(),
+ })
+ c.Assert(err, IsNil)
+ c.Check(a.(*asserts.Account).DisplayName(), Equals, "Dev 1 edited display-name")
+}
diff --git a/overlord/assertstate/export_test.go b/overlord/assertstate/export_test.go
index ca343445d7..1646c05534 100644
--- a/overlord/assertstate/export_test.go
+++ b/overlord/assertstate/export_test.go
@@ -21,5 +21,5 @@ package assertstate
// expose for testing
var (
- Fetch = fetch
+ DoFetch = doFetch
)
diff --git a/overlord/hookstate/hookmgr.go b/overlord/hookstate/hookmgr.go
index 1c32cad56d..fdc8ffeb64 100644
--- a/overlord/hookstate/hookmgr.go
+++ b/overlord/hookstate/hookmgr.go
@@ -36,9 +36,6 @@ import (
"github.com/snapcore/snapd/snap"
)
-// HookRunner is the hook runner. Exported here for use in tests.
-var HookRunner = runHookAndWait
-
// HookManager is responsible for the maintenance of hooks in the system state.
// It runs hooks when they're requested, assuming they're present in the given
// snap. Otherwise they're skipped with no error.
@@ -185,7 +182,7 @@ func (m *HookManager) doRunHook(task *state.Task, tomb *tomb.Tomb) error {
}
// Actually run the hook
- output, err := HookRunner(setup.Snap, setup.Revision, setup.Hook, contextID, tomb)
+ output, err := runHookAndWait(setup.Snap, setup.Revision, setup.Hook, contextID, tomb)
if err != nil {
err = osutil.OutputErr(output, err)
if handlerErr := handler.Error(err); handlerErr != nil {
diff --git a/overlord/patch/patch4.go b/overlord/patch/patch4.go