Blob Blame History Raw
From 71c557b61cc29d0563e6fcf26aefc1365721a676 Mon Sep 17 00:00:00 2001
From: Zygmunt Krynicki <me@zygoon.pl>
Date: Wed, 6 Feb 2019 13:48:01 +0100
Subject: [PATCH] errtracker: neuter error tracker

The error report / tracker allows snapd to report various failures to
the store for central discovery. Unfortunately, the error tracker
depends on bson which is coming from the parts of mongodb package.

With the mongo licensing issue and subsequent removal, to avoid FTBFS
while alternatives are considered, remove the error tracker
implementation to drop the dependency on bson.

Signed-off-by: Zygmunt Krynicki <me@zygoon.pl>
---
 errtracker/errtracker.go      | 484 +-----------------------------
 errtracker/errtracker_test.go | 544 ----------------------------------
 errtracker/export_test.go     | 126 --------
 packaging/fedora/snapd.spec   |   3 -
 4 files changed, 2 insertions(+), 1155 deletions(-)
 delete mode 100644 errtracker/errtracker_test.go
 delete mode 100644 errtracker/export_test.go

diff --git a/errtracker/errtracker.go b/errtracker/errtracker.go
index d7507a9a3..8defc7594 100644
--- a/errtracker/errtracker.go
+++ b/errtracker/errtracker.go
@@ -19,498 +19,18 @@
 
 package errtracker
 
-import (
-	"bytes"
-	"crypto/md5"
-	"crypto/sha512"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"net/http"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"github.com/snapcore/bolt"
-	"gopkg.in/mgo.v2/bson"
-
-	"github.com/snapcore/snapd/arch"
-	"github.com/snapcore/snapd/dirs"
-	"github.com/snapcore/snapd/httputil"
-	"github.com/snapcore/snapd/logger"
-	"github.com/snapcore/snapd/osutil"
-	"github.com/snapcore/snapd/release"
-)
-
 var (
 	CrashDbURLBase string
 	SnapdVersion   string
-
-	// The machine-id file is at different locations depending on how the system
-	// is setup. On Fedora for example /var/lib/dbus/machine-id doesn't exist
-	// but we have /etc/machine-id. See
-	// https://www.freedesktop.org/software/systemd/man/machine-id.html for a
-	// few more details.
-	machineIDs = []string{"/etc/machine-id", "/var/lib/dbus/machine-id"}
-
-	mockedHostSnapd = ""
-	mockedCoreSnapd = ""
-
-	snapConfineProfile = "/etc/apparmor.d/usr.lib.snapd.snap-confine"
-
-	procCpuinfo     = "/proc/cpuinfo"
-	procSelfExe     = "/proc/self/exe"
-	procSelfCwd     = "/proc/self/cwd"
-	procSelfCmdline = "/proc/self/cmdline"
-
-	osGetenv = os.Getenv
-	timeNow  = time.Now
 )
 
