github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/snap/validate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 snap_test 21 22 import ( 23 "fmt" 24 "regexp" 25 "strconv" 26 "strings" 27 28 . "gopkg.in/check.v1" 29 30 . "github.com/snapcore/snapd/snap" 31 32 "github.com/snapcore/snapd/testutil" 33 ) 34 35 type ValidateSuite struct { 36 testutil.BaseTest 37 } 38 39 var _ = Suite(&ValidateSuite{}) 40 41 func createSampleApp() *AppInfo { 42 socket := &SocketInfo{ 43 Name: "sock", 44 ListenStream: "$SNAP_COMMON/socket", 45 } 46 app := &AppInfo{ 47 Snap: &Info{ 48 SideInfo: SideInfo{ 49 RealName: "mysnap", 50 Revision: R(20), 51 }, 52 }, 53 Name: "foo", 54 Daemon: "simple", 55 DaemonScope: SystemDaemon, 56 Plugs: map[string]*PlugInfo{"network-bind": {}}, 57 Sockets: map[string]*SocketInfo{ 58 "sock": socket, 59 }, 60 } 61 socket.App = app 62 return app 63 } 64 65 func (s *ValidateSuite) SetUpTest(c *C) { 66 s.BaseTest.SetUpTest(c) 67 s.BaseTest.AddCleanup(MockSanitizePlugsSlots(func(snapInfo *Info) {})) 68 } 69 70 func (s *ValidateSuite) TearDownTest(c *C) { 71 s.BaseTest.TearDownTest(c) 72 } 73 74 func (s *ValidateSuite) TestValidateVersion(c *C) { 75 validVersions := []string{ 76 "0", "v1.0", "0.12+16.04.20160126-0ubuntu1", 77 "1:6.0.1+r16-3", "1.0~", "1.0+", "README.~1~", 78 "a+++++++++++++++++++++++++++++++", 79 "AZaz:.+~-123", 80 } 81 for _, version := range validVersions { 82 err := ValidateVersion(version) 83 c.Assert(err, IsNil) 84 } 85 invalidVersionsTable := [][2]string{ 86 {"~foo", `must start with an ASCII alphanumeric (and not '~')`}, 87 {"+foo", `must start with an ASCII alphanumeric (and not '+')`}, 88 89 {"foo:", `must end with an ASCII alphanumeric or one of '+' or '~' (and not ':')`}, 90 {"foo.", `must end with an ASCII alphanumeric or one of '+' or '~' (and not '.')`}, 91 {"foo-", `must end with an ASCII alphanumeric or one of '+' or '~' (and not '-')`}, 92 93 {"horrible_underscores", `contains invalid characters: "_"`}, 94 {"foo($bar^baz$)meep", `contains invalid characters: "($", "^", "$)"`}, 95 96 {"árbol", `must be printable, non-whitespace ASCII`}, 97 {"日本語", `must be printable, non-whitespace ASCII`}, 98 {"한글", `must be printable, non-whitespace ASCII`}, 99 {"ру́сский язы́к", `must be printable, non-whitespace ASCII`}, 100 101 {"~foo$bar:", `must start with an ASCII alphanumeric (and not '~'),` + 102 ` must end with an ASCII alphanumeric or one of '+' or '~' (and not ':'),` + 103 ` and contains invalid characters: "$"`}, 104 } 105 for _, t := range invalidVersionsTable { 106 version, reason := t[0], t[1] 107 err := ValidateVersion(version) 108 c.Assert(err, NotNil) 109 c.Assert(err.Error(), Equals, fmt.Sprintf("invalid snap version %s: %s", strconv.QuoteToASCII(version), reason)) 110 } 111 // version cannot be empty 112 c.Assert(ValidateVersion(""), ErrorMatches, `invalid snap version: cannot be empty`) 113 // version length cannot be >32 114 c.Assert(ValidateVersion("this-version-is-a-little-bit-older"), ErrorMatches, 115 `invalid snap version "this-version-is-a-little-bit-older": cannot be longer than 32 characters \(got: 34\)`) 116 } 117 118 func (s *ValidateSuite) TestValidateLicense(c *C) { 119 validLicenses := []string{ 120 "GPL-3.0", "(GPL-3.0)", "GPL-3.0+", "GPL-3.0 AND GPL-2.0", "GPL-3.0 OR GPL-2.0", "MIT OR (GPL-3.0 AND GPL-2.0)", "MIT OR(GPL-3.0 AND GPL-2.0)", 121 } 122 for _, epoch := range validLicenses { 123 err := ValidateLicense(epoch) 124 c.Assert(err, IsNil) 125 } 126 invalidLicenses := []string{ 127 "GPL~3.0", "3.0-GPL", "(GPL-3.0", "(GPL-3.0))", "GPL-3.0++", "+GPL-3.0", "GPL-3.0 GPL-2.0", 128 } 129 for _, epoch := range invalidLicenses { 130 err := ValidateLicense(epoch) 131 c.Assert(err, NotNil) 132 } 133 } 134 135 func (s *ValidateSuite) TestValidateHook(c *C) { 136 validHooks := []*HookInfo{ 137 {Name: "a"}, 138 {Name: "aaa"}, 139 {Name: "a-a"}, 140 {Name: "aa-a"}, 141 {Name: "a-aa"}, 142 {Name: "a-b-c"}, 143 {Name: "valid", CommandChain: []string{"valid"}}, 144 } 145 for _, hook := range validHooks { 146 err := ValidateHook(hook) 147 c.Assert(err, IsNil) 148 } 149 invalidHooks := []*HookInfo{ 150 {Name: ""}, 151 {Name: "a a"}, 152 {Name: "a--a"}, 153 {Name: "-a"}, 154 {Name: "a-"}, 155 {Name: "0"}, 156 {Name: "123"}, 157 {Name: "123abc"}, 158 {Name: "日本語"}, 159 } 160 for _, hook := range invalidHooks { 161 err := ValidateHook(hook) 162 c.Assert(err, ErrorMatches, `invalid hook name: ".*"`) 163 } 164 invalidHooks = []*HookInfo{ 165 {Name: "valid", CommandChain: []string{"in'valid"}}, 166 {Name: "valid", CommandChain: []string{"in valid"}}, 167 } 168 for _, hook := range invalidHooks { 169 err := ValidateHook(hook) 170 c.Assert(err, ErrorMatches, `hook command-chain contains illegal.*`) 171 } 172 } 173 174 // ValidateApp 175 176 func (s *ValidateSuite) TestValidateAppSockets(c *C) { 177 app := createSampleApp() 178 app.Sockets["sock"].SocketMode = 0600 179 c.Check(ValidateApp(app), IsNil) 180 } 181 182 func (s *ValidateSuite) TestValidateAppSocketsEmptyPermsOk(c *C) { 183 app := createSampleApp() 184 c.Check(ValidateApp(app), IsNil) 185 } 186 187 func (s *ValidateSuite) TestValidateAppSocketsWrongPerms(c *C) { 188 app := createSampleApp() 189 app.Sockets["sock"].SocketMode = 1234 190 err := ValidateApp(app) 191 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": cannot use mode: 2322`) 192 } 193 194 func (s *ValidateSuite) TestValidateAppSocketsMissingNetworkBindPlug(c *C) { 195 app := createSampleApp() 196 delete(app.Plugs, "network-bind") 197 err := ValidateApp(app) 198 c.Assert( 199 err, ErrorMatches, 200 `"network-bind" interface plug is required when sockets are used`) 201 } 202 203 func (s *ValidateSuite) TestValidateAppSocketsEmptyListenStream(c *C) { 204 app := createSampleApp() 205 app.Sockets["sock"].ListenStream = "" 206 err := ValidateApp(app) 207 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": "listen-stream" is not defined`) 208 } 209 210 func (s *ValidateSuite) TestValidateAppSocketsInvalidName(c *C) { 211 app := createSampleApp() 212 app.Sockets["sock"].Name = "invalid name" 213 err := ValidateApp(app) 214 c.Assert(err, ErrorMatches, `invalid definition of socket "invalid name": invalid socket name: "invalid name"`) 215 } 216 217 func (s *ValidateSuite) TestValidateAppSocketsValidListenStreamAddresses(c *C) { 218 app := createSampleApp() 219 validListenAddresses := []string{ 220 // socket paths using variables as prefix 221 "$SNAP_DATA/my.socket", 222 "$SNAP_COMMON/my.socket", 223 "$XDG_RUNTIME_DIR/my.socket", 224 // abstract sockets 225 "@snap.mysnap.my.socket", 226 // addresses and ports 227 "1", 228 "1023", 229 "1024", 230 "65535", 231 "127.0.0.1:8080", 232 "[::]:8080", 233 "[::1]:8080", 234 } 235 socket := app.Sockets["sock"] 236 for _, validAddress := range validListenAddresses { 237 socket.ListenStream = validAddress 238 err := ValidateApp(app) 239 c.Check(err, IsNil, Commentf(validAddress)) 240 } 241 } 242 243 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPath(c *C) { 244 app := createSampleApp() 245 invalidListenAddresses := []string{ 246 // socket paths out of the snap dirs 247 "/some/path/my.socket", 248 "/var/snap/mysnap/20/my.socket", // path is correct but has hardcoded prefix 249 // Variables only valid for user mode services 250 "$SNAP_USER_DATA/my.socket", 251 "$SNAP_USER_COMMON/my.socket", 252 } 253 socket := app.Sockets["sock"] 254 for _, invalidAddress := range invalidListenAddresses { 255 socket.ListenStream = invalidAddress 256 err := ValidateApp(app) 257 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream": system daemon sockets must have a prefix of .*`) 258 } 259 } 260 261 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPathContainsDots(c *C) { 262 app := createSampleApp() 263 app.Sockets["sock"].ListenStream = "$SNAP/../some.path" 264 err := ValidateApp(app) 265 c.Assert( 266 err, ErrorMatches, 267 `invalid definition of socket "sock": invalid "listen-stream": "\$SNAP/../some.path" should be written as "some.path"`) 268 } 269 270 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPathPrefix(c *C) { 271 app := createSampleApp() 272 invalidListenAddresses := []string{ 273 "$SNAP/my.socket", // snap dir is not writable 274 "$SOMEVAR/my.socket", 275 } 276 socket := app.Sockets["sock"] 277 for _, invalidAddress := range invalidListenAddresses { 278 socket.ListenStream = invalidAddress 279 err := ValidateApp(app) 280 c.Assert( 281 err, ErrorMatches, 282 `invalid definition of socket "sock": invalid "listen-stream": system daemon sockets must have a prefix of \$SNAP_DATA, \$SNAP_COMMON or \$XDG_RUNTIME_DIR`) 283 } 284 } 285 286 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamAbstractSocket(c *C) { 287 app := createSampleApp() 288 invalidListenAddresses := []string{ 289 "@snap.mysnap", 290 "@snap.mysnap\000.foo", 291 "@snap.notmysnap.my.socket", 292 "@some.other.name", 293 "@snap.myappiswrong.foo", 294 } 295 socket := app.Sockets["sock"] 296 for _, invalidAddress := range invalidListenAddresses { 297 socket.ListenStream = invalidAddress 298 err := ValidateApp(app) 299 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": path for "listen-stream" must be prefixed with.*`) 300 } 301 } 302 303 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamAddress(c *C) { 304 app := createSampleApp() 305 app.Daemon = "simple" 306 app.DaemonScope = SystemDaemon 307 invalidListenAddresses := []string{ 308 "10.0.1.1:8080", 309 "[fafa::baba]:8080", 310 "127.0.0.1\000:8080", 311 "127.0.0.1::8080", 312 } 313 socket := app.Sockets["sock"] 314 for _, invalidAddress := range invalidListenAddresses { 315 socket.ListenStream = invalidAddress 316 err := ValidateApp(app) 317 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" address ".*", must be one of: 127\.0\.0\.1, \[::1\], \[::\]`) 318 } 319 } 320 321 func (s *ValidateSuite) TestValidateAppSocketsInvalidListenStreamPort(c *C) { 322 app := createSampleApp() 323 invalidPorts := []string{ 324 "0", 325 "66536", 326 "-8080", 327 "12312345345", 328 "[::]:-123", 329 "[::1]:3452345234", 330 "invalid", 331 "[::]:invalid", 332 } 333 socket := app.Sockets["sock"] 334 for _, invalidPort := range invalidPorts { 335 socket.ListenStream = invalidPort 336 err := ValidateApp(app) 337 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" port number.*`) 338 } 339 } 340 341 func (s *ValidateSuite) TestValidateAppUserSocketsValidListenStreamAddresses(c *C) { 342 app := createSampleApp() 343 app.DaemonScope = UserDaemon 344 validListenAddresses := []string{ 345 // socket paths using variables as prefix 346 "$SNAP_USER_DATA/my.socket", 347 "$SNAP_USER_COMMON/my.socket", 348 "$XDG_RUNTIME_DIR/my.socket", 349 // abstract sockets 350 "@snap.mysnap.my.socket", 351 // addresses and ports 352 "1", 353 "1023", 354 "1024", 355 "65535", 356 "127.0.0.1:8080", 357 "[::]:8080", 358 "[::1]:8080", 359 } 360 socket := app.Sockets["sock"] 361 for _, validAddress := range validListenAddresses { 362 socket.ListenStream = validAddress 363 err := ValidateApp(app) 364 c.Check(err, IsNil, Commentf(validAddress)) 365 } 366 } 367 368 func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamPath(c *C) { 369 app := createSampleApp() 370 app.DaemonScope = UserDaemon 371 invalidListenAddresses := []string{ 372 // socket paths out of the snap dirs 373 "/some/path/my.socket", 374 // Variables only valid for system mode services 375 "$SNAP_DATA/my.socket", 376 "$SNAP_COMMON/my.socket", 377 } 378 socket := app.Sockets["sock"] 379 for _, invalidAddress := range invalidListenAddresses { 380 socket.ListenStream = invalidAddress 381 err := ValidateApp(app) 382 c.Check(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream": user daemon sockets must have a prefix of .*`) 383 } 384 } 385 386 func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamAbstractSocket(c *C) { 387 app := createSampleApp() 388 app.DaemonScope = UserDaemon 389 invalidListenAddresses := []string{ 390 "@snap.mysnap", 391 "@snap.mysnap\000.foo", 392 "@snap.notmysnap.my.socket", 393 "@some.other.name", 394 "@snap.myappiswrong.foo", 395 } 396 socket := app.Sockets["sock"] 397 for _, invalidAddress := range invalidListenAddresses { 398 socket.ListenStream = invalidAddress 399 err := ValidateApp(app) 400 c.Assert(err, ErrorMatches, `invalid definition of socket "sock": path for "listen-stream" must be prefixed with.*`) 401 } 402 } 403 404 func (s *ValidateSuite) TestValidateAppUserSocketsInvalidListenStreamPort(c *C) { 405 app := createSampleApp() 406 app.DaemonScope = UserDaemon 407 invalidListenAddresses := []string{ 408 "0", 409 "66536", 410 "-8080", 411 "12312345345", 412 "[::]:-123", 413 "[::1]:3452345234", 414 "invalid", 415 "[::]:invalid", 416 } 417 socket := app.Sockets["sock"] 418 for _, invalidAddress := range invalidListenAddresses { 419 socket.ListenStream = invalidAddress 420 err := ValidateApp(app) 421 c.Check(err, ErrorMatches, `invalid definition of socket "sock": invalid "listen-stream" port number .*`) 422 } 423 } 424 425 func (s *ValidateSuite) TestAppWhitelistSimple(c *C) { 426 c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo"}), IsNil) 427 c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo"}), IsNil) 428 c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo"}), IsNil) 429 } 430 431 func (s *ValidateSuite) TestAppWhitelistWithVars(c *C) { 432 c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo $SNAP_DATA"}), IsNil) 433 c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo $SNAP_DATA"}), IsNil) 434 c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo $SNAP_DATA"}), IsNil) 435 } 436 437 func (s *ValidateSuite) TestAppWhitelistIllegal(c *C) { 438 c.Check(ValidateApp(&AppInfo{Name: "x\n"}), NotNil) 439 c.Check(ValidateApp(&AppInfo{Name: "test!me"}), NotNil) 440 c.Check(ValidateApp(&AppInfo{Name: "test'me"}), NotNil) 441 c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo\n"}), NotNil) 442 c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo\n"}), NotNil) 443 c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo\n"}), NotNil) 444 c.Check(ValidateApp(&AppInfo{Name: "foo", BusName: "foo\n"}), NotNil) 445 c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar'baz"}}), NotNil) 446 c.Check(ValidateApp(&AppInfo{Name: "foo", CommandChain: []string{"bar baz"}}), NotNil) 447 } 448 449 func (s *ValidateSuite) TestAppDaemonValue(c *C) { 450 for _, t := range []struct { 451 daemon string 452 ok bool 453 }{ 454 // good 455 {"", true}, 456 {"simple", true}, 457 {"forking", true}, 458 {"oneshot", true}, 459 {"dbus", true}, 460 {"notify", true}, 461 // bad 462 {"invalid-thing", false}, 463 } { 464 var daemonScope DaemonScope 465 if t.daemon != "" { 466 daemonScope = SystemDaemon 467 } 468 if t.ok { 469 c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: daemonScope}), IsNil) 470 } else { 471 c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: daemonScope}), ErrorMatches, fmt.Sprintf(`"daemon" field contains invalid value %q`, t.daemon)) 472 } 473 } 474 } 475 476 func (s *ValidateSuite) TestAppDaemonScopeValue(c *C) { 477 for _, t := range []struct { 478 daemon string 479 daemonScope DaemonScope 480 ok bool 481 }{ 482 // good 483 {"", "", true}, 484 {"simple", SystemDaemon, true}, 485 {"simple", UserDaemon, true}, 486 // bad 487 {"simple", "", false}, 488 {"", SystemDaemon, false}, 489 {"", UserDaemon, false}, 490 {"simple", "invalid-mode", false}, 491 } { 492 app := &AppInfo{Name: "foo", Daemon: t.daemon, DaemonScope: t.daemonScope} 493 err := ValidateApp(app) 494 if t.ok { 495 c.Check(err, IsNil) 496 } else if t.daemon == "" { 497 c.Check(err, ErrorMatches, `"daemon-scope" can only be set for daemons`) 498 } else if t.daemonScope == "" { 499 c.Check(err, ErrorMatches, `"daemon-scope" must be set for daemons`) 500 } else { 501 c.Check(err, ErrorMatches, fmt.Sprintf(`invalid "daemon-scope": %q`, t.daemonScope)) 502 } 503 } 504 } 505 506 func (s *ValidateSuite) TestAppStopMode(c *C) { 507 // check services 508 for _, t := range []struct { 509 stopMode StopModeType 510 ok bool 511 }{ 512 // good 513 {"", true}, 514 {"sigterm", true}, 515 {"sigterm-all", true}, 516 {"sighup", true}, 517 {"sighup-all", true}, 518 {"sigusr1", true}, 519 {"sigusr1-all", true}, 520 {"sigusr2", true}, 521 {"sigusr2-all", true}, 522 // bad 523 {"invalid-thing", false}, 524 } { 525 if t.ok { 526 c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, StopMode: t.stopMode}), IsNil) 527 } else { 528 c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, StopMode: t.stopMode}), ErrorMatches, fmt.Sprintf(`"stop-mode" field contains invalid value %q`, t.stopMode)) 529 } 530 } 531 532 // non-services cannot have a stop-mode 533 err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", StopMode: "sigterm"}) 534 c.Check(err, ErrorMatches, `"stop-mode" cannot be used for "foo", only for services`) 535 } 536 537 func (s *ValidateSuite) TestAppRefreshMode(c *C) { 538 // check services 539 for _, t := range []struct { 540 refreshMode string 541 ok bool 542 }{ 543 // good 544 {"", true}, 545 {"endure", true}, 546 {"restart", true}, 547 // bad 548 {"invalid-thing", false}, 549 } { 550 err := ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, RefreshMode: t.refreshMode}) 551 if t.ok { 552 c.Check(err, IsNil) 553 } else { 554 c.Check(err, ErrorMatches, fmt.Sprintf(`"refresh-mode" field contains invalid value %q`, t.refreshMode)) 555 } 556 } 557 558 // non-services cannot have a refresh-mode 559 err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", RefreshMode: "endure"}) 560 c.Check(err, ErrorMatches, `"refresh-mode" cannot be used for "foo", only for services`) 561 } 562 563 func (s *ValidateSuite) TestAppWhitelistError(c *C) { 564 err := ValidateApp(&AppInfo{Name: "foo", Command: "x\n"}) 565 c.Assert(err, NotNil) 566 c.Check(err.Error(), Equals, `app description field 'command' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:$-]*$')`) 567 } 568 569 func (s *ValidateSuite) TestAppActivatesOn(c *C) { 570 info, err := InfoFromSnapYaml([]byte(`name: foo 571 version: 1.0 572 slots: 573 dbus-slot: 574 interface: dbus 575 bus: system 576 apps: 577 server: 578 daemon: simple 579 activates-on: [dbus-slot] 580 `)) 581 c.Assert(err, IsNil) 582 app := info.Apps["server"] 583 c.Check(ValidateApp(app), IsNil) 584 } 585 586 func (s *ValidateSuite) TestAppActivatesOnNotDaemon(c *C) { 587 info, err := InfoFromSnapYaml([]byte(`name: foo 588 version: 1.0 589 slots: 590 dbus-slot: 591 apps: 592 server: 593 activates-on: [dbus-slot] 594 `)) 595 c.Assert(err, IsNil) 596 app := info.Apps["server"] 597 c.Check(ValidateApp(app), ErrorMatches, `activates-on is only applicable to services`) 598 } 599 600 func (s *ValidateSuite) TestAppActivatesOnSlotNotDbus(c *C) { 601 info, err := InfoFromSnapYaml([]byte(`name: foo 602 version: 1.0 603 apps: 604 server: 605 daemon: simple 606 slots: [network-bind] 607 activates-on: [network-bind] 608 `)) 609 c.Assert(err, IsNil) 610 app := info.Apps["server"] 611 c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "network-bind": slot does not use dbus interface`) 612 } 613 614 func (s *ValidateSuite) TestAppActivatesOnDaemonScopeMismatch(c *C) { 615 info, err := InfoFromSnapYaml([]byte(`name: foo 616 version: 1.0 617 slots: 618 dbus-slot: 619 interface: dbus 620 bus: session 621 apps: 622 server: 623 daemon: simple 624 activates-on: [dbus-slot] 625 `)) 626 c.Assert(err, IsNil) 627 app := info.Apps["server"] 628 c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": bus "session" does not match daemon-scope "system"`) 629 630 info, err = InfoFromSnapYaml([]byte(`name: foo 631 version: 1.0 632 slots: 633 dbus-slot: 634 interface: dbus 635 bus: system 636 apps: 637 server: 638 daemon: simple 639 daemon-scope: user 640 activates-on: [dbus-slot] 641 `)) 642 c.Assert(err, IsNil) 643 app = info.Apps["server"] 644 c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": bus "system" does not match daemon-scope "user"`) 645 } 646 647 func (s *ValidateSuite) TestAppActivatesOnDuplicateApp(c *C) { 648 info, err := InfoFromSnapYaml([]byte(`name: foo 649 version: 1.0 650 slots: 651 dbus-slot: 652 interface: dbus 653 bus: system 654 apps: 655 server: 656 daemon: simple 657 activates-on: [dbus-slot] 658 dup: 659 daemon: simple 660 activates-on: [dbus-slot] 661 `)) 662 c.Assert(err, IsNil) 663 app := info.Apps["server"] 664 c.Check(ValidateApp(app), ErrorMatches, `invalid activates-on value "dbus-slot": slot is also activatable on app "dup"`) 665 } 666 667 // Validate 668 669 func (s *ValidateSuite) TestDetectIllegalYamlBinaries(c *C) { 670 info, err := InfoFromSnapYaml([]byte(`name: foo 671 version: 1.0 672 apps: 673 tes!me: 674 command: someething 675 `)) 676 c.Assert(err, IsNil) 677 678 err = Validate(info) 679 c.Check(err, NotNil) 680 } 681 682 func (s *ValidateSuite) TestDetectIllegalYamlService(c *C) { 683 info, err := InfoFromSnapYaml([]byte(`name: foo 684 version: 1.0 685 apps: 686 tes!me: 687 command: something 688 daemon: forking 689 `)) 690 c.Assert(err, IsNil) 691 692 err = Validate(info) 693 c.Check(err, NotNil) 694 } 695 696 func (s *ValidateSuite) TestIllegalSnapName(c *C) { 697 info, err := InfoFromSnapYaml([]byte(`name: foo.something 698 version: 1.0 699 `)) 700 c.Assert(err, IsNil) 701 702 err = Validate(info) 703 c.Check(err, ErrorMatches, `invalid snap name: "foo.something"`) 704 } 705 706 func (s *ValidateSuite) TestValidateChecksName(c *C) { 707 info, err := InfoFromSnapYaml([]byte(` 708 version: 1.0 709 `)) 710 c.Assert(err, IsNil) 711 712 err = Validate(info) 713 c.Check(err, ErrorMatches, `snap name cannot be empty`) 714 } 715 716 func (s *ValidateSuite) TestIllegalSnapEpoch(c *C) { 717 _, err := InfoFromSnapYaml([]byte(`name: foo 718 version: 1.0 719 epoch: 0* 720 `)) 721 c.Assert(err, ErrorMatches, `.*invalid epoch.*`) 722 } 723 724 func (s *ValidateSuite) TestMissingSnapEpochIsOkay(c *C) { 725 info, err := InfoFromSnapYaml([]byte(`name: foo 726 version: 1.0 727 `)) 728 c.Assert(err, IsNil) 729 c.Assert(Validate(info), IsNil) 730 } 731 732 func (s *ValidateSuite) TestIllegalSnapLicense(c *C) { 733 info, err := InfoFromSnapYaml([]byte(`name: foo 734 version: 1.0 735 license: GPL~3.0 736 `)) 737 c.Assert(err, IsNil) 738 739 err = Validate(info) 740 c.Check(err, ErrorMatches, `cannot validate license "GPL~3.0": unknown license: GPL~3.0`) 741 } 742 743 func (s *ValidateSuite) TestMissingSnapLicenseIsOkay(c *C) { 744 info, err := InfoFromSnapYaml([]byte(`name: foo 745 version: 1.0 746 `)) 747 c.Assert(err, IsNil) 748 c.Assert(Validate(info), IsNil) 749 } 750 751 func (s *ValidateSuite) TestIllegalHookName(c *C) { 752 hookType := NewHookType(regexp.MustCompile(".*")) 753 restore := MockSupportedHookTypes([]*HookType{hookType}) 754 defer restore() 755 756 info, err := InfoFromSnapYaml([]byte(`name: foo 757 version: 1.0 758 hooks: 759 123abc: 760 `)) 761 c.Assert(err, IsNil) 762 763 err = Validate(info) 764 c.Check(err, ErrorMatches, `invalid hook name: "123abc"`) 765 } 766 767 func (s *ValidateSuite) TestPlugSlotNamesUnique(c *C) { 768 info, err := InfoFromSnapYaml([]byte(`name: snap 769 version: 0 770 plugs: 771 foo: 772 slots: 773 foo: 774 `)) 775 c.Assert(err, IsNil) 776 err = Validate(info) 777 c.Check(err, ErrorMatches, `cannot have plug and slot with the same name: "foo"`) 778 } 779 780 func (s *ValidateSuite) TestIllegalAliasName(c *C) { 781 info, err := InfoFromSnapYaml([]byte(`name: foo 782 version: 1.0 783 apps: 784 foo: 785 aliases: [foo$] 786 `)) 787 c.Assert(err, IsNil) 788 789 err = Validate(info) 790 c.Check(err, ErrorMatches, `cannot have "foo\$" as alias name for app "foo" - use only letters, digits, dash, underscore and dot characters`) 791 } 792 793 func (s *ValidateSuite) TestValidatePlugSlotName(c *C) { 794 const yaml1 = ` 795 name: invalid-plugs 796 version: 1 797 plugs: 798 p--lug: null 799 ` 800 strk := NewScopedTracker() 801 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml1), nil, strk) 802 c.Assert(err, IsNil) 803 c.Assert(info.Plugs, HasLen, 1) 804 err = Validate(info) 805 c.Assert(err, ErrorMatches, `invalid plug name: "p--lug"`) 806 807 const yaml2 = ` 808 name: invalid-slots 809 version: 1 810 slots: 811 s--lot: null 812 ` 813 strk = NewScopedTracker() 814 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml2), nil, strk) 815 c.Assert(err, IsNil) 816 c.Assert(info.Slots, HasLen, 1) 817 err = Validate(info) 818 c.Assert(err, ErrorMatches, `invalid slot name: "s--lot"`) 819 820 const yaml3 = ` 821 name: invalid-plugs-iface 822 version: 1 823 plugs: 824 plug: 825 interface: i--face 826 ` 827 strk = NewScopedTracker() 828 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml3), nil, strk) 829 c.Assert(err, IsNil) 830 c.Assert(info.Plugs, HasLen, 1) 831 err = Validate(info) 832 c.Assert(err, ErrorMatches, `invalid interface name "i--face" for plug "plug"`) 833 834 const yaml4 = ` 835 name: invalid-slots-iface 836 version: 1 837 slots: 838 slot: 839 interface: i--face 840 ` 841 strk = NewScopedTracker() 842 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml4), nil, strk) 843 c.Assert(err, IsNil) 844 c.Assert(info.Slots, HasLen, 1) 845 err = Validate(info) 846 c.Assert(err, ErrorMatches, `invalid interface name "i--face" for slot "slot"`) 847 } 848 849 func (s *ValidateSuite) TestValidateBaseNone(c *C) { 850 const yaml = `name: requires-base 851 version: 1 852 base: none 853 ` 854 strk := NewScopedTracker() 855 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 856 c.Assert(err, IsNil) 857 err = Validate(info) 858 c.Assert(err, IsNil) 859 c.Check(info.Base, Equals, "none") 860 } 861 862 func (s *ValidateSuite) TestValidateBaseNoneError(c *C) { 863 yamlTemplate := `name: use-base-none 864 version: 1 865 base: none 866 867 %APPS_OR_HOOKS% 868 ` 869 const apps = ` 870 apps: 871 useradd: 872 command: bin/true 873 ` 874 const hooks = ` 875 hooks: 876 configure: 877 ` 878 879 for _, appsOrHooks := range []string{apps, hooks} { 880 yaml := strings.Replace(yamlTemplate, "%APPS_OR_HOOKS%", appsOrHooks, -1) 881 strk := NewScopedTracker() 882 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 883 c.Assert(err, IsNil) 884 err = Validate(info) 885 c.Assert(err, ErrorMatches, `cannot have apps or hooks with base "none"`) 886 } 887 } 888 889 type testConstraint string 890 891 func (constraint testConstraint) IsOffLimits(path string) bool { 892 return true 893 } 894 895 func (s *ValidateSuite) TestValidateLayout(c *C) { 896 si := &Info{SuggestedName: "foo"} 897 // Several invalid layouts. 898 c.Check(ValidateLayout(&Layout{Snap: si}, nil), 899 ErrorMatches, "layout cannot use an empty path") 900 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo"}, nil), 901 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 902 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", Type: "tmpfs"}, nil), 903 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 904 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", BindFile: "/froz"}, nil), 905 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 906 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Symlink: "/bar", BindFile: "/froz"}, nil), 907 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 908 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", BindFile: "/froz"}, nil), 909 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 910 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "/bar", Symlink: "/froz"}, nil), 911 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 912 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Symlink: "/froz"}, nil), 913 ErrorMatches, `layout "/foo" must define a bind mount, a filesystem mount or a symlink`) 914 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "ext4"}, nil), 915 ErrorMatches, `layout "/foo" uses invalid filesystem "ext4"`) 916 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Type: "tmpfs", User: "foo"}, nil), 917 ErrorMatches, `layout "/foo/bar" uses invalid user "foo"`) 918 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Type: "tmpfs", Group: "foo"}, nil), 919 ErrorMatches, `layout "/foo/bar" uses invalid group "foo"`) 920 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Mode: 02755}, nil), 921 ErrorMatches, `layout "/foo" uses invalid mode 02755`) 922 c.Check(ValidateLayout(&Layout{Snap: si, Path: "$FOO", Type: "tmpfs"}, nil), 923 ErrorMatches, `layout "\$FOO" uses invalid mount point: reference to unknown variable "\$FOO"`) 924 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Bind: "$BAR"}, nil), 925 ErrorMatches, `layout "/foo" uses invalid bind mount source "\$BAR": reference to unknown variable "\$BAR"`) 926 c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/evil", Bind: "/etc"}, nil), 927 ErrorMatches, `layout "\$SNAP/evil" uses invalid bind mount source "/etc": must start with \$SNAP, \$SNAP_DATA or \$SNAP_COMMON`) 928 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Symlink: "$BAR"}, nil), 929 ErrorMatches, `layout "/foo" uses invalid symlink old name "\$BAR": reference to unknown variable "\$BAR"`) 930 c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/evil", Symlink: "/etc"}, nil), 931 ErrorMatches, `layout "\$SNAP/evil" uses invalid symlink old name "/etc": must start with \$SNAP, \$SNAP_DATA or \$SNAP_COMMON`) 932 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo/bar", Bind: "$SNAP/bar/foo"}, []LayoutConstraint{testConstraint("/foo")}), 933 ErrorMatches, `layout "/foo/bar" underneath prior layout item "/foo"`) 934 935 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/dev", Type: "tmpfs"}, nil), 936 ErrorMatches, `layout "/dev" in an off-limits area`) 937 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/dev/foo", Type: "tmpfs"}, nil), 938 ErrorMatches, `layout "/dev/foo" in an off-limits area`) 939 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/home", Type: "tmpfs"}, nil), 940 ErrorMatches, `layout "/home" in an off-limits area`) 941 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/proc", Type: "tmpfs"}, nil), 942 ErrorMatches, `layout "/proc" in an off-limits area`) 943 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/sys", Type: "tmpfs"}, nil), 944 ErrorMatches, `layout "/sys" in an off-limits area`) 945 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/run", Type: "tmpfs"}, nil), 946 ErrorMatches, `layout "/run" in an off-limits area`) 947 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/boot", Type: "tmpfs"}, nil), 948 ErrorMatches, `layout "/boot" in an off-limits area`) 949 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lost+found", Type: "tmpfs"}, nil), 950 ErrorMatches, `layout "/lost\+found" in an off-limits area`) 951 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/media", Type: "tmpfs"}, nil), 952 ErrorMatches, `layout "/media" in an off-limits area`) 953 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/snap", Type: "tmpfs"}, nil), 954 ErrorMatches, `layout "/var/snap" in an off-limits area`) 955 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/lib/snapd", Type: "tmpfs"}, nil), 956 ErrorMatches, `layout "/var/lib/snapd" in an off-limits area`) 957 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var/lib/snapd/hostfs", Type: "tmpfs"}, nil), 958 ErrorMatches, `layout "/var/lib/snapd/hostfs" in an off-limits area`) 959 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lib/firmware", Type: "tmpfs"}, nil), 960 ErrorMatches, `layout "/lib/firmware" in an off-limits area`) 961 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lib/modules", Type: "tmpfs"}, nil), 962 ErrorMatches, `layout "/lib/modules" in an off-limits area`) 963 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/tmp", Type: "tmpfs"}, nil), 964 ErrorMatches, `layout "/tmp" in an off-limits area`) 965 966 // Several valid layouts. 967 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/foo", Type: "tmpfs", Mode: 01755}, nil), IsNil) 968 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/usr", Bind: "$SNAP/usr"}, nil), IsNil) 969 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Bind: "$SNAP_DATA/var"}, nil), IsNil) 970 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Bind: "$SNAP_COMMON/var"}, nil), IsNil) 971 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/etc/foo.conf", Symlink: "$SNAP_DATA/etc/foo.conf"}, nil), IsNil) 972 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", User: "root"}, nil), IsNil) 973 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", Group: "root"}, nil), IsNil) 974 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/a/b", Type: "tmpfs", Mode: 0655}, nil), IsNil) 975 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/usr", Symlink: "$SNAP/usr"}, nil), IsNil) 976 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Symlink: "$SNAP_DATA/var"}, nil), IsNil) 977 c.Check(ValidateLayout(&Layout{Snap: si, Path: "/var", Symlink: "$SNAP_COMMON/var"}, nil), IsNil) 978 c.Check(ValidateLayout(&Layout{Snap: si, Path: "$SNAP/data", Symlink: "$SNAP_DATA"}, nil), IsNil) 979 } 980 981 func (s *ValidateSuite) TestValidateLayoutAll(c *C) { 982 // /usr/foo prevents /usr/foo/bar from being valid (tmpfs) 983 const yaml1 = ` 984 name: broken-layout-1 985 layout: 986 /usr/foo: 987 type: tmpfs 988 /usr/foo/bar: 989 type: tmpfs 990 ` 991 const yaml1rev = ` 992 name: broken-layout-1 993 layout: 994 /usr/foo/bar: 995 type: tmpfs 996 /usr/foo: 997 type: tmpfs 998 ` 999 1000 for _, yaml := range []string{yaml1, yaml1rev} { 1001 strk := NewScopedTracker() 1002 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk) 1003 c.Assert(err, IsNil) 1004 c.Assert(info.Layout, HasLen, 2) 1005 err = ValidateLayoutAll(info) 1006 c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`) 1007 } 1008 1009 // Same as above but with bind-mounts instead of filesystem mounts. 1010 const yaml2 = ` 1011 name: broken-layout-2 1012 layout: 1013 /usr/foo: 1014 bind: $SNAP 1015 /usr/foo/bar: 1016 bind: $SNAP 1017 ` 1018 const yaml2rev = ` 1019 name: broken-layout-2 1020 layout: 1021 /usr/foo/bar: 1022 bind: $SNAP 1023 /usr/foo: 1024 bind: $SNAP 1025 ` 1026 for _, yaml := range []string{yaml2, yaml2rev} { 1027 strk := NewScopedTracker() 1028 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk) 1029 c.Assert(err, IsNil) 1030 c.Assert(info.Layout, HasLen, 2) 1031 err = ValidateLayoutAll(info) 1032 c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`) 1033 } 1034 1035 // /etc/foo (directory) is not clashing with /etc/foo.conf (file) 1036 const yaml3 = ` 1037 name: valid-layout-1 1038 layout: 1039 /etc/foo: 1040 bind: $SNAP_DATA/foo 1041 /etc/foo.conf: 1042 symlink: $SNAP_DATA/foo.conf 1043 ` 1044 const yaml3rev = ` 1045 name: valid-layout-1 1046 layout: 1047 /etc/foo.conf: 1048 symlink: $SNAP_DATA/foo.conf 1049 /etc/foo: 1050 bind: $SNAP_DATA/foo 1051 ` 1052 for _, yaml := range []string{yaml3, yaml3rev} { 1053 strk := NewScopedTracker() 1054 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk) 1055 c.Assert(err, IsNil) 1056 c.Assert(info.Layout, HasLen, 2) 1057 err = ValidateLayoutAll(info) 1058 c.Assert(err, IsNil) 1059 } 1060 1061 // /etc/foo file is not clashing with /etc/foobar 1062 const yaml4 = ` 1063 name: valid-layout-2 1064 layout: 1065 /etc/foo: 1066 symlink: $SNAP_DATA/foo 1067 /etc/foobar: 1068 symlink: $SNAP_DATA/foobar 1069 ` 1070 const yaml4rev = ` 1071 name: valid-layout-2 1072 layout: 1073 /etc/foobar: 1074 symlink: $SNAP_DATA/foobar 1075 /etc/foo: 1076 symlink: $SNAP_DATA/foo 1077 ` 1078 for _, yaml := range []string{yaml4, yaml4rev} { 1079 strk := NewScopedTracker() 1080 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk) 1081 c.Assert(err, IsNil) 1082 c.Assert(info.Layout, HasLen, 2) 1083 err = ValidateLayoutAll(info) 1084 c.Assert(err, IsNil) 1085 } 1086 1087 // /etc/foo file is also clashing with /etc/foo/bar 1088 const yaml5 = ` 1089 name: valid-layout-2 1090 layout: 1091 /usr/foo: 1092 symlink: $SNAP_DATA/foo 1093 /usr/foo/bar: 1094 bind: $SNAP_DATA/foo/bar 1095 ` 1096 const yaml5rev = ` 1097 name: valid-layout-2 1098 layout: 1099 /usr/foo/bar: 1100 bind: $SNAP_DATA/foo/bar 1101 /usr/foo: 1102 symlink: $SNAP_DATA/foo 1103 ` 1104 for _, yaml := range []string{yaml5, yaml5rev} { 1105 strk := NewScopedTracker() 1106 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), &SideInfo{Revision: R(42)}, strk) 1107 c.Assert(err, IsNil) 1108 c.Assert(info.Layout, HasLen, 2) 1109 err = ValidateLayoutAll(info) 1110 c.Assert(err, ErrorMatches, `layout "/usr/foo/bar" underneath prior layout item "/usr/foo"`) 1111 } 1112 1113 const yaml6 = ` 1114 name: tricky-layout-1 1115 layout: 1116 /etc/norf: 1117 bind: $SNAP/etc/norf 1118 /etc/norf: 1119 bind-file: $SNAP/etc/norf 1120 ` 1121 strk := NewScopedTracker() 1122 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml6), &SideInfo{Revision: R(42)}, strk) 1123 c.Assert(err, IsNil) 1124 c.Assert(info.Layout, HasLen, 1) 1125 err = ValidateLayoutAll(info) 1126 c.Assert(err, IsNil) 1127 c.Assert(info.Layout["/etc/norf"].Bind, Equals, "") 1128 c.Assert(info.Layout["/etc/norf"].BindFile, Equals, "$SNAP/etc/norf") 1129 1130 // Two layouts refer to the same path as a directory and a file. 1131 const yaml7 = ` 1132 name: clashing-source-path-1 1133 layout: 1134 /etc/norf: 1135 bind: $SNAP/etc/norf 1136 /etc/corge: 1137 bind-file: $SNAP/etc/norf 1138 ` 1139 1140 strk = NewScopedTracker() 1141 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml7), &SideInfo{Revision: R(42)}, strk) 1142 c.Assert(err, IsNil) 1143 c.Assert(info.Layout, HasLen, 2) 1144 err = ValidateLayoutAll(info) 1145 c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to directory "\$SNAP/etc/norf" but another layout treats it as file`) 1146 1147 // Two layouts refer to the same path as a directory and a file (other way around). 1148 const yaml8 = ` 1149 name: clashing-source-path-2 1150 layout: 1151 /etc/norf: 1152 bind-file: $SNAP/etc/norf 1153 /etc/corge: 1154 bind: $SNAP/etc/norf 1155 ` 1156 strk = NewScopedTracker() 1157 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml8), &SideInfo{Revision: R(42)}, strk) 1158 c.Assert(err, IsNil) 1159 c.Assert(info.Layout, HasLen, 2) 1160 err = ValidateLayoutAll(info) 1161 c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to file "\$SNAP/etc/norf" but another layout treats it as a directory`) 1162 1163 // Two layouts refer to the same path, but one uses variable and the other doesn't. 1164 const yaml9 = ` 1165 name: clashing-source-path-3 1166 layout: 1167 /etc/norf: 1168 bind-file: $SNAP/etc/norf 1169 /etc/corge: 1170 bind: /snap/clashing-source-path-3/42/etc/norf 1171 ` 1172 strk = NewScopedTracker() 1173 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml9), &SideInfo{Revision: R(42)}, strk) 1174 c.Assert(err, IsNil) 1175 c.Assert(info.Layout, HasLen, 2) 1176 err = ValidateLayoutAll(info) 1177 c.Assert(err, ErrorMatches, `layout "/etc/norf" refers to file "\$SNAP/etc/norf" but another layout treats it as a directory`) 1178 1179 // Same source path referred from a bind mount and symlink doesn't clash. 1180 const yaml10 = ` 1181 name: non-clashing-source-1 1182 layout: 1183 /etc/norf: 1184 bind: $SNAP/etc/norf 1185 /etc/corge: 1186 symlink: $SNAP/etc/norf 1187 ` 1188 1189 strk = NewScopedTracker() 1190 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml10), &SideInfo{Revision: R(42)}, strk) 1191 c.Assert(err, IsNil) 1192 c.Assert(info.Layout, HasLen, 2) 1193 err = ValidateLayoutAll(info) 1194 c.Assert(err, IsNil) 1195 1196 // Same source path referred from a file bind mount and symlink doesn't clash. 1197 const yaml11 = ` 1198 name: non-clashing-source-1 1199 layout: 1200 /etc/norf: 1201 bind-file: $SNAP/etc/norf 1202 /etc/corge: 1203 symlink: $SNAP/etc/norf 1204 ` 1205 1206 strk = NewScopedTracker() 1207 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml11), &SideInfo{Revision: R(42)}, strk) 1208 c.Assert(err, IsNil) 1209 c.Assert(info.Layout, HasLen, 2) 1210 err = ValidateLayoutAll(info) 1211 c.Assert(err, IsNil) 1212 1213 // Layout replacing files in another snap's mount point 1214 const yaml12 = ` 1215 name: this-snap 1216 layout: 1217 /snap/that-snap/current/stuff: 1218 symlink: $SNAP/stuff 1219 ` 1220 1221 strk = NewScopedTracker() 1222 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml12), &SideInfo{Revision: R(42)}, strk) 1223 c.Assert(err, IsNil) 1224 c.Assert(info.Layout, HasLen, 1) 1225 err = ValidateLayoutAll(info) 1226 c.Assert(err, ErrorMatches, `layout "/snap/that-snap/current/stuff" defines a layout in space belonging to another snap`) 1227 1228 const yaml13 = ` 1229 name: this-snap 1230 layout: 1231 $SNAP/relative: 1232 symlink: $SNAP/existent-dir 1233 ` 1234 1235 // Layout using $SNAP/... as source 1236 strk = NewScopedTracker() 1237 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml13), &SideInfo{Revision: R(42)}, strk) 1238 c.Assert(err, IsNil) 1239 c.Assert(info.Layout, HasLen, 1) 1240 err = ValidateLayoutAll(info) 1241 c.Assert(err, IsNil) 1242 1243 var yaml14Pattern = ` 1244 name: this-snap 1245 layout: 1246 %s: 1247 symlink: $SNAP/existent-dir 1248 ` 1249 1250 for _, testCase := range []struct { 1251 str string 1252 topLevelDir string 1253 }{ 1254 {"/nonexistent-dir", "/nonexistent-dir"}, 1255 {"/nonexistent-dir/subdir", "/nonexistent-dir"}, 1256 {"///////unclean-absolute-dir", "/unclean-absolute-dir"}, 1257 } { 1258 // Layout adding a new top-level directory 1259 strk = NewScopedTracker() 1260 yaml14 := fmt.Sprintf(yaml14Pattern, testCase.str) 1261 info, err = InfoFromSnapYamlWithSideInfo([]byte(yaml14), &SideInfo{Revision: R(42)}, strk) 1262 c.Assert(err, IsNil) 1263 c.Assert(info.Layout, HasLen, 1) 1264 err = ValidateLayoutAll(info) 1265 c.Assert(err, ErrorMatches, fmt.Sprintf(`layout %q defines a new top-level directory %q`, testCase.str, testCase.topLevelDir)) 1266 } 1267 1268 } 1269 1270 func (s *YamlSuite) TestValidateAppStartupOrder(c *C) { 1271 meta := []byte(` 1272 name: foo 1273 version: 1.0 1274 `) 1275 fooAfterBaz := []byte(` 1276 apps: 1277 foo: 1278 after: [baz] 1279 daemon: simple 1280 bar: 1281 daemon: forking 1282 `) 1283 fooBeforeBaz := []byte(` 1284 apps: 1285 foo: 1286 before: [baz] 1287 daemon: simple 1288 bar: 1289 daemon: forking 1290 `) 1291 1292 fooNotADaemon := []byte(` 1293 apps: 1294 foo: 1295 after: [bar] 1296 bar: 1297 daemon: forking 1298 `) 1299 1300 fooBarNotADaemon := []byte(` 1301 apps: 1302 foo: 1303 after: [bar] 1304 daemon: forking 1305 bar: 1306 `) 1307 fooSelfCycle := []byte(` 1308 apps: 1309 foo: 1310 after: [foo] 1311 daemon: forking 1312 bar: 1313 `) 1314 // cycle between foo and bar 1315 badOrder1 := []byte(` 1316 apps: 1317 foo: 1318 after: [bar] 1319 daemon: forking 1320 bar: 1321 after: [foo] 1322 daemon: forking 1323 `) 1324 // conflicting schedule for baz 1325 badOrder2 := []byte(` 1326 apps: 1327 foo: 1328 before: [bar] 1329 daemon: forking 1330 bar: 1331 after: [foo] 1332 daemon: forking 1333 baz: 1334 before: [foo] 1335 after: [bar] 1336 daemon: forking 1337 `) 1338 // conflicting schedule for baz 1339 badOrder3Cycle := []byte(` 1340 apps: 1341 foo: 1342 before: [bar] 1343 after: [zed] 1344 daemon: forking 1345 bar: 1346 before: [baz] 1347 daemon: forking 1348 baz: 1349 before: [zed] 1350 daemon: forking 1351 zed: 1352 daemon: forking 1353 `) 1354 goodOrder1 := []byte(` 1355 apps: 1356 foo: 1357 after: [bar, zed] 1358 daemon: oneshot 1359 bar: 1360 before: [foo] 1361 daemon: dbus 1362 baz: 1363 after: [foo] 1364 daemon: forking 1365 zed: 1366 daemon: dbus 1367 `) 1368 goodOrder2 := []byte(` 1369 apps: 1370 foo: 1371 after: [baz] 1372 daemon: oneshot 1373 bar: 1374 before: [baz] 1375 daemon: dbus 1376 baz: 1377 daemon: forking 1378 zed: 1379 daemon: dbus 1380 after: [foo, bar, baz] 1381 `) 1382 mixedSystemUserDaemons := []byte(` 1383 apps: 1384 foo: 1385 daemon: simple 1386 bar: 1387 daemon: simple 1388 daemon-scope: user 1389 after: [foo] 1390 `) 1391 1392 tcs := []struct { 1393 name string 1394 desc []byte 1395 err string 1396 }{{ 1397 name: "foo after baz", 1398 desc: fooAfterBaz, 1399 err: `invalid definition of application "foo": before/after references a missing application "baz"`, 1400 }, { 1401 name: "foo before baz", 1402 desc: fooBeforeBaz, 1403 err: `invalid definition of application "foo": before/after references a missing application "baz"`, 1404 }, { 1405 name: "foo not a daemon", 1406 desc: fooNotADaemon, 1407 err: `invalid definition of application "foo": must be a service to define before/after ordering`, 1408 }, { 1409 name: "foo wants bar, bar not a daemon", 1410 desc: fooBarNotADaemon, 1411 err: `invalid definition of application "foo": before/after references a non-service application "bar"`, 1412 }, { 1413 name: "bad order 1", 1414 desc: badOrder1, 1415 err: `applications are part of a before/after cycle: (foo, bar)|(bar, foo)`, 1416 }, { 1417 name: "bad order 2", 1418 desc: badOrder2, 1419 err: `applications are part of a before/after cycle: ((foo|bar|baz)(, )?){3}`, 1420 }, { 1421 name: "bad order 3 - cycle", 1422 desc: badOrder3Cycle, 1423 err: `applications are part of a before/after cycle: ((foo|bar|baz|zed)(, )?){4}`, 1424 }, { 1425 name: "all good, 3 apps", 1426 desc: goodOrder1, 1427 }, { 1428 name: "all good, 4 apps", 1429 desc: goodOrder2, 1430 }, { 1431 name: "self cycle", 1432 desc: fooSelfCycle, 1433 err: `applications are part of a before/after cycle: foo`, 1434 }, { 1435 name: "user daemon wants system daemon", 1436 desc: mixedSystemUserDaemons, 1437 err: `invalid definition of application "bar": before/after references service with different daemon-scope "foo"`, 1438 }} 1439 for _, tc := range tcs { 1440 c.Logf("trying %q", tc.name) 1441 info, err := InfoFromSnapYaml(append(meta, tc.desc...)) 1442 c.Assert(err, IsNil) 1443 1444 err = Validate(info) 1445 if tc.err != "" { 1446 c.Assert(err, ErrorMatches, tc.err) 1447 } else { 1448 c.Assert(err, IsNil) 1449 } 1450 } 1451 } 1452 1453 func (s *ValidateSuite) TestValidateAppWatchdogTimeout(c *C) { 1454 s.testValidateAppTimeout(c, "watchdog") 1455 } 1456 func (s *ValidateSuite) TestValidateAppStartTimeout(c *C) { 1457 s.testValidateAppTimeout(c, "start") 1458 } 1459 func (s *ValidateSuite) TestValidateAppStopTimeout(c *C) { 1460 s.testValidateAppTimeout(c, "stop") 1461 } 1462 1463 func (s *ValidateSuite) testValidateAppTimeout(c *C, timeout string) { 1464 timeout += "-timeout" 1465 meta := []byte(` 1466 name: foo 1467 version: 1.0 1468 `) 1469 fooAllGood := []byte(fmt.Sprintf(` 1470 apps: 1471 foo: 1472 daemon: simple 1473 %s: 12s 1474 `, timeout)) 1475 fooNotADaemon := []byte(fmt.Sprintf(` 1476 apps: 1477 foo: 1478 %s: 12s 1479 `, timeout)) 1480 1481 fooNegative := []byte(fmt.Sprintf(` 1482 apps: 1483 foo: 1484 daemon: simple 1485 %s: -12s 1486 `, timeout)) 1487 1488 tcs := []struct { 1489 name string 1490 desc []byte 1491 err string 1492 }{{ 1493 name: "foo all good", 1494 desc: fooAllGood, 1495 }, { 1496 name: "foo not a service", 1497 desc: fooNotADaemon, 1498 err: timeout + ` is only applicable to services`, 1499 }, { 1500 name: "negative timeout", 1501 desc: fooNegative, 1502 err: timeout + ` cannot be negative`, 1503 }} 1504 for _, tc := range tcs { 1505 c.Logf("trying %q", tc.name) 1506 info, err := InfoFromSnapYaml(append(meta, tc.desc...)) 1507 c.Assert(err, IsNil) 1508 c.Assert(info, NotNil) 1509 1510 err = Validate(info) 1511 if tc.err != "" { 1512 c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err) 1513 } else { 1514 c.Assert(err, IsNil) 1515 } 1516 } 1517 } 1518 1519 func (s *YamlSuite) TestValidateAppTimer(c *C) { 1520 meta := []byte(` 1521 name: foo 1522 version: 1.0 1523 `) 1524 allGood := []byte(` 1525 apps: 1526 foo: 1527 daemon: simple 1528 timer: 10:00-12:00 1529 `) 1530 notAService := []byte(` 1531 apps: 1532 foo: 1533 timer: 10:00-12:00 1534 `) 1535 badTimer := []byte(` 1536 apps: 1537 foo: 1538 daemon: oneshot 1539 timer: mon,10:00-12:00,mon2-wed3 1540 `) 1541 1542 tcs := []struct { 1543 name string 1544 desc []byte 1545 err string 1546 }{{ 1547 name: "all correct", 1548 desc: allGood, 1549 }, { 1550 name: "not a service", 1551 desc: notAService, 1552 err: `timer is only applicable to services`, 1553 }, { 1554 name: "invalid timer", 1555 desc: badTimer, 1556 err: `timer has invalid format: cannot parse "mon2-wed3": invalid schedule fragment`, 1557 }} 1558 for _, tc := range tcs { 1559 c.Logf("trying %q", tc.name) 1560 info, err := InfoFromSnapYaml(append(meta, tc.desc...)) 1561 c.Assert(err, IsNil) 1562 1563 err = Validate(info) 1564 if tc.err != "" { 1565 c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err) 1566 } else { 1567 c.Assert(err, IsNil) 1568 } 1569 } 1570 } 1571 1572 func (s *ValidateSuite) TestValidateOsCannotHaveBase(c *C) { 1573 info, err := InfoFromSnapYaml([]byte(`name: foo 1574 version: 1.0 1575 type: os 1576 base: bar 1577 `)) 1578 c.Assert(err, IsNil) 1579 1580 err = Validate(info) 1581 c.Check(err, ErrorMatches, `cannot have "base" field on "os" snap "foo"`) 1582 } 1583 1584 func (s *ValidateSuite) TestValidateOsCanHaveBaseNone(c *C) { 1585 info, err := InfoFromSnapYaml([]byte(`name: foo 1586 version: 1.0 1587 type: os 1588 base: none 1589 `)) 1590 c.Assert(err, IsNil) 1591 c.Assert(Validate(info), IsNil) 1592 } 1593 1594 func (s *ValidateSuite) TestValidateBaseInorrectSnapName(c *C) { 1595 info, err := InfoFromSnapYaml([]byte(`name: foo 1596 version: 1.0 1597 base: aAAAA 1598 `)) 1599 c.Assert(err, IsNil) 1600 1601 err = Validate(info) 1602 c.Check(err, ErrorMatches, `invalid base name: invalid snap name: \"aAAAA\"`) 1603 } 1604 1605 func (s *ValidateSuite) TestValidateBaseSnapInstanceNameNotAllowed(c *C) { 1606 info, err := InfoFromSnapYaml([]byte(`name: foo 1607 version: 1.0 1608 base: foo_abc 1609 `)) 1610 c.Assert(err, IsNil) 1611 1612 err = Validate(info) 1613 c.Check(err, ErrorMatches, `base cannot specify a snap instance name: "foo_abc"`) 1614 } 1615 1616 func (s *ValidateSuite) TestValidateBaseCannotHaveBase(c *C) { 1617 info, err := InfoFromSnapYaml([]byte(`name: foo 1618 version: 1.0 1619 type: base 1620 base: bar 1621 `)) 1622 c.Assert(err, IsNil) 1623 1624 err = Validate(info) 1625 c.Check(err, ErrorMatches, `cannot have "base" field on "base" snap "foo"`) 1626 } 1627 1628 func (s *ValidateSuite) TestValidateBaseCanHaveBaseNone(c *C) { 1629 info, err := InfoFromSnapYaml([]byte(`name: foo 1630 version: 1.0 1631 type: base 1632 base: none 1633 `)) 1634 c.Assert(err, IsNil) 1635 c.Assert(Validate(info), IsNil) 1636 } 1637 1638 func (s *ValidateSuite) TestValidateCommonIDs(c *C) { 1639 meta := ` 1640 name: foo 1641 version: 1.0 1642 ` 1643 good := meta + ` 1644 apps: 1645 foo: 1646 common-id: org.foo.foo 1647 bar: 1648 common-id: org.foo.bar 1649 baz: 1650 ` 1651 bad := meta + ` 1652 apps: 1653 foo: 1654 common-id: org.foo.foo 1655 bar: 1656 common-id: org.foo.foo 1657 baz: 1658 ` 1659 for i, tc := range []struct { 1660 meta string 1661 err string 1662 }{ 1663 {good, ""}, 1664 {bad, `application ("bar" common-id "org.foo.foo" must be unique, already used by application "foo"|"foo" common-id "org.foo.foo" must be unique, already used by application "bar")`}, 1665 } { 1666 c.Logf("tc #%v", i) 1667 info, err := InfoFromSnapYaml([]byte(tc.meta)) 1668 c.Assert(err, IsNil) 1669 1670 err = Validate(info) 1671 if tc.err == "" { 1672 c.Assert(err, IsNil) 1673 } else { 1674 c.Assert(err, NotNil) 1675 c.Check(err, ErrorMatches, tc.err) 1676 } 1677 } 1678 } 1679 1680 func (s *validateSuite) TestValidateDescription(c *C) { 1681 for _, s := range []string{ 1682 "xx", // boringest ASCII 1683 "🐧🐧", // len("🐧🐧") == 8 1684 "á", // á (combining) 1685 } { 1686 c.Check(ValidateDescription(s), IsNil) 1687 c.Check(ValidateDescription(strings.Repeat(s, 2049)), ErrorMatches, `description can have up to 4096 codepoints, got 4098`) 1688 c.Check(ValidateDescription(strings.Repeat(s, 2048)), IsNil) 1689 } 1690 } 1691 1692 func (s *validateSuite) TestValidateTitle(c *C) { 1693 for _, s := range []string{ 1694 "xx", // boringest ASCII 1695 "🐧🐧", // len("🐧🐧") == 8 1696 "á", // á (combining) 1697 } { 1698 c.Check(ValidateTitle(strings.Repeat(s, 21)), ErrorMatches, `title can have up to 40 codepoints, got 42`) 1699 c.Check(ValidateTitle(strings.Repeat(s, 20)), IsNil) 1700 } 1701 } 1702 1703 func (s *validateSuite) TestValidatePlugSlotName(c *C) { 1704 validNames := []string{ 1705 "a", "aa", "aaa", "aaaa", 1706 "a-a", "aa-a", "a-aa", "a-b-c", 1707 "a0", "a-0", "a-0a", 1708 } 1709 for _, name := range validNames { 1710 c.Assert(ValidatePlugName(name), IsNil) 1711 c.Assert(ValidateSlotName(name), IsNil) 1712 c.Assert(ValidateInterfaceName(name), IsNil) 1713 } 1714 invalidNames := []string{ 1715 // name cannot be empty 1716 "", 1717 // dashes alone are not a name 1718 "-", "--", 1719 // double dashes in a name are not allowed 1720 "a--a", 1721 // name should not end with a dash 1722 "a-", 1723 // name cannot have any spaces in it 1724 "a ", " a", "a a", 1725 // a number alone is not a name 1726 "0", "123", 1727 // identifier must be plain ASCII 1728 "日本語", "한글", "ру́сский язы́к", 1729 } 1730 for _, name := range invalidNames { 1731 c.Assert(ValidatePlugName(name), ErrorMatches, `invalid plug name: ".*"`) 1732 c.Assert(ValidateSlotName(name), ErrorMatches, `invalid slot name: ".*"`) 1733 c.Assert(ValidateInterfaceName(name), ErrorMatches, `invalid interface name: ".*"`) 1734 } 1735 } 1736 1737 func (s *ValidateSuite) TestValidateSnapInstanceNameBadSnapName(c *C) { 1738 info, err := InfoFromSnapYaml([]byte(`name: foo_bad 1739 version: 1.0 1740 `)) 1741 c.Assert(err, IsNil) 1742 1743 err = Validate(info) 1744 c.Check(err, ErrorMatches, `invalid snap name: "foo_bad"`) 1745 } 1746 1747 func (s *ValidateSuite) TestValidateSnapInstanceNameBadInstanceKey(c *C) { 1748 info, err := InfoFromSnapYaml([]byte(`name: foo 1749 version: 1.0 1750 `)) 1751 c.Assert(err, IsNil) 1752 1753 for _, s := range []string{"toolonginstance", "ABCD", "_", "inst@nce", "012345678901"} { 1754 info.InstanceKey = s 1755 err = Validate(info) 1756 c.Check(err, ErrorMatches, fmt.Sprintf(`invalid instance key: %q`, s)) 1757 } 1758 } 1759 1760 func (s *ValidateSuite) TestValidateAppRestart(c *C) { 1761 meta := []byte(` 1762 name: foo 1763 version: 1.0 1764 `) 1765 fooAllGood := []byte(` 1766 apps: 1767 foo: 1768 daemon: simple 1769 restart-condition: on-abort 1770 restart-delay: 12s 1771 `) 1772 fooAllGoodDefault := []byte(` 1773 apps: 1774 foo: 1775 daemon: simple 1776 `) 1777 fooAllGoodJustDelay := []byte(` 1778 apps: 1779 foo: 1780 daemon: simple 1781 restart-delay: 12s 1782 `) 1783 fooConditionNotADaemon := []byte(` 1784 apps: 1785 foo: 1786 restart-condition: on-abort 1787 `) 1788 fooDelayNotADaemon := []byte(` 1789 apps: 1790 foo: 1791 restart-delay: 12s 1792 `) 1793 fooNegativeDelay := []byte(` 1794 apps: 1795 foo: 1796 daemon: simple 1797 restart-delay: -12s 1798 `) 1799 1800 tcs := []struct { 1801 name string 1802 desc []byte 1803 err string 1804 }{{ 1805 name: "foo all good", 1806 desc: fooAllGood, 1807 }, { 1808 name: "foo all good with default values", 1809 desc: fooAllGoodDefault, 1810 }, { 1811 name: "foo all good with restart-delay only", 1812 desc: fooAllGoodJustDelay, 1813 }, { 1814 name: "foo restart-delay but not a service", 1815 desc: fooDelayNotADaemon, 1816 err: `restart-delay is only applicable to services`, 1817 }, { 1818 name: "foo restart-delay but not a service", 1819 desc: fooConditionNotADaemon, 1820 err: `restart-condition is only applicable to services`, 1821 }, { 1822 name: "negative restart-delay", 1823 desc: fooNegativeDelay, 1824 err: `restart-delay cannot be negative`, 1825 }} 1826 for _, tc := range tcs { 1827 c.Logf("trying %q", tc.name) 1828 info, err := InfoFromSnapYaml(append(meta, tc.desc...)) 1829 c.Assert(err, IsNil) 1830 c.Assert(info, NotNil) 1831 1832 err = Validate(info) 1833 if tc.err != "" { 1834 c.Assert(err, ErrorMatches, `invalid definition of application "foo": `+tc.err) 1835 } else { 1836 c.Assert(err, IsNil) 1837 } 1838 } 1839 } 1840 1841 func (s *ValidateSuite) TestValidateSystemUsernames(c *C) { 1842 const yaml1 = `name: binary 1843 version: 1.0 1844 system-usernames: 1845 "b@d": shared 1846 ` 1847 1848 strk := NewScopedTracker() 1849 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml1), nil, strk) 1850 c.Assert(err, IsNil) 1851 c.Assert(info.SystemUsernames, HasLen, 1) 1852 err = Validate(info) 1853 c.Assert(err, ErrorMatches, `invalid system username "b@d"`) 1854 } 1855 1856 const yamlNeedDf = `name: need-df 1857 version: 1.0 1858 plugs: 1859 gtk-3-themes: 1860 interface: content 1861 content: gtk-3-themes 1862 default-provider: gtk-common-themes 1863 icon-themes: 1864 interface: content 1865 content: icon-themes 1866 default-provider: gtk-common-themes 1867 1868 ` 1869 1870 func (s *ValidateSuite) TestNeededDefaultProviders(c *C) { 1871 strk := NewScopedTracker() 1872 info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk) 1873 c.Assert(err, IsNil) 1874 1875 dps := NeededDefaultProviders(info) 1876 c.Check(dps, DeepEquals, map[string][]string{"gtk-common-themes": {"gtk-3-themes", "icon-themes"}}) 1877 } 1878 1879 const yamlNeedDfWithSlot = `name: need-df 1880 version: 1.0 1881 plugs: 1882 gtk-3-themes: 1883 interface: content 1884 default-provider: gtk-common-themes2:with-slot 1885 ` 1886 1887 func (s *ValidateSuite) TestNeededDefaultProvidersLegacyColonSyntax(c *C) { 1888 strk := NewScopedTracker() 1889 info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDfWithSlot), nil, strk) 1890 c.Assert(err, IsNil) 1891 1892 dps := NeededDefaultProviders(info) 1893 c.Check(dps, DeepEquals, map[string][]string{"gtk-common-themes2": {""}}) 1894 } 1895 1896 func (s *validateSuite) TestValidateSnapMissingCore(c *C) { 1897 const yaml = `name: some-snap 1898 version: 1.0` 1899 1900 strk := NewScopedTracker() 1901 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 1902 c.Assert(err, IsNil) 1903 1904 infos := []*Info{info} 1905 errors := ValidateBasesAndProviders(infos) 1906 c.Assert(errors, HasLen, 1) 1907 c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": required snap "core" missing`) 1908 } 1909 1910 func (s *validateSuite) TestValidateSnapMissingBase(c *C) { 1911 const yaml = `name: some-snap 1912 base: some-base 1913 version: 1.0` 1914 1915 strk := NewScopedTracker() 1916 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 1917 c.Assert(err, IsNil) 1918 1919 infos := []*Info{info} 1920 errors := ValidateBasesAndProviders(infos) 1921 c.Assert(errors, HasLen, 1) 1922 c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": base "some-base" is missing`) 1923 } 1924 1925 func (s *validateSuite) TestValidateSnapMissingDefaultProvider(c *C) { 1926 strk := NewScopedTracker() 1927 snapInfo, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk) 1928 c.Assert(err, IsNil) 1929 1930 var coreYaml = `name: core 1931 version: 1.0 1932 type: os` 1933 1934 coreInfo, err := InfoFromSnapYamlWithSideInfo([]byte(coreYaml), nil, strk) 1935 c.Assert(err, IsNil) 1936 1937 infos := []*Info{snapInfo, coreInfo} 1938 errors := ValidateBasesAndProviders(infos) 1939 c.Assert(errors, HasLen, 1) 1940 c.Assert(errors[0], ErrorMatches, `cannot use snap "need-df": default provider "gtk-common-themes" is missing`) 1941 } 1942 1943 func (s *validateSuite) TestValidateSnapBaseNoneOK(c *C) { 1944 const yaml = `name: some-snap 1945 base: none 1946 version: 1.0` 1947 1948 strk := NewScopedTracker() 1949 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 1950 c.Assert(err, IsNil) 1951 1952 infos := []*Info{info} 1953 errors := ValidateBasesAndProviders(infos) 1954 c.Assert(errors, IsNil) 1955 } 1956 1957 func (s *validateSuite) TestValidateSnapSnapd(c *C) { 1958 const yaml = `name: snapd 1959 type: snapd 1960 version: 1.0` 1961 1962 strk := NewScopedTracker() 1963 info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk) 1964 c.Assert(err, IsNil) 1965 1966 infos := []*Info{info} 1967 errors := ValidateBasesAndProviders(infos) 1968 c.Assert(errors, IsNil) 1969 } 1970 1971 func (s *validateSuite) TestValidateDesktopPrefix(c *C) { 1972 // these are extensively tested elsewhere, so just try some common ones 1973 for i, tc := range []struct { 1974 prefix string 1975 exp bool 1976 }{ 1977 {"good", true}, 1978 {"also-good", true}, 1979 {"also-good+instance", true}, 1980 {"", false}, 1981 {"+", false}, 1982 {"@", false}, 1983 {"+good", false}, 1984 {"good+", false}, 1985 {"good+@", false}, 1986 {"old-style_instance", false}, 1987 {"bad+bad+bad", false}, 1988 } { 1989 c.Logf("tc #%v", i) 1990 res := ValidateDesktopPrefix(tc.prefix) 1991 c.Check(res, Equals, tc.exp) 1992 } 1993 } 1994 1995 func (s *ValidateSuite) TestAppInstallMode(c *C) { 1996 // check services 1997 for _, t := range []struct { 1998 installMode string 1999 ok bool 2000 }{ 2001 // good 2002 {"", true}, 2003 {"disable", true}, 2004 {"enable", true}, 2005 // bad 2006 {"invalid-thing", false}, 2007 } { 2008 err := ValidateApp(&AppInfo{Name: "foo", Daemon: "simple", DaemonScope: SystemDaemon, InstallMode: t.installMode}) 2009 if t.ok { 2010 c.Check(err, IsNil) 2011 } else { 2012 c.Check(err, ErrorMatches, fmt.Sprintf(`"install-mode" field contains invalid value %q`, t.installMode)) 2013 } 2014 } 2015 2016 // non-services cannot have a install-mode 2017 err := ValidateApp(&AppInfo{Name: "foo", Daemon: "", InstallMode: "disable"}) 2018 c.Check(err, ErrorMatches, `"install-mode" cannot be used for "foo", only for services`) 2019 }