Compare commits

...

43 commits
0.4.0 ... main

Author SHA1 Message Date
Eddú Meléndez Gonzales
802f18af55
Update GHA versions (#80)
* actions/setup-java@v4
* actions/cache@v4
* actions/checkout@v4
2024-04-16 07:53:09 -05:00
Tim Zöller
1297054b77 Updated dependency versions 2024-04-13 09:32:57 +02:00
Tim Zöller
233c3883eb
Make config clearer in log-strategies.md (#73) (#77)
This wasn't working for me because nothing in here says it all needs to be the value a `:log-to` key in the container config map. Reading the code and adding that made it work. Figured it might be nice to clarify that for others too.

Co-authored-by: Wes Morgan <github@wesmorgan.me>
2024-04-13 09:08:45 +02:00
Ukpono Nnah
49f547585e
tutorial.md: Fix let bindings (#75)
Fix let bindings uneven form
2024-04-13 09:06:01 +02:00
Eddú Meléndez
874d5756af
Update branch to main 2023-08-24 15:57:19 -06:00
Eddú Meléndez Gonzales
9bf75d6387
Add release workflow (#70) 2023-08-24 14:47:48 -06:00
Tim Zöller
8c807a8750
Update testcontainers version to 1.19.0 (#69) 2023-08-23 18:10:26 -06:00
Tim Zöller
390563ac1d Updated project dependencies (#67) 2022-11-16 22:56:32 +01:00
Tim Zöller
667a2d0850 Refactored cleanup to not use deprecated methods anymore (#66) 2022-11-16 22:50:44 +01:00
Tim Zöller
94e582a1aa Upgraded to latest testcontainers-java version (#65) 2022-11-16 15:10:17 +01:00
Brendon Wong
100a938250
The parameter for copy-file-to-container! is container-path not host-path. (#64) 2022-10-06 11:44:46 +02:00
Tim Zöller
25d1ba491b Prepare Release 0.7.3 2022-09-30 12:30:57 +02:00
Tim Zöller
35454d3fd2 Upgrade to testcontainers-java 1.17.4 (#63) 2022-09-30 12:23:59 +02:00
Tim Zöller
1bf2c205c1 Applied automatic styling 2022-07-03 17:22:00 +02:00
Tim Zöller
5464fc35fa Bumped to testcontainers-java 1.17.3 2022-07-03 17:20:21 +02:00
Tim Zöller
d9a42decb2 Fixed deps.edn versios 2022-06-12 14:00:15 +02:00
Tim Zöller
dd517094ce Fixed changelog, set release version 2022-06-12 11:33:45 +02:00
Tim Zöller
fffb398918 Use nginx Docker image for HTTP wait status test 2022-06-12 11:30:56 +02:00
Tim Zöller
e57261ce12 Added allow-insecure flag (#58) 2022-05-25 09:08:32 +02:00
Tim Zöller
ba77335753 Dependency update to Testcontainers 1.17.2 (#57) 2022-05-25 09:00:21 +02:00
Tim Zöller
d246351551 Dependency update to Testcontainers 1.17.1 (#56) 2022-04-23 09:43:15 +02:00
Tim Zöller
a635113b7e Prepare Release 2022-03-26 12:04:53 +01:00
Tim Zöller
fd06f2fe6c Changelog 2022-03-26 11:59:50 +01:00
Tim Zöller
dad4ddbd0f Corrected Markdown links 2022-03-26 11:56:58 +01:00
Tim Zöller
c42886ba56 Added :fn log strategy (#41) 2022-03-26 11:54:28 +01:00
Tim Zöller
a53cf9e271 Documentation for 0.6.0 2022-03-25 22:59:14 +01:00
Tim Zöller
791708bf51 Minor changes, release preparations 2022-03-21 19:24:33 +01:00
Tim Zöller
28b0183339 Applied autoformatter 2022-03-18 14:05:20 +01:00
Tim Zöller
71af11f503 Extended config for wait strategies (#42) 2022-03-18 14:03:42 +01:00
Tim Zöller
6e94e86745 Resolved dependency issues 2022-03-18 11:48:59 +01:00
Tim Zöller
fc70760de7 Bumped TC dependency to 1.16.3 2022-03-17 13:10:48 +01:00
Tim Zöller
d1036e1ff3 Set version to 0.6.0-SNAPSHOT 2021-08-18 11:07:34 +02:00
Tim Zöller
1dacbe6e78 Prepared Release 0.5.0 2021-08-18 10:59:35 +02:00
Tim Zöller
0b1031c083 Merge branch 'master' of github.com:javahippie/clj-test-containers 2021-08-18 10:53:06 +02:00
Tim Zöller
5c6dbed46e Updated Testcontainers version to 1.16.0 2021-08-18 10:51:33 +02:00
Gilles Philippart
b13499bc7c
Fix unbalanced parens and braces (#52) 2021-06-01 19:52:04 +02:00
Andrea Richiardi
12c4b6c1e9
[Fix #50] Add type hints for ResourceReaper (#51) 2021-03-09 09:26:47 +01:00
Tim Zöller
ff1215e710
Removed CircleCI badge 2021-03-04 15:43:37 +01:00
Tim Zöller
d6e4b065cd Removed Circle CI from builds 2021-03-04 15:43:00 +01:00
Tim Zöller
7be77af93b Merge branch 'master' of https://github.com/javahippie/clj-test-containers into master 2021-03-04 15:42:26 +01:00
Tim Zöller
2b8d4a911e #49 - Bumped to Testcontainers 1.15.2 2021-03-04 15:42:08 +01:00
Tim Zöller
51314ae2e7
Set up GitHub Action 2021-02-28 15:31:39 +01:00
Tim Zöller
13c859dee2 New Snapshot version 2020-12-16 10:43:12 +01:00
16 changed files with 804 additions and 419 deletions

View file

@ -1,49 +0,0 @@
# Clojure CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-clojure/ for more details
#
version: 2
jobs:
build:
machine:
image: ubuntu-1604:202004-01
working_directory: ~/repo
environment:
# Customize the JVM maximum heap limit
JVM_OPTS: -Xmx3200m
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "project.clj" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: Install Leiningen
command: wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein && chmod a+x lein && lein --version
- run: lein deps
- save_cache:
paths:
- ~/.lein
- ~/.m2
key: v1-dependencies-{{ checksum "project.clj" }}
# check syntax and warn on reflection
- run: lein check
# run tests!
- run: lein test
# check style
- run: lein cljstyle check --report
- store_test_results:
path: target

18
.github/actions/setup-build/action.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: Set up Build
description: Sets up Build
runs:
using: "composite"
steps:
- uses: actions/setup-java@v4
with:
java-version: '8'
distribution: temurin
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-lein-${{ hashFiles('**/project.clj') }}
restore-keys: ${{ runner.os }}-lein-
- name: Clear existing docker image cache
shell: bash
run: docker image prune -af

22
.github/workflows/clojure.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Clojure CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Build
uses: ./.github/actions/setup-build
- name: Install dependencies
run: lein deps
- name: Run tests
run: lein test

28
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: Release
on:
release:
types: [published]
permissions:
contents: read
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Build
uses: ./.github/actions/setup-build
- name: Set version
run: |
lein change version set '"${{github.event.release.tag_name}}"'
- name: Release ${{github.event.release.tag_name}}
run: lein with-profile release deploy maven
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}

3
.gitignore vendored
View file

@ -8,8 +8,11 @@ pom.xml.asc
/.lein-* /.lein-*
/.nrepl-history /.nrepl-history
/.nrepl-port /.nrepl-port
.clj-kondo/
.hgignore .hgignore
.hg/ .hg/
.DS_Store .DS_Store
.lsp .lsp
.cpcache .cpcache
/.idea/
/clj-test-containers.iml

View file

@ -1,6 +1,46 @@
# 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.7.4] - 2022-11-16
### Changed
- [#64](https://github.com/javahippie/clj-test-containers/pull/64): Fix container path parameter for copy-file-to-container! in README
- [#65](https://github.com/javahippie/clj-test-containers/issues/65): Upgrade to testcontainers-java 1.17.6
- [#66](https://github.com/javahippie/clj-test-containers/issues/66): Remove usage of deprecated API calls for cleanup
- [#67](https://github.com/javahippie/clj-test-containers/issues/67): Update Dependencies
## [0.7.3] - 2202-09-30
### Changed
- [#63](https://github.com/javahippie/clj-test-containers/issues/63): Upgrade to testcontainers-java 1.17.4
## [0.7.2] - 2202-07-03
### Changed
- [#61](https://github.com/javahippie/clj-test-containers/issues/61): Upgrade to testcontainers-java 1.17.3
## [0.7.1] - 2202-06-12
### Added
- [#58](https://github.com/javahippie/clj-test-containers/issues/58): Add new `allowInsecure` flag to the HTTP wait strategy
### Changed
- [#57](https://github.com/javahippie/clj-test-containers/issues/57): Upgrade to testcontainers-java 1.17.2
## [0.7.0] - 2022-04-23
### Changed
- [#56](https://github.com/javahippie/clj-test-containers/issues/56): Update to Testcontainers 1.17.1
## [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
- [#41](https://github.com/javahippie/clj-test-containers/issues/41): Complete logging mechanisms
## [0.5.0] - 2021-08-18
### Changed
- [#49](https://github.com/javahippie/clj-test-containers/issues/49): Updated to latest Testcontainers version
- [#50](https://github.com/javahippie/clj-test-containers/issues/50): supress Reflection warnings
- [#52](https://github.com/javahippie/clj-test-containers/pull/52): Fix unbalanced parens and braces
## [0.4.0] - 2020-12-16 ## [0.4.0] - 2020-12-16
### Added ### Added
- [#43](https://github.com/javahippie/clj-test-containers/issues/43): Provide a way to remove all running testcontainers instances in a REPL session - [#43](https://github.com/javahippie/clj-test-containers/issues/43): Provide a way to remove all running testcontainers instances in a REPL session
@ -12,7 +52,7 @@ All notable changes to this project will be documented in this file. This change
## [0.3.0] - 2020-10-23 ## [0.3.0] - 2020-10-23
### Added ### Added
- [#25](https://github.com/javahippie/clj-test-containers/issues/25): Add support for a container wait stategy - [#25](https://github.com/javahippie/clj-test-containers/issues/25): Add support for a container wait strategy
- [#35](https://github.com/javahippie/clj-test-containers/issues/35): Add support for docker version 2.4.0 - upgrading testcontainers-java version - [#35](https://github.com/javahippie/clj-test-containers/issues/35): Add support for docker version 2.4.0 - upgrading testcontainers-java version
- [#27](https://github.com/javahippie/clj-test-containers/issues/27): Add support for accessing container logs - [#27](https://github.com/javahippie/clj-test-containers/issues/27): Add support for accessing container logs
- [#38](https://github.com/javahippie/clj-test-containers/pull/38): Add type hints to silence reflection warnings - [#38](https://github.com/javahippie/clj-test-containers/pull/38): Add type hints to silence reflection warnings

244
README.md
View file

@ -1,30 +1,35 @@
# clj-test-containers # clj-test-containers
[![javahippie](https://circleci.com/gh/javahippie/clj-test-containers.svg?style=svg)](<LINK>)
[![Clojars Project](http://clojars.org/clj-test-containers/latest-version.svg)](http://clojars.org/clj-test-containers) [![Clojars Project](http://clojars.org/clj-test-containers/latest-version.svg)](http://clojars.org/clj-test-containers)
## What it is ## What it is
This library is a lightweight wrapper around the [Testcontainers Java library](https://www.testcontainers.org/). This library is a lightweight wrapper around the [Testcontainers Java library](https://www.testcontainers.org/).
## What it isn't ## What it isn't
This library does not provide tools to include testcontainers in your testing lifecycle. As there are many different test tools with different approaches to testing in the clojure world, handling the lifecycle is up to you.
This library does not provide tools to include testcontainers in your testing lifecycle. As there are many different
test tools with different approaches to testing in the clojure world, handling the lifecycle is up to you.
## Integration with test runners
There is an [experimental kaocha plugin](https://github.com/lambdaschmiede/kaocha-testcontainers-plugin) you can try out
## Usage ## Usage
The library provides a set of functions to interact with the testcontainers. A simple example, how to create a container with a Docker label, could look like this: The library provides a set of functions to interact with the testcontainers. A simple example, how to create a container
with a Docker label, could look like this:
```clojure ```clojure
(require '[clj-test-containers.core :as tc]) (require '[clj-test-containers.core :as tc])
(def container (-> (tc/create {:image-name "postgres:12.1" (def container (-> (tc/create {:image-name "postgres:12.1"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "verysecret"}}) :env-vars {"POSTGRES_PASSWORD" "verysecret"}})
(tc/bind-filesystem! {:host-path "/tmp" (tc/bind-filesystem! {:host-path "/tmp"
:container-path "/opt" :container-path "/opt"
:mode :read-only}) :mode :read-only})
(tc/start!)) (tc/start!)))
(do-database-testing (:host container) (do-database-testing (:host container)
(get (:mapped-ports container) 5432)) (get (:mapped-ports container) 5432))
@ -38,10 +43,10 @@ If you'd rather create a container from a Dockerfile in your project, it could l
(require '[clj-test-containers.core :as tc]) (require '[clj-test-containers.core :as tc])
(def container (-> (tc/create-from-docker-file {:env-vars {"FOO" "bar"} (def container (-> (tc/create-from-docker-file {:env-vars {"FOO" "bar"}
:exposed-ports [80] :exposed-ports [80]
:docker-file "resources/Dockerfile"}) :docker-file "resources/Dockerfile"})
(tc/start!)) (tc/start!)))
``` ```
If you prefer to use prebuilt containers from the Testcontainers project, you can do it like this If you prefer to use prebuilt containers from the Testcontainers project, you can do it like this
@ -50,28 +55,29 @@ If you prefer to use prebuilt containers from the Testcontainers project, you ca
(require '[clj-test-containers.core :as tc]) (require '[clj-test-containers.core :as tc])
(:import [org.testcontainers.containers PostgreSQLContainer]) (:import [org.testcontainers.containers PostgreSQLContainer])
(def container (-> (tc/init {:container (PostgreSQLContainer. "postgres:12.2") (def container (-> (tc/init {:container (PostgreSQLContainer. "postgres:12.2")
:exposed-ports [5432]}) :exposed-ports [5432]})
(tc/start!)) (tc/start!)))
``` ```
## Functions and Properties ## Functions and Properties
### create ### create
Creates a testcontainers instance from a given Docker label and returns them 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](doc/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](doc/log-strategies.md) to use, e.g. {:log-strategy string} |
#### Result: #### Result:
@ -87,50 +93,52 @@ Creates a testcontainers instance from a given Docker label and returns them
#### Example: #### Example:
```clojure ```clojure
(create {:image-name "alpine:3.2" (create {:image-name "alpine:3.2"
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"} :env-vars {"MAGIC_NUMBER" "42"}
:network (create-network) :network (create-network)
:network-aliases ["api-server"] :network-aliases ["api-server"]
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
``` ```
#### Example using wait-for and healthcheck: #### Example using wait-for and healthcheck:
```clojure ```clojure
(create {:image-name "alpine:3.2" (create {:image-name "alpine:3.2"
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"} :env-vars {"MAGIC_NUMBER" "42"}
:network (create-network) :network (create-network)
:network-aliases ["api-server"] :network-aliases ["api-server"]
:wait-for {:strategy :health} :wait-for {:strategy :health}
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
``` ```
### init ### init
Initializes a given Testcontainer, which was e.g. provided by a library 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](doc/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](doc/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 |
@ -142,28 +150,29 @@ Initializes a given Testcontainer, which was e.g. provided by a library
```clojure ```clojure
;; PostgreSQL container needs a separate library! This is not included. ;; PostgreSQL container needs a separate library! This is not included.
(init {:container (org.testcontainers.containers.PostgreSQLContainer) (init {:container (org.testcontainers.containers.PostgreSQLContainer)
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"} :env-vars {"MAGIC_NUMBER" "42"}
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
``` ```
#### Example using wait-for and a log message: #### Example using wait-for and a log message:
```clojure ```clojure
;; PostgreSQL container needs a separate library! This is not included. ;; PostgreSQL container needs a separate library! This is not included.
(init {:container (org.testcontainers.containers.PostgreSQLContainer) (init {:container (org.testcontainers.containers.PostgreSQLContainer)
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"} :env-vars {"MAGIC_NUMBER" "42"}
:wait-for {:strategy :log :message "accept connections"} :wait-for {:strategy :log :message "accept connections"}
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
``` ```
### create-from-docker-file ### create-from-docker-file
Creates a testcontainer from a Dockerfile Creates a testcontainer from a Dockerfile
#### Config parameters: #### Config parameters:
@ -176,9 +185,10 @@ 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](doc/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](doc/log-strategies.md) to use, e.g. {:log-strategy string} |
| | | | | | | |
#### Result: #### Result:
| Key | Type | Description | | Key | Type | Description |
@ -193,17 +203,18 @@ Creates a testcontainer from a Dockerfile
#### Example: #### Example:
```clojure ```clojure
(create-from-docker-file {:docker-file "resources/Dockerfile" (create-from-docker-file {:docker-file "resources/Dockerfile"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"MAGIC_NUMBER" "42"} :env-vars {"MAGIC_NUMBER" "42"}
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
``` ```
--- ---
### start! ### start!
Starts the Testcontainer, which was defined by `create` Starts the Testcontainer, which was defined by `create`
#### Config parameters: #### Config parameters:
@ -215,6 +226,7 @@ Starts the Testcontainer, which was defined by `create`
| | | | | | | |
#### 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) |
@ -227,17 +239,17 @@ Starts the Testcontainer, which was defined by `create`
#### Example: #### Example:
```clojure ```clojure
(def container (create {:image-name "alpine:3.2" (def container (create {:image-name "alpine:3.2"
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"}) :env-vars {"MAGIC_NUMBER" "42"}}))
(start! container) (start! container)
``` ```
--- ---
### stop! ### stop!
Stops the Testcontainer, which was defined by `create` Stops the Testcontainer, which was defined by `create`
#### Config parameters: #### Config parameters:
@ -248,14 +260,15 @@ Stops the Testcontainer, which was defined by `create`
| `container-config` | Map, mandatory | Return value of the `create` function | | `container-config` | Map, mandatory | Return value of the `create` function |
#### Result: #### Result:
The `container-config` The `container-config`
#### Example: #### Example:
```clojure ```clojure
(def container (create {:image-name "alpine:3.2" (def container (create {:image-name "alpine:3.2"
:exposed-ports [80] :exposed-ports [80]
:env-vars {"MAGIC_NUMBER" "42"}) :env-vars {"MAGIC_NUMBER" "42"}}))
(start! container) (start! container)
(stop! container) (stop! container)
@ -263,37 +276,37 @@ The `container-config`
--- ---
### map-classpath-resource! ### map-classpath-resource!
Maps a resource from your classpath into the containers file system Maps a resource from your classpath into the containers file system
#### Config parameters: #### Config parameters:
| Key | Type | Description | | Key | Type | Description |
| ------------- |:------------- | :-----| | ------------- |:------------- | :-----|
| First parameter: | | | | First parameter: | | |
| `container-config`| Map, mandatory | Return value of the `create` function | | `container-config`| Map, mandatory | Return value of the `create` function |
| Second parameter: | | | | Second parameter: | | |
| `:resource-path` | String, mandatory | Path of your classpath resource | | `:resource-path` | String, mandatory | Path of your classpath resource |
| `:container-path` | String, mandatory | Path, to which the resource should be mapped | | `:container-path` | String, mandatory | Path, to which the resource should be mapped |
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` | | `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
#### Result: #### Result:
The `container-config` The `container-config`
#### Example: #### Example:
```clojure ```clojure
(map-classpath-resource! container {:resource-path "test.sql" (map-classpath-resource! container {:resource-path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:mode :read-only}) :mode :read-only})
``` ```
--- ---
### bind-filesystem! ### bind-filesystem!
Binds a path from your local filesystem into the Docker container as a volume Binds a path from your local filesystem into the Docker container as a volume
#### Config parameters: #### Config parameters:
@ -307,51 +320,51 @@ Binds a path from your local filesystem into the Docker container as a volume
| `:container-path` | String, mandatory | Path, to which the resource should be mapped | | `:container-path` | String, mandatory | Path, to which the resource should be mapped |
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` | | `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
#### Result: #### Result:
The `container-config` The `container-config`
#### Example: #### Example:
```clojure ```clojure
(bind-filesystem! container {:host-path "." (bind-filesystem! container {:host-path "."
:container-path "/opt" :container-path "/opt"
:mode :read-only}) :mode :read-only})
``` ```
--- ---
### copy-file-to-container! ### copy-file-to-container!
Copies a file from your filesystem or classpath into the running container Copies a file from your filesystem or classpath into the running container
#### Config parameters: #### Config parameters:
| Key | Type | Description | | Key | Type | Description |
| ------------- |:------------- | :-----| | ------------- |:------------- | :-----|
| First parameter: | | | | First parameter: | | |
| `container-config`| Map, mandatory | Return value of the `create` function | | `container-config`| Map, mandatory | Return value of the `create` function |
| Second parameter: | | | | Second parameter: | | |
| `:path` | String, mandatory | Path to a classpath resource *or* file on your filesystem | | `:path` | String, mandatory | Path to a classpath resource *or* file on your filesystem |
| `:host-path` | String, mandatory | Path, to which the file should be copied | | `:container-path` | String, mandatory | Path, to which the file should be copied |
| `:type` | Keyword, mandatory | `:classpath-resource` or `:host-path` | | `:type` | Keyword, mandatory | `:classpath-resource` or `:host-path` |
#### Result: #### Result:
The `container-config` The `container-config`
#### Example: #### Example:
```clojure ```clojure
(copy-file-to-container! container {:path "test.sql" (copy-file-to-container! container {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :host-path}) :type :host-path})
``` ```
--- ---
### execute-command! ### execute-command!
Executes a command in the running container, and returns the result Executes a command in the running container, and returns the result
#### Config parameters: #### Config parameters:
@ -363,9 +376,8 @@ Executes a command in the running container, and returns the result
| Second parameter: |   |   | | Second parameter: |   |   |
| `command` | Vector with Strings, mandatory | A vector containing the command and its parameters | | `command` | Vector with Strings, mandatory | A vector containing the command and its parameters |
#### Result: #### Result:
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- | :----- |
| `:exit-code` | int | Exit code of the executed command | | `:exit-code` | int | Exit code of the executed command |
@ -379,8 +391,8 @@ Executes a command in the running container, and returns the result
``` ```
### create-network ### create-network
Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver
Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver
#### Config parameters: #### Config parameters:
@ -389,9 +401,8 @@ Creates a network. The optional map accepts config values for enabling ipv6 and
| `:ipv6` | boolean | Should the network enable IPv6? | | `:ipv6` | boolean | Should the network enable IPv6? |
| `:driver` | String | The network driver used by Docker, e.g. `bridge` or `host` | | `:driver` | String | The network driver used by Docker, e.g. `bridge` or `host` |
#### Result: #### Result:
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- | :----- |
| `:network` | `org.testcontainers.containers.Network` | The instance of the network | | `:network` | `org.testcontainers.containers.Network` | The instance of the network |
@ -403,35 +414,42 @@ Creates a network. The optional map accepts config values for enabling ipv6 and
```clojure ```clojure
;;Create with config ;;Create with config
(create-network {:ipv6 false (create-network {:ipv6 false
:driver "overlay") :driver "overlay"})
;;Create with default config ;;Create with default config
(create-network) (create-network)
``` ```
### perform-cleanup! ### perform-cleanup!
Stops and removes all containers which were created in the JVM, including the REPL session you are in. This is helpful, if you are exploring functionality with containers in the REPL, and create lots of instances on the fly without stopping them. Testcontainers will remove all containers upon JVM shutdown, but the REPL keeps the JVM alive for a long time.
#### Config parameters: Stops and removes all containers which were created in the JVM, including the REPL session you are in. This is helpful,
if you are exploring functionality with containers in the REPL, and create lots of instances on the fly without stopping
them. Testcontainers will remove all containers upon JVM shutdown, but the REPL keeps the JVM alive for a long time.
#### Config parameters:
None None
#### Result: #### Result:
None None
#### Example: #### Example:
```clojure ```clojure
(perform-cleanup!) (perform-cleanup!)
``` ```
### dump-logs ### dump-logs
Call on a started container.
Provided logging was enabled for a container, returns the given log presentation, e.g. as a string Call on a started container. Provided logging was enabled for a container, returns the given log presentation, e.g. as a
string
| Key | Type | Description | | Key | Type | Description |
| ------------- | :------------- | :----- | | ------------- | :------------- | :----- |
| `container-config` | Map, mandatory | The configuration describing the container for which the log should be retrieved | | `container-config` | Map, mandatory | The configuration describing the container for which the log should be retrieved |
## License ## License
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version. Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

View file

@ -1,26 +1,27 @@
{:paths ["src" "resources"] {:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"} :deps {org.clojure/clojure {:mvn/version "1.10.3"}
org.testcontainers/testcontainers {:mvn/version "1.15.0"}} org.testcontainers/testcontainers {:mvn/version "1.19.7"}}
:aliases {:dev {:extra-paths "dev-src"} :aliases {:dev {:extra-paths "dev-src"}
:test {:extra-paths ["test" "test/resources"] :test {:extra-paths ["test" "test/resources"]
:extra-deps {expound/expound {:mvn/version "0.8.5"} :extra-deps {expound/expound {:mvn/version "0.9.0"}
lambdaisland/kaocha {:mvn/version "1.0.700"} lambdaisland/kaocha {:mvn/version "1.88.1376"}
lambdaisland/kaocha-cloverage {:mvn/version "1.0.63"} lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}
lambdaisland/kaocha-junit-xml {:mvn/version "0.0.76"} lambdaisland/kaocha-junit-xml {:mvn/version "1.17.101"}
org.clojure/test.check {:mvn/version "1.1.0"} org.clojure/test.check {:mvn/version "1.1.1"}
org.testcontainers/postgresql {:mvn/version "1.14.3"}}} org.testcontainers/postgresql {:mvn/version "1.19.7"}}}
:test-runner {:extra-paths ["test" "test/resources"] :test-runner {:extra-paths ["test" "test/resources"]
:extra-deps {expound/expound {:mvn/version "0.8.5"} :extra-deps {expound/expound {:mvn/version "0.9.0"}
lambdaisland/kaocha {:mvn/version "1.0.700"} lambdaisland/kaocha {:mvn/version "1.88.1376"}
lambdaisland/kaocha-cloverage {:mvn/version "1.0.63"} lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}
lambdaisland/kaocha-junit-xml {:mvn/version "0.0.76"} lambdaisland/kaocha-junit-xml {:mvn/version "1.17.101"}
org.clojure/test.check {:mvn/version "1.1.0"} org.clojure/test.check {:mvn/version "1.1.1"}
org.testcontainers/postgresql {:mvn/version "1.14.3"}} orchestra {:mvn/version "2021.01.01-1"}
:main-opts ["-m" "kaocha.runner" "--reporter" "kaocha.report/documentation"]} org.testcontainers/postgresql {:mvn/version "1.19.7"}}
:main-opts ["-m" "kaocha.runner" "--reporter" "kaocha.report/documentation"]}
:cljstyle {:extra-deps {mvxcvi/cljstyle {:mvn/version "0.13.0" :cljstyle {:extra-deps {mvxcvi/cljstyle {:mvn/version "0.16.630"
:exclusions [org.clojure/clojure]}} :exclusions [org.clojure/clojure]}}
:main-opts ["-m" "cljstyle.main" "check"]}}} :main-opts ["-m" "cljstyle.main" "check"]}}}

View file

@ -1,27 +1,38 @@
# Introduction # Introduction
## Who is this library for? ## Who is this library for?
This library is meant for Clojure developers who want to write integration tests without having to worry about the infrastructure of the application.
This library is meant for Clojure developers who want to write integration tests without having to worry about the
infrastructure of the application.
## What are Testcontainers? ## What are Testcontainers?
Depending on the complexity of your application, setting up the infrastructure for integration tests is not a simple task. Even if you only need a single database for the integration tests, you need to make it available to every system that executes the tests. But often, one database is not enough and you need to integrate with Webservices, Message Queues, Search Indexes, Caches… Testcontainers try to solve this problem: Very simply put, the testcontainers Java library provides an interface to interact with Docker and enables developers to easily bring up Docker containers for executing tests, and tearing them down again, afterwards. See more at [https://www.testcontainers.org/](https://www.testcontainers.org/)
Depending on the complexity of your application, setting up the infrastructure for integration tests is not a simple
task. Even if you only need a single database for the integration tests, you need to make it available to every system
that executes the tests. But often, one database is not enough and you need to integrate with Webservices, Message
Queues, Search Indexes, Caches… Testcontainers try to solve this problem: Very simply put, the testcontainers Java
library provides an interface to interact with Docker and enables developers to easily bring up Docker containers for
executing tests, and tearing them down again, afterwards. See more
at [https://www.testcontainers.org/](https://www.testcontainers.org/)
## Why do we need a Clojure wrapper? ## Why do we need a Clojure wrapper?
As Testcontainers is a Java library, we do not *need* a Clojure wrapper to work with it. It is completely possible to use it directly via Java interop code:
```java As Testcontainers is a Java library, we do not *need* a Clojure wrapper to work with it. It is completely possible to
(-> (org.testcontainers.containers.GenericContainer. "postgres:12.2") use it directly via Java interop code:
(.withExposedPorts (into-array Integer [(int 5432)]))
(.withEnv "POSTGRES_PASSSWORD" pw)
(.start))
```
But doing so is quite wordy and requires developers to use a lot of methods that manipulate a java instance.
```clojure ```clojure
(-> (tc/create {:image-name "postgres:12.1" (-> (org.testcontainers.containers.GenericContainer. "postgres:12.2")
:exposed-ports [5432] (.withExposedPorts (into-array Integer [(int 5432)]))
:env-vars {"POSTGRES_PASSWORD" pw}}) (.withEnv "POSTGRES_PASSSWORD" pw)
tc/start!) (.start))
```
But doing so is quite wordy and requires developers to use a lot of methods that manipulate a java instance.
```clojure
(-> (tc/create {:image-name "postgres:12.1"
:exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" pw}})
tc/start!)
``` ```

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 `:log` key. This function enables the
dumping of the logs when passed to the `dump-logs` function.
Example:
```clojure
{:log-to {: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-to {:log-strategy :fn
:function (fn [log-line] (println "From Container: " log-line))}}
```

View file

@ -2,7 +2,8 @@
The list of functions included in the library until now is as following: The list of functions included in the library until now is as following:
* `create`: Creates a new Testcontainers instance, accepts parameters for mapped ports, environment variables and a start command * `create`: Creates a new Testcontainers instance, accepts parameters for mapped ports, environment variables and a
start command
* `map-classpath-resource!`: Maps a resource from your classpath into the containers file system * `map-classpath-resource!`: Maps a resource from your classpath into the containers file system
* `bind-filesystem!`: Binds a path from your local filesystem into the Docker container as a volume * `bind-filesystem!`: Binds a path from your local filesystem into the Docker container as a volume
* `start!`: Starts the container * `start!`: Starts the container
@ -10,80 +11,88 @@ The list of functions included in the library until now is as following:
* `copy-file-to-container!`: Copies a file from your filesystem or classpath into the running container * `copy-file-to-container!`: Copies a file from your filesystem or classpath into the running container
* `execute-command!`: Executes a command in the running container, and returns the result * `execute-command!`: Executes a command in the running container, and returns the result
The functions accept and return a map structure, which enables us to operate them on the same data structure in a consistent way. The example shown with Java Interop above would look like this, when using the wrapped functions: The functions accept and return a map structure, which enables us to operate them on the same data structure in a
consistent way. The example shown with Java Interop above would look like this, when using the wrapped functions:
```clojure ```clojure
(require '[clj-test-containers.core :as tc]) (require '[clj-test-containers.core :as tc])
(deftest db-integration-test (deftest db-integration-test
(testing "A simple PostgreSQL integration test" (testing "A simple PostgreSQL integration test"
(let [pw "db-pass" (let [pw "db-pass"
postgres (-> (tc/create {:image-name "postgres:12.1" postgres (-> (tc/create {:image-name "postgres:12.1"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" pw}}))] :env-vars {"POSTGRES_PASSWORD" pw}}))]
(tc/start! postgres) (tc/start! postgres)
(let [datasource (jdbc/get-datasource {:dbtype "postgresql" (let [datasource (jdbc/get-datasource {:dbtype "postgresql"
:dbname "postgres" :dbname "postgres"
:user "postgres" :user "postgres"
:password pw :password pw
:host (:host postgres) :host (:host postgres)
:port (get (:mapped-ports container) 5432)})] :port (get (:mapped-ports container) 5432)})]
(is (= [{:one 1 :two 2}] (with-open [connection (jdbc/get-connection datasource)] (is (= [{:one 1 :two 2}] (with-open [connection (jdbc/get-connection datasource)]
(jdbc/execute! connection ["SELECT 1 ONE, 2 TWO"]))))) (jdbc/execute! connection ["SELECT 1 ONE, 2 TWO"])))))
(tc/stop! postgres)))) (tc/stop! postgres))))
``` ```
## Executing commands inside the container ## Executing commands inside the container
The `execute-command` function enables us to run commands inside the container. The function accepts a container and a vector of strings as parameters, with the first string being the command, followed by potential parameters. The function returns a map with an `:exit-code`, `:stdout` and `:stderr`: The `execute-command` function enables us to run commands inside the container. The function accepts a container and a
vector of strings as parameters, with the first string being the command, followed by potential parameters. The function
returns a map with an `:exit-code`, `:stdout` and `:stderr`:
```clojure ```clojure
(execute-command! container ["whoami"]) (execute-command! container ["whoami"])
> {:exit-code 0 > {:exit-code 0
:stdout "root"} :stdout "root"}
``` ```
## Mounting files into the container ## Mounting files into the container
For some test scenarios it can be helpful to mount files from your filesystem or the resource path of your application into the container, before it is started. This could be helpful if you want to load a dumpfile into your database, before executing the tests. You can do this with the functions `map-classpath-resource!` and `bind-filesystem!`: For some test scenarios it can be helpful to mount files from your filesystem or the resource path of your application
into the container, before it is started. This could be helpful if you want to load a dumpfile into your database,
before executing the tests. You can do this with the functions `map-classpath-resource!` and `bind-filesystem!`:
```clojure ```clojure
(map-classpath-resource! container (map-classpath-resource! container
{:resource-path "test.sql" {:resource-path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:mode :read-only}) :mode :read-only})
``` ```
```clojure ```clojure
(bind-filesystem! {:host-path "." (bind-filesystem! {:host-path "."
:container-path "/opt" :container-path "/opt"
:mode :read-only}) :mode :read-only})
``` ```
It is also possible to copy files into a running container instance: It is also possible to copy files into a running container instance:
```clojure ```clojure
(copy-file-to-container! {:path "test.sql" (copy-file-to-container! {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :host-path}) :type :host-path})
``` ```
# Fixtures for Clojure Test # Fixtures for Clojure Test
The above example creates a Testcontainers instance in the test function itself. If we did this for all of our integration tests, this would spin up a docker image for every test function, and tear it down again, afterwards. If we want to create one image for all tests in the same namespace, we can use Clojures [`use-fixtures`](https://clojuredocs.org/clojure.test/use-fixtures) function, which is described like this:
> Wrap test runs in a fixture function to perform setup and The above example creates a Testcontainers instance in the test function itself. If we did this for all of our
teardown. Using a fixture-type of :each wraps every test integration tests, this would spin up a docker image for every test function, and tear it down again, afterwards. If we
individually, while :once wraps the whole run in a single function. want to create one image for all tests in the same namespace, we can use
Clojures [`use-fixtures`](https://clojuredocs.org/clojure.test/use-fixtures) function, which is described like this:
Assuming we have a function `initialize-db!` in our application which sets up a JDBC connection and stores it in an atom, a fixture for Testcontainers could look like this: > Wrap test runs in a fixture function to perform setup and teardown. Using a fixture-type of :each wraps every test individually, while :once wraps the whole run in a single function.
Assuming we have a function `initialize-db!` in our application which sets up a JDBC connection and stores it in an
atom, a fixture for Testcontainers could look like this:
```clojure ```clojure
(use-fixtures :once (fn [f] (use-fixtures :once (fn [f]
(let [{pw "apassword" (let [pw "apassword"
postgres (tc/start! (tc/create {:image-name "postgres:12.2" postgres (tc/start! (tc/create {:image-name "postgres:12.2"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" pw}}))}] :env-vars {"POSTGRES_PASSWORD" pw}}))]
(my-app/initialize-db! {:dbtype "postgresql" (my-app/initialize-db! {:dbtype "postgresql"
:dbname "postgres" :dbname "postgres"
:user "postgres" :user "postgres"

97
doc/wait-strategies.md Normal file
View file

@ -0,0 +1,97 @@
# Wait strategies
Testcontainers provides a set of wait strategies which help us determine if and when a container is ready to accept
requests. Wait strategies are defined for the `:wait-for` key in the container configuration with the
key `:wait-strategy` determining which strategy to select. The `start!` function will block until the container is ready
and continue processing afterwards.
## HTTP Wait Strategy
The HTTP wait strategy will perform an HTTP call according to the following configuration and only continue if the
criteria is met.
| Parameter | Type | Description |
|-------------------|------------|---------------------------------------------------------|
| wait-strategy | Keyword | :http |
| path | String | The HTTP path to access |
| port | int | The HTTP port to access |
| method | String | The HTTP method to use (get, post, put...) |
| status-codes | seq of int | The status codes which mark a successful request |
| tls | boolean | Should TLS be used? |
| allow-insecure | boolean | Should insecure HTTP endpoints be trusted? |
| read-timeout | long | The duration in seconds the HTTP may take |
| basic-credentials | Map | {:username "User" :password "Password"} |
| headers | Map | HTTP Headers, e.g. {"Accept" "application/json"} |
| startup-timeout | long | The duration in seconds the container may take to start |
Example:
```clojure
(tc/create {:image-name "alpine:3.5"
:command ["/bin/sh"
"-c"
"while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]
:exposed-ports [8080]
:wait-for {:wait-strategy :http
:path "/"
:port 8080
:method "GET"
:status-codes [200]
:tls false
:read-timout 5
:headers {"Accept" "text/plain"}
:startup-timeout 20}})
```
## Health Wait Strategy
The Health Wait Strategy uses a health check defined in the Dockerfile to determine if the container is ready.
| Parameter | Type | Description |
|-----------------|---------|---------------------------------------------------------|
| wait-strategy | Keyword | :port |
| startup-timeout | long | The duration in seconds the container may take to start |
```clojure
(tc/create {:image-name "alpine:3.5"
:exposed-ports [8080]
:wait-for {:wait-strategy :health
:startup-timeout 20}})
```
## Log Wait Strategy
The Log Wait Strategy waits until a certain phrase appears in the Docker containers' log.
| Parameter | Type | Description |
|-----------------|---------|---------------------------------------------------------|
| wait-strategy | Keyword | :log |
| message | String | A substring of an expected line in the containers log |
| times | int | The number of times the predicate has to match |
| startup-timeout | long | The duration in seconds the container may take to start |
```clojure
(tc/create {:image-name "postgres:12.2"
:exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}
:wait-for {:wait-strategy :log
:message "accept connections"
:startup-timeout 10}})
```
## Port Wait Strategy
This strategy is the default selcted by Testcontainers if no wait strategy was defined. It waits for the first port in
the containers port mapping to be opened. It does not accept any parameters beside the `startup-timeout`
| Parameter | Type | Description |
|-----------------|---------|---------------------------------------------------------|
| wait-strategy | Keyword | :port |
| startup-timeout | long | The duration in seconds the container may take to start |
```clojure
(tc/create {:image-name "postgres:12.2"
:exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}
:wait-for {:wait-strategy :port}})
```

View file

@ -1,29 +1,34 @@
(defproject clj-test-containers "0.4.0" (defproject testcontainers-clj "unspecified"
:description "A lightweight, unofficial wrapper around the Testcontainers Java library" :description "A lightweight, official wrapper around the Testcontainers Java library"
:url "https://github.com/javahippie/clj-test-containers" :url "https://github.com/testcontainers/testcontainers-clj"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"} :url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.10.1"] :dependencies [[org.clojure/clojure "1.10.3"]
[org.testcontainers/testcontainers "1.15.1"]] [org.testcontainers/testcontainers "1.19.7"]]
:aliases {"test" ["run" "-m" "kaocha.runner"] :aliases {"test" ["run" "-m" "kaocha.runner"]
"cljstyle" ["run" "-m" "cljstyle.main"]} "cljstyle" ["run" "-m" "cljstyle.main"]}
:plugins [[jainsahab/lein-githooks "1.0.0"]] :plugins [[jainsahab/lein-githooks "1.0.0"]]
:profiles {:dev {:dependencies [[expound "0.8.5"] :profiles {:dev {:dependencies [[expound "0.9.0"]
[lambdaisland/kaocha "1.0.641"] [lambdaisland/kaocha "1.88.1376"]
[lambdaisland/kaocha-cloverage "1.0-45"] [lambdaisland/kaocha-cloverage "1.1.89"]
[lambdaisland/kaocha-junit-xml "0.0.76"] [lambdaisland/kaocha-junit-xml "1.17.101"]
[lambdaisland/kaocha-junit-xml "0.0.76"] [mvxcvi/cljstyle "0.16.630" :exclusions [org.clojure/clojure]]
[mvxcvi/cljstyle "0.14.0" :exclusions [org.clojure/clojure]] [org.clojure/test.check "1.1.1"]
[org.clojure/test.check "1.1.0"] [orchestra "2021.01.01-1"]
[org.clojure/tools.namespace "1.0.0"] [org.clojure/tools.namespace "1.5.0"]
[org.testcontainers/postgresql "1.15.1"] [org.testcontainers/postgresql "1.19.7"]
[com.fzakaria/slf4j-timbre "0.3.20"]] [com.fzakaria/slf4j-timbre "0.4.1"]
:source-paths ["dev-src"]}} [nrepl "1.0.0"]]
:source-paths ["dev-src"]}
:release {:deploy-repositories [["maven" {:url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
:username :env/ossrh_username
:password :env/ossrh_password
:sign-releases false}]]}}
:target-path "target/%s") :target-path "target/%s")

View file

@ -1,23 +1,28 @@
(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]) [clojure.string])
(:import (:import
(java.nio.file (java.nio.file
Paths) Paths)
(org.testcontainers.containers (java.time
BindMode Duration)
GenericContainer (org.testcontainers DockerClientFactory)
Network) (org.testcontainers.containers
(org.testcontainers.containers.output BindMode
ToStringConsumer) GenericContainer
(org.testcontainers.containers.wait.strategy Network)
Wait) (org.testcontainers.containers.output
(org.testcontainers.images.builder BaseConsumer
ImageFromDockerfile) OutputFrame
(org.testcontainers.utility ToStringConsumer)
MountableFile))) (org.testcontainers.containers.wait.strategy
Wait)
(org.testcontainers.images.builder
ImageFromDockerfile)
(org.testcontainers.utility
MountableFile)))
(defn- resolve-bind-mode (defn- resolve-bind-mode
(^BindMode [bind-mode] (^BindMode [bind-mode]
@ -25,99 +30,149 @@
BindMode/READ_WRITE BindMode/READ_WRITE
BindMode/READ_ONLY))) BindMode/READ_ONLY)))
(defn- reaper-instance (defonce started-instances (atom #{}))
[]
(org.testcontainers.utility.ResourceReaper/instance))
(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\"
container) :startup-timeout 60}}
``` container)
```
## Health Strategy ## Health Strategy
The :health strategy only accepts a true or false value. This enables support The :health strategy enables support for Docker's healthcheck feature,
for Docker's healthcheck feature, whereby you can directly leverage the whereby you can directly leverage the healthy state of your container as your wait condition.
healthy state of your container as your wait condition.
Example: Example:
```clojure ```clojure
(wait {:wait-strategy :health :true} container) (wait {:wait-strategy :health
``` :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\"} container) :message \"accept connections\"
```" :startup-timeout 60} container)
:wait-strategy) ```
## Port Strategy
The port strategy waits for the first of the mapped ports to be opened. It only accepts the startup-timeout
value as a parameter.
Example:
```clojure
(wait {:wait-strategy :port
:startup-timeout 60} container
```"
:wait-strategy)
(defmethod wait :http (defmethod wait :http
[{:keys [path [{:keys [path
port port
method
status-codes status-codes
tls tls
allow-insecure
read-timeout read-timeout
basic-credentials] :as options} basic-credentials
headers
startup-timeout] :as options}
^GenericContainer container] ^GenericContainer container]
(let [for-http (Wait/forHttp path)] (let [for-http (Wait/forHttp path)]
(when port (when port
(.forPort for-http port)) (.forPort for-http port))
(when method
(.withMethod for-http method))
(doseq [status-code status-codes] (doseq [status-code status-codes]
(.forStatusCode for-http status-code)) (.forStatusCode for-http status-code))
(when tls (when tls
(.usingTls for-http)) (.usingTls for-http))
(when allow-insecure
(.allowInsecure for-http))
(when read-timeout (when read-timeout
(.withReadTimeout for-http (java.time.Duration/ofSeconds read-timeout))) (.withReadTimeout for-http (Duration/ofSeconds read-timeout)))
(when basic-credentials (when basic-credentials
(let [{username :username password :password} basic-credentials] (let [{:keys [username password]} basic-credentials]
(.withBasicCredentials for-http username password))) (.withBasicCredentials for-http username password)))
(when headers
(.withHeaders for-http headers))
(when startup-timeout
(.withStartupTimeout for-http (Duration/ofSeconds startup-timeout)))
(.waitingFor container for-http) (.waitingFor container for-http)
{:wait-for-http (dissoc options :strategy)})) {:wait-for-http (dissoc options :strategy)}))
(defmethod wait :health (defmethod wait :health
[_ ^GenericContainer container] [{:keys [startup-timeout]} ^GenericContainer container]
(.waitingFor container (Wait/forHealthcheck)) (let [strategy (Wait/forHealthcheck)]
(when startup-timeout
(.withStartupTimeout strategy (Duration/ofSeconds startup-timeout)))
(.waitingFor container strategy))
{:wait-for-healthcheck true}) {:wait-for-healthcheck true})
(defmethod wait :log (defmethod wait :log
[{:keys [message]} ^GenericContainer container] [{:keys [message times startup-timeout]} ^GenericContainer container]
(let [log-message (str ".*" message ".*\\n")] (let [log-message (str ".*" message ".*\\n")
(.waitingFor container (Wait/forLogMessage log-message 1)) strategy (Wait/forLogMessage log-message 1)]
(when times
(.withTimes strategy times))
(when startup-timeout
(.withStartupTimeout strategy (Duration/ofSeconds startup-timeout)))
(.waitingFor container strategy)
{:wait-for-log-message log-message})) {:wait-for-log-message log-message}))
(defmethod wait :port
[{:keys [startup-timeout]} ^GenericContainer container]
(let [strategy (Wait/forListeningPort)]
(when startup-timeout
(.withStartupTimeout strategy (Duration/ofSeconds startup-timeout)))
(.waitingFor container strategy))
{:wait-for-port true})
(defmethod wait :default [_ _] nil) (defmethod wait :default [_ _] nil)
(s/fdef init (s/fdef init
@ -148,11 +203,11 @@
(when network-aliases (when network-aliases
(.setNetworkAliases container network-aliases)) (.setNetworkAliases container network-aliases))
(merge init-options {: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)
:network network} (wait wait-for container))) :network network} (wait wait-for container)))
(s/fdef create (s/fdef create
:args (s/cat :create-options ::cs/create-options) :args (s/cat :create-options ::cs/create-options)
@ -180,11 +235,11 @@
[{:keys [^GenericContainer container] :as container-config} [{:keys [^GenericContainer container] :as container-config}
{:keys [^String resource-path ^String container-path mode]}] {:keys [^String resource-path ^String container-path mode]}]
(assoc container-config (assoc container-config
:container :container
(.withClasspathResourceMapping container (.withClasspathResourceMapping container
resource-path resource-path
container-path container-path
(resolve-bind-mode mode)))) (resolve-bind-mode mode))))
(defn bind-filesystem! (defn bind-filesystem!
"Binds a source from the filesystem to the given container path. Should be "Binds a source from the filesystem to the given container path. Should be
@ -192,11 +247,11 @@
[{:keys [^GenericContainer container] :as container-config} [{:keys [^GenericContainer container] :as container-config}
{:keys [^String host-path ^String container-path mode]}] {:keys [^String host-path ^String container-path mode]}]
(assoc container-config (assoc container-config
:container :container
(.withFileSystemBind container (.withFileSystemBind container
host-path host-path
container-path container-path
(resolve-bind-mode mode)))) (resolve-bind-mode mode))))
(defn copy-file-to-container! (defn copy-file-to-container!
"If a container is not yet started, adds a mapping from mountable file to "If a container is not yet started, adds a mapping from mountable file to
@ -207,16 +262,16 @@
(let [^MountableFile mountable-file (let [^MountableFile mountable-file
(case type (case type
:classpath-resource (MountableFile/forClasspathResource path) :classpath-resource (MountableFile/forClasspathResource path)
:host-path (MountableFile/forHostPath path))] :host-path (MountableFile/forHostPath path))]
(if id (if id
(do (do
(.copyFileToContainer container mountable-file container-path) (.copyFileToContainer container mountable-file container-path)
container-config) container-config)
(assoc container-config (assoc container-config
:container :container
(.withCopyFileToContainer container (.withCopyFileToContainer container
mountable-file mountable-file
container-path))))) container-path)))))
(defn execute-command! (defn execute-command!
"Executes a command in the container, and returns the result" "Executes a command in the container, and returns the result"
@ -227,28 +282,39 @@
: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)
``` ```
"
:log-strategy) ## 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)
(defmethod log :string (defmethod log :string
[_ ^GenericContainer container] [_ ^GenericContainer container]
@ -258,14 +324,20 @@
(-> (.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"
[container-config] [container-config]
((:log container-config))) (let [log-fn (:log container-config)]
(if (some? log-fn)
(log-fn)
(throw (IllegalStateException. "You are trying to access the container logs, but have not configured a log configuration with :log-to")))))
(defn start! (defn start!
"Starts the underlying testcontainer instance and adds new values to the "Starts the underlying testcontainer instance and adds new values to the
@ -278,16 +350,14 @@
[port] [port]
[port (.getMappedPort container port)]) [port (.getMappedPort container port)])
mapped-ports (into {} (map map-port) exposed-ports) mapped-ports (into {} (map map-port) exposed-ports)
container-id (.getContainerId container) container-id ^String (.getContainerId container)
image-name (.getDockerImageName container) image-name ^String (.getDockerImageName container)
logger (log log-to container)] logger (log log-to container)]
(.registerContainerForCleanup (reaper-instance) (swap! started-instances conj {:type :container :id container-id})
container-id
image-name)
(-> container-config (-> container-config
(merge {:id container-id (merge {:id container-id
:mapped-ports mapped-ports :mapped-ports mapped-ports
:image-name image-name} logger) :image-name image-name} logger)
(dissoc :log-to)))) (dissoc :log-to))))
(defn stop! (defn stop!
@ -317,21 +387,43 @@
(let [network (.build builder) (let [network (.build builder)
network-name (.getName network)] network-name (.getName network)]
(.registerNetworkIdForCleanup (reaper-instance) network-name) (swap! started-instances conj {:type :network :id :network-name})
{:network network {:network network
:name network-name :name network-name
:ipv6 (.getEnableIpv6 network) :ipv6 (.getEnableIpv6 network)
:driver (.getDriver network)})))) :driver (.getDriver network)}))))
(def ^:deprecated init-network create-network) (def ^:deprecated init-network create-network)
(defn- remove-network! [instance]
(-> (DockerClientFactory/instance)
(.client)
(.removeNetworkCmd (:id instance))
(.exec))
instance)
(defn- stop-and-remove-container! [instance]
(let [docker-client (DockerClientFactory/instance)]
(-> docker-client
(.client)
(.stopContainerCmd (:id instance))
(.exec))
(-> docker-client
(.client)
(.removeContainerCmd (:id instance))
(.exec)))
instance)
(defn perform-cleanup! (defn perform-cleanup!
"Stops and removes all container instances which were created in the active JVM or REPL session" "Stops and removes all container instances which were created in the active JVM or REPL session"
[] []
(.performCleanup (reaper-instance))) (for [instance @started-instances]
(swap! started-instances disj (case (:type instance)
:container (stop-and-remove-container! instance)
:network (remove-network! instance)))))
;;; REPL Helpers ;; REPL Helpers
(comment (comment
(start! (create {:image-name "postgres:12.1"})) (start! (create {:image-name "postgres:12.1"}))
(perform-cleanup!)) (perform-cleanup!))

View file

@ -35,7 +35,7 @@
(s/def ::log (s/def ::log
keyword?) keyword?)
(s/def ::wait-strategy #{:http :health :log}) (s/def ::wait-strategy #{:http :health :port :log})
(s/def ::log-strategy #{:string}) (s/def ::log-strategy #{:string})

View file

@ -9,9 +9,9 @@
(deftest create-test (deftest create-test
(testing "Testing basic testcontainer generic image initialisation" (testing "Testing basic testcontainer generic image initialisation"
(let [container (sut/create {:image-name "postgres:12.2" (let [container (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
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)))
@ -20,18 +20,35 @@
(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/create {:image-name "postgres:15.3"
:log-to {:log-strategy :string}}) :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}
: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:15.3")
:exposed-ports [5432]
:log-to {:log-strategy :fn
:function #(swap! logs conj %)}}))
(is (filter #(includes? "database system is ready to accept connections" %) @logs))))
(testing "Testing log access to the container with unconfigured logger"
(let [container (sut/start! (sut/init {:container (PostgreSQLContainer. "postgres:15.3")
:exposed-ports [5432]}))]
(is (thrown? IllegalStateException (sut/dump-logs container)))))
(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:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"} :env-vars {"POSTGRES_PASSWORD" "pw"}
:wait-for {:wait-strategy :log :message "accept connections"}}) :wait-for {:wait-strategy :log
:message "accept connections"
:startup-timeout 10}})
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)))
@ -41,9 +58,47 @@
(is (nil? (:id stopped-container))) (is (nil? (:id stopped-container)))
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Testing basic testcontainer generic image initialisation with wait for host port"
(let [container (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}
:wait-for {:wait-strategy :port}})
initialized-container (sut/start! container)
stopped-container (sut/stop! container)]
(is (some? (:id initialized-container)))
(is (some? (:mapped-ports initialized-container)))
(is (some? (get (:mapped-ports initialized-container) 5432)))
(is (= (:wait-for-port initialized-container) true))
(is (nil? (:id stopped-container)))
(is (nil? (:mapped-ports stopped-container)))))
(testing "Testing basic testcontainer generic image initialisation with wait for http"
(let [container (sut/create {:image-name "bitnami/nginx:1.22"
:network-aliases ["foo"]
:exposed-ports [8080]
:wait-for {:wait-strategy :http
:path "/"
:port 8080
:method "GET"
:status-codes [200]
:headers {"Accept" "text/plain"}}})
initialized-container (sut/start! container)
stopped-container (sut/stop! container)]
(is (some? (:id initialized-container)))
(is (some? (:mapped-ports initialized-container)))
(is (some? (get (:mapped-ports initialized-container) 8080)))
(is (= (:wait-for-http initialized-container) {:headers {"Accept" "text/plain"}
:method "GET"
:path "/"
:port 8080
:status-codes [200]
:wait-strategy :http}))
(is (nil? (:id stopped-container)))
(is (nil? (:mapped-ports stopped-container)))))
(testing "Testing basic testcontainer image creation from docker file" (testing "Testing basic testcontainer image creation from docker file"
(let [container (sut/create-from-docker-file {:exposed-ports [80] (let [container (sut/create-from-docker-file {:exposed-ports [80]
:docker-file "test/resources/Dockerfile"}) :docker-file "test/resources/Dockerfile"})
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)))
@ -54,7 +109,8 @@
(testing "Executing a command in the running Docker container with a custom container" (testing "Executing a command in the running Docker container with a custom container"
(let [container (sut/init {:container (PostgreSQLContainer. "postgres:12.2")}) (let [container (sut/init {:container (PostgreSQLContainer. "postgres:15.3")
:exposed-ports [5432]})
initialized-container (sut/start! container) initialized-container (sut/start! container)
result (sut/execute-command! initialized-container ["whoami"]) result (sut/execute-command! initialized-container ["whoami"])
_stopped-container (sut/stop! container)] _stopped-container (sut/stop! container)]
@ -64,9 +120,9 @@
(deftest execute-command-in-container (deftest execute-command-in-container
(testing "Executing a command in the running Docker container" (testing "Executing a command in the running Docker container"
(let [container (sut/create {:image-name "postgres:12.2" (let [container (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
initialized-container (sut/start! container) initialized-container (sut/start! container)
result (sut/execute-command! initialized-container ["whoami"]) result (sut/execute-command! initialized-container ["whoami"])
_stopped-container (sut/stop! container)] _stopped-container (sut/stop! container)]
@ -76,12 +132,12 @@
(deftest init-volume-test (deftest init-volume-test
(testing "Testing mapping of a classpath resource" (testing "Testing mapping of a classpath resource"
(let [container (-> (sut/create {:image-name "postgres:12.2" (let [container (-> (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
(sut/map-classpath-resource! {:resource-path "test.sql" (sut/map-classpath-resource! {:resource-path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:mode :read-only})) :mode :read-only}))
initialized-container (sut/start! container) initialized-container (sut/start! container)
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -93,12 +149,12 @@
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Testing mapping of a filesystem-binding" (testing "Testing mapping of a filesystem-binding"
(let [container (-> (sut/create {:image-name "postgres:12.2" (let [container (-> (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
(sut/bind-filesystem! {:host-path "." (sut/bind-filesystem! {:host-path "."
:container-path "/opt" :container-path "/opt"
:mode :read-only})) :mode :read-only}))
initialized-container (sut/start! container) initialized-container (sut/start! container)
file-check (sut/execute-command! initialized-container ["tail" "/opt/README.md"]) file-check (sut/execute-command! initialized-container ["tail" "/opt/README.md"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -110,12 +166,12 @@
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Copying a file from the host into the container" (testing "Copying a file from the host into the container"
(let [container (-> (sut/create {:image-name "postgres:12.2" (let [container (-> (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
(sut/copy-file-to-container! {:path "test.sql" (sut/copy-file-to-container! {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :host-path})) :type :host-path}))
initialized-container (sut/start! container) initialized-container (sut/start! container)
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -128,12 +184,12 @@
(testing "Copying a file from the classpath into the container" (testing "Copying a file from the classpath into the container"
(let [container (-> (sut/create {:image-name "postgres:12.2" (let [container (-> (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
(sut/copy-file-to-container! {:path "test.sql" (sut/copy-file-to-container! {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :classpath-resource})) :type :classpath-resource}))
initialized-container (sut/start! container) initialized-container (sut/start! container)
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -145,14 +201,14 @@
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Copying a file from the host into a running container" (testing "Copying a file from the host into a running container"
(let [container (sut/create {:image-name "postgres:12.2" (let [container (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
initialized-container (sut/start! container) initialized-container (sut/start! container)
_ (sut/copy-file-to-container! initialized-container _ (sut/copy-file-to-container! initialized-container
{:path "test.sql" {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :host-path}) :type :host-path})
file-check (sut/execute-command! initialized-container file-check (sut/execute-command! initialized-container
["tail" "/opt/test.sql"]) ["tail" "/opt/test.sql"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -164,14 +220,14 @@
(is (nil? (:mapped-ports stopped-container))))) (is (nil? (:mapped-ports stopped-container)))))
(testing "Copying a file from the classpath into a running container" (testing "Copying a file from the classpath into a running container"
(let [container (sut/create {:image-name "postgres:12.2" (let [container (sut/create {:image-name "postgres:15.3"
:exposed-ports [5432] :exposed-ports [5432]
:env-vars {"POSTGRES_PASSWORD" "pw"}}) :env-vars {"POSTGRES_PASSWORD" "pw"}})
initialized-container (sut/start! container) initialized-container (sut/start! container)
_ (sut/copy-file-to-container! initialized-container _ (sut/copy-file-to-container! initialized-container
{:path "test.sql" {:path "test.sql"
:container-path "/opt/test.sql" :container-path "/opt/test.sql"
:type :classpath-resource}) :type :classpath-resource})
file-check (sut/execute-command! initialized-container file-check (sut/execute-command! initialized-container
["tail" "/opt/test.sql"]) ["tail" "/opt/test.sql"])
stopped-container (sut/stop! container)] stopped-container (sut/stop! container)]
@ -185,15 +241,15 @@
(deftest networking-test (deftest networking-test
(testing "Putting two containers into the same network and check their communication" (testing "Putting two containers into the same network and check their communication"
(let [network (sut/create-network) (let [network (sut/create-network)
server-container (sut/create {:image-name "alpine:3.5" server-container (sut/create {:image-name "alpine:3.5"
:network network :network network
:network-aliases ["foo"] :network-aliases ["foo"]
:command ["/bin/sh" :command ["/bin/sh"
"-c" "-c"
"while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]}) "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]})
client-container (sut/create {:image-name "alpine:3.5" client-container (sut/create {:image-name "alpine:3.5"
:network network :network network
:command ["top"]}) :command ["top"]})
started-server (sut/start! server-container) started-server (sut/start! server-container)
started-client (sut/start! client-container) started-client (sut/start! client-container)
response (sut/execute-command! started-client ["wget", "-O", "-", "http://foo:8080"]) response (sut/execute-command! started-client ["wget", "-O", "-", "http://foo:8080"])