detect_test.go 23 KB

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