@@ -1,35 +1,137 @@
1−module Docker.Types where
2−
3−import Data.Aeson (FromJSON, ToJSON, decode, encode,
4− genericParseJSON, genericToJSON, object,
5− parseJSON, toJSON, (.:), (.:?), (.=))
6−import qualified Data.Aeson as JSON
7−import Data.Aeson.Types (defaultOptions, fieldLabelModifier)
8−import Data.Char (toUpper)
9−import qualified Data.Map as M
10−import Data.Text (Text)
11−import qualified Data.Text as T
12−import GHC.Generics (Generic)
13−import Prelude hiding (all, tail)
1+{-# LANGUAGE RecordWildCards #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+module Docker.Types (
5+ Endpoint(..)
6+ , URL
7+ , ApiVersion
8+ , ContainerID
9+ , fromContainerID
10+ , toContainerID
11+ , ImageID
12+ , fromImageID
13+ , toImageID
14+ , Timeout(..)
15+ , Signal(..)
16+ , ContainerDetails(..)
17+ , DockerClientOpts(..)
18+ , defaultClientOpts
19+ , ListOpts(..)
20+ , defaultListOpts
21+ , DockerVersion(..)
22+ , ContainerPortInfo(..)
23+ , Container(..)
24+ , ContainerState(..)
25+ , Status(..)
26+ , Digest
27+ , Labels(..)
28+ , Tag
29+ , Image(..)
30+ , dropImagePrefix
31+ , CreateOpts(..)
32+ , cResources
33+ , hConfig
34+ , defaultCreateOpts
35+ , DetachKeys(..)
36+ , StartOpts(..)
37+ , defaultStartOpts
38+ , DeleteOpts(..)
39+ , Timestamp
40+ , TailLogOpt(..)
41+ , LogOpts(..)
42+ , defaultLogOpts
43+ , VolumePermission(..)
44+ , Volume(..)
45+ , Device(..)
46+ , ContainerName
47+ , VolumeFrom(..)
48+ , Link(..)
49+ , LogDriverType(..)
50+ , LogDriverOptions(..)
51+ , LogDriverConfig(..)
52+ , NetworkMode(..)
53+ , PortType(..)
54+ , NetworkInterface(..)
55+ , Networks(..)
56+ , NetworkSettings(..)
57+ , NetworkOptions(..)
58+ , Mount(..)
59+ , PortBinding(..)
60+ , PortBindings(..)
61+ , HostPort(..)
62+ , RetryCount
63+ , RestartPolicy(..)
64+ , Isolation(..)
65+ , UTSMode(..)
66+ , HostConfig(..)
67+ , Ulimit(..)
68+ , ContainerResources(..)
69+ , Port
70+ , Name
71+ , Value
72+ , EnvVar(..)
73+ , ContainerConfig(..)
74+ ) where
75+
76+import Data.Aeson (FromJSON, ToJSON, genericParseJSON,
77+ genericToJSON, object, parseJSON, toJSON,
78+ (.!=), (.:), (.:?), (.=))
79+import qualified Data.Aeson as JSON
80+import Data.Aeson.Types (defaultOptions, fieldLabelModifier)
81+import Data.Char (isAlphaNum, toUpper)
82+import qualified Data.HashMap.Strict as HM
83+import qualified Data.Map as M
84+import Data.Monoid ((<>))
85+import Data.Text (Text)
86+import qualified Data.Text as T
87+import Data.Time.Clock (UTCTime)
88+import GHC.Generics (Generic)
89+import Prelude hiding (all, tail)
90+import Text.Read (readMaybe)
1491
1592data Endpoint =
1693 VersionEndpoint
1794 | ListContainersEndpoint ListOpts
1895 | ListImagesEndpoint ListOpts
19− | CreateContainerEndpoint
96+ | CreateContainerEndpoint CreateOpts
2097 | StartContainerEndpoint StartOpts ContainerID
2198 | StopContainerEndpoint Timeout ContainerID
2299 | KillContainerEndpoint Signal ContainerID
23100 | RestartContainerEndpoint Timeout ContainerID
24101 | PauseContainerEndpoint ContainerID
25102 | UnpauseContainerEndpoint ContainerID
26− | ContainerLogsEndpoint LogOpts ContainerID
103+ | ContainerLogsEndpoint LogOpts Bool ContainerID -- Second argument (Bool) is whether to follow.
27104 | DeleteContainerEndpoint DeleteOpts ContainerID
105+ | InspectContainerEndpoint ContainerID
106+ deriving (Eq, Show)
28107
29108type URL = Text
30109type ApiVersion = Text
31−type ContainerID = Text
32−type ImageID = Text
110+newtype ContainerID = ContainerID Text
111+ deriving (Eq, Show)
112+
113+fromContainerID :: ContainerID -> Text
114+fromContainerID (ContainerID t) = t
115+
116+toContainerID :: Text -> Maybe ContainerID
117+toContainerID t =
118+ if T.all (\c -> isAlphaNum c || c == ':') t then -- Note: Can we improve this whitelist?
119+ Just $ ContainerID t
120+ else
121+ Nothing
122+
123+newtype ImageID = ImageID Text
124+ deriving (Eq, Show)
125+
126+fromImageID :: ImageID -> Text
127+fromImageID (ImageID t) = t
128+
129+toImageID :: Text -> Maybe ImageID
130+toImageID t =
131+ if T.all (\c -> isAlphaNum c || c == ':') t then -- Note: Can we improve this whitelist?
132+ Just $ ImageID t
133+ else
134+ Nothing
33135
34136data Timeout = Timeout Integer | DefaultTimeout deriving (Eq, Show)
35137
@@ -43,21 +145,121 @@ data Signal = SIGHUP
43145 | SIG Integer
44146 | SIGKILL deriving (Eq, Show)
45147
148+instance FromJSON Signal where
149+ parseJSON (JSON.String "SIGTERM") = return SIGTERM
150+ parseJSON (JSON.String "SIGHUP") = return SIGHUP -- Note: Guessing on the string values for these.
151+ parseJSON (JSON.String "SIGINT") = return SIGINT
152+ parseJSON (JSON.String "SIGQUIT") = return SIGQUIT
153+ parseJSON (JSON.String "SIGSTOP") = return SIGSTOP
154+ parseJSON (JSON.String "SIGUSR1") = return SIGUSR1
155+ parseJSON _ = fail "Unknown Signal"
156+
157+instance ToJSON Signal where
158+ toJSON SIGHUP = "SIGHUP"
159+ toJSON SIGINT = "SIGINT"
160+ toJSON SIGQUIT = "SIGQUIT"
161+ toJSON SIGSTOP = "SIGSTOP"
162+ toJSON SIGTERM = "SIGTERM"
163+ toJSON SIGUSR1 = "SIGUSR1"
164+ toJSON (SIG i) = toJSON i
165+ toJSON SIGKILL = "SIGKILL"
166+
167+data ContainerDetails = ContainerDetails {
168+ appArmorProfile :: Text
169+ , args :: [Text]
170+ , containerDetailsConfig :: ContainerConfig
171+ , created :: UTCTime
172+ , driver :: Text
173+ -- , execIDs -- Not sure what this is in 1.24 spec.
174+ , containerDetailsHostConfig :: HostConfig
175+ , hostnamePath :: FilePath
176+ , hostsPath :: FilePath
177+ , logPath :: FilePath
178+ , containerDetailsId :: ContainerID
179+ , containerDetailsImage :: ImageID
180+ , mountLabel :: Text
181+ , name :: Text
182+ , networkSettings :: NetworkSettings
183+ , path :: FilePath
184+ , processLabel :: Text
185+ , resolveConfPath :: FilePath
186+ , restartCount :: Int
187+ , state :: ContainerState
188+ , mounts :: [Mount]
189+ }
190+ deriving (Eq, Show, Generic)
191+
192+data Mount = Mount {
193+ mountName :: Text
194+ , mountSource :: FilePath
195+ , mountDestination :: FilePath
196+ , mountDriver :: Text
197+ , mountMode :: Text -- Note: Improve this?
198+ , mountRW :: Bool
199+ , mountPropogation :: Text
200+ }
201+ deriving (Eq, Show, Generic)
202+
203+instance FromJSON Mount where
204+ parseJSON (JSON.Object o) = do
205+ name <- o .: "Name"
206+ src <- o .: "Source"
207+ dest <- o .: "Destination"
208+ driver <- o .: "Driver"
209+ mode <- o .: "Mode"
210+ rw <- o .: "RW"
211+ prop <- o .: "Propagation"
212+ return $ Mount name src dest driver mode rw prop
213+ parseJSON _ = fail "Mount is not an object"
214+
215+data ContainerState = ContainerState {
216+ containerError :: Text
217+ , exitCode :: Int
218+ , finishedAt :: Maybe UTCTime -- Note: Is this a maybe?
219+ , oomKilled :: Bool
220+ , dead :: Bool
221+ , paused :: Bool
222+ , pid :: Int
223+ , restarting :: Bool
224+ , running :: Bool
225+ , startedAt :: UTCTime
226+ , status :: Status
227+ }
228+ deriving (Eq, Show, Generic)
229+
230+instance FromJSON ContainerState where
231+ parseJSON (JSON.Object o) = do
232+ err <- o .: "Error"
233+ exit <- o .: "ExitCode"
234+ finished <- o .:? "FinishedAt"
235+ oomKilled <- o .: "OOMKilled"
236+ dead <- o .: "Dead"
237+ paused <- o .: "Paused"
238+ pid <- o .: "Pid"
239+ restarting <- o .: "Restarting"
240+ running <- o .: "Running"
241+ started <- o .: "StartedAt"
242+ st <- o .: "Status"
243+ return $ ContainerState err exit finished oomKilled dead paused pid restarting running started st
244+ parseJSON _ = fail "ContainerState is not an object"
245+
46246data DockerClientOpts = DockerClientOpts {
47247 apiVer :: ApiVersion
48248 , baseUrl :: URL
49249 }
50−
51−data ListOpts = ListOpts { all :: Bool } deriving (Eq, Show)
52−
53−defaultListOpts = ListOpts { all=False }
250+ deriving (Eq, Show)
54251
55252defaultClientOpts :: DockerClientOpts
56253defaultClientOpts = DockerClientOpts {
57− apiVer = "v1.22"
254+ apiVer = "v1.23"
58255 , baseUrl = "http://127.0.0.1:2375"
59256 }
60257
258+data ListOpts = ListOpts { all :: Bool } deriving (Eq, Show)
259+
260+defaultListOpts :: ListOpts
261+defaultListOpts = ListOpts { all=False }
262+
61263data DockerVersion = DockerVersion {
62264 version :: Text
63265 , apiVersion :: ApiVersion
@@ -78,6 +280,57 @@ instance FromJSON DockerVersion where
78280 parseJSON = genericParseJSON defaultOptions {
79281 fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
80282
283+-- instance ToJSON ContainerDetails where
284+-- toJSON = error "TODO"
285+
286+instance FromJSON ContainerDetails where
287+ parseJSON (JSON.Object o) = do
288+ appArmor <- o .: "AppArmorProfile"
289+ args <- o .: "Args"
290+ config <- o .: "Config"
291+ created <- o .: "Created"
292+ driver <- o .: "Driver"
293+ hostConfig <- o .: "HostConfig"
294+ hostnamePath <- o .: "HostnamePath"
295+ hostsPath <- o .: "HostsPath"
296+ logPath <- o .: "LogPath"
297+ id <- o .: "Id"
298+ image <- o .: "Image"
299+ mountLabel <- o .: "MountLabel"
300+ name <- o .: "Name"
301+ networkSettings <- o .: "NetworkSettings"
302+ path <- o .: "Path"
303+ processLabel <- o .: "ProcessLabel"
304+ resolveConfPath <- o .: "ResolvConfPath"
305+ restartCount <- o .: "RestartCount"
306+ state <- o .: "State"
307+ mounts <- o .: "Mounts"
308+ return $ ContainerDetails appArmor args config created driver hostConfig hostnamePath hostsPath logPath id image mountLabel name networkSettings path processLabel resolveConfPath restartCount state mounts
309+ parseJSON _ = fail "ContainerDetails is not an object"
310+
311+instance ToJSON ContainerID where
312+ toJSON (ContainerID cid) = object ["Id" .= cid]
313+
314+instance FromJSON ContainerID where
315+ parseJSON (JSON.Object o) = do
316+ cid <- o .: "Id"
317+ case toContainerID cid of
318+ Nothing ->
319+ fail "Invalid ContainerID"
320+ Just cid ->
321+ return cid
322+ parseJSON _ = fail "ContainerID is not an object."
323+
324+instance ToJSON ImageID where
325+ toJSON (ImageID iid) = JSON.String iid
326+
327+instance FromJSON ImageID where
328+ parseJSON (JSON.String t) = case toImageID t of
329+ Nothing ->
330+ fail "Invalid ImageID"
331+ Just iid ->
332+ return iid
333+ parseJSON _ = fail "ImageID is not an object."
81334
82335data ContainerPortInfo = ContainerPortInfo {
83336 ipAddressInfo :: Maybe Text
@@ -92,50 +345,100 @@ instance FromJSON ContainerPortInfo where
92345 <*> (v .: "PrivatePort")
93346 <*> (v .:? "PublicPort")
94347 <*> (v .:? "Type")
95−
96−-- data NetworkOptions = NetworkOptions {
97−-- ipamConfig :: Maybe Text
98−-- , links :: Maybe Text
99−-- , aliases :: Maybe Text
100−-- , networkId :: Text
101−-- , endpointId :: Text
102−-- , gateway :: Text
103−-- , ipAddress :: Text
104−-- , ipPrefixLen :: Text
105−-- , ipV6Gateway :: Maybe Text
106−-- , globalIPv6Address :: Maybe Text
107−-- , globalIPv6PrefixLen :: Maybe Text
108−-- , macAddress :: Text
109−-- } deriving (Eq, Show)
110−
111−-- data Network = Network (M.Map NetworkMode NetworkOptions) deriving (Eq, Show)
112−
113−-- data NetworkSettings = NetworkSettings {
114−-- bridge
115−-- , sandboxId
116−-- , hairpinMode
117−-- , linkLocalIPv6Address
118−-- , linkLocalIPv6PrefixLen
119−-- , ports
120−-- , sandboxKey
121−-- , secondaryIPAddresses
122−-- , secondaryIPv6Addresses
123−-- , endpointID
124−-- , gateway
125−-- , globalIPv6Address
126−-- , globalIPv6PrefixLen
127−-- , ipAddress
128−-- , ipPrefixLen
129−-- , ipv6Gateway
130−-- , macAddress
131−-- , networks :: [Network]
132−-- }
133−
134−-- data NetworkSettings = NetworkSettings (M.Map Text Text) deriving (Eq, Show, Generic)
135−
136−-- instance FromJSON NetworkSettings
137−
138−-- TODO: Add NetworkSettings
348+ parseJSON _ = fail "ContainerPortInfo: Not a JSON object."
349+
350+-- For inspecting container details.
351+data NetworkOptions = NetworkOptions {
352+-- ipamConfig :: Maybe Text -- Don't see in 1.24
353+-- , links :: Maybe Text -- Don't see in 1.24
354+-- , aliases :: Maybe Text -- Don't see in 1.24
355+ networkOptionsId :: Text
356+ , networkOptionsEndpointId :: Text
357+ , networkOptionsGateway :: Text
358+ , networkOptionsIpAddress :: Text -- Note: Parse this?
359+ , networkOptionsIpPrefixLen :: Int
360+ , networkOptionsIpV6Gateway :: Maybe Text
361+ , networkOptionsGlobalIPv6Address :: Maybe Text
362+ , networkOptionsGlobalIPv6PrefixLen :: Maybe Int
363+ , networkOptionsMacAddress :: Text
364+ } deriving (Eq, Show)
365+
366+instance FromJSON NetworkOptions where
367+ parseJSON (JSON.Object o) = do
368+ networkId <- o .: "NetworkID"
369+ endpointId <- o .: "EndpointID"
370+ gateway <- o .: "Gateway"
371+ ip <- o .: "IPAddress"
372+ ipLen <- o .: "IPPrefixLen"
373+ ip6Gateway <- o .:? "IPv6Gateway"
374+ globalIP6 <- o .:? "GlobalIPv6Address"
375+ globalIP6Len <- o .:? "GlobalIPv6PrefixLen"
376+ mac <- o .: "MacAddress"
377+ return $ NetworkOptions networkId endpointId gateway ip ipLen ip6Gateway globalIP6 globalIP6Len mac
378+ parseJSON _ = fail "NetworkOptions is not an object"
379+
380+newtype Networks = Networks (M.Map NetworkMode NetworkOptions) -- Note: Is it ever possible that there will be duplicates of network modes?
381+ deriving (Eq, Show)
382+
383+instance FromJSON Networks where
384+ parseJSON (JSON.Object o) = do
385+ Networks <$> HM.foldlWithKey' f (return M.empty) o
386+
387+ where
388+ f accM k' v' = do
389+ acc <- accM
390+ k <- parseJSON $ JSON.String k'
391+ v <- parseJSON v'
392+ return $ M.insert k v acc
393+
394+ parseJSON _ = fail "Networks is not an object"
395+
396+data NetworkSettings = NetworkSettings {
397+ networkSettingsBridge :: Text
398+ , networkSettingsSandboxId :: Text
399+ , networkSettingsHairpinMode :: Bool
400+ , networkSettingsLinkLocalIPv6Address :: Text
401+ , networkSettingsLinkLocalIPv6PrefixLen :: Int
402+ , networkSettingsPorts :: [Int] -- TODO: 1.24 spec is unclear
403+ , networkSettingsSandboxKey :: Text
404+ , networkSettingsSecondaryIPAddresses :: Maybe [Text] -- TODO: 1.24 spec is unclear
405+ , networkSettingsSecondaryIPv6Addresses :: Maybe [Text] -- TODO: 1.24 spec is unclear
406+ , networkSettingsEndpointID :: Text
407+ , networkSettingsGateway :: Text
408+ , networkSettingsGlobalIPv6Address :: Text
409+ , networkSettingsGlobalIPv6PrefixLen :: Int
410+ , networkSettingsIpAddress :: Text
411+ , networkSettingsIpPrefixLen :: Int
412+ , networkSettingsIpv6Gateway
413+ , networkSettingsMacAddress
414+ , networkSettingsNetworks :: Networks
415+ }
416+ deriving (Eq, Show)
417+
418+instance FromJSON NetworkSettings where
419+ parseJSON (JSON.Object o) = do
420+ bridge <- o .: "Bridge"
421+ sandbox <- o .: "SandboxID"
422+ hairpin <- o .: "HairpinMode"
423+ localIP6 <- o .: "LinkLocalIPv6Address"
424+ localIP6Len <- o .: "LinkLocalIPv6PrefixLen"
425+ ports <- o .:? "Ports" .!= []
426+ sandboxKey <- o .: "SandboxKey"
427+ secondaryIP <- o .: "SecondaryIPAddresses"
428+ secondayIP6 <- o .: "SecondaryIPv6Addresses"
429+ endpointID <- o .: "EndpointID"
430+ gateway <- o .: "Gateway"
431+ globalIP6 <- o .: "GlobalIPv6Address"
432+ globalIP6Len <- o .: "GlobalIPv6PrefixLen"
433+ ip <- o .: "IPAddress"
434+ ipLen <- o .: "IPPrefixLen"
435+ ip6Gateway <- o .: "IPv6Gateway"
436+ mac <- o .: "MacAddress"
437+ networks <- o .: "Networks"
438+ return $ NetworkSettings bridge sandbox hairpin localIP6 localIP6Len ports sandboxKey secondaryIP secondayIP6 endpointID gateway globalIP6 globalIP6Len ip ipLen ip6Gateway mac networks
439+ parseJSON _ = fail "NetworkSettings is not an Object"
440+
441+-- TODO: Add NetworkSettings, Mounts
139442data Container = Container
140443 { containerId :: ContainerID
141444 , containerNames :: [Text]
@@ -143,10 +446,11 @@ data Container = Container
143446 , containerImageId :: ImageID
144447 , containerCommand :: Text
145448 , containerCreatedAt :: Int
146− , containerStatus :: Text
449+ , containerStatus :: Status
147450 , containerPorts :: [ContainerPortInfo]
148451 , containerLabels :: Labels
149− -- , containerNetworkSettings :: NetworkSettings
452+ , containerNetworks :: Networks
453+ , containerMounts :: [Mount]
150454 } deriving (Show, Eq)
151455
152456instance FromJSON Container where
@@ -160,14 +464,29 @@ instance FromJSON Container where
160464 <*> (v .: "Status")
161465 <*> (v .: "Ports")
162466 <*> (v .: "Labels")
163− -- <*> (v .: "NetworkSettings")
164−
165−data Status = Created | Restarting | Running | Paused | Exited | Dead deriving (Eq, Show, Generic)
166−
167−instance FromJSON Status
168−instance ToJSON Status
467+ <*> (v .: "NetworkSettings" >>= parseNetworks)
468+ <*> (v .: "Mounts")
469+ where
470+ parseNetworks (JSON.Object v) =
471+ (v .: "Networks") >>= parseJSON
472+ parseNetworks _ = fail "Container NetworkSettings: Not a JSON object."
473+
474+ parseJSON _ = fail "Container: Not a JSON object."
475+
476+data Status = Created | Restarting | Running | Paused | Exited | Dead
477+ deriving (Eq, Show, Generic)
478+
479+instance FromJSON Status where
480+ parseJSON (JSON.String "running") = return Running
481+ parseJSON (JSON.String "created") = return Created -- Note: Guessing on the string values of these.
482+ parseJSON (JSON.String "restarting") = return Restarting
483+ parseJSON (JSON.String "paused") = return Paused
484+ parseJSON (JSON.String "exited") = return Exited
485+ parseJSON (JSON.String "dead") = return Dead
486+ parseJSON _ = fail "Unknown Status"
169487
170488type Digest = Text
489+
171490newtype Labels = Labels (M.Map Name Value) deriving (Eq, Show)
172491
173492instance FromJSON Labels where
@@ -183,12 +502,13 @@ data Image = DockerImage {
183502 , imageCreated :: Integer
184503 , imageParentId :: Maybe ImageID
185504 , imageRepoTags :: [Tag]
186− , imageRepoDigests :: [Digest]
505+ , imageRepoDigests :: Maybe [Digest]
187506 , imageSize :: Integer
188507 , imageVirtualSize :: Integer
189− , imageLabels :: Labels
508+ , imageLabels :: Maybe Labels
190509 } deriving (Show, Eq, Generic)
191510
511+dropImagePrefix :: [a] -> [a]
192512dropImagePrefix = drop 5
193513
194514instance FromJSON Image where
@@ -196,17 +516,102 @@ instance FromJSON Image where
196516 fieldLabelModifier = dropImagePrefix}
197517
198518data CreateOpts = CreateOpts {
199− containerConfig :: ContainerConfig
200− , hostConfig :: HostConfig
201− , containerResources :: ContainerResources
519+ containerConfig :: ContainerConfig
520+ , hostConfig :: HostConfig
202521 } deriving (Eq, Show)
203522
523+instance ToJSON CreateOpts where
524+ toJSON (CreateOpts cc hc) = do
525+ let ccJSON = toJSON cc
526+ let hcJSON = toJSON hc
527+ case ccJSON of
528+ JSON.Object (o :: HM.HashMap T.Text JSON.Value) -> do
529+ JSON.Object $ HM.insert "HostConfig" hcJSON o
530+ _ -> error "ContainerConfig is not an object." -- This should never happen.
531+
532+cConfig :: Text -> ContainerConfig
533+cConfig imageName = ContainerConfig {
534+ hostname=Nothing
535+ , domainname=Nothing
536+ , user=Nothing
537+ , attachStdin=False
538+ , attachStdout=False
539+ , image=imageName
540+ , attachStderr=False
541+ , exposedPorts=ExposedPorts M.empty
542+ , tty=False
543+ , openStdin=False
544+ , stdinOnce=False
545+ , env=[]
546+ , cmd=[]
547+ , volumes=Nothing
548+ , workingDir=Nothing
549+ , entrypoint=Nothing
550+ , networkDisabled=False
551+ , macAddress=Nothing
552+ , labels=Nothing
553+ , stopSignal=SIGTERM
554+ }
555+
556+hConfig :: HostConfig
557+hConfig = HostConfig {
558+ binds=[]
559+ , containerIDFile=Nothing
560+ , logConfig=LogDriverConfig JsonFile Nothing
561+ , networkMode=Bridge
562+ , portBindings=PortBindings []
563+ , restartPolicy=RestartOff
564+ , volumeDriver=Nothing
565+ , volumesFrom=[]
566+ , capAdd=[]
567+ , capDrop=[]
568+ , dns=[]
569+ , dnsOptions=[]
570+ , dnsSearch=[]
571+ , extraHosts=[]
572+ , ipcMode=Nothing
573+ , links=[]
574+ , oomScoreAdj=Nothing
575+ , privileged=False
576+ , publishAllPorts=False
577+ , readOnlyRootfs=False
578+ , securityOpt=[]
579+ , shmSize=Nothing
580+ , resources=cResources
581+ }
582+
583+cResources :: ContainerResources
584+cResources = ContainerResources {
585+ cpuShares=Nothing
586+ , blkioWeight=Nothing
587+ , blkioWeightDevice=Nothing
588+ , blkioDeviceReadBps=Nothing
589+ , blkioDeviceWriteBps=Nothing
590+ , blkioDeviceReadIOps=Nothing
591+ , blkioDeviceWriteIOps=Nothing
592+ , cpuPeriod=Nothing
593+ , cpusetCpus=Nothing
594+ , cpusetMems=Nothing
595+ , devices=[]
596+ , kernelMemory=Nothing
597+ , memory=Nothing
598+ , memoryReservation=Nothing
599+ , memorySwap=Nothing
600+ , oomKillDisable=False
601+ , ulimits=[]
602+ }
603+
604+
605+defaultCreateOpts :: T.Text -> CreateOpts
606+defaultCreateOpts imageName = CreateOpts { containerConfig = cConfig imageName, hostConfig = hConfig }
607+
204608-- detachKeys – Override the key sequence for detaching a container.
205609-- Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.
206610data DetachKeys = WithCtrl Char | WithoutCtrl Char | DefaultDetachKey deriving (Eq, Show)
207611
208612data StartOpts = StartOpts { detachKeys :: DetachKeys } deriving (Eq, Show)
209613
614+defaultStartOpts :: StartOpts
210615defaultStartOpts = StartOpts { detachKeys = DefaultDetachKey }
211616
212617data DeleteOpts = DeleteOpts {
@@ -218,16 +623,15 @@ type Timestamp = Integer
218623data TailLogOpt = Tail Integer | All deriving (Eq, Show)
219624
220625data LogOpts = LogOpts {
221− follow :: Bool
222− , stdout :: Bool
626+ stdout :: Bool
223627 , stderr :: Bool
224628 , since :: Maybe Timestamp
225629 , timestamps :: Bool
226630 , tail :: TailLogOpt
227631 } deriving (Eq, Show)
228632
229−defaultLogOpts = LogOpts { follow = False
230− , stdout = True
633+defaultLogOpts :: LogOpts
634+defaultLogOpts = LogOpts { stdout = True
231635 , stderr = True
232636 , since = Nothing
233637 , timestamps = True
@@ -236,14 +640,36 @@ defaultLogOpts = LogOpts { follow = False
236640
237641data VolumePermission = Rw | Ro deriving (Eq, Show, Generic)
238642
239−instance ToJSON VolumePermission
240−instance FromJSON VolumePermission
643+-- instance ToJSON VolumePermission where
644+-- toJSON = error "TODO"
645+-- instance FromJSON VolumePermission where
646+-- parseJSON = error "TODO"
647+
648+-- Note: Note really sure what this should be (1.24).
649+-- This is not the same as [Volume] in HostConfig.
650+newtype Volumes = Volumes [Text] -- [Volume]
651+ deriving (Eq, Show)
652+
653+instance FromJSON Volumes where
654+ parseJSON (JSON.Object o) = return $ Volumes $ HM.keys o
655+ parseJSON _ = fail "Volumes is not an object"
656+
657+instance ToJSON Volumes where
658+ toJSON (Volumes vs) = toJSON vs
241659
242660data Volume = Volume { hostSrc :: Text
243661 , containerDest :: Text
244662 , volumePermission :: Maybe VolumePermission
245663 } deriving (Eq, Show)
246664
665+instance FromJSON Volume where
666+ parseJSON (JSON.String t) = case T.split (== ':') t of
667+ [src, dest] -> return $ Volume src dest Nothing
668+ [src, dest, "rw"] -> return $ Volume src dest $ Just Rw
669+ [src, dest, "ro"] -> return $ Volume src dest $ Just Ro
670+ _ -> fail "Could not parse Volume"
671+ parseJSON _ = fail "Volume is not a string"
672+
247673data Device = Device {
248674 pathOnHost :: FilePath
249675 , pathInContainer :: FilePath
@@ -260,7 +686,20 @@ instance FromJSON Device where
260686
261687type ContainerName = Text
262688
263−data VolumeFrom = VolumeFrom ContainerName VolumePermission deriving (Eq, Show)
689+data VolumeFrom = VolumeFrom ContainerName (Maybe VolumePermission) deriving (Eq, Show)
690+
691+instance FromJSON VolumeFrom where
692+ parseJSON (JSON.String t) = case T.split (== ':') t of
693+ [vol] -> return $ VolumeFrom vol Nothing
694+ [vol, "rw"] -> return $ VolumeFrom vol $ Just Rw
695+ [vol, "ro"] -> return $ VolumeFrom vol $ Just Ro
696+ _ -> fail "Could not parse VolumeFrom"
697+ parseJSON _ = fail "VolumeFrom is not a string"
698+
699+instance ToJSON VolumeFrom where
700+ toJSON (VolumeFrom n p) = case p of
701+ Nothing -> toJSON $ n <> ":" <> "rw"
702+ Just per -> toJSON $ n <> ":" <> (T.pack $ show per)
264703
265704instance ToJSON Volume where
266705 toJSON (Volume src dest mode) = toJSON $ case mode of
@@ -269,47 +708,206 @@ instance ToJSON Volume where
269708
270709data Link = Link Text (Maybe Text) deriving (Eq, Show)
271710
711+instance FromJSON Link where
712+ parseJSON (JSON.String t) = case T.split (== ':') t of
713+ [f] -> return $ Link f Nothing
714+ [f,s] -> return $ Link f $ Just s
715+ _ -> fail "Could not parse Link"
716+ parseJSON _ = fail "Link is not a string"
717+
272718instance ToJSON Link where
273719 toJSON (Link n1 n2) = toJSON $ case n2 of
274720 Nothing -> T.concat[n1, ":", n1] -- use same name in container
275721 Just n -> T.concat[n1, ":", n] -- used specified name in container
276722
277723-- { "Type": "<driver_name>", "Config": {"key1": "val1"} }
278−data LogDriverType = JsonFile | Syslog | Journald | Gelf | AwsLogs | Splunk | LoggingDisabled deriving (Eq, Show)
279−
280−data LogDriverOptions = Map Text Text deriving (Eq, Show)
724+data LogDriverType = JsonFile | Syslog | Journald | Gelf | Fluentd | AwsLogs | Splunk | Etwlogs | LoggingDisabled deriving (Eq, Show)
725+
726+instance FromJSON LogDriverType where
727+ parseJSON (JSON.String "json-file") = return JsonFile
728+ parseJSON (JSON.String "syslog") = return Syslog
729+ parseJSON (JSON.String "journald") = return Journald
730+ parseJSON (JSON.String "gelf") = return Gelf
731+ parseJSON (JSON.String "fluentd") = return Fluentd
732+ parseJSON (JSON.String "awslogs") = return AwsLogs
733+ parseJSON (JSON.String "splunk") = return Splunk
734+ parseJSON (JSON.String "etwlogs") = return Etwlogs
735+ parseJSON (JSON.String "none") = return LoggingDisabled
736+ parseJSON _ = fail "Unknown LogDriverType"
737+
738+instance ToJSON LogDriverType where
739+ toJSON JsonFile = JSON.String "json-file"
740+ toJSON Syslog = JSON.String "syslog"
741+ toJSON Journald = JSON.String "journald"
742+ toJSON Gelf = JSON.String "gelf"
743+ toJSON Fluentd = JSON.String "fluentd"
744+ toJSON AwsLogs = JSON.String "awslogs"
745+ toJSON Splunk = JSON.String "splunk"
746+ toJSON Etwlogs = JSON.String "etwlogs"
747+ toJSON LoggingDisabled = JSON.String "none"
748+
749+newtype LogDriverOptions = LogDriverOptions (M.Map Text Text) deriving (Eq, Show)
750+
751+instance FromJSON LogDriverOptions where
752+ parseJSON (JSON.Object o) = do
753+ LogDriverOptions <$> HM.foldlWithKey' f (return M.empty) o
754+
755+ where
756+ f accM k (JSON.String v) = do
757+ acc <- accM
758+ return $ M.insert k v acc
759+ f _ _ _ = fail "Value of LogDriverOptions is not a string"
760+
761+ parseJSON JSON.Null = return $ LogDriverOptions M.empty
762+ parseJSON _ = fail "LogDriverOptions is not an object"
763+
764+instance ToJSON LogDriverOptions where
765+ toJSON (LogDriverOptions mp) = toJSON mp
281766
282767data LogDriverConfig = LogDriverConfig LogDriverType (Maybe LogDriverOptions) deriving (Eq, Show)
283768
769+instance ToJSON LogDriverConfig where
770+ toJSON (LogDriverConfig driverType driverOptions) = object ["Type" .= driverType, "Config" .= driverOptions]
771+
772+instance FromJSON LogDriverConfig where
773+ parseJSON (JSON.Object o) = do
774+ typ <- o .: "Type"
775+ opts <- o .: "Config"
776+ return $ LogDriverConfig typ opts
777+ parseJSON _ = fail "LogDriverConfig is not an object"
778+
284779-- TODO: Add container:<name|id> mode
285−data NetworkMode = Bridge | Host | NetworkDisabled deriving (Eq, Show)
780+data NetworkMode = Bridge | Host | NetworkDisabled
781+ deriving (Eq, Show, Ord)
782+
783+instance FromJSON NetworkMode where
784+ parseJSON (JSON.String "bridge") = return Bridge
785+ parseJSON (JSON.String "host") = return Host -- Note: Guessing on these.
786+ parseJSON (JSON.String "none") = return NetworkDisabled
787+ parseJSON _ = fail "Unknown NetworkMode"
788+
789+instance ToJSON NetworkMode where
790+ toJSON Bridge = JSON.String "bridge"
791+ toJSON Host = JSON.String "host"
792+ toJSON NetworkDisabled = JSON.String "none"
286793
287−data PortType = TCP | UDP deriving (Eq, Show, Generic)
794+data PortType = TCP | UDP deriving (Eq, Show, Generic, Read, Ord)
288795
289796instance ToJSON PortType where
290797 toJSON TCP = "tcp"
291798 toJSON UDP = "udp"
292799
293800instance FromJSON PortType where
294− parseJSON val = return $ case val of
295− "tcp" -> TCP
296− "udp" -> UDP
801+ parseJSON val = case val of
802+ "tcp" -> return TCP
803+ "udp" -> return UDP
804+ _ -> fail "PortType: Invalid port type."
297805
298806newtype NetworkInterface = NetworkInterface Text deriving (Eq, Show)
299807
808+instance FromJSON NetworkInterface where
809+ parseJSON (JSON.String v) = return $ NetworkInterface v
810+ parseJSON _ = fail "Network interface is not a string."
811+
812+instance ToJSON NetworkInterface where
813+ toJSON (NetworkInterface i) = JSON.String i
814+
815+-- | This datastructure models mapping a Port from the container onto the
816+-- host system s that the service running in the container can be accessed from
817+-- the outside world. We either map a port onto all interfaces (default) or onto a specific
818+-- interface like `127.0.0.1`.
819+newtype PortBindings = PortBindings [PortBinding]
820+ deriving (Eq, Show)
821+
300822-- { <port>/<protocol>: [{ "HostPort": "<port>" }] }
301823data PortBinding = PortBinding {
302824 containerPort :: Port
303825 , portType :: PortType
304− , hostPort :: [HostPort]
826+ , hostPorts :: HostPorts
305827 } deriving (Eq, Show)
306828
307−data HostPort = HostPort NetworkInterface Port deriving (Eq, Show)
829+
830+portAndType2Text :: Port -> PortType -> Text
831+portAndType2Text p t = (T.pack $ show p) <> "/" <> (T.pack $ show t)
832+
833+instance ToJSON PortBinding where
834+ toJSON (PortBinding {..}) = object [portAndType2Text containerPort portType .= hostPorts]
835+
836+instance FromJSON PortBindings where
837+ parseJSON (JSON.Object o) = do
838+ PortBindings <$> HM.foldlWithKey' f (return []) o
839+
840+ where
841+ f accM k v = case T.split (== '/') k of
842+ [port', portType'] -> do
843+ port <- parseIntegerText port'
844+ portType <- parseJSON $ JSON.String portType'
845+ acc <- accM
846+ hps <- parseJSON v
847+ return $ (PortBinding port portType hps):acc
848+ _ ->
849+ fail "Could not parse PortBindings"
850+ parseJSON _ = fail "PortBindings is not an object"
851+
852+instance ToJSON PortBindings where
853+ toJSON (PortBindings []) = JSON.Object HM.empty
854+ toJSON (PortBindings (p:ps)) = toJSON (p:ps)
855+
856+data HostPort = HostPort NetworkInterface Port
857+ deriving (Eq, Show)
858+
859+instance ToJSON HostPort where
860+ toJSON (HostPort i p) = object ["HostPort" .= p, "HostIP" .= i]
861+
862+instance FromJSON HostPort where
863+ parseJSON (JSON.Object o) = do
864+ p <- o .: "HostPort"
865+ i <- o .: "HostIP"
866+ return $ HostPort i p
867+ parseJSON _ = fail "HostPort is not an object."
868+
869+newtype HostPorts = HostPorts [HostPort]
870+ deriving (Eq, Show)
871+
872+
873+instance ToJSON HostPorts where
874+ toJSON (HostPorts hps) = toJSON hps
875+
876+instance FromJSON HostPorts where
877+ parseJSON (JSON.Object o) = do
878+ HostPorts <$> HM.foldlWithKey' f (return []) o
879+
880+ where
881+ f accM k v = do
882+ acc <- accM
883+ p' <- parseJSON v
884+ p <- parseIntegerText p'
885+ return $ (HostPort (NetworkInterface k) p):acc
886+ parseJSON _ = fail "HostPorts is not an object"
308887
309888-- { "Name": "on-failure" , "MaximumRetryCount": 2}
310889type RetryCount = Integer
311890data RestartPolicy = RestartAlways | RestartUnlessStopped | RestartOnFailure RetryCount | RestartOff deriving (Eq, Show)
312891
892+instance FromJSON RestartPolicy where
893+ parseJSON (JSON.Object o) = do
894+ (name :: Text) <- o .: "Name"
895+ case name of
896+ "always" -> return RestartAlways
897+ "unless-stopped" -> return RestartUnlessStopped
898+ "on-failure" -> do
899+ retry <- o .: "MaximumRetryCount"
900+ return $ RestartOnFailure retry
901+ "" -> return RestartOff -- Note: Is this correct? 1.24 spec is unclear.
902+ _ -> fail "Could not parse RestartPolicy"
903+ parseJSON _ = fail "RestartPolicy is not an object"
904+
905+instance ToJSON RestartPolicy where
906+ toJSON RestartAlways = object ["Name" .= JSON.String "always"]
907+ toJSON RestartUnlessStopped = object ["Name" .= JSON.String "unless-stopped"]
908+ toJSON (RestartOnFailure c) = object ["Name" .= JSON.String "on-failure", "MaximumRetryCount" .= c]
909+ toJSON RestartOff = object ["Name" .= JSON.String "off"]
910+
313911data Isolation = Default | Process | Hyperv deriving (Eq, Show)
314912
315913newtype UTSMode = UTSMode Text deriving (Eq, Show)
@@ -319,12 +917,12 @@ newtype UTSMode = UTSMode Text deriving (Eq, Show)
319917-- TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
320918data HostConfig = HostConfig
321919 { binds :: [Volume]
322− , containerIDFile :: FilePath
920+ , containerIDFile :: Maybe FilePath -- 1.24: Only in responses, not create
323921 , logConfig :: LogDriverConfig
324922 , networkMode :: NetworkMode
325− , portBindings :: [PortBinding]
923+ , portBindings :: PortBindings
326924 , restartPolicy :: RestartPolicy
327− , volumeDriver :: Text
925+ , volumeDriver :: Maybe Text
328926 , volumesFrom :: [VolumeFrom]
329927 , capAdd :: [Text]
330928 , capDrop :: [Text]
@@ -332,93 +930,178 @@ data HostConfig = HostConfig
332930 , dnsOptions :: [Text]
333931 , dnsSearch :: [Text]
334932 , extraHosts :: [Text]
335− , groupAdd :: [Integer]
336− , ipcMode :: Text
933+ -- , groupAdd :: [Integer] -- 1.24: Missing from inspecting container details... Going to omit for now.
934+ , ipcMode :: Maybe Text -- 1.24: Only in inspect, not create
337935 , links :: [Link]
338− , oomScoreAdj :: Integer
339− , pidMode :: Text
936+ , oomScoreAdj :: Maybe Integer
937+ -- , pidMode :: Text -- 1.24: Don't see pidMode, just pidsLimit
340938 , privileged :: Bool
341939 , publishAllPorts :: Bool
342940 , readOnlyRootfs :: Bool
343941 , securityOpt :: [Text]
344− , storageOpt :: [Text]
345− , utsMode :: UTSMode
346− , shmSize :: Integer
347− , consoleSize :: Integer
348− , isolation :: Isolation
942+ -- , utsMode :: UTSMode -- 1.24: Don't see this
943+ , shmSize :: Maybe Integer
944+ -- , consoleSize :: Integer -- 1.24: Don't see this
945+ -- , isolation :: Isolation -- 1.24: Don't see this
349946 , resources :: ContainerResources
350947 } deriving (Eq, Show, Generic)
351948
949+instance FromJSON HostConfig where
950+ parseJSON = genericParseJSON defaultOptions {
951+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
952+
953+instance ToJSON HostConfig where
954+ toJSON = genericToJSON defaultOptions {
955+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
956+
957+
352958-- { "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }
353959-- { "Name": "nofile", "Soft": 1024, "Hard": 2048 }
354960data Ulimit = Ulimit {
355961 ulimitName :: Text
356962 , ulimitSoft :: Integer
357963 , ulimitHard :: Integer
358− } deriving (Eq, Show)
964+ } deriving (Eq, Show, Generic)
965+
966+instance FromJSON Ulimit where
967+ parseJSON = genericParseJSON defaultOptions {
968+ fieldLabelModifier = drop 5}
969+
970+instance ToJSON Ulimit where
971+ toJSON = genericToJSON defaultOptions {
972+ fieldLabelModifier = drop 5}
359973
360974data ContainerResources = ContainerResources {
361− cpuShares :: Integer
362− , cgroupParent :: Text
363− , blkioWeight :: Text
364− , blkioWeightDevice :: Text
365− , blkioDeviceReadBps :: Text
366− , blkioDeviceWriteBps :: Text
367− , blkioDeviceReadIOps :: Text
368− , blkioDeviceWriteIOps :: Text
369− , cpuPeriod :: Integer
370− , cpuQuota :: Integer
371− , cpusetCpus :: Integer
372− , cpusetMems :: Text
975+ cpuShares :: Maybe Integer
976+ -- , cgroupParent :: Text -- 1.24: Missing from inspecting container details... Going to omit for now.
977+ , blkioWeight :: Maybe Text
978+ , blkioWeightDevice :: Maybe Text
979+ , blkioDeviceReadBps :: Maybe Text
980+ , blkioDeviceWriteBps :: Maybe Text
981+ , blkioDeviceReadIOps :: Maybe Text
982+ , blkioDeviceWriteIOps :: Maybe Text
983+ , cpuPeriod :: Maybe Integer
984+ -- , cpuQuota :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.
985+ , cpusetCpus :: Maybe Integer
986+ , cpusetMems :: Maybe Text
373987 , devices :: [Device]
374− , diskQuota :: Integer
375− , kernelMemory :: Integer
376− , memory :: Integer
377− , memoryReservation :: Integer
378− , memorySwap :: Integer
379− , memorySwappiness :: Integer
988+ -- , diskQuota :: Integer -- Don't see this ins 1.24.
989+ , kernelMemory :: Maybe Integer
990+ , memory :: Maybe Integer
991+ , memoryReservation :: Maybe Integer
992+ , memorySwap :: Maybe Integer
993+ -- , memorySwappiness :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.
380994 , oomKillDisable :: Bool
381− , pidsLimit :: Integer
995+ -- , pidsLimit :: Integer -- 1.24: Missing from inspecting container details... Going to omit for now.
382996 , ulimits :: [Ulimit]
383− } deriving (Eq, Show)
997+ -- TODO: Missing from 1.24
998+ -- StorageOpt :: [(Text, Text)]
999+ -- VolumeDriver :: ??
1000+ -- EndpointsConfig :: ??
1001+ -- TODO: Only in inspect container in 1.24
1002+ -- CpuPercent :: Int +
1003+ -- MaximumIOps :: Int +
1004+ -- MaximumIOBps :: Int +
1005+ -- LxcConf :: [??] +
1006+ } deriving (Eq, Show, Generic)
1007+
1008+instance ToJSON ContainerResources where
1009+ toJSON = genericToJSON defaultOptions {
1010+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
1011+
1012+instance FromJSON ContainerResources where
1013+ parseJSON = genericParseJSON defaultOptions {
1014+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
3841015
3851016type Port = Integer
3861017
3871018type Name = Text
3881019type Value = Text
3891020
390−data EnvVar = EnvVar Name Value deriving (Eq, Show)
1021+data EnvVar = EnvVar Name Value
1022+ deriving (Eq, Show)
1023+
1024+instance FromJSON EnvVar where
1025+ parseJSON (JSON.String env) =
1026+ let (n, v') = T.break (== '=') env in
1027+ let v = T.drop 1 v' in
1028+ return $ EnvVar n v
1029+ parseJSON _ = fail "EnvVar is not a string"
1030+
1031+instance ToJSON EnvVar where
1032+ toJSON (EnvVar n v) = object [n .= v]
1033+
1034+-- | ExposedPorts represent a enumeraton of all the ports (and their type)
1035+-- that a container should expose to other containers or the host system.
1036+-- `NOTE`: This does not automatically expose the ports onto the host
1037+-- system but rathet it just tags them. It's best to be used with with
1038+-- the PublishAllPorts flag. It is also useful for
1039+-- the deamon to know wich Environment variables to
1040+-- inject into a container linking to our container.
1041+-- Example linking a Postgres container named db would inject the following
1042+-- environment variables automatically if we set the corresponding
1043+-- ExposedPorts:
1044+-- DB_PORT_5432_TCP_PORT="5432"
1045+-- DB_PORT_5432_TCP_PROTO="tcp"
1046+-- DB_PORT_5432_TCP="tcp://172.17.0.1:5432"
1047+newtype ExposedPorts = ExposedPorts (M.Map PortType Port) deriving (Eq, Show)
1048+
1049+instance FromJSON ExposedPorts where
1050+ parseJSON (JSON.Object o) = do
1051+ ExposedPorts <$> HM.foldlWithKey' f (return M.empty) o
1052+
1053+ where
1054+ f accM k _ = case T.split (== '/') k of
1055+ [port', portType'] -> do
1056+ port <- parseIntegerText port'
1057+ portType <- parseJSON $ JSON.String portType'
1058+ acc <- accM
1059+ return $ M.insert portType port acc
1060+ _ ->
1061+ fail "Could not parse ExposedPorts"
1062+ parseJSON _ = fail "ExposedPorts is not an object."
1063+
1064+instance ToJSON ExposedPorts where
1065+ toJSON (ExposedPorts kvs) = object [((T.pack $ show p) <> "/" <> (T.pack $ show t)) .= JSON.Object HM.empty | (t,p) <- (M.toList kvs)]
1066+
3911067
3921068data ContainerConfig = ContainerConfig {
393− hostname :: Text
394− , domainname :: Text
395− , user :: Text
1069+ hostname :: Maybe Text
1070+ , domainname :: Maybe Text
1071+ , user :: Maybe Text
3961072 , attachStdin :: Bool
3971073 , attachStdout :: Bool
3981074 , attachStderr :: Bool
399− -- ExposedPorts are used for the PublishAllPorts flag
400− -- to know which ports to publish. Also it's used for
401− -- the deamon to know wich Environment variables to
402− -- inject into a container linking to our container.
403− -- Example linking a Postgres container named db:
404− -- DB_PORT_5432_TCP_PORT="5432"
405− -- DB_PORT_5432_TCP_PROTO="tcp"
406− -- DB_PORT_5432_TCP="tcp://172.17.0.1:5432"
407− , exposedPorts :: [Port]
408− , publishService :: Text
1075+ , exposedPorts :: ExposedPorts
1076+ -- , publishService :: Text -- Don't see this in 1.24
4091077 , tty :: Bool
4101078 , openStdin :: Bool
4111079 , stdinOnce :: Bool
4121080 , env :: [EnvVar]
413− , cmd :: Text
414− , argsEscaped :: Bool
1081+ , cmd :: [Text]
1082+ -- , argsEscaped :: Bool -- Don't see this in 1.24
4151083 , image :: Text
416− , volumes :: [Volume]
417− , workingDir :: FilePath
418− , entrypoint :: Text
1084+ , volumes :: Maybe Volumes
1085+ , workingDir :: Maybe FilePath
1086+ , entrypoint :: Maybe Text -- Can be null?
4191087 , networkDisabled :: Bool
420− , macAddress :: Text
421− , onBuild :: Text
422− , labels :: Labels
1088+ , macAddress :: Maybe Text
1089+ -- , onBuild :: Text -- For 1.24, only see this in the inspect response.
1090+ , labels :: Maybe Labels
4231091 , stopSignal :: Signal
424− } deriving (Eq, Show)
1092+ } deriving (Eq, Show, Generic)
1093+
1094+instance ToJSON ContainerConfig where
1095+ toJSON = genericToJSON defaultOptions {
1096+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
1097+
1098+instance FromJSON ContainerConfig where
1099+ parseJSON = genericParseJSON defaultOptions {
1100+ fieldLabelModifier = (\(x:xs) -> toUpper x : xs)}
1101+
1102+parseIntegerText :: (Monad m) =>Text -> m Integer
1103+parseIntegerText t = case readMaybe $ T.unpack t of
1104+ Nothing ->
1105+ fail "Could not parse Integer"
1106+ Just i ->
1107+ return i