diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2016-11-23 08:22:24 +0100 |
|---|---|---|
| committer | Michael Vogt <mvo@ubuntu.com> | 2016-11-23 08:22:24 +0100 |
| commit | 5163798cf7b1875f9fb5f2ac1070339f01b82509 (patch) | |
| tree | 6cef8c9ee9a2d8d6316623783d4c80c42aa476a3 | |
| parent | 2946c5a3f9c197dd7187e92d0c43c6f199d1070f (diff) | |
| parent | 9c65dd43be8359fccf3f3724e50a1859f96a962c (diff) | |
Merge remote-tracking branch 'upstream/master' into feature/gofmt-s-testfeature/gofmt-s-test
63 files changed, 632 insertions, 438 deletions
diff --git a/asserts/account_key.go b/asserts/account_key.go index 3e01b4ad12..a6320ff700 100644 --- a/asserts/account_key.go +++ b/asserts/account_key.go @@ -138,7 +138,7 @@ var _ consistencyChecker = (*AccountKey)(nil) // Prerequisites returns references to this account-key's prerequisite assertions. func (ak *AccountKey) Prerequisites() []*Ref { return []*Ref{ - &Ref{Type: AccountType, PrimaryKey: []string{ak.AccountID()}}, + {Type: AccountType, PrimaryKey: []string{ak.AccountID()}}, } } @@ -245,7 +245,7 @@ var ( // Prerequisites returns references to this account-key-request's prerequisite assertions. func (akr *AccountKeyRequest) Prerequisites() []*Ref { return []*Ref{ - &Ref{Type: AccountType, PrimaryKey: []string{akr.AccountID()}}, + {Type: AccountType, PrimaryKey: []string{akr.AccountID()}}, } } diff --git a/asserts/account_key_test.go b/asserts/account_key_test.go index 37887ce183..d1fd153b9e 100644 --- a/asserts/account_key_test.go +++ b/asserts/account_key_test.go @@ -394,6 +394,7 @@ func (aks *accountKeySuite) TestAccountKeyCheckSameAccountAndDifferentName(c *C) newPubKey, err := db.PublicKey(newPrivKey.PublicKey().ID()) c.Assert(err, IsNil) newPubKeyEncoded, err := asserts.EncodePublicKey(newPubKey) + c.Assert(err, IsNil) headers["name"] = "another" headers["public-key-sha3-384"] = newPubKey.ID() @@ -432,6 +433,7 @@ func (aks *accountKeySuite) TestAccountKeyCheckSameNameAndDifferentAccount(c *C) newPubKey, err := db.PublicKey(newPrivKey.PublicKey().ID()) c.Assert(err, IsNil) newPubKeyEncoded, err := asserts.EncodePublicKey(newPubKey) + c.Assert(err, IsNil) acct2 := assertstest.NewAccount(db, "acc-id2", map[string]interface{}{ "authority-id": "canonical", @@ -475,6 +477,7 @@ func (aks *accountKeySuite) TestAccountKeyCheckNameClash(c *C) { newPubKey, err := db.PublicKey(newPrivKey.PublicKey().ID()) c.Assert(err, IsNil) newPubKeyEncoded, err := asserts.EncodePublicKey(newPubKey) + c.Assert(err, IsNil) headers["public-key-sha3-384"] = newPubKey.ID() headers["revision"] = "1" diff --git a/asserts/asserts_test.go b/asserts/asserts_test.go index 00f9af8e23..a6b3314b21 100644 --- a/asserts/asserts_test.go +++ b/asserts/asserts_test.go @@ -353,6 +353,7 @@ func (as *assertsSuite) TestDecoderHappyWithSeparatorsVariations(c *C) { checkContent(c, a, streamData) a, err = decoder.Decode() + c.Check(a, IsNil) c.Check(err, Equals, io.EOF, Commentf("stream: %q", streamData)) } } @@ -379,6 +380,7 @@ func (as *assertsSuite) TestDecoderHappyWithTrailerDoubleNewlines(c *C) { checkContent(c, a, streamData) a, err = decoder.Decode() + c.Check(a, IsNil) c.Check(err, Equals, io.EOF, Commentf("stream: %q", streamData)) } } @@ -578,6 +580,7 @@ func (as *assertsSuite) TestSignFormatAndRevision(c *C) { } a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, testPrivKey1) + c.Assert(err, IsNil) c.Check(a.Revision(), Equals, 11) c.Check(a.Format(), Equals, 1) diff --git a/asserts/database_test.go b/asserts/database_test.go index b3af17fee7..e2c30436ba 100644 --- a/asserts/database_test.go +++ b/asserts/database_test.go @@ -94,6 +94,7 @@ func (opens *openSuite) TestOpenDatabaseTrustedWrongType(c *C) { "primary-key": "0", } a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, testPrivKey0) + c.Assert(err, IsNil) cfg := &asserts.DatabaseConfig{ Trusted: []asserts.Assertion{a}, @@ -559,12 +560,6 @@ func (safs *signAddFindSuite) TestAddUnsupportedFormat(c *C) { "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + "\n\n" + "AXNpZw==" - headers := map[string]interface{}{ - "authority-id": "canonical", - "primary-key": "a", - "format": "77", - "payload": "unsupported", - } aUnsupp, err := asserts.Decode([]byte(unsupported)) c.Assert(err, IsNil) c.Assert(aUnsupp.SupportedFormat(), Equals, false) @@ -575,7 +570,7 @@ func (safs *signAddFindSuite) TestAddUnsupportedFormat(c *C) { c.Check(err, ErrorMatches, `proposed "test-only" assertion has format 77 but 1 is latest supported`) c.Check(asserts.IsUnaccceptedUpdate(err), Equals, false) - headers = map[string]interface{}{ + headers := map[string]interface{}{ "authority-id": "canonical", "primary-key": "a", "format": "1", diff --git a/asserts/device_asserts.go b/asserts/device_asserts.go index 2e427fab0c..83a3099bef 100644 --- a/asserts/device_asserts.go +++ b/asserts/device_asserts.go @@ -45,6 +45,16 @@ func (mod *Model) Model() string { return mod.HeaderString("model") } +// DisplayName returns the human-friendly name of the model or +// falls back to Model if this was not set. +func (mod *Model) DisplayName() string { + display := mod.HeaderString("display-name") + if display == "" { + return mod.Model() + } + return display +} + // Series returns the series of the core software the model uses. func (mod *Model) Series() string { return mod.HeaderString("series") @@ -168,6 +178,12 @@ func assembleModel(assert assertionBase) (Assertion, error) { return nil, err } + // display-name is optional but must be a string + _, err = checkOptionalString(assert.headers, "display-name") + if err != nil { + return nil, err + } + reqSnaps, err := checkStringList(assert.headers, "required-snaps") if err != nil { return nil, err diff --git a/asserts/device_asserts_test.go b/asserts/device_asserts_test.go index b0540101ad..b468683848 100644 --- a/asserts/device_asserts_test.go +++ b/asserts/device_asserts_test.go @@ -53,6 +53,7 @@ const modelExample = "type: model\n" + "series: 16\n" + "brand-id: brand-id1\n" + "model: baz-3000\n" + + "display-name: Baz 3000\n" + "architecture: amd64\n" + "gadget: brand-gadget\n" + "kernel: baz-linux\n" + @@ -76,6 +77,7 @@ func (mods *modelSuite) TestDecodeOK(c *C) { c.Check(model.Series(), Equals, "16") c.Check(model.BrandID(), Equals, "brand-id1") c.Check(model.Model(), Equals, "baz-3000") + c.Check(model.DisplayName(), Equals, "Baz 3000") c.Check(model.Architecture(), Equals, "amd64") c.Check(model.Gadget(), Equals, "brand-gadget") c.Check(model.Kernel(), Equals, "baz-linux") @@ -99,6 +101,23 @@ func (mods *modelSuite) TestDecodeStoreIsOptional(c *C) { c.Check(model.Store(), Equals, "") } +func (mods *modelSuite) TestDecodeDisplayNameIsOptional(c *C) { + withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) + encoded := strings.Replace(withTimestamp, "display-name: Baz 3000\n", "display-name: \n", 1) + a, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + model := a.(*asserts.Model) + // optional but we fallback to Model + c.Check(model.DisplayName(), Equals, "baz-3000") + + encoded = strings.Replace(withTimestamp, "display-name: Baz 3000\n", "", 1) + a, err = asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + model = a.(*asserts.Model) + // optional but we fallback to Model + c.Check(model.DisplayName(), Equals, "baz-3000") +} + func (mods *modelSuite) TestDecodeRequiredSnapsAreOptional(c *C) { withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) encoded := strings.Replace(withTimestamp, reqSnaps, "", 1) @@ -142,6 +161,7 @@ func (mods *modelSuite) TestDecodeInvalid(c *C) { {"model: baz-3000\n", "model: baz/3000\n", `"model" primary key header cannot contain '/'`}, // lift this restriction at a later point {"model: baz-3000\n", "model: BAZ-3000\n", `"model" header cannot contain uppercase letters`}, + {"display-name: Baz 3000\n", "display-name:\n - xyz\n", `"display-name" header must be a string`}, {"architecture: amd64\n", "", `"architecture" header is mandatory`}, {"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`}, {"gadget: brand-gadget\n", "", `"gadget" header is mandatory`}, diff --git a/asserts/membackstore.go b/asserts/membackstore.go index 214359b34c..fe744fefd9 100644 --- a/asserts/membackstore.go +++ b/asserts/membackstore.go @@ -116,7 +116,7 @@ func (br memBSBranch) search(hint []string, found func(Assertion), maxFormat int func (leaf memBSLeaf) search(hint []string, found func(Assertion), maxFormat int) { hint0 := hint[0] if hint0 == "" { - for key, _ := range leaf { + for key := range leaf { cand := leaf.cur(key, maxFormat) if cand != nil { found(cand) diff --git a/asserts/snap_asserts.go b/asserts/snap_asserts.go index 65a09c020d..84666a58de 100644 --- a/asserts/snap_asserts.go +++ b/asserts/snap_asserts.go @@ -106,7 +106,7 @@ var _ consistencyChecker = (*SnapDeclaration)(nil) // Prerequisites returns references to this snap-declaration's prerequisite assertions. func (snapdcl *SnapDeclaration) Prerequisites() []*Ref { return []*Ref{ - &Ref{Type: AccountType, PrimaryKey: []string{snapdcl.PublisherID()}}, + {Type: AccountType, PrimaryKey: []string{snapdcl.PublisherID()}}, } } @@ -332,8 +332,8 @@ var _ consistencyChecker = (*SnapRevision)(nil) func (snaprev *SnapRevision) Prerequisites() []*Ref { return []*Ref{ // XXX: mediate getting current series through some context object? this gets the job done for now - &Ref{Type: SnapDeclarationType, PrimaryKey: []string{release.Series, snaprev.SnapID()}}, - &Ref{Type: AccountType, PrimaryKey: []string{snaprev.DeveloperID()}}, + {Type: SnapDeclarationType, PrimaryKey: []string{release.Series, snaprev.SnapID()}}, + {Type: AccountType, PrimaryKey: []string{snaprev.DeveloperID()}}, } } @@ -457,8 +457,8 @@ var _ consistencyChecker = (*Validation)(nil) // Prerequisites returns references to this validation's prerequisite assertions. func (validation *Validation) Prerequisites() []*Ref { return []*Ref{ - &Ref{Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.SnapID()}}, - &Ref{Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.ApprovedSnapID()}}, + {Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.SnapID()}}, + {Type: SnapDeclarationType, PrimaryKey: []string{validation.Series(), validation.ApprovedSnapID()}}, } } diff --git a/asserts/snap_asserts_test.go b/asserts/snap_asserts_test.go index ec5c791d48..684cbe1934 100644 --- a/asserts/snap_asserts_test.go +++ b/asserts/snap_asserts_test.go @@ -1080,6 +1080,7 @@ AXNpZw==` baseDecl := a.(*asserts.BaseDeclaration) c.Check(baseDecl.Series(), Equals, "16") ts, err := time.Parse(time.RFC3339, "2016-09-29T19:50:49Z") + c.Assert(err, IsNil) c.Check(baseDecl.Timestamp().Equal(ts), Equals, true) c.Check(baseDecl.PlugRule("interfaceX"), IsNil) diff --git a/cmd/snap/cmd_delete_key_test.go b/cmd/snap/cmd_delete_key_test.go index 2b8b18490a..65b83dc3c4 100644 --- a/cmd/snap/cmd_delete_key_test.go +++ b/cmd/snap/cmd_delete_key_test.go @@ -50,8 +50,9 @@ func (s *SnapKeysSuite) TestDeleteKey(c *C) { c.Check(s.Stdout(), Equals, "") c.Check(s.Stderr(), Equals, "") _, err = snap.Parser().ParseArgs([]string{"keys", "--json"}) + c.Assert(err, IsNil) expectedResponse := []snap.Key{ - snap.Key{ + { Name: "default", Sha3_384: "g4Pks54W_US4pZuxhgG_RHNAf_UeZBBuZyGRLLmMj1Do3GkE_r_5A5BFjx24ZwVJ", }, diff --git a/cmd/snap/cmd_keys_test.go b/cmd/snap/cmd_keys_test.go index af20d05bf3..eec332e5ff 100644 --- a/cmd/snap/cmd_keys_test.go +++ b/cmd/snap/cmd_keys_test.go @@ -101,11 +101,11 @@ func (s *SnapKeysSuite) TestKeysJSON(c *C) { c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedResponse := []snap.Key{ - snap.Key{ + { Name: "default", Sha3_384: "g4Pks54W_US4pZuxhgG_RHNAf_UeZBBuZyGRLLmMj1Do3GkE_r_5A5BFjx24ZwVJ", }, - snap.Key{ + { Name: "another", Sha3_384: "DVQf1U4mIsuzlQqAebjjTPYtYJ-GEhJy0REuj3zvpQYTZ7EJj7adBxIXLJ7Vmk3L", }, diff --git a/daemon/api.go b/daemon/api.go index 3c62f96b70..c8a36af764 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -768,6 +768,16 @@ func modeFlags(devMode, jailMode bool) (snapstate.Flags, error) { } +// quotedNames formats a slice of snap names (or other strings) to a +// quoted list of comma separated names, e.g. `"snap1", "snap2"` +func quotedNames(names []string) string { + quoted := make([]string, len(names)) + for i, name := range names { + quoted[i] = strconv.Quote(name) + } + return strings.Join(quoted, ", ") +} + func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated []string, tasksets []*state.TaskSet, err error) { // we need refreshed snap-declarations to enforce refresh-control as best as we can, this also ensures that snap-declarations and their prerequisite assertions are updated regularly if err := assertstateRefreshSnapDeclarations(st, inst.userID); err != nil { @@ -786,12 +796,9 @@ func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated case 1: msg = fmt.Sprintf(i18n.G("Refresh snap %q"), inst.Snaps[0]) default: - quoted := make([]string, len(inst.Snaps)) - for i, name := range inst.Snaps { - quoted[i] = strconv.Quote(name) - } + quoted := quotedNames(inst.Snaps) // TRANSLATORS: the %s is a comma-separated list of quoted snap names - msg = fmt.Sprintf(i18n.G("Refresh snaps %s"), strings.Join(quoted, ", ")) + msg = fmt.Sprintf(i18n.G("Refresh snaps %s"), quoted) } return msg, updated, tasksets, nil @@ -809,12 +816,9 @@ func snapInstallMany(inst *snapInstruction, st *state.State) (msg string, instal case 1: msg = fmt.Sprintf(i18n.G("Install snap %q"), inst.Snaps[0]) default: - quoted := make([]string, len(inst.Snaps)) - for i, name := range inst.Snaps { - quoted[i] = strconv.Quote(name) - } + quoted := quotedNames(inst.Snaps) // TRANSLATORS: the %s is a comma-separated list of quoted snap names - msg = fmt.Sprintf(i18n.G("Install snaps %s"), strings.Join(quoted, ", ")) + msg = fmt.Sprintf(i18n.G("Install snaps %s"), quoted) } return msg, installed, tasksets, nil @@ -884,12 +888,9 @@ func snapRemoveMany(inst *snapInstruction, st *state.State) (msg string, removed case 1: msg = fmt.Sprintf(i18n.G("Remove snap %q"), inst.Snaps[0]) default: - quoted := make([]string, len(inst.Snaps)) - for i, name := range inst.Snaps { - quoted[i] = strconv.Quote(name) - } + quoted := quotedNames(inst.Snaps) // TRANSLATORS: the %s is a comma-separated list of quoted snap names - msg = fmt.Sprintf(i18n.G("Remove snaps %s"), strings.Join(quoted, ", ")) + msg = fmt.Sprintf(i18n.G("Remove snaps %s"), quoted) } return msg, removed, tasksets, nil diff --git a/daemon/api_test.go b/daemon/api_test.go index 26d26e6bf8..5cb89df147 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -312,6 +312,19 @@ func (s *apiSuite) TestSnapInfoOneIntegration(c *check.C) { c.Check(rsp.Result, check.DeepEquals, expected.Result) } +func (s *apiSuite) TestQuotedNames(c *check.C) { + for _, t := range []struct { + in []string + out string + }{ + {[]string{}, ""}, + {[]string{"snap1"}, `"snap1"`}, + {[]string{"snap1", "snap2"}, `"snap1", "snap2"`}, + } { + c.Check(quotedNames(t.in), check.Equals, t.out) + } +} + func (s *apiSuite) TestSnapInfoWithAuth(c *check.C) { state := snapCmd.d.overlord.State() state.Lock() @@ -4305,6 +4318,7 @@ func (s *postCreateUserSuite) TestPostCreateUserFromAssertion(c *check.C) { st := s.d.overlord.State() st.Lock() users, err := auth.Users(st) + c.Assert(err, check.IsNil) st.Unlock() c.Check(users, check.HasLen, 1) } @@ -4359,6 +4373,7 @@ func (s *postCreateUserSuite) TestPostCreateUserFromAssertionAllKnown(c *check.C st := s.d.overlord.State() st.Lock() users, err := auth.Users(st) + c.Assert(err, check.IsNil) st.Unlock() c.Check(users, check.HasLen, 2) } diff --git a/daemon/snap.go b/daemon/snap.go index b1c84b49b5..082e0da4fa 100644 --- a/daemon/snap.go +++ b/daemon/snap.go @@ -93,8 +93,8 @@ func allLocalSnapInfos(st *state.State) ([]aboutSnap, error) { about := make([]aboutSnap, 0, len(snapStates)) var firstErr error - for _, snapState := range snapStates { - info, err := snapState.CurrentInfo() + for _, snapst := range snapStates { + info, err := snapst.CurrentInfo() if err != nil { // XXX: aggregate instead? if firstErr == nil { @@ -102,7 +102,7 @@ func allLocalSnapInfos(st *state.State) ([]aboutSnap, error) { } continue } - about = append(about, aboutSnap{info, snapState}) + about = append(about, aboutSnap{info, snapst}) } return about, firstErr diff --git a/i18n/xgettext-go/main.go b/i18n/xgettext-go/main.go index ce36cee466..b7cc234bfd 100644 --- a/i18n/xgettext-go/main.go +++ b/i18n/xgettext-go/main.go @@ -84,7 +84,7 @@ func constructValue(val interface{}) string { left = left[0 : len(left)-1] right := constructValue(val.(*ast.BinaryExpr).Y) // strip left " (or `) - right = right[1:len(right)] + right = right[1:] return left + right default: panic(fmt.Sprintf("unknown type: %v", val)) diff --git a/i18n/xgettext-go/main_test.go b/i18n/xgettext-go/main_test.go index 7ac1611b65..02183daf64 100644 --- a/i18n/xgettext-go/main_test.go +++ b/i18n/xgettext-go/main_test.go @@ -91,7 +91,7 @@ func main() { c.Assert(err, IsNil) c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo": []msgID{ + "foo": { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -116,7 +116,7 @@ func main() { c.Assert(err, IsNil) c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo": []msgID{ + "foo": { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -152,7 +152,7 @@ msgstr "Project-Id-Version: snappy\n" func (s *xgettextTestSuite) TestWriteOutputSimple(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { fname: "fname", line: 2, @@ -175,7 +175,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { fname: "fname", line: 2, @@ -204,7 +204,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { fname: "fname", line: 2, @@ -225,7 +225,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputNoLocation(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { fname: "fname", line: 2, @@ -247,7 +247,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputFormatHint(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { fname: "fname", line: 2, @@ -271,7 +271,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputPlural(c *C) { msgIDs = map[string][]msgID{ - "foo": []msgID{ + "foo": { { msgidPlural: "plural", fname: "fname", @@ -296,13 +296,13 @@ msgstr[1] "" func (s *xgettextTestSuite) TestWriteOutputSorted(c *C) { msgIDs = map[string][]msgID{ - "aaa": []msgID{ + "aaa": { { fname: "fname", line: 2, }, }, - "zzz": []msgID{ + "zzz": { { fname: "fname", line: 2, @@ -403,7 +403,7 @@ func main() { c.Assert(err, IsNil) c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + "foo\\nbar\\nbaz": { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -438,7 +438,7 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputMultilines(c *C) { msgIDs = map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + "foo\\nbar\\nbaz": { { fname: "fname", line: 2, @@ -462,13 +462,13 @@ msgstr "" func (s *xgettextTestSuite) TestWriteOutputTidy(c *C) { msgIDs = map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + "foo\\nbar\\nbaz": { { fname: "fname", line: 2, }, }, - "zzz\\n": []msgID{ + "zzz\\n": { { fname: "fname", line: 4, diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go index ada77890d3..77589f667a 100644 --- a/interfaces/apparmor/backend_test.go +++ b/interfaces/apparmor/backend_test.go @@ -364,6 +364,7 @@ func (s *backendSuite) TestCombineSnippets(c *C) { c.Assert(err, IsNil) c.Check(string(data), Equals, scenario.content) stat, err := os.Stat(profile) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } diff --git a/interfaces/backend.go b/interfaces/backend.go index fc9becba1c..0875c7dcbc 100644 --- a/interfaces/backend.go +++ b/interfaces/backend.go @@ -23,6 +23,47 @@ import ( "github.com/snapcore/snapd/snap" ) +// ConfinementOptions describe confinement configuration. +// +// The confinement system controls the initial layout of the mount namespace as +// well as the set of actions a process is allowed to perform. Confinement is +// initially defined by the ConfinementType declared by the snap. It can be +// either "strict", "devmode" or "classic". +// +// The "strict" type uses mount layout that puts the core snap as the root +// filesystem and provides strong isolation from the system and from other +// snaps. Violations cause permission errors or mandatory process termination. +// +// The "devmode" type uses the same mount layout as "strict" but switches +// confinement to non-enforcing mode whenever possible. Violations that would +// result in permission error or process termination are instead permitted. A +// diagnostic message is logged when this occurs. +// +// The "classic" type uses mount layout that is identical to the runtime of the +// classic system snapd runs in, in other words there is no "chroot". Most of +// the confinement is lifted, specifically there's no seccomp filter being +// applied and apparmor is using complain mode by default. +// +// The three types defined above map to some combinations of the three flags +// defined below. +// +// The DevMode flag attempts to switch all confinement facilities into +// non-enforcing mode even if the snap requested otherwise. +// +// The JailMode flag attempts to switch all confinement facilities into +// enforcing mode even if the snap requested otherwise. +// +// The Classic flag switches the layout of the mount namespace so that there's +// no "chroot" to the core snap. +type ConfinementOptions struct { + // DevMode flag switches confinement to non-enforcing mode. + DevMode bool + // JailMode flag switches confinement to enforcing mode. + JailMode bool + // Classic flag switches the core snap "chroot" off. + Classic bool +} + // SecurityBackend abstracts interactions between the interface system and the // needs of a particular security system. type SecurityBackend interface { diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go index c7a370bf5a..4563064fe4 100644 --- a/interfaces/builtin/all.go +++ b/interfaces/builtin/all.go @@ -71,6 +71,7 @@ var allInterfaces = []interfaces.Interface{ NewOpenglInterface(), NewOpticalDriveInterface(), NewProcessControlInterface(), + NewRawUsbInterface(), NewRemovableMediaInterface(), NewScreenInhibitControlInterface(), NewShutdownInterface(), @@ -83,7 +84,6 @@ var allInterfaces = []interfaces.Interface{ NewTpmInterface(), NewUPowerObserveInterface(), NewUnity7Interface(), - NewUsbRawInterface(), NewX11Interface(), } diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go index 381ef405ea..4411ec5c7d 100644 --- a/interfaces/builtin/all_test.go +++ b/interfaces/builtin/all_test.go @@ -70,6 +70,7 @@ func (s *AllSuite) TestInterfaces(c *C) { c.Check(all, DeepContains, builtin.NewOpenglInterface()) c.Check(all, DeepContains, builtin.NewOpticalDriveInterface()) c.Check(all, DeepContains, builtin.NewProcessControlInterface()) + c.Check(all, DeepContains, builtin.NewRawUsbInterface()) c.Check(all, DeepContains, builtin.NewRemovableMediaInterface()) c.Check(all, DeepContains, builtin.NewScreenInhibitControlInterface()) c.Check(all, DeepContains, builtin.NewSnapdControlInterface()) @@ -81,6 +82,5 @@ func (s *AllSuite) TestInterfaces(c *C) { c.Check(all, DeepContains, builtin.NewTpmInterface()) c.Check(all, DeepContains, builtin.NewUPowerObserveInterface()) c.Check(all, DeepContains, builtin.NewUnity7Interface()) - c.Check(all, DeepContains, builtin.NewUsbRawInterface()) c.Check(all, DeepContains, builtin.NewX11Interface()) } diff --git a/interfaces/builtin/basedeclaration.go b/interfaces/builtin/basedeclaration.go index e8baaa22e2..02abc20cf0 100644 --- a/interfaces/builtin/basedeclaration.go +++ b/interfaces/builtin/basedeclaration.go @@ -397,6 +397,11 @@ slots: - core deny-connection: on-classic: false + raw-usb: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true removable-media: allow-installation: slot-snap-type: @@ -466,11 +471,6 @@ slots: allow-installation: slot-snap-type: - core - usb-raw: - allow-installation: - slot-snap-type: - - core - deny-auto-connection: true x11: allow-installation: slot-snap-type: diff --git a/interfaces/builtin/basedeclaration_test.go b/interfaces/builtin/basedeclaration_test.go index cb1e07ae94..675cfc71c0 100644 --- a/interfaces/builtin/basedeclaration_test.go +++ b/interfaces/builtin/basedeclaration_test.go @@ -345,27 +345,27 @@ var ( slotInstallation = map[string][]string{ // other - "bluez": []string{"app"}, - "bool-file": []string{"core", "gadget"}, - "browser-support": []string{"core"}, - "content": []string{"app", "gadget"}, - "docker-support": []string{"core"}, - "fwupd": []string{"app"}, - "gpio": []string{"core", "gadget"}, - "hidraw": []string{"core", "gadget"}, - "i2c": []string{"core", "gadget"}, - "location-control": []string{"app"}, - "location-observe": []string{"app"}, - "lxd-support": []string{"core"}, - "mir": []string{"app"}, - "modem-manager": []string{"app", "core"}, - "mpris": []string{"app"}, - "network-manager": []string{"app", "core"}, - "ofono": []string{"app", "core"}, - "ppp": []string{"core"}, - "pulseaudio": []string{"app", "core"}, - "serial-port": []string{"core", "gadget"}, - "udisks2": []string{"app"}, + "bluez": {"app"}, + "bool-file": {"core", "gadget"}, + "browser-support": {"core"}, + "content": {"app", "gadget"}, + "docker-support": {"core"}, + "fwupd": {"app"}, + "gpio": {"core", "gadget"}, + "hidraw": {"core", "gadget"}, + "i2c": {"core", "gadget"}, + "location-control": {"app"}, + "location-observe": {"app"}, + "lxd-support": {"core"}, + "mir": {"app"}, + "modem-manager": {"app", "core"}, + "mpris": {"app"}, + "network-manager": {"app", "core"}, + "ofono": {"app", "core"}, + "ppp": {"core"}, + "pulseaudio": {"app", "core"}, + "serial-port": {"core", "gadget"}, + "udisks2": {"app"}, // snowflakes "docker": nil, "lxd": nil, diff --git a/interfaces/builtin/browser_support.go b/interfaces/builtin/browser_support.go index 55a01294f1..e5daa92b82 100644 --- a/interfaces/builtin/browser_support.go +++ b/interfaces/builtin/browser_support.go @@ -169,6 +169,9 @@ capability sys_chroot, owner @{PROC}/@{pid}/setgroups rw, owner @{PROC}/@{pid}/uid_map rw, owner @{PROC}/@{pid}/gid_map rw, + +# Webkit uses a particular SHM names # LP: 1578217 +owner /{dev,run}/shm/WK2SharedMemory.* rw, ` const browserSupportConnectedPlugSecComp = ` diff --git a/interfaces/builtin/gpio.go b/interfaces/builtin/gpio.go index fae814393a..e158384f5d 100644 --- a/interfaces/builtin/gpio.go +++ b/interfaces/builtin/gpio.go @@ -119,7 +119,7 @@ func (iface *GpioInterface) ConnectedSlotRichSnippet(plug *interfaces.Plug, slot serviceName := interfaces.InterfaceServiceName(slot.Snap.Name(), fmt.Sprintf("gpio-%d", gpioNum)) snippet := &systemd.Snippet{ Services: map[string]systemd.Service{ - serviceName: systemd.Service{ + serviceName: { Type: "oneshot", RemainAfterExit: true, ExecStart: fmt.Sprintf("/bin/sh -c 'test -e /sys/class/gpio/gpio%d || echo %d > /sys/class/gpio/export'", gpioNum, gpioNum), diff --git a/interfaces/builtin/modem_manager.go b/interfaces/builtin/modem_manager.go index 2883395645..b2ecae97a5 100644 --- a/interfaces/builtin/modem_manager.go +++ b/interfaces/builtin/modem_manager.go @@ -1160,7 +1160,7 @@ func (iface *ModemManagerInterface) ConnectedPlugSnippet(plug *interfaces.Plug, return nil, nil case interfaces.SecurityAppArmor: old := []byte("###SLOT_SECURITY_TAGS###") - new := []byte("") + var new []byte if release.OnClassic { // If we're running on classic ModemManager will be part // of the OS snap and will run unconfined. diff --git a/interfaces/builtin/network_manager.go b/interfaces/builtin/network_manager.go index b02cb7b5d6..b39060edc6 100644 --- a/interfaces/builtin/network_manager.go +++ b/interfaces/builtin/network_manager.go @@ -410,7 +410,7 @@ func (iface *NetworkManagerInterface) ConnectedPlugSnippet(plug *interfaces.Plug return nil, nil case interfaces.SecurityAppArmor: old := []byte("###SLOT_SECURITY_TAGS###") - new := []byte("") + var new []byte if release.OnClassic { // If we're running on classic NetworkManager will be part // of the OS snap and will run unconfined. diff --git a/interfaces/builtin/usb_raw.go b/interfaces/builtin/raw_usb.go index 1e10bff44b..485bce9314 100644 --- a/interfaces/builtin/usb_raw.go +++ b/interfaces/builtin/raw_usb.go @@ -23,7 +23,7 @@ import ( "github.com/snapcore/snapd/interfaces" ) -const usbrawConnectedPlugAppArmor = ` +const rawusbConnectedPlugAppArmor = ` # Description: Allow raw access to all connected USB devices. # Reserved because this gives privileged access to the system. # Usage: reserved @@ -40,10 +40,10 @@ const usbrawConnectedPlugAppArmor = ` ` // Transitional interface which allows access to all usb devices. -func NewUsbRawInterface() interfaces.Interface { +func NewRawUsbInterface() interfaces.Interface { return &commonInterface{ - name: "usb-raw", - connectedPlugAppArmor: usbrawConnectedPlugAppArmor, + name: "raw-usb", + connectedPlugAppArmor: rawusbConnectedPlugAppArmor, reservedForOS: true, } } diff --git a/interfaces/builtin/usb_raw_test.go b/interfaces/builtin/raw_usb_test.go index 03dbbb12c5..1a6f925c08 100644 --- a/interfaces/builtin/usb_raw_test.go +++ b/interfaces/builtin/raw_usb_test.go @@ -28,58 +28,58 @@ import ( "github.com/snapcore/snapd/testutil" ) -type UsbRawSuite struct { +type RawUsbSuite struct { iface interfaces.Interface slot *interfaces.Slot plug *interfaces.Plug } -var _ = Suite(&UsbRawSuite{ - iface: builtin.NewUsbRawInterface(), +var _ = Suite(&RawUsbSuite{ + iface: builtin.NewRawUsbInterface(), slot: &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "usb-raw", - Interface: "usb-raw", + Name: "raw-usb", + Interface: "raw-usb", }, }, plug: &interfaces.Plug{ PlugInfo: &snap.PlugInfo{ Snap: &snap.Info{SuggestedName: "other"}, - Name: "usb-raw", - Interface: "usb-raw", + Name: "raw-usb", + Interface: "raw-usb", }, }, }) -func (s *UsbRawSuite) TestName(c *C) { - c.Assert(s.iface.Name(), Equals, "usb-raw") +func (s *RawUsbSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "raw-usb") } -func (s *UsbRawSuite) TestSanitizeSlot(c *C) { +func (s *RawUsbSuite) TestSanitizeSlot(c *C) { err := s.iface.SanitizeSlot(s.slot) c.Assert(err, IsNil) err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, - Name: "usb-raw", - Interface: "usb-raw", + Name: "raw-usb", + Interface: "raw-usb", }}) - c.Assert(err, ErrorMatches, "usb-raw slots are reserved for the operating system snap") + c.Assert(err, ErrorMatches, "raw-usb slots are reserved for the operating system snap") } -func (s *UsbRawSuite) TestSanitizePlug(c *C) { +func (s *RawUsbSuite) TestSanitizePlug(c *C) { err := s.iface.SanitizePlug(s.plug) c.Assert(err, IsNil) } -func (s *UsbRawSuite) TestSanitizeIncorrectInterface(c *C) { +func (s *RawUsbSuite) TestSanitizeIncorrectInterface(c *C) { c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "usb-raw"`) + PanicMatches, `slot is not of interface "raw-usb"`) c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "usb-raw"`) + PanicMatches, `plug is not of interface "raw-usb"`) } -func (s *UsbRawSuite) TestUsedSecuritySystems(c *C) { +func (s *RawUsbSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) c.Assert(err, IsNil) diff --git a/interfaces/dbus/backend_test.go b/interfaces/dbus/backend_test.go index cf563fae73..9946092f12 100644 --- a/interfaces/dbus/backend_test.go +++ b/interfaces/dbus/backend_test.go @@ -212,6 +212,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippets(c *C) { c.Assert(err, IsNil) c.Check(string(data), Equals, "<?xml>\n<policy>...</policy>\n</xml>") stat, err := os.Stat(profile) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go index ba3b48cf63..02a20df8db 100644 --- a/interfaces/repo_test.go +++ b/interfaces/repo_test.go @@ -595,6 +595,7 @@ slots: // ResolveConnect detects lack of candidates func (s *RepositorySuite) TestResolveConnectNoImplicitCandidates(c *C) { err := s.testRepo.AddInterface(&TestInterface{InterfaceName: "other-interface"}) + c.Assert(err, IsNil) coreSnap := snaptest.MockInfo(c, ` name: core type: os @@ -1183,11 +1184,13 @@ func (s *AddRemoveSuite) TestAddSnapComplexErrorHandling(c *C) { SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("plug is invalid") }, SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("slot is invalid") }, }) + c.Assert(err, IsNil) err = s.repo.AddInterface(&TestInterface{ InterfaceName: "invalid-slot-iface", SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("plug is invalid") }, SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("slot is invalid") }, }) + c.Assert(err, IsNil) snapInfo := snaptest.MockInfo(c, ` name: complex plugs: @@ -1385,6 +1388,7 @@ func contentAutoConnect(plug *Plug, slot *Slot) bool { func makeContentConnectionTestSnaps(c *C, plugContentToken, slotContentToken string) (*Repository, *snap.Info, *snap.Info) { repo := NewRepository() err := repo.AddInterface(&TestInterface{InterfaceName: "content", AutoConnectCallback: contentAutoConnect}) + c.Assert(err, IsNil) plugSnap := snaptest.MockInfo(c, fmt.Sprintf(` name: content-plug-snap diff --git a/interfaces/seccomp/backend_test.go b/interfaces/seccomp/backend_test.go index 6d6eed1092..d817028cc9 100644 --- a/interfaces/seccomp/backend_test.go +++ b/interfaces/seccomp/backend_test.go @@ -208,6 +208,7 @@ func (s *backendSuite) TestCombineSnippets(c *C) { c.Assert(err, IsNil) c.Check(string(data), Equals, scenario.content) stat, err := os.Stat(profile) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } diff --git a/interfaces/systemd/backend_test.go b/interfaces/systemd/backend_test.go index 219aeb96f8..f62a97281d 100644 --- a/interfaces/systemd/backend_test.go +++ b/interfaces/systemd/backend_test.go @@ -60,7 +60,7 @@ func (s *backendSuite) TestName(c *C) { func (s *backendSuite) TestUnmarshalRawSnippetMap(c *C) { rawSnippetMap := map[string][][]byte{ - "security-tag": [][]byte{ + "security-tag": { []byte(`{"services": {"foo.service": {"exec-start": "/bin/true"}}}`), []byte(`{"services": {"bar.service": {"exec-start": "/bin/false"}}}`), }, @@ -68,13 +68,13 @@ func (s *backendSuite) TestUnmarshalRawSnippetMap(c *C) { richSnippetMap, err := systemd.UnmarshalRawSnippetMap(rawSnippetMap) c.Assert(err, IsNil) c.Assert(richSnippetMap, DeepEquals, map[string][]*systemd.Snippet{ - "security-tag": []*systemd.Snippet{ - &systemd.Snippet{ + "security-tag": { + { Services: map[string]systemd.Service{ "foo.service": {ExecStart: "/bin/true"}, }, }, - &systemd.Snippet{ + { Services: map[string]systemd.Service{ "bar.service": {ExecStart: "/bin/false"}, }, @@ -85,15 +85,15 @@ func (s *backendSuite) TestUnmarshalRawSnippetMap(c *C) { func (s *backendSuite) TestMergeSnippetMapOK(c *C) { snippetMap := map[string][]*systemd.Snippet{ - "security-tag": []*systemd.Snippet{ - &systemd.Snippet{ + "security-tag": { + { Services: map[string]systemd.Service{ "foo.service": {ExecStart: "/bin/true"}, }, }, }, - "another-tag": []*systemd.Snippet{ - &systemd.Snippet{ + "another-tag": { + { Services: map[string]systemd.Service{ "bar.service": {ExecStart: "/bin/false"}, }, @@ -112,15 +112,15 @@ func (s *backendSuite) TestMergeSnippetMapOK(c *C) { func (s *backendSuite) TestMergeSnippetMapClashing(c *C) { snippetMap := map[string][]*systemd.Snippet{ - "security-tag": []*systemd.Snippet{ - &systemd.Snippet{ + "security-tag": { + { Services: map[string]systemd.Service{ "foo.service": {ExecStart: "/bin/true"}, }, }, }, - "another-tag": []*systemd.Snippet{ - &systemd.Snippet{ + "another-tag": { + { Services: map[string]systemd.Service{ "foo.service": {ExecStart: "/bin/evil"}, }, @@ -141,7 +141,7 @@ func (s *backendSuite) TestRenderSnippet(c *C) { content, err := systemd.RenderSnippet(snippet) c.Assert(err, IsNil) c.Assert(content, DeepEquals, map[string]*osutil.FileState{ - "foo.service": &osutil.FileState{ + "foo.service": { Content: []byte("[Service]\nExecStart=/bin/true\n\n[Install]\nWantedBy=multi-user.target\n"), Mode: 0644, }, diff --git a/interfaces/udev/backend_test.go b/interfaces/udev/backend_test.go index 1825c65501..71b458030a 100644 --- a/interfaces/udev/backend_test.go +++ b/interfaces/udev/backend_test.go @@ -267,6 +267,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippets(c *C) { c.Assert(err, IsNil) c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n") stat, err := os.Stat(fname) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } @@ -284,6 +285,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWhenPlugNoApps(c *C) c.Assert(err, IsNil) c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n") stat, err := os.Stat(fname) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } @@ -301,6 +303,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWhenSlotNoApps(c *C) c.Assert(err, IsNil) c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n") stat, err := os.Stat(fname) + c.Assert(err, IsNil) c.Check(stat.Mode(), Equals, os.FileMode(0644)) s.RemoveSnap(c, snapInfo) } diff --git a/osutil/exitcode_test.go b/osutil/exitcode_test.go index 01a4d80d6b..296a624633 100644 --- a/osutil/exitcode_test.go +++ b/osutil/exitcode_test.go @@ -45,6 +45,7 @@ func (ts *ExitCodeTestSuite) TestExitCode(c *C) { cmd = exec.Command("sh", "-c", "exit 7") err = cmd.Run() e, err = ExitCode(err) + c.Assert(err, IsNil) c.Assert(e, Equals, 7) // ensure that non exec.ExitError values give a error diff --git a/overlord/assertstate/assertmgr.go b/overlord/assertstate/assertmgr.go index ebc242699e..fdfaa5bcf2 100644 --- a/overlord/assertstate/assertmgr.go +++ b/overlord/assertstate/assertmgr.go @@ -341,8 +341,8 @@ func RefreshSnapDeclarations(s *state.State, userID int) error { return nil } fetching := func(f asserts.Fetcher) error { - for _, snapState := range snapStates { - info, err := snapState.CurrentInfo() + for _, snapst := range snapStates { + info, err := snapst.CurrentInfo() if err != nil { return err } @@ -385,8 +385,8 @@ func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, userID int) (vali if err != nil { return nil, err } - for snapName, snapState := range snapStates { - info, err := snapState.CurrentInfo() + for snapName, snapst := range snapStates { + info, err := snapst.CurrentInfo() if err != nil { return nil, err } diff --git a/overlord/auth/auth.go b/overlord/auth/auth.go index 8c910a667c..b1ff031975 100644 --- a/overlord/auth/auth.go +++ b/overlord/auth/auth.go @@ -192,7 +192,7 @@ func Users(st *state.State) ([]*UserState, error) { } users := make([]*UserState, len(authStateData.Users)) - for i, _ := range authStateData.Users { + for i := range authStateData.Users { users[i] = &authStateData.Users[i] } return users, nil diff --git a/overlord/auth/auth_test.go b/overlord/auth/auth_test.go index 299d3a3d8c..a3bdc4fbb9 100644 --- a/overlord/auth/auth_test.go +++ b/overlord/auth/auth_test.go @@ -120,6 +120,7 @@ func (as *authSuite) TestNewUser(c *C) { func (as *authSuite) TestNewUserSortsDischarges(c *C) { as.state.Lock() user, err := auth.NewUser(as.state, "", "email@test.com", "macaroon", []string{"discharge2", "discharge1"}) + c.Assert(err, IsNil) as.state.Unlock() expected := []string{"discharge1", "discharge2"} diff --git a/overlord/devicestate/devicemgr_test.go b/overlord/devicestate/devicemgr_test.go index ea0d212342..97843adfbb 100644 --- a/overlord/devicestate/devicemgr_test.go +++ b/overlord/devicestate/devicemgr_test.go @@ -733,6 +733,7 @@ func (s *deviceMgrSuite) TestDeviceAssertionsDeviceSessionRequest(c *C) { s.state.Lock() devKey, _ := assertstest.GenerateKey(1024) encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey()) + c.Check(err, IsNil) seriala, err := s.storeSigning.Sign(asserts.SerialType, map[string]interface{}{ "brand-id": "canonical", "model": "pc", diff --git a/overlord/devicestate/firstboot_test.go b/overlord/devicestate/firstboot_test.go index 777c112ca4..336bdb122d 100644 --- a/overlord/devicestate/firstboot_test.go +++ b/overlord/devicestate/firstboot_test.go @@ -356,6 +356,7 @@ snaps: defer st.Unlock() tsAll, err := devicestate.PopulateStateFromSeedImpl(st) + c.Assert(err, IsNil) chg := st.NewChange("run-it", "run the populate from seed changes") for _, ts := range tsAll { chg.AddAll(ts) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index f90a8e9fb3..c65c1316ec 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -228,13 +228,13 @@ func (m *InterfaceManager) doDiscardConns(task *state.Task, _ *tomb.Tomb) error snapName := snapSetup.Name() - var snapState snapstate.SnapState - err = snapstate.Get(st, snapName, &snapState) + var snapst snapstate.SnapState + err = snapstate.Get(st, snapName, &snapst) if err != nil && err != state.ErrNoState { return err } - if err == nil && len(snapState.Sequence) != 0 { + if err == nil && len(snapst.Sequence) != 0 { return fmt.Errorf("cannot discard connections for snap %q while it is present", snapName) } conns, err := getConns(st) diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 79eaeba729..a239794dfd 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -702,6 +702,8 @@ version: @VERSION@ ms.serveSnap(snapPath, revno) updated, tss, err := snapstate.UpdateMany(st, []string{"foo"}, 0) + c.Check(updated, IsNil) + c.Check(tss, IsNil) // no validation we, get an error c.Check(err, ErrorMatches, `cannot refresh "foo" to revision 50: no validation by "bar"`) diff --git a/overlord/patch/patch1.go b/overlord/patch/patch1.go index 3c9f17eb5b..c254bb0926 100644 --- a/overlord/patch/patch1.go +++ b/overlord/patch/patch1.go @@ -103,19 +103,19 @@ func patch1(s *state.State) error { return err } - for snapName, snapState := range stateMap { - seq := snapState.Sequence + for snapName, snapst := range stateMap { + seq := snapst.Sequence if len(seq) == 0 { continue } - snapState.Current = seq[len(seq)-1].Revision - typ, err := patch1ReadType(snapName, snapState.Current) + snapst.Current = seq[len(seq)-1].Revision + typ, err := patch1ReadType(snapName, snapst.Current) if err != nil { logger.Noticef("Recording type for snap %q: cannot retrieve info, assuming it's a app: %v", snapName, err) } else { logger.Noticef("Recording type for snap %q: setting to %q", snapName, typ) } - snapState.SnapType = string(typ) + snapst.SnapType = string(typ) } s.Set("snaps", stateMap) diff --git a/overlord/patch/patch5.go b/overlord/patch/patch5.go index 92f7e5dc0d..cf4df82505 100644 --- a/overlord/patch/patch5.go +++ b/overlord/patch/patch5.go @@ -46,12 +46,12 @@ func patch5(st *state.State) error { return err } - for snapName, snapState := range snapStates { - if !snapState.Active { + for snapName, snapst := range snapStates { + if !snapst.Active { continue } - info, err := snapState.CurrentInfo() + info, err := snapst.CurrentInfo() if err != nil { return err } diff --git a/overlord/patch/patch_test.go b/overlord/patch/patch_test.go index 784686a35f..df8a4f562e 100644 --- a/overlord/patch/patch_test.go +++ b/overlord/patch/patch_test.go @@ -179,7 +179,7 @@ func (s *patchSuite) TestError(c *C) { func (s *patchSuite) TestSanity(c *C) { patches := patch.PatchesForTest() levels := make([]int, 0, len(patches)) - for l, _ := range patches { + for l := range patches { levels = append(levels, l) } sort.Ints(levels) diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go index fbe1af17a1..25874bd8d7 100644 --- a/overlord/snapstate/snapmgr.go +++ b/overlord/snapstate/snapmgr.go @@ -416,10 +416,11 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error { targetFn := snapsup.MountFile() if snapsup.DownloadInfo == nil { + var storeInfo *snap.Info // COMPATIBILITY - this task was created from an older version // of snapd that did not store the DownloadInfo in the state // yet. - storeInfo, err := theStore.Snap(snapsup.Name(), snapsup.Channel, snapsup.DevModeAllowed(), snapsup.Revision(), user) + storeInfo, err = theStore.Snap(snapsup.Name(), snapsup.Channel, snapsup.DevModeAllowed(), snapsup.Revision(), user) if err != nil { return err } diff --git a/overlord/snapstate/snapmgr_test.go b/overlord/snapstate/snapmgr_test.go index d4a25ca6f8..701f7782bd 100644 --- a/overlord/snapstate/snapmgr_test.go +++ b/overlord/snapstate/snapmgr_test.go @@ -3343,15 +3343,10 @@ func (s *snapmgrQuerySuite) TestAll(c *C) { snapStates, err := snapstate.All(st) c.Assert(err, IsNil) + c.Assert(snapStates, HasLen, 1) - c.Check(snapStates, HasLen, 1) - - var snapst *snapstate.SnapState - - for name, sst := range snapStates { - c.Assert(name, Equals, "name1") - snapst = sst - } + snapst := snapStates["name1"] + c.Assert(snapst, NotNil) c.Check(snapst.Active, Equals, true) c.Check(snapst.CurrentSideInfo(), NotNil) diff --git a/overlord/state/state_test.go b/overlord/state/state_test.go index 26cf21e660..bbfacdbdd1 100644 --- a/overlord/state/state_test.go +++ b/overlord/state/state_test.go @@ -327,6 +327,7 @@ func (ss *stateSuite) TestNewChangeAndCheckpoint(c *C) { var v int err = chg0.Get("a", &v) + c.Check(err, IsNil) c.Check(v, Equals, 1) c.Check(chg0.Status(), Equals, state.ErrorStatus) @@ -434,6 +435,7 @@ func (ss *stateSuite) TestNewTaskAndCheckpoint(c *C) { var v int err = task0_1.Get("a", &v) + c.Check(err, IsNil) c.Check(v, Equals, 1) c.Check(task0_1.Status(), Equals, state.DoneStatus) diff --git a/overlord/state/task.go b/overlord/state/task.go index 995157c190..0aecdd4e41 100644 --- a/overlord/state/task.go +++ b/overlord/state/task.go @@ -279,7 +279,7 @@ func (t *Task) AtTime() time.Time { const ( // Messages logged in tasks are guaranteed to use the time formatted // per RFC3339 plus the following strings as a prefix, so these may - // be handled programatically and parsed or stripped for presentation. + // be handled programmatically and parsed or stripped for presentation. LogInfo = "INFO" LogError = "ERROR" ) diff --git a/partition/bootloader_test.go b/partition/bootloader_test.go index 806d6b3b17..01fdf3a192 100644 --- a/partition/bootloader_test.go +++ b/partition/bootloader_test.go @@ -149,6 +149,7 @@ func (s *PartitionTestSuite) TestInstallBootloaderConfig(c *C) { } { mockGadgetDir := c.MkDir() err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), nil, 0644) + c.Assert(err, IsNil) err = InstallBootConfig(mockGadgetDir) c.Assert(err, IsNil) fn := filepath.Join(dirs.GlobalRootDir, t.systemFile) diff --git a/progress/progress.go b/progress/progress.go index e734f027d5..334924b6e3 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -100,8 +100,7 @@ func NewTextProgress() *TextProgress { // Start starts showing progress func (t *TextProgress) Start(label string, total float64) { - t.pbar = pb.New64(0) - t.pbar.Total = int64(total) + t.pbar = pb.New64(int64(total)) t.pbar.ShowSpeed = true t.pbar.Units = pb.U_BYTES t.pbar.Prefix(label) diff --git a/snap/implicit.go b/snap/implicit.go index 6bb08b8564..af9e87da67 100644 --- a/snap/implicit.go +++ b/snap/implicit.go @@ -50,6 +50,7 @@ var implicitSlots = []string{ "opengl", "ppp", "process-control", + "raw-usb", "removable-media", "shutdown", "snapd-control", @@ -59,7 +60,6 @@ var implicitSlots = []string{ "timeserver-control", "timezone-control", "tpm", - "usb-raw", } var implicitClassicSlots = []string{ diff --git a/snap/info_test.go b/snap/info_test.go index 9817f827bf..e94494a2df 100644 --- a/snap/info_test.go +++ b/snap/info_test.go @@ -142,6 +142,7 @@ func makeTestSnap(c *C, yaml string) string { snapSource := filepath.Join(tmp, "snapsrc") err := os.MkdirAll(filepath.Join(snapSource, "meta"), 0755) + c.Assert(err, IsNil) // our regular snap.yaml err = ioutil.WriteFile(filepath.Join(snapSource, "meta", "snap.yaml"), []byte(yaml), 0644) diff --git a/snap/snaptest/build_test.go b/snap/snaptest/build_test.go index 52883be108..5ba305cd9f 100644 --- a/snap/snaptest/build_test.go +++ b/snap/snaptest/build_test.go @@ -91,6 +91,7 @@ printf "hello world" err = ioutil.WriteFile(someFile, []byte(""), 0666) c.Assert(err, IsNil) err = os.Chmod(someFile, 0666) + c.Assert(err, IsNil) // an example symlink err = os.Symlink("bin/hello-world", filepath.Join(tempdir, "symlink")) @@ -172,6 +173,7 @@ func (s *BuildTestSuite) TestCopyExcludesWholeDirs(c *C) { c.Assert(ioutil.WriteFile(filepath.Join(sourceDir, ".bzr", "foo"), []byte("hi"), 0755), IsNil) c.Assert(snaptest.CopyToBuildDir(sourceDir, target), IsNil) out, _ := exec.Command("find", sourceDir).Output() + c.Check(string(out), Not(Equals), "") cmd := exec.Command("diff", "-qr", sourceDir, target) cmd.Env = append(cmd.Env, "LANG=C") out, err := cmd.Output() @@ -260,6 +262,7 @@ integration: outputDir := filepath.Join(c.MkDir(), "output") snapOutput := filepath.Join(outputDir, "hello_1.0.1_multi.snap") resultSnap, err := snaptest.BuildSquashfsSnap(sourceDir, outputDir) + c.Assert(err, IsNil) // check that there is result _, err = os.Stat(resultSnap) diff --git a/snap/validate_test.go b/snap/validate_test.go index 3f3d8a18b2..4ff503268c 100644 --- a/snap/validate_test.go +++ b/snap/validate_test.go @@ -83,27 +83,27 @@ func (s *ValidateSuite) TestValidateEpoch(c *C) { func (s *ValidateSuite) TestValidateHook(c *C) { validHooks := []*HookInfo{ - &HookInfo{Name: "a"}, - &HookInfo{Name: "aaa"}, - &HookInfo{Name: "a-a"}, - &HookInfo{Name: "aa-a"}, - &HookInfo{Name: "a-aa"}, - &HookInfo{Name: "a-b-c"}, + {Name: "a"}, + {Name: "aaa"}, + {Name: "a-a"}, + {Name: "aa-a"}, + {Name: "a-aa"}, + {Name: "a-b-c"}, } for _, hook := range validHooks { err := ValidateHook(hook) c.Assert(err, IsNil) } invalidHooks := []*HookInfo{ - &HookInfo{Name: ""}, - &HookInfo{Name: "a a"}, - &HookInfo{Name: "a--a"}, - &HookInfo{Name: "-a"}, - &HookInfo{Name: "a-"}, - &HookInfo{Name: "0"}, - &HookInfo{Name: "123"}, - &HookInfo{Name: "123abc"}, - &HookInfo{Name: "日本語"}, + {Name: ""}, + {Name: "a a"}, + {Name: "a--a"}, + {Name: "-a"}, + {Name: "a-"}, + {Name: "0"}, + {Name: "123"}, + {Name: "123abc"}, + {Name: "日本語"}, } for _, hook := range invalidHooks { err := ValidateHook(hook) diff --git a/store/auth.go b/store/auth.go index b99a3c80a6..e6746583f2 100644 --- a/store/auth.go +++ b/store/auth.go @@ -86,6 +86,9 @@ func requestStoreMacaroon() (string, error) { "permissions": []string{"package_access", "package_purchase"}, } macaroonJSONData, err := json.Marshal(data) + if err != nil { + return "", fmt.Errorf(errorPrefix+"%v", err) + } req, err := http.NewRequest("POST", MyAppsMacaroonACLAPI, bytes.NewReader(macaroonJSONData)) if err != nil { diff --git a/store/store.go b/store/store.go index a1bc528a16..2e0c2d19e9 100644 --- a/store/store.go +++ b/store/store.go @@ -576,6 +576,28 @@ type requestOptions struct { Data []byte } +// retryRequest uses defaultRetryStrategy to call doRequest with retry +func (s *Store) retryRequest(client *http.Client, reqOptions *requestOptions, user *auth.UserState) (resp *http.Response, err error) { + for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { + resp, err = s.doRequest(client, reqOptions, user) + if err != nil { + if shouldRetryError(attempt, err) { + continue + } + break + } + + if shouldRetryHttpResponse(attempt, resp) { + resp.Body.Close() + continue + } + + break + } + + return resp, err +} + // doRequest does an authenticated request to the store handling a potential macaroon refresh required if needed func (s *Store) doRequest(client *http.Client, reqOptions *requestOptions, user *auth.UserState) (*http.Response, error) { req, err := s.newRequest(reqOptions, user) @@ -823,58 +845,46 @@ func (s *Store) Snap(name, channel string, devmode bool, revision snap.Revision, u.RawQuery = query.Encode() - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - reqOptions := &requestOptions{ - Method: "GET", - URL: u, - Accept: halJsonContentType, - } - - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } - return nil, err - } + reqOptions := &requestOptions{ + Method: "GET", + URL: u, + Accept: halJsonContentType, + } - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue - } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return nil, err + } + defer resp.Body.Close() - defer resp.Body.Close() - - // check statusCode - switch resp.StatusCode { - case http.StatusOK: - // OK - case http.StatusNotFound: - return nil, ErrSnapNotFound - default: - msg := fmt.Sprintf("get details for snap %q in channel %q", name, channel) - return nil, respToError(resp, msg) - } + // check statusCode + switch resp.StatusCode { + case http.StatusOK: + // OK + case http.StatusNotFound: + return nil, ErrSnapNotFound + default: + msg := fmt.Sprintf("get details for snap %q in channel %q", name, channel) + return nil, respToError(resp, msg) + } - // and decode json - var remote snapDetails - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&remote); err != nil { - return nil, err - } + // and decode json + var remote snapDetails + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&remote); err != nil { + return nil, err + } - info := infoFromRemote(remote) + info := infoFromRemote(remote) - err = s.decorateOrders([]*snap.Info{info}, channel, user) - if err != nil { - logger.Noticef("cannot get user orders: %v", err) - } + err = s.decorateOrders([]*snap.Info{info}, channel, user) + if err != nil { + logger.Noticef("cannot get user orders: %v", err) + } - s.extractSuggestedCurrency(resp) + s.extractSuggestedCurrency(resp) - return info, nil - } - panic("unreachable") + return info, nil } // A Search is what you do in order to Find something @@ -931,56 +941,46 @@ func (s *Store) Find(search *Search, user *auth.UserState) ([]*snap.Info, error) q.Set("confinement", "strict") u.RawQuery = q.Encode() - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - reqOptions := &requestOptions{ - Method: "GET", - URL: &u, - Accept: halJsonContentType, - } - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } - return nil, err - } + reqOptions := &requestOptions{ + Method: "GET", + URL: &u, + Accept: halJsonContentType, + } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return nil, err + } - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue - } - defer resp.Body.Close() + defer resp.Body.Close() - if resp.StatusCode != 200 { - return nil, respToError(resp, "search") - } + if resp.StatusCode != 200 { + return nil, respToError(resp, "search") + } - if ct := resp.Header.Get("Content-Type"); ct != halJsonContentType { - return nil, fmt.Errorf("received an unexpected content type (%q) when trying to search via %q", ct, resp.Request.URL) - } + if ct := resp.Header.Get("Content-Type"); ct != halJsonContentType { + return nil, fmt.Errorf("received an unexpected content type (%q) when trying to search via %q", ct, resp.Request.URL) + } - var searchData searchResults + var searchData searchResults - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&searchData); err != nil { - return nil, fmt.Errorf("cannot decode reply (got %v) when trying to search via %q", err, resp.Request.URL) - } + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&searchData); err != nil { + return nil, fmt.Errorf("cannot decode reply (got %v) when trying to search via %q", err, resp.Request.URL) + } - snaps := make([]*snap.Info, len(searchData.Payload.Packages)) - for i, pkg := range searchData.Payload.Packages { - snaps[i] = infoFromRemote(pkg) - } + snaps := make([]*snap.Info, len(searchData.Payload.Packages)) + for i, pkg := range searchData.Payload.Packages { + snaps[i] = infoFromRemote(pkg) + } - err = s.decorateOrders(snaps, "", user) - if err != nil { - logger.Noticef("cannot get user orders: %v", err) - } + err = s.decorateOrders(snaps, "", user) + if err != nil { + logger.Noticef("cannot get user orders: %v", err) + } - s.extractSuggestedCurrency(resp) + s.extractSuggestedCurrency(resp) - return snaps, nil - } - panic("unreachable") + return snaps, nil } // RefreshCandidate contains information for the store about the currently @@ -1055,68 +1055,58 @@ func (s *Store) ListRefresh(installed []*RefreshCandidate, user *auth.UserState) return nil, err } - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - reqOptions := &requestOptions{ - Method: "POST", - URL: s.bulkURI, - Accept: halJsonContentType, - ContentType: jsonContentType, - Data: jsonData, - } + reqOptions := &requestOptions{ + Method: "POST", + URL: s.bulkURI, + Accept: halJsonContentType, + ContentType: jsonContentType, + Data: jsonData, + } - if useDeltas() { - logger.Debugf("Deltas enabled. Adding header X-Ubuntu-Delta-Formats: %v", s.deltaFormat) - reqOptions.ExtraHeaders = map[string]string{ - "X-Ubuntu-Delta-Formats": s.deltaFormat, - } + if useDeltas() { + logger.Debugf("Deltas enabled. Adding header X-Ubuntu-Delta-Formats: %v", s.deltaFormat) + reqOptions.ExtraHeaders = map[string]string{ + "X-Ubuntu-Delta-Formats": s.deltaFormat, } + } - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } - return nil, err - } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return nil, err + } - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue - } - defer resp.Body.Close() + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, respToError(resp, "query the store for updates") - } + if resp.StatusCode != http.StatusOK { + return nil, respToError(resp, "query the store for updates") + } - var updateData searchResults - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&updateData); err != nil { - return nil, err - } + var updateData searchResults + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&updateData); err != nil { + return nil, err + } - res := make([]*snap.Info, 0, len(updateData.Payload.Packages)) - for _, rsnap := range updateData.Payload.Packages { - rrev := snap.R(rsnap.Revision) - cand := candidateMap[rsnap.SnapID] + res := make([]*snap.Info, 0, len(updateData.Payload.Packages)) + for _, rsnap := range updateData.Payload.Packages { + rrev := snap.R(rsnap.Revision) + cand := candidateMap[rsnap.SnapID] - // the store also gives us identical revisions, filter those - // out, we are not interested - if rrev == cand.Revision { - continue - } - // do not upgade to a version we rolledback back from - if findRev(rrev, cand.Block) { - continue - } - res = append(res, infoFromRemote(rsnap)) + // the store also gives us identical revisions, filter those + // out, we are not interested + if rrev == cand.Revision { + continue + } + // do not upgade to a version we rolledback back from + if findRev(rrev, cand.Block) { + continue } + res = append(res, infoFromRemote(rsnap)) + } - s.extractSuggestedCurrency(resp) + s.extractSuggestedCurrency(resp) - return res, nil - } - panic("unreachable") + return res, nil } func findRev(needle snap.Revision, haystack []snap.Revision) bool { @@ -1369,47 +1359,37 @@ func (s *Store) Assertion(assertType *asserts.AssertionType, primaryKey []string v.Set("max-format", strconv.Itoa(assertType.MaxSupportedFormat())) u.RawQuery = v.Encode() - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - reqOptions := &requestOptions{ - Method: "GET", - URL: u, - Accept: asserts.MediaType, - } - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } - return nil, err - } + reqOptions := &requestOptions{ + Method: "GET", + URL: u, + Accept: asserts.MediaType, + } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return nil, err + } - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - contentType := resp.Header.Get("Content-Type") - if contentType == jsonContentType || contentType == "application/problem+json" { - var svcErr assertionSvcError - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&svcErr); err != nil { - return nil, fmt.Errorf("cannot decode assertion service error with HTTP status code %d: %v", resp.StatusCode, err) - } - if svcErr.Status == 404 { - return nil, &AssertionNotFoundError{&asserts.Ref{Type: assertType, PrimaryKey: primaryKey}} - } - return nil, fmt.Errorf("assertion service error: [%s] %q", svcErr.Title, svcErr.Detail) + defer resp.Body.Close() + + if resp.StatusCode != 200 { + contentType := resp.Header.Get("Content-Type") + if contentType == jsonContentType || contentType == "application/problem+json" { + var svcErr assertionSvcError + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&svcErr); err != nil { + return nil, fmt.Errorf("cannot decode assertion service error with HTTP status code %d: %v", resp.StatusCode, err) + } + if svcErr.Status == 404 { + return nil, &AssertionNotFoundError{&asserts.Ref{Type: assertType, PrimaryKey: primaryKey}} } - return nil, respToError(resp, "fetch assertion") + return nil, fmt.Errorf("assertion service error: [%s] %q", svcErr.Title, svcErr.Detail) } - - // and decode assertion - dec := asserts.NewDecoder(resp.Body) - return dec.Decode() + return nil, respToError(resp, "fetch assertion") } - panic("unreachable") + + // and decode assertion + dec := asserts.NewDecoder(resp.Body) + return dec.Decode() } // SuggestedCurrency retrieves the cached value for the store's suggested currency @@ -1499,71 +1479,61 @@ func (s *Store) Buy(options *BuyOptions, user *auth.UserState) (*BuyResult, erro return nil, err } - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - reqOptions := &requestOptions{ - Method: "POST", - URL: s.ordersURI, - Accept: jsonContentType, - ContentType: jsonContentType, - Data: jsonData, - } - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } + reqOptions := &requestOptions{ + Method: "POST", + URL: s.ordersURI, + Accept: jsonContentType, + ContentType: jsonContentType, + Data: jsonData, + } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusOK, http.StatusCreated: + // user already ordered or order successful + var orderDetails order + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&orderDetails); err != nil { return nil, err } - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue + if orderDetails.State == "Cancelled" { + return buyOptionError("payment cancelled") } - defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK, http.StatusCreated: - // user already ordered or order successful - var orderDetails order - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&orderDetails); err != nil { - return nil, err - } - - if orderDetails.State == "Cancelled" { - return buyOptionError("payment cancelled") - } - - return &BuyResult{ - State: orderDetails.State, - }, nil - case http.StatusBadRequest: - // Invalid price was specified, etc. - var errorInfo storeErrors - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&errorInfo); err != nil { - return nil, err - } - return buyOptionError(fmt.Sprintf("bad request: %v", errorInfo.Error())) - case http.StatusNotFound: - // Likely because snap ID doesn't exist. - return buyOptionError("server says not found (snap got removed?)") - case http.StatusPaymentRequired: - // Payment failed for some reason. - return nil, ErrPaymentDeclined - case http.StatusUnauthorized: - // TODO handle token expiry and refresh - return nil, ErrInvalidCredentials - default: - var errorInfo storeErrors - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&errorInfo); err != nil { - return nil, err - } - return nil, respToError(resp, fmt.Sprintf("buy snap: %v", errorInfo)) + return &BuyResult{ + State: orderDetails.State, + }, nil + case http.StatusBadRequest: + // Invalid price was specified, etc. + var errorInfo storeErrors + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&errorInfo); err != nil { + return nil, err } + return buyOptionError(fmt.Sprintf("bad request: %v", errorInfo.Error())) + case http.StatusNotFound: + // Likely because snap ID doesn't exist. + return buyOptionError("server says not found (snap got removed?)") + case http.StatusPaymentRequired: + // Payment failed for some reason. + return nil, ErrPaymentDeclined + case http.StatusUnauthorized: + // TODO handle token expiry and refresh + return nil, ErrInvalidCredentials + default: + var errorInfo storeErrors + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&errorInfo); err != nil { + return nil, err + } + return nil, respToError(resp, fmt.Sprintf("buy snap: %v", errorInfo)) } - panic("unreachable") } type storeCustomer struct { @@ -1585,51 +1555,39 @@ func (s *Store) ReadyToBuy(user *auth.UserState) error { Accept: jsonContentType, } - for attempt := retry.Start(defaultRetryStrategy, nil); attempt.Next(); { - resp, err := s.doRequest(s.client, reqOptions, user) - if err != nil { - if shouldRetryError(attempt, err) { - continue - } + resp, err := s.retryRequest(s.client, reqOptions, user) + if err != nil { + return err + } + + switch resp.StatusCode { + case http.StatusOK: + var customer storeCustomer + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&customer); err != nil { return err } - - if shouldRetryHttpResponse(attempt, resp) { - resp.Body.Close() - continue + if !customer.HasPaymentMethod { + return ErrNoPaymentMethods } - - switch resp.StatusCode { - case http.StatusOK: - var customer storeCustomer - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&customer); err != nil { - return err - } - if !customer.HasPaymentMethod { - return ErrNoPaymentMethods - } - if !customer.LatestTOSAccepted { - return ErrTOSNotAccepted - } - return nil - case http.StatusNotFound: - // Likely because user has no account registered on the pay server - return fmt.Errorf("cannot get customer details: server says no account exists") - case http.StatusUnauthorized: - return ErrInvalidCredentials - default: - var errors storeErrors - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&errors); err != nil { - return err - } - if len(errors.Errors) == 0 { - return fmt.Errorf("cannot get customer details: unexpected HTTP code %d", resp.StatusCode) - } - return &errors + if !customer.LatestTOSAccepted { + return ErrTOSNotAccepted } + return nil + case http.StatusNotFound: + // Likely because user has no account registered on the pay server + return fmt.Errorf("cannot get customer details: server says no account exists") + case http.StatusUnauthorized: + return ErrInvalidCredentials + default: + var errors storeErrors + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&errors); err != nil { + return err + } + if len(errors.Errors) == 0 { + return fmt.Errorf("cannot get customer details: unexpected HTTP code %d", resp.StatusCode) + } + return &errors } - - return nil } diff --git a/store/store_test.go b/store/store_test.go index 0809162e06..82402672b0 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -1464,6 +1464,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryRevision(c *C) { defer mockPurchasesServer.Close() ordersURI, err := url.Parse(mockPurchasesServer.URL + ordersPath) + c.Assert(err, IsNil) detailsURI, err := url.Parse(mockServer.URL + "/details/") c.Assert(err, IsNil) cfg := DefaultConfig() @@ -2673,6 +2674,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryAssertion500(c *C) { repo := New(&cfg, nil) _, err = repo.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil) + c.Assert(err, ErrorMatches, `cannot fetch assertion: got unexpected HTTP status code 500 via .+`) c.Assert(n, Equals, 6) } diff --git a/systemd/escape.go b/systemd/escape.go index 615f646bad..b0fa7248c5 100644 --- a/systemd/escape.go +++ b/systemd/escape.go @@ -47,7 +47,7 @@ func EscapeUnitNamePath(in string) string { // leading "." is special if in[0] == '.' { fmt.Fprintf(buf, `\x%x`, in[0]) - in = in[1:len(in)] + in = in[1:] } // replace all special chars diff --git a/tests/main/ubuntu-core-custom-device-reg-extras/manip_seed.py b/tests/main/ubuntu-core-custom-device-reg-extras/manip_seed.py new file mode 100644 index 0000000000..35251a91fd --- /dev/null +++ b/tests/main/ubuntu-core-custom-device-reg-extras/manip_seed.py @@ -0,0 +1,21 @@ +import sys +import yaml + +with open(sys.argv[1]) as f: + seed = yaml.load(f) + +i = 0 +snaps = seed['snaps'] +while i < len(snaps): + entry = snaps[i] + if entry['name'] == 'pc': + snaps[i] = { + "name": "pc", + "unasserted": True, + "file": "pc_x1.snap", + } + break + i += 1 + +with open(sys.argv[1], 'w') as f: + yaml.dump(seed, stream=f, indent=2, default_flow_style=False) diff --git a/tests/main/ubuntu-core-custom-device-reg-extras/prepare-device b/tests/main/ubuntu-core-custom-device-reg-extras/prepare-device new file mode 100755 index 0000000000..bcee1572d5 --- /dev/null +++ b/tests/main/ubuntu-core-custom-device-reg-extras/prepare-device @@ -0,0 +1,5 @@ +#!/bin/sh +snapctl set device-service.url=http://localhost:11029 +snapctl set device-service.headers='{"X-Use-Proposed": "yes"}' +snapctl set registration.proposed-serial="Y1234" +snapctl set registration.body='mac: "00:00:00:00:ff:00"' diff --git a/tests/main/ubuntu-core-custom-device-reg-extras/task.yaml b/tests/main/ubuntu-core-custom-device-reg-extras/task.yaml new file mode 100644 index 0000000000..fd20a25c06 --- /dev/null +++ b/tests/main/ubuntu-core-custom-device-reg-extras/task.yaml @@ -0,0 +1,81 @@ +summary: | + Test that device initialisation and registration can be customized + with the prepare-device gadget hook and this can set request headers, + a proposed serial and the body of the serial assertion +systems: [ubuntu-core-16-64] +prepare: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + . $TESTSLIB/systemd.sh + systemctl stop snapd.service snapd.socket + rm -rf /var/lib/snapd/assertions/* + rm -rf /var/lib/snapd/device + rm -rf /var/lib/snapd/state.json + unsquashfs /var/lib/snapd/snaps/pc_*.snap + mkdir -p squashfs-root/meta/hooks + cp prepare-device squashfs-root/meta/hooks + mksquashfs squashfs-root pc_x1.snap -comp xz + rm -rf squashfs-root + cp pc_x1.snap /var/lib/snapd/seed/snaps/ + mv /var/lib/snapd/seed/assertions/model model.bak + cp /var/lib/snapd/seed/seed.yaml seed.yaml.bak + python3 ./manip_seed.py /var/lib/snapd/seed/seed.yaml + cp $TESTSLIB/assertions/developer1.account /var/lib/snapd/seed/assertions + cp $TESTSLIB/assertions/developer1.account-key /var/lib/snapd/seed/assertions + cp $TESTSLIB/assertions/developer1-pc.model /var/lib/snapd/seed/assertions + cp $TESTSLIB/assertions/testrootorg-store.account-key /var/lib/snapd/seed/assertions + # start fake device svc + systemd_create_and_start_unit fakedevicesvc "$(which fakedevicesvc) localhost:11029" + # kick first boot again + systemctl start snapd.service snapd.socket +restore: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + . $TESTSLIB/systemd.sh + systemctl stop snapd.service snapd.socket + systemd_stop_and_destroy_unit fakedevicesvc + rm -rf /var/lib/snapd/assertions/* + rm -rf /var/lib/snapd/device + rm -rf /var/lib/snapd/state.json + if systemctl status snap-pc-x1.mount ; then + systemctl stop snap-pc-x1.mount + rm -f /etc/systemd/system/snap-pc-x1.mount + rm -f /etc/systemd/system/multi-user.target.wants/snap-pc-x1.mount + rm -f /var/lib/snapd/snaps/pc_x1.snap + systemctl daemon-reload + fi + rm /var/lib/snapd/seed/snaps/pc_x1.snap + cp seed.yaml.bak /var/lib/snapd/seed/seed.yaml + rm /var/lib/snapd/seed/assertions/developer1.account + rm /var/lib/snapd/seed/assertions/developer1.account-key + rm /var/lib/snapd/seed/assertions/developer1-pc.model + rm /var/lib/snapd/seed/assertions/testrootorg-store.account-key + cp model.bak /var/lib/snapd/seed/assertions/model + rm -f *.bak + # kick first boot again + systemctl start snapd.service snapd.socket + # wait for first boot to be done + while ! snap changes | grep -q "Done.*Initialize system state"; do sleep 1; done +execute: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + echo "Wait for first boot to be done" + while ! snap changes | grep -q "Done.*Initialize system state"; do sleep 1; done + echo "We have a model assertion" + snap known model|grep "model: my-model" + + echo "Wait for device initialisation to be done" + while ! snap changes | grep -q "Done.*Initialize device"; do sleep 1; done + + echo "Check we have a serial" + snap known serial|grep "authority-id: developer1" + snap known serial|grep "brand-id: developer1" + snap known serial|grep "model: my-model" + snap known serial|grep "serial: Y1234" + snap known serial|grep 'mac: "00:00:00:00:ff:00"' diff --git a/tests/main/ubuntu-core-custom-device-reg/prepare-device b/tests/main/ubuntu-core-custom-device-reg/prepare-device index fa24c090f8..ac974b4d96 100755 --- a/tests/main/ubuntu-core-custom-device-reg/prepare-device +++ b/tests/main/ubuntu-core-custom-device-reg/prepare-device @@ -1,2 +1,2 @@ #!/bin/sh -snapctl set device-service.url=http://localhost:11029 \ No newline at end of file +snapctl set device-service.url=http://localhost:11029 diff --git a/tests/main/ubuntu-core-custom-device-reg/task.yaml b/tests/main/ubuntu-core-custom-device-reg/task.yaml index 925441d108..ead969a56c 100644 --- a/tests/main/ubuntu-core-custom-device-reg/task.yaml +++ b/tests/main/ubuntu-core-custom-device-reg/task.yaml @@ -40,6 +40,13 @@ restore: | rm -rf /var/lib/snapd/assertions/* rm -rf /var/lib/snapd/device rm -rf /var/lib/snapd/state.json + if systemctl status snap-pc-x1.mount ; then + systemctl stop snap-pc-x1.mount + rm -f /etc/systemd/system/snap-pc-x1.mount + rm -f /etc/systemd/system/multi-user.target.wants/snap-pc-x1.mount + rm -f /var/lib/snapd/snaps/pc_x1.snap + systemctl daemon-reload + fi rm /var/lib/snapd/seed/snaps/pc_x1.snap cp seed.yaml.bak /var/lib/snapd/seed/seed.yaml rm /var/lib/snapd/seed/assertions/developer1.account |
