{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module TestContainers.Docker
(
MonadDocker
, Config(..)
, defaultDockerConfig
, determineConfig
, Tracer
, Trace(..)
, newTracer
, withTrace
, ImageTag
, Image
, imageTag
, ContainerId
, Container
, containerId
, containerImage
, containerIp
, containerPort
, containerReleaseKey
, ToImage
, fromTag
, fromBuildContext
, fromDockerfile
, build
, DockerException(..)
, ContainerRequest
, containerRequest
, setName
, setCmd
, setRm
, setEnv
, setLink
, setExpose
, setWaitingFor
, run
, InspectOutput
, inspect
, stop
, kill
, rm
, withLogs
, WaitUntilReady
, waitUntilReady
, TimeoutException(..)
, waitUntilTimeout
, waitWithLogs
, UnexpectedEndOfPipe(..)
, Pipe(..)
, waitForLogLine
, dockerHostOs
, isDockerOnLinux
, waitUntilMappedPortReachable
, ResIO
, runResourceT
, (&)
) where
import Control.Applicative ((<|>))
import Control.Concurrent (threadDelay)
import Control.Exception (IOException, throw)
import Control.Monad.Catch (Exception, MonadCatch, MonadMask,
MonadThrow, bracket, throwM, try)
import Control.Monad.Fix (mfix)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.IO.Unlift (MonadUnliftIO (withRunInIO))
import Control.Monad.Reader (MonadReader (..), runReaderT)
import Control.Monad.Trans.Resource (MonadResource (liftResourceT),
ReleaseKey, ResIO, register,
runResourceT)
import Data.Aeson (Value, decode')
import qualified Data.Aeson.Optics as Optics
import qualified Data.ByteString.Lazy.Char8 as LazyByteString
import Data.Foldable (traverse_)
import Data.Function ((&))
import Data.List (find)
import Data.Text (Text, pack, strip, unpack)
import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.Text.Lazy as LazyText
import qualified Data.Text.Lazy.Encoding as LazyText
import GHC.Stack (withFrozenCallStack)
import qualified Network.Socket as Socket
import Optics.Fold (pre)
import Optics.Operators ((^?))
import Optics.Optic ((%))
import Prelude hiding (error, id)
import qualified Prelude
import System.Exit (ExitCode (..))
import System.IO (Handle, hClose)
import System.IO.Unsafe (unsafePerformIO)
import qualified System.Process as Process
import System.Timeout (timeout)
data Trace
= TraceDockerInvocation [Text] Text ExitCode
| TraceDockerStdout Text
| TraceDockerStderr Text
| TraceWaitUntilReady (Maybe Int)
| TraceOpenSocket Text Int (Maybe IOException)
deriving stock instance Eq Trace
deriving stock instance Show Trace
newtype Tracer = Tracer { Tracer -> Trace -> IO ()
unTracer :: Trace -> IO () }
deriving newtype instance Semigroup Tracer
deriving newtype instance Monoid Tracer
newTracer
:: (Trace -> IO ())
-> Tracer
newTracer :: (Trace -> IO ()) -> Tracer
newTracer Trace -> IO ()
action = Tracer :: (Trace -> IO ()) -> Tracer
Tracer
{
$sel:unTracer:Tracer :: Trace -> IO ()
unTracer = Trace -> IO ()
action
}
withTrace :: MonadIO m => Tracer -> Trace -> m ()
withTrace :: Tracer -> Trace -> m ()
withTrace Tracer
tracer Trace
trace =
IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Tracer -> Trace -> IO ()
unTracer Tracer
tracer Trace
trace
{-# INLINE withTrace #-}
data Config = Config
{
Config -> Maybe Int
configDefaultWaitTimeout :: Maybe Int
, Config -> Tracer
configTracer :: Tracer
}
defaultDockerConfig :: Config
defaultDockerConfig :: Config
defaultDockerConfig = Config :: Maybe Int -> Tracer -> Config
Config
{
$sel:configDefaultWaitTimeout:Config :: Maybe Int
configDefaultWaitTimeout = Int -> Maybe Int
forall a. a -> Maybe a
Just Int
60
, $sel:configTracer:Config :: Tracer
configTracer = Tracer
forall a. Monoid a => a
mempty
}
determineConfig :: IO Config
determineConfig :: IO Config
determineConfig =
Config -> IO Config
forall (f :: * -> *) a. Applicative f => a -> f a
pure Config
defaultDockerConfig
data DockerException
= DockerException
{
DockerException -> ExitCode
exitCode :: ExitCode
, DockerException -> [Text]
args :: [Text]
, DockerException -> Text
stderr :: Text
}
| InspectUnknownContainerId { DockerException -> Text
id :: ContainerId }
| InspectOutputInvalidJSON { id :: ContainerId }
| InspectOutputUnexpected { id :: ContainerId }
| UnknownPortMapping
{
id :: ContainerId
, DockerException -> Text
port :: Text
}
deriving (DockerException -> DockerException -> Bool
(DockerException -> DockerException -> Bool)
-> (DockerException -> DockerException -> Bool)
-> Eq DockerException
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: DockerException -> DockerException -> Bool
$c/= :: DockerException -> DockerException -> Bool
== :: DockerException -> DockerException -> Bool
$c== :: DockerException -> DockerException -> Bool
Eq, Int -> DockerException -> ShowS
[DockerException] -> ShowS
DockerException -> String
(Int -> DockerException -> ShowS)
-> (DockerException -> String)
-> ([DockerException] -> ShowS)
-> Show DockerException
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DockerException] -> ShowS
$cshowList :: [DockerException] -> ShowS
show :: DockerException -> String
$cshow :: DockerException -> String
showsPrec :: Int -> DockerException -> ShowS
$cshowsPrec :: Int -> DockerException -> ShowS
Show)
instance Exception DockerException
type MonadDocker m =
(MonadIO m, MonadMask m, MonadThrow m, MonadCatch m, MonadResource m, MonadReader Config m)
data ContainerRequest = ContainerRequest
{
ContainerRequest -> ToImage
toImage :: ToImage
, ContainerRequest -> Maybe [Text]
cmd :: Maybe [Text]
, ContainerRequest -> [(Text, Text)]
env :: [(Text, Text)]
, ContainerRequest -> [Int]
exposedPorts :: [Int]
, ContainerRequest -> [(Text, Text)]
volumeMounts :: [(Text, Text)]
, ContainerRequest -> [Text]
links :: [ContainerId]
, ContainerRequest -> Maybe Text
name :: Maybe Text
, ContainerRequest -> Bool
rmOnExit :: Bool
, ContainerRequest -> Maybe WaitUntilReady
readiness :: Maybe WaitUntilReady
}
containerRequest :: ToImage -> ContainerRequest
containerRequest :: ToImage -> ContainerRequest
containerRequest ToImage
image = ContainerRequest :: ToImage
-> Maybe [Text]
-> [(Text, Text)]
-> [Int]
-> [(Text, Text)]
-> [Text]
-> Maybe Text
-> Bool
-> Maybe WaitUntilReady
-> ContainerRequest
ContainerRequest
{
$sel:toImage:ContainerRequest :: ToImage
toImage = ToImage
image
, $sel:name:ContainerRequest :: Maybe Text
name = Maybe Text
forall a. Maybe a
Nothing
, $sel:cmd:ContainerRequest :: Maybe [Text]
cmd = Maybe [Text]
forall a. Maybe a
Nothing
, $sel:env:ContainerRequest :: [(Text, Text)]
env = []
, $sel:exposedPorts:ContainerRequest :: [Int]
exposedPorts = []
, $sel:volumeMounts:ContainerRequest :: [(Text, Text)]
volumeMounts = []
, $sel:links:ContainerRequest :: [Text]
links = []
, $sel:rmOnExit:ContainerRequest :: Bool
rmOnExit = Bool
True
, $sel:readiness:ContainerRequest :: Maybe WaitUntilReady
readiness = Maybe WaitUntilReady
forall a. Maybe a
Nothing
}
setName :: Text -> ContainerRequest -> ContainerRequest
setName :: Text -> ContainerRequest -> ContainerRequest
setName Text
newName ContainerRequest
req =
ContainerRequest
req { $sel:name:ContainerRequest :: Maybe Text
name = Text -> Maybe Text
forall a. a -> Maybe a
Just Text
newName }
setCmd :: [Text] -> ContainerRequest -> ContainerRequest
setCmd :: [Text] -> ContainerRequest -> ContainerRequest
setCmd [Text]
newCmd ContainerRequest
req =
ContainerRequest
req { $sel:cmd:ContainerRequest :: Maybe [Text]
cmd = [Text] -> Maybe [Text]
forall a. a -> Maybe a
Just [Text]
newCmd }
setRm :: Bool -> ContainerRequest -> ContainerRequest
setRm :: Bool -> ContainerRequest -> ContainerRequest
setRm Bool
newRm ContainerRequest
req =
ContainerRequest
req { $sel:rmOnExit:ContainerRequest :: Bool
rmOnExit = Bool
newRm }
setEnv :: [(Text, Text)] -> ContainerRequest -> ContainerRequest
setEnv :: [(Text, Text)] -> ContainerRequest -> ContainerRequest
setEnv [(Text, Text)]
newEnv ContainerRequest
req =
ContainerRequest
req { $sel:env:ContainerRequest :: [(Text, Text)]
env = [(Text, Text)]
newEnv }
setLink :: [ContainerId] -> ContainerRequest -> ContainerRequest
setLink :: [Text] -> ContainerRequest -> ContainerRequest
setLink [Text]
newLink ContainerRequest
req =
ContainerRequest
req { $sel:links:ContainerRequest :: [Text]
links = [Text]
newLink }
setExpose :: [Int] -> ContainerRequest -> ContainerRequest
setExpose :: [Int] -> ContainerRequest -> ContainerRequest
setExpose [Int]
newExpose ContainerRequest
req =
ContainerRequest
req { $sel:exposedPorts:ContainerRequest :: [Int]
exposedPorts = [Int]
newExpose }
setWaitingFor :: WaitUntilReady -> ContainerRequest -> ContainerRequest
setWaitingFor :: WaitUntilReady -> ContainerRequest -> ContainerRequest
setWaitingFor WaitUntilReady
newWaitingFor ContainerRequest
req =
ContainerRequest
req { $sel:readiness:ContainerRequest :: Maybe WaitUntilReady
readiness = WaitUntilReady -> Maybe WaitUntilReady
forall a. a -> Maybe a
Just WaitUntilReady
newWaitingFor }
run :: MonadDocker m => ContainerRequest -> m Container
run :: ContainerRequest -> m Container
run ContainerRequest
request = do
let
ContainerRequest
{
ToImage
toImage :: ToImage
$sel:toImage:ContainerRequest :: ContainerRequest -> ToImage
toImage
, Maybe Text
name :: Maybe Text
$sel:name:ContainerRequest :: ContainerRequest -> Maybe Text
name
, Maybe [Text]
cmd :: Maybe [Text]
$sel:cmd:ContainerRequest :: ContainerRequest -> Maybe [Text]
cmd
, [(Text, Text)]
env :: [(Text, Text)]
$sel:env:ContainerRequest :: ContainerRequest -> [(Text, Text)]
env
, [Int]
exposedPorts :: [Int]
$sel:exposedPorts:ContainerRequest :: ContainerRequest -> [Int]
exposedPorts
, [(Text, Text)]
volumeMounts :: [(Text, Text)]
$sel:volumeMounts:ContainerRequest :: ContainerRequest -> [(Text, Text)]
volumeMounts
, [Text]
links :: [Text]
$sel:links:ContainerRequest :: ContainerRequest -> [Text]
links
, Bool
rmOnExit :: Bool
$sel:rmOnExit:ContainerRequest :: ContainerRequest -> Bool
rmOnExit
, Maybe WaitUntilReady
readiness :: Maybe WaitUntilReady
$sel:readiness:ContainerRequest :: ContainerRequest -> Maybe WaitUntilReady
readiness
} = ContainerRequest
request
image :: Image
image@Image{ Text
$sel:tag:Image :: Image -> Text
tag :: Text
tag } <- ToImage -> forall (m :: * -> *). MonadDocker m => m Image
runToImage ToImage
toImage
let
dockerRun :: [Text]
dockerRun :: [Text]
dockerRun = [[Text]] -> [Text]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Text]] -> [Text]) -> [[Text]] -> [Text]
forall a b. (a -> b) -> a -> b
$
[ [ Text
"run" ] ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--detach" ] ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--name", Text
containerName ] | Just Text
containerName <- [Maybe Text
name] ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--env", Text
variable Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"=" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
value ] | (Text
variable, Text
value) <- [(Text, Text)]
env ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--publish", String -> Text
pack (Int -> String
forall a. Show a => a -> String
show Int
port)] | Int
port <- [Int]
exposedPorts ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--link", Text
container ] | Text
container <- [Text]
links ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--volume", Text
src Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dest ] | (Text
src, Text
dest) <- [(Text, Text)]
volumeMounts ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
"--rm" ] | Bool
rmOnExit ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [ Text
tag ] ] [[Text]] -> [[Text]] -> [[Text]]
forall a. [a] -> [a] -> [a]
++
[ [Text]
command | Just [Text]
command <- [Maybe [Text]
cmd] ]
config :: Config
config@Config { Tracer
configTracer :: Tracer
$sel:configTracer:Config :: Config -> Tracer
configTracer } <- m Config
forall r (m :: * -> *). MonadReader r m => m r
ask
String
stdout <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
configTracer [Text]
dockerRun
let
id :: ContainerId
!id :: Text
id =
Text -> Text
strip (String -> Text
pack String
stdout)
~InspectOutput
inspectOutput = IO InspectOutput -> InspectOutput
forall a. IO a -> a
unsafePerformIO (IO InspectOutput -> InspectOutput)
-> IO InspectOutput -> InspectOutput
forall a b. (a -> b) -> a -> b
$
Tracer -> Text -> IO InspectOutput
forall (m :: * -> *).
(MonadThrow m, MonadIO m) =>
Tracer -> Text -> m InspectOutput
internalInspect Tracer
configTracer Text
id
Container
container <- ResourceT IO Container -> m Container
forall (m :: * -> *) a. MonadResource m => ResourceT IO a -> m a
liftResourceT (ResourceT IO Container -> m Container)
-> ResourceT IO Container -> m Container
forall a b. (a -> b) -> a -> b
$ (Container -> ResourceT IO Container) -> ResourceT IO Container
forall (m :: * -> *) a. MonadFix m => (a -> m a) -> m a
mfix ((Container -> ResourceT IO Container) -> ResourceT IO Container)
-> (Container -> ResourceT IO Container) -> ResourceT IO Container
forall a b. (a -> b) -> a -> b
$ \Container
container -> do
ReleaseKey
releaseKey <- IO () -> ResourceT IO ReleaseKey
forall (m :: * -> *). MonadResource m => IO () -> m ReleaseKey
register (IO () -> ResourceT IO ReleaseKey)
-> IO () -> ResourceT IO ReleaseKey
forall a b. (a -> b) -> a -> b
$ ReaderT Config IO () -> Config -> IO ()
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (ResourceT (ReaderT Config IO) () -> ReaderT Config IO ()
forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT (Container -> ResourceT (ReaderT Config IO) ()
forall (m :: * -> *). MonadDocker m => Container -> m ()
stop Container
container)) Config
config
Container -> ResourceT IO Container
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Container -> ResourceT IO Container)
-> Container -> ResourceT IO Container
forall a b. (a -> b) -> a -> b
$ Container :: Text -> ReleaseKey -> Image -> Config -> InspectOutput -> Container
Container
{
Text
$sel:id:Container :: Text
id :: Text
id
, ReleaseKey
$sel:releaseKey:Container :: ReleaseKey
releaseKey :: ReleaseKey
releaseKey
, Image
$sel:image:Container :: Image
image :: Image
image
, InspectOutput
$sel:inspectOutput:Container :: InspectOutput
inspectOutput :: InspectOutput
inspectOutput
, Config
$sel:config:Container :: Config
config :: Config
config
}
case Maybe WaitUntilReady
readiness of
Just WaitUntilReady
wait ->
Container -> WaitUntilReady -> m ()
forall (m :: * -> *).
MonadDocker m =>
Container -> WaitUntilReady -> m ()
waitUntilReady Container
container WaitUntilReady
wait
Maybe WaitUntilReady
Nothing ->
() -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Container -> m Container
forall (f :: * -> *) a. Applicative f => a -> f a
pure Container
container
docker :: MonadIO m => Tracer -> [Text] -> m String
docker :: Tracer -> [Text] -> m String
docker Tracer
tracer [Text]
args =
Tracer -> [Text] -> Text -> m String
forall (m :: * -> *).
MonadIO m =>
Tracer -> [Text] -> Text -> m String
dockerWithStdin Tracer
tracer [Text]
args Text
""
dockerWithStdin :: MonadIO m => Tracer -> [Text] -> Text -> m String
dockerWithStdin :: Tracer -> [Text] -> Text -> m String
dockerWithStdin Tracer
tracer [Text]
args Text
stdin = IO String -> m String
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO String -> m String) -> IO String -> m String
forall a b. (a -> b) -> a -> b
$ do
(ExitCode
exitCode, String
stdout, String
stderr) <- String -> [String] -> String -> IO (ExitCode, String, String)
Process.readProcessWithExitCode String
"docker"
((Text -> String) -> [Text] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Text -> String
unpack [Text]
args) (Text -> String
unpack Text
stdin)
Tracer -> Trace -> IO ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
tracer ([Text] -> Text -> ExitCode -> Trace
TraceDockerInvocation [Text]
args Text
stdin ExitCode
exitCode)
(String -> IO ()) -> [String] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ (Tracer -> Trace -> IO ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
tracer (Trace -> IO ()) -> (String -> Trace) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Trace
TraceDockerStdout (Text -> Trace) -> (String -> Text) -> String -> Trace
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
pack) (String -> [String]
lines String
stdout)
(String -> IO ()) -> [String] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ (Tracer -> Trace -> IO ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
tracer (Trace -> IO ()) -> (String -> Trace) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Trace
TraceDockerStderr (Text -> Trace) -> (String -> Text) -> String -> Trace
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
pack) (String -> [String]
lines String
stderr)
case ExitCode
exitCode of
ExitCode
ExitSuccess -> String -> IO String
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
stdout
ExitCode
_ -> DockerException -> IO String
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (DockerException -> IO String) -> DockerException -> IO String
forall a b. (a -> b) -> a -> b
$ DockerException :: ExitCode -> [Text] -> Text -> DockerException
DockerException
{
ExitCode
exitCode :: ExitCode
$sel:exitCode:DockerException :: ExitCode
exitCode, [Text]
args :: [Text]
$sel:args:DockerException :: [Text]
args
, $sel:stderr:DockerException :: Text
stderr = String -> Text
pack String
stderr
}
kill :: MonadDocker m => Container -> m ()
kill :: Container -> m ()
kill Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } = do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
_ <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"kill", Text
id ]
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
stop :: MonadDocker m => Container -> m ()
stop :: Container -> m ()
stop Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } = do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
_ <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"stop", Text
id ]
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
rm :: MonadDocker m => Container -> m ()
rm :: Container -> m ()
rm Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } = do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
_ <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"rm", Text
"-f", Text
"-v", Text
id ]
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
withLogs :: forall m a . MonadDocker m => Container -> (Handle -> Handle -> m a) -> m a
withLogs :: Container -> (Handle -> Handle -> m a) -> m a
withLogs Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } Handle -> Handle -> m a
logger = do
let
acquire :: m (Handle, Handle, Handle, Process.ProcessHandle)
acquire :: m (Handle, Handle, Handle, ProcessHandle)
acquire =
IO (Handle, Handle, Handle, ProcessHandle)
-> m (Handle, Handle, Handle, ProcessHandle)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Handle, Handle, Handle, ProcessHandle)
-> m (Handle, Handle, Handle, ProcessHandle))
-> IO (Handle, Handle, Handle, ProcessHandle)
-> m (Handle, Handle, Handle, ProcessHandle)
forall a b. (a -> b) -> a -> b
$ String
-> [String]
-> Maybe String
-> Maybe [(String, String)]
-> IO (Handle, Handle, Handle, ProcessHandle)
Process.runInteractiveProcess
String
"docker"
[ String
"logs", String
"--follow", Text -> String
unpack Text
id ]
Maybe String
forall a. Maybe a
Nothing
Maybe [(String, String)]
forall a. Maybe a
Nothing
release :: (Handle, Handle, Handle, Process.ProcessHandle) -> m ()
release :: (Handle, Handle, Handle, ProcessHandle) -> m ()
release (Handle
stdin, Handle
stdout, Handle
stderr, ProcessHandle
handle) =
IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()
Process.cleanupProcess
(Handle -> Maybe Handle
forall a. a -> Maybe a
Just Handle
stdin, Handle -> Maybe Handle
forall a. a -> Maybe a
Just Handle
stdout, Handle -> Maybe Handle
forall a. a -> Maybe a
Just Handle
stderr, ProcessHandle
handle)
m (Handle, Handle, Handle, ProcessHandle)
-> ((Handle, Handle, Handle, ProcessHandle) -> m ())
-> ((Handle, Handle, Handle, ProcessHandle) -> m a)
-> m a
forall (m :: * -> *) a c b.
MonadMask m =>
m a -> (a -> m c) -> (a -> m b) -> m b
bracket m (Handle, Handle, Handle, ProcessHandle)
acquire (Handle, Handle, Handle, ProcessHandle) -> m ()
release (((Handle, Handle, Handle, ProcessHandle) -> m a) -> m a)
-> ((Handle, Handle, Handle, ProcessHandle) -> m a) -> m a
forall a b. (a -> b) -> a -> b
$ \(Handle
stdin, Handle
stdout, Handle
stderr, ProcessHandle
_handle) -> do
IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Handle -> IO ()
hClose Handle
stdin
Handle -> Handle -> m a
logger Handle
stdout Handle
stderr
type ImageTag = Text
data ToImage = ToImage
{
ToImage -> forall (m :: * -> *). MonadDocker m => m Image
runToImage :: forall m. MonadDocker m => m Image
, ToImage -> ContainerRequest -> ContainerRequest
applyToContainerRequest :: ContainerRequest -> ContainerRequest
}
build :: MonadDocker m => ToImage -> m ToImage
build :: ToImage -> m ToImage
build toImage :: ToImage
toImage@ToImage { ContainerRequest -> ContainerRequest
applyToContainerRequest :: ContainerRequest -> ContainerRequest
$sel:applyToContainerRequest:ToImage :: ToImage -> ContainerRequest -> ContainerRequest
applyToContainerRequest } = do
Image
image <- ToImage -> forall (m :: * -> *). MonadDocker m => m Image
runToImage ToImage
toImage
ToImage -> m ToImage
forall (m :: * -> *) a. Monad m => a -> m a
return (ToImage -> m ToImage) -> ToImage -> m ToImage
forall a b. (a -> b) -> a -> b
$ ToImage :: (forall (m :: * -> *). MonadDocker m => m Image)
-> (ContainerRequest -> ContainerRequest) -> ToImage
ToImage
{
$sel:runToImage:ToImage :: forall (m :: * -> *). MonadDocker m => m Image
runToImage = Image -> m Image
forall (f :: * -> *) a. Applicative f => a -> f a
pure Image
image
, ContainerRequest -> ContainerRequest
applyToContainerRequest :: ContainerRequest -> ContainerRequest
$sel:applyToContainerRequest:ToImage :: ContainerRequest -> ContainerRequest
applyToContainerRequest
}
defaultToImage :: (forall m . MonadDocker m => m Image) -> ToImage
defaultToImage :: (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
defaultToImage forall (m :: * -> *). MonadDocker m => m Image
action = ToImage :: (forall (m :: * -> *). MonadDocker m => m Image)
-> (ContainerRequest -> ContainerRequest) -> ToImage
ToImage
{
$sel:runToImage:ToImage :: forall (m :: * -> *). MonadDocker m => m Image
runToImage = forall (m :: * -> *). MonadDocker m => m Image
action
, $sel:applyToContainerRequest:ToImage :: ContainerRequest -> ContainerRequest
applyToContainerRequest = \ContainerRequest
x -> ContainerRequest
x
}
fromTag :: ImageTag -> ToImage
fromTag :: Text -> ToImage
fromTag Text
tag = (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
defaultToImage ((forall (m :: * -> *). MonadDocker m => m Image) -> ToImage)
-> (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
forall a b. (a -> b) -> a -> b
$ do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
output <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"pull", Text
"--quiet", Text
tag ]
Image -> m Image
forall (m :: * -> *) a. Monad m => a -> m a
return (Image -> m Image) -> Image -> m Image
forall a b. (a -> b) -> a -> b
$ Image :: Text -> Image
Image
{
$sel:tag:Image :: Text
tag = Text -> Text
strip (String -> Text
pack String
output)
}
fromBuildContext
:: FilePath
-> Maybe FilePath
-> ToImage
fromBuildContext :: String -> Maybe String -> ToImage
fromBuildContext String
path Maybe String
mdockerfile = (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
defaultToImage ((forall (m :: * -> *). MonadDocker m => m Image) -> ToImage)
-> (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
forall a b. (a -> b) -> a -> b
$ do
let
args :: [Text]
args
| Just String
dockerfile <- Maybe String
mdockerfile =
[ Text
"build", Text
"-f", String -> Text
pack String
dockerfile, String -> Text
pack String
path ]
| Bool
otherwise =
[ Text
"build", String -> Text
pack String
path ]
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
output <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [Text]
args
Image -> m Image
forall (m :: * -> *) a. Monad m => a -> m a
return (Image -> m Image) -> Image -> m Image
forall a b. (a -> b) -> a -> b
$ Image :: Text -> Image
Image
{
$sel:tag:Image :: Text
tag = Text -> Text
strip (String -> Text
pack String
output)
}
fromDockerfile
:: Text
-> ToImage
fromDockerfile :: Text -> ToImage
fromDockerfile Text
dockerfile = (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
defaultToImage ((forall (m :: * -> *). MonadDocker m => m Image) -> ToImage)
-> (forall (m :: * -> *). MonadDocker m => m Image) -> ToImage
forall a b. (a -> b) -> a -> b
$ do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
String
output <- Tracer -> [Text] -> Text -> m String
forall (m :: * -> *).
MonadIO m =>
Tracer -> [Text] -> Text -> m String
dockerWithStdin Tracer
tracer [ Text
"build", Text
"--quiet", Text
"-" ] Text
dockerfile
Image -> m Image
forall (m :: * -> *) a. Monad m => a -> m a
return (Image -> m Image) -> Image -> m Image
forall a b. (a -> b) -> a -> b
$ Image :: Text -> Image
Image
{
$sel:tag:Image :: Text
tag = Text -> Text
strip (String -> Text
pack String
output)
}
type ContainerId = Text
newtype WaitUntilReady = WaitUntilReady
{
WaitUntilReady -> Config -> Container -> (Maybe Int, ResIO ())
checkContainerReady :: Config -> Container -> (Maybe Int, ResIO ())
}
newtype UnexpectedEndOfPipe = UnexpectedEndOfPipe
{
UnexpectedEndOfPipe -> Text
id :: ContainerId
}
deriving (UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool
(UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool)
-> (UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool)
-> Eq UnexpectedEndOfPipe
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool
$c/= :: UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool
== :: UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool
$c== :: UnexpectedEndOfPipe -> UnexpectedEndOfPipe -> Bool
Eq, Int -> UnexpectedEndOfPipe -> ShowS
[UnexpectedEndOfPipe] -> ShowS
UnexpectedEndOfPipe -> String
(Int -> UnexpectedEndOfPipe -> ShowS)
-> (UnexpectedEndOfPipe -> String)
-> ([UnexpectedEndOfPipe] -> ShowS)
-> Show UnexpectedEndOfPipe
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [UnexpectedEndOfPipe] -> ShowS
$cshowList :: [UnexpectedEndOfPipe] -> ShowS
show :: UnexpectedEndOfPipe -> String
$cshow :: UnexpectedEndOfPipe -> String
showsPrec :: Int -> UnexpectedEndOfPipe -> ShowS
$cshowsPrec :: Int -> UnexpectedEndOfPipe -> ShowS
Show)
instance Exception UnexpectedEndOfPipe
newtype TimeoutException = TimeoutException
{
TimeoutException -> Text
id :: ContainerId
}
deriving (TimeoutException -> TimeoutException -> Bool
(TimeoutException -> TimeoutException -> Bool)
-> (TimeoutException -> TimeoutException -> Bool)
-> Eq TimeoutException
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TimeoutException -> TimeoutException -> Bool
$c/= :: TimeoutException -> TimeoutException -> Bool
== :: TimeoutException -> TimeoutException -> Bool
$c== :: TimeoutException -> TimeoutException -> Bool
Eq, Int -> TimeoutException -> ShowS
[TimeoutException] -> ShowS
TimeoutException -> String
(Int -> TimeoutException -> ShowS)
-> (TimeoutException -> String)
-> ([TimeoutException] -> ShowS)
-> Show TimeoutException
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TimeoutException] -> ShowS
$cshowList :: [TimeoutException] -> ShowS
show :: TimeoutException -> String
$cshow :: TimeoutException -> String
showsPrec :: Int -> TimeoutException -> ShowS
$cshowsPrec :: Int -> TimeoutException -> ShowS
Show)
instance Exception TimeoutException
waitUntilTimeout :: Int -> WaitUntilReady -> WaitUntilReady
waitUntilTimeout :: Int -> WaitUntilReady -> WaitUntilReady
waitUntilTimeout Int
seconds WaitUntilReady
wait = (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
WaitUntilReady ((Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady)
-> (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
forall a b. (a -> b) -> a -> b
$ \Config
config Container
container ->
case WaitUntilReady -> Config -> Container -> (Maybe Int, ResIO ())
checkContainerReady WaitUntilReady
wait Config
config Container
container of
(Maybe Int
Nothing, ResIO ()
check) ->
(Int -> Maybe Int
forall a. a -> Maybe a
Just Int
seconds, ResIO ()
check)
(Just Int
innerTimeout, ResIO ()
check)
| Int
innerTimeout Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
seconds ->
(Int -> Maybe Int
forall a. a -> Maybe a
Just Int
seconds, ResIO ()
check)
| Bool
otherwise ->
(Int -> Maybe Int
forall a. a -> Maybe a
Just Int
innerTimeout, ResIO ()
check)
waitUntilMappedPortReachable
:: Int
-> WaitUntilReady
waitUntilMappedPortReachable :: Int -> WaitUntilReady
waitUntilMappedPortReachable Int
port = (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
WaitUntilReady ((Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady)
-> (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
forall a b. (a -> b) -> a -> b
$ \Config
config Container
container ->
(HasCallStack => (Maybe Int, ResIO ())) -> (Maybe Int, ResIO ())
forall a. HasCallStack => (HasCallStack => a) -> a
withFrozenCallStack ((HasCallStack => (Maybe Int, ResIO ())) -> (Maybe Int, ResIO ()))
-> (HasCallStack => (Maybe Int, ResIO ())) -> (Maybe Int, ResIO ())
forall a b. (a -> b) -> a -> b
$
let
Config { Tracer
configTracer :: Tracer
$sel:configTracer:Config :: Config -> Tracer
configTracer } = Config
config
hostIp :: String
hostIp :: String
hostIp = String
"0.0.0.0"
hostPort :: Int
hostPort :: Int
hostPort = Container -> Int -> Int
containerPort Container
container Int
port
resolve :: IO AddrInfo
resolve = do
let hints :: AddrInfo
hints = AddrInfo
Socket.defaultHints { addrSocketType :: SocketType
Socket.addrSocketType = SocketType
Socket.Stream }
[AddrInfo] -> AddrInfo
forall a. [a] -> a
head ([AddrInfo] -> AddrInfo) -> IO [AddrInfo] -> IO AddrInfo
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe AddrInfo -> Maybe String -> Maybe String -> IO [AddrInfo]
Socket.getAddrInfo (AddrInfo -> Maybe AddrInfo
forall a. a -> Maybe a
Just AddrInfo
hints) (String -> Maybe String
forall a. a -> Maybe a
Just String
hostIp) (String -> Maybe String
forall a. a -> Maybe a
Just (Int -> String
forall a. Show a => a -> String
show Int
hostPort))
open :: AddrInfo -> IO Socket
open AddrInfo
addr = do
Socket
socket <- Family -> SocketType -> ProtocolNumber -> IO Socket
Socket.socket
(AddrInfo -> Family
Socket.addrFamily AddrInfo
addr)
(AddrInfo -> SocketType
Socket.addrSocketType AddrInfo
addr)
(AddrInfo -> ProtocolNumber
Socket.addrProtocol AddrInfo
addr)
Socket -> SockAddr -> IO ()
Socket.connect
Socket
socket
(AddrInfo -> SockAddr
Socket.addrAddress AddrInfo
addr)
Socket -> IO Socket
forall (f :: * -> *) a. Applicative f => a -> f a
pure Socket
socket
retry :: IO ()
retry = do
Either IOException Socket
result <- IO Socket -> IO (Either IOException Socket)
forall (m :: * -> *) e a.
(MonadCatch m, Exception e) =>
m a -> m (Either e a)
try (IO AddrInfo
resolve IO AddrInfo -> (AddrInfo -> IO Socket) -> IO Socket
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= AddrInfo -> IO Socket
open)
case Either IOException Socket
result of
Right Socket
socket -> do
Tracer -> Trace -> IO ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
configTracer (Text -> Int -> Maybe IOException -> Trace
TraceOpenSocket (String -> Text
pack String
hostIp) Int
hostPort Maybe IOException
forall a. Maybe a
Nothing)
Socket -> IO ()
Socket.close Socket
socket
() -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Left (IOException
exception :: IOException) -> do
Tracer -> Trace -> IO ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
configTracer
(Text -> Int -> Maybe IOException -> Trace
TraceOpenSocket (String -> Text
pack String
hostIp) Int
hostPort (IOException -> Maybe IOException
forall a. a -> Maybe a
Just IOException
exception))
Int -> IO ()
threadDelay Int
500000
IO ()
retry
in
(Maybe Int
forall a. Maybe a
Nothing, IO () -> ResIO ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO ()
retry)
waitWithLogs :: (Container -> Handle -> Handle -> IO ()) -> WaitUntilReady
waitWithLogs :: (Container -> Handle -> Handle -> IO ()) -> WaitUntilReady
waitWithLogs Container -> Handle -> Handle -> IO ()
waiter = (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
WaitUntilReady ((Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady)
-> (Config -> Container -> (Maybe Int, ResIO ())) -> WaitUntilReady
forall a b. (a -> b) -> a -> b
$ \Config
config Container
container ->
let
check :: ResIO ()
check = (ReaderT Config (ResourceT IO) () -> Config -> ResIO ())
-> Config -> ReaderT Config (ResourceT IO) () -> ResIO ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReaderT Config (ResourceT IO) () -> Config -> ResIO ()
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT Config
config (ReaderT Config (ResourceT IO) () -> ResIO ())
-> ReaderT Config (ResourceT IO) () -> ResIO ()
forall a b. (a -> b) -> a -> b
$ Container
-> (Handle -> Handle -> ReaderT Config (ResourceT IO) ())
-> ReaderT Config (ResourceT IO) ()
forall (m :: * -> *) a.
MonadDocker m =>
Container -> (Handle -> Handle -> m a) -> m a
withLogs Container
container ((Handle -> Handle -> ReaderT Config (ResourceT IO) ())
-> ReaderT Config (ResourceT IO) ())
-> (Handle -> Handle -> ReaderT Config (ResourceT IO) ())
-> ReaderT Config (ResourceT IO) ()
forall a b. (a -> b) -> a -> b
$ \Handle
stdout Handle
stderr ->
IO () -> ReaderT Config (ResourceT IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ReaderT Config (ResourceT IO) ())
-> IO () -> ReaderT Config (ResourceT IO) ()
forall a b. (a -> b) -> a -> b
$ Container -> Handle -> Handle -> IO ()
waiter Container
container Handle
stdout Handle
stderr
in
(Maybe Int
forall a. Maybe a
Nothing, ResIO ()
check)
data Pipe
= Stdout
| Stderr
deriving (Pipe -> Pipe -> Bool
(Pipe -> Pipe -> Bool) -> (Pipe -> Pipe -> Bool) -> Eq Pipe
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Pipe -> Pipe -> Bool
$c/= :: Pipe -> Pipe -> Bool
== :: Pipe -> Pipe -> Bool
$c== :: Pipe -> Pipe -> Bool
Eq, Eq Pipe
Eq Pipe
-> (Pipe -> Pipe -> Ordering)
-> (Pipe -> Pipe -> Bool)
-> (Pipe -> Pipe -> Bool)
-> (Pipe -> Pipe -> Bool)
-> (Pipe -> Pipe -> Bool)
-> (Pipe -> Pipe -> Pipe)
-> (Pipe -> Pipe -> Pipe)
-> Ord Pipe
Pipe -> Pipe -> Bool
Pipe -> Pipe -> Ordering
Pipe -> Pipe -> Pipe
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Pipe -> Pipe -> Pipe
$cmin :: Pipe -> Pipe -> Pipe
max :: Pipe -> Pipe -> Pipe
$cmax :: Pipe -> Pipe -> Pipe
>= :: Pipe -> Pipe -> Bool
$c>= :: Pipe -> Pipe -> Bool
> :: Pipe -> Pipe -> Bool
$c> :: Pipe -> Pipe -> Bool
<= :: Pipe -> Pipe -> Bool
$c<= :: Pipe -> Pipe -> Bool
< :: Pipe -> Pipe -> Bool
$c< :: Pipe -> Pipe -> Bool
compare :: Pipe -> Pipe -> Ordering
$ccompare :: Pipe -> Pipe -> Ordering
$cp1Ord :: Eq Pipe
Ord, Int -> Pipe -> ShowS
[Pipe] -> ShowS
Pipe -> String
(Int -> Pipe -> ShowS)
-> (Pipe -> String) -> ([Pipe] -> ShowS) -> Show Pipe
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Pipe] -> ShowS
$cshowList :: [Pipe] -> ShowS
show :: Pipe -> String
$cshow :: Pipe -> String
showsPrec :: Int -> Pipe -> ShowS
$cshowsPrec :: Int -> Pipe -> ShowS
Show)
waitForLogLine :: Pipe -> (LazyText.Text -> Bool) -> WaitUntilReady
waitForLogLine :: Pipe -> (Text -> Bool) -> WaitUntilReady
waitForLogLine Pipe
whereToLook Text -> Bool
matches = (Container -> Handle -> Handle -> IO ()) -> WaitUntilReady
waitWithLogs ((Container -> Handle -> Handle -> IO ()) -> WaitUntilReady)
-> (Container -> Handle -> Handle -> IO ()) -> WaitUntilReady
forall a b. (a -> b) -> a -> b
$ \Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } Handle
stdout Handle
stderr -> do
let
logs :: Handle
logs :: Handle
logs = case Pipe
whereToLook of
Pipe
Stdout -> Handle
stdout
Pipe
Stderr -> Handle
stderr
ByteString
logContent <- Handle -> IO ByteString
LazyByteString.hGetContents Handle
logs
let
logLines :: [LazyText.Text]
logLines :: [Text]
logLines =
(ByteString -> Text) -> [ByteString] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map
(OnDecodeError -> ByteString -> Text
LazyText.decodeUtf8With OnDecodeError
lenientDecode)
(ByteString -> [ByteString]
LazyByteString.lines ByteString
logContent)
case (Text -> Bool) -> [Text] -> Maybe Text
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Text -> Bool
matches [Text]
logLines of
Just Text
_ -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Maybe Text
Nothing -> UnexpectedEndOfPipe -> IO ()
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (UnexpectedEndOfPipe -> IO ()) -> UnexpectedEndOfPipe -> IO ()
forall a b. (a -> b) -> a -> b
$ UnexpectedEndOfPipe :: Text -> UnexpectedEndOfPipe
UnexpectedEndOfPipe { Text
id :: Text
$sel:id:UnexpectedEndOfPipe :: Text
id }
waitUntilReady :: MonadDocker m => Container -> WaitUntilReady -> m ()
waitUntilReady :: Container -> WaitUntilReady -> m ()
waitUntilReady container :: Container
container@Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } WaitUntilReady
waiter = do
config :: Config
config@Config { Maybe Int
configDefaultWaitTimeout :: Maybe Int
$sel:configDefaultWaitTimeout:Config :: Config -> Maybe Int
configDefaultWaitTimeout, Tracer
configTracer :: Tracer
$sel:configTracer:Config :: Config -> Tracer
configTracer } <- m Config
forall r (m :: * -> *). MonadReader r m => m r
ask
let
(Maybe Int
timeoutInSeconds, ResIO ()
check) =
WaitUntilReady -> Config -> Container -> (Maybe Int, ResIO ())
checkContainerReady WaitUntilReady
waiter Config
config Container
container
runAction :: m b -> Maybe Int -> m b
runAction m b
action Maybe Int
timeoutSeconds = do
Tracer -> Trace -> m ()
forall (m :: * -> *). MonadIO m => Tracer -> Trace -> m ()
withTrace Tracer
configTracer (Maybe Int -> Trace
TraceWaitUntilReady Maybe Int
timeoutSeconds)
m b
action
withTimeout :: m () -> m ()
withTimeout m ()
action = case Maybe Int
timeoutInSeconds Maybe Int -> Maybe Int -> Maybe Int
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Maybe Int
configDefaultWaitTimeout of
Maybe Int
Nothing ->
m () -> Maybe Int -> m ()
forall (m :: * -> *) b. MonadIO m => m b -> Maybe Int -> m b
runAction m ()
action Maybe Int
forall a. Maybe a
Nothing
Just Int
seconds ->
((forall a. m a -> IO a) -> IO ()) -> m ()
forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> IO a) -> IO b) -> m b
withRunInIO (((forall a. m a -> IO a) -> IO ()) -> m ())
-> ((forall a. m a -> IO a) -> IO ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \forall a. m a -> IO a
runInIO -> do
Maybe ()
result <- Int -> IO () -> IO (Maybe ())
forall a. Int -> IO a -> IO (Maybe a)
timeout (Int
seconds Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000) (IO () -> IO (Maybe ())) -> IO () -> IO (Maybe ())
forall a b. (a -> b) -> a -> b
$ m () -> IO ()
forall a. m a -> IO a
runInIO (m () -> Maybe Int -> m ()
forall (m :: * -> *) b. MonadIO m => m b -> Maybe Int -> m b
runAction m ()
action (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
seconds))
case Maybe ()
result of
Maybe ()
Nothing ->
TimeoutException -> IO ()
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (TimeoutException -> IO ()) -> TimeoutException -> IO ()
forall a b. (a -> b) -> a -> b
$ TimeoutException :: Text -> TimeoutException
TimeoutException { Text
id :: Text
$sel:id:TimeoutException :: Text
id }
Just ()
_ ->
() -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
ResIO () -> m ()
forall (m :: * -> *) a. MonadResource m => ResourceT IO a -> m a
liftResourceT (ResIO () -> ResIO ()
forall (m :: * -> *). MonadUnliftIO m => m () -> m ()
withTimeout ResIO ()
check)
data Image = Image
{
Image -> Text
tag :: ImageTag
}
deriving (Image -> Image -> Bool
(Image -> Image -> Bool) -> (Image -> Image -> Bool) -> Eq Image
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Image -> Image -> Bool
$c/= :: Image -> Image -> Bool
== :: Image -> Image -> Bool
$c== :: Image -> Image -> Bool
Eq, Int -> Image -> ShowS
[Image] -> ShowS
Image -> String
(Int -> Image -> ShowS)
-> (Image -> String) -> ([Image] -> ShowS) -> Show Image
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Image] -> ShowS
$cshowList :: [Image] -> ShowS
show :: Image -> String
$cshow :: Image -> String
showsPrec :: Int -> Image -> ShowS
$cshowsPrec :: Int -> Image -> ShowS
Show)
imageTag :: Image -> ImageTag
imageTag :: Image -> Text
imageTag Image { Text
tag :: Text
$sel:tag:Image :: Image -> Text
tag } = Text
tag
data Container = Container
{
Container -> Text
id :: ContainerId
, Container -> ReleaseKey
releaseKey :: ReleaseKey
, Container -> Image
image :: Image
, Container -> Config
config :: Config
, Container -> InspectOutput
inspectOutput :: InspectOutput
}
type InspectOutput = Value
containerId :: Container -> ContainerId
containerId :: Container -> Text
containerId Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id } = Text
id
containerImage :: Container -> Image
containerImage :: Container -> Image
containerImage Container { Image
image :: Image
$sel:image:Container :: Container -> Image
image } = Image
image
containerReleaseKey :: Container -> ReleaseKey
containerReleaseKey :: Container -> ReleaseKey
containerReleaseKey Container { ReleaseKey
releaseKey :: ReleaseKey
$sel:releaseKey:Container :: Container -> ReleaseKey
releaseKey } = ReleaseKey
releaseKey
containerIp :: Container -> Text
containerIp :: Container -> Text
containerIp =
Container -> Text
internalContainerIp
internalContainerIp :: Container -> Text
internalContainerIp :: Container -> Text
internalContainerIp Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id, InspectOutput
inspectOutput :: InspectOutput
$sel:inspectOutput:Container :: Container -> InspectOutput
inspectOutput } =
case InspectOutput
inspectOutput
InspectOutput
-> Optic' An_AffineTraversal '[] InspectOutput Text -> Maybe Text
forall k s (is :: IxList) a.
Is k An_AffineFold =>
s -> Optic' k is s a -> Maybe a
^? Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
"NetworkSettings"
AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
"IPAddress"
AffineTraversal' InspectOutput InspectOutput
-> Optic A_Prism '[] InspectOutput InspectOutput Text Text
-> Optic' An_AffineTraversal '[] InspectOutput Text
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Optic A_Prism '[] InspectOutput InspectOutput Text Text
forall t. AsPrimitive t => Prism' t Text
Optics._String of
Maybe Text
Nothing ->
DockerException -> Text
forall a e. Exception e => e -> a
throw (DockerException -> Text) -> DockerException -> Text
forall a b. (a -> b) -> a -> b
$ InspectOutputUnexpected :: Text -> DockerException
InspectOutputUnexpected { Text
id :: Text
$sel:id:DockerException :: Text
id }
Just Text
address ->
Text
address
containerPort :: Container -> Int -> Int
containerPort :: Container -> Int -> Int
containerPort Container { Text
id :: Text
$sel:id:Container :: Container -> Text
id, InspectOutput
inspectOutput :: InspectOutput
$sel:inspectOutput:Container :: Container -> InspectOutput
inspectOutput } Int
port =
let
textPort :: Text
textPort :: Text
textPort = String -> Text
pack (Int -> String
forall a. Show a => a -> String
show Int
port) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/tcp"
in
case InspectOutput
inspectOutput
InspectOutput
-> Optic' An_AffineFold '[] InspectOutput Text -> Maybe Text
forall k s (is :: IxList) a.
Is k An_AffineFold =>
s -> Optic' k is s a -> Maybe a
^? Optic' A_Traversal '[Int] InspectOutput Text
-> Optic' An_AffineFold '[] InspectOutput Text
forall k (is :: IxList) s a.
Is k A_Fold =>
Optic' k is s a -> AffineFold s a
pre (Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
"NetworkSettings"
AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
"Ports"
AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
textPort
AffineTraversal' InspectOutput InspectOutput
-> Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
-> Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
forall t. AsValue t => IxTraversal' Int t InspectOutput
Optics.values
Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
-> AffineTraversal' InspectOutput InspectOutput
-> Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Text -> AffineTraversal' InspectOutput InspectOutput
forall t. AsValue t => Text -> AffineTraversal' t InspectOutput
Optics.key Text
"HostPort"
Optic
A_Traversal
'[Int]
InspectOutput
InspectOutput
InspectOutput
InspectOutput
-> Optic A_Prism '[] InspectOutput InspectOutput Text Text
-> Optic' A_Traversal '[Int] InspectOutput Text
forall k m l (ks :: IxList) (is :: IxList) (js :: IxList) s t u v a
b.
(Is k m, Is l m, m ~ Join k l, ks ~ Append is js) =>
Optic k is s t u v -> Optic l js u v a b -> Optic m ks s t a b
% Optic A_Prism '[] InspectOutput InspectOutput Text Text
forall t. AsPrimitive t => Prism' t Text
Optics._String) of
Maybe Text
Nothing ->
DockerException -> Int
forall a e. Exception e => e -> a
throw (DockerException -> Int) -> DockerException -> Int
forall a b. (a -> b) -> a -> b
$ UnknownPortMapping :: Text -> Text -> DockerException
UnknownPortMapping
{
Text
id :: Text
$sel:id:DockerException :: Text
id
, $sel:port:DockerException :: Text
port = Text
textPort
}
Just Text
hostPort ->
String -> Int
forall a. Read a => String -> a
read (Text -> String
unpack Text
hostPort)
inspect :: MonadDocker m => Container -> m InspectOutput
inspect :: Container -> m InspectOutput
inspect Container { InspectOutput
inspectOutput :: InspectOutput
$sel:inspectOutput:Container :: Container -> InspectOutput
inspectOutput } =
InspectOutput -> m InspectOutput
forall (f :: * -> *) a. Applicative f => a -> f a
pure InspectOutput
inspectOutput
internalInspect :: (MonadThrow m, MonadIO m) => Tracer -> ContainerId -> m InspectOutput
internalInspect :: Tracer -> Text -> m InspectOutput
internalInspect Tracer
tracer Text
id = do
String
stdout <- Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"inspect", Text
id ]
case ByteString -> Maybe [InspectOutput]
forall a. FromJSON a => ByteString -> Maybe a
decode' (String -> ByteString
LazyByteString.pack String
stdout) of
Maybe [InspectOutput]
Nothing ->
DockerException -> m InspectOutput
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (DockerException -> m InspectOutput)
-> DockerException -> m InspectOutput
forall a b. (a -> b) -> a -> b
$ InspectOutputInvalidJSON :: Text -> DockerException
InspectOutputInvalidJSON { Text
id :: Text
$sel:id:DockerException :: Text
id }
Just [] ->
DockerException -> m InspectOutput
forall (m :: * -> *) e a. (MonadThrow m, Exception e) => e -> m a
throwM (DockerException -> m InspectOutput)
-> DockerException -> m InspectOutput
forall a b. (a -> b) -> a -> b
$ InspectUnknownContainerId :: Text -> DockerException
InspectUnknownContainerId { Text
id :: Text
$sel:id:DockerException :: Text
id }
Just [InspectOutput
value] ->
InspectOutput -> m InspectOutput
forall (f :: * -> *) a. Applicative f => a -> f a
pure InspectOutput
value
Just [InspectOutput]
_ ->
String -> m InspectOutput
forall a. HasCallStack => String -> a
Prelude.error String
"Internal: Multiple results where I expected single result"
askTracer :: MonadReader Config m => m Tracer
askTracer :: m Tracer
askTracer = do
Config { Tracer
configTracer :: Tracer
$sel:configTracer:Config :: Config -> Tracer
configTracer } <- m Config
forall r (m :: * -> *). MonadReader r m => m r
ask
Tracer -> m Tracer
forall (f :: * -> *) a. Applicative f => a -> f a
pure Tracer
configTracer
{-# INLINE askTracer #-}
dockerHostOs :: MonadDocker m => m Text
dockerHostOs :: m Text
dockerHostOs = do
Tracer
tracer <- m Tracer
forall (m :: * -> *). MonadReader Config m => m Tracer
askTracer
Text -> Text
strip (Text -> Text) -> (String -> Text) -> String -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
pack (String -> Text) -> m String -> m Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Tracer -> [Text] -> m String
forall (m :: * -> *). MonadIO m => Tracer -> [Text] -> m String
docker Tracer
tracer [ Text
"version", Text
"--format", Text
"{{.Server.Os}}" ]
isDockerOnLinux :: MonadDocker m => m Bool
isDockerOnLinux :: m Bool
isDockerOnLinux =
(Text
"linux" Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
==) (Text -> Bool) -> m Text -> m Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> m Text
forall (m :: * -> *). MonadDocker m => m Text
dockerHostOs