Added :fn log strategy (#41)

This commit is contained in:
Tim Zöller 2022-03-26 11:54:28 +01:00
parent a53cf9e271
commit c42886ba56
6 changed files with 155 additions and 90 deletions

View file

@ -1,6 +1,13 @@
# Change Log # Change Log
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).
## [0.6.0] - 2022-03-26
### Changed
- [#55](https://github.com/javahippie/clj-test-containers/issues/55): Upgrade to latest Testcontainers version
### Added
- [#42](https://github.com/javahippie/clj-test-containers/issues/42): Extend wait strategies
## [0.5.0] - 2021-08-18 ## [0.5.0] - 2021-08-18
### Changed ### Changed
- [#49](https://github.com/javahippie/clj-test-containers/issues/49): Updated to latest Testcontainers version - [#49](https://github.com/javahippie/clj-test-containers/issues/49): Updated to latest Testcontainers version

View file

@ -68,16 +68,16 @@ Creates a testcontainers instance from a given Docker label and returns them
#### Config parameters: #### Config parameters:
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- |:----------------------------------------------------------------------------------------------------|
| `:image-name` | String, mandatory | The name and label of an image, e.g. `postgres:12.2` | | `:image-name` | String, mandatory | The name and label of an image, e.g. `postgres:12.2` |
| `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | | `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port |
| `:env-vars` | Map | A map with environment variables | | `:env-vars` | Map | A map with environment variables |
| `:command` | Vector with strings | The start command of the container | | `:command` | Vector with strings | The start command of the container |
| `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | | `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) |
| `:network-aliases` | Map | A list of alias names for the container on the network | | `:network-aliases` | Map | A list of alias names for the container on the network |
| `:wait-for` | Map | A map containing the wait strategy to use and the condition to check for | | `:wait-for` | Map | A map containing the [wait strategy](docs/wait-strategies.md) to use and the condition to check for |
| `:log-to` | Map | A map containing the log strategy to use, e.g. {:log-strategy string} | | `:log-to` | Map | A map containing the [log strategy](docs/log-strategies.md) to use, e.g. {:log-strategy string} |
#### Result: #### Result:
@ -123,22 +123,22 @@ Initializes a given Testcontainer, which was e.g. provided by a library
#### Config parameters: #### Config parameters:
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- |:----------------------------------------------------------------------------------------------------|
| `:container` | `org.testcontainers.containers.GenericContainer`, mandatory | The name and label of an image, e.g. `postgres:12.2` | | `:container` | `org.testcontainers.containers.GenericContainer`, mandatory | The name and label of an image, e.g. `postgres:12.2` |
| `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | | `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port |
| `:env-vars` | Map | A map with environment variables | | `:env-vars` | Map | A map with environment variables |
| `:command` | Vector with strings | The start command of the container | | `:command` | Vector with strings | The start command of the container |
| `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | | `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) |
| `:network-aliases` | Map | A list of alias names for the container on the network | | `:network-aliases` | Map | A list of alias names for the container on the network |
| `:wait-for` | Map | A map containing the wait strategy to use and the condition to check for | | `:wait-for` | Map | A map containing the [wait strategy](docs/wait-strategies.md) to use and the condition to check for |
| `:log-to` | Map | A map containing the log strategy to use, e.g. {:log-strategy string} | | `:log-to` | Map | A map containing the [log strategy](docs/log-strategies.md) to use, e.g. {:log-strategy string} |
| | | | | | | |
#### Result: #### Result:
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- |:------------------------------------------------------------------------------------------|
| `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) | | `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) |
| `:exposed-ports` | Vector with ints | Value of the same input parameter | | `:exposed-ports` | Vector with ints | Value of the same input parameter |
| `:env-vars` | Map | Value of the same input parameter | | `:env-vars` | Map | Value of the same input parameter |
@ -185,8 +185,8 @@ Creates a testcontainer from a Dockerfile
| `:command` | Vector with strings | The start command of the container | | `:command` | Vector with strings | The start command of the container |
| `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | | `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) |
| `:network-aliases` | Map | A list of alias names for the container on the network | | `:network-aliases` | Map | A list of alias names for the container on the network |
| `:wait-for` | Map | A map containing the wait strategy to use and the condition to check for | | `:wait-for` | Map | A map containing the [wait strategy](docs/wait-strategies.md) to use and the condition to check for |
| `:log-to` | Map | A map containing the log strategy to use, e.g. {:log-strategy string} | | `:log-to` | Map | A map containing the [log strategy](docs/log-strategies.md) to use, e.g. {:log-strategy string} |
| | | | | | | |
#### Result: #### Result:

