github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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 }