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