detect_test.go 23 KB

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