summaryrefslogtreecommitdiff
diff options
authorJohn R. Lenton <jlenton@gmail.com>2018-10-08 21:38:47 +0100
committerJohn R. Lenton <jlenton@gmail.com>2018-10-08 21:38:47 +0100
commit305fcf51ad8142afdfa42edbe369be380ea90f5f (patch)
tree27349c37483db5c77bcedfa347629629b698326b
parent7d4183424ab6c4522ed7f266652b445822506cf0 (diff)
parentdb577fc35409fda4fc95681fbacc5a85bb36fadc (diff)
Merge remote-tracking branch 'upstream/master' into sync-lp-translationssync-lp-translations
-rw-r--r--.travis.yml27
-rw-r--r--asserts/store_asserts.go29
-rw-r--r--asserts/store_asserts_test.go15
-rw-r--r--client/client_test.go6
-rw-r--r--daemon/api_test.go10
-rw-r--r--interfaces/connection_test.go26
-rw-r--r--interfaces/policy/helpers.go26
-rw-r--r--interfaces/policy/policy.go2
-rw-r--r--interfaces/policy/policy_test.go135
-rw-r--r--interfaces/repo.go6
-rw-r--r--interfaces/repo_test.go91
-rw-r--r--osutil/context_test.go5
-rw-r--r--osutil/flock_test.go2
-rw-r--r--overlord/devicestate/devicestate_test.go2
-rw-r--r--overlord/ifacestate/handlers.go5
-rw-r--r--overlord/ifacestate/helpers.go22
-rw-r--r--overlord/ifacestate/ifacestate.go11
-rw-r--r--overlord/ifacestate/ifacestate_test.go211
-rw-r--r--overlord/managers_test.go4
-rw-r--r--overlord/overlord_test.go2
-rw-r--r--overlord/patch/patch.go2
-rw-r--r--overlord/patch/patch6.go2
-rw-r--r--overlord/patch/patch6_1.go149
-rw-r--r--overlord/patch/patch6_1_test.go284
-rw-r--r--overlord/snapstate/backend_test.go6
-rw-r--r--overlord/snapstate/snapstate.go15
-rw-r--r--overlord/snapstate/snapstate_test.go26
-rwxr-xr-xrun-checks46
-rw-r--r--systemd/export_test.go12
-rw-r--r--systemd/systemd.go13
-rw-r--r--wrappers/services_test.go13
31 files changed, 1072 insertions, 133 deletions
diff --git a/.travis.yml b/.travis.yml
index d79c139b45..1ceba66594 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,14 +13,17 @@ jobs:
- ./get-deps.sh
script:
- ./run-checks --static
- - ./run-checks --unit
+ - ./run-checks --short-unit
- stage: quick
go: 1.10
name: OSX build and minimal runtime sanity check
os: osx
+ addons:
+ homebrew:
+ packages: [squashfs]
install:
- - brew install squashfs
- ./get-deps.sh
+ # extra dependency on darwin:
- go get golang.org/x/sys/unix
before_script:
- ./mkversion.sh
@@ -51,13 +54,13 @@ jobs:
- true
script:
- ./run-checks --spread
- env:
- global:
- # SPREAD_LINODE_KEY
- - secure: "bzALrfNSLwM0bjceal1PU5rFErvqVhi00Sygx8jruo6htpZay3hrC2sHCKCQKPn1kvCfHidrHX1vnomg5N+B9o25GZEYSjKSGxuvdNDfCZYqPNjMbz5y7xXYfKWgyo+xtrKRM85Nqy121SfRz3KLDvrOLwwreb+pZv8DG1WraFTd7D6rK7nLnnYNUyw665XBMFVnM8ue3Zu9496Ih/TfQXhnNpsZY8xFWte4+cH7JvVCVTs8snjoGVZi3972PzinNkfBgJa24cUzxFMfiN/AwSBXJQKdVv+FsbB4uRgXAqTNwuus7PptiPNxpWWojuhm1Qgbk0XhGIdJxyUYkmNA4UrZ3C29nIRWbuAiHJ6ZWd1ur3dqphqOcgFInltSHkpfEdlL3YK4dCa2SmJESzotUGnyowCUUCXkWdDaZmFTwyK0Y6He9oyXDK5f+/U7SFlPvok0caJCvB9HbTQR1kYdh048I/R+Ht5QrFOZPk21DYWDOYhn7SzthBDZLsaL6n5gX7Y547SsL4B35YVbpaeHzccG6Mox8rI4bqlGFvP1U5i8uXD4uQjJChlVxpmozUEMok9T5RVediJs540p5uc8DQl48Nke02tXzC/XpGAvpnXT7eiiRNW67zOj2QcIV+ni3lBj3HvZeB9cgjzLNrZSl/t9vseqnNwQWpl3V6nd/bU="
- # SPREAD_STORE_USER
- - secure: "LjqfvJ2xz/7cxt1Cywaw5l8gaj5jOhUsf502UeaH+rOnj+9tCdWTtyP8U4nOjjQwiJ0xuygba+FgdnXEyxV+THeXHOF69SRF/1N8JIc3i9G6JK/CqDfFTRMqiRaCf5u7KuOrYZ0ssYNBXyZ8X4Ahls3uFu2DgEuAim1J6wOVSgIoUkduLVrbsn6uB9G5Uuc+C4NMA3TH21IJ6ct35t3T+/EjvoGUHcKtoOsPXdBZvz96xw5mKGIBaLpZdy5WxmhPUsz3MIlZgvi4DR3YIa/9u+QoGNU05f8upJRhwdwkuu9vJwqekXNXDJi/ZGlpkkAPx0feJbyhtz68551Pn1TtmA3TS5JtuMeMZWxCL9SudA7/C3oBRNGnKI3LwvP20pPjdlEYMOCq/oHlxoJylGVdpynZXTtaFS+s4Qhnr+WuNcG3zFa9bJvXPyy1vxPKcjI2DojneTrCTW/L6zg7tBIVQGzTxmC7QWsbTvOQzu+YICyeeS3g+iJ+QyP6+/oTyER3a3vmZCtXqsBJTznesS0SL5AkK+8moBGct96S6kT55XCDVgThWV0OGH6l4LwVSOjPioNzXNhVLZ8GKkXrMZXKSaWAeYptzWl4Gfz0Y4nFCu3aqIOyie7janPPgeEL0E2ZjndIs+ZigtN1LCol+GJN7fXzUFy8Fichqhhwvb3YLyE="
- # SPREAD_STORE_PASSWORD
- - secure: "Le4CMhklfadi4aBQIEaEMbsFIB608GOvSHjVUxkDxkkUAVwl/Ov4Dni5d0Tn4e/xcxPkcm+pPg64dn0Jxzwx6XfWlxhWC10vYh+/GjpZW1znahtb/Gf9CNZOJJEy5LSeI7/uJ3LYcFd0FU0EJSerNeQJc5d8jmJH8UnuqObHOk29YD//XILiLRa1XALEimwXeQyGQePBmDTxPQQv1VLFjgfaJa5Xy55Us7AKTML2V7lhaeKCSEIp3x9liLAtnKlJhyXaXO/e4b3ZJTgXwYh+vENK1E2pxalpjBNPaJNkvtbsjFtYNXJoXca+hBVs5Sq1PCBhkEGxqFUsD8VLQd+MEXp4MYOF5fBhxIa3qOSjtuR+WmZ9G6fEysEBV6Y3F3D6HYWTpNkcHNXJCwdtOM+n92zNEBDIrufwzTPpyJXpoxZCCXrk3HHRdyDktvJYLrHdn1bM19mgYguesMZHTC5xMD6ifwdRoylmApjImXOvVxf2HdQiNvNLDqvaHgmYwNfl0+KbaVz+O2EDPCRnT5wOCpSeSUet47EPITdjr5OnTwLpOVaY+iSvn90EUB/8+ZU01TRYgc+6VNPHokLVjuiQJSrE4yTx/c2MnY9eRaOosVXngYfoS/L3XwDwZiQoeLZs04bScvxzGQIGCJ+CBzNPENtZ4AUh55Yl/vVNReZJeaY="
- # SPREAD_GOOGLE_KEY
- - secure: "dIA2HrartowFL2Gl5jXiVMd9hIJyIeummYwxeBL9MzO48E/BIJyIGHudEOo8oCnZ5a0yb8TqYgND2FCgJU1V5I2LyxH6T9kizHjtmIGgeM4qlEGKRlptb2v7DFkaHeW4Mpp4gLk8hYIeWyq9OR+SlK6f0Jj049LLKfQoX6GzTPug5+MMEQOJs55OJ6f6gvCv2o3oj6WFybaohMCO4GbNYQSPLwheyTSkT0efnW9QqTN0w62pDMqscVURO90/CUeZyCcXw2uOBegwPNTBoo/+4+nZsfSNeupV8wX4vVYL0ZFL6IO3mViDoZBD4SGTNF/9x8Lc1WeKm9HlELzy5krdLqsvdV/fQSWhBzwkdykKVA3Aae5dAMIGRt7e5bJaUg+/HdtOgA5jr+qey/c/BN11MyaSOMNPNGjRuv9NAcEjxoN2JkiDXfpA3lE9kjd7TBTexGe4RJGJLJjT9s8XxdKufBfruC/yhVGdVkRoc2tsAJPZ72Ds9qH0FH28zNFAgAitCLDfInjhPMPvZJhb3Bqx5P/0DE5zUbduE9kYK0iiZRJ4AaytQy+R4nJCXE42mWv5cxoE84opVqO9cBu1TPCC8gTRQFWpJt1rP+DvwjaFiswvptG8obxNpHmkhcItPGmRVN9P9Yjd9nHvegS83tsbrd2KOyMmCk3/1KWhLufisHE="
+env:
+ global:
+ # SPREAD_LINODE_KEY
+ - secure: "bzALrfNSLwM0bjceal1PU5rFErvqVhi00Sygx8jruo6htpZay3hrC2sHCKCQKPn1kvCfHidrHX1vnomg5N+B9o25GZEYSjKSGxuvdNDfCZYqPNjMbz5y7xXYfKWgyo+xtrKRM85Nqy121SfRz3KLDvrOLwwreb+pZv8DG1WraFTd7D6rK7nLnnYNUyw665XBMFVnM8ue3Zu9496Ih/TfQXhnNpsZY8xFWte4+cH7JvVCVTs8snjoGVZi3972PzinNkfBgJa24cUzxFMfiN/AwSBXJQKdVv+FsbB4uRgXAqTNwuus7PptiPNxpWWojuhm1Qgbk0XhGIdJxyUYkmNA4UrZ3C29nIRWbuAiHJ6ZWd1ur3dqphqOcgFInltSHkpfEdlL3YK4dCa2SmJESzotUGnyowCUUCXkWdDaZmFTwyK0Y6He9oyXDK5f+/U7SFlPvok0caJCvB9HbTQR1kYdh048I/R+Ht5QrFOZPk21DYWDOYhn7SzthBDZLsaL6n5gX7Y547SsL4B35YVbpaeHzccG6Mox8rI4bqlGFvP1U5i8uXD4uQjJChlVxpmozUEMok9T5RVediJs540p5uc8DQl48Nke02tXzC/XpGAvpnXT7eiiRNW67zOj2QcIV+ni3lBj3HvZeB9cgjzLNrZSl/t9vseqnNwQWpl3V6nd/bU="
+ # SPREAD_STORE_USER
+ - secure: "LjqfvJ2xz/7cxt1Cywaw5l8gaj5jOhUsf502UeaH+rOnj+9tCdWTtyP8U4nOjjQwiJ0xuygba+FgdnXEyxV+THeXHOF69SRF/1N8JIc3i9G6JK/CqDfFTRMqiRaCf5u7KuOrYZ0ssYNBXyZ8X4Ahls3uFu2DgEuAim1J6wOVSgIoUkduLVrbsn6uB9G5Uuc+C4NMA3TH21IJ6ct35t3T+/EjvoGUHcKtoOsPXdBZvz96xw5mKGIBaLpZdy5WxmhPUsz3MIlZgvi4DR3YIa/9u+QoGNU05f8upJRhwdwkuu9vJwqekXNXDJi/ZGlpkkAPx0feJbyhtz68551Pn1TtmA3TS5JtuMeMZWxCL9SudA7/C3oBRNGnKI3LwvP20pPjdlEYMOCq/oHlxoJylGVdpynZXTtaFS+s4Qhnr+WuNcG3zFa9bJvXPyy1vxPKcjI2DojneTrCTW/L6zg7tBIVQGzTxmC7QWsbTvOQzu+YICyeeS3g+iJ+QyP6+/oTyER3a3vmZCtXqsBJTznesS0SL5AkK+8moBGct96S6kT55XCDVgThWV0OGH6l4LwVSOjPioNzXNhVLZ8GKkXrMZXKSaWAeYptzWl4Gfz0Y4nFCu3aqIOyie7janPPgeEL0E2ZjndIs+ZigtN1LCol+GJN7fXzUFy8Fichqhhwvb3YLyE="
+ # SPREAD_STORE_PASSWORD
+ - secure: "Le4CMhklfadi4aBQIEaEMbsFIB608GOvSHjVUxkDxkkUAVwl/Ov4Dni5d0Tn4e/xcxPkcm+pPg64dn0Jxzwx6XfWlxhWC10vYh+/GjpZW1znahtb/Gf9CNZOJJEy5LSeI7/uJ3LYcFd0FU0EJSerNeQJc5d8jmJH8UnuqObHOk29YD//XILiLRa1XALEimwXeQyGQePBmDTxPQQv1VLFjgfaJa5Xy55Us7AKTML2V7lhaeKCSEIp3x9liLAtnKlJhyXaXO/e4b3ZJTgXwYh+vENK1E2pxalpjBNPaJNkvtbsjFtYNXJoXca+hBVs5Sq1PCBhkEGxqFUsD8VLQd+MEXp4MYOF5fBhxIa3qOSjtuR+WmZ9G6fEysEBV6Y3F3D6HYWTpNkcHNXJCwdtOM+n92zNEBDIrufwzTPpyJXpoxZCCXrk3HHRdyDktvJYLrHdn1bM19mgYguesMZHTC5xMD6ifwdRoylmApjImXOvVxf2HdQiNvNLDqvaHgmYwNfl0+KbaVz+O2EDPCRnT5wOCpSeSUet47EPITdjr5OnTwLpOVaY+iSvn90EUB/8+ZU01TRYgc+6VNPHokLVjuiQJSrE4yTx/c2MnY9eRaOosVXngYfoS/L3XwDwZiQoeLZs04bScvxzGQIGCJ+CBzNPENtZ4AUh55Yl/vVNReZJeaY="
+ # SPREAD_GOOGLE_KEY
+ - secure: "dIA2HrartowFL2Gl5jXiVMd9hIJyIeummYwxeBL9MzO48E/BIJyIGHudEOo8oCnZ5a0yb8TqYgND2FCgJU1V5I2LyxH6T9kizHjtmIGgeM4qlEGKRlptb2v7DFkaHeW4Mpp4gLk8hYIeWyq9OR+SlK6f0Jj049LLKfQoX6GzTPug5+MMEQOJs55OJ6f6gvCv2o3oj6WFybaohMCO4GbNYQSPLwheyTSkT0efnW9QqTN0w62pDMqscVURO90/CUeZyCcXw2uOBegwPNTBoo/+4+nZsfSNeupV8wX4vVYL0ZFL6IO3mViDoZBD4SGTNF/9x8Lc1WeKm9HlELzy5krdLqsvdV/fQSWhBzwkdykKVA3Aae5dAMIGRt7e5bJaUg+/HdtOgA5jr+qey/c/BN11MyaSOMNPNGjRuv9NAcEjxoN2JkiDXfpA3lE9kjd7TBTexGe4RJGJLJjT9s8XxdKufBfruC/yhVGdVkRoc2tsAJPZ72Ds9qH0FH28zNFAgAitCLDfInjhPMPvZJhb3Bqx5P/0DE5zUbduE9kYK0iiZRJ4AaytQy+R4nJCXE42mWv5cxoE84opVqO9cBu1TPCC8gTRQFWpJt1rP+DvwjaFiswvptG8obxNpHmkhcItPGmRVN9P9Yjd9nHvegS83tsbrd2KOyMmCk3/1KWhLufisHE="
diff --git a/asserts/store_asserts.go b/asserts/store_asserts.go
index b3cddaa243..74c559de96 100644
--- a/asserts/store_asserts.go
+++ b/asserts/store_asserts.go
@@ -26,11 +26,12 @@ import (
)
// Store holds a store assertion, defining the configuration needed to connect
-// a device to the store.
+// a device to the store or relative to a non-default store.
type Store struct {
assertionBase
- url *url.URL
- timestamp time.Time
+ url *url.URL
+ friendlyStores []string
+ timestamp time.Time
}
// Store returns the identifying name of the operator's store.
@@ -48,6 +49,12 @@ func (store *Store) URL() *url.URL {
return store.url
}
+// FriendlyStores returns stores holding snaps that are also exposed
+// through this one.
+func (store *Store) FriendlyStores() []string {
+ return store.friendlyStores
+}
+
// Location returns a summary of the store's location/purpose.
func (store *Store) Location() string {
return store.HeaderString("location")
@@ -59,7 +66,9 @@ func (store *Store) Timestamp() time.Time {
}
func (store *Store) checkConsistency(db RODatabase, acck *AccountKey) error {
- // Will be applied to a system's snapd so must be signed by a trusted authority.
+ // Will be applied to a system's snapd or influence snapd
+ // policy decisions (via friendly-stores) so must be signed by a trusted
+ // authority!
if !db.IsTrustedAccount(store.AuthorityID()) {
return fmt.Errorf("store assertion %q is not signed by a directly trusted authority: %s",
store.Store(), store.AuthorityID())
@@ -129,6 +138,11 @@ func assembleStore(assert assertionBase) (Assertion, error) {
return nil, err
}
+ friendlyStores, err := checkStringList(assert.headers, "friendly-stores")
+ if err != nil {
+ return nil, err
+ }
+
_, err = checkOptionalString(assert.headers, "location")
if err != nil {
return nil, err
@@ -140,8 +154,9 @@ func assembleStore(assert assertionBase) (Assertion, error) {
}
return &Store{
- assertionBase: assert,
- url: url,
- timestamp: timestamp,
+ assertionBase: assert,
+ url: url,
+ friendlyStores: friendlyStores,
+ timestamp: timestamp,
}, nil
}
diff --git a/asserts/store_asserts_test.go b/asserts/store_asserts_test.go
index 62003e22a6..8bc37d1fab 100644
--- a/asserts/store_asserts_test.go
+++ b/asserts/store_asserts_test.go
@@ -63,6 +63,7 @@ func (s *storeSuite) TestDecodeOK(c *C) {
c.Check(store.URL().String(), Equals, "https://store.example.com")
c.Check(store.Location(), Equals, "upstairs")
c.Check(store.Timestamp().Equal(s.ts), Equals, true)
+ c.Check(store.FriendlyStores(), HasLen, 0)
}
var storeErrPrefix = "assertion store: "
@@ -78,6 +79,7 @@ func (s *storeSuite) TestDecodeInvalidHeaders(c *C) {
{s.tsLine, "", `"timestamp" header is mandatory`},
{s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
{s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
+ {"url: https://store.example.com\n", "friendly-stores: foo\n", `"friendly-stores" header must be a list of strings`},
}
for _, test := range tests {
@@ -187,6 +189,19 @@ func (s *storeSuite) TestCheckAuthority(c *C) {
c.Assert(err, IsNil)
}
+func (s *storeSuite) TestFriendlyStores(c *C) {
+ encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", `friendly-stores:
+ - store1
+ - store2
+ - store3
+`, 1)
+ assert, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ store := assert.(*asserts.Store)
+ c.Check(store.URL(), IsNil)
+ c.Check(store.FriendlyStores(), DeepEquals, []string{"store1", "store2", "store3"})
+}
+
func (s *storeSuite) TestCheckOperatorAccount(c *C) {
storeDB, db := makeStoreAndCheckDB(c)
diff --git a/client/client_test.go b/client/client_test.go
index 09596315bd..c63cde3bf2 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -51,6 +51,7 @@ type clientSuite struct {
doCalls int
header http.Header
status int
+ restore func()
}
var _ = Suite(&clientSuite{})
@@ -70,10 +71,13 @@ func (cs *clientSuite) SetUpTest(c *C) {
cs.doCalls = 0
dirs.SetRootDir(c.MkDir())
+
+ cs.restore = client.MockDoRetry(time.Millisecond, 10*time.Millisecond)
}
func (cs *clientSuite) TearDownTest(c *C) {
os.Unsetenv(client.TestAuthFileEnvKey)
+ cs.restore()
}
func (cs *clientSuite) Do(req *http.Request) (*http.Response, error) {
@@ -99,8 +103,6 @@ func (cs *clientSuite) TestNewPanics(c *C) {
}
func (cs *clientSuite) TestClientDoReportsErrors(c *C) {
- restore := client.MockDoRetry(10*time.Millisecond, 100*time.Millisecond)
- defer restore()
cs.err = errors.New("ouchie")
err := cs.cli.Do("GET", "/", nil, nil, nil)
c.Check(err, ErrorMatches, "cannot communicate with server: ouchie")
diff --git a/daemon/api_test.go b/daemon/api_test.go
index 78b3a5b134..f865935172 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -3952,7 +3952,7 @@ func (s *apiSuite) TestInterfacesLegacy(c *check.C) {
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
}
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, check.IsNil)
req, err := http.NewRequest("GET", "/v2/interfaces", nil)
@@ -4015,7 +4015,7 @@ func (s *apiSuite) TestInterfacesModern(c *check.C) {
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
}
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, check.IsNil)
req, err := http.NewRequest("GET", "/v2/interfaces?select=connected&doc=true&plugs=true&slots=true", nil)
@@ -4204,7 +4204,7 @@ func (s *apiSuite) TestConnectAlreadyConnected(c *check.C) {
d.overlord.Loop()
defer d.overlord.Stop()
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, check.IsNil)
conns := map[string]interface{}{
"consumer:plug producer:slot": map[string]interface{}{
@@ -4385,7 +4385,7 @@ func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slot
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
}
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, check.IsNil)
st := d.overlord.State()
@@ -4598,7 +4598,7 @@ func (s *apiSuite) TestDisconnectCoreSystemAlias(c *check.C) {
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"},
}
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, check.IsNil)
st := d.overlord.State()
diff --git a/interfaces/connection_test.go b/interfaces/connection_test.go
index 5337dbd292..ef24d52303 100644
--- a/interfaces/connection_test.go
+++ b/interfaces/connection_test.go
@@ -329,3 +329,29 @@ func (s *connSuite) TestCopyAttributes(c *C) {
cpy["e"].(map[string]interface{})["e1"] = "x"
c.Check(orig["e"].(map[string]interface{})["e1"], Equals, "E1")
}
+
+func (s *connSuite) TestNewConnectedPlugExplicitStaticAttrs(c *C) {
+ staticAttrs := map[string]interface{}{
+ "baz": "boom",
+ }
+ dynAttrs := map[string]interface{}{
+ "foo": "bar",
+ }
+ plug := NewConnectedPlug(s.plug, staticAttrs, dynAttrs)
+ c.Assert(plug, NotNil)
+ c.Assert(plug.StaticAttrs(), DeepEquals, map[string]interface{}{"baz": "boom"})
+ c.Assert(plug.DynamicAttrs(), DeepEquals, map[string]interface{}{"foo": "bar"})
+}
+
+func (s *connSuite) TestNewConnectedSlotExplicitStaticAttrs(c *C) {
+ staticAttrs := map[string]interface{}{
+ "baz": "boom",
+ }
+ dynAttrs := map[string]interface{}{
+ "foo": "bar",
+ }
+ slot := NewConnectedSlot(s.slot, staticAttrs, dynAttrs)
+ c.Assert(slot, NotNil)
+ c.Assert(slot.StaticAttrs(), DeepEquals, map[string]interface{}{"baz": "boom"})
+ c.Assert(slot.DynamicAttrs(), DeepEquals, map[string]interface{}{"foo": "bar"})
+}
diff --git a/interfaces/policy/helpers.go b/interfaces/policy/helpers.go
index d238ca8222..9dc976ba75 100644
--- a/interfaces/policy/helpers.go
+++ b/interfaces/policy/helpers.go
@@ -95,16 +95,30 @@ func checkOnClassic(c *asserts.OnClassicConstraint) error {
return nil
}
-func checkDeviceScope(c *asserts.DeviceScopeConstraint, model *asserts.Model) error {
+func checkDeviceScope(c *asserts.DeviceScopeConstraint, model *asserts.Model, store *asserts.Store) error {
if c == nil {
return nil
}
if model == nil {
return fmt.Errorf("cannot match on-store/on-brand/on-model without model")
}
+ if store != nil && store.Store() != model.Store() {
+ return fmt.Errorf("store assertion and model store must match")
+ }
if len(c.Store) != 0 {
if !strutil.ListContains(c.Store, model.Store()) {
- return fmt.Errorf("on-store mismatch")
+ mismatch := true
+ if store != nil {
+ for _, sto := range c.Store {
+ if strutil.ListContains(store.FriendlyStores(), sto) {
+ mismatch = false
+ break
+ }
+ }
+ }
+ if mismatch {
+ return fmt.Errorf("on-store mismatch")
+ }
}
}
if len(c.Brand) != 0 {
@@ -143,7 +157,7 @@ func checkPlugConnectionConstraints1(connc *ConnectCandidate, cstrs *asserts.Plu
if err := checkOnClassic(cstrs.OnClassic); err != nil {
return err
}
- if err := checkDeviceScope(cstrs.DeviceScope, connc.Model); err != nil {
+ if err := checkDeviceScope(cstrs.DeviceScope, connc.Model, connc.Store); err != nil {
return err
}
return nil
@@ -186,7 +200,7 @@ func checkSlotConnectionConstraints1(connc *ConnectCandidate, cstrs *asserts.Slo
if err := checkOnClassic(cstrs.OnClassic); err != nil {
return err
}
- if err := checkDeviceScope(cstrs.DeviceScope, connc.Model); err != nil {
+ if err := checkDeviceScope(cstrs.DeviceScope, connc.Model, connc.Store); err != nil {
return err
}
return nil
@@ -218,7 +232,7 @@ func checkSlotInstallationConstraints1(ic *InstallCandidate, slot *snap.SlotInfo
if err := checkOnClassic(cstrs.OnClassic); err != nil {
return err
}
- if err := checkDeviceScope(cstrs.DeviceScope, ic.Model); err != nil {
+ if err := checkDeviceScope(cstrs.DeviceScope, ic.Model, ic.Store); err != nil {
return err
}
return nil
@@ -250,7 +264,7 @@ func checkPlugInstallationConstraints1(ic *InstallCandidate, plug *snap.PlugInfo
if err := checkOnClassic(cstrs.OnClassic); err != nil {
return err
}
- if err := checkDeviceScope(cstrs.DeviceScope, ic.Model); err != nil {
+ if err := checkDeviceScope(cstrs.DeviceScope, ic.Model, ic.Store); err != nil {
return err
}
return nil
diff --git a/interfaces/policy/policy.go b/interfaces/policy/policy.go
index 4f3424ffea..72793b2106 100644
--- a/interfaces/policy/policy.go
+++ b/interfaces/policy/policy.go
@@ -38,6 +38,7 @@ type InstallCandidate struct {
BaseDeclaration *asserts.BaseDeclaration
Model *asserts.Model
+ Store *asserts.Store
}
func (ic *InstallCandidate) checkSlotRule(slot *snap.SlotInfo, rule *asserts.SlotRule, snapRule bool) error {
@@ -128,6 +129,7 @@ type ConnectCandidate struct {
BaseDeclaration *asserts.BaseDeclaration
Model *asserts.Model
+ Store *asserts.Store
}
func nestedGet(which string, attrs interfaces.Attrer, path string) (interface{}, error) {
diff --git a/interfaces/policy/policy_test.go b/interfaces/policy/policy_test.go
index 14af2b1718..42bc153aba 100644
--- a/interfaces/policy/policy_test.go
+++ b/interfaces/policy/policy_test.go
@@ -1639,6 +1639,9 @@ var (
otherModel *asserts.Model
myModel1 *asserts.Model
myModel2 *asserts.Model
+ myModel3 *asserts.Model
+
+ substore1 *asserts.Store
)
func init() {
@@ -1693,6 +1696,41 @@ AXNpZw==`))
panic(err)
}
myModel2 = a.(*asserts.Model)
+
+ a, err = asserts.Decode([]byte(`type: model
+authority-id: my-brand
+series: 16
+brand-id: my-brand
+model: my-model3
+store: substore1
+architecture: armhf
+kernel: krnl
+gadget: gadget
+timestamp: 2018-09-12T12:00:00Z
+sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
+
+AXNpZw==`))
+ if err != nil {
+ panic(err)
+ }
+ myModel3 = a.(*asserts.Model)
+
+ a, err = asserts.Decode([]byte(`type: store
+store: substore1
+authority-id: canonical
+operator-id: canonical
+friendly-stores:
+ - a-store
+ - store1
+ - store2
+timestamp: 2018-09-12T12:00:00Z
+sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
+
+AXNpZw==`))
+ if err != nil {
+ panic(err)
+ }
+ substore1 = a.(*asserts.Store)
}
func (s *policySuite) TestPlugDeviceScopeCheckAutoConnection(c *C) {
@@ -1737,6 +1775,40 @@ func (s *policySuite) TestPlugDeviceScopeCheckAutoConnection(c *C) {
}
}
+func (s *policySuite) TestPlugDeviceScopeFriendlyStoreCheckAutoConnection(c *C) {
+ tests := []struct {
+ model *asserts.Model
+ store *asserts.Store
+ iface string
+ err string // "" => no error
+ }{
+ {nil, nil, "auto-plug-on-store1", `auto-connection not allowed by plug rule of interface "auto-plug-on-store1" for "plug-snap" snap`},
+ {myModel3, nil, "auto-plug-on-store1", `auto-connection not allowed by plug rule of interface "auto-plug-on-store1" for "plug-snap" snap`},
+ {myModel3, substore1, "auto-plug-on-store1", ""},
+ {myModel2, substore1, "auto-plug-on-store1", `auto-connection not allowed by plug rule of interface "auto-plug-on-store1" for "plug-snap" snap`},
+ }
+
+ for _, t := range tests {
+ cand := policy.ConnectCandidate{
+ Plug: interfaces.NewConnectedPlug(s.plugSnap.Plugs[t.iface], nil, nil),
+ Slot: interfaces.NewConnectedSlot(s.slotSnap.Slots[t.iface], nil, nil),
+ PlugSnapDeclaration: s.plugDecl,
+ SlotSnapDeclaration: s.slotDecl,
+
+ BaseDeclaration: s.baseDecl,
+
+ Model: t.model,
+ Store: t.store,
+ }
+ err := cand.CheckAutoConnect()
+ if t.err == "" {
+ c.Check(err, IsNil)
+ } else {
+ c.Check(err, ErrorMatches, t.err)
+ }
+ }
+}
+
func (s *policySuite) TestSlotDeviceScopeCheckAutoConnection(c *C) {
tests := []struct {
model *asserts.Model
@@ -1779,6 +1851,40 @@ func (s *policySuite) TestSlotDeviceScopeCheckAutoConnection(c *C) {
}
}
+func (s *policySuite) TestSlotDeviceScopeFriendlyStoreCheckAutoConnection(c *C) {
+ tests := []struct {
+ model *asserts.Model
+ store *asserts.Store
+ iface string
+ err string // "" => no error
+ }{
+ {nil, nil, "auto-slot-on-store1", `auto-connection not allowed by slot rule of interface "auto-slot-on-store1" for "slot-snap" snap`},
+ {myModel3, nil, "auto-slot-on-store1", `auto-connection not allowed by slot rule of interface "auto-slot-on-store1" for "slot-snap" snap`},
+ {myModel3, substore1, "auto-slot-on-store1", ""},
+ {myModel2, substore1, "auto-slot-on-store1", `auto-connection not allowed by slot rule of interface "auto-slot-on-store1" for "slot-snap" snap`},
+ }
+
+ for _, t := range tests {
+ cand := policy.ConnectCandidate{
+ Plug: interfaces.NewConnectedPlug(s.plugSnap.Plugs[t.iface], nil, nil),
+ Slot: interfaces.NewConnectedSlot(s.slotSnap.Slots[t.iface], nil, nil),
+ PlugSnapDeclaration: s.plugDecl,
+ SlotSnapDeclaration: s.slotDecl,
+
+ BaseDeclaration: s.baseDecl,
+
+ Model: t.model,
+ Store: t.store,
+ }
+ err := cand.CheckAutoConnect()
+ if t.err == "" {
+ c.Check(err, IsNil)
+ } else {
+ c.Check(err, ErrorMatches, t.err)
+ }
+ }
+}
+
func (s *policySuite) TestDeviceScopeInstallation(c *C) {
const plugSnap = `name: install-snap
version: 0
@@ -1818,20 +1924,28 @@ slots:
tests := []struct {
model *asserts.Model
+ store *asserts.Store
installYaml string
plugsSlots string
err string // "" => no error
}{
- {nil, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
- {otherModel, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
- {myModel1, plugSnap, plugOnStore1, ""},
- {myModel2, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
- {otherModel, plugSnap, plugOnMulti, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
- {myModel1, plugSnap, plugOnMulti, ""},
- {myModel2, plugSnap, plugOnMulti, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
- {otherModel, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for "install-snap" snap`},
- {myModel1, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for "install-snap" snap`},
- {myModel2, slotSnap, slotOnStore2, ""},
+ {nil, nil, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {otherModel, nil, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {myModel1, nil, plugSnap, plugOnStore1, ""},
+ {myModel2, nil, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {otherModel, nil, plugSnap, plugOnMulti, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {myModel1, nil, plugSnap, plugOnMulti, ""},
+ {myModel2, nil, plugSnap, plugOnMulti, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {otherModel, nil, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for "install-snap" snap`},
+ {myModel1, nil, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for "install-snap" snap`},
+ {myModel2, nil, slotSnap, slotOnStore2, ""},
+ // friendly-stores
+ {myModel3, nil, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {myModel3, substore1, plugSnap, plugOnStore1, ""},
+ {myModel2, substore1, plugSnap, plugOnStore1, `installation not allowed by "install-plug-device-scope" plug rule of interface "install-plug-device-scope" for "install-snap" snap`},
+ {myModel3, nil, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for \"install-snap\" snap`},
+ {myModel3, substore1, slotSnap, slotOnStore2, ""},
+ {myModel2, substore1, slotSnap, slotOnStore2, `installation not allowed by "install-slot-device-scope" slot rule of interface "install-slot-device-scope" for \"install-snap\" snap`},
}
for _, t := range tests {
@@ -1856,6 +1970,7 @@ AXNpZw==`, "@plugsSlots@", strings.TrimSpace(t.plugsSlots), 1)))
SnapDeclaration: snapDecl,
BaseDeclaration: s.baseDecl,
Model: t.model,
+ Store: t.store,
}
err = cand.Check()
if t.err == "" {
diff --git a/interfaces/repo.go b/interfaces/repo.go
index 73eaee06ae..27a80e0435 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -612,7 +612,7 @@ type PolicyFunc func(*ConnectedPlug, *ConnectedSlot) (bool, error)
// Connect establishes a connection between a plug and a slot.
// The plug and the slot must have the same interface.
// When connections are reloaded policyCheck is null (we don't check policy again).
-func (r *Repository) Connect(ref *ConnRef, plugDynamicAttrs, slotDynamicAttrs map[string]interface{}, policyCheck PolicyFunc) (*Connection, error) {
+func (r *Repository) Connect(ref *ConnRef, plugStaticAttrs, plugDynamicAttrs, slotStaticAttrs, slotDynamicAttrs map[string]interface{}, policyCheck PolicyFunc) (*Connection, error) {
r.m.Lock()
defer r.m.Unlock()
@@ -642,8 +642,8 @@ func (r *Repository) Connect(ref *ConnRef, plugDynamicAttrs, slotDynamicAttrs ma
return nil, fmt.Errorf("internal error: unknown interface %q", plug.Interface)
}
- cplug := NewConnectedPlug(plug, nil, plugDynamicAttrs)
- cslot := NewConnectedSlot(slot, nil, slotDynamicAttrs)
+ cplug := NewConnectedPlug(plug, plugStaticAttrs, plugDynamicAttrs)
+ cslot := NewConnectedSlot(slot, slotStaticAttrs, slotDynamicAttrs)
// policyCheck is null when reloading connections
if policyCheck != nil {
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index ee4dfe66d5..61e9a70072 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -409,7 +409,7 @@ func (s *RepositorySuite) TestRemovePlugFailsWhenPlugIsConnected(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Removing a plug used by a slot returns an appropriate error
err = s.testRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name)
@@ -747,7 +747,7 @@ func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotIsConnected(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Removing a slot occupied by a plug returns an appropriate error
err = s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name)
@@ -1227,7 +1227,7 @@ func (s *RepositorySuite) TestResolveDisconnectMatrixTypical(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err := s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
scenarios := []struct {
@@ -1309,7 +1309,7 @@ func (s *RepositorySuite) TestConnectFailsWhenPlugDoesNotExist(c *C) {
c.Assert(err, IsNil)
// Connecting an unknown plug returns an appropriate error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect plug "plug" from snap "consumer": no such plug`)
}
@@ -1318,7 +1318,7 @@ func (s *RepositorySuite) TestConnectFailsWhenSlotDoesNotExist(c *C) {
c.Assert(err, IsNil)
// Connecting to an unknown slot returns an error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect slot "slot" from snap "producer": no such slot`)
}
@@ -1328,7 +1328,7 @@ func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- conn, err := s.testRepo.Connect(connRef, nil, nil, nil)
+ conn, err := s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
c.Assert(conn, NotNil)
c.Assert(conn.Plug, NotNil)
@@ -1336,7 +1336,7 @@ func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) {
c.Assert(conn.Plug.Name(), Equals, "plug")
c.Assert(conn.Slot.Name(), Equals, "slot")
// Connecting exactly the same thing twice succeeds without an error but does nothing.
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Only one connection is actually present.
c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
@@ -1361,7 +1361,7 @@ func (s *RepositorySuite) TestConnectFailsWhenSlotAndPlugAreIncompatible(c *C) {
c.Assert(err, IsNil)
// Connecting a plug to an incompatible slot fails with an appropriate error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect plug "consumer:plug" \(interface "other-interface"\) to "producer:slot" \(interface "interface"\)`)
}
@@ -1372,7 +1372,7 @@ func (s *RepositorySuite) TestConnectSucceeds(c *C) {
c.Assert(err, IsNil)
// Connecting a plug works okay
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
}
@@ -1416,9 +1416,9 @@ func (s *RepositorySuite) TestDisconnectFailsWhenNotConnected(c *C) {
func (s *RepositorySuite) TestDisconnectSucceeds(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err = s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
err = s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name)
c.Assert(err, IsNil)
@@ -1454,7 +1454,7 @@ func (s *RepositorySuite) TestConnectedFailsWithoutPlugOrSlot(c *C) {
func (s *RepositorySuite) TestConnectedFindsConnections(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connected(s.plug.Snap.InstanceName(), s.plug.Name)
@@ -1475,7 +1475,7 @@ func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) {
}
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plug, slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connected("", s.slot.Name)
@@ -1487,7 +1487,7 @@ func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) {
func (s *RepositorySuite) TestConnections(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connections(s.plug.Snap.InstanceName())
@@ -1506,7 +1506,7 @@ func (s *RepositorySuite) TestConnections(c *C) {
func (s *RepositorySuite) TestConnectionsWithSelfConnected(c *C) {
c.Assert(s.testRepo.AddPlug(s.plugSelf), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plugSelf, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plugSelf, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connections(s.plugSelf.Snap.InstanceName())
@@ -1523,7 +1523,7 @@ func (s *RepositorySuite) TestConnectionsWithSelfConnected(c *C) {
func (s *RepositorySuite) TestDisconnectAll(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns := []*ConnRef{NewConnRef(s.plug, s.slot)}
@@ -1543,7 +1543,7 @@ func (s *RepositorySuite) TestInterfacesSmokeTest(c *C) {
c.Assert(err, IsNil)
// After connecting the result is as expected
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
ifaces := s.testRepo.Interfaces()
c.Assert(ifaces, DeepEquals, &Interfaces{
@@ -1604,7 +1604,7 @@ func (s *RepositorySuite) TestSnapSpecification(c *C) {
// Establish connection between plug and slot
connRef := NewConnRef(s.plug, s.slot)
- _, err = repo.Connect(connRef, nil, nil, nil)
+ _, err = repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Snaps should get static and connection-specific security now
@@ -1642,7 +1642,7 @@ func (s *RepositorySuite) TestSnapSpecificationFailureWithConnectionSnippets(c *
c.Assert(repo.AddPlug(s.plug), IsNil)
c.Assert(repo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
@@ -1672,7 +1672,7 @@ func (s *RepositorySuite) TestSnapSpecificationFailureWithPermanentSnippets(c *C
c.Assert(repo.AddPlug(s.plug), IsNil)
c.Assert(repo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := repo.Connect(connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName())
@@ -1928,7 +1928,7 @@ func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedPlug(c *C) {
_, err = s.addSnap(c, testProducerYaml)
c.Assert(err, IsNil)
connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
- _, err = s.repo.Connect(connRef, nil, nil, nil)
+ _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
err = s.repo.RemoveSnap("consumer")
c.Assert(err, ErrorMatches, "cannot remove connected plug consumer.iface")
@@ -1940,7 +1940,7 @@ func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedSlot(c *C) {
_, err = s.addSnap(c, testProducerYaml)
c.Assert(err, IsNil)
connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
- _, err = s.repo.Connect(connRef, nil, nil, nil)
+ _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
err = s.repo.RemoveSnap("producer")
c.Assert(err, ErrorMatches, "cannot remove connected slot producer.iface")
@@ -2013,7 +2013,7 @@ func (s *DisconnectSnapSuite) TestNotConnected(c *C) {
func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) {
connRef := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
- _, err := s.repo.Connect(connRef, nil, nil, nil)
+ _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Disconnect s1 with which has an outgoing connection to s2
affected, err := s.repo.DisconnectSnap("s1")
@@ -2024,7 +2024,7 @@ func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) {
func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) {
connRef := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
- _, err := s.repo.Connect(connRef, nil, nil, nil)
+ _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Disconnect s1 with which has an incoming connection from s2
affected, err := s.repo.DisconnectSnap("s1")
@@ -2037,10 +2037,10 @@ func (s *DisconnectSnapSuite) TestCrossConnection(c *C) {
// This test is symmetric wrt s1 <-> s2 connections
for _, snapName := range []string{"s1", "s2"} {
connRef1 := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
- _, err := s.repo.Connect(connRef1, nil, nil, nil)
+ _, err := s.repo.Connect(connRef1, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
connRef2 := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
- _, err = s.repo.Connect(connRef2, nil, nil, nil)
+ _, err = s.repo.Connect(connRef2, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
affected, err := s.repo.DisconnectSnap(snapName)
c.Assert(err, IsNil)
@@ -2050,14 +2050,14 @@ func (s *DisconnectSnapSuite) TestCrossConnection(c *C) {
}
func (s *DisconnectSnapSuite) TestParallelInstances(c *C) {
- _, err := s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2_instance", Name: "iface-a"}}, nil, nil, nil)
+ _, err := s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2_instance", Name: "iface-a"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
affected, err := s.repo.DisconnectSnap("s1")
c.Assert(err, IsNil)
c.Check(affected, testutil.Contains, "s1")
c.Check(affected, testutil.Contains, "s2_instance")
- _, err = s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s2_instance", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}, nil, nil, nil)
+ _, err = s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s2_instance", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
affected, err = s.repo.DisconnectSnap("s1")
c.Assert(err, IsNil)
@@ -2202,13 +2202,13 @@ apps:
c.Assert(r.AddSnap(s4), IsNil)
// Connect a few things for the tests below.
- _, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
+ _, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
+ _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil)
+ _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s4", Name: "i2"}, SlotRef: SlotRef{Snap: "s3_instance", Name: "i2"}}, nil, nil, nil)
+ _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s4", Name: "i2"}, SlotRef: SlotRef{Snap: "s3_instance", Name: "i2"}}, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
// Without any names or options we get the summary of all the interfaces.
@@ -2298,7 +2298,7 @@ func (s *RepositorySuite) TestBeforeConnectValidation(c *C) {
slotDynAttrs := map[string]interface{}{"attr1": "val1"}
policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
- conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
c.Assert(err, IsNil)
c.Assert(conn, NotNil)
@@ -2333,7 +2333,7 @@ func (s *RepositorySuite) TestBeforeConnectValidationFailure(c *C) {
policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
- conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `cannot connect plug "consumer" of snap "s1": invalid plug`)
c.Assert(conn, IsNil)
@@ -2359,7 +2359,7 @@ func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) {
return false, fmt.Errorf("policy check failed")
}
- conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `policy check failed`)
c.Assert(conn, IsNil)
@@ -2374,7 +2374,7 @@ func (s *RepositorySuite) TestConnection(c *C) {
conn, err := s.testRepo.Connection(connRef)
c.Assert(err, ErrorMatches, `no connection from consumer:plug to producer:slot`)
- _, err = s.testRepo.Connect(connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conn, err = s.testRepo.Connection(connRef)
@@ -2389,6 +2389,25 @@ func (s *RepositorySuite) TestConnection(c *C) {
c.Assert(err, ErrorMatches, `snap "a" has no slot named "b"`)
}
+func (s *RepositorySuite) TestConnectWithStaticAttrs(c *C) {
+ c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
+ c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
+
+ connRef := NewConnRef(s.plug, s.slot)
+
+ plugAttrs := map[string]interface{}{"foo": "bar"}
+ slotAttrs := map[string]interface{}{"boo": "baz"}
+ _, err := s.testRepo.Connect(connRef, plugAttrs, nil, slotAttrs, nil, nil)
+ c.Assert(err, IsNil)
+
+ conn, err := s.testRepo.Connection(connRef)
+ c.Assert(err, IsNil)
+ c.Assert(conn.Plug.Name(), Equals, "plug")
+ c.Assert(conn.Slot.Name(), Equals, "slot")
+ c.Assert(conn.Plug.StaticAttrs(), DeepEquals, plugAttrs)
+ c.Assert(conn.Slot.StaticAttrs(), DeepEquals, slotAttrs)
+}
+
type hotplugTestInterface struct{ InterfaceName string }
func (h *hotplugTestInterface) Name() string {
diff --git a/osutil/context_test.go b/osutil/context_test.go
index 147be71326..52ecbcd42b 100644
--- a/osutil/context_test.go
+++ b/osutil/context_test.go
@@ -23,6 +23,7 @@ import (
"io"
"os/exec"
"strings"
+ "testing"
"time"
"golang.org/x/net/context"
@@ -74,6 +75,10 @@ func (ctxSuite) TestRun(c *check.C) {
}
func (ctxSuite) TestRunRace(c *check.C) {
+ if testing.Short() {
+ c.Skip("skippinng non-short test")
+ }
+
// first, time how long /bin/false takes
t0 := time.Now()
cmderr := exec.Command("/bin/false").Run()
diff --git a/osutil/flock_test.go b/osutil/flock_test.go
index de32d28e15..26c85efbda 100644
--- a/osutil/flock_test.go
+++ b/osutil/flock_test.go
@@ -134,7 +134,7 @@ func (s *flockSuite) TestLockUnlockNonblockingWorks(c *C) {
if osutil.FileExists(lockPath) {
break
}
- time.Sleep(1 * time.Second)
+ time.Sleep(time.Millisecond)
}
lock, err := osutil.NewFileLock(lockPath)
diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go
index caaede5b1b..c88a7d349b 100644
--- a/overlord/devicestate/devicestate_test.go
+++ b/overlord/devicestate/devicestate_test.go
@@ -2350,7 +2350,7 @@ func makeMockRepoWithConnectedSnaps(c *C, st *state.State, info11, core11 *snap.
_, err = repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: info11.InstanceName(), Name: ifname},
SlotRef: interfaces.SlotRef{Snap: core11.InstanceName(), Name: ifname},
- }, nil, nil, nil)
+ }, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
conns, err := repo.Connected("snap-with-snapd-control", "snapd-control")
c.Assert(err, IsNil)
diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go
index ac93a32688..43e6ffcd85 100644
--- a/overlord/ifacestate/handlers.go
+++ b/overlord/ifacestate/handlers.go
@@ -408,7 +408,8 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) error {
policyChecker = policyCheck.check
}
- conn, err := m.repo.Connect(connRef, plugDynamicAttrs, slotDynamicAttrs, policyChecker)
+ // static attributes of the plug and slot not provided, the ones from snap infos will be used
+ conn, err := m.repo.Connect(connRef, nil, plugDynamicAttrs, nil, slotDynamicAttrs, policyChecker)
if err != nil || conn == nil {
return err
}
@@ -556,7 +557,7 @@ func (m *InterfaceManager) undoDisconnect(task *state.Task, _ *tomb.Tomb) error
return fmt.Errorf("snap %q has no %q slot", connRef.SlotRef.Snap, connRef.SlotRef.Name)
}
- _, err = m.repo.Connect(connRef, oldconn.DynamicPlugAttrs, oldconn.DynamicSlotAttrs, nil)
+ _, err = m.repo.Connect(connRef, nil, oldconn.DynamicPlugAttrs, nil, oldconn.DynamicSlotAttrs, nil)
if err != nil {
return err
}
diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go
index 2752540623..7c0bd381f5 100644
--- a/overlord/ifacestate/helpers.go
+++ b/overlord/ifacestate/helpers.go
@@ -276,7 +276,7 @@ func (m *InterfaceManager) reloadConnections(snapName string) ([]string, error)
}
// Note: reloaded connections are not checked against policy again, and also we don't call BeforeConnect* methods on them.
- if _, err := m.repo.Connect(connRef, conn.DynamicPlugAttrs, conn.DynamicSlotAttrs, nil); err != nil {
+ if _, err := m.repo.Connect(connRef, conn.StaticPlugAttrs, conn.DynamicPlugAttrs, conn.StaticSlotAttrs, conn.DynamicSlotAttrs, nil); err != nil {
if _, ok := err.(*interfaces.UnknownPlugSlotError); ok {
// Some versions of snapd may have left stray connections that
// don't have the corresponding plug or slot anymore. Before we
@@ -377,6 +377,15 @@ func (c *autoConnectChecker) check(plug *interfaces.ConnectedPlug, slot *interfa
return false, err
}
+ var storeAs *asserts.Store
+ if modelAs.Store() != "" {
+ var err error
+ storeAs, err = assertstate.Store(c.st, modelAs.Store())
+ if err != nil && !asserts.IsNotFound(err) {
+ return false, err
+ }
+ }
+
var plugDecl *asserts.SnapDeclaration
if plug.Snap().SnapID != "" {
var err error
@@ -405,6 +414,7 @@ func (c *autoConnectChecker) check(plug *interfaces.ConnectedPlug, slot *interfa
SlotSnapDeclaration: slotDecl,
BaseDeclaration: c.baseDecl,
Model: modelAs,
+ Store: storeAs,
}
return ic.CheckAutoConnect() == nil, nil
@@ -432,6 +442,15 @@ func (c *connectChecker) check(plug *interfaces.ConnectedPlug, slot *interfaces.
return false, fmt.Errorf("cannot get model assertion: %v", err)
}
+ var storeAs *asserts.Store
+ if modelAs.Store() != "" {
+ var err error
+ storeAs, err = assertstate.Store(c.st, modelAs.Store())
+ if err != nil && !asserts.IsNotFound(err) {
+ return false, err
+ }
+ }
+
var plugDecl *asserts.SnapDeclaration
if plug.Snap().SnapID != "" {
var err error
@@ -458,6 +477,7 @@ func (c *connectChecker) check(plug *interfaces.ConnectedPlug, slot *interfaces.
SlotSnapDeclaration: slotDecl,
BaseDeclaration: c.baseDecl,
Model: modelAs,
+ Store: storeAs,
}
// if either of plug or slot snaps don't have a declaration it
diff --git a/overlord/ifacestate/ifacestate.go b/overlord/ifacestate/ifacestate.go
index 4c4251fe14..f5a7f48de4 100644
--- a/overlord/ifacestate/ifacestate.go
+++ b/overlord/ifacestate/ifacestate.go
@@ -26,6 +26,7 @@ import (
"sync"
"time"
+ "github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/policy"
@@ -364,6 +365,15 @@ func CheckInterfaces(st *state.State, snapInfo *snap.Info) error {
return err
}
+ var storeAs *asserts.Store
+ if modelAs.Store() != "" {
+ var err error
+ storeAs, err = assertstate.Store(st, modelAs.Store())
+ if err != nil && !asserts.IsNotFound(err) {
+ return err
+ }
+ }
+
baseDecl, err := assertstate.BaseDeclaration(st)
if err != nil {
return fmt.Errorf("internal error: cannot find base declaration: %v", err)
@@ -379,6 +389,7 @@ func CheckInterfaces(st *state.State, snapInfo *snap.Info) error {
SnapDeclaration: snapDecl,
BaseDeclaration: baseDecl,
Model: modelAs,
+ Store: storeAs,
}
return ic.Check()
diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go
index da59902f3d..c2a129f67d 100644
--- a/overlord/ifacestate/ifacestate_test.go
+++ b/overlord/ifacestate/ifacestate_test.go
@@ -950,6 +950,45 @@ func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeRightStore(c *C)
})
}
+func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeWrongFriendlyStore(c *C) {
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"other-store"},
+ })
+
+ s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
+ c.Check(change.Err(), ErrorMatches, `(?s).*connection not allowed by plug rule of interface "test".*`)
+ c.Check(change.Status(), Equals, state.ErrorStatus)
+
+ repo := s.manager(c).Repository()
+ ifaces := repo.Interfaces()
+ c.Check(ifaces.Connections, HasLen, 0)
+ })
+}
+
+func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeRightFriendlyStore(c *C) {
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"my-store"},
+ })
+
+ s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
+ c.Assert(change.Err(), IsNil)
+ c.Check(change.Status(), Equals, state.DoneStatus)
+
+ repo := s.manager(c).Repository()
+ ifaces := repo.Interfaces()
+ c.Assert(ifaces.Connections, HasLen, 1)
+ c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
+ })
+}
+
func (s *interfaceManagerSuite) testConnectTaskCheckDeviceScope(c *C, check func(*state.Change)) {
restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
type: base-declaration
@@ -1884,6 +1923,53 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDevi
})
}
+// The auto-connect task will check snap declarations providing the
+// model assertion to fulfill device scope constraints: here the
+// wrong "friendly store"s of the store in the model assertion fail an
+// on-store constraint.
+func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeWrongFriendlyStore(c *C) {
+
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"other-store"},
+ })
+
+ s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
+ // Ensure nothing is connected.
+ c.Check(conns, HasLen, 0)
+ c.Check(repoConns, HasLen, 0)
+ })
+}
+
+// The auto-connect task will check snap declarations providing the
+// model assertion to fulfill device scope constraints: here a
+// "friendly store" of the store in the model assertion passes an
+// on-store constraint.
+func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeFriendlyStore(c *C) {
+
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"my-store"},
+ })
+
+ s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
+ // Ensure that "test" plug is now saved in the state as auto-connected.
+ c.Check(conns, DeepEquals, map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{"auto": true, "interface": "test",
+ "plug-static": map[string]interface{}{"attr1": "value1"},
+ "slot-static": map[string]interface{}{"attr2": "value2"},
+ }})
+ // Ensure that "test" is really connected.
+ c.Check(repoConns, HasLen, 1)
+ })
+}
+
func (s *interfaceManagerSuite) testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c *C, check func(map[string]interface{}, []*interfaces.ConnRef)) {
restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
type: base-declaration
@@ -1966,6 +2052,23 @@ func (s *interfaceManagerSuite) mockModel(c *C, extraHeaders map[string]interfac
c.Assert(err, IsNil)
}
+func (s *interfaceManagerSuite) mockStore(c *C, storeID string, extraHeaders map[string]interface{}) {
+ headers := map[string]interface{}{
+ "store": storeID,
+ "operator-id": s.storeSigning.AuthorityID,
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ for k, v := range extraHeaders {
+ headers[k] = v
+ }
+ storeAs, err := s.storeSigning.Sign(asserts.StoreType, headers, nil, "")
+ c.Assert(err, IsNil)
+ s.state.Lock()
+ defer s.state.Unlock()
+ err = assertstate.Add(s.state, storeAs)
+ c.Assert(err, IsNil)
+}
+
// The setup-profiles task will only touch connection state for the task it
// operates on or auto-connects to and will leave other state intact.
func (s *interfaceManagerSuite) TestDoSetupSnapSecurityKeepsExistingConnectionState(c *C) {
@@ -2650,7 +2753,17 @@ func (s *interfaceManagerSuite) TestManagerReloadsConnections(c *C) {
s.state.Lock()
s.state.Set("conns", map[string]interface{}{
- "consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
+ "consumer:plug producer:slot": map[string]interface{}{
+ "interface": "test",
+ "plug-static": map[string]interface{}{
+ "attr1": "value2",
+ "attr3": "value3",
+ },
+ "slot-static": map[string]interface{}{
+ "attr2": "value4",
+ "attr4": "value6",
+ },
+ },
})
s.state.Unlock()
@@ -2659,7 +2772,21 @@ func (s *interfaceManagerSuite) TestManagerReloadsConnections(c *C) {
ifaces := repo.Interfaces()
c.Assert(ifaces.Connections, HasLen, 1)
- c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
+ cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
+ c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{cref})
+
+ conn, err := repo.Connection(cref)
+ c.Assert(err, IsNil)
+ c.Assert(conn.Plug.Name(), Equals, "plug")
+ c.Assert(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{
+ "attr1": "value2",
+ "attr3": "value3",
+ })
+ c.Assert(conn.Slot.Name(), Equals, "slot")
+ c.Assert(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{
+ "attr2": "value4",
+ "attr4": "value6",
+ })
}
func (s *interfaceManagerSuite) TestManagerDoesntReloadUndesiredAutoconnections(c *C) {
@@ -2713,7 +2840,7 @@ func (s *interfaceManagerSuite) TestSetupProfilesDevModeMultiple(c *C) {
PlugRef: interfaces.PlugRef{Snap: siP.InstanceName(), Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: siC.InstanceName(), Name: "slot"},
}
- _, err = repo.Connect(connRef, nil, nil, nil)
+ _, err = repo.Connect(connRef, nil, nil, nil, nil, nil)
c.Assert(err, IsNil)
change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
@@ -2907,6 +3034,80 @@ slots:
c.Check(ifacestate.CheckInterfaces(s.state, snapInfo), ErrorMatches, `installation not allowed.*`)
}
+func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeRightFriendlyStore(c *C) {
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"my-store"},
+ })
+
+ restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
+type: base-declaration
+authority-id: canonical
+series: 16
+slots:
+ test:
+ deny-installation: true
+`))
+ defer restore()
+ s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+
+ s.mockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
+ "format": "3",
+ "slots": map[string]interface{}{
+ "test": map[string]interface{}{
+ "allow-installation": map[string]interface{}{
+ "on-store": []interface{}{"my-store"},
+ },
+ },
+ },
+ })
+ snapInfo := s.mockSnap(c, producerYaml)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ c.Check(ifacestate.CheckInterfaces(s.state, snapInfo), IsNil)
+}
+
+func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeWrongFriendlyStore(c *C) {
+ s.mockModel(c, map[string]interface{}{
+ "store": "my-substore",
+ })
+
+ s.mockStore(c, "my-substore", map[string]interface{}{
+ "friendly-stores": []interface{}{"other-store"},
+ })
+
+ restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
+type: base-declaration
+authority-id: canonical
+series: 16
+slots:
+ test:
+ deny-installation: true
+`))
+ defer restore()
+ s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+
+ s.mockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
+ "format": "3",
+ "slots": map[string]interface{}{
+ "test": map[string]interface{}{
+ "allow-installation": map[string]interface{}{
+ "on-store": []interface{}{"my-store"},
+ },
+ },
+ },
+ })
+ snapInfo := s.mockSnap(c, producerYaml)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ c.Check(ifacestate.CheckInterfaces(s.state, snapInfo), ErrorMatches, `installation not allowed.*`)
+}
+
func (s *interfaceManagerSuite) TestCheckInterfacesConsidersImplicitSlots(c *C) {
snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
@@ -3787,7 +3988,7 @@ func (s *interfaceManagerSuite) TestDisconnectInterfaces(c *C) {
repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
- }, plugDynAttrs, slotDynAttrs, nil)
+ }, nil, plugDynAttrs, nil, slotDynAttrs, nil)
chg := s.state.NewChange("install", "")
t := s.state.NewTask("auto-disconnect", "")
@@ -3856,7 +4057,7 @@ func (s *interfaceManagerSuite) testDisconnectInterfacesRetry(c *C, conflictingK
repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
- }, nil, nil, nil)
+ }, nil, nil, nil, nil, nil)
sup := &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index df3321496c..6cdf4f0575 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -2584,7 +2584,7 @@ apps:
repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"},
SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"},
- }, nil, nil, nil)
+ }, nil, nil, nil, nil, nil)
ts, err := snapstate.Remove(st, "some-snap", snap.R(0))
c.Assert(err, IsNil)
@@ -2672,7 +2672,7 @@ func (ms *mgrsSuite) TestDisconnectOnUninstallRemovesAutoconnection(c *C) {
repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"},
SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"},
- }, nil, nil, nil)
+ }, nil, nil, nil, nil, nil)
ts, err := snapstate.Remove(st, "some-snap", snap.R(0))
c.Assert(err, IsNil)
diff --git a/overlord/overlord_test.go b/overlord/overlord_test.go
index 226e1ef367..9a3781e40c 100644
--- a/overlord/overlord_test.go
+++ b/overlord/overlord_test.go
@@ -108,7 +108,7 @@ func (ovs *overlordSuite) TestNew(c *C) {
}
func (ovs *overlordSuite) TestNewWithGoodState(c *C) {
- fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"some":"data","refresh-privacy-key":"0123456789ABCDEF"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0,"last-lane-id":0}`, patch.Level))
+ fakeState := []byte(fmt.Sprintf(`{"data":{"patch-level":%d,"patch-sublevel":%d,"some":"data","refresh-privacy-key":"0123456789ABCDEF"},"changes":null,"tasks":null,"last-change-id":0,"last-task-id":0,"last-lane-id":0}`, patch.Level, patch.Sublevel))
err := ioutil.WriteFile(dirs.SnapStateFile, fakeState, 0600)
c.Assert(err, IsNil)
diff --git a/overlord/patch/patch.go b/overlord/patch/patch.go
index 74e43620c1..eda75d9025 100644
--- a/overlord/patch/patch.go
+++ b/overlord/patch/patch.go
@@ -38,7 +38,7 @@ var Level = 6
// Sublevel is the current implemented sublevel for the Level.
// Sublevel 0 is the first patch for the new Level, rollback below x.0 is not possible.
// Sublevel patches > 0 do not prevent rollbacks.
-var Sublevel = 0
+var Sublevel = 1
type PatchFunc func(s *state.State) error
diff --git a/overlord/patch/patch6.go b/overlord/patch/patch6.go
index eda872381d..f389a1c308 100644
--- a/overlord/patch/patch6.go
+++ b/overlord/patch/patch6.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- patches[6] = []PatchFunc{patch6}
+ patches[6] = []PatchFunc{patch6, patch6_1}
}
type patch6Flags struct {
diff --git a/overlord/patch/patch6_1.go b/overlord/patch/patch6_1.go
new file mode 100644
index 0000000000..e52ce569f3
--- /dev/null
+++ b/overlord/patch/patch6_1.go
@@ -0,0 +1,149 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 patch
+
+import (
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
+
+type connStatePatch6_1 struct {
+ Auto bool `json:"auto,omitempty"`
+ ByGadget bool `json:"by-gadget,omitempty"`
+ Interface string `json:"interface,omitempty"`
+ Undesired bool `json:"undesired,omitempty"`
+ StaticPlugAttrs map[string]interface{} `json:"plug-static,omitempty"`
+ DynamicPlugAttrs map[string]interface{} `json:"plug-dynamic,omitempty"`
+ StaticSlotAttrs map[string]interface{} `json:"slot-static,omitempty"`
+ DynamicSlotAttrs map[string]interface{} `json:"slot-dynamic,omitempty"`
+}
+
+// processConns updates conns map and augments it with plug-static and slot-static attributes from current snap info.
+// NOTE: missing snap or missing plugs/slots are ignored and not reported as errors as we might have stale connections
+// and ifacemgr deals with them (i.e. discards) on startup; we want to process all good slots and plugs here.
+func processConns(conns map[string]connStatePatch6_1, infos map[string]*snap.Info) (bool, error) {
+ var updated bool
+ for id, conn := range conns {
+ // static attributes already present
+ if len(conn.StaticPlugAttrs) > 0 || len(conn.StaticSlotAttrs) > 0 {
+ continue
+ }
+
+ // undesired connections have all their attributes dropped, so don't set them
+ if conn.Undesired {
+ continue
+ }
+
+ connRef, err := interfaces.ParseConnRef(id)
+ if err != nil {
+ return false, err
+ }
+
+ var ok bool
+ var plugSnapInfo, slotSnapInfo *snap.Info
+
+ // read current snap info from disk and keep it around in infos map
+ if plugSnapInfo, ok = infos[connRef.PlugRef.Snap]; !ok {
+ plugSnapInfo, err = snap.ReadCurrentInfo(connRef.PlugRef.Snap)
+ if err == nil {
+ infos[connRef.PlugRef.Snap] = plugSnapInfo
+ }
+ }
+ if slotSnapInfo, ok = infos[connRef.SlotRef.Snap]; !ok {
+ slotSnapInfo, err = snap.ReadCurrentInfo(connRef.SlotRef.Snap)
+ if err == nil {
+ infos[connRef.SlotRef.Snap] = slotSnapInfo
+ }
+ }
+
+ if slotSnapInfo != nil {
+ if slot, ok := slotSnapInfo.Slots[connRef.SlotRef.Name]; ok && slot.Attrs != nil {
+ conn.StaticSlotAttrs = slot.Attrs
+ updated = true
+ }
+ }
+
+ if plugSnapInfo != nil {
+ if plug, ok := plugSnapInfo.Plugs[connRef.PlugRef.Name]; ok && plug.Attrs != nil {
+ conn.StaticPlugAttrs = plug.Attrs
+ updated = true
+ }
+ }
+
+ conns[id] = conn
+ }
+
+ return updated, nil
+}
+
+// patch6_1:
+// - add static plug and slot attributes to connections that miss them. Attributes are read from current snap info.
+func patch6_1(st *state.State) error {
+ infos := make(map[string]*snap.Info)
+
+ // update all pending "discard-conns" tasks as they may keep connection data in "removed".
+ for _, task := range st.Tasks() {
+ if task.Change().Status().Ready() {
+ continue
+ }
+
+ var removed map[string]connStatePatch6_1
+ if task.Kind() == "discard-conns" {
+ err := task.Get("removed", &removed)
+ if err == state.ErrNoState {
+ continue
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ updated, err := processConns(removed, infos)
+ if err != nil {
+ return err
+ }
+
+ if updated {
+ task.Set("removed", removed)
+ }
+ }
+
+ // update conns
+ var conns map[string]connStatePatch6_1
+ err := st.Get("conns", &conns)
+ if err == state.ErrNoState {
+ // no connections to process
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+
+ updated, err := processConns(conns, infos)
+ if err != nil {
+ return err
+ }
+ if updated {
+ st.Set("conns", conns)
+ }
+
+ return nil
+}
diff --git a/overlord/patch/patch6_1_test.go b/overlord/patch/patch6_1_test.go
new file mode 100644
index 0000000000..db10545c40
--- /dev/null
+++ b/overlord/patch/patch6_1_test.go
@@ -0,0 +1,284 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 patch_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord/patch"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+)
+
+type patch61Suite struct{}
+
+var _ = Suite(&patch61Suite{})
+
+var statePatch6_1JSON = []byte(`
+{
+ "last-task-id": 999,
+ "last-change-id": 99,
+
+ "data": {
+ "patch-level": 6,
+ "snaps": {
+ "core": {
+ "sequence": [{"name": "core", "revision": "2"}],
+ "flags": 1,
+ "current": "2"}
+ },
+ "conns": {
+ "gnome-calculator:icon-themes gtk-common-themes:icon-themes": {
+ "auto": true,
+ "interface": "content",
+ "plug-static": {
+ "content": "icon-themes",
+ "default-provider": "gtk-common-themes",
+ "target": "$SNAP/data-dir/icons"
+ },
+ "slot-static": {
+ "content": "icon-themes",
+ "source": {
+ "read": ["$SNAP/share/icons/Adwaita"]
+ }
+ }
+ },
+ "foobar:fooplug gtk-common-themes:icon-themes": {
+ "auto": true,
+ "interface": "content"
+ },
+ "gnome-calculator:network core:network": {
+ "auto": true,
+ "interface": "network"
+ },
+ "other-snap:icon-themes gtk-common-themes:icon-themes": {
+ "auto": true,
+ "interface": "content",
+ "undesired":true
+ }
+ }
+ },
+ "changes": {
+ "1": {
+ "id": "1",
+ "kind": "install-snap",
+ "summary": "install a snap",
+ "status": 0,
+ "data": {"snap-names": ["foobar"]},
+ "task-ids": ["11"]
+ }
+ },
+ "tasks": {
+ "11": {
+ "id": "11",
+ "change": "1",
+ "kind": "discard-conns",
+ "summary": "",
+ "status": 0,
+ "data": {"snap-setup": {
+ "channel": "edge",
+ "flags": 1
+ },
+ "removed": {
+ "foobar:fooplug gtk-common-themes:icon-themes": {
+ "auto": true,
+ "interface": "content"
+ }
+ }
+ },
+ "halt-tasks": []
+ }
+ }
+}`)
+
+var mockSnap1Yaml = `
+name: gnome-calculator
+version: 3.28.2
+summary: GNOME Calculator
+grade: stable
+plugs:
+ icon-themes:
+ default-provider: gtk-common-themes
+ interface: content
+ target: $SNAP/data-dir/icons
+ sound-themes:
+ default-provider: gtk-common-themes
+ interface: content
+ target: $SNAP/data-dir/sounds
+slots:
+ gnome-calculator:
+ bus: session
+ interface: dbus
+ name: org.gnome.Calculator
+apps:
+ gnome-calculator:
+ command: command-gnome-calculator.wrapper
+`
+
+var mockSnap2Yaml = `
+name: foobar
+version: 1
+summary: FooBar snap
+plugs:
+ fooplug:
+ interface: content
+ target: $SNAP/foo-dir/icons
+slots:
+ fooslot:
+ bus: session
+ interface: dbus
+ name: org.gnome.Calculator
+apps:
+ foo:
+ command: bar
+`
+
+var mockSnap3Yaml = `
+name: gtk-common-themes
+version: 1
+summary: gtk common themes snap
+slots:
+ icon-themes:
+ bus: session
+ interface: dbus
+ name: org.gnome.Calculator
+apps:
+ foo:
+ command: bar
+`
+
+var mockSnap4Yaml = `
+name: other-snap
+version: 1.1
+plugs:
+ icon-themes:
+ default-provider: gtk-common-themes
+ interface: content
+ target: $SNAP/data-dir/icons
+apps:
+ foo:
+ command: bar
+`
+
+func (s *patch61Suite) SetUpTest(c *C) {
+ dirs.SetRootDir(c.MkDir())
+
+ err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapStateFile, statePatch6_1JSON, 0644)
+ c.Assert(err, IsNil)
+
+ snap.MockSanitizePlugsSlots(func(*snap.Info) {})
+
+ snaptest.MockSnapCurrent(c, mockSnap1Yaml, &snap.SideInfo{Revision: snap.R("x1")})
+ snaptest.MockSnapCurrent(c, mockSnap2Yaml, &snap.SideInfo{Revision: snap.R("x1")})
+ snaptest.MockSnapCurrent(c, mockSnap3Yaml, &snap.SideInfo{Revision: snap.R("x1")})
+ snaptest.MockSnapCurrent(c, mockSnap4Yaml, &snap.SideInfo{Revision: snap.R("x1")})
+}
+
+func (s *patch61Suite) TestPatch61(c *C) {
+ restore := patch.MockLevel(6, 1)
+ defer restore()
+
+ r, err := os.Open(dirs.SnapStateFile)
+ c.Assert(err, IsNil)
+ defer r.Close()
+ st, err := state.ReadState(nil, r)
+ c.Assert(err, IsNil)
+
+ // repeat the patch a few times to ensure it's idempotent
+ for i := 0; i < 3; i++ {
+ st.Lock()
+ sublevel := 0
+ st.Set("patch-sublevel", sublevel)
+ st.Unlock()
+
+ c.Assert(patch.Apply(st), IsNil)
+ st.Lock()
+
+ st.Get("patch-sublevel", &sublevel)
+ c.Assert(sublevel, Equals, 1)
+
+ var conns map[string]interface{}
+ c.Assert(st.Get("conns", &conns), IsNil)
+ c.Assert(conns, DeepEquals, map[string]interface{}{
+ "gnome-calculator:icon-themes gtk-common-themes:icon-themes": map[string]interface{}{
+ "auto": true,
+ "interface": "content",
+ "plug-static": map[string]interface{}{
+ "content": "icon-themes",
+ "default-provider": "gtk-common-themes",
+ "target": "$SNAP/data-dir/icons",
+ },
+ "slot-static": map[string]interface{}{
+ "content": "icon-themes",
+ "source": map[string]interface{}{
+ "read": []interface{}{"$SNAP/share/icons/Adwaita"},
+ },
+ },
+ },
+ "foobar:fooplug gtk-common-themes:icon-themes": map[string]interface{}{
+ "auto": true,
+ "interface": "content",
+ "plug-static": map[string]interface{}{
+ "target": "$SNAP/foo-dir/icons",
+ },
+ "slot-static": map[string]interface{}{
+ "bus": "session",
+ "name": "org.gnome.Calculator",
+ },
+ },
+ "gnome-calculator:network core:network": map[string]interface{}{
+ "auto": true,
+ "interface": "network",
+ },
+ "other-snap:icon-themes gtk-common-themes:icon-themes": map[string]interface{}{
+ "undesired": true,
+ "auto": true,
+ "interface": "content",
+ },
+ })
+
+ var removed map[string]interface{}
+ task := st.Task("11")
+ c.Assert(task, NotNil)
+ c.Assert(task.Get("removed", &removed), IsNil)
+ c.Assert(removed, DeepEquals, map[string]interface{}{
+ "foobar:fooplug gtk-common-themes:icon-themes": map[string]interface{}{
+ "auto": true,
+ "interface": "content",
+ "plug-static": map[string]interface{}{
+ "target": "$SNAP/foo-dir/icons",
+ },
+ "slot-static": map[string]interface{}{
+ "bus": "session",
+ "name": "org.gnome.Calculator",
+ },
+ },
+ })
+ st.Unlock()
+ }
+}
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index eef0787202..2556402ad7 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -188,6 +188,12 @@ func (f *fakeStore) snap(spec snapSpec, user *auth.UserState) (*snap.Info, error
typ = snap.TypeOS
case "some-base":
typ = snap.TypeBase
+ case "some-kernel":
+ typ = snap.TypeKernel
+ case "some-gadget":
+ typ = snap.TypeGadget
+ case "some-snapd":
+ typ = snap.TypeSnapd
}
if spec.Name == "snap-unknown" {
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index a97dbc7de0..1276fc7bfe 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -58,6 +58,16 @@ const (
UseConfigDefaults
)
+func isParallelInstallable(snapsup *SnapSetup) error {
+ if snapsup.InstanceKey == "" {
+ return nil
+ }
+ if snapsup.Type == snap.TypeApp {
+ return nil
+ }
+ return fmt.Errorf("cannot install snap of type %v as %q", snapsup.Type, snapsup.InstanceName())
+}
+
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int) (*state.TaskSet, error) {
if snapsup.InstanceName() == "system" {
return nil, fmt.Errorf("cannot install reserved snap name 'system'")
@@ -96,8 +106,9 @@ func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int
}
}
- // TODO parallel-install: block parallel installation of core, kernel,
- // gadget and snapd snaps
+ if err := isParallelInstallable(snapsup); err != nil {
+ return nil, err
+ }
if err := CheckChangeConflict(st, snapsup.InstanceName(), snapst); err != nil {
return nil, err
diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go
index 27594a4d8b..0aa4be68be 100644
--- a/overlord/snapstate/snapstate_test.go
+++ b/overlord/snapstate/snapstate_test.go
@@ -1686,6 +1686,32 @@ confinement: devmode
c.Assert(err, ErrorMatches, `.* requires devmode or confinement override`)
}
+func (s *snapmgrTestSuite) TestParallelInstanceInstallNotAllowed(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.ReplaceStore(s.state, sneakyStore{fakeStore: s.fakeStore, state: s.state})
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.parallel-instances", true)
+ tr.Commit()
+
+ _, err := snapstate.Install(s.state, "core_foo", "", snap.R(0), 0, snapstate.Flags{})
+ c.Check(err, ErrorMatches, `cannot install snap of type os as "core_foo"`)
+
+ _, err = snapstate.Install(s.state, "some-base_foo", "", snap.R(0), 0, snapstate.Flags{})
+ c.Check(err, ErrorMatches, `cannot install snap of type base as "some-base_foo"`)
+
+ _, err = snapstate.Install(s.state, "some-gadget_foo", "", snap.R(0), 0, snapstate.Flags{})
+ c.Check(err, ErrorMatches, `cannot install snap of type gadget as "some-gadget_foo"`)
+
+ _, err = snapstate.Install(s.state, "some-kernel_foo", "", snap.R(0), 0, snapstate.Flags{})
+ c.Check(err, ErrorMatches, `cannot install snap of type kernel as "some-kernel_foo"`)
+
+ _, err = snapstate.Install(s.state, "some-snapd_foo", "", snap.R(0), 0, snapstate.Flags{})
+ c.Check(err, ErrorMatches, `cannot install snap of type snapd as "some-snapd_foo"`)
+}
+
func (s *snapmgrTestSuite) TestUpdateTasksPropagatesErrors(c *C) {
s.state.Lock()
defer s.state.Unlock()
diff --git a/run-checks b/run-checks
index 59bed4af31..15b55ca600 100755
--- a/run-checks
+++ b/run-checks
@@ -29,6 +29,8 @@ fi
export GOPATH="${GOPATH:-$(realpath "$(dirname "$0")"/../../../../)}"
export PATH="$PATH:${GOPATH%%:*}/bin"
+short=
+
STATIC=
UNIT=
SPREAD=
@@ -46,6 +48,10 @@ case "${1:-all}" in
--unit)
UNIT=1
;;
+ --short-unit)
+ UNIT=1
+ short=1
+ ;;
--spread)
SPREAD=1
;;
@@ -216,30 +222,34 @@ fi
if [ "$UNIT" = 1 ]; then
./get-deps.sh
- # Prepare the coverage output profile.
- rm -rf .coverage
- mkdir .coverage
- echo "mode: $COVERMODE" > .coverage/coverage.out
-
echo Building
go build -v github.com/snapcore/snapd/...
# tests
echo Running tests from "$PWD"
- if dpkg --compare-versions "$(go version | awk '$3 ~ /^go[0-9]/ {print substr($3, 3)}')" ge 1.10; then
- # shellcheck disable=SC2046
- $goctest -v -coverprofile=.coverage/coverage.out -covermode="$COVERMODE" $(go list ./... | grep -v '/vendor/' )
+ if [ "$short" = 1 ]; then
+ # shellcheck disable=SC2046
+ $goctest -short -v $(go list ./... | grep -v '/vendor/' )
else
- for pkg in $(go list ./... | grep -v '/vendor/' ); do
- go test -i "$pkg"
- $goctest -v -coverprofile=.coverage/profile.out -covermode="$COVERMODE" "$pkg"
- append_coverage .coverage/profile.out
- done
- fi
-
- # upload to codecov.io if on travis
- if [ "${TRAVIS_BUILD_NUMBER:-}" ]; then
- curl -s https://codecov.io/bash | bash /dev/stdin -f .coverage/coverage.out
+ # Prepare the coverage output profile.
+ rm -rf .coverage
+ mkdir .coverage
+ echo "mode: $COVERMODE" > .coverage/coverage.out
+
+ if dpkg --compare-versions "$(go version | awk '$3 ~ /^go[0-9]/ {print substr($3, 3)}')" ge 1.10; then
+ # shellcheck disable=SC2046
+ $goctest -v -coverprofile=.coverage/coverage.out -covermode="$COVERMODE" $(go list ./... | grep -v '/vendor/' )
+ else
+ for pkg in $(go list ./... | grep -v '/vendor/' ); do
+ go test -i "$pkg"
+ $goctest -v -coverprofile=.coverage/profile.out -covermode="$COVERMODE" "$pkg"
+ append_coverage .coverage/profile.out
+ done
+ fi
+ # upload to codecov.io if on travis
+ if [ "${TRAVIS_BUILD_NUMBER:-}" ]; then
+ curl -s https://codecov.io/bash | bash /dev/stdin -f .coverage/coverage.out
+ fi
fi
fi
diff --git a/systemd/export_test.go b/systemd/export_test.go
index 71386c1d75..2ccf45c994 100644
--- a/systemd/export_test.go
+++ b/systemd/export_test.go
@@ -21,24 +21,12 @@ package systemd
import (
"io"
- "time"
)
var (
Jctl = jctl
)
-func MockStopDelays(checkDelay, notifyDelay time.Duration) func() {
- oldCheckDelay := stopCheckDelay
- oldNotifyDelay := stopNotifyDelay
- stopCheckDelay = checkDelay
- stopNotifyDelay = notifyDelay
- return func() {
- stopCheckDelay = oldCheckDelay
- stopNotifyDelay = oldNotifyDelay
- }
-}
-
func MockOsGetenv(f func(string) string) func() {
oldOsGetenv := osGetenv
osGetenv = f
diff --git a/systemd/systemd.go b/systemd/systemd.go
index f2a527edb9..c264a67d8f 100644
--- a/systemd/systemd.go
+++ b/systemd/systemd.go
@@ -69,6 +69,19 @@ func MockSystemctl(f func(args ...string) ([]byte, error)) func() {
}
}
+// MockStopDelays is used from tests so that Stop can be less
+// forgiving there.
+func MockStopDelays(checkDelay, notifyDelay time.Duration) func() {
+ oldCheckDelay := stopCheckDelay
+ oldNotifyDelay := stopNotifyDelay
+ stopCheckDelay = checkDelay
+ stopNotifyDelay = notifyDelay
+ return func() {
+ stopCheckDelay = oldCheckDelay
+ stopNotifyDelay = oldNotifyDelay
+ }
+}
+
func Available() error {
_, err := systemctlCmd("--version")
return err
diff --git a/wrappers/services_test.go b/wrappers/services_test.go
index 4c50787f3d..58ea6ca4ef 100644
--- a/wrappers/services_test.go
+++ b/wrappers/services_test.go
@@ -46,7 +46,7 @@ type servicesTestSuite struct {
sysdLog [][]string
- restorer func()
+ systemctlRestorer, delaysRestorer func()
}
var _ = Suite(&servicesTestSuite{})
@@ -56,15 +56,18 @@ func (s *servicesTestSuite) SetUpTest(c *C) {
s.sysdLog = nil
dirs.SetRootDir(s.tempdir)
- s.restorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
+ s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
s.sysdLog = append(s.sysdLog, cmd)
return []byte("ActiveState=inactive\n"), nil
})
+ s.delaysRestorer = systemd.MockStopDelays(time.Millisecond, 25*time.Second)
+
}
func (s *servicesTestSuite) TearDownTest(c *C) {
dirs.SetRootDir("")
- s.restorer()
+ s.systemctlRestorer()
+ s.delaysRestorer()
}
func (s *servicesTestSuite) TestAddSnapServicesAndRemove(c *C) {
@@ -140,7 +143,7 @@ func (s *servicesTestSuite) TestRemoveSnapWithSocketsRemovesSocketsService(c *C)
}
func (s *servicesTestSuite) TestRemoveSnapPackageFallbackToKill(c *C) {
- restore := wrappers.MockKillWait(200 * time.Millisecond)
+ restore := wrappers.MockKillWait(time.Millisecond)
defer restore()
var sysdLog [][]string
@@ -159,7 +162,7 @@ version: 42
apps:
wat:
command: wat
- stop-timeout: 250ms
+ stop-timeout: 20ms
daemon: forking
`, &snap.SideInfo{Revision: snap.R(11)})