Add support for accessing container logs. (#34)
* Add support for accessing container logs. closes #27 -=david=- * Don't add logs to configuration if not capturing the log * Further refinements
This commit is contained in:
parent
9f36f0db78
commit
ec26e4c078
5 changed files with 106 additions and 32 deletions
|
|
@ -23,9 +23,9 @@
|
||||||
[org.clojure/test.check "1.1.0"]
|
[org.clojure/test.check "1.1.0"]
|
||||||
[org.clojure/tools.namespace "1.0.0"]
|
[org.clojure/tools.namespace "1.0.0"]
|
||||||
[org.testcontainers/postgresql "1.14.3"]]
|
[org.testcontainers/postgresql "1.14.3"]]
|
||||||
:githooks {:auto-install true
|
; :githooks {:auto-install true
|
||||||
:ci-env-variable "CI"
|
; :ci-env-variable "CI"
|
||||||
:pre-commit ["script/pre-commit"]}
|
; :pre-commit ["script/pre-commit"]
|
||||||
:source-paths ["dev-src"]}}
|
:source-paths ["dev-src"]}}
|
||||||
|
|
||||||
:target-path "target/%s")
|
:target-path "target/%s")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
(ns clj-test-containers.core
|
(ns clj-test-containers.core
|
||||||
(:require
|
(:require
|
||||||
[clj-test-containers.spec.core :as cs]
|
[clj-test-containers.spec.core :as cs]
|
||||||
[clojure.spec.alpha :as s])
|
[clojure.spec.alpha :as s]
|
||||||
|
[clojure.string])
|
||||||
(:import
|
(:import
|
||||||
(java.nio.file
|
(java.nio.file
|
||||||
Paths)
|
Paths)
|
||||||
|
|
@ -9,6 +10,8 @@
|
||||||
BindMode
|
BindMode
|
||||||
GenericContainer
|
GenericContainer
|
||||||
Network)
|
Network)
|
||||||
|
(org.testcontainers.containers.output
|
||||||
|
ToStringConsumer)
|
||||||
(org.testcontainers.containers.wait.strategy
|
(org.testcontainers.containers.wait.strategy
|
||||||
Wait)
|
Wait)
|
||||||
(org.testcontainers.images.builder
|
(org.testcontainers.images.builder
|
||||||
|
|
@ -34,7 +37,7 @@
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(wait {:strategy :http
|
(wait {:wait-strategy :http
|
||||||
:port 80
|
:port 80
|
||||||
:path \"/\"
|
:path \"/\"
|
||||||
:status-codes [200 201]
|
:status-codes [200 201]
|
||||||
|
|
@ -42,14 +45,29 @@
|
||||||
:read-timeout 5
|
:read-timeout 5
|
||||||
:basic-credentials {:username \"user\"
|
:basic-credentials {:username \"user\"
|
||||||
:password \"password\"}}
|
:password \"password\"}}
|
||||||
container))
|
container)
|
||||||
```
|
```
|
||||||
## Health Strategy
|
## Health Strategy
|
||||||
TBD
|
The :health strategy only accepts a true or false value. This enables support for Docker's
|
||||||
|
healthcheck feature, whereby you can directly leverage the healthy state of your container
|
||||||
|
as your wait condition.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(wait {:wait-strategy :health :true} container)
|
||||||
|
```
|
||||||
|
|
||||||
## Log Strategy
|
## Log Strategy
|
||||||
TBD"
|
The :log strategy accepts a message which simply causes the output of your container's log
|
||||||
:strategy)
|
to be used in determining if the container is ready or not. The output is `grepped` against
|
||||||
|
the log message.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(wait {:wait-strategy :log
|
||||||
|
:message \"accept connections\"} container)
|
||||||
|
```"
|
||||||
|
:wait-strategy)
|
||||||
|
|
||||||
(defmethod wait :http
|
(defmethod wait :http
|
||||||
[{:keys [path port status-codes tls read-timeout basic-credentials] :as options} container]
|
[{:keys [path port status-codes tls read-timeout basic-credentials] :as options} container]
|
||||||
|
|
@ -93,7 +111,7 @@
|
||||||
|
|
||||||
(defn init
|
(defn init
|
||||||
"Sets the properties for a testcontainer instance"
|
"Sets the properties for a testcontainer instance"
|
||||||
[{:keys [container exposed-ports env-vars command network network-aliases wait-for]}]
|
[{:keys [container exposed-ports env-vars command network network-aliases wait-for] :as init-options}]
|
||||||
|
|
||||||
(.setExposedPorts container (map int exposed-ports))
|
(.setExposedPorts container (map int exposed-ports))
|
||||||
|
|
||||||
|
|
@ -108,7 +126,7 @@
|
||||||
(when network-aliases
|
(when network-aliases
|
||||||
(.setNetworkAliases container (java.util.ArrayList. network-aliases)))
|
(.setNetworkAliases container (java.util.ArrayList. network-aliases)))
|
||||||
|
|
||||||
(merge {:container container
|
(merge init-options {:container container
|
||||||
:exposed-ports (vec (.getExposedPorts container))
|
:exposed-ports (vec (.getExposedPorts container))
|
||||||
:env-vars (into {} (.getEnvMap container))
|
:env-vars (into {} (.getEnvMap container))
|
||||||
:host (.getHost container)
|
:host (.getHost container)
|
||||||
|
|
@ -179,16 +197,58 @@
|
||||||
:stdout (.getStdout result)
|
:stdout (.getStdout result)
|
||||||
:stderr (.getStderr result)}))
|
:stderr (.getStderr result)}))
|
||||||
|
|
||||||
|
(defmulti log
|
||||||
|
"Sets a log strategy on the container as a means of accessing the container logs.
|
||||||
|
It currently only supports a :string as the strategy to use.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
```
|
||||||
|
"
|
||||||
|
:log-strategy)
|
||||||
|
|
||||||
|
(defmethod log :string
|
||||||
|
[_ container]
|
||||||
|
(let [to-string-consumer (ToStringConsumer.)]
|
||||||
|
(.followOutput container to-string-consumer)
|
||||||
|
{:string-log (fn []
|
||||||
|
(-> (.toUtf8String to-string-consumer)
|
||||||
|
(clojure.string/replace #"\n+" "\n")))}))
|
||||||
|
|
||||||
|
(defmethod log :slf4j [_ _] nil)
|
||||||
|
|
||||||
|
(defmethod log :default [_ _] nil)
|
||||||
|
|
||||||
|
(defn dump-logs
|
||||||
|
"Dumps the logs found by invoking the function on the :string-log key"
|
||||||
|
[container-config]
|
||||||
|
((:string-log container-config)))
|
||||||
|
|
||||||
(defn start!
|
(defn start!
|
||||||
"Starts the underlying testcontainer instance and adds new values to the response map, e.g. :id and :first-mapped-port"
|
"Starts the underlying testcontainer instance and adds new values to the response map, e.g. :id and :first-mapped-port"
|
||||||
[container-config]
|
[container-config]
|
||||||
(let [container (:container container-config)]
|
(let [{:keys [container log-to]} container-config]
|
||||||
(.start container)
|
(.start container)
|
||||||
(-> container-config
|
(-> (merge container-config
|
||||||
(assoc :id (.getContainerId container))
|
{:id (.getContainerId container)
|
||||||
(assoc :mapped-ports (into {}
|
:mapped-ports (into {}
|
||||||
(map (fn [port] [port (.getMappedPort container port)])
|
(map (fn [port] [port (.getMappedPort container port)])
|
||||||
(:exposed-ports container-config)))))))
|
(:exposed-ports container-config)))}
|
||||||
|
(log log-to container))
|
||||||
|
(dissoc :log-to))))
|
||||||
|
|
||||||
(defn stop!
|
(defn stop!
|
||||||
"Stops the underlying container"
|
"Stops the underlying container"
|
||||||
|
|
@ -196,6 +256,7 @@
|
||||||
(.stop (:container container-config))
|
(.stop (:container container-config))
|
||||||
(-> container-config
|
(-> container-config
|
||||||
(dissoc :id)
|
(dissoc :id)
|
||||||
|
(dissoc :string-log)
|
||||||
(dissoc :mapped-ports)))
|
(dissoc :mapped-ports)))
|
||||||
|
|
||||||
(s/fdef create-network
|
(s/fdef create-network
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,9 @@
|
||||||
(s/def ::log
|
(s/def ::log
|
||||||
keyword?)
|
keyword?)
|
||||||
|
|
||||||
(s/def ::strategy #{:http :health :log})
|
(s/def ::wait-strategy #{:http :health :log})
|
||||||
|
|
||||||
|
(s/def ::log-strategy #{:string})
|
||||||
|
|
||||||
(s/def ::path
|
(s/def ::path
|
||||||
string?)
|
string?)
|
||||||
|
|
@ -45,3 +47,6 @@
|
||||||
|
|
||||||
(s/def ::check
|
(s/def ::check
|
||||||
boolean?)
|
boolean?)
|
||||||
|
|
||||||
|
(s/def ::string
|
||||||
|
string?)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
(s/def ::wait-for
|
(s/def ::wait-for
|
||||||
(s/keys :req-un [::csc/strategy]
|
(s/keys :req-un [::csc/wait-strategy]
|
||||||
:opt-un [::csc/path
|
:opt-un [::csc/path
|
||||||
::csc/message
|
::csc/message
|
||||||
::csc/check]))
|
::csc/check]))
|
||||||
|
|
||||||
|
(s/def ::log-to
|
||||||
|
(s/keys :req-un [::csc/log-strategy]
|
||||||
|
:opt-un [::csc/string]))
|
||||||
|
|
||||||
(s/def ::network
|
(s/def ::network
|
||||||
(s/nilable (s/keys :req-un [::csn/network
|
(s/nilable (s/keys :req-un [::csn/network
|
||||||
::csn/name
|
::csn/name
|
||||||
|
|
@ -22,7 +26,9 @@
|
||||||
::csc/env-vars
|
::csc/env-vars
|
||||||
::csc/host]
|
::csc/host]
|
||||||
:opt-un [::network
|
:opt-un [::network
|
||||||
::wait-for]))
|
::wait-for
|
||||||
|
::log-to]))
|
||||||
|
|
||||||
|
|
||||||
(s/def ::init-options
|
(s/def ::init-options
|
||||||
(s/keys :req-un [::csc/container]
|
(s/keys :req-un [::csc/container]
|
||||||
|
|
@ -31,6 +37,7 @@
|
||||||
::csc/command
|
::csc/command
|
||||||
::network
|
::network
|
||||||
::wait-for
|
::wait-for
|
||||||
|
::log-to
|
||||||
::csc/network-aliases]))
|
::csc/network-aliases]))
|
||||||
|
|
||||||
(s/def ::create-options
|
(s/def ::create-options
|
||||||
|
|
@ -40,6 +47,7 @@
|
||||||
::csc/command
|
::csc/command
|
||||||
::network
|
::network
|
||||||
::wait-for
|
::wait-for
|
||||||
|
::log-to
|
||||||
::csc/network-aliases]))
|
::csc/network-aliases]))
|
||||||
|
|
||||||
(s/def ::create-network-options
|
(s/def ::create-network-options
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
(let [container (sut/create {:image-name "postgres:12.2"
|
(let [container (sut/create {:image-name "postgres:12.2"
|
||||||
:exposed-ports [5432]
|
:exposed-ports [5432]
|
||||||
:env-vars {"POSTGRES_PASSWORD" "pw"}
|
:env-vars {"POSTGRES_PASSWORD" "pw"}
|
||||||
:wait-for {:strategy :log :message "accept connections"}})
|
:wait-for {:wait-strategy :log :message "accept connections"}})
|
||||||
initialized-container (sut/start! container)
|
initialized-container (sut/start! container)
|
||||||
stopped-container (sut/stop! container)]
|
stopped-container (sut/stop! container)]
|
||||||
(is (some? (:id initialized-container)))
|
(is (some? (:id initialized-container)))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue