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