docker_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. package dockeracquisition
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strings"
  8. "testing"
  9. "time"
  10. "github.com/crowdsecurity/crowdsec/pkg/types"
  11. dockerTypes "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/client"
  13. log "github.com/sirupsen/logrus"
  14. "gopkg.in/tomb.v2"
  15. "github.com/stretchr/testify/assert"
  16. )
  17. const testContainerName = "docker_test"
  18. func TestConfigure(t *testing.T) {
  19. log.Infof("Test 'TestConfigure'")
  20. tests := []struct {
  21. config string
  22. expectedErr string
  23. }{
  24. {
  25. config: `foobar: asd`,
  26. expectedErr: "line 1: field foobar not found in type dockeracquisition.DockerConfiguration",
  27. },
  28. {
  29. config: `
  30. mode: tail
  31. source: docker`,
  32. expectedErr: "no containers names or containers ID configuration provided",
  33. },
  34. {
  35. config: `
  36. mode: cat
  37. source: docker
  38. container_name:
  39. - toto`,
  40. expectedErr: "",
  41. },
  42. }
  43. subLogger := log.WithFields(log.Fields{
  44. "type": "docker",
  45. })
  46. for _, test := range tests {
  47. f := DockerSource{}
  48. err := f.Configure([]byte(test.config), subLogger)
  49. if test.expectedErr != "" && err == nil {
  50. t.Fatalf("Expected err %s but got nil !", test.expectedErr)
  51. }
  52. if test.expectedErr != "" {
  53. assert.Contains(t, err.Error(), test.expectedErr)
  54. }
  55. }
  56. }
  57. func TestConfigureDSN(t *testing.T) {
  58. log.Infof("Test 'TestConfigureDSN'")
  59. tests := []struct {
  60. name string
  61. dsn string
  62. expectedErr string
  63. }{
  64. {
  65. name: "invalid DSN",
  66. dsn: "asd://",
  67. expectedErr: "invalid DSN asd:// for docker source, must start with docker://",
  68. },
  69. {
  70. name: "empty DSN",
  71. dsn: "docker://",
  72. expectedErr: "empty docker:// DSN",
  73. },
  74. {
  75. name: "DSN ok with log_level",
  76. dsn: "docker://test_docker?log_level=warn",
  77. expectedErr: "",
  78. },
  79. {
  80. name: "DSN invalid log_level",
  81. dsn: "docker://test_docker?log_level=foobar",
  82. expectedErr: "unknown level foobar: not a valid logrus Level:",
  83. },
  84. {
  85. name: "DSN ok with multiple parameters",
  86. dsn: "docker://test_docker?since=42min&docker_host=unix:///var/run/podman/podman.sock",
  87. expectedErr: "",
  88. },
  89. }
  90. subLogger := log.WithFields(log.Fields{
  91. "type": "docker",
  92. })
  93. for _, test := range tests {
  94. f := DockerSource{}
  95. err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger)
  96. if test.expectedErr != "" {
  97. assert.Contains(t, err.Error(), test.expectedErr)
  98. } else {
  99. assert.Equal(t, err, nil)
  100. }
  101. }
  102. }
  103. type mockDockerCli struct {
  104. client.Client
  105. }
  106. func TestStreamingAcquisition(t *testing.T) {
  107. log.SetOutput(os.Stdout)
  108. log.SetLevel(log.InfoLevel)
  109. log.Info("Test 'TestStreamingAcquisition'")
  110. tests := []struct {
  111. config string
  112. expectedErr string
  113. expectedOutput string
  114. expectedLines int
  115. logType string
  116. logLevel log.Level
  117. }{
  118. {
  119. config: `
  120. source: docker
  121. mode: cat
  122. container_name:
  123. - docker_test`,
  124. expectedErr: "",
  125. expectedOutput: "",
  126. expectedLines: 3,
  127. logType: "test",
  128. logLevel: log.InfoLevel,
  129. },
  130. {
  131. config: `
  132. source: docker
  133. mode: cat
  134. container_name_regexp:
  135. - docker_*`,
  136. expectedErr: "",
  137. expectedOutput: "",
  138. expectedLines: 3,
  139. logType: "test",
  140. logLevel: log.InfoLevel,
  141. },
  142. }
  143. for _, ts := range tests {
  144. var logger *log.Logger
  145. var subLogger *log.Entry
  146. if ts.expectedOutput != "" {
  147. logger.SetLevel(ts.logLevel)
  148. subLogger = logger.WithFields(log.Fields{
  149. "type": "docker",
  150. })
  151. } else {
  152. subLogger = log.WithFields(log.Fields{
  153. "type": "docker",
  154. })
  155. }
  156. dockerTomb := tomb.Tomb{}
  157. out := make(chan types.Event)
  158. dockerSource := DockerSource{}
  159. err := dockerSource.Configure([]byte(ts.config), subLogger)
  160. if err != nil {
  161. t.Fatalf("Unexpected error : %s", err)
  162. }
  163. dockerSource.Client = new(mockDockerCli)
  164. actualLines := 0
  165. readerTomb := &tomb.Tomb{}
  166. streamTomb := tomb.Tomb{}
  167. streamTomb.Go(func() error {
  168. return dockerSource.StreamingAcquisition(out, &dockerTomb)
  169. })
  170. readerTomb.Go(func() error {
  171. time.Sleep(1 * time.Second)
  172. ticker := time.NewTicker(1 * time.Second)
  173. for {
  174. select {
  175. case <-out:
  176. actualLines++
  177. ticker.Reset(1 * time.Second)
  178. case <-ticker.C:
  179. log.Infof("no more line to read")
  180. readerTomb.Kill(nil)
  181. return nil
  182. }
  183. }
  184. })
  185. time.Sleep(10 * time.Second)
  186. if ts.expectedErr == "" && err != nil {
  187. t.Fatalf("Unexpected error : %s", err)
  188. } else if ts.expectedErr != "" && err != nil {
  189. assert.Contains(t, err.Error(), ts.expectedErr)
  190. continue
  191. } else if ts.expectedErr != "" && err == nil {
  192. t.Fatalf("Expected error %s, but got nothing !", ts.expectedErr)
  193. }
  194. if err := readerTomb.Wait(); err != nil {
  195. t.Fatal(err)
  196. }
  197. //time.Sleep(4 * time.Second)
  198. if ts.expectedLines != 0 {
  199. assert.Equal(t, ts.expectedLines, actualLines)
  200. }
  201. dockerSource.t.Kill(nil)
  202. err = streamTomb.Wait()
  203. if err != nil {
  204. t.Fatalf("docker acquisition error: %s", err)
  205. }
  206. }
  207. }
  208. func (cli *mockDockerCli) ContainerList(ctx context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error) {
  209. containers := make([]dockerTypes.Container, 0)
  210. container := &dockerTypes.Container{
  211. ID: "12456",
  212. Names: []string{testContainerName},
  213. }
  214. containers = append(containers, *container)
  215. return containers, nil
  216. }
  217. func (cli *mockDockerCli) ContainerLogs(ctx context.Context, container string, options dockerTypes.ContainerLogsOptions) (io.ReadCloser, error) {
  218. startLineByte := "\x01\x00\x00\x00\x00\x00\x00\x1f"
  219. data := []string{"docker", "test", "1234"}
  220. ret := ""
  221. for _, line := range data {
  222. ret += fmt.Sprintf("%s%s\n", startLineByte, line)
  223. }
  224. r := io.NopCloser(strings.NewReader(ret)) // r type is io.ReadCloser
  225. return r, nil
  226. }
  227. func TestOneShot(t *testing.T) {
  228. log.Infof("Test 'TestOneShot'")
  229. tests := []struct {
  230. dsn string
  231. expectedErr string
  232. expectedOutput string
  233. expectedLines int
  234. logType string
  235. logLevel log.Level
  236. }{
  237. {
  238. dsn: "docker://non_exist_docker",
  239. expectedErr: "no docker found, can't run one shot acquisition",
  240. expectedOutput: "",
  241. expectedLines: 0,
  242. logType: "test",
  243. logLevel: log.InfoLevel,
  244. },
  245. {
  246. dsn: "docker://" + testContainerName,
  247. expectedErr: "",
  248. expectedOutput: "",
  249. expectedLines: 3,
  250. logType: "test",
  251. logLevel: log.InfoLevel,
  252. },
  253. }
  254. for _, ts := range tests {
  255. var subLogger *log.Entry
  256. var logger *log.Logger
  257. if ts.expectedOutput != "" {
  258. logger.SetLevel(ts.logLevel)
  259. subLogger = logger.WithFields(log.Fields{
  260. "type": "docker",
  261. })
  262. } else {
  263. log.SetLevel(ts.logLevel)
  264. subLogger = log.WithFields(log.Fields{
  265. "type": "docker",
  266. })
  267. }
  268. dockerClient := &DockerSource{}
  269. labels := make(map[string]string)
  270. labels["type"] = ts.logType
  271. if err := dockerClient.ConfigureByDSN(ts.dsn, labels, subLogger); err != nil {
  272. t.Fatalf("unable to configure dsn '%s': %s", ts.dsn, err)
  273. }
  274. dockerClient.Client = new(mockDockerCli)
  275. out := make(chan types.Event)
  276. actualLines := 0
  277. if ts.expectedLines != 0 {
  278. go func() {
  279. READLOOP:
  280. for {
  281. select {
  282. case <-out:
  283. actualLines++
  284. case <-time.After(1 * time.Second):
  285. break READLOOP
  286. }
  287. }
  288. }()
  289. }
  290. tomb := tomb.Tomb{}
  291. err := dockerClient.OneShotAcquisition(out, &tomb)
  292. if ts.expectedErr == "" && err != nil {
  293. t.Fatalf("Unexpected error : %s", err)
  294. } else if ts.expectedErr != "" && err != nil {
  295. assert.Contains(t, err.Error(), ts.expectedErr)
  296. continue
  297. } else if ts.expectedErr != "" && err == nil {
  298. t.Fatalf("Expected error %s, but got nothing !", ts.expectedErr)
  299. }
  300. // else we do the check before actualLines is incremented ...
  301. time.Sleep(1 * time.Second)
  302. if ts.expectedLines != 0 {
  303. assert.Equal(t, ts.expectedLines, actualLines)
  304. }
  305. }
  306. }