34
doc/log-strategies.md Normal file
View file

@ -0,0 +1,34 @@
# Log strategies
This library offers two ways to access the logs of the running container: The :string strategy and the :fn strategy.
## String Strategy
The `:string` strategy sets up a function in the returned map, under the `string-log` key. This function enables the
dumping of the logs when passed to the `dump-logs` function.
Example:
```clojure
{:log-strategy :string}
```
Then, later in your program, you can access the logs thus:
```clojure
(def container-config (tc/start! container))
(tc/dump-logs container-config)
```
## Function Strategy
The `:fn` strategy accepts an additional parameter `:function` in the configuration map, which allows you to pass a
function to the Testcontainers log mechanism which accepts a single String parameter and gets called for every log line.
This way you can pass the container logging on to the logging library of your choice.
Example:
```clojure
{:log-strategy :fn
:function (fn [log-line] (println "From Container: " log-line)}
```

View file

@ -23,7 +23,8 @@
[orchestra "2021.01.01-1"] [orchestra "2021.01.01-1"]
[org.clojure/tools.namespace "1.2.0"] [org.clojure/tools.namespace "1.2.0"]
[org.testcontainers/postgresql "1.16.3"] [org.testcontainers/postgresql "1.16.3"]
[com.fzakaria/slf4j-timbre "0.3.21"]] [com.fzakaria/slf4j-timbre "0.3.21"]
[nrepl "0.9.0"]]
:source-paths ["dev-src"]}} :source-paths ["dev-src"]}}
:target-path "target/%s") :target-path "target/%s")

View file

