Skip to content

Commit

Permalink
feat(image): create a more complete function
Browse files Browse the repository at this point in the history
This function allows to fine-tune every aspect of the creation of the
container starting from the Dockerfile, it is probably overkill and I
might think about creating some helpful wrappers in the future.
  • Loading branch information
massix committed Jan 23, 2024
1 parent e37d988 commit f07febb
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 5 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ mkContainer "postgres:14-alpine"
It is also possible to create a container from a Dockerfile definition, the system
will `docker build` it for you and afterwards you will have a useable container.

There are actually three different flavours of the same basic functionality:
1. `mkContainerFromDockerfile` which takes a `FilePath` to a context (i.e., a folder
containing a `Dockerfile`) and the name of an image, which will be used as the
result of the build;
1. `mkContainerFromDockerfile'` which takes a `FilePath` to a context, a single
`Dockerfile` which **must** be inside of the context and the name of an image,
which will be used as the result of the build;
1. a more complete `mkContainerFromDockerfileOpts` which takes a lot of parameters but
gives more control about all the building options, the parameter it takes are:
* `FilePath` to a context;
* `Maybe String` of a Dockerfile (if `Nothing`, will use `Dockerfile`);
* `Maybe PullPolicy` to specify the `PullPolicy` to use;
* `IsImage` for the image name;
* `Maybe (Array KV)` for the build arguments;
* `Maybe Boolean` which, if `Just true` will reuse the cache

#### Example

```purescript
Expand All @@ -263,6 +279,9 @@ main = do
cnt <- mkContainerFromDockerfile' "./path/to/a/folder" "Dockerfile" "my-image:latest"
```

For the third version of the function, it is better to take a look at the
[test file directly](./test/Test/Images.purs), which contains a full working call of the function.

