@@ -7,6 +7,7 @@ module Docker.Client.Api (
7 , createContainer
7 , createContainer
8 , startContainer
8 , startContainer
9 , stopContainer
9 , stopContainer
10 , killContainer
11 , killContainer
11 , restartContainer
12 , restartContainer
12 , pauseContainer
13 , pauseContainer
@@ -14,110 +15,131 @@ module Docker.Client.Api (
14 , deleteContainer
15 , deleteContainer
15 , inspectContainer
16 , inspectContainer
16 , getContainerLogs
17 , getContainerLogs
18+ , getContainerLogsStream
17 -- * Images
19 -- * Images
18 , listImages
20 , listImages
21+ , buildImageFromDockerfile
19 -- * Other
22 -- * Other
20 , getDockerVersion
23 , getDockerVersion
23−import Control.Monad.Except (ExceptT (..), runExceptT, throwError)
26+import Control.Monad.Catch (MonadMask(..))
24−import Control.Monad.Reader (ask, lift)
27+import Control.Monad.IO.Class
28+import Control.Monad.Reader (ask, lift)
25−import Data.Aeson (FromJSON, eitherDecode')
29+import Data.Aeson (FromJSON, eitherDecode')
26−import qualified Data.ByteString.Lazy as BSL
30+import qualified Data.ByteString as BS
31+import qualified Data.ByteString.Lazy as BSL
32+import Data.Conduit (Sink)
33+import qualified Data.Conduit.Binary as Conduit
27−import qualified Data.Text as Text
34+import qualified Data.Text as Text
28−import Network.HTTP.Client (responseBody, responseStatus)
35+import Network.HTTP.Client (responseStatus)
29import Network.HTTP.Types (StdMethod (..))
36import Network.HTTP.Types (StdMethod (..))
37+import System.Exit (ExitCode(..))
31import Docker.Client.Http
39import Docker.Client.Http
32import Docker.Client.Types
40import Docker.Client.Types
41+import Docker.Client.Utils
34−requestUnit :: (Monad m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError ())
43+requestUnit :: (MonadIO m, MonadMask m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError ())
35requestUnit verb endpoint = const (Right ()) <$> requestHelper verb endpoint
44requestUnit verb endpoint = const (Right ()) <$> requestHelper verb endpoint
37−requestHelper :: (Monad m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError Response)
46+requestHelper :: (MonadIO m, MonadMask m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError BSL.ByteString)
38−requestHelper verb endpoint = runExceptT $ do
47+requestHelper verb endpoint = requestHelper' verb endpoint Conduit.sinkLbs
49+requestHelper' :: (MonadIO m, MonadMask m) => HttpVerb -> Endpoint -> Sink BS.ByteString m a -> DockerT m (Either DockerError a)
50+requestHelper' verb endpoint sink = do
39− (opts, httpHandler) <- lift ask
51+ (opts, HttpHandler httpHandler) <- ask
40 case mkHttpRequest verb endpoint opts of
52 case mkHttpRequest verb endpoint opts of
41 Nothing ->
53 Nothing ->
42− throwError $ DockerInvalidRequest endpoint
54+ return $ Left $ DockerInvalidRequest endpoint
43 Just request -> do
55 Just request -> do
56+ -- JP: Do we need runResourceT?
57+ -- lift $ NHS.httpSink request $ \response ->
44− response <- ExceptT $ lift $ httpHandler request
58+ lift $ httpHandler request $ \response ->
46− -- Check status code.
59+ -- Check status code.
47− let status = responseStatus response
60+ let status = responseStatus response in
48− maybe (return ()) throwError $
49− statusCodeToError endpoint status
61+ case statusCodeToError endpoint status of
51− return response
63+ return $ Left err
53−parseResponse :: (FromJSON a, Monad m) => Either DockerError Response -> DockerT m (Either DockerError a)
67+parseResponse :: (FromJSON a, Monad m) => Either DockerError BSL.ByteString -> DockerT m (Either DockerError a)
54parseResponse (Left err) =
68parseResponse (Left err) =
55 return $ Left err
69 return $ Left err
56parseResponse (Right response) =
70parseResponse (Right response) =
57 -- Parse request body.
71 -- Parse request body.
58− case eitherDecode' $ responseBody response of
72+ case eitherDecode' response of
59 Left err ->
73 Left err ->
60 return $ Left $ DockerClientDecodeError $ Text.pack err
74 return $ Left $ DockerClientDecodeError $ Text.pack err
61 Right r ->
75 Right r ->
62 return $ Right r
76 return $ Right r
64-- | Gets the version of the docker engine remote API.
78-- | Gets the version of the docker engine remote API.
65−getDockerVersion :: forall m. Monad m => DockerT m (Either DockerError DockerVersion)
79+getDockerVersion :: forall m. (MonadIO m, MonadMask m) => DockerT m (Either DockerError DockerVersion)
66getDockerVersion = requestHelper GET VersionEndpoint >>= parseResponse
80getDockerVersion = requestHelper GET VersionEndpoint >>= parseResponse
68-- | Lists all running docker containers. Pass in @'defaultListOpts' {all
82-- | Lists all running docker containers. Pass in @'defaultListOpts' {all
69-- = True}@ to get a list of stopped containers as well.
83-- = True}@ to get a list of stopped containers as well.
70−listContainers :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Container])
84+listContainers :: forall m. (MonadIO m, MonadMask m) => ListOpts -> DockerT m (Either DockerError [Container])
71listContainers opts = requestHelper GET (ListContainersEndpoint opts) >>= parseResponse
85listContainers opts = requestHelper GET (ListContainersEndpoint opts) >>= parseResponse
73-- | Lists all docker images.
87-- | Lists all docker images.
74−listImages :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Image])
88+listImages :: forall m. (MonadIO m, MonadMask m) => ListOpts -> DockerT m (Either DockerError [Image])
75listImages opts = requestHelper GET (ListImagesEndpoint opts) >>= parseResponse
89listImages opts = requestHelper GET (ListImagesEndpoint opts) >>= parseResponse
77-- | Creates a docker container but does __not__ start it. See
91-- | Creates a docker container but does __not__ start it. See
78-- 'CreateOpts' for a list of options and you can use 'defaultCreateOpts'
92-- 'CreateOpts' for a list of options and you can use 'defaultCreateOpts'
79-- for some sane defaults.
93-- for some sane defaults.
80−createContainer :: forall m. Monad m => CreateOpts -> Maybe ContainerName -> DockerT m (Either DockerError ContainerID)
94+createContainer :: forall m. (MonadIO m, MonadMask m) => CreateOpts -> Maybe ContainerName -> DockerT m (Either DockerError ContainerID)
81createContainer opts cn = requestHelper POST (CreateContainerEndpoint opts cn) >>= parseResponse
95createContainer opts cn = requestHelper POST (CreateContainerEndpoint opts cn) >>= parseResponse
83-- | Start a container from a given 'ContainerID' that we get from
97-- | Start a container from a given 'ContainerID' that we get from
84-- 'createContainer'. See 'StartOpts' for a list of configuration options
98-- 'createContainer'. See 'StartOpts' for a list of configuration options
85-- for starting a container. Use 'defaultStartOpts' for sane defaults.
99-- for starting a container. Use 'defaultStartOpts' for sane defaults.
86−startContainer :: forall m. Monad m => StartOpts -> ContainerID -> DockerT m (Either DockerError ())
100+startContainer :: forall m. (MonadIO m, MonadMask m) => StartOpts -> ContainerID -> DockerT m (Either DockerError ())
87startContainer sopts cid = requestUnit POST $ StartContainerEndpoint sopts cid
101startContainer sopts cid = requestUnit POST $ StartContainerEndpoint sopts cid
89-- | Attempts to stop a container with the given 'ContainerID' gracefully
103-- | Attempts to stop a container with the given 'ContainerID' gracefully
90-- (SIGTERM).
104-- (SIGTERM).
91-- The docker daemon will wait for the given 'Timeout' and then send
105-- The docker daemon will wait for the given 'Timeout' and then send
92-- a SIGKILL killing the container.
106-- a SIGKILL killing the container.
93−stopContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())
107+stopContainer :: forall m. (MonadIO m, MonadMask m) => Timeout -> ContainerID -> DockerT m (Either DockerError ())
94stopContainer t cid = requestUnit POST $ StopContainerEndpoint t cid
108stopContainer t cid = requestUnit POST $ StopContainerEndpoint t cid
110+-- | Blocks until a container with the given 'ContainerID' stops,
111+-- then returns the exit code
112+waitContainer :: forall m. (MonadIO m, MonadMask m) => ContainerID -> DockerT m (Either DockerError ExitCode)
113+waitContainer cid = fmap (fmap statusCodeToExitCode) (requestHelper POST (WaitContainerEndpoint cid) >>= parseResponse)
115+ statusCodeToExitCode (StatusCode 0) = ExitSuccess
116+ statusCodeToExitCode (StatusCode x) = ExitFailure x
96-- | Sends a 'Signal' to the container with the given 'ContainerID'. Same
118-- | Sends a 'Signal' to the container with the given 'ContainerID'. Same
97-- as 'stopContainer' but you choose the signal directly.
119-- as 'stopContainer' but you choose the signal directly.
98−killContainer :: forall m. Monad m => Signal -> ContainerID -> DockerT m (Either DockerError ())
120+killContainer :: forall m. (MonadIO m, MonadMask m) => Signal -> ContainerID -> DockerT m (Either DockerError ())
99killContainer s cid = requestUnit POST $ KillContainerEndpoint s cid
121killContainer s cid = requestUnit POST $ KillContainerEndpoint s cid
101-- | Restarts a container with the given 'ContainerID'.
123-- | Restarts a container with the given 'ContainerID'.
102−restartContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())
124+restartContainer :: forall m. (MonadIO m, MonadMask m) => Timeout -> ContainerID -> DockerT m (Either DockerError ())
103restartContainer t cid = requestUnit POST $ RestartContainerEndpoint t cid
125restartContainer t cid = requestUnit POST $ RestartContainerEndpoint t cid
105-- | Pauses a container with the given 'ContainerID'.
127-- | Pauses a container with the given 'ContainerID'.
106−pauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())
128+pauseContainer :: forall m. (MonadIO m, MonadMask m) => ContainerID -> DockerT m (Either DockerError ())
107pauseContainer cid = requestUnit POST $ PauseContainerEndpoint cid
129pauseContainer cid = requestUnit POST $ PauseContainerEndpoint cid
109-- | Unpauses a container with the given 'ContainerID'.
131-- | Unpauses a container with the given 'ContainerID'.
110−unpauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())
132+unpauseContainer :: forall m. (MonadIO m, MonadMask m) => ContainerID -> DockerT m (Either DockerError ())
111unpauseContainer cid = requestUnit GET $ UnpauseContainerEndpoint cid
133unpauseContainer cid = requestUnit GET $ UnpauseContainerEndpoint cid
113-- | Deletes a container with the given 'ContainerID'.
135-- | Deletes a container with the given 'ContainerID'.
114-- See "DeleteOpts" for options and use 'defaultDeleteOpts' for sane
136-- See "DeleteOpts" for options and use 'defaultDeleteOpts' for sane
115-- defaults.
137-- defaults.
116−deleteContainer :: forall m. Monad m => DeleteOpts -> ContainerID -> DockerT m (Either DockerError ())
138+deleteContainer :: forall m. (MonadIO m, MonadMask m) => DeleteOpts -> ContainerID -> DockerT m (Either DockerError ())
117deleteContainer dopts cid = requestUnit DELETE $ DeleteContainerEndpoint dopts cid
139deleteContainer dopts cid = requestUnit DELETE $ DeleteContainerEndpoint dopts cid
119-- | Gets 'ContainerDetails' for a given 'ContainerID'.
141-- | Gets 'ContainerDetails' for a given 'ContainerID'.
120−inspectContainer :: forall m . Monad m => ContainerID -> DockerT m (Either DockerError ContainerDetails)
142+inspectContainer :: forall m . (MonadIO m, MonadMask m) => ContainerID -> DockerT m (Either DockerError ContainerDetails)
121inspectContainer cid = requestHelper GET (InspectContainerEndpoint cid) >>= parseResponse
143inspectContainer cid = requestHelper GET (InspectContainerEndpoint cid) >>= parseResponse
123-- | Get's container's logs for a given 'ContainerID'.
145-- | Get's container's logs for a given 'ContainerID'.
@@ -135,12 +157,19 @@ inspectContainer cid = requestHelper GET (InspectContainerEndpoint cid) >>= pars
136-- The recommended method is to use one of the other 'LogDriverType's available (like
158-- The recommended method is to use one of the other 'LogDriverType's available (like
137-- syslog) for creating your containers.
159-- syslog) for creating your containers.
138−getContainerLogs :: forall m. Monad m => LogOpts -> ContainerID -> DockerT m (Either DockerError BSL.ByteString)
160+getContainerLogs :: forall m. (MonadIO m, MonadMask m) => LogOpts -> ContainerID -> DockerT m (Either DockerError BSL.ByteString)
139−getContainerLogs logopts cid = fmap responseBody <$> requestHelper GET (ContainerLogsEndpoint logopts False cid)
161+getContainerLogs logopts cid = requestHelper GET (ContainerLogsEndpoint logopts False cid)
141−-- TODO: Use http-conduit to output to a sink.
142−-- getContainerLogsStream :: forall m. Monad m => Sink BSL.ByteString m b -> LogOpts -> ContainerID -> DockerT m (Either DockerError b)
163+getContainerLogsStream :: forall m b . (MonadIO m, MonadMask m) => LogOpts -> ContainerID -> Sink BS.ByteString m b -> DockerT m (Either DockerError b)
143−-- getContainerLogsStream sink logopts cid = runResourceT $ do
164+getContainerLogsStream logopts cid sink = requestHelper' GET (ContainerLogsEndpoint logopts True cid) sink
144−-- response <- http request manager
165+-- JP: Should the second (follow) argument be True? XXX
145−-- responseBody response C.$$+- sink
166+
146−
167+-- TODO: Add X-Registry-Config
168+-- TODO: Add support for remote URLs to a Dockerfile
169+-- TODO: Clean up temp tar.gz file after the image is built
170+buildImageFromDockerfile :: forall m. (MonadIO m, MonadMask m) => BuildOpts -> FilePath -> DockerT m (Either DockerError ())
171+buildImageFromDockerfile opts base = do
172+ ctx <- makeBuildContext $ BuildContextRootDir base
174+ Left e -> return $ Left e
175+ Right c -> requestUnit POST (BuildImageEndpoint opts c)