From 14267b88fd011454e4b2ce42a005143f77309942 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 3 Sep 2020 16:02:35 -0700 Subject: [PATCH 1/7] mountinfo.Mounted: return true for / Let's assume that the root directory is always mounted (I can't imagine a case when it's not). This should speed up Mounted("/") case. Signed-off-by: Kir Kolyshkin --- mountinfo/mountinfo.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mountinfo/mountinfo.go b/mountinfo/mountinfo.go index d8359e06..1987fcbb 100644 --- a/mountinfo/mountinfo.go +++ b/mountinfo/mountinfo.go @@ -1,6 +1,9 @@ package mountinfo -import "io" +import ( + "io" + "os" +) // GetMounts retrieves a list of mounts for the current running process, // with an optional filter applied (use nil for no filter). @@ -22,6 +25,10 @@ func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) { // One way to ensure it is to process the path using filepath.Abs followed by // filepath.EvalSymlinks before calling this function. func Mounted(path string) (bool, error) { + // root is always mounted + if path == string(os.PathSeparator) { + return true, nil + } return mounted(path) } From abf73b4a4359f6f0e613bbae2f45b78a62be8040 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Wed, 9 Sep 2020 13:17:25 -0700 Subject: [PATCH 2/7] mountinfo: bump golang.org/x/sys ... to a version that includes openat2 implementation (see https://golang.org/cl/253057). Signed-off-by: Kir Kolyshkin --- mountinfo/go.mod | 2 +- mountinfo/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mountinfo/go.mod b/mountinfo/go.mod index f72b80b4..9749ea96 100644 --- a/mountinfo/go.mod +++ b/mountinfo/go.mod @@ -2,4 +2,4 @@ module github.com/moby/sys/mountinfo go 1.14 -require golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae +require golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 diff --git a/mountinfo/go.sum b/mountinfo/go.sum index df97583d..2a5be7ea 100644 --- a/mountinfo/go.sum +++ b/mountinfo/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From ecf1ea870a1d655af86522ed2a3ed6ea5f52bd8f Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 8 Sep 2020 13:56:27 -0700 Subject: [PATCH 3/7] ci: add golang 1.15.x ... and drop 1.13.x Signed-off-by: Kir Kolyshkin --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d73936c..3c8196f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.13.x, 1.14.x] + go-version: [1.14.x, 1.15.x] platform: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: From 78374f66ebcdf86701f7c07f7a56b5af88a6c3b4 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 3 Sep 2020 16:04:19 -0700 Subject: [PATCH 4/7] mountinfo.Mounted: optimize on Linux with openat2 A new (as of Linux 5.6) openat2(2) system call can be used to check whether a path is a mount point or not, and it even works for bind mounts! This relies on the fact that openat2() with RESOLVE_NO_XDEV flag won't cross the filesystem boundary (that includes bind mounts). If EXDEV error is received, it means the argument is definitely a mount. If no error is received, it means the argument is definitely not a mount. For all the other errors (most probably ENOSYS or EPERM), fall back to existing methods (mountedByStat, mountedByMountinfo). Add a couple of tests: * TestMountedBy prepares a few mounts, and checks that various mountedBy* functions work as expected. Requires root. * TestMountedByOpenat2VsMountinfo reads the mountinfo and then tries to use mountedByOpenat2() for each mount, ignoring EPERM as the test might not be run as root. AFAIK this is the only way to test the new functionality if one is not root. v2 changes: - drop our own openat2 wrapper; - add O_PATH to openat2 flags; - add O_CLOEXEC to all open* calls; - rm socket and symlink special cases. v3: - drop the stat parameter, remove the dir/file distinction; - use openat2 for dirfd as well, so we can bail out earlier. Signed-off-by: Kir Kolyshkin --- mountinfo/mounted_linux.go | 53 +++++++ mountinfo/mounted_linux_test.go | 254 ++++++++++++++++++++++++++++++++ mountinfo/mounted_unix.go | 39 +++++ mountinfo/mountinfo_freebsd.go | 12 ++ mountinfo/mountinfo_unix.go | 35 ----- 5 files changed, 358 insertions(+), 35 deletions(-) create mode 100644 mountinfo/mounted_linux.go create mode 100644 mountinfo/mounted_linux_test.go create mode 100644 mountinfo/mounted_unix.go delete mode 100644 mountinfo/mountinfo_unix.go diff --git a/mountinfo/mounted_linux.go b/mountinfo/mounted_linux.go new file mode 100644 index 00000000..738f16fb --- /dev/null +++ b/mountinfo/mounted_linux.go @@ -0,0 +1,53 @@ +package mountinfo + +import ( + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +// mountedByOpenat2 is a method of detecting a mount that works for all kinds +// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel. +func mountedByOpenat2(path string) (bool, error) { + dir, last := filepath.Split(path) + + dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC, + }) + if err != nil { + return false, &os.PathError{Op: "openat2", Path: dir, Err: err} + } + fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW, + Resolve: unix.RESOLVE_NO_XDEV, + }) + _ = unix.Close(dirfd) + switch err { + case nil: // definitely not a mount + _ = unix.Close(fd) + return false, nil + case unix.EXDEV: // definitely a mount + return true, nil + } + // not sure + return false, &os.PathError{Op: "openat2", Path: path, Err: err} +} + +func mounted(path string) (bool, error) { + // Try a fast path, using openat2() with RESOLVE_NO_XDEV. + mounted, err := mountedByOpenat2(path) + if err == nil { + return mounted, nil + } + // Another fast path: compare st.st_dev fields. + mounted, err = mountedByStat(path) + // This does not work for bind mounts, so false negative + // is possible, therefore only trust if return is true. + if mounted && err == nil { + return mounted, nil + } + + // Fallback to parsing mountinfo + return mountedByMountinfo(path) +} diff --git a/mountinfo/mounted_linux_test.go b/mountinfo/mounted_linux_test.go new file mode 100644 index 00000000..96b39b5b --- /dev/null +++ b/mountinfo/mounted_linux_test.go @@ -0,0 +1,254 @@ +package mountinfo + +import ( + "errors" + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" + "testing" + + "golang.org/x/sys/unix" +) + +const ( + notMounted = "not-mounted" +) + +func prepareMounts(t *testing.T) (dir string, mounts []string, err error) { + dir, err = ioutil.TempDir("", t.Name()) + if err != nil { + return + } + + // A real (tmpfs) mount. + mnt := filepath.Join(dir, "tmpfs-mount") + err = os.Mkdir(mnt, 0750) + if err != nil { + return + } + + err = unix.Mount("tmpfs", mnt, "tmpfs", 0, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + // A directory bind-mounted to itself. + mnt = filepath.Join(dir, "bind-mount-dir") + err = os.Mkdir(mnt, 0750) + if err != nil { + return + } + + err = unix.Mount(mnt, mnt, "", unix.MS_BIND, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + // A directory bind-mounted to other directory. + src := filepath.Join(dir, "some-dir") + err = os.Mkdir(src, 0750) + if err != nil { + return + } + + mnt = filepath.Join(dir, "bind-mounted-dir2") + err = os.Mkdir(mnt, 0750) + if err != nil { + return + } + err = unix.Mount(src, mnt, "", unix.MS_BIND, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + // A regular file bind-mounted to itself. + mnt = filepath.Join(dir, "bind-mount-file") + err = ioutil.WriteFile(mnt, []byte(""), 0640) + if err != nil { + return + } + + err = unix.Mount(mnt, mnt, "", unix.MS_BIND, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + // Not mounted socket. + sock := filepath.Join(dir, notMounted+".sock") + _, err = net.Listen("unix", sock) + if err != nil { + return + } + mounts = append(mounts, sock) + + // Bind-mounted socket. + mnt = filepath.Join(dir, "bind-mounted-socket") + err = ioutil.WriteFile(mnt, []byte(""), 0640) + if err != nil { + return + } + err = unix.Mount(sock, mnt, "", unix.MS_BIND, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + // Not mounted directory. + mnt = filepath.Join(dir, notMounted+"-dir") + err = os.Mkdir(mnt, 0750) + if err != nil { + return + } + mounts = append(mounts, mnt) + + // Not mounted file. + mnt = filepath.Join(dir, notMounted+"-file") + err = ioutil.WriteFile(mnt, []byte(""), 0640) + if err != nil { + return + } + mounts = append(mounts, mnt) + + // A broken not-mounted symlink. + symlink := filepath.Join(dir, notMounted+"-broken-symlink") + err = unix.Symlink("non-existent-dest", symlink) + if err != nil { + err = &os.PathError{Op: "symlink", Path: symlink, Err: err} + return + } + mounts = append(mounts, symlink) + + // A valid not-mounted symlink. + dst := filepath.Join(dir, "file") + err = ioutil.WriteFile(dst, []byte(""), 0640) + if err != nil { + return + } + symlink = filepath.Join(dir, notMounted+"-valid-symlink") + err = unix.Symlink(dst, symlink) + if err != nil { + err = &os.PathError{Op: "symlink", Path: symlink, Err: err} + return + } + mounts = append(mounts, symlink) + + // A valid bind-mounted symlink + mnt = filepath.Join(dir, "bind-mounted-symlink") + err = ioutil.WriteFile(mnt, []byte(""), 0640) + if err != nil { + return + } + err = unix.Mount(symlink, mnt, "", unix.MS_BIND, "") + if err != nil { + err = &os.PathError{Op: "mount", Path: mnt, Err: err} + return + } + mounts = append(mounts, mnt) + + return +} + +func cleanupMounts(t *testing.T, dir string, mounts []string) { + for _, m := range mounts { + if strings.Contains(m, notMounted) { + continue + } + err := unix.Unmount(m, unix.MNT_DETACH) + if err != nil { + t.Logf("can't umount %s: %v", m, err) + } + } + if err := os.RemoveAll(dir); err != nil { + t.Log(err) + } +} + +func TestMountedBy(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("requires root") + } + + dir, mounts, err := prepareMounts(t) + defer cleanupMounts(t, dir, mounts) + if err != nil { + t.Fatalf("setup failed: %v", err) + } + + checked := false + for _, m := range mounts { + exp := !strings.Contains(m, notMounted) + + mounted, err := mountedByMountinfo(m) + if err != nil { + t.Errorf("mountedByMountinfo(%q) error: %v", m, err) + } else if mounted != exp { + t.Errorf("mountedByMountinfo(%q): expected %v, got %v", m, exp, mounted) + } + + checked = true + mounted, err = mountedByOpenat2(m) + if err != nil { + t.Errorf("mountedByOpenat2(%q) error: %v", m, err) + } else if mounted != exp { + t.Errorf("mountedByOpenat2(%q): expected %v, got %v", m, exp, mounted) + } + + mounted, err = mountedByStat(m) + // mountedByStat can't detect bind mounts, returning + // errNotSure in case it can't reliably detect the mount. + if strings.Contains(m, "bind") { + exp = false + } + if err != nil { + t.Errorf("mountedByStat(%q) error: %v", m, err) + } else { + if mounted != exp { + t.Errorf("mountedByStat(%q): expected %v, got %v", m, exp, mounted) + } + } + } + if !checked { + t.Skip("no mounts to check can be found") + } +} + +func TestMountedByOpenat2VsMountinfo(t *testing.T) { + fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{Flags: unix.O_RDONLY}) + if err != nil { + t.Skipf("openat2: %v (old kernel? need Linux 5.6+)", err) + } + unix.Close(fd) + + mounts, err := GetMounts(nil) + if err != nil { + t.Fatalf("GetMounts error: %v", err) + } + + for _, mount := range mounts { + m := mount.Mountpoint + if m == "/" { + // mountedBy*() won't work for /, so skip it + // (this special case is handled by Mounted). + continue + } + mounted, err := mountedByOpenat2(m) + if err != nil { + if !errors.Is(err, os.ErrPermission) { + t.Errorf("mountedByOpenat2(%q) error: %+v", m, err) + } + } else if !mounted { + t.Errorf("mountedByOpenat2(%q): expected true, got false", m) + } + } +} diff --git a/mountinfo/mounted_unix.go b/mountinfo/mounted_unix.go new file mode 100644 index 00000000..21c5186f --- /dev/null +++ b/mountinfo/mounted_unix.go @@ -0,0 +1,39 @@ +// +build linux freebsd,cgo + +package mountinfo + +import ( + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +func mountedByStat(path string) (bool, error) { + var st unix.Stat_t + err := unix.Lstat(path, &st) + if err != nil { + return false, &os.PathError{Op: "stat", Path: path, Err: err} + } + dev := st.Dev + parent := filepath.Dir(path) + if err := unix.Lstat(parent, &st); err != nil { + return false, &os.PathError{Op: "stat", Path: parent, Err: err} + } + if dev != st.Dev { + // Device differs from that of parent, + // so definitely a mount point. + return true, nil + } + // NB: this does not detect bind mounts on Linux. + return false, nil +} + +func mountedByMountinfo(path string) (bool, error) { + entries, err := GetMounts(SingleEntryFilter(path)) + if err != nil { + return false, err + } + + return len(entries) > 0, nil +} diff --git a/mountinfo/mountinfo_freebsd.go b/mountinfo/mountinfo_freebsd.go index a7dbb1b4..b30dc162 100644 --- a/mountinfo/mountinfo_freebsd.go +++ b/mountinfo/mountinfo_freebsd.go @@ -51,3 +51,15 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) { } return out, nil } + +func mounted(path string) (bool, error) { + // Fast path: compare st.st_dev fields. + // This should always work for FreeBSD. + mounted, err := mountedByStat(path) + if err == nil { + return mounted, nil + } + + // Fallback to parsing mountinfo + return mountedByMountinfo(path) +} diff --git a/mountinfo/mountinfo_unix.go b/mountinfo/mountinfo_unix.go deleted file mode 100644 index 12ac7d75..00000000 --- a/mountinfo/mountinfo_unix.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build linux freebsd,cgo - -package mountinfo - -import ( - "path/filepath" - - "golang.org/x/sys/unix" -) - -func mounted(path string) (bool, error) { - var st unix.Stat_t - - err := unix.Lstat(path, &st) - switch err { - case unix.ENOENT: - // Nonexistent path, so not a mount point. - return false, nil - case nil: - dev := st.Dev - err = unix.Lstat(filepath.Dir(path), &st) - if err == nil && dev != st.Dev { - // Device number differs from that of parent, - // so definitely a mount point. - return true, nil - } - } - - entries, err := GetMounts(SingleEntryFilter(path)) - if err != nil { - return false, err - } - - return len(entries) > 0, nil -} From 0c38c022baed1ffc01ca0f345ab54ff055b6423e Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 14 Sep 2020 15:20:23 -0700 Subject: [PATCH 5/7] mountinfo: treat ENOENT as "not mounted" The positive side effect from this is we're returning earlier, not falling back on a slow path. The downside is this might not be entirely true, as a mounted path can be overshadowed by another mount, but still present in mountinfo. As the mount is inaccessible anyway, there is nothing that can be done with it, so let's treat it as non-mount. Signed-off-by: Kir Kolyshkin --- mountinfo/mounted_linux.go | 5 +++++ mountinfo/mounted_unix.go | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/mountinfo/mounted_linux.go b/mountinfo/mounted_linux.go index 738f16fb..bc9f6b2a 100644 --- a/mountinfo/mounted_linux.go +++ b/mountinfo/mounted_linux.go @@ -16,6 +16,9 @@ func mountedByOpenat2(path string) (bool, error) { Flags: unix.O_PATH | unix.O_CLOEXEC, }) if err != nil { + if err == unix.ENOENT { // not a mount + return false, nil + } return false, &os.PathError{Op: "openat2", Path: dir, Err: err} } fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{ @@ -29,6 +32,8 @@ func mountedByOpenat2(path string) (bool, error) { return false, nil case unix.EXDEV: // definitely a mount return true, nil + case unix.ENOENT: // not a mount + return false, nil } // not sure return false, &os.PathError{Op: "openat2", Path: path, Err: err} diff --git a/mountinfo/mounted_unix.go b/mountinfo/mounted_unix.go index 21c5186f..6f3ecf6b 100644 --- a/mountinfo/mounted_unix.go +++ b/mountinfo/mounted_unix.go @@ -11,8 +11,12 @@ import ( func mountedByStat(path string) (bool, error) { var st unix.Stat_t - err := unix.Lstat(path, &st) - if err != nil { + + if err := unix.Lstat(path, &st); err != nil { + if err == unix.ENOENT { + // Treat ENOENT as "not mounted". + return false, nil + } return false, &os.PathError{Op: "stat", Path: path, Err: err} } dev := st.Dev From 682f2bc299800251f299cf7a794e9514eeb58942 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Fri, 11 Sep 2020 11:58:19 -0700 Subject: [PATCH 6/7] mountinfo.mountedByMountinfo: normalize path Currently, the path provided to mountinfo.Mounted() must be - an absolute path; - with all symlinks resolved; - cleaned. Otherwise, the function may return a false negative (in case this is a bind mount and the kernel is too old to have openat2) -- i.e. it will return "not mounted" while the path is in fact mounted. This commit removes the above limitation by doing a path normalization. This should slow things down but it's not affecting any of the fast paths, so the slowdown will only be seen in the "bind mount and older kernel" case (which is already pretty slow since we have to parse mountinfo). v2: as in mountedByOpenat2 and mountedByStat, treat ENOENT as "not mounted". Signed-off-by: Kir Kolyshkin --- mountinfo/doc.go | 5 ++--- mountinfo/mounted_unix.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/mountinfo/doc.go b/mountinfo/doc.go index 73ec4050..21aa8dd5 100644 --- a/mountinfo/doc.go +++ b/mountinfo/doc.go @@ -14,9 +14,8 @@ // parse filters while reading mountinfo. A filter can skip some entries, or stop // processing the rest of the file once the needed information is found. // -// For functions that have path as an argument (such as Mounted or various filters), -// the argument must be -// - an absolute path; +// For mountinfo filters that accept path as an argument, the path must be: +// - absolute; // - having all symlinks resolved; // - being cleaned. // diff --git a/mountinfo/mounted_unix.go b/mountinfo/mounted_unix.go index 6f3ecf6b..c4d66b2f 100644 --- a/mountinfo/mounted_unix.go +++ b/mountinfo/mounted_unix.go @@ -3,6 +3,8 @@ package mountinfo import ( + "errors" + "fmt" "os" "path/filepath" @@ -33,7 +35,28 @@ func mountedByStat(path string) (bool, error) { return false, nil } +func normalizePath(path string) (realPath string, err error) { + if realPath, err = filepath.Abs(path); err != nil { + return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err) + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err) + } + if _, err := os.Stat(realPath); err != nil { + return "", fmt.Errorf("failed to stat target of %q: %w", path, err) + } + return realPath, nil +} + func mountedByMountinfo(path string) (bool, error) { + path, err := normalizePath(path) + if err != nil { + if errors.Is(err, unix.ENOENT) { + // treat ENOENT as "not mounted" + return false, nil + } + return false, err + } entries, err := GetMounts(SingleEntryFilter(path)) if err != nil { return false, err From 9c884dc3e7afd0e8a792e1516b3ce8b8073e546f Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 14 Sep 2020 19:18:41 -0700 Subject: [PATCH 7/7] ci: run tests under Fedora 32 This should make it possible to test the recently added mountedByOpenat2 functionality, which requires Linux kernel 5.6+. Alas, even the latest Ubuntu release available on Travis, Focal (20.04), has a kernel that's not new enough, so we have to setup a vagrant box. The machinery is mostly stolen from the runc repo at https://github.com/opencontainers/runc/ Signed-off-by: Kir Kolyshkin --- .ci/Vagrantfile.fedora32 | 31 +++++++++++++++++++++++++++++++ .ci/install-vagrant.sh | 20 ++++++++++++++++++++ .travis.yml | 15 +++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 .ci/Vagrantfile.fedora32 create mode 100755 .ci/install-vagrant.sh create mode 100644 .travis.yml diff --git a/.ci/Vagrantfile.fedora32 b/.ci/Vagrantfile.fedora32 new file mode 100644 index 00000000..68325f3a --- /dev/null +++ b/.ci/Vagrantfile.fedora32 @@ -0,0 +1,31 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| +# Fedora box is used for testing cgroup v2 support + config.vm.box = "fedora/32-cloud-base" + config.vm.provider :virtualbox do |v| + v.memory = 2048 + v.cpus = 2 + end + config.vm.provider :libvirt do |v| + v.memory = 2048 + v.cpus = 2 + end + config.vm.provision "shell", inline: <<-SHELL + set -e -u -o pipefail + # Work around dnf mirror failures by retrying a few times + for i in $(seq 0 2); do + sleep $i + cat << EOF | dnf -y shell && break +config exclude kernel,kernel-core +config install_weak_deps false +update +install make golang-go libseccomp-devel git-core +ts run +EOF + done + dnf clean all + + SHELL +end diff --git a/.ci/install-vagrant.sh b/.ci/install-vagrant.sh new file mode 100755 index 00000000..f4aa05ae --- /dev/null +++ b/.ci/install-vagrant.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eux -o pipefail +VAGRANT_VERSION="2.2.10" + +# Based on code from https://github.com/opencontainers/runc +DEB="vagrant_${VAGRANT_VERSION}_$(uname -m).deb" +wget "https://releases.hashicorp.com/vagrant/${VAGRANT_VERSION}/$DEB" +apt-get update +apt-get install -q -y \ + bridge-utils \ + dnsmasq-base \ + ebtables \ + libvirt-bin \ + libvirt-dev \ + qemu-kvm \ + qemu-utils \ + ruby-dev \ + ./"$DEB" +rm -f "$DEB" +vagrant plugin install vagrant-libvirt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..bcf2819a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +dist: bionic +os: linux +language: minimal +cache: + directories: + - /home/travis/.vagrant.d/boxes +jobs: + include: + - name: "Fedora 32" + before_install: + - sudo .ci/install-vagrant.sh + - ln -sf .ci/Vagrantfile.fedora32 Vagrantfile + - sudo vagrant up && sudo mkdir -p /root/.ssh && sudo sh -c "vagrant ssh-config >> /root/.ssh/config" + script: + - sudo ssh default -t 'cd /vagrant && sudo make'