### Start a container
Once you have your container, you can start it by calling the `startContainer`
function, for which the signature is the following:
Expand Down
30 changes: 30 additions & 0 deletions src/Test/Testcontainers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@ const cloneContainer = container => {

export const mkContainerImpl = Constructor => image => Constructor(new GenericContainer(image));

export const mkContainerFromDockerfileOptsImpl = Constructor => contextPath => maybeDockerfile => maybePullPolicy => img => maybeBuildArgs => maybeCache => Left => Right => async () => {
const container = GenericContainer.fromDockerfile(contextPath, maybeDockerfile.constructor.name === "Just" ? maybeDockerfile.value0 : undefined);

if (maybeBuildArgs.constructor.name === "Just") {
const converted = maybeBuildArgs.value0.reduce((acc, curr) => {
acc[curr.key] = curr.value;
return acc;
}, {});
container.withBuildArgs(converted);
}

if (maybeCache.constructor.name === "Just") {
container.withCache(maybeCache.value0);
}

if (maybePullPolicy.constructor.name === "Just") {
if (maybePullPolicy.value0.constructor.name === "AlwaysPull") {
container.withPullPolicy(PullPolicy.alwaysPull());
} else {
container.withPullPolicy(PullPolicy.defaultPolicy());
}
}

try {
return Right(Constructor(await container.build(img)));
} catch (e) {
return Left(e.message);
}
};

export const mkContainerFromDockerfileImpl = Constructor => contextPath => image => async () => {
const container = await GenericContainer.fromDockerfile(contextPath).withCache(false).build(image);
return Constructor(container);
Expand Down
29 changes: 29 additions & 0 deletions src/Test/Testcontainers.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Test.Testcontainers
, mkContainer
, mkContainerFromDockerfile
, mkContainerFromDockerfile'
, mkContainerFromDockerfileOpts
, restartContainer
, setAddedCapabilities
, setBindMounts
Expand Down Expand Up @@ -44,6 +45,7 @@ import Prelude

import Control.Promise (Promise, toAffE)
import Data.Either (Either(..))
import Data.Maybe (Maybe)
import Effect (Effect)
import Effect.Aff (Aff)
import Effect.Aff.Class (class MonadAff, liftAff)
Expand All @@ -53,6 +55,18 @@ import Test.Testcontainers.Types (class IsImage, BindMounts, Capability, CopyCon
foreign import mkContainerImpl :: (GenericContainer -> TestContainer) -> String -> TestContainer
foreign import mkContainerFromDockerfileImpl :: (GenericContainer -> TestContainer) -> String -> String -> Effect (Promise TestContainer)
foreign import mkContainerFromDockerfileCustomDockerfileImpl :: (GenericContainer -> TestContainer) -> String -> String -> String -> Effect (Promise TestContainer)
foreign import mkContainerFromDockerfileOptsImpl
:: (GenericContainer -> TestContainer) --^ constructor
-> String --^ ContextPath
-> Maybe String --^ Dockerfile
-> Maybe PullPolicy --^ Pull Policy
-> String --^ Image
-> Maybe (Array KV) --^ Build Args
-> Maybe Boolean --^ Cache
-> (String -> Either String TestContainer) --^ Left
-> (TestContainer -> Either String TestContainer) --^ Right
-> Effect (Promise (Either String TestContainer))

foreign import setExposedPortsImpl :: TestContainer -> (GenericContainer -> TestContainer) -> Array Int -> TestContainer
foreign import setPullPolicyImpl :: TestContainer -> (GenericContainer -> TestContainer) -> PullPolicy -> TestContainer
foreign import setCommandImpl :: TestContainer -> (GenericContainer -> TestContainer) -> Array String -> TestContainer
Expand Down Expand Up @@ -141,6 +155,21 @@ mkContainer i =
in
mkContainerImpl (GenericContainer s) orig

mkContainerFromDockerfileOpts
:: a. IsImage a
=> FilePath
-> Maybe String
-> Maybe PullPolicy
-> a
-> Maybe (Array KV)
-> Maybe Boolean
-> Aff (Either String TestContainer)
mkContainerFromDockerfileOpts context fp pp img buildArgs cache =
let
s@(Image orig) = toImage img
in
toAffE $ mkContainerFromDockerfileOptsImpl (GenericContainer s) context fp pp orig buildArgs cache Left Right

mkContainerFromDockerfile :: a. IsImage a => FilePath -> a -> Aff TestContainer
mkContainerFromDockerfile fp img =
let
Expand Down
40 changes: 35 additions & 5 deletions test/Test/Images.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ module Test.Images where
import Prelude

import Control.Monad.Error.Class (throwError)
import Data.Either (Either(..))
import Data.Either (Either(..), isRight)
import Data.Maybe (Maybe(..))
import Effect.Aff (error)
import Partial.Unsafe (unsafePartial)
import Test.Partials (forceRight)
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)
import Test.Testcontainers (mkContainerFromDockerfile, mkContainerFromDockerfile', setCommand, withContainer)
import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
import Test.Testcontainers (mkContainerFromDockerfile, mkContainerFromDockerfile', mkContainerFromDockerfileOpts, setCommand, withContainer)
import Test.Utils (launchCommand)

imagesTest :: Spec Unit
Expand All @@ -16,7 +19,7 @@ imagesTest =
it "should create a container from a Dockerfile" $ do
cnt <- mkContainerFromDockerfile "./test/images/" "my-image:latest"

res <- withContainer (setCommand ["sleep", "360"] cnt) $ \c -> do
res <- withContainer (setCommand [ "sleep", "360" ] cnt) $ \c -> do
-- figlet has been installed from the Dockerfile
launchCommand c [ "which", "figlet" ]
(\s -> s `shouldEqual` "/usr/bin/figlet\n")
Expand All @@ -28,7 +31,34 @@ imagesTest =

it "should create a container from a custom Dockerfile" $ do
cnt <- mkContainerFromDockerfile' "./test/images" "Dockerfile.alpine" "my-image:latest"
res <- withContainer (setCommand ["sleep", "360"] cnt) $ \c -> do
res <- withContainer (setCommand [ "sleep", "360" ] cnt) $ \c -> do
launchCommand c [ "which", "curl" ]
(\s -> s `shouldEqual` "/usr/bin/curl\n")
(\code -> code `shouldEqual` 0)

case res of
Left e -> throwError $ error e
Right _ -> pure unit

it "should be able to fine tune the creation of a container" $ do
cnt <- mkContainerFromDockerfileOpts "./test/images"
(Just "DockerfileWithBuildArgs")
Nothing
"my-image:latest"
( Just
[ { key: "FIRST_PKG", value: "figlet" }
, { key: "SECOND_PKG", value: "curl" }
]
)
(Just false)
cnt `shouldSatisfy` isRight
let cnt' = unsafePartial $ forceRight cnt

res <- withContainer (setCommand [ "sleep", "360" ] cnt') $ \c -> do
launchCommand c [ "which", "figlet" ]
(\s -> s `shouldEqual` "/usr/bin/figlet\n")
(\code -> code `shouldEqual` 0)

launchCommand c [ "which", "curl" ]
(\s -> s `shouldEqual` "/usr/bin/curl\n")
(\code -> code `shouldEqual` 0)
Expand Down
8 changes: 8 additions & 0 deletions test/images/DockerfileWithBuildArgs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# vim: ft=dockerfile

FROM alpine:latest

ARG FIRST_PKG
ARG SECOND_PKG

RUN apk add --no-cache $FIRST_PKG $SECOND_PKG

0 comments on commit f07febb

Please sign in to comment.