github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/snap/pack/pack_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package pack_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strings"
    31  	"testing"
    32  
    33  	. "gopkg.in/check.v1"
    34  
    35  	"github.com/snapcore/snapd/dirs"
    36  
    37  	// for SanitizePlugsSlots
    38  	_ "github.com/snapcore/snapd/interfaces/builtin"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/snap/pack"
    41  	"github.com/snapcore/snapd/snap/squashfs"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  func Test(t *testing.T) { TestingT(t) }
    46  
    47  type packSuite struct {
    48  	testutil.BaseTest
    49  }
    50  
    51  var _ = Suite(&packSuite{})
    52  
    53  func (s *packSuite) SetUpTest(c *C) {
    54  	s.BaseTest.SetUpTest(c)
    55  
    56  	// chdir into a tempdir
    57  	pwd, err := os.Getwd()
    58  	c.Assert(err, IsNil)
    59  	s.AddCleanup(func() { os.Chdir(pwd) })
    60  	err = os.Chdir(c.MkDir())
    61  	c.Assert(err, IsNil)
    62  
    63  	// use fake root
    64  	dirs.SetRootDir(c.MkDir())
    65  }
    66  
    67  func (s *packSuite) TearDownTest(c *C) {
    68  	s.BaseTest.TearDownTest(c)
    69  }
    70  
    71  func makeExampleSnapSourceDir(c *C, snapYamlContent string) string {
    72  	tempdir := c.MkDir()
    73  	c.Assert(os.Chmod(tempdir, 0755), IsNil)
    74  
    75  	// use meta/snap.yaml
    76  	metaDir := filepath.Join(tempdir, "meta")
    77  	err := os.Mkdir(metaDir, 0755)
    78  	c.Assert(err, IsNil)
    79  	err = ioutil.WriteFile(filepath.Join(metaDir, "snap.yaml"), []byte(snapYamlContent), 0644)
    80  	c.Assert(err, IsNil)
    81  
    82  	const helloBinContent = `#!/bin/sh
    83  printf "hello world"
    84  `
    85  
    86  	// an example binary
    87  	binDir := filepath.Join(tempdir, "bin")
    88  	err = os.Mkdir(binDir, 0755)
    89  	c.Assert(err, IsNil)
    90  	err = ioutil.WriteFile(filepath.Join(binDir, "hello-world"), []byte(helloBinContent), 0755)
    91  	c.Assert(err, IsNil)
    92  
    93  	// unusual permissions for dir
    94  	tmpDir := filepath.Join(tempdir, "tmp")
    95  	err = os.Mkdir(tmpDir, 0755)
    96  	c.Assert(err, IsNil)
    97  	// avoid umask
    98  	err = os.Chmod(tmpDir, 01777)
    99  	c.Assert(err, IsNil)
   100  
   101  	// and file
   102  	someFile := filepath.Join(tempdir, "file-with-perm")
   103  	err = ioutil.WriteFile(someFile, []byte(""), 0666)
   104  	c.Assert(err, IsNil)
   105  	err = os.Chmod(someFile, 0666)
   106  	c.Assert(err, IsNil)
   107  
   108  	// an example symlink
   109  	err = os.Symlink("bin/hello-world", filepath.Join(tempdir, "symlink"))
   110  	c.Assert(err, IsNil)
   111  
   112  	return tempdir
   113  }
   114  
   115  func (s *packSuite) TestPackNoManifestFails(c *C) {
   116  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   117  	c.Assert(os.Remove(filepath.Join(sourceDir, "meta", "snap.yaml")), IsNil)
   118  	_, err := pack.Snap(sourceDir, pack.Defaults)
   119  	c.Assert(err, ErrorMatches, `.*/meta/snap\.yaml: no such file or directory`)
   120  }
   121  
   122  func (s *packSuite) TestPackMissingAppFails(c *C) {
   123  	sourceDir := makeExampleSnapSourceDir(c, `name: hello
   124  version: 0
   125  apps:
   126   foo:
   127    command: bin/hello-world
   128  `)
   129  	c.Assert(os.Remove(filepath.Join(sourceDir, "bin", "hello-world")), IsNil)
   130  	_, err := pack.Snap(sourceDir, pack.Defaults)
   131  	c.Assert(err, Equals, snap.ErrMissingPaths)
   132  }
   133  
   134  func (s *packSuite) TestValidateMissingAppFailsWithErrMissingPaths(c *C) {
   135  	var buf bytes.Buffer
   136  	sourceDir := makeExampleSnapSourceDir(c, `name: hello
   137  version: 0
   138  apps:
   139   foo:
   140    command: bin/hello-world
   141    plugs: [potato]
   142  `)
   143  	err := pack.CheckSkeleton(&buf, sourceDir)
   144  	c.Assert(err, IsNil)
   145  	c.Check(buf.String(), Equals, "snap \"hello\" has bad plugs or slots: potato (unknown interface \"potato\")\n")
   146  
   147  	buf.Reset()
   148  	c.Assert(os.Remove(filepath.Join(sourceDir, "bin", "hello-world")), IsNil)
   149  
   150  	err = pack.CheckSkeleton(&buf, sourceDir)
   151  	c.Assert(err, Equals, snap.ErrMissingPaths)
   152  	c.Check(buf.String(), Equals, "")
   153  }
   154  
   155  func (s *packSuite) TestPackExcludesBackups(c *C) {
   156  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   157  	target := c.MkDir()
   158  	// add a backup file
   159  	c.Assert(ioutil.WriteFile(filepath.Join(sourceDir, "foo~"), []byte("hi"), 0755), IsNil)
   160  	snapfile, err := pack.Snap(sourceDir, &pack.Options{TargetDir: c.MkDir()})
   161  	c.Assert(err, IsNil)
   162  	c.Assert(squashfs.New(snapfile).Unpack("*", target), IsNil)
   163  
   164  	cmd := exec.Command("diff", "-qr", sourceDir, target)
   165  	cmd.Env = append(cmd.Env, "LANG=C")
   166  	out, err := cmd.Output()
   167  	c.Check(err, NotNil)
   168  	c.Check(string(out), Matches, `(?m)Only in \S+: foo~`)
   169  }
   170  
   171  func (s *packSuite) TestPackExcludesTopLevelDEBIAN(c *C) {
   172  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   173  	target := c.MkDir()
   174  	// add a toplevel DEBIAN
   175  	c.Assert(os.MkdirAll(filepath.Join(sourceDir, "DEBIAN", "foo"), 0755), IsNil)
   176  	// and a non-toplevel DEBIAN
   177  	c.Assert(os.MkdirAll(filepath.Join(sourceDir, "bar", "DEBIAN", "baz"), 0755), IsNil)
   178  	snapfile, err := pack.Snap(sourceDir, &pack.Options{TargetDir: c.MkDir()})
   179  	c.Assert(err, IsNil)
   180  	c.Assert(squashfs.New(snapfile).Unpack("*", target), IsNil)
   181  	cmd := exec.Command("diff", "-qr", sourceDir, target)
   182  	cmd.Env = append(cmd.Env, "LANG=C")
   183  	out, err := cmd.Output()
   184  	c.Check(err, NotNil)
   185  	c.Check(string(out), Matches, `(?m)Only in \S+: DEBIAN`)
   186  	// but *only one* DEBIAN is skipped
   187  	c.Check(strings.Count(string(out), "Only in"), Equals, 1)
   188  }
   189  
   190  func (s *packSuite) TestPackExcludesWholeDirs(c *C) {
   191  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   192  	target := c.MkDir()
   193  	// add a file inside a skipped dir
   194  	c.Assert(os.Mkdir(filepath.Join(sourceDir, ".bzr"), 0755), IsNil)
   195  	c.Assert(ioutil.WriteFile(filepath.Join(sourceDir, ".bzr", "foo"), []byte("hi"), 0755), IsNil)
   196  	snapfile, err := pack.Snap(sourceDir, &pack.Options{TargetDir: c.MkDir()})
   197  	c.Assert(err, IsNil)
   198  	c.Assert(squashfs.New(snapfile).Unpack("*", target), IsNil)
   199  	out, _ := exec.Command("find", sourceDir).Output()
   200  	c.Check(string(out), Not(Equals), "")
   201  	cmd := exec.Command("diff", "-qr", sourceDir, target)
   202  	cmd.Env = append(cmd.Env, "LANG=C")
   203  	out, err = cmd.Output()
   204  	c.Check(err, NotNil)
   205  	c.Check(string(out), Matches, `(?m)Only in \S+: \.bzr`)
   206  }
   207  
   208  func (s *packSuite) TestDebArchitecture(c *C) {
   209  	c.Check(pack.DebArchitecture(&snap.Info{Architectures: []string{"foo"}}), Equals, "foo")
   210  	c.Check(pack.DebArchitecture(&snap.Info{Architectures: []string{"foo", "bar"}}), Equals, "multi")
   211  	c.Check(pack.DebArchitecture(&snap.Info{Architectures: nil}), Equals, "all")
   212  }
   213  
   214  func (s *packSuite) TestPackSimple(c *C) {
   215  	sourceDir := makeExampleSnapSourceDir(c, `name: hello
   216  version: 1.0.1
   217  architectures: ["i386", "amd64"]
   218  integration:
   219   app:
   220    apparmor-profile: meta/hello.apparmor
   221  `)
   222  
   223  	outputDir := filepath.Join(c.MkDir(), "output")
   224  	absSnapFile := filepath.Join(c.MkDir(), "foo.snap")
   225  
   226  	type T struct {
   227  		outputDir, filename, expected string
   228  	}
   229  
   230  	table := []T{
   231  		// no output dir, no filename -> default in .
   232  		{"", "", "hello_1.0.1_multi.snap"},
   233  		// no output dir, relative filename -> filename in .
   234  		{"", "foo.snap", "foo.snap"},
   235  		// no putput dir, absolute filename -> absolute filename
   236  		{"", absSnapFile, absSnapFile},
   237  		// output dir, no filename -> default in outputdir
   238  		{outputDir, "", filepath.Join(outputDir, "hello_1.0.1_multi.snap")},
   239  		// output dir, relative filename -> filename in outputDir
   240  		{filepath.Join(outputDir, "inner"), "../foo.snap", filepath.Join(outputDir, "foo.snap")},
   241  		// output dir, absolute filename -> absolute filename
   242  		{outputDir, absSnapFile, absSnapFile},
   243  	}
   244  
   245  	for i, t := range table {
   246  		comm := Commentf("%d", i)
   247  		resultSnap, err := pack.Snap(sourceDir, &pack.Options{
   248  			TargetDir: t.outputDir,
   249  			SnapName:  t.filename,
   250  		})
   251  		c.Assert(err, IsNil, comm)
   252  
   253  		// check that there is result
   254  		_, err = os.Stat(resultSnap)
   255  		c.Assert(err, IsNil, comm)
   256  		c.Assert(resultSnap, Equals, t.expected, comm)
   257  
   258  		// check that the content looks sane
   259  		output, err := exec.Command("unsquashfs", "-ll", resultSnap).CombinedOutput()
   260  		c.Assert(err, IsNil, comm)
   261  		for _, needle := range []string{
   262  			"meta/snap.yaml",
   263  			"bin/hello-world",
   264  			"symlink -> bin/hello-world",
   265  		} {
   266  			expr := fmt.Sprintf(`(?ms).*%s.*`, regexp.QuoteMeta(needle))
   267  			c.Assert(string(output), Matches, expr, comm)
   268  		}
   269  	}
   270  
   271  }
   272  
   273  func (s *packSuite) TestPackGadgetValidate(c *C) {
   274  	sourceDir := makeExampleSnapSourceDir(c, `name: funky-gadget
   275  version: 1.0.1
   276  type: gadget
   277  `)
   278  
   279  	var gadgetYamlContent = `
   280  volumes:
   281    bad:
   282      bootloader: grub
   283      structure:
   284        - name: fs-struct
   285          type: DA,21686148-6449-6E6F-744E-656564454649
   286          size: 1M
   287          filesystem: ext4
   288          content:
   289            - source: foo/
   290              target: /
   291        - name: bare-struct
   292          type: DA,21686148-6449-6E6F-744E-656564454649
   293          size: 1M
   294          content:
   295            - image: bare.img
   296  
   297  `
   298  	err := ioutil.WriteFile(filepath.Join(sourceDir, "meta/gadget.yaml"), []byte(gadgetYamlContent), 0644)
   299  	c.Assert(err, IsNil)
   300  
   301  	outputDir := filepath.Join(c.MkDir(), "output")
   302  	absSnapFile := filepath.Join(c.MkDir(), "foo.snap")
   303  
   304  	// gadget validation fails during layout
   305  	_, err = pack.Snap(sourceDir, &pack.Options{
   306  		TargetDir: outputDir,
   307  		SnapName:  absSnapFile,
   308  	})
   309  	c.Assert(err, ErrorMatches, `invalid layout of volume "bad": cannot lay out structure #1 \("bare-struct"\): content "bare.img": stat .*/bare.img: no such file or directory`)
   310  
   311  	err = ioutil.WriteFile(filepath.Join(sourceDir, "bare.img"), []byte("foo"), 0644)
   312  	c.Assert(err, IsNil)
   313  
   314  	// gadget validation fails during content presence checks
   315  	_, err = pack.Snap(sourceDir, &pack.Options{
   316  		TargetDir: outputDir,
   317  		SnapName:  absSnapFile,
   318  	})
   319  	c.Assert(err, ErrorMatches, `invalid volume "bad": structure #0 \("fs-struct"\), content source:foo/: source path does not exist`)
   320  
   321  	err = os.Mkdir(filepath.Join(sourceDir, "foo"), 0644)
   322  	c.Assert(err, IsNil)
   323  	// all good now
   324  	_, err = pack.Snap(sourceDir, &pack.Options{
   325  		TargetDir: outputDir,
   326  		SnapName:  absSnapFile,
   327  	})
   328  	c.Assert(err, IsNil)
   329  }
   330  
   331  func (s *packSuite) TestPackWithCompressionHappy(c *C) {
   332  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   333  
   334  	for _, comp := range []string{"", "xz", "lzo"} {
   335  		snapfile, err := pack.Snap(sourceDir, &pack.Options{
   336  			TargetDir:   c.MkDir(),
   337  			Compression: comp,
   338  		})
   339  		c.Assert(err, IsNil)
   340  		c.Assert(snapfile, testutil.FilePresent)
   341  	}
   342  }
   343  
   344  func (s *packSuite) TestPackWithCompressionUnhappy(c *C) {
   345  	sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}")
   346  
   347  	for _, comp := range []string{"gzip", "zstd", "silly"} {
   348  		snapfile, err := pack.Snap(sourceDir, &pack.Options{
   349  			TargetDir:   c.MkDir(),
   350  			Compression: comp,
   351  		})
   352  		c.Assert(err, ErrorMatches, fmt.Sprintf("cannot use compression %q", comp))
   353  		c.Assert(snapfile, Equals, "")
   354  	}
   355  }