@ -13,6 +13,8 @@
GenericContainer GenericContainer
Network) Network)
(org.testcontainers.containers.output (org.testcontainers.containers.output
BaseConsumer
OutputFrame
ToStringConsumer) ToStringConsumer)
(org.testcontainers.containers.wait.strategy (org.testcontainers.containers.wait.strategy
Wait) Wait)
@ -34,64 +36,64 @@
(defmulti wait (defmulti wait
"Sets a wait strategy to the container. Supports :http, :health and :log as "Sets a wait strategy to the container. Supports :http, :health and :log as
strategies. strategies.
## HTTP Strategy ## HTTP Strategy
The :http strategy will only accept the container as initialized if it can be The :http strategy will only accept the container as initialized if it can be
accessed via HTTP. It accepts a path, a port, a vector of status codes, a accessed via HTTP. It accepts a path, a port, a vector of status codes, a
boolean that specifies if TLS is enabled, a read timeout in seconds and a map boolean that specifies if TLS is enabled, a read timeout in seconds and a map
with basic credentials, containing username and password. Only the path is with basic credentials, containing username and password. Only the path is
required, all others are optional. required, all others are optional.
Example: Example:
```clojure ```clojure
(wait {:wait-strategy :http (wait {:wait-strategy :http
:port 80 :port 80
:path \"/\" :path \"/\"
:status-codes [200 201] :status-codes [200 201]
:tls true :tls true
:read-timeout 5 :read-timeout 5
:basic-credentials {:username \"user\" :basic-credentials {:username \"user\"
:password \"password\" :password \"password\"
:startup-timeout 60}} :startup-timeout 60}}
container) container)
``` ```
## Health Strategy ## Health Strategy
The :health strategy enables support for Docker's healthcheck feature, The :health strategy enables support for Docker's healthcheck feature,
whereby you can directly leverage the healthy state of your container as your wait condition. whereby you can directly leverage the healthy state of your container as your wait condition.
Example: Example:
```clojure ```clojure
(wait {:wait-strategy :health (wait {:wait-strategy :health
:startup-timeout 60} container) :startup-timeout 60} container)
``` ```
## Log Strategy ## Log Strategy
The :log strategy accepts a message which simply causes the output of your The :log strategy accepts a message which simply causes the output of your
container's log to be used in determining if the container is ready or not. container's log to be used in determining if the container is ready or not.
The output is `grepped` against the log message. The output is `grepped` against the log message.
Example: Example:
```clojure ```clojure
(wait {:wait-strategy :log (wait {:wait-strategy :log
:message \"accept connections\" :message \"accept connections\"
:startup-timeout 60} container) :startup-timeout 60} container)
``` ```
## Port Strategy ## Port Strategy
The port strategy waits for the first of the mapped ports to be opened. It only accepts the startup-timeout The port strategy waits for the first of the mapped ports to be opened. It only accepts the startup-timeout
value as a parameter. value as a parameter.
Example: Example:
```clojure ```clojure
(wait {:wait-strategy :port (wait {:wait-strategy :port
:startup-timeout 60} container :startup-timeout 60} container
```" ```"
:wait-strategy) :wait-strategy)
(defmethod wait :http (defmethod wait :http
@ -278,27 +280,38 @@
:stderr (.getStderr result)})) :stderr (.getStderr result)}))
(defmulti log (defmulti log
"Sets a log strategy on the container as a means of accessing the container "Sets a log strategy on the container as a means of accessing the container logs.
logs. It currently only supports a :string as the strategy to use.
## String Strategy ## String Strategy
The :string strategy sets up a function in the returned map, under the The `:string` strategy sets up a function in the returned map, under the
`string-log` key. This function enables the dumping of the logs when passed to `string-log` key. This function enables the dumping of the logs when passed to
the `dump-logs` function. the `dump-logs` function.
Example: Example:
```clojure ```clojure
{:log-strategy :string} {:log-strategy :string}
``` ```
Then, later in your program, you can access the logs thus: Then, later in your program, you can access the logs thus:
```clojure ```clojure
(def container-config (tc/start! container)) (def container-config (tc/start! container))
(tc/dump-logs container-config) (tc/dump-logs container-config)
``` ```
"
## Function Strategy
The `:fn` strategy accepts an additional parameter `:function` in the configuration
map, which allows you to pass a function to the Testcontainers log mechanism
which accepts a single String parameter and gets called for every log line. This
way you can pass the container logging on to the logging library of your choice.
Example:
```clojure
{:log-strategy :fn
:function (fn [log-line] (println \"From Container: \" log-line)}
```
"
:log-strategy) :log-strategy)
(defmethod log :string (defmethod log :string
@ -309,9 +322,12 @@
(-> (.toUtf8String to-string-consumer) (-> (.toUtf8String to-string-consumer)
(clojure.string/replace #"\n+" "\n")))})) (clojure.string/replace #"\n+" "\n")))}))
(defmethod log :slf4j [_ _] nil) ; Not yet implemented (defmethod log :fn [{:keys [function]} ^GenericContainer container]
(.followOutput container (proxy [BaseConsumer] []
(^void accept [^OutputFrame frame]
(function (.getUtf8String frame))))))
(defmethod log :default [_ _] nil) ; Not yet implemented (defmethod log :default [_ _] nil)
(defn dump-logs (defn dump-logs
"Dumps the logs found by invoking the function on the :string-log key" "Dumps the logs found by invoking the function on the :string-log key"

View file

@ -20,13 +20,20 @@
(is (nil? (:id stopped-container))) (is (nil? (:id stopped-container)))
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Testing log access to the container" (testing "Testing log access to the container with string logs"
(let [container (sut/init {:container (PostgreSQLContainer. "postgres:12.2") (let [container (sut/init {:container (PostgreSQLContainer. "postgres:12.2")
:log-to {:log-strategy :string}}) :log-to {:log-strategy :string}})
initialized-container (sut/start! container)] initialized-container (sut/start! container)]
(Thread/sleep 500) (Thread/sleep 500)
(is (includes? (sut/dump-logs initialized-container) "database system is ready to accept connections")))) (is (includes? (sut/dump-logs initialized-container) "database system is ready to accept connections"))))
(testing "Testing log access to the container with function logs"
(let [logs (atom [])]
(sut/start! (sut/init {:container (PostgreSQLContainer. "postgres:12.2")
:log-to {:log-strategy :fn
:function #(swap! logs conj %)}}))
(is (filter #(includes? "database system is ready to accept connections" %) @logs))))
(testing "Testing basic testcontainer generic image initialisation with wait for log message" (testing "Testing basic testcontainer generic image initialisation with wait for log message"
(let [container (sut/create {:image-name "postgres:12.2" (let [container (sut/create {:image-name "postgres:12.2"
:exposed-ports [5432] :exposed-ports [5432]