docker 0.2.0.4 → 0.3.0.0
raw patch · 13 files changed
+2177/−513 lines, 13 filesdep +blaze-builderdep +connectiondep +data-default-classdep −HsOpenSSLdep −data-defaultdep −http-client-openssldep ~QuickCheckdep ~aesondep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: blaze-builder, connection, data-default-class, http-client, http-client-tls, mtl, network, scientific, time, tls, transformers, unordered-containers, x509, x509-store, x509-system
Dependencies removed: HsOpenSSL, data-default, http-client-openssl, network-uri, pipes, pipes-bytestring, pipes-http, pipes-text, wreq
Dependency ranges changed: QuickCheck, aeson, base, bytestring, containers, http-types, lens, tasty, tasty-hunit, tasty-quickcheck, text
API changes (from Hackage documentation)
- Network.Docker: _dockerEmptyDeleteQuery :: Endpoint -> DockerClientOpts -> IO (Response ByteString)
- Network.Docker: _dockerEmptyPostQuery :: Endpoint -> DockerClientOpts -> IO (Response ByteString)
- Network.Docker: _dockerGetQuery :: Endpoint -> DockerClientOpts -> IO (Response ByteString)
- Network.Docker: _dockerPostQuery :: ToJSON a => Endpoint -> DockerClientOpts -> a -> IO (Response ByteString)
- Network.Docker: constructRelativeUrl :: String -> String
- Network.Docker: constructUrl :: URL -> ApiVersion -> Endpoint -> URL
- Network.Docker: createContainer :: DockerClientOpts -> CreateContainerOpts -> IO (Maybe Text)
- Network.Docker: decodeResponse :: (Functor f, FromJSON a) => f (Response ByteString) -> f (Maybe a)
- Network.Docker: defaultClientOpts :: DockerClientOpts
- Network.Docker: deleteContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker: deleteContainerWithOpts :: DeleteOpts -> DockerClientOpts -> String -> IO (Status)
- Network.Docker: emptyPost :: String
- Network.Docker: fullUrl :: DockerClientOpts -> Endpoint -> URL
- Network.Docker: getAllDockerContainers :: DockerClientOpts -> IO (Maybe [DockerContainer])
- Network.Docker: getAllDockerImages :: DockerClientOpts -> IO (Maybe [DockerImage])
- Network.Docker: getContainerLogs :: DockerClientOpts -> String -> IO (ByteString)
- Network.Docker: getContainerLogsStream :: DockerClientOpts -> String -> IO ()
- Network.Docker: getDockerContainers :: DockerClientOpts -> IO (Maybe [DockerContainer])
- Network.Docker: getDockerImages :: DockerClientOpts -> IO (Maybe [DockerImage])
- Network.Docker: getDockerVersion :: DockerClientOpts -> IO (Maybe DockerVersion)
- Network.Docker: getOutOfResponse :: AsValue body0 => Text -> Response body0 -> Maybe Text
- Network.Docker: getResponseStatusCode :: Response body -> Status
- Network.Docker: getSSL :: SSLOptions -> String -> IO (Response ByteString)
- Network.Docker: killContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker: mkOpts :: IO SSLContext -> Options
- Network.Docker: pauseContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker: postSSL :: ToJSON a => SSLOptions -> String -> a -> IO (Response ByteString)
- Network.Docker: restartContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker: setupSSLCtx :: SSLOptions -> IO SSLContext
- Network.Docker: startContainer :: DockerClientOpts -> String -> StartContainerOpts -> IO (Status)
- Network.Docker: stopContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker: unpauseContainer :: DockerClientOpts -> String -> IO (Status)
- Network.Docker.Options: dopts :: Options
- Network.Docker.Types: CreateContainerOpts :: String -> String -> Int -> Int -> Bool -> Bool -> Bool -> Maybe Object -> Bool -> Bool -> Bool -> Maybe Object -> [String] -> String -> Maybe Object -> Maybe Object -> String -> Bool -> Maybe Object -> CreateContainerOpts
- Network.Docker.Types: DeleteOpts :: Bool -> Bool -> DeleteOpts
- Network.Docker.Types: DockerClientOpts :: ApiVersion -> URL -> SSL -> DockerClientOpts
- Network.Docker.Types: DockerContainer :: ResourceId -> ResourceId -> String -> Int -> [String] -> String -> Maybe [PortMap] -> DockerContainer
- Network.Docker.Types: DockerImage :: ResourceId -> Int -> Maybe String -> [Tag] -> Int -> Int -> DockerImage
- Network.Docker.Types: DockerVersion :: String -> String -> String -> String -> String -> DockerVersion
- Network.Docker.Types: NoSSL :: SSL
- Network.Docker.Types: PortMap :: IP -> Port -> Port -> PortType -> PortMap
- Network.Docker.Types: ResourceId :: String -> ResourceId
- Network.Docker.Types: RestartAlways :: RestartPolicy
- Network.Docker.Types: RestartNever :: RestartPolicy
- Network.Docker.Types: RestartOnFailure :: Int -> RestartPolicy
- Network.Docker.Types: SSL :: SSLOptions -> SSL
- Network.Docker.Types: SSLOptions :: FilePath -> FilePath -> SSLOptions
- Network.Docker.Types: StartContainerOpts :: [Text] -> [Text] -> [(Text, Text)] -> [((Int, Text), Int)] -> Bool -> Bool -> [Text] -> [Text] -> RestartPolicy -> StartContainerOpts
- Network.Docker.Types: [_Arch] :: DockerVersion -> String
- Network.Docker.Types: [_Binds] :: StartContainerOpts -> [Text]
- Network.Docker.Types: [_Dns] :: StartContainerOpts -> [Text]
- Network.Docker.Types: [_GitCommit] :: DockerVersion -> String
- Network.Docker.Types: [_GoVersion] :: DockerVersion -> String
- Network.Docker.Types: [_KernelVersion] :: DockerVersion -> String
- Network.Docker.Types: [_Links] :: StartContainerOpts -> [Text]
- Network.Docker.Types: [_LxcConf] :: StartContainerOpts -> [(Text, Text)]
- Network.Docker.Types: [_PortBindings] :: StartContainerOpts -> [((Int, Text), Int)]
- Network.Docker.Types: [_Privileged] :: StartContainerOpts -> Bool
- Network.Docker.Types: [_PublishAllPorts] :: StartContainerOpts -> Bool
- Network.Docker.Types: [_RestartPolicy] :: StartContainerOpts -> RestartPolicy
- Network.Docker.Types: [_Version] :: DockerVersion -> String
- Network.Docker.Types: [_VolumesFrom] :: StartContainerOpts -> [Text]
- Network.Docker.Types: [_attachStderr] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_attachStdin] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_attachStdout] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_cmd] :: CreateContainerOpts -> [String]
- Network.Docker.Types: [_command] :: DockerContainer -> String
- Network.Docker.Types: [_containerCreatedAt] :: DockerContainer -> Int
- Network.Docker.Types: [_containerId] :: DockerContainer -> ResourceId
- Network.Docker.Types: [_containerImageId] :: DockerContainer -> ResourceId
- Network.Docker.Types: [_disableNetwork] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_env] :: CreateContainerOpts -> Maybe Object
- Network.Docker.Types: [_exposedPorts] :: CreateContainerOpts -> Maybe Object
- Network.Docker.Types: [_hostname] :: CreateContainerOpts -> String
- Network.Docker.Types: [_id] :: ResourceId -> String
- Network.Docker.Types: [_imageCreatedAt] :: DockerImage -> Int
- Network.Docker.Types: [_imageId] :: DockerImage -> ResourceId
- Network.Docker.Types: [_image] :: CreateContainerOpts -> String
- Network.Docker.Types: [_ip] :: PortMap -> IP
- Network.Docker.Types: [_memorySwap] :: CreateContainerOpts -> Int
- Network.Docker.Types: [_memory] :: CreateContainerOpts -> Int
- Network.Docker.Types: [_names] :: DockerContainer -> [String]
- Network.Docker.Types: [_openStdin] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_parentId] :: DockerImage -> Maybe String
- Network.Docker.Types: [_portSpecs] :: CreateContainerOpts -> Maybe Object
- Network.Docker.Types: [_ports] :: DockerContainer -> Maybe [PortMap]
- Network.Docker.Types: [_privatePort] :: PortMap -> Port
- Network.Docker.Types: [_publicPort] :: PortMap -> Port
- Network.Docker.Types: [_repoTags] :: DockerImage -> [Tag]
- Network.Docker.Types: [_size] :: DockerImage -> Int
- Network.Docker.Types: [_status] :: DockerContainer -> String
- Network.Docker.Types: [_stdinOnce] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_tty] :: CreateContainerOpts -> Bool
- Network.Docker.Types: [_type] :: PortMap -> PortType
- Network.Docker.Types: [_user] :: CreateContainerOpts -> String
- Network.Docker.Types: [_virtualSize] :: DockerImage -> Int
- Network.Docker.Types: [_volumesFrom] :: CreateContainerOpts -> Maybe Object
- Network.Docker.Types: [_volumes] :: CreateContainerOpts -> Maybe Object
- Network.Docker.Types: [_workingDir] :: CreateContainerOpts -> String
- Network.Docker.Types: [apiVersion] :: DockerClientOpts -> ApiVersion
- Network.Docker.Types: [baseUrl] :: DockerClientOpts -> URL
- Network.Docker.Types: [force] :: DeleteOpts -> Bool
- Network.Docker.Types: [optionsCert] :: SSLOptions -> FilePath
- Network.Docker.Types: [optionsKey] :: SSLOptions -> FilePath
- Network.Docker.Types: [removeVolumes] :: DeleteOpts -> Bool
- Network.Docker.Types: [ssl] :: DockerClientOpts -> SSL
- Network.Docker.Types: attachStderr :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: attachStdin :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: attachStdout :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: class HasResourceId c_agZ8 where id = (.) resourceId id
- Network.Docker.Types: cmd :: Lens' CreateContainerOpts [String]
- Network.Docker.Types: command :: Lens' DockerContainer String
- Network.Docker.Types: containerCreatedAt :: Lens' DockerContainer Int
- Network.Docker.Types: containerId :: Lens' DockerContainer ResourceId
- Network.Docker.Types: containerImageId :: Lens' DockerContainer ResourceId
- Network.Docker.Types: data CreateContainerOpts
- Network.Docker.Types: data DeleteOpts
- Network.Docker.Types: data DockerClientOpts
- Network.Docker.Types: data DockerContainer
- Network.Docker.Types: data DockerImage
- Network.Docker.Types: data DockerVersion
- Network.Docker.Types: data PortMap
- Network.Docker.Types: data ResourceId
- Network.Docker.Types: data RestartPolicy
- Network.Docker.Types: data SSL
- Network.Docker.Types: data SSLOptions
- Network.Docker.Types: data StartContainerOpts
- Network.Docker.Types: defaultCreateOpts :: CreateContainerOpts
- Network.Docker.Types: defaultDeleteOpts :: DeleteOpts
- Network.Docker.Types: defaultStartOpts :: StartContainerOpts
- Network.Docker.Types: disableNetwork :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: env :: Lens' CreateContainerOpts (Maybe Object)
- Network.Docker.Types: exposedPorts :: Lens' CreateContainerOpts (Maybe Object)
- Network.Docker.Types: hostname :: Lens' CreateContainerOpts String
- Network.Docker.Types: id :: HasResourceId c_agZ8 => Lens' c_agZ8 String
- Network.Docker.Types: image :: Lens' CreateContainerOpts String
- Network.Docker.Types: imageCreatedAt :: Lens' DockerImage Int
- Network.Docker.Types: imageId :: Lens' DockerImage ResourceId
- Network.Docker.Types: instance Data.Aeson.Types.Class.FromJSON Network.Docker.Types.DockerContainer
- Network.Docker.Types: instance Data.Aeson.Types.Class.FromJSON Network.Docker.Types.DockerImage
- Network.Docker.Types: instance Data.Aeson.Types.Class.FromJSON Network.Docker.Types.DockerVersion
- Network.Docker.Types: instance Data.Aeson.Types.Class.FromJSON Network.Docker.Types.PortMap
- Network.Docker.Types: instance Data.Aeson.Types.Class.ToJSON Network.Docker.Types.CreateContainerOpts
- Network.Docker.Types: instance Data.Aeson.Types.Class.ToJSON Network.Docker.Types.DockerVersion
- Network.Docker.Types: instance Data.Aeson.Types.Class.ToJSON Network.Docker.Types.RestartPolicy
- Network.Docker.Types: instance Data.Aeson.Types.Class.ToJSON Network.Docker.Types.StartContainerOpts
- Network.Docker.Types: instance GHC.Classes.Eq Network.Docker.Types.DockerContainer
- Network.Docker.Types: instance GHC.Classes.Eq Network.Docker.Types.DockerImage
- Network.Docker.Types: instance GHC.Classes.Eq Network.Docker.Types.DockerVersion
- Network.Docker.Types: instance GHC.Classes.Eq Network.Docker.Types.PortMap
- Network.Docker.Types: instance GHC.Classes.Eq Network.Docker.Types.ResourceId
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.CreateContainerOpts
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.DockerClientOpts
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.DockerContainer
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.DockerImage
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.DockerVersion
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.PortMap
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.ResourceId
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.RestartPolicy
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.SSL
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.SSLOptions
- Network.Docker.Types: instance GHC.Show.Show Network.Docker.Types.StartContainerOpts
- Network.Docker.Types: instance Network.Docker.Types.HasResourceId Network.Docker.Types.DockerContainer
- Network.Docker.Types: instance Network.Docker.Types.HasResourceId Network.Docker.Types.DockerImage
- Network.Docker.Types: instance Network.Docker.Types.HasResourceId Network.Docker.Types.ResourceId
- Network.Docker.Types: memory :: Lens' CreateContainerOpts Int
- Network.Docker.Types: memorySwap :: Lens' CreateContainerOpts Int
- Network.Docker.Types: names :: Lens' DockerContainer [String]
- Network.Docker.Types: openStdin :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: parentId :: Lens' DockerImage (Maybe String)
- Network.Docker.Types: portSpecs :: Lens' CreateContainerOpts (Maybe Object)
- Network.Docker.Types: ports :: Lens' DockerContainer (Maybe [PortMap])
- Network.Docker.Types: repoTags :: Lens' DockerImage [Tag]
- Network.Docker.Types: resourceId :: HasResourceId c_agZ8 => Lens' c_agZ8 ResourceId
- Network.Docker.Types: size :: Lens' DockerImage Int
- Network.Docker.Types: status :: Lens' DockerContainer String
- Network.Docker.Types: stdinOnce :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: tty :: Lens' CreateContainerOpts Bool
- Network.Docker.Types: type ApiVersion = String
- Network.Docker.Types: type Endpoint = String
- Network.Docker.Types: type IP = String
- Network.Docker.Types: type Port = Int
- Network.Docker.Types: type PortType = String
- Network.Docker.Types: type Tag = String
- Network.Docker.Types: type URL = String
- Network.Docker.Types: user :: Lens' CreateContainerOpts String
- Network.Docker.Types: virtualSize :: Lens' DockerImage Int
- Network.Docker.Types: volumes :: Lens' CreateContainerOpts (Maybe Object)
- Network.Docker.Types: volumesFrom :: Lens' CreateContainerOpts (Maybe Object)
- Network.Docker.Types: workingDir :: Lens' CreateContainerOpts String
- Network.Docker.Utils: strip_underscore :: String -> String
+ Docker.Client.Api: createContainer :: forall m. Monad m => CreateOpts -> Maybe ContainerName -> DockerT m (Either DockerError ContainerID)
+ Docker.Client.Api: deleteContainer :: forall m. Monad m => DeleteOpts -> ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: getContainerLogs :: forall m. Monad m => LogOpts -> ContainerID -> DockerT m (Either DockerError ByteString)
+ Docker.Client.Api: getDockerVersion :: forall m. Monad m => DockerT m (Either DockerError DockerVersion)
+ Docker.Client.Api: inspectContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ContainerDetails)
+ Docker.Client.Api: killContainer :: forall m. Monad m => Signal -> ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: listContainers :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Container])
+ Docker.Client.Api: listImages :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Image])
+ Docker.Client.Api: pauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: restartContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: startContainer :: forall m. Monad m => StartOpts -> ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: stopContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Api: unpauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())
+ Docker.Client.Http: DockerClientDecodeError :: Text -> DockerError
+ Docker.Client.Http: DockerConnectionError :: DockerError
+ Docker.Client.Http: DockerInvalidRequest :: Endpoint -> DockerError
+ Docker.Client.Http: DockerInvalidStatusCode :: Status -> DockerError
+ Docker.Client.Http: DockerT :: (Monad m => ReaderT (DockerClientOpts, HttpHandler m) m a) -> DockerT m a
+ Docker.Client.Http: GenericDockerError :: Text -> DockerError
+ Docker.Client.Http: [unDockerT] :: DockerT m a -> Monad m => ReaderT (DockerClientOpts, HttpHandler m) m a
+ Docker.Client.Http: clientParamsSetCA :: ClientParams -> FilePath -> IO ClientParams
+ Docker.Client.Http: clientParamsWithClientAuthentication :: HostName -> PortNumber -> FilePath -> FilePath -> IO (Either String ClientParams)
+ Docker.Client.Http: data DockerError
+ Docker.Client.Http: defaultHttpHandler :: MonadIO m => HttpHandler m
+ Docker.Client.Http: httpHandler :: MonadIO m => Manager -> HttpHandler m
+ Docker.Client.Http: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Docker.Client.Http.DockerT m)
+ Docker.Client.Http: instance Control.Monad.Trans.Class.MonadTrans Docker.Client.Http.DockerT
+ Docker.Client.Http: instance GHC.Base.Applicative m => GHC.Base.Applicative (Docker.Client.Http.DockerT m)
+ Docker.Client.Http: instance GHC.Base.Functor m => GHC.Base.Functor (Docker.Client.Http.DockerT m)
+ Docker.Client.Http: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader (Docker.Client.Types.DockerClientOpts, Docker.Client.Http.HttpHandler m) (Docker.Client.Http.DockerT m)
+ Docker.Client.Http: instance GHC.Base.Monad m => GHC.Base.Monad (Docker.Client.Http.DockerT m)
+ Docker.Client.Http: instance GHC.Classes.Eq Docker.Client.Http.DockerError
+ Docker.Client.Http: instance GHC.Show.Show Docker.Client.Http.DockerError
+ Docker.Client.Http: mkHttpRequest :: HttpVerb -> Endpoint -> DockerClientOpts -> Maybe Request
+ Docker.Client.Http: newtype DockerT m a
+ Docker.Client.Http: runDockerT :: Monad m => (DockerClientOpts, HttpHandler m) -> DockerT m a -> m a
+ Docker.Client.Http: statusCodeToError :: Endpoint -> Status -> Maybe DockerError
+ Docker.Client.Http: type HttpHandler m = Request -> m (Either DockerError Response)
+ Docker.Client.Http: type HttpVerb = StdMethod
+ Docker.Client.Http: type Request = Request
+ Docker.Client.Http: type Response = Response ByteString
+ Docker.Client.Http: unixHttpHandler :: MonadIO m => FilePath -> HttpHandler m
+ Docker.Client.Internal: encodeQ :: String -> ByteString
+ Docker.Client.Internal: encodeURL :: [Text] -> Text
+ Docker.Client.Internal: encodeURLWithQuery :: [Text] -> Query -> Text
+ Docker.Client.Internal: getEndpoint :: Endpoint -> Text
+ Docker.Client.Internal: getEndpointRequestBody :: Endpoint -> Maybe ByteString
+ Docker.Client.Types: All :: TailLogOpt
+ Docker.Client.Types: AwsLogs :: LogDriverType
+ Docker.Client.Types: Bind :: Text -> Text -> Maybe VolumePermission -> Bind
+ Docker.Client.Types: Bridge :: NetworkMode
+ Docker.Client.Types: Container :: ContainerID -> [Text] -> Text -> ImageID -> Text -> Int -> Status -> [ContainerPortInfo] -> [Label] -> [Network] -> [Mount] -> Container
+ Docker.Client.Types: ContainerConfig :: Maybe Text -> Maybe Text -> Maybe Text -> Bool -> Bool -> Bool -> [ExposedPort] -> Bool -> Bool -> Bool -> [EnvVar] -> [Text] -> Text -> [Volume] -> Maybe FilePath -> Maybe Text -> Maybe Bool -> Maybe Text -> [Label] -> Signal -> ContainerConfig
+ Docker.Client.Types: ContainerDetails :: Text -> [Text] -> ContainerConfig -> UTCTime -> Text -> HostConfig -> FilePath -> FilePath -> FilePath -> ContainerID -> ImageID -> Text -> Text -> NetworkSettings -> FilePath -> Text -> FilePath -> Int -> ContainerState -> [Mount] -> ContainerDetails
+ Docker.Client.Types: ContainerLogsEndpoint :: LogOpts -> Bool -> ContainerID -> Endpoint
+ Docker.Client.Types: ContainerPortInfo :: Maybe Text -> Port -> Maybe Port -> Maybe PortType -> ContainerPortInfo
+ Docker.Client.Types: ContainerResources :: Maybe Integer -> Maybe Integer -> Maybe [DeviceWeight] -> Maybe [DeviceRate] -> Maybe [DeviceRate] -> Maybe [DeviceRate] -> Maybe [DeviceRate] -> Maybe Integer -> Maybe Text -> Maybe Text -> [Device] -> Maybe MemoryConstraint -> Maybe MemoryConstraint -> Maybe MemoryConstraint -> Maybe MemoryConstraint -> Bool -> [Ulimit] -> ContainerResources
+ Docker.Client.Types: ContainerState :: Text -> Int -> Maybe UTCTime -> Bool -> Bool -> Bool -> Int -> Bool -> Bool -> UTCTime -> Status -> ContainerState
+ Docker.Client.Types: CreateContainerEndpoint :: CreateOpts -> (Maybe ContainerName) -> Endpoint
+ Docker.Client.Types: CreateOpts :: ContainerConfig -> HostConfig -> CreateOpts
+ Docker.Client.Types: Created :: Status
+ Docker.Client.Types: Dead :: Status
+ Docker.Client.Types: Default :: Isolation
+ Docker.Client.Types: DefaultDetachKey :: DetachKeys
+ Docker.Client.Types: DefaultTimeout :: Timeout
+ Docker.Client.Types: DeleteContainerEndpoint :: DeleteOpts -> ContainerID -> Endpoint
+ Docker.Client.Types: DeleteOpts :: Bool -> Bool -> DeleteOpts
+ Docker.Client.Types: Device :: FilePath -> FilePath -> Text -> Device
+ Docker.Client.Types: DeviceRate :: FilePath -> Text -> DeviceRate
+ Docker.Client.Types: DeviceWeight :: FilePath -> Text -> DeviceWeight
+ Docker.Client.Types: DockerClientOpts :: ApiVersion -> URL -> DockerClientOpts
+ Docker.Client.Types: DockerImage :: ImageID -> Integer -> Maybe ImageID -> [Tag] -> [Digest] -> Integer -> Integer -> [Label] -> Image
+ Docker.Client.Types: DockerVersion :: Text -> ApiVersion -> Text -> Text -> Text -> Text -> Text -> Text -> DockerVersion
+ Docker.Client.Types: EnvVar :: Name -> Value -> EnvVar
+ Docker.Client.Types: Etwlogs :: LogDriverType
+ Docker.Client.Types: Exited :: Status
+ Docker.Client.Types: ExposedPort :: Port -> PortType -> ExposedPort
+ Docker.Client.Types: Fluentd :: LogDriverType
+ Docker.Client.Types: Gelf :: LogDriverType
+ Docker.Client.Types: Host :: NetworkMode
+ Docker.Client.Types: HostConfig :: [Bind] -> Maybe FilePath -> LogDriverConfig -> NetworkMode -> [PortBinding] -> RestartPolicy -> Maybe Text -> [VolumeFrom] -> [Text] -> [Text] -> [Text] -> [Text] -> [Text] -> [Text] -> Maybe Text -> [Link] -> Maybe Integer -> Bool -> Bool -> Bool -> [Text] -> Maybe Integer -> ContainerResources -> HostConfig
+ Docker.Client.Types: HostPort :: Text -> Port -> HostPort
+ Docker.Client.Types: Hyperv :: Isolation
+ Docker.Client.Types: InspectContainerEndpoint :: ContainerID -> Endpoint
+ Docker.Client.Types: Journald :: LogDriverType
+ Docker.Client.Types: JsonFile :: LogDriverType
+ Docker.Client.Types: KillContainerEndpoint :: Signal -> ContainerID -> Endpoint
+ Docker.Client.Types: Label :: Name -> Value -> Label
+ Docker.Client.Types: Link :: Text -> (Maybe Text) -> Link
+ Docker.Client.Types: ListContainersEndpoint :: ListOpts -> Endpoint
+ Docker.Client.Types: ListImagesEndpoint :: ListOpts -> Endpoint
+ Docker.Client.Types: ListOpts :: Bool -> ListOpts
+ Docker.Client.Types: LogDriverConfig :: LogDriverType -> [LogDriverOption] -> LogDriverConfig
+ Docker.Client.Types: LogDriverOption :: Name -> Value -> LogDriverOption
+ Docker.Client.Types: LogOpts :: Bool -> Bool -> Maybe Timestamp -> Bool -> TailLogOpt -> LogOpts
+ Docker.Client.Types: LoggingDisabled :: LogDriverType
+ Docker.Client.Types: Mount :: Text -> FilePath -> FilePath -> Text -> Maybe VolumePermission -> Bool -> Text -> Mount
+ Docker.Client.Types: Network :: NetworkMode -> NetworkOptions -> Network
+ Docker.Client.Types: NetworkDisabled :: NetworkMode
+ Docker.Client.Types: NetworkOptions :: Text -> Text -> Text -> Text -> Int -> Maybe Text -> Maybe Text -> Maybe Int -> Text -> NetworkOptions
+ Docker.Client.Types: NetworkSettings :: Text -> Text -> Bool -> Text -> Int -> [PortBinding] -> Text -> Maybe [Text] -> Maybe [Text] -> Text -> Text -> Text -> Int -> Text -> Int -> Text -> Text -> [Network] -> NetworkSettings
+ Docker.Client.Types: PauseContainerEndpoint :: ContainerID -> Endpoint
+ Docker.Client.Types: Paused :: Status
+ Docker.Client.Types: PortBinding :: Port -> PortType -> [HostPort] -> PortBinding
+ Docker.Client.Types: Process :: Isolation
+ Docker.Client.Types: ReadOnly :: VolumePermission
+ Docker.Client.Types: ReadWrite :: VolumePermission
+ Docker.Client.Types: RestartAlways :: RestartPolicy
+ Docker.Client.Types: RestartContainerEndpoint :: Timeout -> ContainerID -> Endpoint
+ Docker.Client.Types: RestartOff :: RestartPolicy
+ Docker.Client.Types: RestartOnFailure :: RetryCount -> RestartPolicy
+ Docker.Client.Types: RestartUnlessStopped :: RestartPolicy
+ Docker.Client.Types: Restarting :: Status
+ Docker.Client.Types: Running :: Status
+ Docker.Client.Types: SIG :: Integer -> Signal
+ Docker.Client.Types: SIGHUP :: Signal
+ Docker.Client.Types: SIGINT :: Signal
+ Docker.Client.Types: SIGKILL :: Signal
+ Docker.Client.Types: SIGQUIT :: Signal
+ Docker.Client.Types: SIGSTOP :: Signal
+ Docker.Client.Types: SIGTERM :: Signal
+ Docker.Client.Types: SIGUSR1 :: Signal
+ Docker.Client.Types: Splunk :: LogDriverType
+ Docker.Client.Types: StartContainerEndpoint :: StartOpts -> ContainerID -> Endpoint
+ Docker.Client.Types: StartOpts :: DetachKeys -> StartOpts
+ Docker.Client.Types: StopContainerEndpoint :: Timeout -> ContainerID -> Endpoint
+ Docker.Client.Types: Syslog :: LogDriverType
+ Docker.Client.Types: TCP :: PortType
+ Docker.Client.Types: Tail :: Integer -> TailLogOpt
+ Docker.Client.Types: Timeout :: Integer -> Timeout
+ Docker.Client.Types: UDP :: PortType
+ Docker.Client.Types: UTSMode :: Text -> UTSMode
+ Docker.Client.Types: Ulimit :: Text -> Integer -> Integer -> Ulimit
+ Docker.Client.Types: UnpauseContainerEndpoint :: ContainerID -> Endpoint
+ Docker.Client.Types: VersionEndpoint :: Endpoint
+ Docker.Client.Types: Volume :: FilePath -> Volume
+ Docker.Client.Types: VolumeFrom :: ContainerName -> (Maybe VolumePermission) -> VolumeFrom
+ Docker.Client.Types: WithCtrl :: Char -> DetachKeys
+ Docker.Client.Types: WithoutCtrl :: Char -> DetachKeys
+ Docker.Client.Types: [all] :: ListOpts -> Bool
+ Docker.Client.Types: [apiVer] :: DockerClientOpts -> ApiVersion
+ Docker.Client.Types: [apiVersion] :: DockerVersion -> ApiVersion
+ Docker.Client.Types: [appArmorProfile] :: ContainerDetails -> Text
+ Docker.Client.Types: [arch] :: DockerVersion -> Text
+ Docker.Client.Types: [args] :: ContainerDetails -> [Text]
+ Docker.Client.Types: [attachStderr] :: ContainerConfig -> Bool
+ Docker.Client.Types: [attachStdin] :: ContainerConfig -> Bool
+ Docker.Client.Types: [attachStdout] :: ContainerConfig -> Bool
+ Docker.Client.Types: [baseUrl] :: DockerClientOpts -> URL
+ Docker.Client.Types: [binds] :: HostConfig -> [Bind]
+ Docker.Client.Types: [blkioDeviceReadBps] :: ContainerResources -> Maybe [DeviceRate]
+ Docker.Client.Types: [blkioDeviceReadIOps] :: ContainerResources -> Maybe [DeviceRate]
+ Docker.Client.Types: [blkioDeviceWriteBps] :: ContainerResources -> Maybe [DeviceRate]
+ Docker.Client.Types: [blkioDeviceWriteIOps] :: ContainerResources -> Maybe [DeviceRate]
+ Docker.Client.Types: [blkioWeightDevice] :: ContainerResources -> Maybe [DeviceWeight]
+ Docker.Client.Types: [blkioWeight] :: ContainerResources -> Maybe Integer
+ Docker.Client.Types: [buildTime] :: DockerVersion -> Text
+ Docker.Client.Types: [capAdd] :: HostConfig -> [Text]
+ Docker.Client.Types: [capDrop] :: HostConfig -> [Text]
+ Docker.Client.Types: [cgroupPermissions] :: Device -> Text
+ Docker.Client.Types: [cmd] :: ContainerConfig -> [Text]
+ Docker.Client.Types: [containerCommand] :: Container -> Text
+ Docker.Client.Types: [containerConfig] :: CreateOpts -> ContainerConfig
+ Docker.Client.Types: [containerCreatedAt] :: Container -> Int
+ Docker.Client.Types: [containerDest] :: Bind -> Text
+ Docker.Client.Types: [containerDetailsConfig] :: ContainerDetails -> ContainerConfig
+ Docker.Client.Types: [containerDetailsHostConfig] :: ContainerDetails -> HostConfig
+ Docker.Client.Types: [containerDetailsId] :: ContainerDetails -> ContainerID
+ Docker.Client.Types: [containerDetailsImage] :: ContainerDetails -> ImageID
+ Docker.Client.Types: [containerError] :: ContainerState -> Text
+ Docker.Client.Types: [containerIDFile] :: HostConfig -> Maybe FilePath
+ Docker.Client.Types: [containerId] :: Container -> ContainerID
+ Docker.Client.Types: [containerImageId] :: Container -> ImageID
+ Docker.Client.Types: [containerImageName] :: Container -> Text
+ Docker.Client.Types: [containerLabels] :: Container -> [Label]
+ Docker.Client.Types: [containerMounts] :: Container -> [Mount]
+ Docker.Client.Types: [containerNames] :: Container -> [Text]
+ Docker.Client.Types: [containerNetworks] :: Container -> [Network]
+ Docker.Client.Types: [containerPort] :: PortBinding -> Port
+ Docker.Client.Types: [containerPorts] :: Container -> [ContainerPortInfo]
+ Docker.Client.Types: [containerStatus] :: Container -> Status
+ Docker.Client.Types: [cpuPeriod] :: ContainerResources -> Maybe Integer
+ Docker.Client.Types: [cpuShares] :: ContainerResources -> Maybe Integer
+ Docker.Client.Types: [cpusetCpus] :: ContainerResources -> Maybe Text
+ Docker.Client.Types: [cpusetMems] :: ContainerResources -> Maybe Text
+ Docker.Client.Types: [created] :: ContainerDetails -> UTCTime
+ Docker.Client.Types: [dead] :: ContainerState -> Bool
+ Docker.Client.Types: [deleteVolumes] :: DeleteOpts -> Bool
+ Docker.Client.Types: [detachKeys] :: StartOpts -> DetachKeys
+ Docker.Client.Types: [deviceRatePath] :: DeviceRate -> FilePath
+ Docker.Client.Types: [deviceRateRate] :: DeviceRate -> Text
+ Docker.Client.Types: [deviceWeightPath] :: DeviceWeight -> FilePath
+ Docker.Client.Types: [deviceWeightWeight] :: DeviceWeight -> Text
+ Docker.Client.Types: [devices] :: ContainerResources -> [Device]
+ Docker.Client.Types: [dnsOptions] :: HostConfig -> [Text]
+ Docker.Client.Types: [dnsSearch] :: HostConfig -> [Text]
+ Docker.Client.Types: [dns] :: HostConfig -> [Text]
+ Docker.Client.Types: [domainname] :: ContainerConfig -> Maybe Text
+ Docker.Client.Types: [driver] :: ContainerDetails -> Text
+ Docker.Client.Types: [entrypoint] :: ContainerConfig -> Maybe Text
+ Docker.Client.Types: [env] :: ContainerConfig -> [EnvVar]
+ Docker.Client.Types: [exitCode] :: ContainerState -> Int
+ Docker.Client.Types: [exposedPorts] :: ContainerConfig -> [ExposedPort]
+ Docker.Client.Types: [extraHosts] :: HostConfig -> [Text]
+ Docker.Client.Types: [finishedAt] :: ContainerState -> Maybe UTCTime
+ Docker.Client.Types: [force] :: DeleteOpts -> Bool
+ Docker.Client.Types: [gitCommit] :: DockerVersion -> Text
+ Docker.Client.Types: [goVersion] :: DockerVersion -> Text
+ Docker.Client.Types: [hostConfig] :: CreateOpts -> HostConfig
+ Docker.Client.Types: [hostIp] :: HostPort -> Text
+ Docker.Client.Types: [hostPorts] :: PortBinding -> [HostPort]
+ Docker.Client.Types: [hostPost] :: HostPort -> Port
+ Docker.Client.Types: [hostSrc] :: Bind -> Text
+ Docker.Client.Types: [hostnamePath] :: ContainerDetails -> FilePath
+ Docker.Client.Types: [hostname] :: ContainerConfig -> Maybe Text
+ Docker.Client.Types: [hostsPath] :: ContainerDetails -> FilePath
+ Docker.Client.Types: [imageCreated] :: Image -> Integer
+ Docker.Client.Types: [imageId] :: Image -> ImageID
+ Docker.Client.Types: [imageLabels] :: Image -> [Label]
+ Docker.Client.Types: [imageParentId] :: Image -> Maybe ImageID
+ Docker.Client.Types: [imageRepoDigests] :: Image -> [Digest]
+ Docker.Client.Types: [imageRepoTags] :: Image -> [Tag]
+ Docker.Client.Types: [imageSize] :: Image -> Integer
+ Docker.Client.Types: [imageVirtualSize] :: Image -> Integer
+ Docker.Client.Types: [image] :: ContainerConfig -> Text
+ Docker.Client.Types: [ipAddressInfo] :: ContainerPortInfo -> Maybe Text
+ Docker.Client.Types: [ipcMode] :: HostConfig -> Maybe Text
+ Docker.Client.Types: [kernelMemory] :: ContainerResources -> Maybe MemoryConstraint
+ Docker.Client.Types: [kernelVersion] :: DockerVersion -> Text
+ Docker.Client.Types: [labels] :: ContainerConfig -> [Label]
+ Docker.Client.Types: [links] :: HostConfig -> [Link]
+ Docker.Client.Types: [logConfig] :: HostConfig -> LogDriverConfig
+ Docker.Client.Types: [logPath] :: ContainerDetails -> FilePath
+ Docker.Client.Types: [macAddress] :: ContainerConfig -> Maybe Text
+ Docker.Client.Types: [memoryReservation] :: ContainerResources -> Maybe MemoryConstraint
+ Docker.Client.Types: [memorySwap] :: ContainerResources -> Maybe MemoryConstraint
+ Docker.Client.Types: [memory] :: ContainerResources -> Maybe MemoryConstraint
+ Docker.Client.Types: [mountDestination] :: Mount -> FilePath
+ Docker.Client.Types: [mountDriver] :: Mount -> Text
+ Docker.Client.Types: [mountLabel] :: ContainerDetails -> Text
+ Docker.Client.Types: [mountMode] :: Mount -> Maybe VolumePermission
+ Docker.Client.Types: [mountName] :: Mount -> Text
+ Docker.Client.Types: [mountPropogation] :: Mount -> Text
+ Docker.Client.Types: [mountRW] :: Mount -> Bool
+ Docker.Client.Types: [mountSource] :: Mount -> FilePath
+ Docker.Client.Types: [mounts] :: ContainerDetails -> [Mount]
+ Docker.Client.Types: [name] :: ContainerDetails -> Text
+ Docker.Client.Types: [networkDisabled] :: ContainerConfig -> Maybe Bool
+ Docker.Client.Types: [networkMode] :: HostConfig -> NetworkMode
+ Docker.Client.Types: [networkOptionsEndpointId] :: NetworkOptions -> Text
+ Docker.Client.Types: [networkOptionsGateway] :: NetworkOptions -> Text
+ Docker.Client.Types: [networkOptionsGlobalIPv6Address] :: NetworkOptions -> Maybe Text
+ Docker.Client.Types: [networkOptionsGlobalIPv6PrefixLen] :: NetworkOptions -> Maybe Int
+ Docker.Client.Types: [networkOptionsId] :: NetworkOptions -> Text
+ Docker.Client.Types: [networkOptionsIpAddress] :: NetworkOptions -> Text
+ Docker.Client.Types: [networkOptionsIpPrefixLen] :: NetworkOptions -> Int
+ Docker.Client.Types: [networkOptionsIpV6Gateway] :: NetworkOptions -> Maybe Text
+ Docker.Client.Types: [networkOptionsMacAddress] :: NetworkOptions -> Text
+ Docker.Client.Types: [networkSettingsBridge] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsEndpointID] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsGateway] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsGlobalIPv6Address] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsGlobalIPv6PrefixLen] :: NetworkSettings -> Int
+ Docker.Client.Types: [networkSettingsHairpinMode] :: NetworkSettings -> Bool
+ Docker.Client.Types: [networkSettingsIpAddress] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsIpPrefixLen] :: NetworkSettings -> Int
+ Docker.Client.Types: [networkSettingsIpv6Gateway] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsLinkLocalIPv6Address] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsLinkLocalIPv6PrefixLen] :: NetworkSettings -> Int
+ Docker.Client.Types: [networkSettingsMacAddress] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsNetworks] :: NetworkSettings -> [Network]
+ Docker.Client.Types: [networkSettingsPorts] :: NetworkSettings -> [PortBinding]
+ Docker.Client.Types: [networkSettingsSandboxId] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsSandboxKey] :: NetworkSettings -> Text
+ Docker.Client.Types: [networkSettingsSecondaryIPAddresses] :: NetworkSettings -> Maybe [Text]
+ Docker.Client.Types: [networkSettingsSecondaryIPv6Addresses] :: NetworkSettings -> Maybe [Text]
+ Docker.Client.Types: [networkSettings] :: ContainerDetails -> NetworkSettings
+ Docker.Client.Types: [oomKillDisable] :: ContainerResources -> Bool
+ Docker.Client.Types: [oomKilled] :: ContainerState -> Bool
+ Docker.Client.Types: [oomScoreAdj] :: HostConfig -> Maybe Integer
+ Docker.Client.Types: [openStdin] :: ContainerConfig -> Bool
+ Docker.Client.Types: [os] :: DockerVersion -> Text
+ Docker.Client.Types: [pathInContainer] :: Device -> FilePath
+ Docker.Client.Types: [pathOnHost] :: Device -> FilePath
+ Docker.Client.Types: [path] :: ContainerDetails -> FilePath
+ Docker.Client.Types: [paused] :: ContainerState -> Bool
+ Docker.Client.Types: [pid] :: ContainerState -> Int
+ Docker.Client.Types: [portBindings] :: HostConfig -> [PortBinding]
+ Docker.Client.Types: [portTypeInfo] :: ContainerPortInfo -> Maybe PortType
+ Docker.Client.Types: [portType] :: PortBinding -> PortType
+ Docker.Client.Types: [privatePortInfo] :: ContainerPortInfo -> Port
+ Docker.Client.Types: [privileged] :: HostConfig -> Bool
+ Docker.Client.Types: [processLabel] :: ContainerDetails -> Text
+ Docker.Client.Types: [publicPortInfo] :: ContainerPortInfo -> Maybe Port
+ Docker.Client.Types: [publishAllPorts] :: HostConfig -> Bool
+ Docker.Client.Types: [readonlyRootfs] :: HostConfig -> Bool
+ Docker.Client.Types: [resolveConfPath] :: ContainerDetails -> FilePath
+ Docker.Client.Types: [resources] :: HostConfig -> ContainerResources
+ Docker.Client.Types: [restartCount] :: ContainerDetails -> Int
+ Docker.Client.Types: [restartPolicy] :: HostConfig -> RestartPolicy
+ Docker.Client.Types: [restarting] :: ContainerState -> Bool
+ Docker.Client.Types: [running] :: ContainerState -> Bool
+ Docker.Client.Types: [securityOpt] :: HostConfig -> [Text]
+ Docker.Client.Types: [shmSize] :: HostConfig -> Maybe Integer
+ Docker.Client.Types: [since] :: LogOpts -> Maybe Timestamp
+ Docker.Client.Types: [startedAt] :: ContainerState -> UTCTime
+ Docker.Client.Types: [state] :: ContainerDetails -> ContainerState
+ Docker.Client.Types: [status] :: ContainerState -> Status
+ Docker.Client.Types: [stderr] :: LogOpts -> Bool
+ Docker.Client.Types: [stdinOnce] :: ContainerConfig -> Bool
+ Docker.Client.Types: [stdout] :: LogOpts -> Bool
+ Docker.Client.Types: [stopSignal] :: ContainerConfig -> Signal
+ Docker.Client.Types: [tail] :: LogOpts -> TailLogOpt
+ Docker.Client.Types: [timestamps] :: LogOpts -> Bool
+ Docker.Client.Types: [tty] :: ContainerConfig -> Bool
+ Docker.Client.Types: [ulimitHard] :: Ulimit -> Integer
+ Docker.Client.Types: [ulimitName] :: Ulimit -> Text
+ Docker.Client.Types: [ulimitSoft] :: Ulimit -> Integer
+ Docker.Client.Types: [ulimits] :: ContainerResources -> [Ulimit]
+ Docker.Client.Types: [user] :: ContainerConfig -> Maybe Text
+ Docker.Client.Types: [version] :: DockerVersion -> Text
+ Docker.Client.Types: [volumeDriver] :: HostConfig -> Maybe Text
+ Docker.Client.Types: [volumePermission] :: Bind -> Maybe VolumePermission
+ Docker.Client.Types: [volumesFrom] :: HostConfig -> [VolumeFrom]
+ Docker.Client.Types: [volumes] :: ContainerConfig -> [Volume]
+ Docker.Client.Types: [workingDir] :: ContainerConfig -> Maybe FilePath
+ Docker.Client.Types: addBind :: Bind -> CreateOpts -> CreateOpts
+ Docker.Client.Types: addExposedPort :: ExposedPort -> CreateOpts -> CreateOpts
+ Docker.Client.Types: addLink :: Link -> CreateOpts -> CreateOpts
+ Docker.Client.Types: addPortBinding :: PortBinding -> CreateOpts -> CreateOpts
+ Docker.Client.Types: addVolume :: Volume -> CreateOpts -> CreateOpts
+ Docker.Client.Types: addVolumeFrom :: VolumeFrom -> CreateOpts -> CreateOpts
+ Docker.Client.Types: data Bind
+ Docker.Client.Types: data Container
+ Docker.Client.Types: data ContainerConfig
+ Docker.Client.Types: data ContainerDetails
+ Docker.Client.Types: data ContainerID
+ Docker.Client.Types: data ContainerPortInfo
+ Docker.Client.Types: data ContainerResources
+ Docker.Client.Types: data ContainerState
+ Docker.Client.Types: data CreateOpts
+ Docker.Client.Types: data DeleteOpts
+ Docker.Client.Types: data DetachKeys
+ Docker.Client.Types: data Device
+ Docker.Client.Types: data DeviceRate
+ Docker.Client.Types: data DeviceWeight
+ Docker.Client.Types: data DockerClientOpts
+ Docker.Client.Types: data DockerVersion
+ Docker.Client.Types: data Endpoint
+ Docker.Client.Types: data EnvVar
+ Docker.Client.Types: data ExposedPort
+ Docker.Client.Types: data HostConfig
+ Docker.Client.Types: data HostPort
+ Docker.Client.Types: data Image
+ Docker.Client.Types: data ImageID
+ Docker.Client.Types: data Isolation
+ Docker.Client.Types: data Label
+ Docker.Client.Types: data Link
+ Docker.Client.Types: data ListOpts
+ Docker.Client.Types: data LogDriverConfig
+ Docker.Client.Types: data LogDriverOption
+ Docker.Client.Types: data LogDriverType
+ Docker.Client.Types: data LogOpts
+ Docker.Client.Types: data Mount
+ Docker.Client.Types: data Network
+ Docker.Client.Types: data NetworkMode
+ Docker.Client.Types: data NetworkOptions
+ Docker.Client.Types: data NetworkSettings
+ Docker.Client.Types: data PortBinding
+ Docker.Client.Types: data PortType
+ Docker.Client.Types: data RestartPolicy
+ Docker.Client.Types: data Signal
+ Docker.Client.Types: data StartOpts
+ Docker.Client.Types: data Status
+ Docker.Client.Types: data TailLogOpt
+ Docker.Client.Types: data Timeout
+ Docker.Client.Types: data Ulimit
+ Docker.Client.Types: data VolumeFrom
+ Docker.Client.Types: data VolumePermission
+ Docker.Client.Types: defaultClientOpts :: DockerClientOpts
+ Docker.Client.Types: defaultContainerConfig :: Text -> ContainerConfig
+ Docker.Client.Types: defaultContainerResources :: ContainerResources
+ Docker.Client.Types: defaultCreateOpts :: Text -> CreateOpts
+ Docker.Client.Types: defaultDeleteOpts :: DeleteOpts
+ Docker.Client.Types: defaultHostConfig :: HostConfig
+ Docker.Client.Types: defaultListOpts :: ListOpts
+ Docker.Client.Types: defaultLogOpts :: LogOpts
+ Docker.Client.Types: defaultStartOpts :: StartOpts
+ Docker.Client.Types: dropImagePrefix :: [a] -> [a]
+ Docker.Client.Types: fromContainerID :: ContainerID -> Text
+ Docker.Client.Types: fromImageID :: ImageID -> Text
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Bind
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Container
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerConfig
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerDetails
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerID
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerPortInfo
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerResources
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ContainerState
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Device
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.DeviceRate
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.DeviceWeight
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.DockerVersion
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.EnvVar
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.HostConfig
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.HostPort
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Image
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.ImageID
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Link
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.LogDriverConfig
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.LogDriverType
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.MemoryConstraint
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Mount
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.NetworkMode
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.NetworkOptions
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.NetworkSettings
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.PortType
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.RestartPolicy
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Signal
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Status
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.Ulimit
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.VolumeFrom
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON Docker.Client.Types.VolumePermission
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.ExposedPort]
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.Label]
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.LogDriverOption]
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.Network]
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.PortBinding]
+ Docker.Client.Types: instance Data.Aeson.Types.FromJSON.FromJSON [Docker.Client.Types.Volume]
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.Bind
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.ContainerConfig
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.ContainerID
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.CreateOpts
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.Device
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.DeviceRate
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.DeviceWeight
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.DockerVersion
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.EnvVar
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.HostConfig
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.HostPort
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.ImageID
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.Link
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.LogDriverConfig
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.LogDriverType
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.MemoryConstraint
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.NetworkMode
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.PortType
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.RestartPolicy
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.Signal
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.Ulimit
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.VolumeFrom
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON Docker.Client.Types.VolumePermission
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON [Docker.Client.Types.ExposedPort]
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON [Docker.Client.Types.Label]
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON [Docker.Client.Types.LogDriverOption]
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON [Docker.Client.Types.PortBinding]
+ Docker.Client.Types: instance Data.Aeson.Types.ToJSON.ToJSON [Docker.Client.Types.Volume]
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Bind
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Container
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerConfig
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerDetails
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerID
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerPortInfo
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerResources
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ContainerState
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.CreateOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DeleteOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DetachKeys
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Device
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DeviceRate
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DeviceWeight
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DockerClientOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.DockerVersion
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Endpoint
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.EnvVar
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ExposedPort
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.HostConfig
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.HostPort
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Image
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ImageID
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Isolation
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Label
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Link
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.ListOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.LogDriverConfig
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.LogDriverOption
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.LogDriverType
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.LogOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.MemoryConstraint
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.MemoryConstraintSize
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Mount
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Network
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.NetworkMode
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.NetworkOptions
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.NetworkSettings
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.PortBinding
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.PortType
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.RestartPolicy
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Signal
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.StartOpts
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Status
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.TailLogOpt
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Timeout
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.UTSMode
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Ulimit
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.Volume
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.VolumeFrom
+ Docker.Client.Types: instance GHC.Classes.Eq Docker.Client.Types.VolumePermission
+ Docker.Client.Types: instance GHC.Classes.Ord Docker.Client.Types.NetworkMode
+ Docker.Client.Types: instance GHC.Classes.Ord Docker.Client.Types.PortType
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.ContainerConfig
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.ContainerDetails
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.ContainerResources
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.ContainerState
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.Device
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.DockerVersion
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.HostConfig
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.Image
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.Mount
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.PortType
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.Status
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.Ulimit
+ Docker.Client.Types: instance GHC.Generics.Generic Docker.Client.Types.VolumePermission
+ Docker.Client.Types: instance GHC.Read.Read Docker.Client.Types.PortType
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Bind
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Container
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerConfig
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerDetails
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerID
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerPortInfo
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerResources
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ContainerState
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.CreateOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DeleteOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DetachKeys
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Device
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DeviceRate
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DeviceWeight
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DockerClientOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.DockerVersion
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Endpoint
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.EnvVar
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ExposedPort
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.HostConfig
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.HostPort
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Image
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ImageID
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Isolation
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Label
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Link
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.ListOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.LogDriverConfig
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.LogDriverOption
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.LogDriverType
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.LogOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.MemoryConstraint
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.MemoryConstraintSize
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Mount
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Network
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.NetworkMode
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.NetworkOptions
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.NetworkSettings
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.PortBinding
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.PortType
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.RestartPolicy
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Signal
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.StartOpts
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Status
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.TailLogOpt
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Timeout
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.UTSMode
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Ulimit
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.Volume
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.VolumeFrom
+ Docker.Client.Types: instance GHC.Show.Show Docker.Client.Types.VolumePermission
+ Docker.Client.Types: newtype UTSMode
+ Docker.Client.Types: newtype Volume
+ Docker.Client.Types: setCmd :: Text -> CreateOpts -> CreateOpts
+ Docker.Client.Types: toContainerID :: Text -> Maybe ContainerID
+ Docker.Client.Types: toImageID :: Text -> Maybe ImageID
+ Docker.Client.Types: type ApiVersion = Text
+ Docker.Client.Types: type ContainerName = Text
+ Docker.Client.Types: type Digest = Text
+ Docker.Client.Types: type Name = Text
+ Docker.Client.Types: type Port = Integer
+ Docker.Client.Types: type RetryCount = Integer
+ Docker.Client.Types: type Tag = Text
+ Docker.Client.Types: type Timestamp = Integer
+ Docker.Client.Types: type URL = Text
+ Docker.Client.Types: type Value = Text
Files
- LICENSE +1/−1
- Network/Docker.hs +0/−144
- Network/Docker/Options.hs +0/−12
- Network/Docker/Types.hs +0/−263
- Network/Docker/Utils.hs +0/−6
- README.md +25/−2
- docker.cabal +50/−49
- src/Docker/Client.hs +112/−0
- src/Docker/Client/Api.hs +146/−0
- src/Docker/Client/Http.hs +233/−0
- src/Docker/Client/Internal.hs +75/−0
- src/Docker/Client/Types.hs +1423/−0
- tests/tests.hs +112/−36
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2014, Deni Bertovic+Copyright (c) 2016, Deni Bertovic All rights reserved.
− Network/Docker.hs
@@ -1,144 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Network.Docker where--import Control.Applicative ((<$>), (<*>))-import Control.Lens-import Data.Aeson (FromJSON, ToJSON, decode,- eitherDecode, toJSON)-import Data.Aeson.Lens (key, _String)-import Data.Aeson.TH-import qualified Data.ByteString.Lazy as L-import Data.Char-import qualified Data.Text as T-import Network.Docker.Options-import Network.Docker.Types-import Network.HTTP.Client.OpenSSL-import Network.Wreq-import OpenSSL (withOpenSSL)-import OpenSSL.Session (SSLContext, context)-import qualified OpenSSL.Session as SSL-import Pipes-import qualified Pipes.ByteString as PB-import qualified Pipes.HTTP as PH-import Text.Printf (printf)---defaultClientOpts :: DockerClientOpts-defaultClientOpts = DockerClientOpts- { apiVersion = "v1.12"- , baseUrl = "http://127.0.0.1:3128/"- , ssl = NoSSL- }--constructUrl :: URL -> ApiVersion -> Endpoint -> URL-constructUrl url apiVersion endpoint = printf "%s%s%s" url apiVersion endpoint--constructRelativeUrl url = url :: String--decodeResponse r = decode <$> (^. responseBody) <$> r--getOutOfResponse k r = (^? responseBody . key k . _String) r--getResponseStatusCode r = (^. responseStatus) r--fullUrl :: DockerClientOpts -> Endpoint -> URL-fullUrl clientOpts endpoint = constructUrl (baseUrl clientOpts) (apiVersion clientOpts) endpoint--setupSSLCtx :: SSLOptions -> IO SSLContext-setupSSLCtx (SSLOptions key cert) = do- ctx <- SSL.context- SSL.contextSetPrivateKeyFile ctx key- SSL.contextSetCertificateFile ctx cert- SSL.contextAddOption ctx SSL.SSL_OP_NO_SSLv3- SSL.contextAddOption ctx SSL.SSL_OP_NO_SSLv2- return ctx---mkOpts c = defaults & manager .~ Left (opensslManagerSettings c)--getSSL- :: SSLOptions- -> String- -> IO (Response L.ByteString)-getSSL sopts url = withOpenSSL $ getWith (mkOpts $ setupSSLCtx sopts) url--postSSL- :: ToJSON a- => SSLOptions- -> String- -> a- -> IO (Response L.ByteString)-postSSL sopts url = withOpenSSL . postWith (mkOpts $ setupSSLCtx sopts) url . toJSON--_dockerGetQuery :: Endpoint -> DockerClientOpts -> IO(Response L.ByteString)-_dockerGetQuery endpoint clientOpts@DockerClientOpts{ssl = NoSSL} =- get (fullUrl clientOpts endpoint)-_dockerGetQuery endpoint clientOpts@DockerClientOpts{ssl = SSL sslOpts} =- getSSL sslOpts (fullUrl clientOpts endpoint)--_dockerPostQuery :: ToJSON a => Endpoint -> DockerClientOpts -> a -> IO (Response L.ByteString)-_dockerPostQuery endpoint clientOpts@DockerClientOpts{ssl = NoSSL} postObject =- post (fullUrl clientOpts endpoint) (toJSON postObject)-_dockerPostQuery endpoint clientOpts@DockerClientOpts{ssl = SSL sslOpts} postObject =- postSSL sslOpts (fullUrl clientOpts endpoint) postObject--emptyPost = "" :: String-_dockerEmptyPostQuery endpoint clientOpts = post (fullUrl clientOpts endpoint) (toJSON emptyPost)--_dockerEmptyDeleteQuery endpoint clientOpts = delete (fullUrl clientOpts endpoint)--getDockerVersion :: DockerClientOpts -> IO (Maybe DockerVersion)-getDockerVersion = decodeResponse . _dockerGetQuery "/version"--getDockerContainers :: DockerClientOpts -> IO (Maybe [DockerContainer])-getDockerContainers = decodeResponse . _dockerGetQuery "/containers/json"--getAllDockerContainers :: DockerClientOpts -> IO (Maybe [DockerContainer])-getAllDockerContainers = decodeResponse . _dockerGetQuery "/containers/json?all=true"--getDockerImages :: DockerClientOpts -> IO (Maybe [DockerImage])-getDockerImages = decodeResponse . _dockerGetQuery "/images/json"--getAllDockerImages :: DockerClientOpts -> IO (Maybe [DockerImage])-getAllDockerImages = decodeResponse . _dockerGetQuery "/images/json?all=true"--createContainer :: DockerClientOpts -> CreateContainerOpts -> IO(Maybe T.Text)-createContainer clientOpts createOpts = getOutOfResponse "Id" <$> (_dockerPostQuery "/containers/create" clientOpts createOpts)--startContainer :: DockerClientOpts -> String -> StartContainerOpts -> IO(Status)-startContainer clientOpts containerId startOpts = (^. responseStatus) <$> _dockerPostQuery (printf "/containers/%s/start" containerId) clientOpts startOpts--stopContainer :: DockerClientOpts -> String -> IO (Status)-stopContainer clientOpts containerId = (^. responseStatus) <$> _dockerEmptyPostQuery (printf "/containers/%s/stop" containerId) clientOpts--killContainer :: DockerClientOpts -> String -> IO (Status)-killContainer clientOpts containerId = (^. responseStatus) <$> _dockerEmptyPostQuery (printf "/containers/%s/kill" containerId) clientOpts--restartContainer :: DockerClientOpts -> String -> IO (Status)-restartContainer clientOpts containerId = (^. responseStatus) <$> _dockerEmptyPostQuery (printf "/containers/%s/restart" containerId) clientOpts--pauseContainer :: DockerClientOpts -> String -> IO (Status)-pauseContainer clientOpts containerId = (^. responseStatus) <$> _dockerEmptyPostQuery (printf "/containers/%s/pause" containerId) clientOpts--unpauseContainer :: DockerClientOpts -> String -> IO (Status)-unpauseContainer clientOpts containerId = (^. responseStatus) <$> _dockerEmptyPostQuery (printf "/containers/%s/unpause" containerId) clientOpts--deleteContainer :: DockerClientOpts -> String -> IO (Status)-deleteContainer = deleteContainerWithOpts defaultDeleteOpts--deleteContainerWithOpts :: DeleteOpts -> DockerClientOpts -> String -> IO (Status)-deleteContainerWithOpts (DeleteOpts removeVolumes force) clientOpts containerId = (^. responseStatus) <$> _dockerEmptyDeleteQuery req clientOpts- where req = printf "/containers/%s?v=%s;force=%s" containerId (show removeVolumes) (show force)--getContainerLogsStream :: DockerClientOpts -> String -> IO ()-getContainerLogsStream clientOpts containerId = do- req <- PH.parseUrl (fullUrl clientOpts url)- let req' = req {PH.method = "GET"}- PH.withManager PH.defaultManagerSettings $ \m -> PH.withHTTP req' m $ \resp -> runEffect $ PH.responseBody resp >-> PB.stdout- where url = (printf "/containers/%s/logs?stdout=1&stderr=1&follow=1" containerId)--getContainerLogs :: DockerClientOpts -> String -> IO (L.ByteString)-getContainerLogs clientOpts containerId = (^. responseBody) <$> _dockerGetQuery url clientOpts- where url = (printf "/containers/%s/logs?stdout=1&stderr=1" containerId)-
− Network/Docker/Options.hs
@@ -1,12 +0,0 @@-module Network.Docker.Options where--import Data.Aeson.TH-import Network.Docker.Utils--dopts :: Options-dopts = defaultOptions {- fieldLabelModifier = strip_underscore- }---
− Network/Docker/Types.hs
@@ -1,263 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}--module Network.Docker.Types where--import Control.Applicative-import Control.Lens.TH-import Data.Aeson-import Data.Aeson.TH-import Data.Bool-import qualified Data.ByteString.Lazy as BS-import Data.Default-import qualified Data.Map as Map-import qualified Data.Text as T-import GHC.Generics-import Network.Docker.Options-import Network.Wreq.Types (Postable)-import OpenSSL.Session (SSLContext)-import Prelude hiding (id)--type URL = String-type ApiVersion = String-type Endpoint = String--type Tag = String-type IP = String-type Port = Int-type PortType = String--data SSL = NoSSL | SSL SSLOptions deriving Show--data DockerClientOpts = DockerClientOpts {- apiVersion :: ApiVersion- , baseUrl :: URL- , ssl :: SSL- } deriving (Show)---data SSLOptions = SSLOptions {- optionsKey :: FilePath- , optionsCert :: FilePath- } deriving Show---data ResourceId = ResourceId { _id :: String } deriving (Show, Eq)---data DockerImage = DockerImage- { _imageId :: ResourceId- , _imageCreatedAt :: Int- , _parentId :: Maybe String- , _repoTags :: [Tag]- , _size :: Int- , _virtualSize :: Int- } deriving (Show, Eq)---data DockerVersion = DockerVersion- { _Version :: String- , _GitCommit :: String- , _GoVersion :: String- , _Arch :: String- , _KernelVersion :: String- } deriving (Show, Eq)----- The JSON looks likes this:--- "Ports":[{"IP":"0.0.0.0","PrivatePort":55555,"PublicPort":55555,"Type":"tcp"}]--data PortMap = PortMap- { _ip :: IP- , _privatePort :: Port- , _publicPort :: Port- , _type :: PortType- } deriving (Show, Eq)--data DeleteOpts = DeleteOpts- { removeVolumes :: Bool- , force :: Bool- }--defaultDeleteOpts = DeleteOpts False False---data DockerContainer = DockerContainer- { _containerId :: ResourceId- , _containerImageId :: ResourceId- , _command :: String- , _containerCreatedAt :: Int- , _names :: [String]- , _status :: String- , _ports :: Maybe [PortMap]- } deriving (Show, Eq)---data CreateContainerOpts = CreateContainerOpts- { _hostname :: String- , _user :: String- , _memory :: Int- , _memorySwap :: Int- , _attachStdin :: Bool- , _attachStdout :: Bool- , _attachStderr :: Bool- , _portSpecs :: Maybe Object- , _tty :: Bool- , _openStdin :: Bool- , _stdinOnce :: Bool- , _env :: Maybe Object- , _cmd :: [String]- , _image :: String- , _volumes :: Maybe Object- , _volumesFrom :: Maybe Object- , _workingDir :: String- , _disableNetwork :: Bool- , _exposedPorts :: Maybe Object- } deriving (Show)--defaultCreateOpts = CreateContainerOpts {- _hostname = ""- , _user = ""- , _memory = 0- , _memorySwap = 0- , _attachStdin = False- , _attachStdout = False- , _attachStderr = False- , _portSpecs = Nothing- , _tty = False- , _openStdin = False- , _stdinOnce = False- , _env = Nothing- , _cmd = []- , _image = "debian"- , _volumes = Nothing- , _volumesFrom = Nothing- , _workingDir = ""- , _disableNetwork = False- , _exposedPorts = Nothing- }--instance ToJSON CreateContainerOpts where- toJSON (CreateContainerOpts {..}) = object- [ "Hostname" .= _hostname- , "User" .= _user- , "Memory" .= _memory- , "MemorySwap" .= _memorySwap- , "AttachStdin" .= _attachStdin- , "AttachStdout" .= _attachStdout- , "AttachStderr" .= _attachStderr- , "PortSpecs" .= _portSpecs- , "Tty" .= _tty- , "OpenStdin" .= _openStdin- , "StdinOnce" .= _stdinOnce- , "Env" .= _env- , "Cmd" .= _cmd- , "Image" .= _image- , "Volumes" .= _volumes- , "VolumesFrom" .= _volumesFrom- , "WrokingDir" .= _workingDir- , "DisableNetwork" .= _disableNetwork- , "ExposedPorts" .= _exposedPorts- ]---- data CreateContainerResponse = CreateContainerResponse--- { _createdContainerId :: String--- , _warnings :: Maybe [T.Text]--- } deriving (Show)--data StartContainerOpts = StartContainerOpts- { _Binds :: [T.Text]- , _Links :: [T.Text]- , _LxcConf :: [(T.Text, T.Text)]- , _PortBindings :: [((Int,T.Text),Int)]- , _PublishAllPorts :: Bool- , _Privileged :: Bool- , _Dns :: [T.Text]- , _VolumesFrom :: [T.Text]- , _RestartPolicy :: RestartPolicy- } deriving (Show)--defaultStartOpts = StartContainerOpts- { _Binds = []- , _Links = []- , _LxcConf = []- , _PortBindings = []- , _PublishAllPorts = False- , _Privileged = False- , _Dns = []- , _VolumesFrom = []- , _RestartPolicy = RestartNever- }--instance ToJSON StartContainerOpts where- toJSON (StartContainerOpts {..}) = object- [ "Binds" .= _Binds- , "Links" .= _Links- , "LxcConf" .= _LxcConf- , "PortBindings" .= object (map (\((h,p),c)->T.concat [T.pack (show h),"/",p] .= [object ["HostPort" .= show c]]) _PortBindings)- , "PublishAllPorts" .= _PublishAllPorts- , "Privileged" .= _Privileged- , "Dns" .= _Dns- , "VolumesFrom" .= _VolumesFrom- , "RestartPolicy" .= _RestartPolicy- ]--data RestartPolicy = RestartNever- | RestartAlways- | RestartOnFailure Int- deriving (Show)--instance ToJSON RestartPolicy where- toJSON RestartNever = object ["Name" .= (""::String), "MaximumRetryCount" .= (0::Int) ]- toJSON RestartAlways = object ["Name" .= ("always"::String), "MaximumRetryCount" .= (0::Int) ]- toJSON (RestartOnFailure n) = object ["Name" .= ("on-failure"::String), "MaximumRetryCount" .= n ]--makeClassy ''ResourceId---- makeLenses ''CreateContainerResponse-makeLenses ''DockerImage-makeLenses ''DockerContainer-makeLenses ''CreateContainerOpts--instance HasResourceId DockerImage where- resourceId = imageId--instance FromJSON DockerImage where- parseJSON (Object v) =- DockerImage <$> ResourceId <$> (v .: "Id")- <*> (v .: "Created")- <*> (v .:? "ParentId")- <*> (v .: "RepoTags")- <*> (v .: "Size")- <*> (v .: "VirtualSize")--instance FromJSON PortMap where- parseJSON (Object v) =- PortMap <$> (v .: "IP")- <*> (v .: "PrivatePort")- <*> (v .: "PublicPort")- <*> (v .: "Type")--instance HasResourceId DockerContainer where- resourceId = containerId--instance FromJSON DockerContainer where- parseJSON (Object v) =- DockerContainer <$> (ResourceId <$> (v .: "Id"))- <*> (ResourceId <$> (v .: "Id"))- <*> (v .: "Command")- <*> (v .: "Created")- <*> (v .: "Names")- <*> (v .: "Status")- <*> (v .:? "Ports")---- instance FromJSON CreateContainerResponse where--- parseJSON (Object v) =--- CreateContainerResponse <$> (v .: "Id")--- <*> (v .:? "warnings")--$(deriveJSON dopts ''DockerVersion)-
− Network/Docker/Utils.hs
@@ -1,6 +0,0 @@-module Network.Docker.Utils where--import Data.Char--strip_underscore :: String -> String-strip_underscore (_:xs) = xs
README.md view
@@ -1,3 +1,26 @@-# Docker Remote API wrapper for Haskell+# An API client for docker written in Haskell -## Work in progress+| Master | Dev |+| -------| ---- |+| [](https://travis-ci.org/denibertovic/docker-hs) | [](https://travis-ci.org/denibertovic/docker-hs) |+++## Current state++**Not production ready yet!**++The library is going through a **total rewrite**.++See [Issue #12](https://github.com/denibertovic/docker-hs/issues/12) for things+that need to happen before a new release.+++## Contributing++If you wish to contribute please see the Issue mentioned above or any issue tagged with "help wanted".+Make sure to submit your PR's against the `dev` branch.++Please consider filling an Issue first and discuss design decisions and implementation details before+writing any code. This is so that no development cycles go to waste on implementing a feature that+might not get merged either because of implementation details or other reasons.+
docker.cabal view
@@ -1,16 +1,13 @@--- Initial docker.cabal generated by cabal init. For further--- documentation, see http://haskell.org/cabal/users-guide/- name: docker-version: 0.2.0.4-synopsis: Haskell wrapper for Docker Remote API--- description:+version: 0.3.0.0+synopsis: An API client for docker written in Haskell+description: See README.md homepage: https://github.com/denibertovic/docker-hs license: BSD3 license-file: LICENSE-author: Deni Bertovic-maintainer: deni@denibertovic.com--- copyright:+author: Deni Bertovic <deni@denibertovic.com>, James Parker <jp@jamesparker.me>+maintainer: Deni Bertovic <deni@denibertovic.com>+copyright: BSD3 category: Network build-type: Simple extra-source-files: README.md@@ -18,52 +15,56 @@ stability: experimental library- exposed-modules: Network.Docker, Network.Docker.Options, Network.Docker.Types, Network.Docker.Utils- -- other-modules:- other-extensions: OverloadedStrings, DeriveFunctor, FlexibleContexts, TemplateHaskell- build-depends: base >= 4.7 && < 4.9- , lens >= 4.2- , aeson >= 0.7- , lens-aeson- , bytestring >= 0.10- , text >= 1.1- , wreq >= 0.3- , containers >= 0.5- , data-default >= 0.5.3- , network-uri >= 2.6.0.1- , pipes-http >= 1.0.0- , pipes >= 4.1.2- , pipes-text >= 0.0.0.12- , pipes-bytestring >= 2.1.0- , HsOpenSSL- , http-client-openssl- -- hs-source-dirs:+ default-extensions: DeriveGeneric, OverloadedStrings, ScopedTypeVariables, ExplicitForAll, MultiParamTypeClasses, FlexibleContexts, GeneralizedNewtypeDeriving, RankNTypes, DeriveFunctor, FlexibleInstances+ hs-source-dirs: src+ exposed-modules: Docker.Client, Docker.Client.Api, Docker.Client.Types, Docker.Client.Internal, Docker.Client.Http+ -- other-modules: Docker.Internal+ build-depends: base >= 4.7 && < 5+ , aeson >= 0.9.0 && < 1.1.0+ , blaze-builder >= 0.4.0 && < 0.5.0+ , bytestring >= 0.10.0 && < 0.11.0+ , containers >= 0.5.0 && < 0.6.0+ , data-default-class >= 0.0.1 && < 0.2.0+ , http-client >= 0.4.0 && < 0.6.0+ , http-types >= 0.9 && < 0.10.0+ , mtl >= 2.0.0 && < 3.0.0+ , network >= 2.6.0+ , text >= 1.0.0 && < 2.0.0+ , time >= 1.5.0 && < 1.7.0+ , scientific >= 0.3.0 && < 0.4.0+ , tls >= 1.3.7 && < 1.4.0+ , unordered-containers >= 0.2.0 && < 0.3.0+ , x509 >= 1.6.0 && < 1.7.0+ , x509-store >= 1.6.0 && < 1.7.0+ , x509-system >= 1.6.0 && < 1.7.0 default-language: Haskell2010+ ghc-options: -Wall -fno-warn-name-shadowing -test-suite tests- build-depends: base >= 4.7 && < 4.9+test-suite docker-hs-tests+ build-depends: base >= 4.7 && < 5+ , bytestring+ , connection , docker- , lens >= 4.2- , aeson >= 0.7+ , aeson+ , containers+ , unordered-containers+ , http-client+ , http-client-tls+ , http-types+ , tasty+ , tasty-hunit+ , tasty-quickcheck+ , text+ , lens , lens-aeson- , bytestring >= 0.10- , text >= 1.1- , wreq >= 0.3- , containers >= 0.5- , data-default >= 0.5.3- , pipes >= 4.1.2- , pipes-http >= 1.0.0- , pipes-text >= 0.0.0.12- , pipes-bytestring >= 2.1.0- , tasty >= 0.10.1- , tasty-quickcheck >= 0.8.2- , QuickCheck >= 2.7- , tasty-hunit >= 0.8+ , transformers+ , QuickCheck , process- , http-types- , HsOpenSSL- , http-client-openssl type: exitcode-stdio-1.0 main-is: tests.hs hs-source-dirs: tests default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/denibertovic/docker-hs
+ src/Docker/Client.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-|+Module : Docker.Client+Copyright : (c) Deni Bertovic, 2016+License : BSD3+Maintainer : deni@denibertovic.com+Stability : experimental+Portability : POSIX++= Getting started++Creating a container:++We're going to create a nginx container and we're going to map port @80@ from within the container to port @8000@ on+the host.++@+import Docker.Client++runNginxContainer :: IO ContainerID+runNginxContainer = runDockerT (defaultClientOpts, defaultHttpHandler) $ do+ let pb = PortBinding 80 TCP [HostPort "0.0.0.0" 8000]+ let myCreateOpts = addPortBinding pb $ defaultCreateOpts "nginx:latest"+ cid <- createContainer myCreateOpts+ case cid of+ Left err -> error $ show err+ Right i -> do+ _ <- startContainer defaultStartOpts i+ return i+@++Let's start out nginx container:++>>> cid <- runNginxContainer+++Visit http://localhost:8000 and verify that nginx is running.++Let's stop the container now:++@+stopNginxContainer :: ContainerID -> IO ()+stopNginxContainer cid = runDockerT (defaultClientOpts, defaultHttpHandler) $ do+ r <- stopContainer DefaultTimeout cid+ case r of+ Left err -> error "I failed to stop the container"+ Right _ -> return ()+@++>>> stopNginxContainer cid++Let's start a Postgres container by mapping the \/tmp directory from within the container to the+\/tmp directory on the host. That way we make sure that the data we write to \/tmp in the container will+persist on the host file system.++@+runPostgresContainer :: IO ContainerID+runPostgresContainer = runDockerT (defaultClientOpts, defaultHttpHandler) $ do+ let pb = PortBinding 5432 TCP [HostPort "0.0.0.0" 5432]+ let myCreateOpts = addBinds [Bind "\/tmp" "\/tmp" Nothing] $ addPortBinding pb $ defaultCreateOpts "postgres:9.5"+ cid <- createContainer myCreateOpts+ case cid of+ Left err -> error $ show err+ Right i -> do+ _ <- startContainer defaultStartOpts i+ return i+@++= Get Docker API Version++>>> runDockerT (defaultClientOpts, defaultHttpHandler) $ getDockerVersion+Right (DockerVersion {version = "1.12.0", apiVersion = "1.24", gitCommit = "8eab29e", goVersion = "go1.6.3", os = "linux", arch = "amd64", kernelVersion = "4.6.0-1-amd64", buildTime = "2016-07-28T21:46:40.664812891+00:00"})++= Setup SSL Authentication++Let's create a custom 'HttpHandler' that uses a client's certificate and private key for SSL authentication.+It also accepts a self-signed CA certificate which is specified via 'clientParamsSetCA'.+This handler can replace 'defaultHttpHandler' in the arguments to 'runDockerT'.++@+let host = "domain.name"+let port = fromInteger 4000+let privKey = "path\/to\/private\/key"+let cert = "path\/to\/certificate"+let ca = "path\/to\/CA"+paramsE <- clientParamsWithClientAuthentication host port privKey cert+case paramsE of+ Left err ->+ error err+ Right params' -> do+ params <- clientParamsSetCA params' ca+ settings <- HTTP.mkManagerSettings (TLSSettings params) Nothing+ manager <- newManager settings+ return $ httpHandler manager+@+++-}+module Docker.Client (+ -- * Client functions+ module Docker.Client.Api+ -- * Types+ , module Docker.Client.Types+ -- * Http+ , module Docker.Client.Http+ ) where++import Docker.Client.Api+import Docker.Client.Http+import Docker.Client.Types
+ src/Docker/Client/Api.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Docker.Client.Api (+ -- * Containers+ listContainers+ , createContainer+ , startContainer+ , stopContainer+ , killContainer+ , restartContainer+ , pauseContainer+ , unpauseContainer+ , deleteContainer+ , inspectContainer+ , getContainerLogs+ -- * Images+ , listImages+ -- * Other+ , getDockerVersion+ ) where++import Control.Monad.Except (ExceptT (..), runExceptT, throwError)+import Control.Monad.Reader (ask, lift)+import Data.Aeson (FromJSON, eitherDecode')+import qualified Data.ByteString.Lazy as BSL+import qualified Data.Text as Text+import Network.HTTP.Client (responseBody, responseStatus)+import Network.HTTP.Types (StdMethod (..))++import Docker.Client.Http+import Docker.Client.Types++requestUnit :: (Monad m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError ())+requestUnit verb endpoint = const (Right ()) <$> requestHelper verb endpoint++requestHelper :: (Monad m) => HttpVerb -> Endpoint -> DockerT m (Either DockerError Response)+requestHelper verb endpoint = runExceptT $ do+ (opts, httpHandler) <- lift ask+ case mkHttpRequest verb endpoint opts of+ Nothing ->+ throwError $ DockerInvalidRequest endpoint+ Just request -> do+ response <- ExceptT $ lift $ httpHandler request++ -- Check status code.+ let status = responseStatus response+ maybe (return ()) throwError $+ statusCodeToError endpoint status++ return response++parseResponse :: (FromJSON a, Monad m) => Either DockerError Response -> DockerT m (Either DockerError a)+parseResponse (Left err) =+ return $ Left err+parseResponse (Right response) =+ -- Parse request body.+ case eitherDecode' $ responseBody response of+ Left err ->+ return $ Left $ DockerClientDecodeError $ Text.pack err+ Right r ->+ return $ Right r++-- | Gets the version of the docker engine remote API.+getDockerVersion :: forall m. Monad m => DockerT m (Either DockerError DockerVersion)+getDockerVersion = requestHelper GET VersionEndpoint >>= parseResponse++-- | Lists all running docker containers. Pass in @'defaultListOpts' {all+-- = True}@ to get a list of stopped containers as well.+listContainers :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Container])+listContainers opts = requestHelper GET (ListContainersEndpoint opts) >>= parseResponse++-- | Lists all docker images.+listImages :: forall m. Monad m => ListOpts -> DockerT m (Either DockerError [Image])+listImages opts = requestHelper GET (ListImagesEndpoint opts) >>= parseResponse++-- | Creates a docker container but does __not__ start it. See+-- 'CreateOpts' for a list of options and you can use 'defaultCreateOpts'+-- for some sane defaults.+createContainer :: forall m. Monad m => CreateOpts -> Maybe ContainerName -> DockerT m (Either DockerError ContainerID)+createContainer opts cn = requestHelper POST (CreateContainerEndpoint opts cn) >>= parseResponse++-- | Start a container from a given 'ContainerID' that we get from+-- 'createContainer'. See 'StartOpts' for a list of configuration options+-- for starting a container. Use 'defaultStartOpts' for sane defaults.+startContainer :: forall m. Monad m => StartOpts -> ContainerID -> DockerT m (Either DockerError ())+startContainer sopts cid = requestUnit POST $ StartContainerEndpoint sopts cid++-- | Attempts to stop a container with the given 'ContainerID' gracefully+-- (SIGTERM).+-- The docker daemon will wait for the given 'Timeout' and then send+-- a SIGKILL killing the container.+stopContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())+stopContainer t cid = requestUnit POST $ StopContainerEndpoint t cid++-- | Sends a 'Signal' to the container with the given 'ContainerID'. Same+-- as 'stopContainer' but you choose the signal directly.+killContainer :: forall m. Monad m => Signal -> ContainerID -> DockerT m (Either DockerError ())+killContainer s cid = requestUnit POST $ KillContainerEndpoint s cid++-- | Restarts a container with the given 'ContainerID'.+restartContainer :: forall m. Monad m => Timeout -> ContainerID -> DockerT m (Either DockerError ())+restartContainer t cid = requestUnit POST $ RestartContainerEndpoint t cid++-- | Pauses a container with the given 'ContainerID'.+pauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())+pauseContainer cid = requestUnit POST $ PauseContainerEndpoint cid++-- | Unpauses a container with the given 'ContainerID'.+unpauseContainer :: forall m. Monad m => ContainerID -> DockerT m (Either DockerError ())+unpauseContainer cid = requestUnit GET $ UnpauseContainerEndpoint cid++-- | Deletes a container with the given 'ContainerID'.+-- See "DeleteOpts" for options and use 'defaultDeleteOpts' for sane+-- defaults.+deleteContainer :: forall m. Monad m => DeleteOpts -> ContainerID -> DockerT m (Either DockerError ())+deleteContainer dopts cid = requestUnit DELETE $ DeleteContainerEndpoint dopts cid++-- | Gets 'ContainerDetails' for a given 'ContainerID'.+inspectContainer :: forall m . Monad m => ContainerID -> DockerT m (Either DockerError ContainerDetails)+inspectContainer cid = requestHelper GET (InspectContainerEndpoint cid) >>= parseResponse++-- | Get's container's logs for a given 'ContainerID'.+-- This will only work with the 'JsonFile' 'LogDriverType' as the other driver types disable+-- this endpoint and it will return a 'DockerError'.+--+-- See 'LogOpts' for options that you can pass and+-- 'defaultLogOpts' for sane defaults.+--+-- __NOTE__: Currently streaming logs is+-- not supported and this function will fetch the entire log that the+-- container produced in the json-file on disk. Depending on the logging+-- setup of the process in your container this can be a significant amount+-- which might block your application...so use with caution.+--+-- The recommended method is to use one of the other 'LogDriverType's available (like+-- syslog) for creating your containers.+getContainerLogs :: forall m. Monad m => LogOpts -> ContainerID -> DockerT m (Either DockerError BSL.ByteString)+getContainerLogs logopts cid = fmap responseBody <$> requestHelper GET (ContainerLogsEndpoint logopts False cid)++-- TODO: Use http-conduit to output to a sink.+-- getContainerLogsStream :: forall m. Monad m => Sink BSL.ByteString m b -> LogOpts -> ContainerID -> DockerT m (Either DockerError b)+-- getContainerLogsStream sink logopts cid = runResourceT $ do+-- response <- http request manager+-- responseBody response C.$$+- sink+
+ src/Docker/Client/Http.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Docker.Client.Http where++import Control.Monad.Reader (ReaderT, runReaderT)+import qualified Data.ByteString.Char8 as BSC+import qualified Data.ByteString.Lazy as BL+import Data.Default.Class (def)+import Data.Monoid ((<>))+import Data.Text.Encoding (encodeUtf8)+import Data.X509 (CertificateChain (..))+import Data.X509.CertificateStore (makeCertificateStore)+import Data.X509.File (readKeyFile, readSignedObject)+import Network.HTTP.Client (defaultManagerSettings, httpLbs,+ managerRawConnection, method,+ newManager, parseUrl,+ requestBody, requestHeaders)+import qualified Network.HTTP.Client as HTTP+import Network.HTTP.Client.Internal (makeConnection)+import Network.HTTP.Types (StdMethod, status101, status200,+ status201, status204)+import Network.TLS (ClientHooks (..),+ ClientParams (..), Shared (..),+ Supported (..),+ defaultParamsClient)+import Network.TLS.Extra (ciphersuite_strong)+import System.X509 (getSystemCertificateStore)++import Control.Exception (try)+import Control.Monad.Except+import Control.Monad.Reader.Class+import Data.Text as T+import Data.Typeable (Typeable)+import qualified Network.HTTP.Types as HTTP+import qualified Network.Socket as S+import qualified Network.Socket.ByteString as SBS+++import Docker.Client.Internal (getEndpoint,+ getEndpointRequestBody)+import Docker.Client.Types (DockerClientOpts, Endpoint (..),+ baseUrl)++type Request = HTTP.Request+type Response = HTTP.Response BL.ByteString+type HttpVerb = StdMethod+type HttpHandler m = Request -> m (Either DockerError Response)++data DockerError = DockerConnectionError+ | DockerInvalidRequest Endpoint+ | DockerClientDecodeError Text -- ^ Could not parse the response from the Docker endpoint.+ | DockerInvalidStatusCode HTTP.Status -- ^ Invalid exit code received from Docker endpoint.+ | GenericDockerError Text deriving (Eq, Show, Typeable)++newtype DockerT m a = DockerT {+ unDockerT :: Monad m => ReaderT (DockerClientOpts, HttpHandler m) m a+ } deriving (Functor) -- Applicative, Monad, MonadReader, MonadError, MonadTrans++instance Applicative m => Applicative (DockerT m) where+ pure a = DockerT $ pure a+ (<*>) (DockerT f) (DockerT v) = DockerT $ f <*> v++instance Monad m => Monad (DockerT m) where+ (DockerT m) >>= f = DockerT $ m >>= unDockerT . f+ return = pure++instance Monad m => MonadReader (DockerClientOpts, HttpHandler m) (DockerT m) where+ ask = DockerT ask+ local f (DockerT m) = DockerT $ local f m++instance MonadTrans DockerT where+ lift m = DockerT $ lift m++instance MonadIO m => MonadIO (DockerT m) where+ liftIO = lift . liftIO++runDockerT :: Monad m => (DockerClientOpts, HttpHandler m) -> DockerT m a -> m a+runDockerT (opts, h) r = runReaderT (unDockerT r) (opts, h)++-- The reason we return Maybe Request is because the parseURL function+-- might find out parameters are invalid and will fail to build a Request+-- Since we are the ones building the Requests this shouldn't happen, but would+-- benefit from testing that on all of our Endpoints+mkHttpRequest :: HttpVerb -> Endpoint -> DockerClientOpts -> Maybe Request+mkHttpRequest verb e opts = request+ where fullE = T.unpack (baseUrl opts) ++ T.unpack (getEndpoint e)+ initialR = parseUrl fullE+ request' = case initialR of+ Just ir ->+ return $ ir {method = (encodeUtf8 . T.pack $ show verb),+ requestHeaders = [("Content-Type", "application/json; charset=utf-8")]}+ Nothing -> Nothing+ request = (\r -> maybe r (\body -> r {requestBody = HTTP.RequestBodyLBS body,+ requestHeaders = [("Content-Type", "application/json; charset=utf-8")]}) $ getEndpointRequestBody e) <$> request'+ -- Note: Do we need to set length header?++defaultHttpHandler :: MonadIO m => HttpHandler m+defaultHttpHandler request = do+ manager <- liftIO $ newManager defaultManagerSettings+ httpHandler manager request++httpHandler :: MonadIO m => HTTP.Manager -> HttpHandler m+httpHandler manager request = liftIO $ do+ try (httpLbs request manager) >>= \res -> case res of+ Right res -> return $ Right res+ Left HTTP.FailedConnectionException{} -> return $ Left DockerConnectionError+ Left HTTP.FailedConnectionException2{} -> return $ Left DockerConnectionError+ Left e -> return $ Left $ GenericDockerError (T.pack $ show e)++-- | Connect to a unix domain socket (the default docker socket is+-- at \/var\/run\/docker.sock)+--+-- Docker seems to ignore the hostname in requests sent over unix domain+-- sockets (and the port obviously doesn't matter either)+unixHttpHandler :: MonadIO m => FilePath -- ^ The socket to connect to+ -> HttpHandler m+unixHttpHandler fp request = do+ let mSettings = defaultManagerSettings+ { managerRawConnection = return $ openUnixSocket fp}+ manager <- liftIO $ newManager mSettings+ httpHandler manager request++ where+ openUnixSocket filePath _ _ _ = do+ s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol+ S.connect s (S.SockAddrUnix filePath)+ makeConnection (SBS.recv s 8096)+ (SBS.sendAll s)+ (S.sClose s)++-- TODO:+-- Move this to http-client-tls or network?+-- Add CA.+-- Maybe change this to: HostName -> PortNumber -> ClientParams -> IO (Either String TLSSettings)+clientParamsWithClientAuthentication :: S.HostName -> S.PortNumber -> FilePath -> FilePath -> IO (Either String ClientParams)+clientParamsWithClientAuthentication host port keyFile certificateFile = do+ keys <- readKeyFile keyFile+ cert <- readSignedObject certificateFile+ case keys of+ [key] ->+ -- TODO: load keys/path from file+ let params = (defaultParamsClient host $ BSC.pack $ show port) {+ clientHooks = def+ { onCertificateRequest = \_ -> return (Just (CertificateChain cert, key))}+ , clientSupported = def+ { supportedCiphers = ciphersuite_strong}+ }+ in+ return $ Right params+ _ ->+ return $ Left $ "Could not read key file: " ++ keyFile++clientParamsSetCA :: ClientParams -> FilePath -> IO ClientParams+clientParamsSetCA params path = do+ userStore <- makeCertificateStore <$> readSignedObject path+ systemStore <- getSystemCertificateStore+ let store = userStore <> systemStore+ let oldShared = clientShared params+ return $ params { clientShared = oldShared+ { sharedCAStore = store }+ }+++-- If the status is an error, returns a Just DockerError. Otherwise, returns Nothing.+statusCodeToError :: Endpoint -> HTTP.Status -> Maybe DockerError+statusCodeToError VersionEndpoint st =+ if st == status200 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (ListContainersEndpoint _) st =+ if st == status200 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (ListImagesEndpoint _) st =+ if st == status200 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (CreateContainerEndpoint _ _) st =+ if st == status201 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (StartContainerEndpoint _ _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (StopContainerEndpoint _ _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (KillContainerEndpoint _ _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (RestartContainerEndpoint _ _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (PauseContainerEndpoint _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (UnpauseContainerEndpoint _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (ContainerLogsEndpoint _ _ _) st =+ if st == status200 || st == status101 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (DeleteContainerEndpoint _ _) st =+ if st == status204 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st+statusCodeToError (InspectContainerEndpoint _) st =+ if st == status200 then+ Nothing+ else+ Just $ DockerInvalidStatusCode st++
+ src/Docker/Client/Internal.hs view
@@ -0,0 +1,75 @@+module Docker.Client.Internal where+++import Blaze.ByteString.Builder (toByteString)+import qualified Data.Aeson as JSON+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BSL+import Data.Text as T+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Network.HTTP.Types (Query, encodePath,+ encodePathSegments)++import Docker.Client.Types+++encodeURL :: [T.Text] -> T.Text+encodeURL ps = decodeUtf8 $ toByteString $ encodePathSegments ps++encodeURLWithQuery :: [T.Text] -> Query -> T.Text+encodeURLWithQuery ps q = decodeUtf8 $ toByteString $ encodePath ps q++encodeQ :: String -> ByteString+encodeQ = encodeUtf8 . T.pack++getEndpoint :: Endpoint -> T.Text+getEndpoint VersionEndpoint = encodeURL ["version"]+getEndpoint (ListContainersEndpoint _) = encodeURL ["containers", "json"] -- Make use of lsOpts here+getEndpoint (ListImagesEndpoint _) = encodeURL ["images", "json"] -- Make use of lsOpts here+getEndpoint (CreateContainerEndpoint _ cn) = case cn of+ Just cn -> encodeURLWithQuery ["containers", "create"] [("name", Just (encodeQ $ T.unpack cn))]+ Nothing -> encodeURL ["containers", "create"]+getEndpoint (StartContainerEndpoint startOpts cid) = encodeURLWithQuery ["containers", fromContainerID cid, "start"] query+ where query = case (detachKeys startOpts) of+ WithCtrl c -> [("detachKeys", Just (encodeQ $ ctrl ++ [c]))]+ WithoutCtrl c -> [("detachKeys", Just (encodeQ [c]))]+ DefaultDetachKey -> []+ ctrl = ['c', 't', 'r', 'l', '-']+getEndpoint (StopContainerEndpoint t cid) = encodeURLWithQuery ["containers", fromContainerID cid, "stop"] query+ where query = case t of+ Timeout x -> [("t", Just (encodeQ $ show x))]+ DefaultTimeout -> []+getEndpoint (KillContainerEndpoint s cid) = encodeURLWithQuery ["containers", fromContainerID cid, "kill"] query+ where query = case s of+ SIG x -> [("signal", Just (encodeQ $ show x))]+ _ -> [("signal", Just (encodeQ $ show s))]+getEndpoint (RestartContainerEndpoint t cid) = encodeURLWithQuery ["containers", fromContainerID cid, "restart"] query+ where query = case t of+ Timeout x -> [("t", Just (encodeQ $ show x))]+ DefaultTimeout -> []+getEndpoint (PauseContainerEndpoint cid) = encodeURL ["containers", fromContainerID cid, "pause"]+getEndpoint (UnpauseContainerEndpoint cid) = encodeURL ["containers", fromContainerID cid, "unpause"]+-- Make use of since/timestamps/tail logopts here instead of ignoreing them+getEndpoint (ContainerLogsEndpoint (LogOpts stdout stderr _ _ _) follow cid) =+ encodeURLWithQuery ["containers", fromContainerID cid, "logs"] query+ where query = [("stdout", Just (encodeQ $ show stdout)), ("stderr", Just (encodeQ $ show stderr)), ("follow", Just (encodeQ $ show follow))]+getEndpoint (DeleteContainerEndpoint (DeleteOpts removeVolumes force) cid) =+ encodeURLWithQuery ["containers", fromContainerID cid] query+ where query = [("v", Just (encodeQ $ show removeVolumes)), ("force", Just (encodeQ $ show force))]+getEndpoint (InspectContainerEndpoint cid) =+ encodeURLWithQuery ["containers", fromContainerID cid, "json"] []++getEndpointRequestBody :: Endpoint -> Maybe BSL.ByteString+getEndpointRequestBody VersionEndpoint = Nothing+getEndpointRequestBody (ListContainersEndpoint _) = Nothing+getEndpointRequestBody (ListImagesEndpoint _) = Nothing+getEndpointRequestBody (CreateContainerEndpoint opts _) = Just $ JSON.encode opts+getEndpointRequestBody (StartContainerEndpoint _ _) = Nothing+getEndpointRequestBody (StopContainerEndpoint _ _) = Nothing+getEndpointRequestBody (KillContainerEndpoint _ _) = Nothing+getEndpointRequestBody (RestartContainerEndpoint _ _) = Nothing+getEndpointRequestBody (PauseContainerEndpoint _) = Nothing+getEndpointRequestBody (UnpauseContainerEndpoint _) = Nothing+getEndpointRequestBody (ContainerLogsEndpoint _ _ _) = Nothing+getEndpointRequestBody (DeleteContainerEndpoint _ _) = Nothing+getEndpointRequestBody (InspectContainerEndpoint _) = Nothing
+ src/Docker/Client/Types.hs view
@@ -0,0 +1,1423 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Docker.Client.Types (+ Endpoint(..)+ , URL+ , ApiVersion+ , ContainerID+ , fromContainerID+ , toContainerID+ , ImageID+ , fromImageID+ , toImageID+ , Timeout(..)+ , Signal(..)+ , ContainerDetails(..)+ , DockerClientOpts(..)+ , defaultClientOpts+ , ListOpts(..)+ , defaultListOpts+ , DockerVersion(..)+ , ContainerPortInfo(..)+ , Container(..)+ , ContainerState(..)+ , Status(..)+ , Digest+ , Label(..)+ , Tag+ , Image(..)+ , dropImagePrefix+ , CreateOpts(..)+ , defaultCreateOpts+ , DetachKeys(..)+ , StartOpts(..)+ , defaultStartOpts+ , DeleteOpts(..)+ , defaultDeleteOpts+ , Timestamp+ , TailLogOpt(..)+ , LogOpts(..)+ , defaultLogOpts+ , VolumePermission(..)+ , Bind(..)+ , Volume(..)+ , Device(..)+ , ContainerName+ , VolumeFrom(..)+ , Link(..)+ , LogDriverType(..)+ , LogDriverOption(..)+ , LogDriverConfig(..)+ , NetworkMode(..)+ , PortType(..)+-- , NetworkInterface(..)+ , Network(..)+ , NetworkSettings(..)+ , NetworkOptions(..)+ , Mount(..)+ , PortBinding(..)+ , HostPort(..)+ , RetryCount+ , RestartPolicy(..)+ , Isolation(..)+ , UTSMode(..)+ , HostConfig(..)+ , defaultHostConfig+ , Ulimit(..)+ , ContainerResources(..)+ , defaultContainerResources+ , Port+ , Name+ , Value+ , EnvVar(..)+ , ContainerConfig(..)+ , defaultContainerConfig+ , ExposedPort(..)+ , DeviceWeight(..)+ , DeviceRate(..)+ , addPortBinding+ , addExposedPort+ , addBind+ , setCmd+ , addLink+ , addVolume+ , addVolumeFrom+ ) where++import Data.Aeson (FromJSON, ToJSON, genericParseJSON,+ genericToJSON, object, parseJSON, toJSON,+ (.!=), (.:), (.:?), (.=))+import qualified Data.Aeson as JSON+import Data.Aeson.Types (defaultOptions, fieldLabelModifier)+import Data.Char (isAlphaNum, toUpper)+import qualified Data.HashMap.Strict as HM+import Data.Monoid ((<>))+import Data.Scientific (floatingOrInteger)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time.Clock (UTCTime)+import GHC.Generics (Generic)+import Prelude hiding (all, tail)+import Text.Read (readMaybe)++-- | List of Docker Engine API endpoints+data Endpoint =+ VersionEndpoint+ | ListContainersEndpoint ListOpts+ | ListImagesEndpoint ListOpts+ | CreateContainerEndpoint CreateOpts (Maybe ContainerName)+ | StartContainerEndpoint StartOpts ContainerID+ | StopContainerEndpoint Timeout ContainerID+ | KillContainerEndpoint Signal ContainerID+ | RestartContainerEndpoint Timeout ContainerID+ | PauseContainerEndpoint ContainerID+ | UnpauseContainerEndpoint ContainerID+ | ContainerLogsEndpoint LogOpts Bool ContainerID -- ^ Second argument (Bool) is whether to follow which is currently hardcoded to False.+ -- See note in 'Docker.Client.Api.getContainerLogs' for explanation why.+ | DeleteContainerEndpoint DeleteOpts ContainerID+ | InspectContainerEndpoint ContainerID+ deriving (Eq, Show)++-- | We should newtype this+type URL = Text++-- | We should newtype this+type ApiVersion = Text+++-- | ID of a contianer+newtype ContainerID = ContainerID Text+ deriving (Eq, Show)++-- | Used for extracting the id of the container from the newtype+fromContainerID :: ContainerID -> Text+fromContainerID (ContainerID t) = t++-- | Used for parsing a Text value into a ContainerID. We apply some basic+-- validation here.+toContainerID :: Text -> Maybe ContainerID+toContainerID t =+ if T.all (\c -> isAlphaNum c || c == ':') t then -- Note: Can we improve this whitelist?+ Just $ ContainerID t+ else+ Nothing++-- ID of an image.+newtype ImageID = ImageID Text+ deriving (Eq, Show)++-- | Used for extracting the id of the image from the newtype.+fromImageID :: ImageID -> Text+fromImageID (ImageID t) = t++-- | Helper function used for parsing a Text value into an ImageID. For now+-- just basic validation is used.+toImageID :: Text -> Maybe ImageID+toImageID t =+ if T.all (\c -> isAlphaNum c || c == ':') t then -- Note: Can we improve this whitelist?+ Just $ ImageID t+ else+ Nothing++-- | Timeout used for stopping a container. DefaultTimeout is 10 seconds.+data Timeout = Timeout Integer | DefaultTimeout deriving (Eq, Show)++-- TODO: Add more Signals or use an existing lib+-- | Signal used for sending to the process running in the container.+-- The default signal is SIGTERM.+data Signal = SIGHUP+ | SIGINT+ | SIGQUIT+ | SIGSTOP+ | SIGTERM+ | SIGUSR1+ | SIG Integer+ | SIGKILL deriving (Eq, Show)++instance FromJSON Signal where+ parseJSON (JSON.String "SIGTERM") = return SIGTERM+ parseJSON (JSON.String "SIGHUP") = return SIGHUP -- Note: Guessing on the string values for these.+ parseJSON (JSON.String "SIGINT") = return SIGINT+ parseJSON (JSON.String "SIGQUIT") = return SIGQUIT+ parseJSON (JSON.String "SIGSTOP") = return SIGSTOP+ parseJSON (JSON.String "SIGUSR1") = return SIGUSR1+ parseJSON _ = fail "Unknown Signal"++instance ToJSON Signal where+ toJSON SIGHUP = "SIGHUP"+ toJSON SIGINT = "SIGINT"+ toJSON SIGQUIT = "SIGQUIT"+ toJSON SIGSTOP = "SIGSTOP"+ toJSON SIGTERM = "SIGTERM"+ toJSON SIGUSR1 = "SIGUSR1"+ toJSON (SIG i) = toJSON i+ toJSON SIGKILL = "SIGKILL"++data ContainerDetails = ContainerDetails {+ appArmorProfile :: Text+ , args :: [Text]+ , containerDetailsConfig :: ContainerConfig+ , created :: UTCTime+ , driver :: Text+ -- , execIDs -- Not sure what this is in 1.24 spec.+ , containerDetailsHostConfig :: HostConfig+ , hostnamePath :: FilePath+ , hostsPath :: FilePath+ , logPath :: FilePath+ , containerDetailsId :: ContainerID+ , containerDetailsImage :: ImageID+ , mountLabel :: Text+ , name :: Text+ , networkSettings :: NetworkSettings+ , path :: FilePath+ , processLabel :: Text+ , resolveConfPath :: FilePath+ , restartCount :: Int+ , state :: ContainerState+ , mounts :: [Mount]+ }+ deriving (Eq, Show, Generic)++-- | Data type used for parsing the mount information from a container+-- list.+data Mount = Mount {+ mountName :: Text+ , mountSource :: FilePath+ , mountDestination :: FilePath+ , mountDriver :: Text+ , mountMode :: Maybe VolumePermission -- apparently this can be null+ , mountRW :: Bool+ , mountPropogation :: Text+ }+ deriving (Eq, Show, Generic)++instance FromJSON Mount where+ parseJSON (JSON.Object o) = do+ name <- o .: "Name"+ src <- o .: "Source"+ dest <- o .: "Destination"+ driver <- o .: "Driver"+ mode <- o .: "Mode"+ rw <- o .: "RW"+ prop <- o .: "Propagation"+ return $ Mount name src dest driver mode rw prop+ parseJSON _ = fail "Mount is not an object"++-- | Data type used for parsing the container state from a list of+-- containers.+data ContainerState = ContainerState {+ containerError :: Text+ , exitCode :: Int+ , finishedAt :: Maybe UTCTime -- Note: Is this a maybe?+ , oomKilled :: Bool+ , dead :: Bool+ , paused :: Bool+ , pid :: Int+ , restarting :: Bool+ , running :: Bool+ , startedAt :: UTCTime+ , status :: Status+ }+ deriving (Eq, Show, Generic)++instance FromJSON ContainerState where+ parseJSON (JSON.Object o) = do+ err <- o .: "Error"+ exit <- o .: "ExitCode"+ finished <- o .:? "FinishedAt"+ oomKilled <- o .: "OOMKilled"+ dead <- o .: "Dead"+ paused <- o .: "Paused"+ pid <- o .: "Pid"+ restarting <- o .: "Restarting"+ running <- o .: "Running"+ started <- o .: "StartedAt"+ st <- o .: "Status"+ return $ ContainerState err exit finished oomKilled dead paused pid restarting running started st+ parseJSON _ = fail "ContainerState is not an object"+++-- | Client options used to configure the remote engine we're talking to+data DockerClientOpts = DockerClientOpts {+ apiVer :: ApiVersion+ , baseUrl :: URL+ }+ deriving (Eq, Show)++-- | Default "DockerClientOpts" used for talking to the docker engine.+defaultClientOpts :: DockerClientOpts+defaultClientOpts = DockerClientOpts {+ apiVer = "v1.24"+ , baseUrl = "http://127.0.0.1:2375"+ }++-- | List options used for filtering the list of container or images.+data ListOpts = ListOpts { all :: Bool } deriving (Eq, Show)++-- | Default "ListOpts". Doesn't list stopped containers.+defaultListOpts :: ListOpts+defaultListOpts = ListOpts { all=False }++-- | Data type used for represneting the version of the docker engine+-- remote API.+data DockerVersion = DockerVersion {+ version :: Text+ , apiVersion :: ApiVersion+ , gitCommit :: Text+ , goVersion :: Text+ , os :: Text+ , arch :: Text+ , kernelVersion :: Text+ , buildTime :: Text+ } deriving (Show, Eq, Generic)+++instance ToJSON DockerVersion where+ toJSON = genericToJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++instance FromJSON DockerVersion where+ parseJSON = genericParseJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++instance FromJSON ContainerDetails where+ parseJSON v@(JSON.Object o) = do+ appArmor <- o .: "AppArmorProfile"+ args <- o .: "Args"+ config <- o .: "Config"+ created <- o .: "Created"+ driver <- o .: "Driver"+ hostConfig <- o .: "HostConfig"+ hostnamePath <- o .: "HostnamePath"+ hostsPath <- o .: "HostsPath"+ logPath <- o .: "LogPath"+ id <- parseJSON v+ image <- o .: "Image"+ mountLabel <- o .: "MountLabel"+ name <- o .: "Name"+ networkSettings <- o .: "NetworkSettings"+ path <- o .: "Path"+ processLabel <- o .: "ProcessLabel"+ resolveConfPath <- o .: "ResolvConfPath"+ restartCount <- o .: "RestartCount"+ state <- o .: "State"+ mounts <- o .: "Mounts"+ return $ ContainerDetails appArmor args config created driver hostConfig hostnamePath hostsPath logPath id image mountLabel name networkSettings path processLabel resolveConfPath restartCount state mounts+ parseJSON _ = fail "ContainerDetails is not an object"++instance ToJSON ContainerID where+ toJSON (ContainerID cid) = object ["Id" .= cid]++instance FromJSON ContainerID where+ parseJSON (JSON.Object o) = do+ cid <- o .: "Id"+ case toContainerID cid of+ Nothing ->+ fail "Invalid ContainerID"+ Just cid ->+ return cid+ parseJSON _ = fail "ContainerID is not an object."++instance ToJSON ImageID where+ toJSON (ImageID iid) = JSON.String iid++instance FromJSON ImageID where+ parseJSON (JSON.String t) = case toImageID t of+ Nothing ->+ fail "Invalid ImageID"+ Just iid ->+ return iid+ parseJSON _ = fail "ImageID is not an object."++-- | Data type used for representing the information of various ports that+-- a contianer may expose.+data ContainerPortInfo = ContainerPortInfo {+ ipAddressInfo :: Maybe Text+ , privatePortInfo :: Port+ , publicPortInfo :: Maybe Port+ , portTypeInfo :: Maybe PortType+ } deriving (Eq, Show)++instance FromJSON ContainerPortInfo where+ parseJSON (JSON.Object v) =+ ContainerPortInfo <$> (v .:? "IP")+ <*> (v .: "PrivatePort")+ <*> (v .:? "PublicPort")+ <*> (v .:? "Type")+ parseJSON _ = fail "ContainerPortInfo: Not a JSON object."++-- For inspecting container details.+-- | Data type used for parsing the network information of each container+-- when listing them.+data NetworkOptions = NetworkOptions {+-- ipamConfig :: Maybe Text -- Don't see in 1.24+-- , links :: Maybe Text -- Don't see in 1.24+-- , aliases :: Maybe Text -- Don't see in 1.24+ networkOptionsId :: Text+ , networkOptionsEndpointId :: Text+ , networkOptionsGateway :: Text+ , networkOptionsIpAddress :: Text -- Note: Parse this?+ , networkOptionsIpPrefixLen :: Int+ , networkOptionsIpV6Gateway :: Maybe Text+ , networkOptionsGlobalIPv6Address :: Maybe Text+ , networkOptionsGlobalIPv6PrefixLen :: Maybe Int+ , networkOptionsMacAddress :: Text+ } deriving (Eq, Show)++instance FromJSON NetworkOptions where+ parseJSON (JSON.Object o) = do+ networkId <- o .: "NetworkID"+ endpointId <- o .: "EndpointID"+ gateway <- o .: "Gateway"+ ip <- o .: "IPAddress"+ ipLen <- o .: "IPPrefixLen"+ ip6Gateway <- o .:? "IPv6Gateway"+ globalIP6 <- o .:? "GlobalIPv6Address"+ globalIP6Len <- o .:? "GlobalIPv6PrefixLen"+ mac <- o .: "MacAddress"+ return $ NetworkOptions networkId endpointId gateway ip ipLen ip6Gateway globalIP6 globalIP6Len mac+ parseJSON _ = fail "NetworkOptions is not an object"++-- TODO: Not sure what this is used for anymore.+data Network = Network NetworkMode NetworkOptions+ deriving (Eq, Show)++instance FromJSON [Network] where+ parseJSON (JSON.Object o) = HM.foldlWithKey' f (return []) o+ where+ f accM k' v' = do+ acc <- accM+ k <- parseJSON $ JSON.String k'+ v <- parseJSON v'+ return $ (Network k v):acc+ parseJSON _ = fail "Networks is not an object"++-- | Data type reprsenting the various network settings a container can have.+data NetworkSettings = NetworkSettings {+ networkSettingsBridge :: Text+ , networkSettingsSandboxId :: Text+ , networkSettingsHairpinMode :: Bool+ , networkSettingsLinkLocalIPv6Address :: Text+ , networkSettingsLinkLocalIPv6PrefixLen :: Int+ , networkSettingsPorts :: [PortBinding]+ , networkSettingsSandboxKey :: Text+ , networkSettingsSecondaryIPAddresses :: Maybe [Text] -- TODO: 1.24 spec is unclear+ , networkSettingsSecondaryIPv6Addresses :: Maybe [Text] -- TODO: 1.24 spec is unclear+ , networkSettingsEndpointID :: Text+ , networkSettingsGateway :: Text+ , networkSettingsGlobalIPv6Address :: Text+ , networkSettingsGlobalIPv6PrefixLen :: Int+ , networkSettingsIpAddress :: Text+ , networkSettingsIpPrefixLen :: Int+ , networkSettingsIpv6Gateway :: Text+ , networkSettingsMacAddress :: Text+ , networkSettingsNetworks :: [Network]+ }+ deriving (Eq, Show)++instance FromJSON NetworkSettings where+ parseJSON (JSON.Object o) = do+ bridge <- o .: "Bridge"+ sandbox <- o .: "SandboxID"+ hairpin <- o .: "HairpinMode"+ localIP6 <- o .: "LinkLocalIPv6Address"+ localIP6Len <- o .: "LinkLocalIPv6PrefixLen"+ ports <- o .: "Ports" -- .!= []+ sandboxKey <- o .: "SandboxKey"+ secondaryIP <- o .: "SecondaryIPAddresses"+ secondayIP6 <- o .: "SecondaryIPv6Addresses"+ endpointID <- o .: "EndpointID"+ gateway <- o .: "Gateway"+ globalIP6 <- o .: "GlobalIPv6Address"+ globalIP6Len <- o .: "GlobalIPv6PrefixLen"+ ip <- o .: "IPAddress"+ ipLen <- o .: "IPPrefixLen"+ ip6Gateway <- o .: "IPv6Gateway"+ mac <- o .: "MacAddress"+ networks <- o .: "Networks"+ return $ NetworkSettings bridge sandbox hairpin localIP6 localIP6Len ports sandboxKey secondaryIP secondayIP6 endpointID gateway globalIP6 globalIP6Len ip ipLen ip6Gateway mac networks+ parseJSON _ = fail "NetworkSettings is not an object."++-- | Data type used for parsing a list of containers.+data Container = Container+ { containerId :: ContainerID+ , containerNames :: [Text]+ , containerImageName :: Text+ , containerImageId :: ImageID+ , containerCommand :: Text+ , containerCreatedAt :: Int+ , containerStatus :: Status+ , containerPorts :: [ContainerPortInfo]+ , containerLabels :: [Label]+ , containerNetworks :: [Network]+ , containerMounts :: [Mount]+ } deriving (Show, Eq)++instance FromJSON Container where+ parseJSON (JSON.Object v) =+ Container <$> (v .: "Id")+ <*> (v .: "Names")+ <*> (v .: "Image")+ <*> (v .: "ImageID")+ <*> (v .: "Command")+ <*> (v .: "Created")+ <*> (v .: "Status")+ <*> (v .: "Ports")+ <*> (v .: "Labels")+ <*> (v .: "NetworkSettings" >>= parseNetworks)+ <*> (v .: "Mounts")+ where+ parseNetworks (JSON.Object v) =+ (v .: "Networks") >>= parseJSON+ parseNetworks _ = fail "Container NetworkSettings: Not a JSON object."++ parseJSON _ = fail "Container: Not a JSON object."++-- | Represents the status of the container life cycle.+data Status = Created | Restarting | Running | Paused | Exited | Dead+ deriving (Eq, Show, Generic)++instance FromJSON Status where+ parseJSON (JSON.String "running") = return Running+ parseJSON (JSON.String "created") = return Created -- Note: Guessing on the string values of these.+ parseJSON (JSON.String "restarting") = return Restarting+ parseJSON (JSON.String "paused") = return Paused+ parseJSON (JSON.String "exited") = return Exited+ parseJSON (JSON.String "dead") = return Dead+ parseJSON _ = fail "Unknown Status"++-- | Alias for representing a RepoDigest. We could newtype this and add+-- some validation.+type Digest = Text++-- | Container and Image Labels.+data Label = Label Name Value deriving (Eq, Show)+++-- If there are multiple lables with the same Name in the list+-- then the last one wins.+instance ToJSON [Label] where+ toJSON [] = emptyJsonObject+ toJSON (l:ls) = toJsonKeyVal (l:ls) key val+ where key (Label k _) = T.unpack k+ val (Label _ v) = v++instance FromJSON [Label] where+ parseJSON (JSON.Object o) = HM.foldlWithKey' f (return []) o+ where f accM k v = do+ acc <- accM+ value <- parseJSON v+ return $ (Label k value):acc+ parseJSON JSON.Null = return []+ parseJSON _ = fail "Failed to parse Labels. Not an object."++-- | Alias for Tags.+type Tag = Text++-- | Data type used for parsing information from a list of images.+data Image = DockerImage {+ imageId :: ImageID+ , imageCreated :: Integer+ , imageParentId :: Maybe ImageID+ , imageRepoTags :: [Tag]+ , imageRepoDigests :: [Digest]+ , imageSize :: Integer+ , imageVirtualSize :: Integer+ , imageLabels :: [Label]+ } deriving (Show, Eq, Generic)++-- | Helper function used for dropping the "image" prefix when serializing+-- the Image data type to and from json.+dropImagePrefix :: [a] -> [a]+dropImagePrefix = drop 5+++instance FromJSON Image where+ parseJSON (JSON.Object o) = do+ imageId <- o .: "Id"+ imageCreated <- o .: "Created"+ imageParentId <- o .:? "ParentId"+ imageRepoTags <- o .:? "RepoTags" .!= []+ imageRepoDigests <- o .:? "RepoDigests" .!= []+ imageSize <- o .: "Size"+ imageVirtualSize <- o .: "VirtualSize"+ imageLabels <- o .:? "Labels" .!= []+ return $ DockerImage imageId imageCreated imageParentId imageRepoTags imageRepoDigests imageSize imageVirtualSize imageLabels+ parseJSON _ = fail "Failed to parse DockerImage."+++-- | Options used for creating a Container.+data CreateOpts = CreateOpts {+ containerConfig :: ContainerConfig+ , hostConfig :: HostConfig+ } deriving (Eq, Show)++instance ToJSON CreateOpts where+ toJSON (CreateOpts cc hc) = do+ let ccJSON = toJSON cc+ let hcJSON = toJSON hc+ case ccJSON of+ JSON.Object (o :: HM.HashMap T.Text JSON.Value) -> do+ JSON.Object $ HM.insert "HostConfig" hcJSON o+ _ -> error "ContainerConfig is not an object." -- This should never happen.++-- | Container configuration used for creating a container with sensible+-- defaults.+defaultContainerConfig :: Text -> ContainerConfig+defaultContainerConfig imageName = ContainerConfig {+ hostname=Nothing+ , domainname=Nothing+ , user=Nothing+ , attachStdin=False+ , attachStdout=False+ , image=imageName+ , attachStderr=False+ , exposedPorts=[]+ , tty=False+ , openStdin=False+ , stdinOnce=False+ , env=[]+ , cmd=[]+ , volumes=[]+ , workingDir=Nothing+ , entrypoint=Nothing+ , networkDisabled=Nothing+ , macAddress=Nothing+ , labels=[]+ , stopSignal=SIGTERM+ }++-- | Default host confiratuon used for creating a container.+defaultHostConfig :: HostConfig+defaultHostConfig = HostConfig {+ binds=[]+ , containerIDFile=Nothing+ , logConfig=LogDriverConfig JsonFile []+ , networkMode=Bridge+ , portBindings=[]+ , restartPolicy=RestartOff+ , volumeDriver=Nothing+ , volumesFrom=[]+ , capAdd=[]+ , capDrop=[]+ , dns=[]+ , dnsOptions=[]+ , dnsSearch=[]+ , extraHosts=[]+ , ipcMode=Nothing+ , links=[]+ , oomScoreAdj=Nothing+ , privileged=False+ , publishAllPorts=False+ , readonlyRootfs=False+ , securityOpt=[]+ , shmSize=Nothing+ , resources=defaultContainerResources+ }++-- Default container resource contstraints (None).+defaultContainerResources :: ContainerResources+defaultContainerResources = ContainerResources {+ cpuShares=Nothing+ , blkioWeight=Nothing+ , blkioWeightDevice=Nothing+ , blkioDeviceReadBps=Nothing+ , blkioDeviceWriteBps=Nothing+ , blkioDeviceReadIOps=Nothing+ , blkioDeviceWriteIOps=Nothing+ , cpuPeriod=Nothing+ , cpusetCpus=Nothing+ , cpusetMems=Nothing+ , devices=[]+ , kernelMemory=Nothing+ , memory=Nothing+ , memoryReservation=Nothing+ , memorySwap=Nothing+ , oomKillDisable=False+ , ulimits=[]+ }++-- | Default create options when creating a container. You only need to+-- specify an image name and the rest is all sensible defaults.+defaultCreateOpts :: T.Text -> CreateOpts+defaultCreateOpts imageName = CreateOpts { containerConfig = defaultContainerConfig imageName, hostConfig = defaultHostConfig }++-- | Override the key sequence for detaching a container.+-- Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.+data DetachKeys = WithCtrl Char | WithoutCtrl Char | DefaultDetachKey deriving (Eq, Show)++-- | Options for starting a container.+data StartOpts = StartOpts { detachKeys :: DetachKeys } deriving (Eq, Show)++-- | Default options for staring a container.+defaultStartOpts :: StartOpts+defaultStartOpts = StartOpts { detachKeys = DefaultDetachKey }++-- | Options for deleting a container.+data DeleteOpts = DeleteOpts {+ deleteVolumes :: Bool -- ^ Automatically cleanup volumes that the container created as well.+ , force :: Bool -- ^ If the container is still running force deletion anyway.+ } deriving (Eq, Show)++-- | Default options for deleting a container. Most of the time we DON'T+-- want to delete the container's volumes or force delete it if it's+-- running.+defaultDeleteOpts :: DeleteOpts+defaultDeleteOpts = DeleteOpts { deleteVolumes = False, force = False }++-- | Timestamp alias.+type Timestamp = Integer++-- | Used for requesting N number of lines when tailing a containers log+-- output.+data TailLogOpt = Tail Integer | All deriving (Eq, Show)+++-- | Log options used when requesting the log output from a container.+data LogOpts = LogOpts {+ stdout :: Bool+ , stderr :: Bool+ , since :: Maybe Timestamp+ , timestamps :: Bool+ , tail :: TailLogOpt+ } deriving (Eq, Show)++-- | Sensible default for log options.+defaultLogOpts :: LogOpts+defaultLogOpts = LogOpts { stdout = True+ , stderr = True+ , since = Nothing+ , timestamps = True+ , tail = All+ }++-- TOOD: Add support for SELinux Volume labels (eg. "ro,z" or "ro/Z")+-- | Set permissions on volumes that you mount in the container.+data VolumePermission = ReadWrite | ReadOnly deriving (Eq, Show, Generic)++instance ToJSON VolumePermission where+ toJSON ReadWrite = "rw"+ toJSON ReadOnly = "ro"++instance FromJSON VolumePermission where+ parseJSON "rw" = return ReadWrite+ parseJSON "ro" = return ReadOnly+ parseJSON _ = fail "Failed to parse VolumePermission"++-- | Used for marking a directory in the container as "exposed" hence+-- taking it outside of the COW filesystem and making it mountable+-- in other containers using "VolumesFrom". The volume usually get's+-- created somewhere in @/var/lib/docker/volumes@ (depending on the volume+-- driver used).+-- The CLI example is:+--+-- @+-- docker run --name app -v \/opt\/data -it myapp:latest+-- docker run --name app2 --volumes-from app \/bin\/bash -c "ls -l \/opt\/data"+-- @+newtype Volume = Volume FilePath deriving (Eq, Show)++instance ToJSON [Volume] where+ toJSON [] = emptyJsonObject+ toJSON (v:vs) = toJsonKey (v:vs) getKey+ where getKey (Volume v) = v++instance FromJSON [Volume] where+ parseJSON (JSON.Object o) = return $ map (Volume . T.unpack) $ HM.keys o+ parseJSON (JSON.Null) = return []+ parseJSON _ = fail "Volume is not an object"+++data Bind = Bind { hostSrc :: Text+ , containerDest :: Text+ , volumePermission :: Maybe VolumePermission+ } deriving (Eq, Show)++instance FromJSON Bind where+ parseJSON (JSON.String t) = case T.split (== ':') t of+ [src, dest] -> return $ Bind src dest Nothing+ [src, dest, "rw"] -> return $ Bind src dest $ Just ReadWrite+ [src, dest, "ro"] -> return $ Bind src dest $ Just ReadOnly+ _ -> fail "Could not parse Bind"+ parseJSON _ = fail "Bind is not a string"++data Device = Device {+ pathOnHost :: FilePath+ , pathInContainer :: FilePath+ , cgroupPermissions :: Text+ } deriving (Eq, Show, Generic)++instance ToJSON Device where+ toJSON = genericToJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++instance FromJSON Device where+ parseJSON = genericParseJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++type ContainerName = Text++data VolumeFrom = VolumeFrom ContainerName (Maybe VolumePermission) deriving (Eq, Show)++instance FromJSON VolumeFrom where+ parseJSON (JSON.String t) = case T.split (== ':') t of+ [vol] -> return $ VolumeFrom vol Nothing+ [vol, "rw"] -> return $ VolumeFrom vol $ Just ReadWrite+ [vol, "ro"] -> return $ VolumeFrom vol $ Just ReadOnly+ _ -> fail "Could not parse VolumeFrom"+ parseJSON _ = fail "VolumeFrom is not a string"++instance ToJSON VolumeFrom where+ toJSON (VolumeFrom n p) = case p of+ Nothing -> toJSON $ n <> ":" <> "rw"+ Just per -> toJSON $ n <> ":" <> (T.pack $ show per)++instance ToJSON Bind where+ toJSON (Bind src dest mode) = toJSON $ case mode of+ Nothing -> T.concat[src, ":", dest]+ Just m -> T.concat[src, ":", dest, ":", (T.pack $ show m)]++data Link = Link Text (Maybe Text) deriving (Eq, Show)++instance FromJSON Link where+ parseJSON (JSON.String t) = case T.split (== ':') t of+ [f] -> return $ Link f Nothing+ [f,s] -> return $ Link f $ Just s+ _ -> fail "Could not parse Link"+ parseJSON _ = fail "Link is not a string"++instance ToJSON Link where+ toJSON (Link n1 n2) = toJSON $ case n2 of+ Nothing -> T.concat[n1, ":", n1] -- use same name in container+ Just n -> T.concat[n1, ":", n] -- used specified name in container++-- { "Type": "<driver_name>", "Config": {"key1": "val1"} }+data LogDriverType = JsonFile | Syslog | Journald | Gelf | Fluentd | AwsLogs | Splunk | Etwlogs | LoggingDisabled deriving (Eq, Show)++instance FromJSON LogDriverType where+ parseJSON (JSON.String "json-file") = return JsonFile+ parseJSON (JSON.String "syslog") = return Syslog+ parseJSON (JSON.String "journald") = return Journald+ parseJSON (JSON.String "gelf") = return Gelf+ parseJSON (JSON.String "fluentd") = return Fluentd+ parseJSON (JSON.String "awslogs") = return AwsLogs+ parseJSON (JSON.String "splunk") = return Splunk+ parseJSON (JSON.String "etwlogs") = return Etwlogs+ parseJSON (JSON.String "none") = return LoggingDisabled+ parseJSON _ = fail "Unknown LogDriverType"++instance ToJSON LogDriverType where+ toJSON JsonFile = JSON.String "json-file"+ toJSON Syslog = JSON.String "syslog"+ toJSON Journald = JSON.String "journald"+ toJSON Gelf = JSON.String "gelf"+ toJSON Fluentd = JSON.String "fluentd"+ toJSON AwsLogs = JSON.String "awslogs"+ toJSON Splunk = JSON.String "splunk"+ toJSON Etwlogs = JSON.String "etwlogs"+ toJSON LoggingDisabled = JSON.String "none"++data LogDriverOption = LogDriverOption Name Value deriving (Eq, Show)++instance ToJSON [LogDriverOption] where+ toJSON [] = emptyJsonObject+ toJSON (o:os) = toJsonKeyVal (o:os) key val+ where key (LogDriverOption n _) = T.unpack n+ val (LogDriverOption _ v) = v++instance FromJSON [LogDriverOption] where+ parseJSON (JSON.Object o) = HM.foldlWithKey' f (return []) o+ where f accM k v = do+ acc <- accM+ value <- parseJSON v+ return $ (LogDriverOption k value):acc+ parseJSON JSON.Null = return []+ parseJSON _ = fail "Failed to parse LogDriverOptions"++data LogDriverConfig = LogDriverConfig LogDriverType [LogDriverOption] deriving (Eq, Show)++instance ToJSON LogDriverConfig where+ toJSON (LogDriverConfig driverType []) = object ["Type" .= driverType]+ toJSON (LogDriverConfig driverType driverOptions) = object ["Type" .= driverType, "Config" .= driverOptions]++instance FromJSON LogDriverConfig where+ parseJSON (JSON.Object o) = do+ typ <- o .: "Type"+ opts <- o .: "Config"+ return $ LogDriverConfig typ opts+ parseJSON _ = fail "LogDriverConfig is not an object"++-- TODO: Add container:<name|id> mode+data NetworkMode = Bridge | Host | NetworkDisabled+ deriving (Eq, Show, Ord)++instance FromJSON NetworkMode where+ parseJSON (JSON.String "bridge") = return Bridge+ parseJSON (JSON.String "host") = return Host -- Note: Guessing on these.+ parseJSON (JSON.String "none") = return NetworkDisabled+ parseJSON _ = fail "Unknown NetworkMode"++instance ToJSON NetworkMode where+ toJSON Bridge = JSON.String "bridge"+ toJSON Host = JSON.String "host"+ toJSON NetworkDisabled = JSON.String "none"++data PortType = TCP | UDP deriving (Eq, Generic, Read, Ord)++instance Show PortType where+ show TCP = "tcp"+ show UDP = "udp"++instance ToJSON PortType where+ toJSON TCP = "tcp"+ toJSON UDP = "udp"++instance FromJSON PortType where+ parseJSON val = case val of+ "tcp" -> return TCP+ "udp" -> return UDP+ _ -> fail "PortType: Invalid port type."++-- newtype NetworkInterface = NetworkInterface Text deriving (Eq, Show)+--+-- instance FromJSON NetworkInterface where+-- parseJSON (JSON.String v) = return $ NetworkInterface v+-- parseJSON _ = fail "Network interface is not a string."+--+-- instance ToJSON NetworkInterface where+-- toJSON (NetworkInterface i) = JSON.String i++-- | This datastructure models mapping a Port from the container onto the+-- host system s that the service running in the container can be accessed from+-- the outside world. We either map a port onto all interfaces (default) or onto a specific+-- interface like `127.0.0.1`.+-- __NOTE__: We should disallow duplicate port bindings as the ToJSON+-- instance will only send the last one.+-- { <port>/<protocol>: [{ "HostPort": "<port>" }] }+data PortBinding = PortBinding {+ containerPort :: Port+ , portType :: PortType+ , hostPorts :: [HostPort]+ } deriving (Eq, Show)++portAndType2Text :: Port -> PortType -> Text+portAndType2Text p t = (T.pack $ show p) <> "/" <> (T.pack $ show t)+++-- | A helper function to more easily add a bind mount to existing+-- "CreateOpts" records.+addBind :: Bind -> CreateOpts -> CreateOpts+addBind b c = c{hostConfig=hc{binds=obs <> [b]}}+ where hc = hostConfig c+ obs = binds $ hostConfig c++-- | Helper function for adding a Command to and existing+-- CreateOpts record.+setCmd :: Text -> CreateOpts -> CreateOpts+setCmd ccmd c = c{containerConfig=cc{cmd=[ccmd]}}+ where cc = containerConfig c++-- | Helper function for adding a "Link" to and existing+-- CreateOpts record.+addLink :: Link -> CreateOpts -> CreateOpts+addLink l c = c{hostConfig=hc{links=ols <> [l]}}+ where hc = hostConfig c+ ols = links $ hostConfig c++-- | Helper function for adding a "Volume" to and existing+-- CreateOpts record.+addVolume :: Volume -> CreateOpts -> CreateOpts+addVolume v c = c{containerConfig=cc{volumes=oldvs <> [v]}}+ where cc = containerConfig c+ oldvs = volumes cc++-- | Helper function for adding a "VolumeFrom" to and existing+-- CreateOpts record.+addVolumeFrom :: VolumeFrom -> CreateOpts -> CreateOpts+addVolumeFrom vf c = c{hostConfig=hc{volumesFrom=oldvfs <> [vf]}}+ where hc = hostConfig c+ oldvfs = volumesFrom hc++-- | A convenience function that adds PortBindings to and exiting+-- "CreateOpts" record. Useful with 'defaultCreateOpts'+-- Example:+--+-- >>> let pb = PortBinding 80 TCP [HostPort "0.0.0.0" 8000]+-- >>> addPortBinding pb $ defaultCreateOpts "nginx:latest"+addPortBinding :: PortBinding -> CreateOpts -> CreateOpts+addPortBinding pb c = c{hostConfig=hc{portBindings=pbs <> [pb]}}+ where hc = hostConfig c+ pbs = portBindings $ hostConfig c++-- | Helper function for adding a "ExposedPort" to and existing+-- CreateOpts record.+addExposedPort :: ExposedPort -> CreateOpts -> CreateOpts+addExposedPort ep c = c{containerConfig=cc{exposedPorts=oldeps <> [ep]}}+ where cc = containerConfig c+ oldeps = exposedPorts cc+++instance FromJSON [PortBinding] where+ parseJSON (JSON.Object o) = HM.foldlWithKey' f (return []) o+ where+ f accM k v = case T.split (== '/') k of+ [port', portType'] -> do+ port <- parseIntegerText port'+ portType <- parseJSON $ JSON.String portType'+ acc <- accM+ hps <- parseJSON v+ return $ (PortBinding port portType hps):acc+ _ -> fail "Could not parse PortBindings"+ parseJSON (JSON.Null) = return []+ parseJSON _ = fail "PortBindings is not an object"++instance ToJSON [PortBinding] where+ toJSON [] = emptyJsonObject+ toJSON (p:ps) = toJsonKeyVal (p:ps) key val+ where key p = T.unpack $ portAndType2Text (containerPort p) (portType p)+ val p = hostPorts p++data HostPort = HostPort {+ hostIp :: Text+ , hostPost :: Port+ }+ deriving (Eq, Show)++instance ToJSON HostPort where+ toJSON (HostPort i p) = object ["HostPort" .= show p, "HostIp" .= i]++instance FromJSON HostPort where+ parseJSON (JSON.Object o) = do+ p <- o .: "HostPort" >>= parseIntegerText+ i <- o .: "HostIp"+ return $ HostPort i p+ parseJSON _ = fail "HostPort is not an object."++-- { "Name": "on-failure" , "MaximumRetryCount": 2}+type RetryCount = Integer+data RestartPolicy = RestartAlways | RestartUnlessStopped | RestartOnFailure RetryCount | RestartOff deriving (Eq, Show)++instance FromJSON RestartPolicy where+ parseJSON (JSON.Object o) = do+ (name :: Text) <- o .: "Name"+ case name of+ "always" -> return RestartAlways+ "unless-stopped" -> return RestartUnlessStopped+ "on-failure" -> do+ retry <- o .: "MaximumRetryCount"+ return $ RestartOnFailure retry+ "off" -> return RestartOff+ _ -> fail "Could not parse RestartPolicy"+ parseJSON _ = fail "RestartPolicy is not an object"++instance ToJSON RestartPolicy where+ toJSON RestartAlways = object ["Name" .= JSON.String "always"]+ toJSON RestartUnlessStopped = object ["Name" .= JSON.String "unless-stopped"]+ toJSON (RestartOnFailure c) = object ["Name" .= JSON.String "on-failure", "MaximumRetryCount" .= c]+ toJSON RestartOff = object ["Name" .= JSON.String "off"]++data Isolation = Default | Process | Hyperv deriving (Eq, Show)++newtype UTSMode = UTSMode Text deriving (Eq, Show)++-- TODO: Add Tmpfs : List of tmpfs (mounts) used for the container+-- TODO: Add UTSMode : UTS namespace to use for the container+-- TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container+data HostConfig = HostConfig+ { binds :: [Bind]+ , containerIDFile :: Maybe FilePath -- 1.24: Only in responses, not create+ , logConfig :: LogDriverConfig+ , networkMode :: NetworkMode+ , portBindings :: [PortBinding]+ , restartPolicy :: RestartPolicy+ , volumeDriver :: Maybe Text+ , volumesFrom :: [VolumeFrom]+ , capAdd :: [Text]+ , capDrop :: [Text]+ , dns :: [Text]+ , dnsOptions :: [Text]+ , dnsSearch :: [Text]+ , extraHosts :: [Text]+ -- , groupAdd :: [Integer] -- 1.24: Missing from inspecting container details... Going to omit for now.+ , ipcMode :: Maybe Text -- 1.24: Only in inspect, not create+ , links :: [Link]+ , oomScoreAdj :: Maybe Integer+ -- , pidMode :: Text -- 1.24: Don't see pidMode, just pidsLimit+ , privileged :: Bool+ , publishAllPorts :: Bool+ , readonlyRootfs :: Bool+ , securityOpt :: [Text]+ -- , utsMode :: UTSMode -- 1.24: Don't see this+ , shmSize :: Maybe Integer+ -- , consoleSize :: Integer -- 1.24: Don't see this+ -- , isolation :: Isolation -- 1.24: Don't see this+ , resources :: ContainerResources+ } deriving (Eq, Show, Generic)++instance FromJSON HostConfig where+ parseJSON v@(JSON.Object o) = HostConfig+ <$> o .: "Binds"+ <*> o .: "ContainerIDFile"+ <*> o .: "LogConfig"+ <*> o .: "NetworkMode"+ <*> o .: "PortBindings"+ <*> o .: "RestartPolicy"+ <*> o .: "VolumeDriver"+ <*> o .: "VolumesFrom"+ <*> o .: "CapAdd"+ <*> o .: "CapDrop"+ <*> o .: "Dns"+ <*> o .: "DnsOptions"+ <*> o .: "DnsSearch"+ <*> o .: "ExtraHosts"+ <*> o .: "IpcMode"+ <*> o .:? "Links" .!= []+ <*> o .: "OomScoreAdj"+ <*> o .: "Privileged"+ <*> o .: "PublishAllPorts"+ <*> o .: "ReadonlyRootfs"+ <*> o .: "SecurityOpt"+ <*> o .: "ShmSize"+ <*> parseJSON v+ parseJSON _ = fail "HostConfig is not an object."++instance ToJSON HostConfig where+ toJSON HostConfig{..} =+ let arr = [+ "Binds" .= binds+ , "ContainerIDFile" .= containerIDFile+ , "LogConfig" .= logConfig+ , "NetworkMode" .= networkMode+ , "PortBindings" .= portBindings+ , "RestartPolicy" .= restartPolicy+ , "VolumeDriver" .= volumeDriver+ , "VolumesFrom" .= volumesFrom+ , "CapAdd" .= capAdd+ , "CapDrop" .= capDrop+ , "Dns" .= dns+ , "DnsOptions" .= dnsOptions+ , "DnsSearch" .= dnsSearch+ , "ExtraHosts" .= extraHosts+ , "IpcMode" .= ipcMode+ , "Links" .= links+ , "OomScoreAdj" .= oomScoreAdj+ , "Privileged" .= privileged+ , "PublishAllPorts" .= publishAllPorts+ , "ReadonlyRootfs" .= readonlyRootfs+ , "SecurityOpt" .= securityOpt+ , "ShmSize" .= shmSize+ ]+ in+ object $ arr <> ( resourcesArr resources)++ where+ -- JP: Not sure if this is better than a separate ToJSON instance with a bunch of `HM.insert`s.+ resourcesArr ContainerResources{..} = [+ "CpuShares" .= cpuShares+ , "BlkioWeight" .= blkioWeight+ , "BlkioWeightDevice" .= blkioWeightDevice+ , "BlkioDeviceReadBps" .= blkioDeviceReadBps+ , "BlkioDeviceWriteBps" .= blkioDeviceWriteBps+ , "BlkioDeviceReadIOps" .= blkioDeviceReadIOps+ , "BlkioDeviceWriteIOps" .= blkioDeviceWriteIOps+ , "CpuPeriod" .= cpuPeriod+ , "CpusetCpus" .= cpusetCpus+ , "CpusetMems" .= cpusetMems+ , "Devices" .= devices+ , "KernelMemory" .= kernelMemory+ , "Memory" .= memory+ , "MemoryReservation" .= memoryReservation+ , "MemorySwap" .= memorySwap+ , "OomKillDisable" .= oomKillDisable+ , "Ulimits" .= ulimits+ ]+++++-- { "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }+-- { "Name": "nofile", "Soft": 1024, "Hard": 2048 }+data Ulimit = Ulimit {+ ulimitName :: Text+ , ulimitSoft :: Integer+ , ulimitHard :: Integer+ } deriving (Eq, Show, Generic)++instance FromJSON Ulimit where+ parseJSON = genericParseJSON defaultOptions {+ fieldLabelModifier = drop 5}++instance ToJSON Ulimit where+ toJSON = genericToJSON defaultOptions {+ fieldLabelModifier = drop 5}++data DeviceWeight = DeviceWeight {+ deviceWeightPath :: FilePath+ , deviceWeightWeight :: Text+ }+ deriving (Show, Eq)++instance FromJSON DeviceWeight where+ parseJSON (JSON.Object o) = DeviceWeight+ <$> o .: "Path"+ <*> o .: "Weight"+ parseJSON _ = fail "DeviceWeight is not an object."++instance ToJSON DeviceWeight where+ toJSON (DeviceWeight p w) = object [+ "Path" .= p+ , "Weight" .= w+ ]++data DeviceRate = DeviceRate {+ deviceRatePath :: FilePath+ , deviceRateRate :: Text+ }+ deriving (Show, Eq)++instance FromJSON DeviceRate where+ parseJSON (JSON.Object o) = DeviceRate+ <$> o .: "Path"+ <*> o .: "Rate"+ parseJSON _ = fail "DeviceRate is not an object."++instance ToJSON DeviceRate where+ toJSON (DeviceRate p r) = object [+ "Path" .= p+ , "Rate" .= r+ ]++data MemoryConstraintSize = B | MB | GB deriving (Eq, Show)++data MemoryConstraint = MemoryConstraint Integer MemoryConstraintSize deriving (Eq, Show)++instance ToJSON MemoryConstraint where+ toJSON (MemoryConstraint x B) = toJSON x+ toJSON (MemoryConstraint x MB) = toJSON $ x * 1024 * 1024+ toJSON (MemoryConstraint x GB) = toJSON $ x * 1024 * 1024 * 1024++instance FromJSON MemoryConstraint where+ parseJSON (JSON.Number x) = case (floatingOrInteger x) of+ Left (_ :: Double) -> fail "Failed to parse MemoryConstraint"+ Right i -> return $ MemoryConstraint i B+ -- The docker daemon will always return the number as bytes (integer), regardless of how we set them (using MB or GB)+ parseJSON _ = fail "Failed to parse MemoryConstraint"++data ContainerResources = ContainerResources {+ cpuShares :: Maybe Integer+ -- , cgroupParent :: Text -- 1.24: Missing from inspecting container details... Going to omit for now.+ , blkioWeight :: Maybe Integer+ , blkioWeightDevice :: Maybe [DeviceWeight]+ , blkioDeviceReadBps :: Maybe [DeviceRate] -- TODO: Not Text+ , blkioDeviceWriteBps :: Maybe [DeviceRate] -- TODO: Not Text+ , blkioDeviceReadIOps :: Maybe [DeviceRate] -- TODO: Not Text+ , blkioDeviceWriteIOps :: Maybe [DeviceRate] -- TODO: Not Text+ , cpuPeriod :: Maybe Integer+ -- , cpuQuota :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.+ , cpusetCpus :: Maybe Text+ , cpusetMems :: Maybe Text+ , devices :: [Device]+ -- , diskQuota :: Integer -- Don't see this ins 1.24.+ , kernelMemory :: Maybe MemoryConstraint+ , memory :: Maybe MemoryConstraint+ , memoryReservation :: Maybe MemoryConstraint+ , memorySwap :: Maybe MemoryConstraint+ -- , memorySwappiness :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.+ , oomKillDisable :: Bool+ -- , pidsLimit :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.+ , ulimits :: [Ulimit]+ -- TODO: Missing from 1.24+ -- StorageOpt :: [(Text, Text)]+ -- VolumeDriver :: ??+ -- EndpointsConfig :: ??+ -- TODO: Only in inspect container in 1.24+ -- CpuPercent :: Int ++ -- MaximumIOps :: Int ++ -- MaximumIOBps :: Int ++ -- LxcConf :: [??] ++ } deriving (Eq, Show, Generic)++-- instance ToJSON ContainerResources where+-- toJSON = genericToJSON defaultOptions {+-- fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++instance FromJSON ContainerResources where+ parseJSON = genericParseJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++type Port = Integer++type Name = Text+type Value = Text++data EnvVar = EnvVar Name Value+ deriving (Eq, Show)++instance FromJSON EnvVar where+ parseJSON (JSON.String env) =+ let (n, v') = T.break (== '=') env in+ let v = T.drop 1 v' in+ return $ EnvVar n v+ parseJSON _ = fail "EnvVar is not a string"++instance ToJSON EnvVar where+ toJSON (EnvVar n v) = object [n .= v]++-- | ExposedPort represents a port (and it's type)+-- that a container should expose to other containers or the host system.+-- `NOTE`: This does not automatically expose the port onto the host+-- system but rather it just tags it. It's best to be used with+-- the PublishAllPorts flag. It is also useful for+-- the daemon to know which Environment variables to+-- inject into a container linking to our container.+-- Example linking a Postgres container named db would inject the following+-- environment variables automatically if we set the corresponding+--+-- ExposedPort:+--+-- @+-- DB_PORT_5432_TCP_PORT="5432"+-- DB_PORT_5432_TCP_PROTO="tcp"+-- DB_PORT_5432_TCP="tcp://172.17.0.1:5432"+-- @+data ExposedPort = ExposedPort Port PortType deriving (Eq, Show)++instance FromJSON [ExposedPort] where+ parseJSON (JSON.Object o) = HM.foldlWithKey' f (return []) o+ where+ f accM k _ = case T.split (== '/') k of+ [port', portType'] -> do+ port <- parseIntegerText port'+ portType <- parseJSON $ JSON.String portType'+ acc <- accM+ return $ (ExposedPort port portType):acc+ _ -> fail "Could not parse ExposedPorts"+ parseJSON (JSON.Null) = return []+ parseJSON _ = fail "ExposedPorts is not an object"++instance ToJSON [ExposedPort] where+ toJSON [] = emptyJsonObject+ toJSON (p:ps) = toJsonKey (p:ps) key+ where key (ExposedPort p t) = show p <> slash <> show t+ slash = T.unpack "/"++data ContainerConfig = ContainerConfig {+ hostname :: Maybe Text+ , domainname :: Maybe Text+ , user :: Maybe Text+ , attachStdin :: Bool+ , attachStdout :: Bool+ , attachStderr :: Bool+ , exposedPorts :: [ExposedPort]+ -- , publishService :: Text -- Don't see this in 1.24+ , tty :: Bool+ , openStdin :: Bool+ , stdinOnce :: Bool+ , env :: [EnvVar]+ , cmd :: [Text]+ -- , argsEscaped :: Bool -- Don't see this in 1.24+ , image :: Text+ , volumes :: [Volume]+ , workingDir :: Maybe FilePath+ , entrypoint :: Maybe Text -- Can be null?+ , networkDisabled :: Maybe Bool -- Note: Should we expand the JSON instance and take away the Maybe? Null is False?+ , macAddress :: Maybe Text+ -- , onBuild :: Maybe Text -- For 1.24, only see this in the inspect response.+ , labels :: [Label]+ , stopSignal :: Signal+ } deriving (Eq, Show, Generic)++instance ToJSON ContainerConfig where+ toJSON = genericToJSON defaultOptions {+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}++instance FromJSON ContainerConfig where+ parseJSON (JSON.Object o) = do+ hostname <- o .:? "Hostname"+ domainname <- o .:? "Domainname"+ user <- o .:? "User"+ attachStdin <- o .: "AttachStdin"+ attachStdout <- o .: "AttachStdout"+ attachStderr <- o .: "AttachStderr"+ exposedPorts <- o .:? "ExposedPorts" .!= []+ tty <- o .: "Tty"+ openStdin <- o .: "OpenStdin"+ stdinOnce <- o .: "StdinOnce"+ env <- o .: "Env"+ cmd <- o .: "Cmd"+ image <- o .: "Image"+ volumes <- o .: "Volumes"+ workingDir <- o .:? "WorkingDir"+ entrypoint <- o .:? "Entrypoint"+ networkDisabled <- o .:? "networkDisabled"+ macAddress <- o .:? "MacAddress"+ labels <- o .:? "Labels" .!= []+ stopSignal <- o .: "StopSignal"+ return $ ContainerConfig hostname domainname user attachStdin attachStdout attachStderr exposedPorts tty openStdin stdinOnce env cmd image volumes workingDir entrypoint networkDisabled+ macAddress labels stopSignal+ parseJSON _ = fail "NetworkSettings is not an object."++parseIntegerText :: (Monad m) => Text -> m Integer+parseIntegerText t = case readMaybe $ T.unpack t of+ Nothing ->+ fail "Could not parse Integer"+ Just i ->+ return i++-- | Helper function for converting a data type [a] to a json dictionary+-- like so {"something": {}, "something2": {}}+toJsonKey :: Foldable t => t a -> (a -> String) -> JSON.Value+toJsonKey vs getKey = JSON.Object $ foldl f HM.empty vs+ where f acc x = HM.insert (T.pack $ getKey x) (JSON.Object HM.empty) acc++-- | Helper function for converting a data type [a] to a json dictionary+-- like so {"something": "val1", "something2": "val2"}+toJsonKeyVal :: (Foldable t, JSON.ToJSON r) => t a -> (a -> String) -> (a -> r) -> JSON.Value+toJsonKeyVal vs getKey getVal = JSON.Object $ foldl f HM.empty vs+ where f acc x = HM.insert (T.pack $ getKey x) (toJSON $ getVal x) acc++-- | Helper function that return an empty dictionary "{}"+emptyJsonObject :: JSON.Value+emptyJsonObject = JSON.Object HM.empty+
tests/tests.hs view
@@ -1,62 +1,138 @@-module Main where+{-# LANGUAGE OverloadedStrings #-} -import Network.Docker-import Network.Docker.Types+module Main where import qualified Test.QuickCheck.Monadic as QCM import Test.Tasty import Test.Tasty.HUnit-import qualified Test.Tasty.QuickCheck as QC+import Test.Tasty.QuickCheck (testProperty) import Control.Concurrent (threadDelay)+import Control.Lens ((^.), (^?))+import Control.Monad.IO.Class+import Control.Monad.Trans.Class (lift)+import qualified Data.Aeson as JSON+import Data.Aeson.Lens (key, _Object, _String, _Value) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy as BL+import qualified Data.HashMap.Strict as HM+import Data.Int (Int)+import qualified Data.Map as M import Data.Maybe (fromJust, isJust)-import Data.Text (unpack)+import Data.Monoid+import Data.Text (Text, unpack)+import Network.Connection (TLSSettings (..))+import Network.HTTP.Client (newManager)+import Network.HTTP.Client.TLS import Network.HTTP.Types.Status import System.Process (system) -opts = defaultClientOpts { baseUrl = "http://127.0.0.1:2375/" }+import Docker.Client -test_image_name = "docker-hs-test"+-- opts = defaultClientOpts +testImageName = "docker-hs-test"+ toStrict1 = B.concat . BL.toChunks -checkDockerVersion :: IO ()-checkDockerVersion = do v <- getDockerVersion opts- assert $ isJust v+runDocker f = do+ -- let opts = defaultClientOpts {baseUrl = "https://127.0.0.1:2376/"}+ -- params' <- clientParamsWithClientAuthentication "127.0.0.1" 2376 "~/.docker/test-client-key.pem" "~/.docker/test-client-cert.pem" >>= fromRight+ -- params <- clientParamsSetCA params' "~/.docker/test-ca.pem"+ -- let settings = mkManagerSettings (TLSSettings params) Nothing+ -- mgr <- newManager settings+ runDockerT (defaultClientOpts, defaultHttpHandler) f -findTestImage :: IO ()-findTestImage = do images <- getDockerImages opts- let x = fmap (filter ((== [test_image_name++":latest"]) . _repoTags)) images- assert $ fmap length x == Just 1+testDockerVersion :: IO ()+testDockerVersion = runDocker $ do+ v <- getDockerVersion+ lift $ assert $ isRight v -runAndReadLog :: IO ()-runAndReadLog = do containerId <- createContainer opts (defaultCreateOpts {_image = test_image_name++":latest"})- assert $ isJust containerId- let c = unpack $ fromJust containerId- status1 <- startContainer opts c defaultStartOpts- threadDelay 300000 -- give 300ms for the application to finish- assert $ status1 == status204- status2 <- killContainer opts c- logs <- getContainerLogs opts c- assert $ status2 == status204- assert $ (C.pack "123") `C.isInfixOf` (toStrict1 logs)- status3 <- deleteContainer opts c- assert $ status3 == status204+testFindImage :: IO ()+testFindImage = runDocker $ do+ images <- listImages defaultListOpts >>= fromRight+ let xs = filter ((== [imageFullName]) . imageRepoTags) images+ lift $ assert $ length xs == 1+ where imageFullName = testImageName <> ":latest" +testRunAndReadLog :: IO ()+testRunAndReadLog = runDocker $ do+ containerId <- createContainer (defaultCreateOpts (testImageName <> ":latest")) Nothing+ c <- fromRight containerId+ status1 <- startContainer defaultStartOpts c+ _ <- inspectContainer c >>= fromRight+ lift $ threadDelay 300000 -- give 300ms for the application to finish+ lift $ assert $ status1 == Right ()+ status2 <- killContainer SIGTERM c+ logs <- getContainerLogs defaultLogOpts c >>= fromRight+ lift $ assert $ status2 == Right ()+ lift $ assert $ (C.pack "123") `C.isInfixOf` (toStrict1 logs)+ status3 <- deleteContainer (DeleteOpts True True) c+ lift $ assert $ status3 == Right () -tests :: TestTree-tests = testGroup "Metrics tests" [- testCase "Get docker version" checkDockerVersion,- testCase "Find image by name" findTestImage,- testCase "Run a dummy container and read its log" runAndReadLog]+testLogDriverOptionsJson :: TestTree+testLogDriverOptionsJson = testGroup "Testing LogDriverOptions JSON" [ test1, test2, test3 ]+ where+ test1 = testCase "Driver option 1" $ assert $ JSON.toJSON sample ^. key key1 . _String == val1+ test2 = testCase "Driver option 2" $ assert $ JSON.toJSON sample ^. key key2 . _String == val2+ test3 = testCase "Test override" $ assert $ JSON.toJSON sample2 ^. key key1 . _String == "override"+ sample = [LogDriverOption key1 val1, LogDriverOption key2 val2]+ sample2 = [LogDriverOption key1 val1, LogDriverOption key1 "override"]+ key1 = "some-key"+ val1 = "some-val"+ key2 = "some-key2"+ val2 = "some-key2" -setup :: IO()-setup = system ("docker build -t "++test_image_name++" tests") >> return ()+testExposedPortsJson :: TestTree+testExposedPortsJson = testGroup "Testing ExposedPorts JSON" [ testTCP, testUDP ]+ where+ testTCP = testCase "tcp port" $ assert $ JSON.toJSON sampleEP ^. key "80/tcp" . _Object == HM.empty+ testUDP = testCase "udp port" $ assert $ JSON.toJSON sampleEP ^. key "1337/tcp" . _Object == HM.empty+ sampleEP = [ExposedPort 80 TCP, ExposedPort 1337 UDP] -main :: IO()+testLabelsJson :: TestTree+testLabelsJson = testGroup "Testing Labels JSON" [ testLS1, testLS2, testOverride ]+ where+ testLS1 = testCase "test label key1" $ assert $ JSON.toJSON sampleLS ^. key key1 . _String == val1+ testLS2 = testCase "test label key2" $ assert $ JSON.toJSON sampleLS ^. key key2 . _String == val2+ testOverride = testCase "test label override" $ assert $ JSON.toJSON [Label key1 val1, Label key1 "override"] ^. key key1 . _String == "override"+ sampleLS = [Label key1 val1, Label key2 val2]+ key1 = "com.example.some-label"+ val1 = "some-value"+ key2 = "something"+ val2 = "value"++testVolumesJson :: TestTree+testVolumesJson = testGroup "Testing Volumes JSON" [ testSample1, testSample2 ]+ where+ testSample1 = testCase "Test exposing volume: /tmp" $ assert $ JSON.toJSON sampleVolumes ^. key "/tmp" . _Object == HM.empty+ testSample2 = testCase "Test exposing volume: /opt" $ assert $ JSON.toJSON sampleVolumes ^. key "/opt" . _Object == HM.empty+ sampleVolumes = [Volume "/tmp", Volume "/opt"]++integrationTests :: TestTree+integrationTests = testGroup "Integration Tests" [+ testCase "Get docker version" testDockerVersion,+ testCase "Find image by name" testFindImage,+ testCase "Run a dummy container and read its log" testRunAndReadLog]++jsonTests :: TestTree+jsonTests = testGroup "JSON Tests" [testExposedPortsJson, testVolumesJson, testLabelsJson, testLogDriverOptionsJson]++setup :: IO ()+setup = system ("docker build -t "++unpack testImageName++" tests") >> return ()++isRight (Left _) = False+isRight (Right _) = True++fromRight :: (MonadIO m, Show l) => Either l r -> m r+fromRight (Left l) = do+ liftIO $ assertFailure $ "Left: " ++ show l+ undefined+fromRight (Right r) = return r++main :: IO () main = do setup- defaultMain tests+ defaultMain $ testGroup "Tests" [jsonTests, integrationTests]+