-type reportsDB struct {
-	db *bolt.DB
-
-	// map of hash(dupsig) -> time-of-report
-	reportedBucket *bolt.Bucket
-
-	// time until an error report is cleaned from the database,
-	// usually 7 days
-	cleanupTime time.Duration
-}
-
-func hashString(s string) string {
-	h := sha512.New()
-	io.WriteString(h, s)
-	return fmt.Sprintf("%x", h.Sum(nil))
-}
-
-func newReportsDB(fname string) (*reportsDB, error) {
-	if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil {
-		return nil, err
-	}
-	bdb, err := bolt.Open(fname, 0600, &bolt.Options{
-		Timeout: 10 * time.Second,
-	})
-	if err != nil {
-		return nil, err
-	}
-	bdb.Update(func(tx *bolt.Tx) error {
-		_, err := tx.CreateBucketIfNotExists([]byte("reported"))
-		if err != nil {
-			return fmt.Errorf("create bucket: %s", err)
-		}
-		return nil
-	})
-
-	db := &reportsDB{
-		db:          bdb,
-		cleanupTime: time.Duration(7 * 24 * time.Hour),
-	}
-
-	return db, nil
-}
-
-func (db *reportsDB) Close() error {
-	return db.db.Close()
-}
-
-// AlreadyReported returns true if an identical report has been sent recently
-func (db *reportsDB) AlreadyReported(dupSig string) bool {
-	// robustness
-	if db == nil {
-		return false
-	}
-	var reported []byte
-	db.db.View(func(tx *bolt.Tx) error {
-		b := tx.Bucket([]byte("reported"))
-		reported = b.Get([]byte(hashString(dupSig)))
-		return nil
-	})
-	return len(reported) > 0
-}
-
-func (db *reportsDB) cleanupOldRecords() {
-	db.db.Update(func(tx *bolt.Tx) error {
-		now := time.Now()
-
-		b := tx.Bucket([]byte("reported"))
-		b.ForEach(func(dupSigHash, reportTime []byte) error {
-			var t time.Time
-			t.UnmarshalBinary(reportTime)
-
-			if now.After(t.Add(db.cleanupTime)) {
-				if err := b.Delete(dupSigHash); err != nil {
-					return err
-				}
-			}
-			return nil
-		})
-		return nil
-	})
-}
-
-// MarkReported marks an error report as reported to the error tracker
-func (db *reportsDB) MarkReported(dupSig string) error {
-	// robustness
-	if db == nil {
-		return fmt.Errorf("cannot mark error report as reported with an uninitialized reports database")
-	}
-	db.cleanupOldRecords()
-
-	return db.db.Update(func(tx *bolt.Tx) error {
-		b := tx.Bucket([]byte("reported"))
-		tb, err := time.Now().MarshalBinary()
-		if err != nil {
-			return err
-		}
-		return b.Put([]byte(hashString(dupSig)), tb)
-	})
-}
-
-func whoopsieEnabled() bool {
-	cmd := exec.Command("systemctl", "is-enabled", "whoopsie.service")
-	output, _ := cmd.CombinedOutput()
-	switch string(output) {
-	case "enabled\n":
-		return true
-	case "disabled\n":
-		return false
-	default:
-		logger.Debugf("unexpected output when checking for whoopsie.service (not installed?): %s", output)
-		return true
-	}
-}
-
-// distroRelease returns a distro release as it is expected by daisy.ubuntu.com
-func distroRelease() string {
-	ID := release.ReleaseInfo.ID
-	if ID == "ubuntu" {
-		ID = "Ubuntu"
-	}
-
-	return fmt.Sprintf("%s %s", ID, release.ReleaseInfo.VersionID)
-}
-
-func readMachineID() ([]byte, error) {
-	for _, id := range machineIDs {
-		machineID, err := ioutil.ReadFile(id)
-		if err == nil {
-			return bytes.TrimSpace(machineID), nil
-		} else if !os.IsNotExist(err) {
-			logger.Noticef("cannot read %s: %s", id, err)
-		}
-	}
-
-	return nil, fmt.Errorf("cannot report: no suitable machine id file found")
-}
-
-func snapConfineProfileDigest(suffix string) string {
-	profileText, err := ioutil.ReadFile(filepath.Join(dirs.GlobalRootDir, snapConfineProfile+suffix))
-	if err != nil {
-		return ""
-	}
-	// NOTE: uses md5sum for easier comparison against dpkg meta-data
-	return fmt.Sprintf("%x", md5.Sum(profileText))
-}
-
-var didSnapdReExec = func() string {
-	// TODO: move this into osutil.Reexeced() ?
-	exe, err := os.Readlink(procSelfExe)
-	if err != nil {
-		return "unknown"
-	}
-	if strings.HasPrefix(exe, dirs.SnapMountDir) {
-		return "yes"
-	}
-	return "no"
-}
-
 // Report reports an error with the given snap to the error tracker
 func Report(snap, errMsg, dupSig string, extra map[string]string) (string, error) {
-	if extra == nil {
-		extra = make(map[string]string)
-	}
-	extra["ProblemType"] = "Snap"
-	extra["Snap"] = snap
-
-	// check if we haven't already reported this error
-	db, err := newReportsDB(dirs.ErrtrackerDbDir)
-	if err != nil {
-		logger.Noticef("cannot open error reports database: %v", err)
-	}
-	defer db.Close()
-
-	if db.AlreadyReported(dupSig) {
-		return "already-reported", nil
-	}
-
-	// do the actual report
-	oopsID, err := report(errMsg, dupSig, extra)
-	if err != nil {
-		return "", err
-	}
-	if err := db.MarkReported(dupSig); err != nil {
-		logger.Noticef("cannot mark %s as reported: %s", oopsID, err)
-	}
-
-	return oopsID, nil
+	return "", nil
 }
 
 // ReportRepair reports an error with the given repair assertion script
 // to the error tracker
 func ReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) {
-	if extra == nil {
-		extra = make(map[string]string)
-	}
-	extra["ProblemType"] = "Repair"
-	extra["Repair"] = repair
-
-	return report(errMsg, dupSig, extra)
-}
-
-func detectVirt() string {
-	cmd := exec.Command("systemd-detect-virt")
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		return ""
-	}
-	return strings.TrimSpace(string(output))
-}
-
-func journalError() string {
-	// TODO: look into using systemd package (needs refactor)
-
-	// Before changing this line to be more consistent or nicer or anything
-	// else, remember it needs to run a lot of different systemd's: today,
-	// anything from 238 (on arch) to 204 (on ubuntu 14.04); this is why
-	// doing the refactor to the systemd package to only worry about this in
-	// there might be worth it.
-	output, err := exec.Command("journalctl", "-b", "--priority=warning..err", "--lines=1000").CombinedOutput()
-	if err != nil {
-		if len(output) == 0 {
-			return fmt.Sprintf("error: %v", err)
-		}
-		output = append(output, fmt.Sprintf("\nerror: %v", err)...)
-	}
-	return string(output)
-}
-
-func procCpuinfoMinimal() string {
-	buf, err := ioutil.ReadFile(procCpuinfo)
-	if err != nil {
-		// if we can't read cpuinfo, we want to know _why_
-		return fmt.Sprintf("error: %v", err)
-	}
-	idx := bytes.LastIndex(buf, []byte("\nprocessor\t:"))
-
-	// if not found (which will happen on non-x86 architectures, which is ok
-	// because they'd typically not have the same info over and over again),
-	// return whole buffer; otherwise, return from just after the \n
-	return string(buf[idx+1:])
-}
-
-func procExe() string {
-	out, err := os.Readlink(procSelfExe)
-	if err != nil {
-		return fmt.Sprintf("error: %v", err)
-	}
-	return out
-}
-
-func procCwd() string {
-	out, err := os.Readlink(procSelfCwd)
-	if err != nil {
-		return fmt.Sprintf("error: %v", err)
-	}
-	return out
-}
-
-func procCmdline() string {
-	out, err := ioutil.ReadFile(procSelfCmdline)
-	if err != nil {
-		return fmt.Sprintf("error: %v", err)
-	}
-	return string(out)
-}
-
-func environ() string {
-	safeVars := []string{
-		"SHELL", "TERM", "LANGUAGE", "LANG", "LC_CTYPE",
-		"LC_COLLATE", "LC_TIME", "LC_NUMERIC",
-		"LC_MONETARY", "LC_MESSAGES", "LC_PAPER",
-		"LC_NAME", "LC_ADDRESS", "LC_TELEPHONE",
-		"LC_MEASUREMENT", "LC_IDENTIFICATION", "LOCPATH",
-	}
-	unsafeVars := []string{"XDG_RUNTIME_DIR", "LD_PRELOAD", "LD_LIBRARY_PATH"}
-	knownPaths := map[string]bool{
-		"/snap/bin":               true,
-		"/var/lib/snapd/snap/bin": true,
-		"/sbin":                   true,
-		"/bin":                    true,
-		"/usr/sbin":               true,
-		"/usr/bin":                true,
-		"/usr/local/sbin":         true,
-		"/usr/local/bin":          true,
-		"/usr/local/games":        true,
-		"/usr/games":              true,
-	}
-
-	// + 1 for PATH
-	out := make([]string, 0, len(safeVars)+len(unsafeVars)+1)
-
-	for _, k := range safeVars {
-		if v := osGetenv(k); v != "" {
-			out = append(out, fmt.Sprintf("%s=%s", k, v))
-		}
-	}
-
-	for _, k := range unsafeVars {
-		if v := osGetenv(k); v != "" {
-			out = append(out, k+"=<set>")
-		}
-	}
-
-	if paths := filepath.SplitList(osGetenv("PATH")); len(paths) > 0 {
-		for i, p := range paths {
-			p = filepath.Clean(p)
-			if !knownPaths[p] {
-				if strings.Contains(p, "/home") || strings.Contains(p, "/tmp") {
-					p = "(user)"
-				} else {
-					p = "(custom)"
-				}
-			}
-			paths[i] = p
-		}
-		out = append(out, fmt.Sprintf("PATH=%s", strings.Join(paths, string(filepath.ListSeparator))))
-	}
-
-	return strings.Join(out, "\n")
-}
-
-func report(errMsg, dupSig string, extra map[string]string) (string, error) {
-	if CrashDbURLBase == "" {
-		return "", nil
-	}
-	if extra == nil || extra["ProblemType"] == "" {
-		return "", fmt.Errorf(`key "ProblemType" not set in %v`, extra)
-	}
-
-	if !whoopsieEnabled() {
-		return "", nil
-	}
-
-	machineID, err := readMachineID()
-	if err != nil {
-		return "", err
-	}
-
-	identifier := fmt.Sprintf("%x", sha512.Sum512(machineID))
-
-	crashDbUrl := fmt.Sprintf("%s/%s", CrashDbURLBase, identifier)
-
-	hostSnapdPath := filepath.Join(dirs.DistroLibExecDir, "snapd")
-	coreSnapdPath := filepath.Join(dirs.SnapMountDir, "core/current/usr/lib/snapd/snapd")
-	if mockedHostSnapd != "" {
-		hostSnapdPath = mockedHostSnapd
-	}
-	if mockedCoreSnapd != "" {
-		coreSnapdPath = mockedCoreSnapd
-	}
-	hostBuildID, _ := osutil.ReadBuildID(hostSnapdPath)
-	coreBuildID, _ := osutil.ReadBuildID(coreSnapdPath)
-	if hostBuildID == "" {
-		hostBuildID = "unknown"
-	}
-	if coreBuildID == "" {
-		coreBuildID = "unknown"
-	}
-
-	report := map[string]string{
-		"Architecture":       arch.UbuntuArchitecture(),
-		"SnapdVersion":       SnapdVersion,
-		"DistroRelease":      distroRelease(),
-		"HostSnapdBuildID":   hostBuildID,
-		"CoreSnapdBuildID":   coreBuildID,
-		"Date":               timeNow().Format(time.ANSIC),
-		"KernelVersion":      osutil.KernelVersion(),
-		"ErrorMessage":       errMsg,
-		"DuplicateSignature": dupSig,
-
-		"JournalError":       journalError(),
-		"ExecutablePath":     procExe(),
-		"ProcCmdline":        procCmdline(),
-		"ProcCpuinfoMinimal": procCpuinfoMinimal(),
-		"ProcCwd":            procCwd(),
-		"ProcEnviron":        environ(),
-		"DetectedVirt":       detectVirt(),
-		"SourcePackage":      "snapd",
-
-		"DidSnapdReExec": didSnapdReExec(),
-	}
-
-	if desktop := osGetenv("XDG_CURRENT_DESKTOP"); desktop != "" {
-		report["CurrentDesktop"] = desktop
-	}
-
-	for k, v := range extra {
-		// only set if empty
-		if _, ok := report[k]; !ok {
-			report[k] = v
-		}
-	}
-
-	// include md5 hashes of the apparmor conffile for easier debbuging
-	// of not-updated snap-confine apparmor profiles
-	for _, sp := range []struct {
-		suffix string
-		key    string
-	}{
-		{"", "MD5SumSnapConfineAppArmorProfile"},
-		{".dpkg-new", "MD5SumSnapConfineAppArmorProfileDpkgNew"},
-		{".real", "MD5SumSnapConfineAppArmorProfileReal"},
-		{".real.dpkg-new", "MD5SumSnapConfineAppArmorProfileRealDpkgNew"},
-	} {
-		digest := snapConfineProfileDigest(sp.suffix)
-		if digest != "" {
-			report[sp.key] = digest
-		}
-
-	}
-
-	// see if we run in testing mode
-	if osutil.GetenvBool("SNAPPY_TESTING") {
-		logger.Noticef("errtracker.Report is *not* sent because SNAPPY_TESTING is set")
-		logger.Noticef("report: %v", report)
-		return "oops-not-sent", nil
-	}
-
-	// send it for real
-	reportBson, err := bson.Marshal(report)
-	if err != nil {
-		return "", err
-	}
-	client := &http.Client{}
-	req, err := http.NewRequest("POST", crashDbUrl, bytes.NewBuffer(reportBson))
-	if err != nil {
-		return "", err
-	}
-	req.Header.Add("Content-Type", "application/octet-stream")
-	req.Header.Add("X-Whoopsie-Version", httputil.UserAgent())
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != 200 {
-		return "", fmt.Errorf("cannot upload error report, return code: %d", resp.StatusCode)
-	}
-	oopsID, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-
-	return string(oopsID), nil
+	return "", nil
 }
