Compare commits
74 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
802f18af55 | ||
|
|
1297054b77 | ||
|
|
233c3883eb | ||
|
|
49f547585e | ||
|
|
874d5756af | ||
|
|
9bf75d6387 | ||
|
|
8c807a8750 | ||
|
|
390563ac1d | ||
|
|
667a2d0850 | ||
|
|
94e582a1aa | ||
|
|
100a938250 | ||
|
|
25d1ba491b | ||
|
|
35454d3fd2 | ||
|
|
1bf2c205c1 | ||
|
|
5464fc35fa | ||
|
|
d9a42decb2 | ||
|
|
dd517094ce | ||
|
|
fffb398918 | ||
|
|
e57261ce12 | ||
|
|
ba77335753 | ||
|
|
d246351551 | ||
|
|
a635113b7e | ||
|
|
fd06f2fe6c | ||
|
|
dad4ddbd0f | ||
|
|
c42886ba56 | ||
|
|
a53cf9e271 | ||
|
|
791708bf51 | ||
|
|
28b0183339 | ||
|
|
71af11f503 | ||
|
|
6e94e86745 | ||
|
|
fc70760de7 | ||
|
|
d1036e1ff3 | ||
|
|
1dacbe6e78 | ||
|
|
0b1031c083 | ||
|
|
5c6dbed46e | ||
|
|
b13499bc7c | ||
|
|
12c4b6c1e9 | ||
|
|
ff1215e710 | ||
|
|
d6e4b065cd | ||
|
|
7be77af93b | ||
|
|
2b8d4a911e | ||
|
|
51314ae2e7 | ||
|
|
13c859dee2 | ||
|
|
ea6637dc02 | ||
|
|
b8866fb883 | ||
|
|
10af1004da | ||
|
|
cdbd6242c8 | ||
|
|
b4bd44b944 | ||
|
|
b4abae5c33 | ||
|
|
57573b483e | ||
|
|
c06dc04b8b | ||
|
|
461add59f7 | ||
|
|
43d573e6f5 | ||
|
|
f7befdf86d | ||
|
|
670e89d1b8 | ||
|
|
dbcdbec881 | ||
|
|
6e3f05aa89 | ||
|
|
193482c1b8 | ||
|
|
94975f68f0 | ||
|
|
f40dcec5f0 | ||
|
|
dac2b1ba4b | ||
|
|
167fa385a2 | ||
|
|
36aa2632c9 | ||
|
|
ec26e4c078 | ||
|
|
9f36f0db78 | ||
|
|
651b4faf7f | ||
|
|
add417b77d | ||
|
|
bed19526b3 | ||
|
|
ea854767ae | ||
|
|
76359eec37 | ||
|
|
3d4a5a7ce8 | ||
|
|
c0c08b2e26 | ||
|
|
36f3820f4f | ||
|
|
a327fc3f75 |
22 changed files with 1384 additions and 452 deletions
|
|
@ -1,42 +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
|
||||
|
||||
- run:
|
||||
name: Install Leiningen
|
||||
command: wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein && chmod a+x lein && ./lein
|
||||
|
||||
# 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: lein deps
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "project.clj" }}
|
||||
|
||||
# run tests!
|
||||
- run: lein test
|
||||
|
||||
- store_test_results:
|
||||
path: target
|
||||
5
.cljstyle
Normal file
5
.cljstyle
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{:files {:pattern #"\.(clj[sc]?|edn|cljstyle)$"},
|
||||
:rules
|
||||
{:indentation {:list-indent 1},
|
||||
:blank-lines {:padding-lines 1},
|
||||
:namespaces {:indent-size 1}}}
|
||||
18
.github/actions/setup-build/action.yml
vendored
Normal file
18
.github/actions/setup-build/action.yml
vendored
Normal 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
22
.github/workflows/clojure.yml
vendored
Normal 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
28
.github/workflows/release.yml
vendored
Normal 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 }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -8,6 +8,11 @@ pom.xml.asc
|
|||
/.lein-*
|
||||
/.nrepl-history
|
||||
/.nrepl-port
|
||||
.clj-kondo/
|
||||
.hgignore
|
||||
.hg/
|
||||
.DS_Store
|
||||
.lsp
|
||||
.cpcache
|
||||
/.idea/
|
||||
/clj-test-containers.iml
|
||||
|
|
|
|||
64
CHANGELOG.md
64
CHANGELOG.md
|
|
@ -1,6 +1,70 @@
|
|||
# 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/).
|
||||
|
||||
## [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
|
||||
### Added
|
||||
- [#43](https://github.com/javahippie/clj-test-containers/issues/43): Provide a way to remove all running testcontainers instances in a REPL session
|
||||
|
||||
### Changed
|
||||
- [#40](https://github.com/javahippie/clj-test-containers/issues/40): Increase Testcontainers version to 1.15.0
|
||||
- [#47](https://github.com/javahippie/clj-test-containers/issues/47): Increase Testcontainers version to 1.15.1
|
||||
- [#44](https://github.com/javahippie/clj-test-containers/pull/44): Use .copyFileToContainer when the container is already started
|
||||
|
||||
## [0.3.0] - 2020-10-23
|
||||
### Added
|
||||
- [#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
|
||||
- [#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
|
||||
- [#33](https://github.com/javahippie/clj-test-containers/pull/33): Add more options to the HTTP wait strategy
|
||||
- [#28](https://github.com/javahippie/clj-test-containers/pull/28): Link to the java lib
|
||||
- [#23](https://github.com/javahippie/clj-test-containers/pull/23): Configure clojure.spec, spec out a few functions
|
||||
- [#24](https://github.com/javahippie/clj-test-containers/pull/24): cljstyle redux, with pre-commit hook
|
||||
- [#21](https://github.com/javahippie/clj-test-containers/pull/21): Install and configure cljstyle
|
||||
|
||||
### Changed
|
||||
- [#29](https://github.com/javahippie/clj-test-containers/issues/29): init-network should be called create-network!
|
||||
|
||||
## [0.2.0] - 2020-08-05
|
||||
### Added
|
||||
- [#2](https://github.com/javahippie/clj-test-containers/issues/2): Enable configuration of networking
|
||||
|
|
|
|||
426
README.md
426
README.md
|
|
@ -1,30 +1,35 @@
|
|||
# clj-test-containers
|
||||
|
||||
[](<LINK>)
|
||||
|
||||
[](http://clojars.org/clj-test-containers)
|
||||
|
||||
## What it is
|
||||
This application is supposed to be a lightweight wrapper around the Testcontainers Java library.
|
||||
|
||||
This library is a lightweight wrapper around the [Testcontainers Java library](https://www.testcontainers.org/).
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
(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]
|
||||
:env-vars {"POSTGRES_PASSWORD" "verysecret"}})
|
||||
(tc/bind-filesystem! {:host-path "/tmp"
|
||||
:env-vars {"POSTGRES_PASSWORD" "verysecret"}})
|
||||
(tc/bind-filesystem! {:host-path "/tmp"
|
||||
:container-path "/opt"
|
||||
:mode :read-only})
|
||||
(tc/start!))
|
||||
:mode :read-only})
|
||||
(tc/start!)))
|
||||
|
||||
(do-database-testing (:host container)
|
||||
(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])
|
||||
|
||||
(def container (-> (tc/create-from-docker-file {:env-vars {"FOO" "bar"}
|
||||
:exposed-ports [80]
|
||||
:docker-file "resources/Dockerfile"})
|
||||
(tc/start!))
|
||||
(def container (-> (tc/create-from-docker-file {:env-vars {"FOO" "bar"}
|
||||
:exposed-ports [80]
|
||||
:docker-file "resources/Dockerfile"})
|
||||
(tc/start!)))
|
||||
```
|
||||
|
||||
If you prefer to use prebuilt containers from the Testcontainers project, you can do it like this
|
||||
|
|
@ -50,288 +55,334 @@ If you prefer to use prebuilt containers from the Testcontainers project, you ca
|
|||
(require '[clj-test-containers.core :as tc])
|
||||
(:import [org.testcontainers.containers PostgreSQLContainer])
|
||||
|
||||
(def container (-> (tc/init {:container (PostgreSQLContainer. "postgres:12.2")
|
||||
:exposed-ports [5432]})
|
||||
(tc/start!))
|
||||
(def container (-> (tc/init {:container (PostgreSQLContainer. "postgres:12.2")
|
||||
:exposed-ports [5432]})
|
||||
(tc/start!)))
|
||||
```
|
||||
|
||||
## Functions and Properties
|
||||
|
||||
### 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:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | A map with environment variables|
|
||||
| `:command` | Vector with strings | The start command of the container|
|
||||
| `:network` | Map | A map containing the configuration of a Docker Network (see: `init-network`)|
|
||||
| `:network-aliases` | Map | A list of alias names for the container on the network |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- |:----------------------------------------------------------------------------------------------------|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | A map with environment variables |
|
||||
| `: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-aliases` | Map | A list of alias names for the container on the network |
|
||||
| `: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](doc/log-strategies.md) to use, e.g. {:log-strategy string} |
|
||||
|
||||
#### Result:
|
||||
#### Result:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter|
|
||||
| `:host` | String | The host for the Docker Container|
|
||||
| `:network` | Map | The network configuration of the Container, if provided|
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter |
|
||||
| `:host` | String | The host for the Docker Container |
|
||||
| `:network` | Map | The network configuration of the Container, if provided |
|
||||
| `:wait-for` | Map | The wait-for configuration of the Container, if provided! |
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(create {:image-name "alpine:3.2"
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:network (init-network)
|
||||
(create {:image-name "alpine:3.2"
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:network (create-network)
|
||||
:network-aliases ["api-server"]
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
```
|
||||
|
||||
#### Example using wait-for and healthcheck:
|
||||
|
||||
```clojure
|
||||
(create {:image-name "alpine:3.2"
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:network (create-network)
|
||||
:network-aliases ["api-server"]
|
||||
:wait-for {:strategy :health}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
```
|
||||
|
||||
### init
|
||||
|
||||
Initializes a given Testcontainer, which was e.g. provided by a library
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | A map with environment variables|
|
||||
| `:command` | Vector with strings | The start command of the container|
|
||||
| `:network` | Map | A map containing the configuration of a Docker Network (see: `init-network`)|
|
||||
| `:network-aliases` | Map | A list of alias names for the container on the network |
|
||||
#### Result:
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- |:----------------------------------------------------------------------------------------------------|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | A map with environment variables |
|
||||
| `: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-aliases` | Map | A list of alias names for the container on the network |
|
||||
| `: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](doc/log-strategies.md) to use, e.g. {:log-strategy string} |
|
||||
| | | |
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter|
|
||||
| `:host` | String | The host for the Docker Container|
|
||||
| `:network` | Map | The network configuration of the Container, if provided|
|
||||
#### Result:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- |:------------------------------------------------------------------------------------------|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter |
|
||||
| `:host` | String | The host for the Docker Container |
|
||||
| `:network` | Map | The network configuration of the Container, if provided |
|
||||
| `:wait-for` | Map | The wait-for configuration of the Container, if provided! |
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
;; 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]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
```
|
||||
|
||||
#### Example using wait-for and a log message:
|
||||
|
||||
```clojure
|
||||
;; PostgreSQL container needs a separate library! This is not included.
|
||||
(init {:container (org.testcontainers.containers.PostgreSQLContainer)
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:wait-for {:strategy :log :message "accept connections"}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
```
|
||||
|
||||
### create-from-docker-file
|
||||
|
||||
Creates a testcontainer from a Dockerfile
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `:docker-file` | String, mandatory | String containing a path to a Dockerfile |
|
||||
| `: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|
|
||||
| `:command` | Vector with strings | The start command of the container|
|
||||
| `:network` | Map | A map containing the configuration of a Docker Network (see: `init-network`)|
|
||||
| `:network-aliases` | Map | A list of alias names for the container on the network |
|
||||
#### Result:
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `:docker-file` | String, mandatory | String containing a path to a Dockerfile |
|
||||
| `: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 |
|
||||
| `: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-aliases` | Map | A list of alias names for the container on the network |
|
||||
| `: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](doc/log-strategies.md) to use, e.g. {:log-strategy string} |
|
||||
| | | |
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter|
|
||||
| `:host` | String | The host for the Docker Container|
|
||||
| `:network` | Map | The network configuration of the Container, if provided|
|
||||
#### Result:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter |
|
||||
| `:host` | String | The host for the Docker Container |
|
||||
| `:network` | Map | The network configuration of the Container, if provided |
|
||||
| `:wait-for` | Map | The wait-for configuration of the Container, if provided! |
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(create-from-docker-file {:docker-file "resources/Dockerfile"
|
||||
(create-from-docker-file {:docker-file "resources/Dockerfile"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
:env-vars {"MAGIC_NUMBER" "42"}
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### start!
|
||||
|
||||
Starts the Testcontainer, which was defined by `create`
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| First parameter: | | |
|
||||
| `container-config` | Map, mandatory | Return value of the `create` function |
|
||||
| | | |
|
||||
|
||||
#### Result:
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter|
|
||||
| `:host` | String | The host for the Docker Container|
|
||||
| `:id` | String | The ID of the started docker container|
|
||||
| `:mapped-ports` | Map | A map containing the container port as key and the mapped local port as a value|
|
||||
#### Result:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `: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 |
|
||||
| `:env-vars` | Map | Value of the same input parameter |
|
||||
| `:host` | String | The host for the Docker Container |
|
||||
| `:id` | String | The ID of the started docker container |
|
||||
| `:mapped-ports` | Map | A map containing the container port as key and the mapped local port as a value |
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(def container (create {:image-name "alpine:3.2"
|
||||
(def container (create {:image-name "alpine:3.2"
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"})
|
||||
|
||||
(start! container)
|
||||
:env-vars {"MAGIC_NUMBER" "42"}}))
|
||||
|
||||
(start! container)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### stop!
|
||||
|
||||
Stops the Testcontainer, which was defined by `create`
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| First parameter: | | |
|
||||
| `container-config` | Map, mandatory | Return value of the `create` function |
|
||||
|
||||
#### Result:
|
||||
|
||||
#### Result:
|
||||
The `container-config`
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(def container (create {:image-name "alpine:3.2"
|
||||
(def container (create {:image-name "alpine:3.2"
|
||||
:exposed-ports [80]
|
||||
:env-vars {"MAGIC_NUMBER" "42"})
|
||||
|
||||
(start! container)
|
||||
(stop! container)
|
||||
:env-vars {"MAGIC_NUMBER" "42"}}))
|
||||
|
||||
(start! container)
|
||||
(stop! container)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### map-classpath-resource!
|
||||
|
||||
Maps a resource from your classpath into the containers file system
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `:resource-path` | String, mandatory | Path of your classpath resource |
|
||||
| `:container-path` | String, mandatory | Path, to which the resource should be mapped |
|
||||
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
|
||||
| `:resource-path` | String, mandatory | Path of your classpath resource |
|
||||
| `:container-path` | String, mandatory | Path, to which the resource should be mapped |
|
||||
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
|
||||
|
||||
#### Result:
|
||||
|
||||
#### Result:
|
||||
The `container-config`
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(map-classpath-resource! container {:resource-path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:mode :read-only})
|
||||
(map-classpath-resource! container {:resource-path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:mode :read-only})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### bind-filesystem!
|
||||
|
||||
Binds a path from your local filesystem into the Docker container as a volume
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `:host-path` | String , mandatory | Path on your local filesystem |
|
||||
| `:container-path` | String, mandatory | Path, to which the resource should be mapped |
|
||||
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| First parameter: | | |
|
||||
| `container-config` | Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `:host-path` | String , mandatory | Path on your local filesystem |
|
||||
| `:container-path` | String, mandatory | Path, to which the resource should be mapped |
|
||||
| `:mode` | Keyword, mandatory | `:read-only` or `:read-write` |
|
||||
|
||||
#### Result:
|
||||
|
||||
#### Result:
|
||||
The `container-config`
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(bind-filesystem! container {:host-path "."
|
||||
(bind-filesystem! container {:host-path "."
|
||||
:container-path "/opt"
|
||||
:mode :read-only})
|
||||
:mode :read-only})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### copy-file-to-container!
|
||||
|
||||
Copies a file from your filesystem or classpath into the running container
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `: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 |
|
||||
| `:type` | Keyword, mandatory | `:classpath-resource` or `:host-path` |
|
||||
| `:path` | String, mandatory | Path to a classpath resource *or* file on your filesystem |
|
||||
| `:container-path` | String, mandatory | Path, to which the file should be copied |
|
||||
| `:type` | Keyword, mandatory | `:classpath-resource` or `:host-path` |
|
||||
|
||||
#### Result:
|
||||
|
||||
#### Result:
|
||||
The `container-config`
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(copy-file-to-container! container {:path "test.sql"
|
||||
(copy-file-to-container! container {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path})
|
||||
:type :host-path})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
### execute-command!
|
||||
|
||||
Executes a command in the running container, and returns the result
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| First parameter: | | |
|
||||
| `container-config`| Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `command` | Vector with Strings, mandatory | A vector containing the command and its parameters |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| First parameter: | | |
|
||||
| `container-config` | Map, mandatory | Return value of the `create` function |
|
||||
| Second parameter: | | |
|
||||
| `command` | Vector with Strings, mandatory | A vector containing the command and its parameters |
|
||||
|
||||
#### Result:
|
||||
|
||||
|
||||
#### Result:
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `:exit-code` | int | Exit code of the executed command |
|
||||
| `:stdout` | String | Content of stdout |
|
||||
| `:stdin` | String | Content of stdin |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `:exit-code` | int | Exit code of the executed command |
|
||||
| `:stdout` | String | Content of stdout |
|
||||
| `:stdin` | String | Content of stdin |
|
||||
|
||||
#### Example:
|
||||
|
||||
|
|
@ -339,45 +390,66 @@ Executes a command in the running container, and returns the result
|
|||
(execute-command! container ["tail" "/opt/test.sql"])
|
||||
```
|
||||
|
||||
### init-network
|
||||
Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver
|
||||
### create-network
|
||||
|
||||
Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver
|
||||
|
||||
#### Config parameters:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `:ipv6`| boolean | Should the network enable IPv6? |
|
||||
| `:driver`| String | The network driver used by Docker, e.g. `bridge` or `host`
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `:ipv6` | boolean | Should the network enable IPv6? |
|
||||
| `:driver` | String | The network driver used by Docker, e.g. `bridge` or `host` |
|
||||
|
||||
#### Result:
|
||||
|
||||
|
||||
#### Result:
|
||||
| Key | Type | Description |
|
||||
| ------------- |:------------- | :-----|
|
||||
| `:network` | `org.testcontainers.containers.Network` | The instance of the network |
|
||||
| `:id` | String | The identifier of the network |
|
||||
| `:name` | String | The name of the network |
|
||||
| `:ipv6` | boolean | Does the network enable IPv6? |
|
||||
| `:driver` | String | The network driver used |
|
||||
| Key | Type | Description |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `:network` | `org.testcontainers.containers.Network` | The instance of the network |
|
||||
| `:name` | String | The name of the network |
|
||||
| `:ipv6` | boolean | Does the network enable IPv6? |
|
||||
| `:driver` | String | The network driver used |
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
;;Create with config
|
||||
(init-network {:ipv6 false
|
||||
:driver "overlay")
|
||||
(create-network {:ipv6 false
|
||||
:driver "overlay"})
|
||||
|
||||
;;Create with default config
|
||||
(init-network)
|
||||
;;Create with default config
|
||||
(create-network)
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
None
|
||||
|
||||
#### Result:
|
||||
|
||||
None
|
||||
|
||||
#### Example:
|
||||
|
||||
```clojure
|
||||
(perform-cleanup!)
|
||||
```
|
||||
|
||||
### dump-logs
|
||||
|
||||
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 |
|
||||
| ------------- | :------------- | :----- |
|
||||
| `container-config` | Map, mandatory | The configuration describing the container for which the log should be retrieved |
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2020 Tim Zöller
|
||||
|
||||
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.
|
||||
|
|
|
|||
27
deps.edn
Normal file
27
deps.edn
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{:paths ["src" "resources"]
|
||||
|
||||
:deps {org.clojure/clojure {:mvn/version "1.10.3"}
|
||||
org.testcontainers/testcontainers {:mvn/version "1.19.7"}}
|
||||
|
||||
:aliases {:dev {:extra-paths "dev-src"}
|
||||
:test {:extra-paths ["test" "test/resources"]
|
||||
:extra-deps {expound/expound {:mvn/version "0.9.0"}
|
||||
lambdaisland/kaocha {:mvn/version "1.88.1376"}
|
||||
lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}
|
||||
lambdaisland/kaocha-junit-xml {:mvn/version "1.17.101"}
|
||||
org.clojure/test.check {:mvn/version "1.1.1"}
|
||||
org.testcontainers/postgresql {:mvn/version "1.19.7"}}}
|
||||
|
||||
:test-runner {:extra-paths ["test" "test/resources"]
|
||||
:extra-deps {expound/expound {:mvn/version "0.9.0"}
|
||||
lambdaisland/kaocha {:mvn/version "1.88.1376"}
|
||||
lambdaisland/kaocha-cloverage {:mvn/version "1.1.89"}
|
||||
lambdaisland/kaocha-junit-xml {:mvn/version "1.17.101"}
|
||||
org.clojure/test.check {:mvn/version "1.1.1"}
|
||||
orchestra {:mvn/version "2021.01.01-1"}
|
||||
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.16.630"
|
||||
:exclusions [org.clojure/clojure]}}
|
||||
:main-opts ["-m" "cljstyle.main" "check"]}}}
|
||||
14
dev-src/user.clj
Normal file
14
dev-src/user.clj
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
(ns user
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[expound.alpha :as ea]
|
||||
[kaocha.repl]))
|
||||
|
||||
(try
|
||||
;; Attempt to set *explain-out*, assuming that we're inside of
|
||||
;; a binding context...
|
||||
(set! s/*explain-out* ea/printer)
|
||||
|
||||
(catch IllegalStateException _
|
||||
;; ...if not, just alter the root binding.
|
||||
(alter-var-root #'s/*explain-out* (constantly ea/printer))))
|
||||
43
doc/intro.md
43
doc/intro.md
|
|
@ -1,27 +1,38 @@
|
|||
# Introduction
|
||||
|
||||
## 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?
|
||||
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?
|
||||
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
|
||||
(-> (org.testcontainers.containers.GenericContainer. "postgres:12.2")
|
||||
(.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.
|
||||
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:
|
||||
|
||||
```clojure
|
||||
(-> (tc/create {:image-name "postgres:12.1"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}})
|
||||
tc/start!)
|
||||
(-> (org.testcontainers.containers.GenericContainer. "postgres:12.2")
|
||||
(.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
|
||||
(-> (tc/create {:image-name "postgres:12.1"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}})
|
||||
tc/start!)
|
||||
```
|
||||
|
|
|
|||
34
doc/log-strategies.md
Normal file
34
doc/log-strategies.md
Normal 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))}}
|
||||
```
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
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
|
||||
* `bind-filesystem!`: Binds a path from your local filesystem into the Docker container as a volume
|
||||
* `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
|
||||
* `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
|
||||
(require '[clj-test-containers.core :as tc])
|
||||
|
||||
(deftest db-integration-test
|
||||
(testing "A simple PostgreSQL integration test"
|
||||
(let [pw "db-pass"
|
||||
postgres (-> (tc/create {:image-name "postgres:12.1"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}}))]
|
||||
(tc/start! postgres)
|
||||
(let [datasource (jdbc/get-datasource {:dbtype "postgresql"
|
||||
:dbname "postgres"
|
||||
:user "postgres"
|
||||
:password pw
|
||||
:host (:host postgres)
|
||||
:port (get (:mapped-ports container) 5432)})]
|
||||
(is (= [{:one 1 :two 2}] (with-open [connection (jdbc/get-connection datasource)]
|
||||
(jdbc/execute! connection ["SELECT 1 ONE, 2 TWO"])))))
|
||||
(tc/stop! postgres))))
|
||||
(testing "A simple PostgreSQL integration test"
|
||||
(let [pw "db-pass"
|
||||
postgres (-> (tc/create {:image-name "postgres:12.1"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}}))]
|
||||
(tc/start! postgres)
|
||||
(let [datasource (jdbc/get-datasource {:dbtype "postgresql"
|
||||
:dbname "postgres"
|
||||
:user "postgres"
|
||||
:password pw
|
||||
:host (:host postgres)
|
||||
:port (get (:mapped-ports container) 5432)})]
|
||||
(is (= [{:one 1 :two 2}] (with-open [connection (jdbc/get-connection datasource)]
|
||||
(jdbc/execute! connection ["SELECT 1 ONE, 2 TWO"])))))
|
||||
(tc/stop! postgres))))
|
||||
```
|
||||
|
||||
## 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
|
||||
(execute-command! container ["whoami"])
|
||||
|
||||
> {:exit-code 0
|
||||
:stdout "root"}
|
||||
:stdout "root"}
|
||||
```
|
||||
|
||||
## 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
|
||||
(map-classpath-resource! container
|
||||
{:resource-path "test.sql"
|
||||
(map-classpath-resource! container
|
||||
{:resource-path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:mode :read-only})
|
||||
:mode :read-only})
|
||||
```
|
||||
|
||||
```clojure
|
||||
(bind-filesystem! {:host-path "."
|
||||
:container-path "/opt"
|
||||
:mode :read-only})
|
||||
(bind-filesystem! {:host-path "."
|
||||
:container-path "/opt"
|
||||
:mode :read-only})
|
||||
```
|
||||
|
||||
It is also possible to copy files into a running container instance:
|
||||
|
||||
```clojure
|
||||
(copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path})
|
||||
(copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path})
|
||||
```
|
||||
|
||||
# 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
|
||||
teardown. Using a fixture-type of :each wraps every test
|
||||
individually, while :once wraps the whole run in a single function.
|
||||
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:
|
||||
|
||||
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
|
||||
(use-fixtures :once (fn [f]
|
||||
(let [{pw "apassword"
|
||||
postgres (tc/start! (tc/create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}}))}]
|
||||
(let [pw "apassword"
|
||||
postgres (tc/start! (tc/create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" pw}}))]
|
||||
(my-app/initialize-db! {:dbtype "postgresql"
|
||||
:dbname "postgres"
|
||||
:user "postgres"
|
||||
|
|
|
|||
97
doc/wait-strategies.md
Normal file
97
doc/wait-strategies.md
Normal 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}})
|
||||
```
|
||||
36
project.clj
36
project.clj
|
|
@ -1,20 +1,34 @@
|
|||
(defproject clj-test-containers "0.2.0"
|
||||
:description "A lightweight, unofficial wrapper around the Testcontainers Java library"
|
||||
(defproject testcontainers-clj "unspecified"
|
||||
: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"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
|
||||
:dependencies [[org.clojure/clojure "1.10.1"]
|
||||
[org.testcontainers/testcontainers "1.14.3"]]
|
||||
:dependencies [[org.clojure/clojure "1.10.3"]
|
||||
[org.testcontainers/testcontainers "1.19.7"]]
|
||||
|
||||
:aliases {"test" ["run" "-m" "kaocha.runner"]}
|
||||
:aliases {"test" ["run" "-m" "kaocha.runner"]
|
||||
"cljstyle" ["run" "-m" "cljstyle.main"]}
|
||||
|
||||
:profiles {:dev {:dependencies [[org.testcontainers/postgresql "1.14.3"]
|
||||
[lambdaisland/kaocha-cloverage "1.0-45"]
|
||||
[lambdaisland/kaocha "1.0.641"]
|
||||
[lambdaisland/kaocha-junit-xml "0.0.76"]]}}
|
||||
:plugins [[jainsahab/lein-githooks "1.0.0"]]
|
||||
|
||||
:profiles {:dev {:dependencies [[expound "0.9.0"]
|
||||
[lambdaisland/kaocha "1.88.1376"]
|
||||
[lambdaisland/kaocha-cloverage "1.1.89"]
|
||||
[lambdaisland/kaocha-junit-xml "1.17.101"]
|
||||
[mvxcvi/cljstyle "0.16.630" :exclusions [org.clojure/clojure]]
|
||||
[org.clojure/test.check "1.1.1"]
|
||||
[orchestra "2021.01.01-1"]
|
||||
[org.clojure/tools.namespace "1.5.0"]
|
||||
[org.testcontainers/postgresql "1.19.7"]
|
||||
[com.fzakaria/slf4j-timbre "0.4.1"]
|
||||
[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")
|
||||
|
||||
|
|
|
|||
21
script/pre-commit
Executable file
21
script/pre-commit
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||
|
||||
FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g')
|
||||
|
||||
if [[ -z "$FILES" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Fixing Clojure style..."
|
||||
|
||||
# Format all selected files
|
||||
echo "$FILES" | xargs lein cljstyle fix --report
|
||||
|
||||
# Add back the modified/prettified files to staging
|
||||
echo "$FILES" | xargs git add
|
||||
|
||||
echo "Done!"
|
||||
|
|
@ -1,141 +1,429 @@
|
|||
(ns clj-test-containers.core
|
||||
(:require [clojure.spec.alpha :as s])
|
||||
(:import [org.testcontainers.containers GenericContainer]
|
||||
[org.testcontainers.utility MountableFile]
|
||||
[org.testcontainers.containers BindMode Network]
|
||||
[org.testcontainers.images.builder ImageFromDockerfile]
|
||||
[java.nio.file Paths]))
|
||||
(:require
|
||||
[clj-test-containers.spec.core :as cs]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.string])
|
||||
(:import
|
||||
(java.nio.file
|
||||
Paths)
|
||||
(java.time
|
||||
Duration)
|
||||
(org.testcontainers DockerClientFactory)
|
||||
(org.testcontainers.containers
|
||||
BindMode
|
||||
GenericContainer
|
||||
Network)
|
||||
(org.testcontainers.containers.output
|
||||
BaseConsumer
|
||||
OutputFrame
|
||||
ToStringConsumer)
|
||||
(org.testcontainers.containers.wait.strategy
|
||||
Wait)
|
||||
(org.testcontainers.images.builder
|
||||
ImageFromDockerfile)
|
||||
(org.testcontainers.utility
|
||||
MountableFile)))
|
||||
|
||||
(defn- resolve-bind-mode
|
||||
[bind-mode]
|
||||
(if (= :read-write bind-mode)
|
||||
BindMode/READ_WRITE
|
||||
BindMode/READ_ONLY))
|
||||
(^BindMode [bind-mode]
|
||||
(if (= :read-write bind-mode)
|
||||
BindMode/READ_WRITE
|
||||
BindMode/READ_ONLY)))
|
||||
|
||||
(defonce started-instances (atom #{}))
|
||||
|
||||
(defmulti wait
|
||||
"Sets a wait strategy to the container. Supports :http, :health and :log as
|
||||
strategies.
|
||||
|
||||
## HTTP Strategy
|
||||
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
|
||||
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
|
||||
required, all others are optional.
|
||||
|
||||
Example:
|
||||
|
||||
```clojure
|
||||
(wait {:wait-strategy :http
|
||||
:port 80
|
||||
:path \"/\"
|
||||
:status-codes [200 201]
|
||||
:tls true
|
||||
:read-timeout 5
|
||||
:basic-credentials {:username \"user\"
|
||||
:password \"password\"
|
||||
:startup-timeout 60}}
|
||||
container)
|
||||
```
|
||||
|
||||
## Health Strategy
|
||||
The :health strategy enables support for Docker's healthcheck feature,
|
||||
whereby you can directly leverage the healthy state of your container as your wait condition.
|
||||
|
||||
Example:
|
||||
|
||||
```clojure
|
||||
(wait {:wait-strategy :health
|
||||
:startup-timeout 60} container)
|
||||
```
|
||||
|
||||
## Log Strategy
|
||||
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.
|
||||
The output is `grepped` against the log message.
|
||||
|
||||
Example:
|
||||
|
||||
```clojure
|
||||
(wait {:wait-strategy :log
|
||||
:message \"accept connections\"
|
||||
:startup-timeout 60} container)
|
||||
```
|
||||
|
||||
## 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
|
||||
[{:keys [path
|
||||
port
|
||||
method
|
||||
status-codes
|
||||
tls
|
||||
allow-insecure
|
||||
read-timeout
|
||||
basic-credentials
|
||||
headers
|
||||
startup-timeout] :as options}
|
||||
^GenericContainer container]
|
||||
(let [for-http (Wait/forHttp path)]
|
||||
(when port
|
||||
(.forPort for-http port))
|
||||
|
||||
(when method
|
||||
(.withMethod for-http method))
|
||||
|
||||
(doseq [status-code status-codes]
|
||||
(.forStatusCode for-http status-code))
|
||||
|
||||
(when tls
|
||||
(.usingTls for-http))
|
||||
|
||||
(when allow-insecure
|
||||
(.allowInsecure for-http))
|
||||
|
||||
(when read-timeout
|
||||
(.withReadTimeout for-http (Duration/ofSeconds read-timeout)))
|
||||
|
||||
(when basic-credentials
|
||||
(let [{:keys [username password]} basic-credentials]
|
||||
(.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)
|
||||
|
||||
{:wait-for-http (dissoc options :strategy)}))
|
||||
|
||||
(defmethod wait :health
|
||||
[{:keys [startup-timeout]} ^GenericContainer container]
|
||||
(let [strategy (Wait/forHealthcheck)]
|
||||
|
||||
(when startup-timeout
|
||||
(.withStartupTimeout strategy (Duration/ofSeconds startup-timeout)))
|
||||
|
||||
(.waitingFor container strategy))
|
||||
{:wait-for-healthcheck true})
|
||||
|
||||
(defmethod wait :log
|
||||
[{:keys [message times startup-timeout]} ^GenericContainer container]
|
||||
(let [log-message (str ".*" message ".*\\n")
|
||||
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}))
|
||||
|
||||
(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)
|
||||
|
||||
(s/fdef init
|
||||
:args (s/cat :init-options ::cs/init-options)
|
||||
:ret ::cs/container)
|
||||
|
||||
(defn init
|
||||
"Sets the properties for a testcontainer instance"
|
||||
[{:keys [container exposed-ports env-vars command network network-aliases]}]
|
||||
[{:keys [^GenericContainer container
|
||||
exposed-ports
|
||||
env-vars
|
||||
command
|
||||
network
|
||||
network-aliases
|
||||
wait-for] :as init-options}]
|
||||
|
||||
(.setExposedPorts container (map int exposed-ports))
|
||||
|
||||
(run! (fn [[k v]] (.addEnv container k v)) env-vars)
|
||||
(doseq [[k v] env-vars]
|
||||
(.addEnv container k v))
|
||||
|
||||
(when command
|
||||
(.setCommand container (into-array String command)))
|
||||
(.setCommand container ^"[Ljava.lang.String;" (into-array String command)))
|
||||
|
||||
(when network
|
||||
(.setNetwork container (:network network)))
|
||||
|
||||
(when network-aliases
|
||||
(.setNetworkAliases container (java.util.ArrayList. network-aliases)))
|
||||
(.setNetworkAliases container network-aliases))
|
||||
|
||||
{:container container
|
||||
:exposed-ports (vec (.getExposedPorts container))
|
||||
:env-vars (into {} (.getEnvMap container))
|
||||
:host (.getHost container)
|
||||
:network network})
|
||||
(merge init-options {:container container
|
||||
:exposed-ports (vec (.getExposedPorts container))
|
||||
:env-vars (into {} (.getEnvMap container))
|
||||
:host (.getHost container)
|
||||
:network network} (wait wait-for container)))
|
||||
|
||||
(s/fdef create
|
||||
:args (s/cat :create-options ::cs/create-options)
|
||||
:ret ::cs/container)
|
||||
|
||||
(defn create
|
||||
"Creates a generic testcontainer and sets its properties"
|
||||
[{:keys [image-name] :as options}]
|
||||
(->> (GenericContainer. image-name)
|
||||
(->> (GenericContainer. ^String image-name)
|
||||
(assoc options :container)
|
||||
init))
|
||||
|
||||
(defn create-from-docker-file
|
||||
"Creates a testcontainer from a provided Dockerfile"
|
||||
[{:keys [docker-file] :as options}]
|
||||
(->> (.withDockerfile (ImageFromDockerfile.) (Paths/get "." (into-array [docker-file])))
|
||||
(->> (.withDockerfile (ImageFromDockerfile.)
|
||||
(Paths/get "." (into-array [docker-file])))
|
||||
(GenericContainer.)
|
||||
(assoc options :container)
|
||||
init))
|
||||
|
||||
(defn map-classpath-resource!
|
||||
"Maps a resource in the classpath to the given container path. Should be called before starting the container!"
|
||||
[container-config
|
||||
{:keys [resource-path container-path mode]}]
|
||||
(assoc container-config :container (.withClasspathResourceMapping (:container container-config)
|
||||
resource-path
|
||||
container-path
|
||||
(resolve-bind-mode mode))))
|
||||
"Maps a resource in the classpath to the given container path. Should be
|
||||
called before starting the container!"
|
||||
[{:keys [^GenericContainer container] :as container-config}
|
||||
{:keys [^String resource-path ^String container-path mode]}]
|
||||
(assoc container-config
|
||||
:container
|
||||
(.withClasspathResourceMapping container
|
||||
resource-path
|
||||
container-path
|
||||
(resolve-bind-mode mode))))
|
||||
|
||||
(defn bind-filesystem!
|
||||
"Binds a source from the filesystem to the given container path. Should be called before starting the container!"
|
||||
[container-config {:keys [host-path container-path mode]}]
|
||||
"Binds a source from the filesystem to the given container path. Should be
|
||||
called before starting the container!"
|
||||
[{:keys [^GenericContainer container] :as container-config}
|
||||
{:keys [^String host-path ^String container-path mode]}]
|
||||
(assoc container-config
|
||||
:container (.withFileSystemBind (:container container-config)
|
||||
host-path
|
||||
container-path
|
||||
(resolve-bind-mode mode))))
|
||||
:container
|
||||
(.withFileSystemBind container
|
||||
host-path
|
||||
container-path
|
||||
(resolve-bind-mode mode))))
|
||||
|
||||
(defn copy-file-to-container!
|
||||
"Copies a file into the running container"
|
||||
[container-config
|
||||
{:keys [container-path path type]}]
|
||||
(let [mountable-file (cond
|
||||
(= :classpath-resource type)
|
||||
(MountableFile/forClasspathResource path)
|
||||
|
||||
(= :host-path type)
|
||||
(MountableFile/forHostPath path)
|
||||
:else
|
||||
:error)]
|
||||
(assoc container-config
|
||||
:container
|
||||
(.withCopyFileToContainer (:container container-config)
|
||||
mountable-file
|
||||
container-path))))
|
||||
"If a container is not yet started, adds a mapping from mountable file to
|
||||
container path that will be copied to the container on startup. If the
|
||||
container is already running, copy the file to the running container."
|
||||
[{:keys [^GenericContainer container id] :as container-config}
|
||||
{:keys [^String container-path ^String path type]}]
|
||||
(let [^MountableFile mountable-file
|
||||
(case type
|
||||
:classpath-resource (MountableFile/forClasspathResource path)
|
||||
:host-path (MountableFile/forHostPath path))]
|
||||
(if id
|
||||
(do
|
||||
(.copyFileToContainer container mountable-file container-path)
|
||||
container-config)
|
||||
(assoc container-config
|
||||
:container
|
||||
(.withCopyFileToContainer container
|
||||
mountable-file
|
||||
container-path)))))
|
||||
|
||||
(defn execute-command!
|
||||
"Executes a command in the container, and returns the result"
|
||||
[container-config command]
|
||||
(let [container (:container container-config)
|
||||
result (.execInContainer container
|
||||
(into-array command))]
|
||||
[{:keys [^GenericContainer container]} command]
|
||||
(let [result (.execInContainer container (into-array command))]
|
||||
{:exit-code (.getExitCode result)
|
||||
:stdout (.getStdout result)
|
||||
:stderr (.getStderr result)}))
|
||||
:stdout (.getStdout result)
|
||||
:stderr (.getStderr result)}))
|
||||
|
||||
(defmulti log
|
||||
"Sets a log strategy on the container as a means of accessing the container logs.
|
||||
|
||||
## String Strategy
|
||||
The `:string` strategy sets up a function in the returned map, under the
|
||||
`string-log` key. This function enables the dumping of the logs when passed to
|
||||
the `dump-logs` function.
|
||||
|
||||
Example:
|
||||
|
||||
```clojure
|
||||
{:log-strategy :string}
|
||||
```
|
||||
|
||||
Then, later in your program, you can access the logs thus:
|
||||
|
||||
```clojure
|
||||
(def container-config (tc/start! container))
|
||||
(tc/dump-logs container-config)
|
||||
```
|
||||
|
||||
## Function Strategy
|
||||
The `:fn` strategy accepts an additional parameter `:function` in the configuration
|
||||
map, which allows you to pass a function to the Testcontainers log mechanism
|
||||
which accepts a single String parameter and gets called for every log line. This
|
||||
way you can pass the container logging on to the logging library of your choice.
|
||||
|
||||
Example:
|
||||
```clojure
|
||||
{:log-strategy :fn
|
||||
:function (fn [log-line] (println \"From Container: \" log-line)}
|
||||
```
|
||||
"
|
||||
:log-strategy)
|
||||
|
||||
(defmethod log :string
|
||||
[_ ^GenericContainer container]
|
||||
(let [to-string-consumer (ToStringConsumer.)]
|
||||
(.followOutput container to-string-consumer)
|
||||
{:log (fn []
|
||||
(-> (.toUtf8String to-string-consumer)
|
||||
(clojure.string/replace #"\n+" "\n")))}))
|
||||
|
||||
(defmethod log :fn [{:keys [function]} ^GenericContainer container]
|
||||
(.followOutput container (proxy [BaseConsumer] []
|
||||
(^void accept [^OutputFrame frame]
|
||||
(function (.getUtf8String frame))))))
|
||||
|
||||
(defmethod log :default [_ _] nil)
|
||||
|
||||
(defn dump-logs
|
||||
"Dumps the logs found by invoking the function on the :string-log key"
|
||||
[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!
|
||||
"Starts the underlying testcontainer instance and adds new values to the response map, e.g. :id and :first-mapped-port"
|
||||
[container-config]
|
||||
(let [container (:container container-config)]
|
||||
(.start container)
|
||||
"Starts the underlying testcontainer instance and adds new values to the
|
||||
response map, e.g. :id and :first-mapped-port"
|
||||
[{:keys [^GenericContainer container
|
||||
log-to
|
||||
exposed-ports] :as container-config}]
|
||||
(.start container)
|
||||
(let [map-port (fn map-port
|
||||
[port]
|
||||
[port (.getMappedPort container port)])
|
||||
mapped-ports (into {} (map map-port) exposed-ports)
|
||||
container-id ^String (.getContainerId container)
|
||||
image-name ^String (.getDockerImageName container)
|
||||
logger (log log-to container)]
|
||||
(swap! started-instances conj {:type :container :id container-id})
|
||||
(-> container-config
|
||||
(assoc :id (.getContainerId container))
|
||||
(assoc :mapped-ports (into {}
|
||||
(map (fn [port] [port (.getMappedPort container port)])
|
||||
(:exposed-ports container-config)))))))
|
||||
(merge {:id container-id
|
||||
:mapped-ports mapped-ports
|
||||
:image-name image-name} logger)
|
||||
(dissoc :log-to))))
|
||||
|
||||
(defn stop!
|
||||
"Stops the underlying container"
|
||||
[container-config]
|
||||
(.stop (:container container-config))
|
||||
(-> container-config
|
||||
(dissoc :id)
|
||||
(dissoc :mapped-ports)))
|
||||
[{:keys [^GenericContainer container] :as container-config}]
|
||||
(.stop container)
|
||||
(dissoc container-config :id :string-log :mapped-ports))
|
||||
|
||||
(s/fdef create-network
|
||||
:args (s/alt :nullary (s/cat)
|
||||
:unary (s/cat :create-network-options
|
||||
::cs/create-network-options))
|
||||
:ret ::cs/network)
|
||||
|
||||
(defn- build-network
|
||||
[{:keys [ipv6 driver]}]
|
||||
(let [builder (Network/builder)]
|
||||
|
||||
(when ipv6
|
||||
(.enableIpv6 builder true))
|
||||
|
||||
(when driver
|
||||
(.driver builder driver))
|
||||
|
||||
(let [network (.build builder)]
|
||||
{:network network
|
||||
:id (.getId network)
|
||||
:name (.getName network)
|
||||
:ipv6 (.getEnableIpv6 network)
|
||||
:driver (.getDriver network)})))
|
||||
|
||||
(defn init-network
|
||||
"Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver"
|
||||
(defn create-network
|
||||
"Creates a network. The optional map accepts config values for enabling ipv6
|
||||
and setting the driver"
|
||||
([]
|
||||
(build-network {}))
|
||||
([options]
|
||||
(build-network options)))
|
||||
(create-network {}))
|
||||
([{:keys [ipv6 driver]}]
|
||||
(let [builder (Network/builder)]
|
||||
(when ipv6
|
||||
(.enableIpv6 builder true))
|
||||
|
||||
(when driver
|
||||
(.driver builder driver))
|
||||
|
||||
(let [network (.build builder)
|
||||
network-name (.getName network)]
|
||||
(swap! started-instances conj {:type :network :id :network-name})
|
||||
{:network network
|
||||
:name network-name
|
||||
:ipv6 (.getEnableIpv6 network)
|
||||
:driver (.getDriver 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!
|
||||
"Stops and removes all container instances which were created in the active JVM or REPL session"
|
||||
[]
|
||||
(for [instance @started-instances]
|
||||
(swap! started-instances disj (case (:type instance)
|
||||
:container (stop-and-remove-container! instance)
|
||||
:network (remove-network! instance)))))
|
||||
|
||||
|
||||
;; REPL Helpers
|
||||
(comment
|
||||
(start! (create {:image-name "postgres:12.1"}))
|
||||
(perform-cleanup!))
|
||||
|
|
|
|||
52
src/clj_test_containers/spec/container.clj
Normal file
52
src/clj_test_containers/spec/container.clj
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
(ns clj-test-containers.spec.container
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.spec.gen.alpha :as gen])
|
||||
(:import
|
||||
(org.testcontainers.containers
|
||||
GenericContainer)))
|
||||
|
||||
(s/def ::container
|
||||
(s/with-gen #(instance? GenericContainer %)
|
||||
#(gen/fmap (fn [^String image-name] (GenericContainer. image-name))
|
||||
(gen/string-alphanumeric))))
|
||||
|
||||
(s/def ::exposed-ports
|
||||
(s/coll-of (s/int-in 1 65535)))
|
||||
|
||||
(s/def ::env-vars
|
||||
(s/map-of string? string?))
|
||||
|
||||
(s/def ::command
|
||||
(s/coll-of string?))
|
||||
|
||||
(s/def ::network-aliases
|
||||
(s/coll-of string?))
|
||||
|
||||
(s/def ::image-name
|
||||
string?)
|
||||
|
||||
(s/def ::http
|
||||
keyword?)
|
||||
|
||||
(s/def ::health
|
||||
keyword?)
|
||||
|
||||
(s/def ::log
|
||||
keyword?)
|
||||
|
||||
(s/def ::wait-strategy #{:http :health :port :log})
|
||||
|
||||
(s/def ::log-strategy #{:string})
|
||||
|
||||
(s/def ::path
|
||||
string?)
|
||||
|
||||
(s/def ::message
|
||||
string?)
|
||||
|
||||
(s/def ::check
|
||||
boolean?)
|
||||
|
||||
(s/def ::string
|
||||
string?)
|
||||
54
src/clj_test_containers/spec/core.clj
Normal file
54
src/clj_test_containers/spec/core.clj
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
(ns clj-test-containers.spec.core
|
||||
(:require
|
||||
[clj-test-containers.spec.container :as csc]
|
||||
[clj-test-containers.spec.network :as csn]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(s/def ::wait-for
|
||||
(s/keys :req-un [::csc/wait-strategy]
|
||||
:opt-un [::csc/path
|
||||
::csc/message
|
||||
::csc/check]))
|
||||
|
||||
(s/def ::log-to
|
||||
(s/keys :req-un [::csc/log-strategy]
|
||||
:opt-un [::csc/string]))
|
||||
|
||||
(s/def ::network
|
||||
(s/nilable (s/keys :req-un [::csn/network
|
||||
::csn/name
|
||||
::csn/ipv6
|
||||
::csn/driver])))
|
||||
|
||||
(s/def ::container
|
||||
(s/keys :req-un [::csc/container
|
||||
::csc/exposed-ports
|
||||
::csc/env-vars
|
||||
::csc/host]
|
||||
:opt-un [::network
|
||||
::wait-for
|
||||
::log-to]))
|
||||
|
||||
(s/def ::init-options
|
||||
(s/keys :req-un [::csc/container]
|
||||
:opt-un [::csc/exposed-ports
|
||||
::csc/env-vars
|
||||
::csc/command
|
||||
::network
|
||||
::wait-for
|
||||
::log-to
|
||||
::csc/network-aliases]))
|
||||
|
||||
(s/def ::create-options
|
||||
(s/keys :req-un [::csc/image-name]
|
||||
:opt-un [::csc/exposed-ports
|
||||
::csc/env-vars
|
||||
::csc/command
|
||||
::network
|
||||
::wait-for
|
||||
::log-to
|
||||
::csc/network-aliases]))
|
||||
|
||||
(s/def ::create-network-options
|
||||
(s/keys :opt-un [::csn/ipv6
|
||||
::csn/driver]))
|
||||
22
src/clj_test_containers/spec/network.clj
Normal file
22
src/clj_test_containers/spec/network.clj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
(ns clj-test-containers.spec.network
|
||||
(:require
|
||||
[clojure.spec.alpha :as s])
|
||||
(:import
|
||||
(org.testcontainers.containers
|
||||
Network)))
|
||||
|
||||
(s/def ::id
|
||||
string?)
|
||||
|
||||
(s/def ::ipv6
|
||||
(s/nilable boolean?))
|
||||
|
||||
(s/def ::driver
|
||||
(s/nilable string?))
|
||||
|
||||
(s/def ::name
|
||||
string?)
|
||||
|
||||
(s/def ::network
|
||||
(s/with-gen #(instance? Network %)
|
||||
#(s/gen #{(Network/newNetwork)})))
|
||||
|
|
@ -1,27 +1,106 @@
|
|||
(ns clj-test-containers.core-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[clj-test-containers.core :refer :all])
|
||||
(:import [org.testcontainers.containers PostgreSQLContainer]))
|
||||
(:require
|
||||
[clj-test-containers.core :as sut]
|
||||
[clojure.string :refer [includes?]]
|
||||
[clojure.test :refer [deftest is testing]])
|
||||
(:import
|
||||
(org.testcontainers.containers
|
||||
PostgreSQLContainer)))
|
||||
|
||||
(deftest create-test
|
||||
(testing "Testing basic testcontainer generic image initialisation"
|
||||
|
||||
(let [container (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
initialized-container (start! container)
|
||||
stopped-container (stop! container)]
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
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 (nil? (:id stopped-container)))
|
||||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
(testing "Testing log access to the container with string logs"
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}
|
||||
:log-to {:log-strategy :string}})
|
||||
initialized-container (sut/start! container)]
|
||||
(Thread/sleep 500)
|
||||
(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"
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}
|
||||
:wait-for {:wait-strategy :log
|
||||
:message "accept connections"
|
||||
:startup-timeout 10}})
|
||||
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-log-message initialized-container) ".*accept connections.*\\n"))
|
||||
(is (nil? (:id 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"
|
||||
(let [container (create-from-docker-file {:exposed-ports [80]
|
||||
:docker-file "test/resources/Dockerfile"})
|
||||
initialized-container (start! container)
|
||||
stopped-container (stop! container)]
|
||||
(let [container (sut/create-from-docker-file {:exposed-ports [80]
|
||||
:docker-file "test/resources/Dockerfile"})
|
||||
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) 80)))
|
||||
|
|
@ -30,37 +109,38 @@
|
|||
|
||||
|
||||
(testing "Executing a command in the running Docker container with a custom container"
|
||||
(let [container (init {:container (PostgreSQLContainer. "postgres:12.2")})
|
||||
initialized-container (start! container)
|
||||
result (execute-command! initialized-container ["whoami"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (sut/init {:container (PostgreSQLContainer. "postgres:15.3")
|
||||
:exposed-ports [5432]})
|
||||
initialized-container (sut/start! container)
|
||||
result (sut/execute-command! initialized-container ["whoami"])
|
||||
_stopped-container (sut/stop! container)]
|
||||
(is (= 0 (:exit-code result)))
|
||||
(is (= "root\n" (:stdout result))))))
|
||||
|
||||
(deftest execute-command-in-container
|
||||
|
||||
(testing "Executing a command in the running Docker container"
|
||||
(let [container (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
initialized-container (start! container)
|
||||
result (execute-command! initialized-container ["whoami"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
initialized-container (sut/start! container)
|
||||
result (sut/execute-command! initialized-container ["whoami"])
|
||||
_stopped-container (sut/stop! container)]
|
||||
(is (= 0 (:exit-code result)))
|
||||
(is (= "root\n" (:stdout result))))))
|
||||
|
||||
(deftest init-volume-test
|
||||
|
||||
(testing "Testing mapping of a classpath resource"
|
||||
(let [container (-> (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(map-classpath-resource! {:resource-path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:mode :read-only}))
|
||||
initialized-container (start! container)
|
||||
file-check (execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (-> (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(sut/map-classpath-resource! {:resource-path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:mode :read-only}))
|
||||
initialized-container (sut/start! container)
|
||||
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
stopped-container (sut/stop! container)]
|
||||
(is (some? (:id initialized-container)))
|
||||
(is (some? (:mapped-ports initialized-container)))
|
||||
(is (some? (get (:mapped-ports initialized-container) 5432)))
|
||||
|
|
@ -69,15 +149,15 @@
|
|||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
(testing "Testing mapping of a filesystem-binding"
|
||||
(let [container (-> (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(bind-filesystem! {:host-path "."
|
||||
:container-path "/opt"
|
||||
:mode :read-only}))
|
||||
initialized-container (start! container)
|
||||
file-check (execute-command! initialized-container ["tail" "/opt/README.md"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (-> (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(sut/bind-filesystem! {:host-path "."
|
||||
:container-path "/opt"
|
||||
:mode :read-only}))
|
||||
initialized-container (sut/start! container)
|
||||
file-check (sut/execute-command! initialized-container ["tail" "/opt/README.md"])
|
||||
stopped-container (sut/stop! container)]
|
||||
(is (some? (:id initialized-container)))
|
||||
(is (some? (:mapped-ports initialized-container)))
|
||||
(is (some? (get (:mapped-ports initialized-container) 5432)))
|
||||
|
|
@ -86,15 +166,15 @@
|
|||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
(testing "Copying a file from the host into the container"
|
||||
(let [container (-> (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path}))
|
||||
initialized-container (start! container)
|
||||
file-check (execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (-> (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(sut/copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path}))
|
||||
initialized-container (sut/start! container)
|
||||
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
stopped-container (sut/stop! container)]
|
||||
(is (some? (:id initialized-container)))
|
||||
(is (some? (:mapped-ports initialized-container)))
|
||||
(is (some? (get (:mapped-ports initialized-container) 5432)))
|
||||
|
|
@ -102,16 +182,55 @@
|
|||
(is (nil? (:id stopped-container)))
|
||||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
|
||||
(testing "Copying a file from the classpath into the container"
|
||||
(let [container (-> (create {:image-name "postgres:12.2"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :classpath-resource}))
|
||||
initialized-container (start! container)
|
||||
file-check (execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
stopped-container (stop! container)]
|
||||
(let [container (-> (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
(sut/copy-file-to-container! {:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :classpath-resource}))
|
||||
initialized-container (sut/start! container)
|
||||
file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"])
|
||||
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 (= 0 (:exit-code file-check)))
|
||||
(is (nil? (:id stopped-container)))
|
||||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
(testing "Copying a file from the host into a running container"
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
initialized-container (sut/start! container)
|
||||
_ (sut/copy-file-to-container! initialized-container
|
||||
{:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :host-path})
|
||||
file-check (sut/execute-command! initialized-container
|
||||
["tail" "/opt/test.sql"])
|
||||
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 (= 0 (:exit-code file-check)))
|
||||
(is (nil? (:id stopped-container)))
|
||||
(is (nil? (:mapped-ports stopped-container)))))
|
||||
|
||||
(testing "Copying a file from the classpath into a running container"
|
||||
(let [container (sut/create {:image-name "postgres:15.3"
|
||||
:exposed-ports [5432]
|
||||
:env-vars {"POSTGRES_PASSWORD" "pw"}})
|
||||
initialized-container (sut/start! container)
|
||||
_ (sut/copy-file-to-container! initialized-container
|
||||
{:path "test.sql"
|
||||
:container-path "/opt/test.sql"
|
||||
:type :classpath-resource})
|
||||
file-check (sut/execute-command! initialized-container
|
||||
["tail" "/opt/test.sql"])
|
||||
stopped-container (sut/stop! container)]
|
||||
(is (some? (:id initialized-container)))
|
||||
(is (some? (:mapped-ports initialized-container)))
|
||||
(is (some? (get (:mapped-ports initialized-container) 5432)))
|
||||
|
|
@ -119,25 +238,22 @@
|
|||
(is (nil? (:id stopped-container)))
|
||||
(is (nil? (:mapped-ports stopped-container))))))
|
||||
|
||||
|
||||
(deftest networking-test
|
||||
|
||||
(testing "Putting two containers into the same network and check their communication"
|
||||
(let [network (init-network)
|
||||
server-container (create {:image-name "alpine:3.5"
|
||||
:network network
|
||||
:network-aliases ["foo"]
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]})
|
||||
client-container (create {:image-name "alpine:3.5"
|
||||
:network network
|
||||
:command ["top"]})
|
||||
started-server (start! server-container)
|
||||
started-client (start! client-container)
|
||||
response (execute-command! started-client ["wget", "-O", "-", "http://foo:8080"])
|
||||
stopped-server (stop! started-server)
|
||||
stopped-client (stop! started-client)]
|
||||
|
||||
(let [network (sut/create-network)
|
||||
server-container (sut/create {:image-name "alpine:3.5"
|
||||
:network network
|
||||
:network-aliases ["foo"]
|
||||
:command ["/bin/sh"
|
||||
"-c"
|
||||
"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"
|
||||
:network network
|
||||
:command ["top"]})
|
||||
started-server (sut/start! server-container)
|
||||
started-client (sut/start! client-container)
|
||||
response (sut/execute-command! started-client ["wget", "-O", "-", "http://foo:8080"])
|
||||
_stopped-server (sut/stop! started-server)
|
||||
_stopped-client (sut/stop! started-client)]
|
||||
(is (= 0 (:exit-code response)))
|
||||
(is (= "yay" (:stdout response))))))
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
{:plugins [:kaocha.plugin/junit-xml
|
||||
:kaocha.plugin/cloverage
|
||||
:kaocha.plugin.alpha/spec-test-check]
|
||||
:reporter [kaocha.report/documentation]
|
||||
:kaocha.plugin.junit-xml/target-file "target/junit.xml"}
|
||||
|
|
|
|||
Loading…
Reference in a new issue