diff options
| author | John R. Lenton <jlenton@gmail.com> | 2018-10-08 21:38:47 +0100 |
|---|---|---|
| committer | John R. Lenton <jlenton@gmail.com> | 2018-10-08 21:38:47 +0100 |
| commit | 305fcf51ad8142afdfa42edbe369be380ea90f5f (patch) | |
| tree | 27349c37483db5c77bcedfa347629629b698326b | |
| parent | 7d4183424ab6c4522ed7f266652b445822506cf0 (diff) | |
| parent | db577fc35409fda4fc95681fbacc5a85bb36fadc (diff) | |
Merge remote-tracking branch 'upstream/master' into sync-lp-translationssync-lp-translations
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)}) |
