docker_test.go 7.5 KB

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