github.com/crowdsecurity/crowdsec@v1.6.1/pkg/setup/detect_test.go (about) 1 package setup_test 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "runtime" 8 "testing" 9 10 "github.com/lithammer/dedent" 11 "github.com/stretchr/testify/require" 12 13 "github.com/crowdsecurity/go-cs-lib/cstest" 14 15 "github.com/crowdsecurity/crowdsec/pkg/setup" 16 ) 17 18 //nolint:dupword 19 var fakeSystemctlOutput = `UNIT FILE STATE VENDOR PRESET 20 crowdsec-setup-detect.service enabled enabled 21 apache2.service enabled enabled 22 apparmor.service enabled enabled 23 apport.service enabled enabled 24 atop.service enabled enabled 25 atopacct.service enabled enabled 26 finalrd.service enabled enabled 27 fwupd-refresh.service enabled enabled 28 fwupd.service enabled enabled 29 30 9 unit files listed.` 31 32 func fakeExecCommandNotFound(command string, args ...string) *exec.Cmd { 33 cs := []string{"-test.run=TestSetupHelperProcess", "--", command} 34 cs = append(cs, args...) 35 cmd := exec.Command("this-command-does-not-exist", cs...) 36 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 37 38 return cmd 39 } 40 41 func fakeExecCommand(command string, args ...string) *exec.Cmd { 42 cs := []string{"-test.run=TestSetupHelperProcess", "--", command} 43 cs = append(cs, args...) 44 //nolint:gosec 45 cmd := exec.Command(os.Args[0], cs...) 46 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 47 48 return cmd 49 } 50 51 func TestSetupHelperProcess(t *testing.T) { 52 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 53 return 54 } 55 56 fmt.Fprint(os.Stdout, fakeSystemctlOutput) 57 os.Exit(0) 58 } 59 60 func tempYAML(t *testing.T, content string) os.File { 61 t.Helper() 62 require := require.New(t) 63 file, err := os.CreateTemp("", "") 64 require.NoError(err) 65 66 _, err = file.WriteString(dedent.Dedent(content)) 67 require.NoError(err) 68 69 err = file.Close() 70 require.NoError(err) 71 72 file, err = os.Open(file.Name()) 73 require.NoError(err) 74 75 return *file 76 } 77 78 func TestPathExists(t *testing.T) { 79 t.Parallel() 80 81 type test struct { 82 path string 83 expected bool 84 } 85 86 tests := []test{ 87 {"/this-should-not-exist", false}, 88 } 89 90 if runtime.GOOS == "windows" { 91 tests = append(tests, test{`C:\`, true}) 92 } else { 93 tests = append(tests, test{"/tmp", true}) 94 } 95 96 for _, tc := range tests { 97 tc := tc 98 env := setup.NewExprEnvironment(setup.DetectOptions{}, setup.ExprOS{}) 99 100 t.Run(tc.path, func(t *testing.T) { 101 t.Parallel() 102 actual := env.PathExists(tc.path) 103 require.Equal(t, tc.expected, actual) 104 }) 105 } 106 } 107 108 func TestVersionCheck(t *testing.T) { 109 t.Parallel() 110 111 tests := []struct { 112 version string 113 constraint string 114 expected bool 115 expectedErr string 116 }{ 117 {"1", "=1", true, ""}, 118 {"1", "!=1", false, ""}, 119 {"1", "<=1", true, ""}, 120 {"1", ">1", false, ""}, 121 {"1", ">=1", true, ""}, 122 {"1.0", "<1.0", false, ""}, 123 {"1", "<1", false, ""}, 124 {"1.3.5", "1.3", true, ""}, 125 {"1.0", "<1.0", false, ""}, 126 {"1.0", "<=1.0", true, ""}, 127 {"2", ">1, <3", true, ""}, 128 {"2", "<=2, >=2.2", false, ""}, 129 {"2.3", "~2", true, ""}, 130 {"2.3", "=2", true, ""}, 131 {"1.1.1", "=1.1", true, ""}, 132 {"1.1.1", "1.1", true, ""}, 133 {"1.1", "!=1.1.1", true, ""}, 134 {"1.1", "~1.1.1", false, ""}, 135 {"1.1.1", "~1.1", true, ""}, 136 {"1.1.3", "~1.1", true, ""}, 137 {"19.04", "<19.10", true, ""}, 138 {"19.04", ">=19.10", false, ""}, 139 {"19.04", "=19.4", true, ""}, 140 {"19.04", "~19.4", true, ""}, 141 {"1.2.3", "~1.2", true, ""}, 142 {"1.2.3", "!=1.2", false, ""}, 143 {"1.2.3", "1.1.1 - 1.3.4", true, ""}, 144 {"1.3.5", "1.1.1 - 1.3.4", false, ""}, 145 {"1.3.5", "=1", true, ""}, 146 {"1.3.5", "1", true, ""}, 147 } 148 149 for _, tc := range tests { 150 tc := tc 151 e := setup.ExprOS{RawVersion: tc.version} 152 153 t.Run(fmt.Sprintf("Check(%s,%s)", tc.version, tc.constraint), func(t *testing.T) { 154 t.Parallel() 155 actual, err := e.VersionCheck(tc.constraint) 156 cstest.RequireErrorContains(t, err, tc.expectedErr) 157 require.Equal(t, tc.expected, actual) 158 }) 159 } 160 } 161 162 // This is not required for Masterminds/semver 163 /* 164 func TestNormalizeVersion(t *testing.T) { 165 t.Parallel() 166 167 tests := []struct { 168 version string 169 expected string 170 }{ 171 {"0", "0"}, 172 {"2", "2"}, 173 {"3.14", "3.14"}, 174 {"1.0", "1.0"}, 175 {"18.04", "18.4"}, 176 {"0.0.0", "0.0.0"}, 177 {"18.04.0", "18.4.0"}, 178 {"18.0004.0", "18.4.0"}, 179 {"21.04.2", "21.4.2"}, 180 {"050", "50"}, 181 {"trololo", "trololo"}, 182 {"0001.002.03", "1.2.3"}, 183 {"0001.002.03-trololo", "0001.002.03-trololo"}, 184 } 185 186 for _, tc := range tests { 187 tc := tc 188 t.Run(tc.version, func(t *testing.T) { 189 t.Parallel() 190 actual := setup.NormalizeVersion(tc.version) 191 require.Equal(t, tc.expected, actual) 192 }) 193 } 194 } 195 */ 196 197 func TestListSupported(t *testing.T) { 198 t.Parallel() 199 200 tests := []struct { 201 name string 202 yml string 203 expected []string 204 expectedErr string 205 }{ 206 { 207 "list configured services", 208 ` 209 version: 1.0 210 detect: 211 foo: 212 bar: 213 baz: 214 `, 215 []string{"foo", "bar", "baz"}, 216 "", 217 }, 218 { 219 "invalid yaml: blahblah", 220 "blahblah", 221 nil, 222 "yaml: unmarshal errors:", 223 }, 224 { 225 "invalid yaml: tabs are not allowed", 226 ` 227 version: 1.0 228 detect: 229 foos: 230 `, 231 nil, 232 "yaml: line 4: found character that cannot start any token", 233 }, 234 { 235 "invalid yaml: no version", 236 "{}", 237 nil, 238 "missing version tag (must be 1.0)", 239 }, 240 { 241 "invalid yaml: bad version", 242 "version: 2.0", 243 nil, 244 "invalid version tag '2.0' (must be 1.0)", 245 }, 246 } 247 248 for _, tc := range tests { 249 tc := tc 250 t.Run(tc.name, func(t *testing.T) { 251 t.Parallel() 252 f := tempYAML(t, tc.yml) 253 defer os.Remove(f.Name()) 254 supported, err := setup.ListSupported(&f) 255 cstest.RequireErrorContains(t, err, tc.expectedErr) 256 require.ElementsMatch(t, tc.expected, supported) 257 }) 258 } 259 } 260 261 func TestApplyRules(t *testing.T) { 262 t.Parallel() 263 require := require.New(t) 264 265 tests := []struct { 266 name string 267 rules []string 268 expectedOk bool 269 expectedErr string 270 }{ 271 { 272 "empty list is always true", // XXX or false? 273 []string{}, 274 true, 275 "", 276 }, 277 { 278 "simple true expression", 279 []string{"1+1==2"}, 280 true, 281 "", 282 }, 283 { 284 "simple false expression", 285 []string{"2+2==5"}, 286 false, 287 "", 288 }, 289 { 290 "all expressions are true", 291 []string{"1+2==3", "1!=2"}, 292 true, 293 "", 294 }, 295 { 296 "all expressions must be true", 297 []string{"true", "1==3", "1!=2"}, 298 false, 299 "", 300 }, 301 { 302 "each expression must be a boolan", 303 []string{"true", "\"notabool\""}, 304 false, 305 "rule '\"notabool\"': type must be a boolean", 306 }, 307 { 308 // we keep evaluating expressions to ensure that the 309 // file is formally correct, even if it can some time. 310 "each expression must be a boolan (no short circuit)", 311 []string{"false", "3"}, 312 false, 313 "rule '3': type must be a boolean", 314 }, 315 { 316 "unknown variable", 317 []string{"false", "doesnotexist"}, 318 false, 319 "rule 'doesnotexist': cannot fetch doesnotexist from", 320 }, 321 { 322 "unknown expression", 323 []string{"false", "doesnotexist()"}, 324 false, 325 "rule 'doesnotexist()': cannot fetch doesnotexist from", 326 }, 327 } 328 329 env := setup.ExprEnvironment{} 330 331 for _, tc := range tests { 332 tc := tc 333 t.Run(tc.name, func(t *testing.T) { 334 t.Parallel() 335 svc := setup.Service{When: tc.rules} 336 _, actualOk, err := setup.ApplyRules(svc, env) //nolint:typecheck,nolintlint // exported only for tests 337 cstest.RequireErrorContains(t, err, tc.expectedErr) 338 require.Equal(tc.expectedOk, actualOk) 339 }) 340 } 341 } 342 343 // XXX TODO: TestApplyRules with journalctl default 344 345 func TestUnitFound(t *testing.T) { 346 require := require.New(t) 347 setup.ExecCommand = fakeExecCommand 348 349 defer func() { setup.ExecCommand = exec.Command }() 350 351 env := setup.NewExprEnvironment(setup.DetectOptions{}, setup.ExprOS{}) 352 353 installed, err := env.UnitFound("crowdsec-setup-detect.service") 354 require.NoError(err) 355 356 require.True(installed) 357 } 358 359 // TODO apply rules to filter a list of Service structs 360 // func testFilterWithRules(t *testing.T) { 361 // } 362 363 func TestDetectSimpleRule(t *testing.T) { 364 require := require.New(t) 365 setup.ExecCommand = fakeExecCommand 366 367 f := tempYAML(t, ` 368 version: 1.0 369 detect: 370 good: 371 when: 372 - true 373 bad: 374 when: 375 - false 376 ugly: 377 `) 378 defer os.Remove(f.Name()) 379 380 detected, err := setup.Detect(&f, setup.DetectOptions{}) 381 require.NoError(err) 382 383 expected := []setup.ServiceSetup{ 384 {DetectedService: "good"}, 385 {DetectedService: "ugly"}, 386 } 387 388 require.ElementsMatch(expected, detected.Setup) 389 } 390 391 func TestDetectUnitError(t *testing.T) { 392 if runtime.GOOS == "windows" { 393 t.Skip("skipping on windows") 394 } 395 396 require := require.New(t) 397 setup.ExecCommand = fakeExecCommandNotFound 398 399 defer func() { setup.ExecCommand = exec.Command }() 400 401 tests := []struct { 402 name string 403 config string 404 expected setup.Setup 405 expectedErr string 406 }{ 407 { 408 "error is reported if systemctl does not exist", 409 ` 410 version: 1.0 411 detect: 412 wizard: 413 when: 414 - UnitFound("crowdsec-setup-detect.service")`, 415 setup.Setup{[]setup.ServiceSetup{}}, 416 `while looking for service wizard: rule 'UnitFound("crowdsec-setup-detect.service")': ` + 417 `running systemctl: exec: "this-command-does-not-exist": executable file not found in $PATH`, 418 }, 419 } 420 421 for _, tc := range tests { 422 tc := tc 423 t.Run(tc.name, func(t *testing.T) { 424 f := tempYAML(t, tc.config) 425 defer os.Remove(f.Name()) 426 427 detected, err := setup.Detect(&f, setup.DetectOptions{}) 428 cstest.RequireErrorContains(t, err, tc.expectedErr) 429 require.Equal(tc.expected, detected) 430 }) 431 } 432 } 433 434 func TestDetectUnit(t *testing.T) { 435 require := require.New(t) 436 setup.ExecCommand = fakeExecCommand 437 438 defer func() { setup.ExecCommand = exec.Command }() 439 440 tests := []struct { 441 name string 442 config string 443 expected setup.Setup 444 expectedErr string 445 }{ 446 // { 447 // "detect a single unit, with default log filter", 448 // ` 449 // version: 1.0 450 // detect: 451 // wizard: 452 // when: 453 // - UnitFound("crowdsec-setup-detect.service") 454 // datasource: 455 // labels: 456 // type: syslog 457 // sorcerer: 458 // when: 459 // - UnitFound("sorcerer.service")`, 460 // setup.Setup{ 461 // Setup: []setup.ServiceSetup{ 462 // { 463 // DetectedService: "wizard", 464 // DataSource: setup.DataSourceItem{ 465 // "Labels": map[string]string{"type": "syslog"}, 466 // "JournalCTLFilter": []string{"_SYSTEMD_UNIT=crowdsec-setup-detect.service"}, 467 // }, 468 // }, 469 // }, 470 // }, 471 // "", 472 // }, 473 // { 474 // "detect a single unit, but type label is missing", 475 // ` 476 // version: 1.0 477 // detect: 478 // wizard: 479 // when: 480 // - UnitFound("crowdsec-setup-detect.service")`, 481 // setup.Setup{}, 482 // "missing type label for service wizard", 483 // }, 484 { 485 "detect unit and pick up acquisistion filter", 486 ` 487 version: 1.0 488 detect: 489 wizard: 490 when: 491 - UnitFound("crowdsec-setup-detect.service") 492 datasource: 493 source: journalctl 494 labels: 495 type: syslog 496 journalctl_filter: 497 - _MY_CUSTOM_FILTER=something`, 498 setup.Setup{ 499 Setup: []setup.ServiceSetup{ 500 { 501 DetectedService: "wizard", 502 DataSource: setup.DataSourceItem{ 503 // XXX this should not be DataSourceItem ?? 504 "source": "journalctl", 505 "labels": setup.DataSourceItem{"type": "syslog"}, 506 "journalctl_filter": []interface{}{"_MY_CUSTOM_FILTER=something"}, 507 }, 508 }, 509 }, 510 }, 511 "", 512 }, 513 } 514 515 for _, tc := range tests { 516 tc := tc 517 t.Run(tc.name, func(t *testing.T) { 518 f := tempYAML(t, tc.config) 519 defer os.Remove(f.Name()) 520 521 detected, err := setup.Detect(&f, setup.DetectOptions{}) 522 cstest.RequireErrorContains(t, err, tc.expectedErr) 523 require.Equal(tc.expected, detected) 524 }) 525 } 526 } 527 528 func TestDetectForcedUnit(t *testing.T) { 529 require := require.New(t) 530 setup.ExecCommand = fakeExecCommand 531 532 defer func() { setup.ExecCommand = exec.Command }() 533 534 f := tempYAML(t, ` 535 version: 1.0 536 detect: 537 wizard: 538 when: 539 - UnitFound("crowdsec-setup-forced.service") 540 datasource: 541 source: journalctl 542 labels: 543 type: syslog 544 journalctl_filter: 545 - _SYSTEMD_UNIT=crowdsec-setup-forced.service 546 `) 547 defer os.Remove(f.Name()) 548 549 detected, err := setup.Detect(&f, setup.DetectOptions{ForcedUnits: []string{"crowdsec-setup-forced.service"}}) 550 require.NoError(err) 551 552 expected := setup.Setup{ 553 Setup: []setup.ServiceSetup{ 554 { 555 DetectedService: "wizard", 556 DataSource: setup.DataSourceItem{ 557 "source": "journalctl", 558 "labels": setup.DataSourceItem{"type": "syslog"}, 559 "journalctl_filter": []interface{}{"_SYSTEMD_UNIT=crowdsec-setup-forced.service"}, 560 }, 561 }, 562 }, 563 } 564 require.Equal(expected, detected) 565 } 566 567 func TestDetectForcedProcess(t *testing.T) { 568 if runtime.GOOS == "windows" { 569 // while looking for service wizard: rule 'ProcessRunning("foobar")': while looking up running processes: could not get Name: A device attached to the system is not functioning. 570 t.Skip("skipping on windows") 571 } 572 573 require := require.New(t) 574 setup.ExecCommand = fakeExecCommand 575 576 defer func() { setup.ExecCommand = exec.Command }() 577 578 f := tempYAML(t, ` 579 version: 1.0 580 detect: 581 wizard: 582 when: 583 - ProcessRunning("foobar") 584 `) 585 defer os.Remove(f.Name()) 586 587 detected, err := setup.Detect(&f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}}) 588 require.NoError(err) 589 590 expected := setup.Setup{ 591 Setup: []setup.ServiceSetup{ 592 {DetectedService: "wizard"}, 593 }, 594 } 595 require.Equal(expected, detected) 596 } 597 598 func TestDetectSkipService(t *testing.T) { 599 if runtime.GOOS == "windows" { 600 t.Skip("skipping on windows") 601 } 602 603 require := require.New(t) 604 setup.ExecCommand = fakeExecCommand 605 606 defer func() { setup.ExecCommand = exec.Command }() 607 608 f := tempYAML(t, ` 609 version: 1.0 610 detect: 611 wizard: 612 when: 613 - ProcessRunning("foobar") 614 `) 615 defer os.Remove(f.Name()) 616 617 detected, err := setup.Detect(&f, setup.DetectOptions{ForcedProcesses: []string{"foobar"}, SkipServices: []string{"wizard"}}) 618 require.NoError(err) 619 620 expected := setup.Setup{[]setup.ServiceSetup{}} 621 require.Equal(expected, detected) 622 } 623 624 func TestDetectForcedOS(t *testing.T) { 625 require := require.New(t) 626 setup.ExecCommand = fakeExecCommand 627 628 defer func() { setup.ExecCommand = exec.Command }() 629 630 type test struct { 631 name string 632 config string 633 forced setup.ExprOS 634 expected setup.Setup 635 expectedErr string 636 } 637 638 tests := []test{ 639 { 640 "detect OS - force linux", 641 ` 642 version: 1.0 643 detect: 644 linux: 645 when: 646 - OS.Family == "linux"`, 647 setup.ExprOS{Family: "linux"}, 648 setup.Setup{ 649 Setup: []setup.ServiceSetup{ 650 {DetectedService: "linux"}, 651 }, 652 }, 653 "", 654 }, 655 { 656 "detect OS - force windows", 657 ` 658 version: 1.0 659 detect: 660 windows: 661 when: 662 - OS.Family == "windows"`, 663 setup.ExprOS{Family: "windows"}, 664 setup.Setup{ 665 Setup: []setup.ServiceSetup{ 666 {DetectedService: "windows"}, 667 }, 668 }, 669 "", 670 }, 671 { 672 "detect OS - ubuntu (no match)", 673 ` 674 version: 1.0 675 detect: 676 linux: 677 when: 678 - OS.Family == "linux" && OS.ID == "ubuntu"`, 679 setup.ExprOS{Family: "linux"}, 680 setup.Setup{[]setup.ServiceSetup{}}, 681 "", 682 }, 683 { 684 "detect OS - ubuntu (match)", 685 ` 686 version: 1.0 687 detect: 688 linux: 689 when: 690 - OS.Family == "linux" && OS.ID == "ubuntu"`, 691 setup.ExprOS{Family: "linux", ID: "ubuntu"}, 692 setup.Setup{ 693 Setup: []setup.ServiceSetup{ 694 {DetectedService: "linux"}, 695 }, 696 }, 697 "", 698 }, 699 { 700 "detect OS - ubuntu (match with version)", 701 ` 702 version: 1.0 703 detect: 704 linux: 705 when: 706 - OS.Family == "linux" && OS.ID == "ubuntu" && OS.VersionCheck("19.04")`, 707 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.04"}, 708 setup.Setup{ 709 Setup: []setup.ServiceSetup{ 710 {DetectedService: "linux"}, 711 }, 712 }, 713 "", 714 }, 715 { 716 "detect OS - ubuntu >= 20.04 (no match: no version detected)", 717 ` 718 version: 1.0 719 detect: 720 linux: 721 when: 722 - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`, 723 setup.ExprOS{Family: "linux"}, 724 setup.Setup{[]setup.ServiceSetup{}}, 725 "", 726 }, 727 { 728 "detect OS - ubuntu >= 20.04 (no match: version is lower)", 729 ` 730 version: 1.0 731 detect: 732 linux: 733 when: 734 - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`, 735 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.10"}, 736 setup.Setup{[]setup.ServiceSetup{}}, 737 "", 738 }, 739 { 740 "detect OS - ubuntu >= 20.04 (match: same version)", 741 ` 742 version: 1.0 743 detect: 744 linux: 745 when: 746 - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`, 747 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.04"}, 748 setup.Setup{ 749 Setup: []setup.ServiceSetup{ 750 {DetectedService: "linux"}, 751 }, 752 }, 753 "", 754 }, 755 { 756 "detect OS - ubuntu >= 20.04 (match: version is higher)", 757 ` 758 version: 1.0 759 detect: 760 linux: 761 when: 762 - OS.ID == "ubuntu" && OS.VersionCheck(">=20.04")`, 763 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "22.04"}, 764 setup.Setup{ 765 Setup: []setup.ServiceSetup{ 766 {DetectedService: "linux"}, 767 }, 768 }, 769 "", 770 }, 771 772 { 773 "detect OS - ubuntu < 20.04 (no match: no version detected)", 774 ` 775 version: 1.0 776 detect: 777 linux: 778 when: 779 - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`, 780 setup.ExprOS{Family: "linux"}, 781 setup.Setup{[]setup.ServiceSetup{}}, 782 "", 783 }, 784 { 785 "detect OS - ubuntu < 20.04 (no match: version is higher)", 786 ` 787 version: 1.0 788 detect: 789 linux: 790 when: 791 - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`, 792 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.10"}, 793 setup.Setup{[]setup.ServiceSetup{}}, 794 "", 795 }, 796 { 797 "detect OS - ubuntu < 20.04 (no match: same version)", 798 ` 799 version: 1.0 800 detect: 801 linux: 802 when: 803 - OS.ID == "ubuntu" && OS.VersionCheck("<20.04")`, 804 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "20.04"}, 805 setup.Setup{[]setup.ServiceSetup{}}, 806 "", 807 }, 808 { 809 "detect OS - ubuntu < 20.04 (match: version is lower)", 810 ` 811 version: 1.0 812 detect: 813 linux: 814 when: 815 - OS.ID == "ubuntu" 816 - OS.VersionCheck("<20.04")`, 817 setup.ExprOS{Family: "linux", ID: "ubuntu", RawVersion: "19.10"}, 818 setup.Setup{ 819 Setup: []setup.ServiceSetup{ 820 {DetectedService: "linux"}, 821 }, 822 }, 823 "", 824 }, 825 } 826 827 for _, tc := range tests { 828 tc := tc 829 t.Run(tc.name, func(t *testing.T) { 830 f := tempYAML(t, tc.config) 831 defer os.Remove(f.Name()) 832 833 detected, err := setup.Detect(&f, setup.DetectOptions{ForcedOS: tc.forced}) 834 cstest.RequireErrorContains(t, err, tc.expectedErr) 835 require.Equal(tc.expected, detected) 836 }) 837 } 838 } 839 840 func TestDetectDatasourceValidation(t *testing.T) { 841 // It could be a good idea to test UnmarshalConfig() separately in addition 842 // to Configure(), in each datasource. For now, we test these here. 843 844 require := require.New(t) 845 setup.ExecCommand = fakeExecCommand 846 847 defer func() { setup.ExecCommand = exec.Command }() 848 849 type test struct { 850 name string 851 config string 852 expected setup.Setup 853 expectedErr string 854 } 855 856 tests := []test{ 857 { 858 name: "source is empty", 859 config: ` 860 version: 1.0 861 detect: 862 wizard: 863 datasource: 864 labels: 865 type: something`, 866 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 867 expectedErr: "invalid datasource for wizard: source is empty", 868 }, { 869 name: "source is unknown", 870 config: ` 871 version: 1.0 872 detect: 873 foobar: 874 datasource: 875 source: wombat`, 876 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 877 expectedErr: "invalid datasource for foobar: unknown source 'wombat'", 878 }, { 879 name: "source is misplaced", 880 config: ` 881 version: 1.0 882 detect: 883 foobar: 884 datasource: 885 source: file`, 886 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 887 expectedErr: "yaml: unmarshal errors:\n line 6: field source not found in type setup.Service", 888 }, { 889 name: "source is mismatched", 890 config: ` 891 version: 1.0 892 detect: 893 foobar: 894 datasource: 895 source: journalctl 896 filename: /path/to/file.log`, 897 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 898 expectedErr: "invalid datasource for foobar: cannot parse JournalCtlSource configuration: yaml: unmarshal errors:\n line 1: field filename not found in type journalctlacquisition.JournalCtlConfiguration", 899 }, { 900 name: "source file: required fields", 901 config: ` 902 version: 1.0 903 detect: 904 foobar: 905 datasource: 906 source: file`, 907 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 908 expectedErr: "invalid datasource for foobar: no filename or filenames configuration provided", 909 }, { 910 name: "source journalctl: required fields", 911 config: ` 912 version: 1.0 913 detect: 914 foobar: 915 datasource: 916 source: journalctl`, 917 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 918 expectedErr: "invalid datasource for foobar: journalctl_filter is required", 919 }, { 920 name: "source cloudwatch: required fields", 921 config: ` 922 version: 1.0 923 detect: 924 foobar: 925 datasource: 926 source: cloudwatch`, 927 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 928 expectedErr: "invalid datasource for foobar: group_name is mandatory for CloudwatchSource", 929 }, { 930 name: "source syslog: all fields are optional", 931 config: ` 932 version: 1.0 933 detect: 934 foobar: 935 datasource: 936 source: syslog`, 937 expected: setup.Setup{ 938 Setup: []setup.ServiceSetup{ 939 { 940 DetectedService: "foobar", 941 DataSource: setup.DataSourceItem{"source": "syslog"}, 942 }, 943 }, 944 }, 945 }, { 946 name: "source docker: required fields", 947 config: ` 948 version: 1.0 949 detect: 950 foobar: 951 datasource: 952 source: docker`, 953 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 954 expectedErr: "invalid datasource for foobar: no containers names or containers ID configuration provided", 955 }, { 956 name: "source kinesis: required fields (enhanced fanout=false)", 957 config: ` 958 version: 1.0 959 detect: 960 foobar: 961 datasource: 962 source: kinesis`, 963 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 964 expectedErr: "invalid datasource for foobar: stream_name is mandatory when use_enhanced_fanout is false", 965 }, { 966 name: "source kinesis: required fields (enhanced fanout=true)", 967 config: ` 968 version: 1.0 969 detect: 970 foobar: 971 datasource: 972 source: kinesis 973 use_enhanced_fanout: true`, 974 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 975 expectedErr: "invalid datasource for foobar: stream_arn is mandatory when use_enhanced_fanout is true", 976 }, { 977 name: "source kafka: required fields", 978 config: ` 979 version: 1.0 980 detect: 981 foobar: 982 datasource: 983 source: kafka`, 984 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 985 expectedErr: "invalid datasource for foobar: cannot create a kafka reader with an empty list of broker addresses", 986 }, { 987 name: "source loki: required fields", 988 config: ` 989 version: 1.0 990 detect: 991 foobar: 992 datasource: 993 source: loki`, 994 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 995 expectedErr: "invalid datasource for foobar: loki query is mandatory", 996 }, 997 } 998 999 if runtime.GOOS == "windows" { 1000 tests = append(tests, test{ 1001 name: "source wineventlog: required fields", 1002 config: ` 1003 version: 1.0 1004 detect: 1005 foobar: 1006 datasource: 1007 source: wineventlog`, 1008 expected: setup.Setup{Setup: []setup.ServiceSetup{}}, 1009 expectedErr: "invalid datasource for foobar: event_channel or xpath_query must be set", 1010 }) 1011 } 1012 1013 for _, tc := range tests { 1014 tc := tc 1015 t.Run(tc.name, func(t *testing.T) { 1016 f := tempYAML(t, tc.config) 1017 defer os.Remove(f.Name()) 1018 detected, err := setup.Detect(&f, setup.DetectOptions{}) 1019 cstest.RequireErrorContains(t, err, tc.expectedErr) 1020 require.Equal(tc.expected, detected) 1021 }) 1022 } 1023 }