diff --git a/errtracker/errtracker_test.go b/errtracker/errtracker_test.go
deleted file mode 100644
index a79535618..000000000
--- a/errtracker/errtracker_test.go
+++ /dev/null
@@ -1,544 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 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 errtracker_test
-
-import (
-	"crypto/sha512"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/http/httptest"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"testing"
-	"time"
-
-	"gopkg.in/mgo.v2/bson"
-
-	. "gopkg.in/check.v1"
-
-	"github.com/snapcore/snapd/arch"
-	"github.com/snapcore/snapd/dirs"
-	"github.com/snapcore/snapd/errtracker"
-	"github.com/snapcore/snapd/osutil"
-	"github.com/snapcore/snapd/release"
-	"github.com/snapcore/snapd/testutil"
-)
-
-// Hook up check.v1 into the "go test" runner
-func Test(t *testing.T) { TestingT(t) }
-
-type ErrtrackerTestSuite struct {
-	testutil.BaseTest
-
-	tmpdir string
-
-	hostBuildID   string
-	coreBuildID   string
-	distroRelease string
-}
-
-var _ = Suite(&ErrtrackerTestSuite{})
-
-var truePath = osutil.LookPathDefault("true", "/bin/true")
-var falsePath = osutil.LookPathDefault("false", "/bin/false")
-
-const someJournalEntry = "Mar 29 22:08:00 localhost kernel: [81B blob data]"
-
-func (s *ErrtrackerTestSuite) SetUpTest(c *C) {
-	s.BaseTest.SetUpTest(c)
-
-	s.tmpdir = c.MkDir()
-	dirs.SetRootDir(s.tmpdir)
-
-	p := filepath.Join(s.tmpdir, "machine-id")
-	err := ioutil.WriteFile(p, []byte("bbb1a6a5bcdb418380056a2d759c3f7c"), 0644)
-	c.Assert(err, IsNil)
-	s.AddCleanup(errtracker.MockMachineIDPaths([]string{p}))
-	s.AddCleanup(errtracker.MockHostSnapd(truePath))
-	s.AddCleanup(errtracker.MockCoreSnapd(falsePath))
-	s.AddCleanup(errtracker.MockReExec(func() string {
-		return "yes"
-	}))
-	mockDetectVirt := testutil.MockCommand(c, "systemd-detect-virt", "echo none")
-	s.AddCleanup(mockDetectVirt.Restore)
-
-	s.hostBuildID, err = osutil.ReadBuildID(truePath)
-	c.Assert(err, IsNil)
-	s.coreBuildID, err = osutil.ReadBuildID(falsePath)
-	c.Assert(err, IsNil)
-	if release.ReleaseInfo.ID == "ubuntu" {
-		s.distroRelease = fmt.Sprintf("%s %s", strings.Title(release.ReleaseInfo.ID), release.ReleaseInfo.VersionID)
-	} else {
-		s.distroRelease = fmt.Sprintf("%s %s", release.ReleaseInfo.ID, release.ReleaseInfo.VersionID)
-	}
-
-	mockCpuinfo := filepath.Join(s.tmpdir, "cpuinfo")
-	mockSelfCmdline := filepath.Join(s.tmpdir, "self.cmdline")
-	mockSelfExe := filepath.Join(s.tmpdir, "self.exe")
-	mockSelfCwd := filepath.Join(s.tmpdir, "self.cwd")
-
-	c.Assert(ioutil.WriteFile(mockCpuinfo, []byte(`
-processor	: 0
-bugs		: very yes
-etc		: ...
-
-processor	: 42
-bugs		: very yes
-`[1:]), 0644), IsNil)
-	c.Assert(ioutil.WriteFile(mockSelfCmdline, []byte("foo\x00bar\x00baz"), 0644), IsNil)
-	c.Assert(os.Symlink("target of /proc/self/exe", mockSelfExe), IsNil)
-	c.Assert(os.Symlink("target of /proc/self/cwd", mockSelfCwd), IsNil)
-
-	s.AddCleanup(errtracker.MockOsGetenv(func(s string) string {
-		switch s {
-		case "SHELL":
-			return "/bin/sh"
-		case "XDG_CURRENT_DESKTOP":
-			return "Unity"
-		}
-		return ""
-	}))
-	s.AddCleanup(errtracker.MockProcCpuinfo(mockCpuinfo))
-	s.AddCleanup(errtracker.MockProcSelfCmdline(mockSelfCmdline))
-	s.AddCleanup(errtracker.MockProcSelfExe(mockSelfExe))
-	s.AddCleanup(errtracker.MockProcSelfCwd(mockSelfCwd))
-	s.AddCleanup(testutil.MockCommand(c, "journalctl", "echo "+someJournalEntry).Restore)
-}
-
-func (s *ErrtrackerTestSuite) TestReport(c *C) {
-	n := 0
-	identifier := ""
-
-	snapConfineProfile := filepath.Join(s.tmpdir, "/etc/apparmor.d/usr.lib.snapd.snap-confine")
-	err := os.MkdirAll(filepath.Dir(snapConfineProfile), 0755)
-	c.Assert(err, IsNil)
-	err = ioutil.WriteFile(snapConfineProfile, []byte("# fake profile of snap-confine"), 0644)
-	c.Assert(err, IsNil)
-
-	err = ioutil.WriteFile(snapConfineProfile+".dpkg-new", []byte{0}, 0644)
-	c.Assert(err, IsNil)
-	err = ioutil.WriteFile(snapConfineProfile+".real", []byte{0}, 0644)
-	c.Assert(err, IsNil)
-	err = ioutil.WriteFile(snapConfineProfile+".real.dpkg-new", []byte{0}, 0644)
-	c.Assert(err, IsNil)
-
-	prev := errtracker.SnapdVersion
-	defer func() { errtracker.SnapdVersion = prev }()
-	errtracker.SnapdVersion = "some-snapd-version"
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		switch n {
-		case 0:
-			c.Check(r.Method, Equals, "POST")
-			c.Check(r.URL.Path, Matches, "/[a-z0-9]+")
-			identifier = r.URL.Path
-			b, err := ioutil.ReadAll(r.Body)
-			c.Assert(err, IsNil)
-
-			var data map[string]string
-			err = bson.Unmarshal(b, &data)
-			c.Assert(err, IsNil)
-			c.Check(data, DeepEquals, map[string]string{
-				"DistroRelease":      s.distroRelease,
-				"HostSnapdBuildID":   s.hostBuildID,
-				"CoreSnapdBuildID":   s.coreBuildID,
-				"SnapdVersion":       "some-snapd-version",
-				"Date":               "Fri Feb 17 09:51:00 2017",
-				"KernelVersion":      osutil.KernelVersion(),
-				"ErrorMessage":       "failed to do stuff",
-				"DuplicateSignature": "[failed to do stuff]",
-				"Architecture":       arch.UbuntuArchitecture(),
-				"DidSnapdReExec":     "yes",
-
-				"ProblemType": "Snap",
-				"Snap":        "some-snap",
-				"Channel":     "beta",
-
-				"ProcCpuinfoMinimal": "processor\t: 42\nbugs\t\t: very yes\n",
-				"ExecutablePath":     "target of /proc/self/exe",
-				"ProcCwd":            "target of /proc/self/cwd",
-				"ProcCmdline":        "foo\x00bar\x00baz",
-				"ProcEnviron":        "SHELL=/bin/sh",
-				"JournalError":       someJournalEntry + "\n",
-				"SourcePackage":      "snapd",
-				"CurrentDesktop":     "Unity",
-				"DetectedVirt":       "none",
-
-				"MD5SumSnapConfineAppArmorProfile":            "7a7aa5f21063170c1991b84eb8d86de1",
-				"MD5SumSnapConfineAppArmorProfileDpkgNew":     "93b885adfe0da089cdf634904fd59f71",
-				"MD5SumSnapConfineAppArmorProfileReal":        "93b885adfe0da089cdf634904fd59f71",
-				"MD5SumSnapConfineAppArmorProfileRealDpkgNew": "93b885adfe0da089cdf634904fd59f71",
-			})
-			fmt.Fprintf(w, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-		case 1:
-			c.Check(r.Method, Equals, "POST")
-			c.Check(r.URL.Path, Matches, identifier)
-			fmt.Fprintf(w, "xxxxx-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-		default:
-			c.Fatalf("expected one request, got %d", n+1)
-		}
-
-		n++
-	}
-
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-	restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
-	defer restorer()
-
-	id, err := errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", map[string]string{
-		"Channel": "beta",
-	})
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-	c.Check(n, Equals, 1)
-
-	// run again with the *same* dupSig and verify that it won't send
-	// that again
-	id, err = errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", map[string]string{
-		"Channel": "beta",
-	})
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "already-reported")
-	c.Check(n, Equals, 1)
-
-	// run again with different data, verify identifier is unchanged
-	id, err = errtracker.Report("some-other-snap", "failed to do more stuff", "[failed to do more stuff]", nil)
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "xxxxx-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-	c.Check(n, Equals, 2)
-}
-
-func (s *ErrtrackerTestSuite) TestReportUnderTesting(c *C) {
-	os.Setenv("SNAPPY_TESTING", "1")
-	defer os.Unsetenv("SNAPPY_TESTING")
-
-	n := 0
-	prev := errtracker.SnapdVersion
-	defer func() { errtracker.SnapdVersion = prev }()
-	errtracker.SnapdVersion = "some-snapd-version"
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		n++
-	}
-
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-	restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
-	defer restorer()
-
-	id, err := errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", map[string]string{
-		"Channel": "beta",
-	})
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "oops-not-sent")
-	c.Check(n, Equals, 0)
-}
-
-func (s *ErrtrackerTestSuite) TestTriesAllKnownMachineIDs(c *C) {
-	p := filepath.Join(c.MkDir(), "machine-id")
-	machineID := []byte("bbb1a6a5bcdb418380056a2d759c3f7c")
-	err := ioutil.WriteFile(p, machineID, 0644)
-	c.Assert(err, IsNil)
-	s.AddCleanup(errtracker.MockMachineIDPaths([]string{"/does/not/exist", p}))
-
-	n := 0
-	var identifiers []string
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		identifiers = append(identifiers, r.URL.Path)
-		n++
-	}
-
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-	restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
-	defer restorer()
-
-	_, err = errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", map[string]string{
-		"Channel": "beta",
-	})
-	c.Check(err, IsNil)
-	c.Check(n, Equals, 1)
-	c.Check(identifiers, DeepEquals, []string{fmt.Sprintf("/%x", sha512.Sum512(machineID))})
-}
-
-func (s *ErrtrackerTestSuite) TestReportRepair(c *C) {
-	n := 0
-	prev := errtracker.SnapdVersion
-	defer func() { errtracker.SnapdVersion = prev }()
-	errtracker.SnapdVersion = "some-snapd-version"
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		switch n {
-		case 0:
-			c.Check(r.Method, Equals, "POST")
-			c.Check(r.URL.Path, Matches, "/[a-z0-9]+")
-			b, err := ioutil.ReadAll(r.Body)
-			c.Assert(err, IsNil)
-
-			var data map[string]string
-			err = bson.Unmarshal(b, &data)
-			c.Assert(err, IsNil)
-			c.Check(data, DeepEquals, map[string]string{
-				"DistroRelease":    s.distroRelease,
-				"HostSnapdBuildID": s.hostBuildID,
-				"CoreSnapdBuildID": s.coreBuildID,
-				"SnapdVersion":     "some-snapd-version",
-				"Date":             "Fri Feb 17 09:51:00 2017",
-				"KernelVersion":    osutil.KernelVersion(),
-				"Architecture":     arch.UbuntuArchitecture(),
-				"DidSnapdReExec":   "yes",
-
-				"ProblemType":        "Repair",
-				"Repair":             `"repair (1; brand-id:canonical)"`,
-				"ErrorMessage":       "failure in script",
-				"DuplicateSignature": "[dupSig]",
-				"BrandID":            "canonical",
-
-				"ProcCpuinfoMinimal": "processor\t: 42\nbugs\t\t: very yes\n",
-				"ExecutablePath":     "target of /proc/self/exe",
-				"ProcCwd":            "target of /proc/self/cwd",
-				"ProcCmdline":        "foo\x00bar\x00baz",
-				"ProcEnviron":        "SHELL=/bin/sh",
-				"JournalError":       someJournalEntry + "\n",
-				"SourcePackage":      "snapd",
-				"CurrentDesktop":     "Unity",
-				"DetectedVirt":       "none",
-			})
-			fmt.Fprintf(w, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-		default:
-			c.Fatalf("expected one request, got %d", n+1)
-		}
-
-		n++
-	}
-
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-	restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
-	defer restorer()
-
-	id, err := errtracker.ReportRepair(`"repair (1; brand-id:canonical)"`, "failure in script", "[dupSig]", map[string]string{
-		"BrandID": "canonical",
-	})
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
-	c.Check(n, Equals, 1)
-}
-
-func (s *ErrtrackerTestSuite) TestReportWithWhoopsieDisabled(c *C) {
-	mockCmd := testutil.MockCommand(c, "systemctl", "echo disabled; exit 1")
-	defer mockCmd.Restore()
-
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		c.Fatalf("The server should not be hit from here")
-	}
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-
-	id, err := errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", nil)
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "")
-}
-
-func (s *ErrtrackerTestSuite) TestReportWithNoWhoopsieInstalled(c *C) {
-	mockCmd := testutil.MockCommand(c, "systemctl", "echo Failed to get unit file state for whoopsie.service; exit 1")
-	defer mockCmd.Restore()
-
-	n := 0
-	handler := func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprintf(w, "1234-oopsid")
-		n++
-	}
-	server := httptest.NewServer(http.HandlerFunc(handler))
-	defer server.Close()
-	restorer := errtracker.MockCrashDbURL(server.URL)
-	defer restorer()
-
-	id, err := errtracker.Report("some-snap", "failed to do stuff", "[failed to do stuff]", nil)
-	c.Check(err, IsNil)
-	c.Check(id, Equals, "1234-oopsid")
-	c.Check(n, Equals, 1)
-}
-
-func (s *ErrtrackerTestSuite) TestProcCpuinfo(c *C) {
-	fn := filepath.Join(s.tmpdir, "cpuinfo")
-	// sanity check
-	buf, err := ioutil.ReadFile(fn)
-	c.Assert(err, IsNil)
-	c.Check(string(buf), Equals, `
-processor	: 0
-bugs		: very yes
-etc		: ...
-
-processor	: 42
-bugs		: very yes
-`[1:])
-
-	// just the last processor entry
-	c.Check(errtracker.ProcCpuinfoMinimal(), Equals, `
-processor	: 42
-bugs		: very yes
-`[1:])
-
-	// if no processor line, just return the whole thing
-	c.Assert(ioutil.WriteFile(fn, []byte("yadda yadda\n"), 0644), IsNil)
-	c.Check(errtracker.ProcCpuinfoMinimal(), Equals, "yadda yadda\n")
-
-	c.Assert(os.Remove(fn), IsNil)
-	c.Check(errtracker.ProcCpuinfoMinimal(), Matches, "error: .* no such file or directory")
-}
-
-func (s *ErrtrackerTestSuite) TestProcExe(c *C) {
-	c.Check(errtracker.ProcExe(), Equals, "target of /proc/self/exe")
-	c.Assert(os.Remove(filepath.Join(s.tmpdir, "self.exe")), IsNil)
-	c.Check(errtracker.ProcExe(), Matches, "error: .* no such file or directory")
-}
-
-func (s *ErrtrackerTestSuite) TestProcCwd(c *C) {
-	c.Check(errtracker.ProcCwd(), Equals, "target of /proc/self/cwd")
-	c.Assert(os.Remove(filepath.Join(s.tmpdir, "self.cwd")), IsNil)
-	c.Check(errtracker.ProcCwd(), Matches, "error: .* no such file or directory")
-}
-
-func (s *ErrtrackerTestSuite) TestProcCmdline(c *C) {
-	c.Check(errtracker.ProcCmdline(), Equals, "foo\x00bar\x00baz")
-	c.Assert(os.Remove(filepath.Join(s.tmpdir, "self.cmdline")), IsNil)
-	c.Check(errtracker.ProcCmdline(), Matches, "error: .* no such file or directory")
-}
-
-func (s *ErrtrackerTestSuite) TestJournalError(c *C) {
-	jctl := testutil.MockCommand(c, "journalctl", "echo "+someJournalEntry)
-	defer jctl.Restore()
-	c.Check(errtracker.JournalError(), Equals, someJournalEntry+"\n")
-	c.Check(jctl.Calls(), DeepEquals, [][]string{
-		{"journalctl", "-b", "--priority=warning..err", "--lines=1000"},
-	})
-}
-
-func (s *ErrtrackerTestSuite) TestJournalErrorSilentError(c *C) {
-	jctl := testutil.MockCommand(c, "journalctl", "kill $$")
-	defer jctl.Restore()
-	c.Check(errtracker.JournalError(), Matches, "error: signal: [Tt]erminated")
-	c.Check(jctl.Calls(), DeepEquals, [][]string{
-		{"journalctl", "-b", "--priority=warning..err", "--lines=1000"},
-	})
-}
-
-func (s *ErrtrackerTestSuite) TestJournalErrorError(c *C) {
-	jctl := testutil.MockCommand(c, "journalctl", "echo OOPS; exit 1")
-	defer jctl.Restore()
-	c.Check(errtracker.JournalError(), Equals, "OOPS\n\nerror: exit status 1")
-	c.Check(jctl.Calls(), DeepEquals, [][]string{
-		{"journalctl", "-b", "--priority=warning..err", "--lines=1000"},
-	})
-}
-
-func (s *ErrtrackerTestSuite) TestEnviron(c *C) {
-	defer errtracker.MockOsGetenv(func(s string) string {
-		switch s {
-		case "SHELL":
-			// marked as safe
-			return "/bin/sh"
-		case "GPG_AGENT_INFO":
-			// not marked as safe
-			return ".gpg-agent:0:1"
-		case "TERM":
-			// not really set
-			return ""
-		case "PATH":
-			// special handling from here down
-			return "/something/random:/sbin/:/home/ubuntu/bin:/bin:/snap/bin"
-		case "XDG_RUNTIME_DIR":
-			return "/some/thing"
-		case "LD_PRELOAD":
-			return "foo"
-		case "LD_LIBRARY_PATH":
-			return "bar"
-		}
-		return ""
-	})()
-
-	env := strings.Split(errtracker.Environ(), "\n")
-	sort.Strings(env)
-
-	c.Check(env, DeepEquals, []string{
-		"LD_LIBRARY_PATH=<set>",
-		"LD_PRELOAD=<set>",
-		// note also /sbin/ -> /sbin
-		"PATH=(custom):/sbin:(user):/bin:/snap/bin",
-		"SHELL=/bin/sh",
-		"XDG_RUNTIME_DIR=<set>",
-	})
-}
-
-func (s *ErrtrackerTestSuite) TestReportsDB(c *C) {
-	db, err := errtracker.NewReportsDB(filepath.Join(s.tmpdir, "foo.db"))
-	c.Assert(err, IsNil)
-
-	c.Check(db.AlreadyReported("some-dup-sig"), Equals, false)
-
-	err = db.MarkReported("some-dup-sig")
-	c.Check(err, IsNil)
-
-	c.Check(db.AlreadyReported("some-dup-sig"), Equals, true)
-	c.Check(db.AlreadyReported("other-dup-sig"), Equals, false)
-}
-
-func (s *ErrtrackerTestSuite) TestReportsDBCleanup(c *C) {
-	db, err := errtracker.NewReportsDB(filepath.Join(s.tmpdir, "foo.db"))
-	c.Assert(err, IsNil)
-
-	errtracker.SetReportDBCleanupTime(db, 1*time.Millisecond)
-
-	err = db.MarkReported("some-dup-sig")
-	c.Check(err, IsNil)
-
-	time.Sleep(10 * time.Millisecond)
-	err = db.MarkReported("other-dup-sig")
-	c.Check(err, IsNil)
-
-	// this one got cleaned out
-	c.Check(db.AlreadyReported("some-dup-sig"), Equals, false)
-	// this one is still fresh
-	c.Check(db.AlreadyReported("other-dup-sig"), Equals, true)
-}
-
-func (s *ErrtrackerTestSuite) TestReportsDBnilDoesNotCrash(c *C) {
-	db, err := errtracker.NewReportsDB("/proc/1/environ")
-	c.Assert(err, NotNil)
-	c.Check(db, IsNil)
-
-	c.Check(db.AlreadyReported("dupSig"), Equals, false)
-	c.Check(db.MarkReported("dupSig"), ErrorMatches, "cannot mark error report as reported with an uninitialized reports database")
-}
diff --git a/errtracker/export_test.go b/errtracker/export_test.go
deleted file mode 100644
index ff3ac0b60..000000000
--- a/errtracker/export_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 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 errtracker
-
-import (
-	"time"
-)
-
-func MockCrashDbURL(url string) (restorer func()) {
-	old := CrashDbURLBase
-	CrashDbURLBase = url
-	return func() {
-		CrashDbURLBase = old
-	}
-}
-
-func MockMachineIDPaths(paths []string) (restorer func()) {
-	old := machineIDs
-	machineIDs = paths
-	return func() {
-		machineIDs = old
-	}
-}
-
-func MockHostSnapd(path string) (restorer func()) {
-	old := mockedHostSnapd
-	mockedHostSnapd = path
-	return func() {
-		mockedHostSnapd = old
-	}
-}
-
-func MockCoreSnapd(path string) (restorer func()) {
-	old := mockedCoreSnapd
-	mockedCoreSnapd = path
-	return func() {
-		mockedCoreSnapd = old
-	}
-}
-
-func MockTimeNow(f func() time.Time) (restorer func()) {
-	old := timeNow
-	timeNow = f
-	return func() {
-		timeNow = old
-	}
-}
-
-func MockReExec(f func() string) (restorer func()) {
-	oldDidSnapdReExec := didSnapdReExec
-	didSnapdReExec = f
-	return func() {
-		didSnapdReExec = oldDidSnapdReExec
-	}
-}
-
-func MockOsGetenv(f func(string) string) (restorer func()) {
-	old := osGetenv
-	osGetenv = f
-	return func() {
-		osGetenv = old
-	}
-}
-
-func MockProcCpuinfo(filename string) (restorer func()) {
-	old := procCpuinfo
-	procCpuinfo = filename
-	return func() {
-		procCpuinfo = old
-	}
-}
-
-func MockProcSelfExe(filename string) (restorer func()) {
-	old := procSelfExe
-	procSelfExe = filename
-	return func() {
-		procSelfExe = old
-	}
-}
-
-func MockProcSelfCwd(filename string) (restorer func()) {
-	old := procSelfCwd
-	procSelfCwd = filename
-	return func() {
-		procSelfCwd = old
-	}
-}
-
-func MockProcSelfCmdline(filename string) (restorer func()) {
-	old := procSelfCmdline
-	procSelfCmdline = filename
-	return func() {
-		procSelfCmdline = old
-	}
-}
-
-var (
-	ProcExe            = procExe
-	ProcCwd            = procCwd
-	ProcCmdline        = procCmdline
-	JournalError       = journalError
-	ProcCpuinfoMinimal = procCpuinfoMinimal
-	Environ            = environ
-	NewReportsDB       = newReportsDB
-)
-
-func SetReportDBCleanupTime(db *reportsDB, d time.Duration) {
-	db.cleanupTime = d
-}
diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec
index 390f7966f..fc9515001 100644
--- a/packaging/fedora/snapd.spec
+++ b/packaging/fedora/snapd.spec
@@ -168,7 +168,6 @@ BuildRequires: golang(golang.org/x/net/context)
 BuildRequires: golang(golang.org/x/net/context/ctxhttp)
 BuildRequires: golang(gopkg.in/check.v1)
 BuildRequires: golang(gopkg.in/macaroon.v1)
-BuildRequires: golang(gopkg.in/mgo.v2/bson)
 BuildRequires: golang(gopkg.in/retry.v1)
 BuildRequires: golang(gopkg.in/tomb.v2)
 BuildRequires: golang(gopkg.in/yaml.v2)
@@ -266,7 +265,6 @@ Requires:      golang(golang.org/x/net/context)
 Requires:      golang(golang.org/x/net/context/ctxhttp)
 Requires:      golang(gopkg.in/check.v1)
 Requires:      golang(gopkg.in/macaroon.v1)
-Requires:      golang(gopkg.in/mgo.v2/bson)
 Requires:      golang(gopkg.in/retry.v1)
 Requires:      golang(gopkg.in/tomb.v2)
 Requires:      golang(gopkg.in/yaml.v2)
@@ -295,7 +293,6 @@ Provides:      bundled(golang(golang.org/x/net/context))
 Provides:      bundled(golang(golang.org/x/net/context/ctxhttp))
 Provides:      bundled(golang(gopkg.in/check.v1))
 Provides:      bundled(golang(gopkg.in/macaroon.v1))
-Provides:      bundled(golang(gopkg.in/mgo.v2/bson))
 Provides:      bundled(golang(gopkg.in/retry.v1))
 Provides:      bundled(golang(gopkg.in/tomb.v2))
 Provides:      bundled(golang(gopkg.in/yaml.v2))
-- 
2.20.1